赞
踩
字符指针是指向字符或字符串的指针,例如 char*。我们都知道数组名存放的是首元素的地址,而指针同理存放的是首字符的地址。但不同的是,字符型数组在初始化时会开辟新的内存块,用相同的常量字符串初始化不同数组也会开辟出不同的内存块。而字符指针是指向一个地址,也就是说,不论是多少个字符指针,只要你指向的是同一个常量字符串,那么实际你指向的内存地址是不会发生变化的,因为作为常量的字符串有着它独一份不会改变的地址。
下面是一道非常经典的例题:
#include <stdio.h> int main() { char str1[] = "hello world."; char str2[] = "hello world."; char *str3 = "hello world."; char *str4 = "hello world."; if (str1 == str2) { printf("str1 and str2 are same\n"); } else { printf("str1 and str2 are not same\n"); } if (str3 == str4) { printf("str3 and str4 are same\n"); } else { printf("str3 and str4 are not same\n"); } return 0; }
结果为
由于str1
和str2
是数组名,表示数组首元素的地址,而他们在初始化时开辟的是不同的内存块,所以地址不同;而str3
和 str4
是指针,指向的是首字符的地址,但作为字符串常量,它的地址是不变的,所以这两个字符指针所存放的地址是相同的。
指针数组是存放指针的数组。如int *arr[3]
,其中 [3] 的优先级高,确定它为一个数组,数组名为arr,类型为 int * 。
数组指针是指向数组的指针。如int (*p)[3]
, (*p)的优先级最高,确定它为一个指针,而指针指向的 [3] 是一个数组,数组类型为 int。
刚开始我们可能不太好分清楚指针数组和数组指针,他们的名字很相似,不免会使人混乱。那么如何分清指针数组与数组指针呢?答案就是优先级。我们可以将其拆分开来,最先定义优先级高的,一般判断出最高的优先级时,我们就能初步判定出它到底是数组还是指针,由此便可得出它的类型。
&数组名和数组名的区分 int arr[10]
我们都知道数组名表示数组首元素的地址,这在前面的字符数组中也提到过。例如 arr 表示 arr[10] 这个数组的首元素地址,即arr[1] 的地址。但当我们用 %p 去分别打印 arr 和 &arr 的地址时,会发现他们两个的地址是一样的。但他们的实际意义是相同的吗?我们可以通过下面一段代码来验证。
#include <stdio.h>
int main()
{
int arr[10] = { 0 };
printf("arr = %p\n", arr);
printf("&arr = %p\n", &arr);
printf("arr + 1 = %p\n", arr + 1);
printf("&arr + 1 = %p\n", &arr + 1);
return 0;
}
这段代码分别打印了 arr 、&arr 、arr+1 和 &arr+1 的地址。
结果为:
我们可以看到,当他们各自 +1 以后的结果不同,可知实际意义也有所不同。arr 与 arr+1 的差值为4,是一个int类型的大小,也就是跳过了一个元素。&arr 则表示的是数组的地址。数组的地址 +1 ,跳了整个数组的大小,而该数组有10个元素,每个元素都是int类型,所以 &arr+1 相对于 &arr 的差值刚好为40。由此可知,&arr 的类型为数组指针,他可以等同于 *int (p)[10] 。
函数指针是能够指向函数的指针,例如 void (*Fun) ()
。Fun 先和 * 结合,确定 Fun 为指针,而指针指向的是一个函数,指向的函数无参数,返回值类型为 void 。函数在调用时需要开辟栈帧,这时就会开辟出新的地址,而函数名就代表函数地址,函数指针则存储函数的地址。
在《c陷阱与缺陷》中,有这样两个代码:
(*(void (*)())0)();
这个代码看上去确实给人一种很混乱的感觉,毕竟有这么多的括号在内,但只要找到它的优先级,还是很好理解的。我们可以看到,其中中间的(void (*)())
作为一部分整体是在0的前面,也就是对0进行一个类型的转换,将0强转为一个地址,(*)的优先级最高,表明它是一个指针,而指针指向的是一个函数(),函数的返回值为void。所以它转换后的地址的类型就是“指向返回值为void的函数的指针”。而整体就是调用0地址处的函数。
再来看另外一个代码:
void (*signal(int , void(*)(int)))(int);
同样是一段看上去比较复杂的代码,首先signal
先与后面小括号结合,说明它是一个函数,它的参数是signal
后面括号内的int
和 void(*)(int)
。而除了signal函数的其他部分表示的是函数的返回值,void(*) (int)
,是一个函数指针。所以这个代码实际上就表示的是一个signal
函数,函数的参数为 (int , void(*)(int))
,函数的返回值是一个函数指针void(*) (int)
。当然这个函数有简化的方式:
typedef void(*pfun_t)(int);
pfun_t signal(int , pfun_t);
第一个语句通过 typedef
将 pfun_t
定义为函数指针的类型,而第二个语句就十分明了,signal
函数的参数为int
和 pfun_t
,返回值为 pfun_t
。
我们知道了函数指针是指向函数的指针,那函数指针数组也就是存放函数指针的数组。
例如 int (*parr[10])()
。首先它必须是一个数组,所以要先和中括号结合,拆分开来就是 parr[10]
,它的优先级最高确定它是一个数组。其次是 *
,表明指针,而指向的内容就是后面的函数 ()
,数组类型为 int
。
函数指针数组最大的用途:转移表。
#define _CRT_SECURE_NO_WARNINGS 1 #include <stdio.h> int Add(int x, int y) { return x + y; } int Sub(int x, int y) { return x - y; } int Mul(int x, int y) { return x * y; } int Div(int x, int y) { return x / y; } //以上为加减乘除四个函数 void menu() { printf("1.Add\n2.Sub\n3.Mul\n4.Div\n"); } int main() { int input = 0;//input用来做不同计算函数的选项 int a = 0; int b = 0; int(*Pfun[5])(int, int) = { 0, Add, Sub, Mul, Div }; //第一个元素为0,使元素下标1234分别代表加减乘除函数 do { menu(); printf("请输入菜单选项: "); scanf("%d", &input); if (input >= 1 && input <= 4) { printf("请输入两个操作数:"); scanf("%d %d", &a, &b); printf("%d\n", Pfun[input](a, b)); //Pfun[input](a, b)直接调用下标为input的函数 } } while (input); return 0; }
由以上代码我们可以看到,使用转移表会很大程度上去节省我们调用函数的空间,只需要通过函数指针数组访问元素下标地址去调用,而不用一一去调用,能让代码避免更加冗余。
在理解了函数指针数组之后,那么指向函数指针数组的指针也不难掌握。首先它是一个指针,指针指向一个数组,数组中的元素是函数指针。其实只要将每一层由内到外逐层分析理解,就不难分辨。
例如上文的函数指针数组int (*parr[10])()
,那么 我们再增加一个指向该数组的指针,也就是
int (*(*parr)[10])()
这是一个指向存放十个函数指针元素数组的指针。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。