目录
0 指针
0.1 分类
如上图
0.2 解析方法
0.3 数据类型
一维数组的数据类型为 char *,多维数组因为必须限制除第一维外所有的维度,故数据类型如下举例:
char array1[] = "a01";
char *p1 = array1;
char array2[][4] = {"a01", "a02", "a03"};
char (*p2)[4] = array2;
char array3[][3][4] = {
{"a01","a02","a03","a04"},
{"b01","b02","b03","b04"},
};
char (*p3)[3][4] = array3;
array1 的维度为:(4,) array1 和 p1 的数据类型为:char *
array2 的维度为:(3, 4) array2 和 p2 的数据类型为:char (*)[4]
array3 的维度为:(2, 3, 4) array3 和 p3 的数据类型为:char (**)[3][4]
而对于如下
char **p4 = &p1;
char (**p5)[4] = &p2;
char (**p6)[3][4] = &p3;
p4 的数据类型为:char **
p5 的数据类型为:char (**)[4]
p6 的数据类型为:char (**)[3][4]
char **p7 = &p5;
char (**p8)[4] = &p6;
char (**p9)[3][4] = &p7;
p7 的数据类型为:char ***
p8 的数据类型为:char (***)[4]
p9 的数据类型为:char (***)[3][4]
0.4 普通指针
对于最普通的指针,如下图所示
举例
char v = 'a';
char *p = &v;
char *p = “a01”;
参数传递
p 是指针,其数据类型为 char *,需要 char ** 来接收
char *p;
char **rec = &p;
1 多级指针
(1) char **p
char c = 'a';
char *p1 = &c;
char **p = &p1;
参数传递
p 是指针,其数据类型为 char **,需要 char *** 来接收
char **p;
char ***rec = &p;
(2) char ***p
同理
2 指针数组
元素为指针的数组
p 本身是数组,而数组内部的元素是指针
先定义如下:
char c1 = 'a', c2 = 'b', c3 = 'c', c4 = 'd', c5 = 'e', c6 = 'f';
char *pc1 = &c1, *pc2 = &c2, *pc3 = &c3, *pc4 = &c4, *pc5 = &c5, *pc6 = &c6;
(1) char *p[]
char *p[] = {&c1, &c2, &c3};
重申一下!p 只是个数组(p表示数组的首地址),不能给 p 赋值(也不能指向任何东西)
比如,下面这样是错误的:
char array2[][4]= {&c1, &c2, &c3}; char *p[] = array; // Error,p是数组(p表示数组的首地址),不能被赋值
但是这样可以(数组里面的元素是指针):
char array1[] = "a01"; char *p[3]; p[0] = array1; puts(p[0]); // “a01”
如果需要传递多维数组,请使用数组指针
参数传递
对于上述 p ,首先 p 是一维数组,同时因为 p 中的元素为也就是 p[0] 的数据类型为 char * ,那么 p 的数据类型即为 char **
同时因为 p 自身是数组,所以 rec 的数据类型需与 p 一致,也必须为 char **
char *p[] = {&c1, &c2, &c3};
char **rec = p;
(2) char *p[][3]
n行3列的指针数组
p 是一个二维数组,维度为 (n x 3),数组中每个元素为 char 型
注:多维数组在定义时,列数必须确定
以2行3列举例:
char *p[][3] = {
{&c1, &c2, &c3},
{&c4, &c5, &c6},
};
参数传递
(1) 对于上述 p ,首先 p 是二维数组,同时因为 p 中的元素为也就是 p[0][0] 的数据类型为 char * ,那么 p[0] 的数据类型即为 char **,而这些都是对于一维数组来说的(不需要维度约束),而对于 p ,因为是二维数组(需要进行维度约束),且数组中每一个元素也就是p[0][0]为 char *,所以需要将本来的 char *** 转化为 char * (*)[3]。
太麻烦了,换一种方式
(2) 对于上述 p ,首先 p 是一个二维数组,且维度为 (2 x 3),所以有 char (*)[3],又因为二维数组内的元素为 char *,所以 p 的数据类型为 char * (*)[3]
同时因为 p 自身是数组,所以 rec 的数据类型也必须为 char * (*)[3]
char *p[][3] = {
{&c1, &c2, &c3},
{&c4, &c5, &c6},
};
char * (*rec)[3] = p;
(3) char **p[][3]
原理同上
char **p[][3] = {
{&pc1, &pc2, &pc3},
{&pc4, &pc5, &pc6},
};
参数传递
对于上述 p ,首先 p 是一个二维数组,且维度为 (2 x 3),所以有 char (*)[3],又因为二维数组内的元素为 char **,所以 p 的数据类型为 char ** (*)[3]
同时因为 p 自身是数组,所以 rec 的数据类型也必须为 char ** (*)[3]
【补充】:
p[0] 的数据类型:char *** (p[0] 为一维数组,不需要维度约束)
p[0][0] 的数据类型:char **
p[0][0][0] 的数据类型:char *
char **p[][3] = {
{&pc1, &pc2, &pc3},
{&pc4, &pc5, &pc6},
};
char ** (*rec)[3] = p;
3 数组指针
指向数组的指针
不过这种概念正常只用于维度 >= 2 的数组
(1) char (*p)[4]
p是一个指针,指向一个二维数组,该二维数组的维度为:(n x 4),二维数组的元素为 char 型变量。
举例,n == 3
【注】:此处 [ ] 内的数字不能省略,必须确定除最外侧的所有内侧的维度。 eg: char (*p)[1][2][3] <—— 都必须指定
char array2[][4] = {"a01", // (3 x 4)
"a02",
"a03"};
char (*p)[4] = array2;
或
char (*p)[4];
p = array2;
【注】:p 是一个指针,一次只能被赋予一个元素,像下面这样就是错误的:
char (*p)[4] = {"a01", "a02", "a03"}; // Error
参数传递
对于上述 p ,因为 p 本身就是指针,所以直接去掉变量名即可得到数据类型:char (*)[4]
同时因为 p 自身是指针,所以 rec 的数据类型必须为 char (**)[4]
char (**rec)[4] = &p;
补充:*p 的数据类型为:char *
(2) char (*p)[3][4]
p 是一个指针,指向一个三维数组,该三维数组的维度为:(n x 3 x 4),数组中的元素为 char 型
举例,n == 2
char array3[][3][4] = { // (2 x 3 x 4)
{"a01", "a02", "a03"},
{"b01", "b02", "b03"},
};
char (*p)[3][4] = array3;
参数传递
对于上述 p ,因为 p 本身就是指针,所以直接去掉变量名即可得到数据类型:char (*)[3][4]
同时因为 p 自身是指针,所以 rec 的数据类型必须为 char (**)[3][4]
char (**rec)[3][4] = &p;
补充:
*p 的数据类型为:char (*)[4]
*(*p) 的数据类型为:char *
(3) char * (*p)[3]
p是一个指针,指向一个二维数组,该二维数组的维度为:(n x 3),二维数组的元素为指针。
举例,n == 2
char *array4[][3] = { // (2 x 3 x 4)
{&c1, &c2, &c3},
{&c4, &c5, &c6},
};
char * (*p)[3] = array4;
参数传递
对于上述 p ,因为 p 本身就是指针,所以直接去掉变量名即可得到数据类型:char * (*)[3]
同时因为 p 自身是指针,所以 rec 的数据类型必须为 char * (**)[4]
char * (**rec)[3] = &p;
补充:
*p 的数据类型为:char **
*(*p) 的数据类型为:char *
(4) char (**p)[4]
指向 [ 指向数组的指针 ] 的指针
p 是一个指针,其所指的对象为一个指针,而该指针指向一个二维数组,该二维数组的维度为:(n x 4),二维数组的元素为 char 型变量。
举例,该二维数组维度为:(3 x 4)
char array2[][4] = {"a01", // (3 x 4)
"a02",
"a03"};
char (*t)[4] = array2;
char (**p)[4] = &t;
参数传递
对于上述 p ,因为 p 本身就是指针,所以直接去掉变量名即可得到数据类型:char (**)[4]
同时因为 p 自身是指针,所以 rec 的数据类型必须为 char (***)[4]
char (***rec)[4] = &p;
补充:
*p 的数据类型为:char (*)[4]
*(*p) 的数据类型为:char *
4 数组指针数组
元素为 [ 指向数组的指针 ] 的数组
(1) char (*p[])[4]
p是一个数组,其中每个元素等于 3(1)
换句话说,像这样的 p:char (*p)[4],给我多来几个
比如来俩
char array21[][4] = {"a01", "a02", "a03"}; // (3 x 4)
char array22[][4] = {"b01", "b02", "b03"}; // (3 x 4)
char (*p[])[4] = {array21, array22};
或
char (*p[2])[4];
p[0] = array21;
p[1] = array22;
【注】:与3(1)同理,下面这样是错误的:
char (*p[])[4] = { // Error {"a01", "a02", "a03"}, {"b01", "b02", "b03"}, };
参数传递
对于上述 p ,因为 p 中元素为二维数组,且维度为 (3 x 4) 也就是说 p[0] 的数据类型为 char (*)[4] ,那么 p 的数据类型即为 char (**)[4]
同时因为 p 自身是数组,所以 rec 的数据类型也必须为 char (**)[4]
char (**rec)[4] = p;
补充:
p[0] 的数据类型:char (*)[4]
p[0][0] 的数据类型:char *
p[0][0][0] 的数据类型:char
(2) char (*p[])[3][4]
p是一个数组,其中每个元素等于 3(2)
换句话说,像这样的 p:char (*p)[3][4],给我多来几个
比如来俩
char array31[][3][4] = { // (2 x 3 x 4)
{"a01", "a02", "a03"},
{"b01", "b02", "b03"},
};
char array32[][3][4] = { // (2 x 3 x 4)
{"c01", "c02", "c03"},
{"d01", "d02", "d03"},
};
char (*p[])[3][4] = {array31, array32};
参数传递
对于上述 p ,因为 p 中元素为三维数组,且维度为 (2 x 3 x 4) 也就是说 p[0] 的数据类型为 char (*)[3][4] ,那么 p 的数据类型即为 char (**)[3][4]
同时因为 p 自身是数组,所以 rec 的数据类型也必须为 char (**)[3][4]
char (**rec)[3][4] = p;
补充:
p[0] 的数据类型:char (*)[4]
p[0][0] 的数据类型:char *
p[0][0][0] 的数据类型:char
(3) char * (*p[])[3][4]
同理,参考3(3)和4(2)
(4) char * (*p[][2])[3][4]
同理
char *array31[][3][4] = { // (2 x 3 x 4)
{{&c1, &c2, &c3, &c4}, {&c1, &c2, &c3, &c4}, {&c1, &c2, &c3, &c4}},
{{&c1, &c2, &c3, &c4}, {&c1, &c2, &c3, &c4}, {&c1, &c2, &c3, &c4}},
};
char *array32[][3][4] = { // (2 x 3 x 4)
{{&c1, &c2, &c3, &c4}, {&c1, &c2, &c3, &c4}, {&c1, &c2, &c3, &c4}},
{{&c1, &c2, &c3, &c4}, {&c1, &c2, &c3, &c4}, {&c1, &c2, &c3, &c4}},
};
char *array33[][3][4] = { // (2 x 3 x 4)
{{&c1, &c2, &c3, &c4}, {&c1, &c2, &c3, &c4}, {&c1, &c2, &c3, &c4}},
{{&c1, &c2, &c3, &c4}, {&c1, &c2, &c3, &c4}, {&c1, &c2, &c3, &c4}},
};
char *array34[][3][4] = { // (2 x 3 x 4)
{{&c1, &c2, &c3, &c4}, {&c1, &c2, &c3, &c4}, {&c1, &c2, &c3, &c4}},
{{&c1, &c2, &c3, &c4}, {&c1, &c2, &c3, &c4}, {&c1, &c2, &c3, &c4}},
};
char * (*p[][2])[3][4] = {
{array31, array32},
{array33, array34},
};
参数传递
对于上述 p ,因为 p 中元素为三维数组,且维度为 (2 x 3 x 4),故 p[0][0] 的数据类型应为 char (*)[3][4],又因为该三维数组的子元素也就是 p[0][0][0][0][0] 的数据类型为 char *,故 p[0][0] 的数据类型应为 char * (*)[3][4]
又因为 p 自身是二维数组,则 p[0] 的数据类型为:char * (**)[3][4],故 p 的数据类型为 char * (* (*)[2])[3][4]
所以 rec 的数据类型也必须为 char * (* (*)[2])[3][4]
char (*(*rec)[2])[3][4] = p;
补充:
p[0] 的数据类型:char * (**)[3][4]
p[0][0] 的数据类型:char * (*)[3][4]
p[0][0][0] 的数据类型:char * (*)[4]
p[0][0][0][0] 的数据类型:char **
p[0][0][0][0][0] 的数据类型:char *
5 指针数组指针
参考第 2 章中的参数传递,接收变量 rec 皆为指针数组指针
6 函数指针
指向函数的指针
定义:函数返回值类型 (* 指针变量名) (函数参数列表);
如:char (*pfun)(int);
定义了一个指针变量 pfun,该指针变量可以指向返回值类型为 char 型,且有一个整型参数的函数。
pfun 的数据类型为: int(*)(int)
char fun(int x)
{
return x % 256;
}
char (*pfun)(int); // 声明
pfun = fun;
(*pfun)(1234); // 调用
或使用 typedef 声明
typedef char (*pFUN)(int); // 声明数据类型
pFUN pfun;
pfun = fun;
(*pfun)(1234); // 调用
参数传递
因为 pfun 的数据类型为: char (*)(int,int),且 pfun 为指针,所以 rec 的数据类型必须为:char (**)(int,int)
char (**rec)(int, int) = &pfun;
函数指针数组
例如:char (*pfun[])(int)
像这样的函数指针 pfun:char (*pfun)(int),给我多来几个
比如来俩
char fun1(int x)
{
return x % 256;
}
char fun2(int y)
{
return y % 256;
}
char (*pfun[])(int) = {fun1, fun2};
或
char (*pfun[])(int);
pfun = {fun1, fun2};
返回类型为函数指针的函数
return_type(*function(func_parameter_list))(parameter_list)
例如:char (*fun(int))(int)
函数 fun(int) 的返回值为一个函数指针,数据类型为: char (*)(int)
char (*fun3(int x))(int)
{
if(x > 2)
return fun1;
else
return fun2;
}
7 指针函数
返回值为指针的函数
如:char * fun(int, int);
8 练习
说了这么多,举几个了例子练习一下
(1) 有一次,一个程序员与我交谈一个问题。他当时正在编写一个独立运行于某种微处理器上的C程序。当计算机启动时,硬件将调用首地址为 0 位置的子例程。为了模拟开机启动的情形,我们必须设计出一个C语句,以显示调用该子例程。(节选自《C陷阱与缺陷》)
(*0)()
(void (*)())0
(*(void (*)())0)()
typedef void (*pfun)();
(*(pfun)0)();
(2) 在信号处理头文件 signal.h 中有一个库函数 signal,该函数接收两个参数,一个代表设置处理功能的信号值(整型),另一个是对应信号的处理函数(函数指针)(节选自《C陷阱与缺陷》)
函数声明为:
void (*signal(int sig, void (*handler)(int)))(int)
signal后面紧跟括号代表其并非函数指针,而是一个函数,参数列表为 ( int sig, void (*handler)(int) ),第一个参数为 int 型,第二个参数为函数指针: void (*handler)(int)
然后结合 void (* 函数signal )(int),可知,函数 signal 的返回值是一个函数指针,数据类型为:void (*)(int) (该数据类型与 signla 的第二个参数的数据类型相同)
可使用 typedef 对该声明进行简化(选自 glibc 中 signal 源码)
typedef void (*__sighandler_t)(int);
__sighandler_t signal (int sig, __sighandler_t handler)
参考
《C专家编程》
《C陷阱与缺陷》