🦴前言
指针是我们内存中最小单元的地址编号,因此指针能储存我们程序中各种变量在内存中的对应地址(&取出地址放入指针变量内,使用时通过*解引用访问操作即可),指针使用起来高效迅速,能够用一个指针变量记住复杂变量的地址,然后对其进行远程操作;但因为指针代表的是计算中的底层地址,不容易观察,因此很多人认为指针很难,根本学不懂,其实没那么夸张,指针不过是地址,是我们用来辅助完成程序设计的工具而已。下面让我带大家进入指针的世界!🎉🎉🎉
🦴正文
📍指针与指针类型
🪡指针类型
指针跟数据一样也有自己的类型,比如整型指针、字符型指针、结构体指针等,不同的变量类型最好对应使用不同的指针类型(特殊情况除外)
我们知道不同数据类型大小不相同,比如 int 是4字节,char 是1字节,那么对应的指针变量大小是否也是如此呢?
答案是否,指针是个很公平的东西,无论上面类型的指针,大小都是4字节(64位平台下是8字节),下面看我用 sizeof 证明这一特点:
🪡指针 + - 整数
既然指针大小都是一样的,那为什么还要区分类型呢?相信这是学习指针时大多数同学的疑惑点,既然C语言是经过巧妙设计完善的,所以指针类型存在肯定有它的作用,比如下面这段代码:
//指针类型-移动步长 int main() { int arr[10] = { 1,2,3,4,5,6,7,8,9,10 }; int* pa = arr; char* pb = arr; pa++; pb++; printf("数组首元素地址:%p\n\n", &arr[0]); printf("整型指针+1:%p\n\n", pa); printf("字符型指针+1:%p\n\n", pb); printf("pa加1后:%d\n",*pa); printf("pb加1后:%d\n",*pb); return 0; }
🪡不同指针类型解引用
既然不同的类型的指针移动距离不同,那么解引用权限是否也不相同呢?
//指针类型-解引用权限 int main() { int a = 0x11223344; int b = 0x11223344; int* pa = &a; char* pb = &b; *pa = 0; *pb = 0; printf("%d %d\n", a, b); return 0; }
🪡小结
指针类型有它存在的意义:
1.指针类型决定了指针移动的步长(大小是字节)
2.指针类型决定了指针解引用的权限有多大(大小同样是字节)
📍野指针
俗话说能力越大,责任越大。既然指针这么强大,那么在使用它时肯定要谨慎,避免产生野指针(指向位置不可知,即指针与变量间的强绑定关系不可控),野指针可能会内存泄露的危害(如果野指针指向某个变量,并且野指针没被销毁,那么在使用指针的过程中可能误使用到野指针,从而导致指向变量内存的改变)
🪡野指针的成因
1.指针未初始化
2.指针越界访问
3.指针指向空间被释放
🪡如何避免野指针
在使用指针时一定要小心谨慎,其次注意以下五点:
- 1.指针在创建时最好初始化
- 2.指针在使用时要观察是否会出现越界现象
- 3.当指针指向的原空间被释放后,指针要及时置空(赋NULL)
- 4.避免指针指向局部变量的地址
- 5.指针在使用前检查有效性(类型是否匹配)
📍指针运算
指针与整数、指针间都有运算,比如前面提到了的指针 + - 整数得出不同指针类型的移动步长和解引用权限不同,其实指针 + - 整数在数组身上能起到代替下标寻找元素的作用。
🪡指针 + - 整数
//指针 + - 整数 int main() { int arr[5] = { 1,2,3,4,5 }; int* pa = arr; int i = 0; for (i = 0; i < 5; i++) { printf("%d ", *(pa + i)); } return 0; }
🪡指针 - 指针
指针 - 指针得到的是两个指针间的元素个数,就像日期 - 日期得到的是中间的天数一个道理。不过没有指针 + 指针这一说法,这样是没有意义的。
指针间的关系运算
指针在使用时需要注意一些细节,比如在使用指针遍历数组时,最好从前往后遍历,不推荐从后往前遍历,因为这是标准未定义的,盲目使用可能会造成bug。
标准规定:
允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与 指向第一个元素之前的那个内存位置的指针进行比较。
//指针间的关系运算 int main() { int arr[5] = { 1,2,3,4,5 }; int* pa = arr; for (; pa <= &arr[4]; pa++) { printf("%d ", *pa); } return 0; }
📍指针与数组
指针与数组间有着密不可分的关系,比如指针 + - 整数能够像数组下标一样直接访问元素,此外数组名还能作为首元素地址直接被指针指向。
数组名就是首元素地址,两种情况除外:
1.sizeof 计算数组大小时,数组名表示整个数组,计算结果即整个数组的大小(元素数量*元素大小),单位是字节
2.&数组名时,取出的是整个数组的地址,进行指针运算时,将直接跨过整个数组
//指针与数组 int main() { int arr[5] = { 1,2,3,4,5 }; int* pa = arr; int i = 0; for (i = 0; i < 5; i++) { printf("arr[%d] %p<==>%p pa+%d\n", i, &arr[i], pa + i, i); } return 0; }
📍二级指针
一级指针用来指向变量地址,二级指针则是指向一级指针的地址,二级指针也能通过解引用两次的方式远程控制变量。一颗 * 为一级指针,两颗 * 就是二级指针,依次类推可以有 n 级指针,指针指向层数越多,程序就越难理解,我们在写程序时都是一、二级指针用的多。
//二级指针 int main() { int a = 10; int* pa = &a; int** ppa = &pa; **ppa = 20; printf("初始变量a %d\n\n", a); printf("一级指针pa %d\n\n", *pa); printf("二级指针ppa %d\n\n", **ppa); return 0; }
指针数组
指针数组是存储指针的数组,本质上仍是数组,不过因为数组中存储的是元素地址,因此在使用指针对其中的元素进行访问时,需要使用二级指针。
//指针数组 int main() { int* arr[5] = { 0 }; int** pa = arr; int i = 0; for (i = 0; i < 5; i++) { arr[i] = &i; printf("%d ", **pa); } return 0; }
🦴总结
从指针是什么到指针数组的,指针初阶的内容至此我们就介绍完了。现在我们可以开始愉快的使用指针变量了,记住不要造出野指针,也不要让指针的指向关系过于复杂,避免我们在学习指针的过程中出现从入门到放弃的情况。
如果你觉得本文写的还不错的话,期待留下一个小小的赞👍,你的支持是我分享的最大动力!
如果本文有不足或错误的地方,随时欢迎指出,我会在第一时间改正!