指针浅析

本文详细探讨了C语言中的指针,包括指针的分类、解析方法、数据类型及其应用,如普通指针、多级指针、指针数组、数组指针、数组指针数组、指针数组指针、函数指针和指针函数。通过实例分析了各种指针类型的参数传递方式,是理解C语言指针的重要参考资料。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

0 指针

0.1 分类

0.2 解析方法

0.3 数据类型

0.4 普通指针

参数传递

1 多级指针

(1) char **p

参数传递

(2) char ***p

2 指针数组

(1) char *p[]

参数传递

(2) char *p[][3]

参数传递

(3) char **p[][3]

参数传递

3 数组指针

(1) char (*p)[4]

参数传递

(2) char (*p)[3][4]

参数传递

(3) char * (*p)[3]

参数传递

(4) char (**p)[4]

参数传递

4 数组指针数组

(1) char (*p[])[4]

参数传递

(2) char (*p[])[3][4]

参数传递

(3) char * (*p[])[3][4]

(4) char * (*p[][2])[3][4]

参数传递

5 指针数组指针

6 函数指针

参数传递

7 指针函数

参考


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位置的子例程” ,如果直接:
 
(*0)()
这样做并没有什么用,因为运算符 * 只能对指针进行操作,那么首先需要将 0 转换为一个函数地址,也就是数据类型为函数指针,返回值为void的函数指针的数据类型为:void (*)(),那么对 0 进行数据类型转换:
 
(void (*)())0
然后对其进行调用:
 
(*(void (*)())0)()
更清晰的写法是使用 typedef 来声明
 
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陷阱与缺陷》

Signal ()函数用法和总结

signal源码

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值