本文深入剖析数组指针、函数指针、以及函数、数组和指针的深度结合,最后一针见血地指出多级指针的本质。
数组指针
基本概念
什么是数组指针?
int (*pa)[3];
pa就是一个数组指针。首先它是一个指针,它指向一个数组,这个数组里存放了3个int型数字
再通过以下代码来看一些基本概念:
int a[3];
int * p = a;
p = &a;
int (*pa)[3] = &a;
int size = sizeof(pa);
a[i]
然后看一个程序:
#include <stdlib.h>
#include <stdio.h>
int main()
{
int a1[3];
int (*p)[3] = &a1;
printf("sizeof(p) = %ld\n", sizeof(p));
printf("sizeof(&a1) = %ld\n", sizeof(&a1));
printf("sizeof(a1) = %ld\n", sizeof(a1));
int a2[2][2];
printf("sizeof(a2) = %ld\n", sizeof(a2));
return 0;
}
总结一下上面的几个重要知识点:
- 地址即指针
- 数组名即数组
- 数组名等同于该数组首元素的地址
- 对数组名取地址后所得到的就是一个数组指针
- 方括号运算符就是方括号左边的变量(可视为基本地址)加偏移后再解引用(deference)
- 数组与指针其实不同
数组指针与二维数组
假设有一个二维数组 int a[2][3] = {{1,2,3}, {4,5,6}}; 以下来解释每一项的含义:
a : 这是二维数组名&a : 这是一个数组指针,它指向一个二维数组a[0] : 这是一维数组的数组名&a[0] : 对一维数组的数组名取地址,因此这是一个数组指针,指向一个一维数组&a[0][0] : 对int型数组中的一个元素取地址,因此这是一个int *类型的指针
那么以上这5项的在数值上是一样吗? 是一样的。但若进行 +1 操作,则所增加的字节数则可能完全不同。
下面给出一个较为全面的示例程序:
#include <cstdlib>
#include <cstdio>
#include <typeinfo>
int main()
{
int a[2][3] = {{1,2,3}, {4,5,6}};
int (*p0)[3] = NULL;
int (*p1)[3] = NULL;
p0 = &a[0];
p1 = &a[1];
int(*pp)[2][3] = &a;
printf("a = %p\n", a);
printf("&a = %p\n", &a);
printf("pp = %p\n", pp);
printf("a[0] = %p\n", a[0]);
printf("&a[0] = %p\n", &a[0]);
printf("&a[0][0] = %p\n", &a[0][0]);
printf("p0 = %p\n", p0);
printf("typeid(a): %s\n", typeid(a).name());
printf("typeid(&a): %s\n", typeid(&a).name());
printf("typeid(pp): %s\n", typeid(pp).name());
printf("typeid(a[0]): %s\n", typeid(a[0]).name());
printf("typeid(&a[0]): %s\n", typeid(&a[0]).name());
printf("typeid(p0): %s\n", typeid(p0).name());
printf("typeid(&a[0][0]): %s\n", typeid(&a[0][0]).name());
printf("0: %d %d %d \n", *(*p0 + 0), *(a[0] + 1), *(*p0 + 2));
printf("1: %d %d %d \n", *(*p1 + 0), *(a[1] + 1), *(*p1 + 2));
return 0;
}
其输出为:
a = 0x7ffca5e4ca90
&a = 0x7ffca5e4ca90
pp = 0x7ffca5e4ca90
a[0] = 0x7ffca5e4ca90
&a[0] = 0x7ffca5e4ca90
&a[0][0] = 0x7ffca5e4ca90
p0 = 0x7ffca5e4ca90
typeid(a): A2_A3_i
typeid(&a): PA2_A3_i
typeid(pp): PA2_A3_i
typeid(a[0]): A3_i
typeid(&a[0]): PA3_i
typeid(p0): PA3_i
typeid(&a[0][0]): Pi
0: 1 2 3
1: 4 5 6
若 pp + 1, 则得到的新地址比 pp 增大了 2*3*4 = 24 字节 若 p0 + 1, 则得到的新地址比 p0 增大了 3*4 = 12 字节
另外,在C语言中,无法将一个数组作为一个函数的参数来直接传递。如果将数组名作为一个函数的参数,那么数组名将立刻被转换为指向该数组首元素的指针。 因此,若传递的是一个二维数组名,比如上述的a ,则实际传递的是 &a[0] , 而因为a[0] 是一维数组,所以&a[0] 是数组指针,即 int (*) [3] 类型。
函数指针
函数指针的typedef 写法,和函数指针的声明基本一致。即在函数声明前面加一个typedef 即可。
typedef void (*fp)();
以后就可以用 fp fp1, fp2; 这样的语句来定义函数指针了。
示例程序:
typedef void (*fp)();
void func() {
cout << "My func" << endl;
}
fp fp1 = func;
fp1();
(*fp1)();
如果一个函数返回函数指针,会是怎么样的呢?经典的例子是signal函数。 详情请见这篇博文《返回函数指针的函数与signal函数原型分析》.
最后看这条语句是什么意思呢:
int (*p[4])(int, int);
先分析中间部分,标识符p先与中括号结合(优先级高),所以p是一个数组,该数组中有4个元素;然后剩下的内容(即 int (*)(int, int) )表示一个函数指针,那么就说明数组中的元素类型是函数指针; 所以上面这条语句表示: p是一个数组,该数组中保存了4个函数指针。
深入理解函数、数组与指针的结合
以下是规则允许的
-
函数的返回值允许是一个函数指针,如 int(*fun())(); 解释: 去掉fun以及后面的一对小括号,即 int(*)(); 这就是一个函数指针,而去掉的小括号代表函数,所以这是一个返回函数指针的函数。 -
函数的返回值允许是一个指向数组的指针,如 int (*foo())[]; 解释: 去掉foo以及后面的一对小括号,即 int (*)[]; 这就是一个数组指针,而去掉的小括号代表函数,所以这是一个返回数组指针的函数。 -
数组里面允许有函数指针,如 int (*foo[])(); 解释:去掉foo以及后面的一堆中括号,即 int (*)(); 这就是一个函数指针,而去掉的中括号代表数组,所以这是一个包含函数指针的数组。
现在回头看著名的signal函数的原型: void (*signal(int, void (*)(int)))(int); 使用以上的分析法, 首先,去掉signal以及后面的一对小括号,得到 void (*)(int); , 这就是一个函数指针; 其次,去掉的是 signal(int, void (*)(int)) , 这是一个函数,它拥有2个参数,第一个参数是int型,第二个参数也是一个函数指针; 综上,signal函数 是一个返回函数指针的函数,它拥有2个参数,第1个参数是int,第2个参数也是一个函数指针。
二级指针与多级指针
二级指针的存在只有一个意义,就是修改它所指向的一级指针。 比如一个函数的参数是一个二级指针,如 void func(int **pp); 那么这个 int ** pp 存在的意义就是func的调用者希望在func中去修改一个一级指针。如:
void func(int **pp)
{
...
*pp = some_int_ptr;
...
}
void caller()
{
int * pa = NULL;
func(&pa);
}
多级指针同理。 (完)
|