C语言中几种易混淆的指针用法
指针是C语言的精华,在使用上非常灵活,十分强大。但是同时,也非常容易出错。尤其是当指针和数组混合使用的时候,刚开始看的时候十分使人迷惑,再加上函数指针,一个简单的指针定义和声明都像在看天书。以下就是看一些开源代码时候遇到的指针的几种易混淆易出错的点。
数组和指针
char str[10]
上面定义了一个字符数组,str
存放的类型是char
型的变量。str
也是一个指针,指向数组第一个元素的内存地址。
1
2
3
4
5
6
7
8
9
|
#include<stdio.h>
int main(){
char str[10]="hello";
printf("%s\n",str);
printf("str[0]:%p\n",&str[0]);
printf("str:%p\n",str);
return 0;
}
|
以上代码为代码演示,str
本身是一个指针,但是str[0]
是一个数组中的元素,是一个char
类型的变量,所以如果要打印它的地址,需要取地址运算符&
。程序的输出不出意外,str[0]
的地址和str
的地址相同。
1
2
3
|
hello
str[0]:0x7ffd923ede1e
str:0x7ffd923ede1e
|
所以数组名的本质就是指向数组的第一个元素的指针。因此,C语言中通过数组第一个元素的指针来操作数组。
还有一种比较迷惑的事情就是char *s
。s
在这里是一个字符指针。,但是同时,可以通过这个字符指针来操作一个字符数组,也就是字符串,下面举个例子。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
#include<stdio.h>
int main(){
char *pStr;
char str[10]="hello";
pStr=str;
printf("string:%s\n",pStr);
printf("str[2]:%c\n",pStr[2]);
pStr="world";
printf("address:%p\n",&"world");
printf("pStr:%p\n",pStr);
printf("constant:%s\n",pStr);
return 0;
}
|
上面代码定义了一个字符数组(字符串),再定义一个字符指针指向数组第一个元素。到这里都不难理解,但是后面直接给这个指针赋值就很奇怪了。pStr
明明是一个指针,怎么能直接给他赋值为字符串呢。原因就在于这个字符串“hello”
,在编译时,编译器认为它是一个常量。所以这里并不是直接将字符串赋值给pStr
,而是让pStr
指向字符串常量的地址。程序的输出也验证了这一观点。
1
2
3
4
5
|
string:hello
str[2]:l
address:0x558e363b582a
pStr:0x558e363b582a
constant:world
|
指针数组和数组指针
下面辨析一下指针数组和数组指针。顾名思义,指针数组是一个数组,数组中存储的元素是指针。而数组指针是一个指针,它指向一个数组。
指针数组
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
#include<stdio.h>
int main(){
char *array[]={"java","C","C++","python"};
printf("常量地址:\n");
for(int i=0;i<4;++i)
printf("%p\n",array[i]);
printf("-------------------\n");
printf("指针地址\n");
for(int i=0;i<4;++i)
printf("%p\n",&array[i]);
printf("-------------------\n");
printf("%s\n",array[0]);
return 0;
}
|
根据上文,数组中的这几个字符串实质上是地址。所以要通过定义一个指针数组来访问它们。可以根据打印出来地址的大小判断以上的观点。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
常量地址:
0x5626c6fda884
0x5626c6fda889
0x5626c6fda88b
0x5626c6fda88f
-------------------
指针地址
0x7ffefc015360
0x7ffefc015368
0x7ffefc015370
0x7ffefc015378
-------------------
java
|
可以看到,字符串常量在是按顺序存放的,由于字符串后面要加一个\0
来表示字符结束,所以存储时会多占用一个字节。实验用的机器是64位,所以指针也是64位大小,可以看到指针的地址都是相差8个字节,也就是64位。
函数指针
函数指针
函数指针首先是一个指针,它指向的是函数的地址。可以通过以下语句来创建并使用函数指针。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
#include<stdio.h>
int func(int a,int b){
printf("a:%d b:%d a+b: %d\n",a,b,a+b);
return a+b;
}
int main(){
int (*f1)(int ,int )=func;
int (*f2)(int ,int )=&func;
printf("f1:%p f2:%p\n",f1,f2);
f1(2,2);
f2(2,2);
return 0;
}
|
上面定义了函数指针f1,f2
,这两种方式来给指针赋值都是允许的。分别打印出指针的值,应该是相同的内容,指向了函数的地址。可以直接通过函数指针来调用函数。
1
2
3
|
f1:0x55ab47c4564a f2:0x55ab47c4564a
a:2 b:2 a+b: 4
a:2 b:2 a+b: 4
|
要注意的是在定义函数指针时一定要括号,让*
号和指针名称结合。不然由于运算优先级的问题,定义的函数指针就会变成返回值为指针类型的函数。
参数为函数指针的函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
#include<stdio.h>
int fun1(int a,int b){
printf("a:%d b:%d a+b:%d\n",a,b,a+b);
return a+b;
}
int (*fun2(void))(int ,int ){
int (*pfun)(int ,int )=fun1;
return pfun;
}
int main(){
int (*pfun)(int ,int )=fun2();
pfun(2,2);
return 0;
}
输出结果:
a:2 b:2 a+b:4
|
以上定义了函数fun2
,它返回值类型是函数指针类型,指向fun1
函数,fun1
需要的函数参数是void
。在主函数中定义了函数指针pfun
指向fun2
函数的返回值,也就是fun1
函数的地址。