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 *ss在这里是一个字符指针。,但是同时,可以通过这个字符指针来操作一个字符数组,也就是字符串,下面举个例子。

 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函数的地址。