一个存在已久的谣言
源码
#include <stdio.h> int main() { int ar[10] = { 1,2,3,4,5,6,7,8,9,10 }; printf("&ar[0]=%p ",&ar[0]); printf("ar=%p ", ar); printf("&ar=%p ", ar); getchar(); return 0; }
运行结果:
根据运行结果,很多人就会得出“数组名就是首元素的地址”这样错误的结论。见代码
#include <stdio.h> int main() { int ar[10] = { 1,2,3,4,5,6,7,8,9,10 }; printf("&ar[0]=%p ",&ar[0]); printf("ar=%p ", ar); printf("&ar=%p ", ar); printf("sizeof(ar)=%d ", sizeof(ar)); printf("sizeof(&ar[0])=%d ", sizeof(&ar[0])); getchar(); return 0; }
运行结果:
如果 “数组名就是首元素的地址” 结论属实,那么数组名的大小就是一个指针的大小。事实上,数组名代表整个数组空间。
数组名(ar)本身的确是个地址,在数值上等于数组首元素取地址(&ar[0]),等于对数组名取地址(&ar)。数值上这三个数相等,那只是表象。其实质是地址背后指向内存空间的能力是不同的。
这里面还有个有趣的问题,就是数组作为形参会退化为指针。参考C++——数组形参退化为指针。这也就是为啥,数组作为形参的时候还要再多给一个数组长度参数。
数组指针、指针数组
数组名是地址,与数组首元素地址仅代表自己类型那么大内存不同,数组名内存指向能力非常强。数组名指向整个数组空间。进一步讲,对数组名取地址,即就是在对整个数组取地址,则数组的地址自然要用指向数组的指针才能接收,所以,必须定义指向数组的指针类型,即为数组指针。见代码
#include <stdio.h> int main() { int ar[10] = { 1,2,3,4,5,6,7,8,9,10 }; int *p = ar; /*下面是错误代码 int **p = &ar;*/ //正确写法 int(*pp)[10] = &ar; getchar(); return 0; }
int *p = ar; p的类型int *,p指向的类型int
int **p = &ar; p的类型int** ,p指向的类型int*
int ar[10]的类型,int[10]
int(*pp)[10] = &ar;pp的类型是 int (*)[10],pp指向int [10]类型
数组指针、指针数组重点在于后两个字。即数组指针本质上是指针,指针数组本质上是数组。这是字面上理解,代码角度怎么区分呢?
int ar[10] = { 1,2,3,4,5,6,7,8,9,10 }; int(*p1)[10] = &ar; //数组指针 int* p2[3]; //指针数组
变量(这里是p1,p2)与[ ]优先结合,所以int* p2[3];是指针数组,是一个数组。 要想变成指针,需要使用 ()强制优先结合指针。
总结:
数组指针
首先它是一个指针,它指向一个数组,在32位系统下永远是占4个字节,至于它所指向的数组占多少字节是不知道的,它是“指向数组的指针”简称
对于数组指针,强调的是指针的概念,只不过,指针的能力是用来指向数组类型的,并且其方括号中的数字一定,例如:int (*p)[10],p就是指向数组的指针,其中p指针规定了只能指向整形的数组,并且数组大小只能是10个整形空间,不能多也不能少,多之少之都会认为其指针的能力与指向的实体不符。
指针数组
指针数组,首先它是一个数组,数组的元素都是指针,数组占多少字节由数组本身决定,它是“储存指针的数组”的简称。
对于指针数组,强调的是数组的概念,只不过,数组所保存的类型是指针罢了,其地位跟普通的数组没有什么区别,都是数组,只不过是大家保存的类型不同而已,因此,我们美名其曰:保存指针的数组就称其为指针数组, 例如:int *p1[10]
指针数组最典型的例子就是main函数:int
main(
int
argc,
char
*argv[]) 第二个参数是一个字符串指针数组
#include <stdio.h> int main() { int ar[3] = { 1,2,3 }; //数组指针指向ar int (*p)[3] = &ar; //指针数组 int* q[3] = {&ar[0],&ar[1],&ar[2]}; char* pstr[] = { "Hello","halo","nihao" }; getchar(); return 0; }
函数指针、指针函数
函数指针
函数指针,首先它是一个指针,只不过,指针所指向的类型是函数,它是“指向函数的指针”的简称
#include <stdio.h> int Max(int a, int b) { printf("%d ", a > b ? a : b); return a > b ? a : b; } //函数指针 int(*pfun)(int, int); void main() { //情形1 Max(1, 2); //情形2 int(*pfun)(int, int); pfun = &Max; (*pfun)(1, 2); //情形3 pfun = Max; pfun(1, 2); getchar(); }
一般来说,我们调动函数往往是通过函数名来进行调动,例如情形1,由于指针强悍与无所不能,只要你能表示出来的,指针都可以想办法指向,因此,通过指针来调动函数就显得很自然了,我们把能够指向函数的指针称为函数指针,例如情形2,把Max函数的地址赋给了pfun函数指针,在调动时先取值,然后再调动函数,这是一种标准的做法,事实上,由于函数名就是函数的入口地址,本身也充当了地址,因此,我们可以简化程序,例如情形3,由于指针所指之物为函数,因此它的调动就行如直接运行函数类似了,但是,心里的清楚,情形3的做法实际是情形2的简写过程。
注意,一般指针都有其加1的能力,但是,函数指针不允许做这样的运算。即pfun+1是一个非法的操作。
指针函数
指针函数,首先它是一个函数,只不过,函数所返回的类型是指针类型,它是“返回指针类型的函数”的简称。 我们把返回指针类型的函数称其为指针函数,那就意味着只要返回值为指针,无论是什么类型的指针,都有资格称为指针函数
//指针函数,返回整形指针 int* fun(int a, int b) { return 0; }
像这种,函数fun,参数是(int a, int b),返回值是int* 。这种比较明显
返回函数指针的指针函数
先看一种错误的写法。对于VS2012以后的IDE,这种代码写得时候直接显示红色〰,根本编译不过
#include <stdio.h> int Max(int a, int b) { printf("%d ", a > b ? a : b); return a > b ? a : b; } //指针函数,返回函数指针。但是这种写法是错误的 int(*) (int,int) func(int a, int b, int(*FUN)(int, int)) { printf("max value=%d ", FUN(a, b)); return FUN; } void main() { func(1, 2, Max); getchar(); }
正确写法
#include <stdio.h> int Max(int a, int b) { printf("%d ", a > b ? a : b); return a > b ? a : b; } //函数指针 int(*pfun)(int, int); //func这个函数参数是(int a, int b, int(*FUN)(int, int)) //返回值是个指针,这个指针是int (*) (int, int)型函数指针 int(*func(int a, int b, int(*FUN)(int, int))) (int, int) { printf("max value=%d ", FUN(a, b)); return FUN; } void main() { func(1, 2, Max); getchar(); }
但是这种代码写出来太难理解了,可以使用typedef简化代码
#include <stdio.h> int Max(int a, int b) { printf("%d ", a > b ? a : b); return a > b ? a : b; } //将函数指针定义成类型 typedef int(*pfun)(int, int); //func这个函数参数是(int a, int b, int(*FUN)(int, int)) //返回值是个指针,这个指针是int (*) (int, int)型函数指针 //int(*func(int a, int b, int(*FUN)(int, int))) (int, int) pfun func(int a, int b, pfun FUN) { printf("max value=%d ", FUN(a, b)); return FUN; } void main() { func(1, 2, Max); getchar(); }