说白了就是国内那堆教材搞的。
这些教材硬要把指针拖到后面讲,讲数组时不讲指针,讲函数传参时不讲指针,硬是要拖到后面才来个指针大汇总。把指针和数组的天然关系割裂开。
这些教材喜欢故弄玄虚,说int *p[5]是指针数组,int (*p)[5]是数组指针,光提了个概念就没下文了,都不提这些概念的意义是什么。学生只是死记硬背,就算考了90+的高分,也不知道原来字符串按大小排序可以用指针数组,只排指针就行了,不用把字符串本身移来移去,节省大量时间和空间;也不知道原来int a[2][5],当a做右值时会退化成数组指针int (*)[5]。
这些教材喜欢把数组的“下标访问”和“指针访问”割裂开,让学生误以为是两种访问方式,却不知道本质上就没有什么下标访问,只有指针访问,下标只是个语法糖而已,即a[n]等价于*(a+n)。[]只在定义数组时有用,比如int a[5];访问时就没用了,不然为什么2[a]都可以?
这些教材喜欢把"传值"和“传址”说得绝对,好像传int就是传值,传int*就是传址。老师上课反复强调
void swap_int1(int a, int b){int c=a; a=b; b=c;}其实不能交换,因为是值传递;
void swap_int2(int *a, int *b){int c=*a; *a=*b; *b=c;}是地址传递,所以能交换。
那么,如果想交换两个int*型,写成
void swap_pint1(const int *a, const int *b){const int *c=a; a=b; b=c;}可以吗?为什么不可以?难道这不是传址吗?
一个负责任的书,或者一个负责任的老师,会说,“值”和“址”是相对的。一个int型变量,它有值也有址;一个int*型变量,它也有值有址,只是它的值是另一个int型变量的地址而已。所以要写成
void swap_pint2(const int **a, const int **b){const int *c=*a; *a=*b; *b=c;}才可以。这样二重指针很自然地就闪亮登场了。
这些教材对数组作函数参数传递时退化成指针的事实讳莫如深。老师上课反复强调
void swap_int1(int a, int b){int c=a; a=b; b=c;}其实不能交换,因为是值传递;但是
void swap_intarray(int a[]){int c=a[0]; a[0]=a[1]; a[1]=c;}却能交换a[0]和a[1]。难道这不是值传递吗?
负责任的书、负责任的老师,会说,数组在作函数参数传递时会退化成指针,从而丢失数组的长度信息。如在某x64系统下,int a[10];sizeof一下,用printf打出来,发现是40;做函数实参传过去再sizeof,成了8了——退化成了指针,从而丢失长度信息。你以为数组是复制了一个传过去,其实并非如此,实际上只是传了个指针而已,根本没复制数组。还会说,二维数组本质上是数组的数组,int a[2][5]会退化成int (*)[5]而不是int **。但是也可以令
int *p=(int *)a;
然后二维数组就被“展平”成了一维数组。p[7]就是a[1][2]。(因为1*5+2=7)要知道,在高级语言里,多维数组决不允许如此任人宰割。
指针玩灵活了,再做ASCII字符串处理,总觉得有了灵魂。比如
char *strcpy(char *dst, const char *src) { char *tdst = dst; assert(dst && src); while ((*tdst++ = *src++) != ' ') /* Nothing */; return dst; }
写这段代码时,大脑中就能感觉到两个指针在一同“跑腿”,是活的!用下标写,则感觉是死的:
char *strcpy(char *dst, const char *src) { assert(dst && src); for (int i=0; ; i++) { dst[i] = src[i]; if (' ' == dst[i]) break; } return dst; }