🚀前言
在阿辉上一篇博客指针的基础篇中我们了解到指针的一些基础知识
- 指针变量是用来存放地址的变量,通过指针可以找到所存地址指向的空间
- 指针变量的大小与平台有关,64位/32位平台大小为8字节/4个字节
- 指针变量的类型决定了指针变量所指向的内存空间的类型和大小以及指针加减整数时移动的字节数
- 指针的运算
- 多级指针
有了以上对于指针的基础了解,那么今天阿辉将为大家介绍C语言的指针部分,包括字符指针、数组指针、数组传参的本质以及函数指针,关注阿辉不迷路哦 😘 ,内容干货满满😋,接下来就跟着阿辉一起学习吧👊
🚀字符指针变量
在指针的类型中我们知道有⼀种指针类型为字符指针 char*
一般我们这么用:
int main() { char ch = 'w'; char *pc = &ch; *pc = 'w'; return 0; }
其实它还可以这么用:
int main() { char* pstr = "hi bro"; printf("%s\n", pstr); return 0; }
可能大家认为ptr里面存的是hi bro,实际上ptr里面存的是字符串的首元素地址也就是h的地址,这里大家有没有发现其实这和字符数组是一样的,比如char arr[] = “hi bro”数组名arr
也是首元素h
的地址,我们不妨把字符指针理解成字符数组,但真的这么简单吗?他们还是有两点不同,我们接着看👇
第一点不同:
我们可以看到当我们去改hi bro的内容时编译器直接报错,这是因为hi bro是常量字符串,而常量字符串被存储在程序的只读数据段(.rodata)中,这个数据段是只读的,意味着其中的数据在程序执行期间是不可修改的,而字符数组是可以修改的
第二点不同:
int main() { char* str1 = "hi bro"; char* str2 = "hi bro"; char arr1[] = "hi bro"; char arr2[] = "hi bro"; if (str1 == str2) printf("str1与str2空间相同\n"); else printf("str1与str2空间不相同\n"); if(arr1 == arr2) printf("arr1与arr2空间相同\n"); else printf("arr1与arr2空间不相同\n"); return 0; }
输出:
str1与str2空间相同 arr1与arr2空间不相同
其实很简单,这里str1
和str2
指向的是一个同一个常量字符串。C/C++会把常量字符串存储到单独的一个内存区域,当指针指向同一个字符串的时候,他们实际会指向同一块内存。但是用相同的常量字符串去初始化不同的数组的时候就会开辟出不同的内存块。所以arr1
和arr2
不同,str1
和str2
相同
🚀数组指针变量
在之前阿辉的数组篇中讲到关于数组名的理解,数组名是数组首元素的地址,而取地址数组名是整个数组的地址,那取地址数组名既然是地址那它应该存在什么类型的指针中呢?没错,就是数组指针
例如:
int arr[5] = { 0,1,2,3,4 }; int(*p)[5] = &arr; p是变量名 int(*)[5]是p变量的类型 *指的是p是指针 int[5]表示p指向的空间的类型是5个int类型变量的数组 []的优先级高于*,所以要把*p用括号()括起来表明p是指针 我们对p解引用,*p就是arr也就是首元素地址 我们对*p再次解引用,就访问到数组第一个元素**p等价于arr[0] 通过*(*p + i)我们就可以遍历数组arr[5]
那数组指针有何用?别急我们接着看👊
🚀数组传参的本质
✈️一维数组传参的本质
我们知道一维数组的数组名是首元素地址,当我们传一个数组给函数时,我们不会去在开辟一块同样的空间,而是通过首元素地址访问数组,这样有效的避免了空间的浪费
函数接收一维数组有三种方式,例如:
void test(int a[]); void test(int a[5]); void test(int* a);
写法确实有三种,不过前两种人模狗样的是数组(毕竟传数组用数组接受很容易理解)本质上还是指针,包括[5]
这个5
屁用没有
注意
我个人的一点总结:当一维数组名作为首元素地址传给函数后,函数内部接收地址的形参不再具有数组名的性质,仅仅是一个普通的指针
怎么理解呢,我们接着看👇
void test(int a[]) { &a; sizeof(a); } int main() { int a[5] = { 1,2,3,4,5 }; test(a); return 0; }
上图是x86环境下的调试结果,我们看到&a
类型是int * *
是二级指针而并非数组指针,sizeof(a)
的值是4,如果a
还具有数组名的特性,我们知道&a
将是int(*)[5]
类型的数组指针,而sizeof(a)
的值将是20。这恰恰说明了当一维数组名作为首元素地址传给函数后,函数内部接收地址的形参不再具有数组名的性质,仅仅是一个普通的指针
✈️二维数组传参的本质
void test(int arr[][4]); void test(int arr[3][4]); 行可以省略,列不能省略
那函数接收二维数组还有没有其他的方式?
二维数组的数组名怎么理解呢?二维数组可以理解为数组的数组,二维数组的每一行理解为二维数组的一个元素,二维数组的数组名同样是首元素地址,只不过二维数组的首元素是第一行,比如下图中二维数组arr[3][4]
首元素是绿色的那一行一维数组,所以二维数组的数组名arr
表示一个int(*)[4]
类型的数组指针
那么二维数组的传参就可以这么写void test(int (*arr)[4])
例子:
void test(int (*arr)[4]) { int i = 0; int j = 0; for (i = 0; i < 3; i++) { for (j = 0; j < 4; j++) { printf("%d ", *(*(arr + i) + j)); } printf("\n"); } } int main() { int arr[3][4] = { {1,2,3,4},{2,3,4,5},{3,4,5,6} }; test(arr); return 0; }
总结
- 一维数组的数组名是存放该数组首元素的一个指针,二维数组的数组名是存放该数组首元素的一个数组指针
- 一个数组int arr[5]={1,2,3,4,5},你可以用arr[1]和*(arr + 1)两种方式访问数组的了第二个元素,甚至可以1[arr]访问了解一下不建议使用。*(arr+i)等价于arr[i]
🚀函数指针变量
函数指针变量顾名思义是存放函数地址的指针,函数也有地址吗?
没错函数也有地址,函数的函数名就是函数的地址
我们来看一段代码👇
#include <stdio.h> void test() { printf("hehe\n"); } int main() { printf("test: %p\n", test); printf("&test: %p\n", &test); return 0; }
输出:
test:0xff44f68a &test:0xff44f68a
我们看到test
与&test
打印出来的地址是一样的,不仅打印出来的是一样的,它俩本质也是一样的,这俩等价,这个&
多少有点多余 😆
那函数指针如何创建呢?其实与数组指针类似
int add(int x, int y) { return x + y; } int main() { int(*p)(int, int) = add; p是函数指针变量名 *表示p是一个指针 int(*)(int,int)是p的类型 int(int,int)表示p所指向的空间是函数,函数的返回类型是int 而且有两个int类型的形参 return 0; }
那函数指针变量有什么用呢?
我们可以通过函数指针变量来调用函数,用上面的函数演示
int ret = (*p)(3,5); *P要用括号括起来,因为函数调用操作符的优先级更高
其实p
、*p
、add
以及&add
这四个等价
所以调用add
这个函数(*p)(3,5)
和p(3,5)
这俩都可以,取地址和解引用都挺多余😆
🚀指针变量是什么?
大家看到这个标题或许很懵,指针变量不就是用来存放地址的变量嘛,讲这么多了还问?
int add(int x, int y) { return x + y; } int main() { int(*padd)(int, int) = add; int a = 0; int* pa = &a; int arr[5] = { 1,2,3,4,5 }; int(*parr)[5] = &arr; return 0; }
上面这段代码中padd
是int( * )(in,int)类型的函数指针变量,pa
是int类型的指针变量,parr
是int( * )[5]类型的数组指针变量,其实add
也是int( * )(in,int)类型的函数指针变量,&a
也是int类型的指针变量,&arr
也是int( * )[5]类型的数组指针变量
地址是类似于
0xff40688a
这样的玩意,add、&a、&arr
里面存的不也是地址嘛,SO他们也是指针变量
到这里,阿辉今天对于C语言中一些特殊类型的指针的分享就结束了,希望这篇博客能让大家有所收获, 如果觉得阿辉写得不错的话,记得给个赞呗,你们的支持是我创作的最大动力🌹