c语言之指针、数组、函数之间的微妙联系

一、指针

指针就是一个存储着内存地址的变量,通过所存的地址可以找到相对应的变量单元,因此将地址形象化地成为“指针”,意思就是可以通过它找到以它为地址的内存单元。
为了加以理解我们用图来进行解析:
下面我们定义一个整形的指针变量p

int *p;

这里写图片描述

也就是说,指针是一个地址,而指针变量是存放地址的变量。


###指针使用的注意事项

  • 指针变量前面的*表示该变量为一个指针型变量
  • 在定义指针变量时必须指定指针的类型,一个指针变量的类型的含义包括以下两个方面:一是以存储单元编号表示的地址,另一个是它指向的存储单元的数据类型(如:int 、float、 char、 short、 long 、double等) ,指针的类型决定了在对指针进行解引用时,指针的访问权限,也就是说指针能访问几个字节。
  • 指针变量只能存放地址
  • *p表示对指针变量p进行解引用表示它指向的内存单元中存储的内容
#include<stdio.h>
#include<stdlib.h>
int main()
{
    int n = 10;
    char *pc = (char *)&n;
    int *p = &n;
    printf("%p\n", &n);
    printf("%p\n",pc);
    printf("%p\n", pc+1);
    printf("%p\n", p);
    printf("%p\n", p+1);
    system("pause");
    return 0;
}

这里写图片描述

  • 从上面的代码运行结果可以看出,指针变量的类型决定了指针变量在加减一个整数时向前或者向后移动的距离有多大。
  • 二级指针
int a=10;
int *p=&a;
int **pp=&p; 

怎样去理解这个二级指针**p呢?
int *pp=&p中,int 表示这个指针变量的类型为int *类型,*pp表示这个变量是一个一级指针,因此说pp为一个二级指针。一级指针p中存放的是变量a的地址,而一级指针变量p的地址存放在二级指针变量pp中,这样我们就可以对pp进行解引用从而找到p,然后再对p进行解引用从而进一步找到变量a。

二、指针和数组

数组名

  • 数组名代表整个数组时只有以下两种情况:
    1、sizeof(数组名),这里的数组名表示整个数组
    2、&数组名,这里的数组名表示整个数组
    除过以上两种情况之外,所有的数组名均降级表示首元素地址。
  • 指针和数组其实并没有任何关系,只是有相似的使用方式而已。但是我们可以通过指针对数组进行引用。

指针数组

什么是指针数组?

 char *arr[10];
 int *arr1[10];

如上所示,分别定义了一个字符型的指针数组和整型的指针数组。
指针数组本质上是一个数组,只不过数组的各个元素都是指针变量,char * 和int * 表示数组中的每个指针变量的类型。

数组指针

数组指针本质上还是一个指针,只不过它是一个存放数组地址的指针变量,也就是说数组指针可以指向一个数组。

 int arr[10]={0};
 int (*p)[10]=&arr;

上面所示的代码中,定义了一个数组指针p,p指向数组arr。其中p必须先和*结合表示p是一个指针变量,最后再和[]相结合表示一个数组指针。
在指针指向数组元素时,可以对指针进行一下的运算操作:

  • 加一个整数(用+或者+=),如p+1
  • 减一个整数(用- 或者- =),如p-1
    如果指针变量p已经指向数组中的一个元素时,则p+1指向该元素的下一个元素,p-1表示该元素的上一个元素。在执行 p+1或者p-1时,是加上或者减上该数组每个元素所占的字节数。

  • 自加运算,如p++,++p

  • 自减运算,如p–,–p
  • 对数组指针p进行p+1操作,然后在进行解引用*(p+i),表示指向数组中的第i个元素。
    在数组传参时可以用指针接受,举个例子吧!
 void test()
 {
 int arr[3][5]={0};
 print(arr);
 }
 //可以用以下几种方法调用
 void print(int arr[3][5])
 {}
 void print(int arr[][5])
 {}
 void print(int (*arr)[5])
 {}

指针和数组的定义与声明

定义是定义一个之前不存在的变量,而声明是让它知道这个定义的变量存在。
注意:声明和定义指的是同一块空间。
下面我们举个例子来具体了解一下:
test.c

char arr[] = "abcdef";
char *p = "abcdef";

main.c

#include<stdio.h>
#include<stdlib.h>
extern char arr[];//用extern进行声明
extern char *p;
int main()
{
    printf("%s\n", arr);
    printf("%s\n", p);
    system("pause");
    return 0;
}

这里输出的都是abcdef.
但是当我们将代码改成下面这样的时候会怎样?
test.c

char arr[] = "abcdef";

main.c

#include<stdio.h>
#include<stdlib.h>
extern char *arr;
int main()
{
    printf("%s\n", arr);
    system("pause");
    return 0;
}

当运行程序时,程序会崩溃。 这里为什么程序会崩溃呢?
我们在test.c中定义的是一个数组,但是我们在声明的时候却声明的是一个指针。arr在这里表示的是数组的首元素地址0,在声明的时候*arr是一个指针,因为指针的大小为4个字节,所以它只能访问数组arr的前四个字节,又因为字符在内存中是以它的ASCII码值存放的并且在内存中是以小端的存储方式存放的,所以指针arr中存放的内容为0X64636261。所以在输出arr是其实是访问0X64636261这块空间存储的内容,因为这个地址是非法的,所以无法访问程序会崩溃。
要想成功输出“abcdef”,我们可以进行如下修改:

 printf("%s\n",(char*)&arr);

接下来我们将代码再修改一下:

test.c

char *p = "abcdef";

main.c

#include<stdio.h>
#include<stdlib.h>
extern char p[];

int main()
{
    printf("%s\n", p);
    system("pause");
    return 0;
}

当程序运行后,输出的是一串随机的符号,在定义是将p定义为一个指针,指针的大小为4个字节,在声明时将p声明为一个数组,那么这个数组的大小也就是4个字节,将p声明为数组后,p表示的首元素地址(也就是定义的指针p的4个字节的地址),p中共有4个元素,当输出时从首元素地址所指向的内容开始向后输出4个字节,因为它判断不了这个地址到底是什么,所以输出的是随机值。
要想成功输出“abcdef”,我们可以进行如下修改:

printf("%s\n",*(char **)p);

通过以上的问题说明数组和指针是没有关系的,只不过可以通过指针来引用数组。

函数指针

函数名和&函数名一样,都表示的是函数的地址。要想将函数的地址存储起来,可以利用函数指针。
举个例子:

void test()
{
printf("hello kitty\n");
}
void (*pfun)();

这里的pfun必须先和*结合说明它是一个指针,在和函数操作符()结合说明它是一个指针函数。pfun前面的void表示所指向函数的返回类型,函数操作符()中的参数表示所指向函数的参数。
现在我们来看两个刺激的例子:

(*(void(*) ())0)();

这个例子很变态吧!让我们来仔细的分析一下它到底是个什么东西?

  1. 从void(*)()可以看出这是一个函数指针类型。这个函数没有参数,没有返回类型。
  2. (void(*) ())0)表示将0这个地址强制转换为一个函数指针的类型,将一个函数存放在首地址为0的内存中。
  3. (( void( ) ())0)表示对这个函数指针进行解引用,也就是取出从0地址开始的这段内存中的内容,这个内容就是这段内存中所存储的函数。
  4. ((void( ) ())0)()表示的是对这个函数进行调用。
 void(*signal(int ,void(*)(int)))(int);

让我们来仔细分析一下这个例子:

  1. singal是一个函数
  2. singal函数的参数有两个一个是int型,一个是函数指针类型,该函数指针所指向的函数有一个整型类型的参数,返回值为void
  3. singal函数的返回类型为一个函数指针类型,该指针指向的函数有一个整型参数,返回类型为void。

函数指针数组和函数指针的数组的指针

函数指针数组
函数指针数组是一个存放函数地址的数组。
举个例子吧!

int (*parr[10])();

parr先和[]结合,表示parr是一个数组,存储了10个是一个int (*)()类型的函数指针的元素。
函数指针数组的指针
这个读起来相当拗口吧!它究竟是一个什么东西呢?其实它本身的含义并没有读起来那么复杂。
函数指针数组的指针其实就是一个指针,只不过这个指针指向一个数组,这个数组中存放的都是指向函数的指针。
来举个例子吧!

char* (*(*pf)[3])(char *p);

这里的pf是一个指针,这个指针指向了存有3个元素的数组,而这个数组里面存放的元素是指向函数的指针,这些指针指向一些返回类型为char *,参数为一个字符指针。

猜你喜欢

转载自blog.csdn.net/liuwenjuan_cherry/article/details/80144937