【C语言】11. 深入理解指针1 【C语言】11. 深入理解指针1一、内存和地址1.1 内存宿舍楼类比1.2 编址原理二、指针变量和地址2.1 取地址操作符2.2 指针变量和解引用操作符*1指针变量 存放地址的变量2解引用操作符* 通过地址找到指向的对象2.3 指针变量的大小三、指针变量类型的意义3.1 指针的解引用权限不同3.2 指针±整数步长不同3.3 void* 指针泛型指针四、const 修饰指针4.1 const 修饰变量4.2 const修饰指针变量五、指针运算5.1 指针 ± 整数5.2 指针 - 指针5.3 指针的关系运算六、野指针6.1 野指针三大成因6.2 规避野指针四步走七、assert断言7.1 基本用法7.2 特点7.3 开启/关闭八、指针的使用和传址调用8.1 模拟实现 strlen8.2 传值调用 vs 传址调用Swap经典案例8.3 对比总结一、内存和地址1.1 内存宿舍楼类比我们知道计算机上CPU中央处理器在处理数据的时候需要的数据是在内存中读取的处理后的数据也会放回内存中那我们买电脑的时候电脑上内存是8GB/16GB/32GB等那这些内存空间如何⾼效的管理呢其实也是把内存划分为⼀个个的内存单元每个内存单元的大小取1个字节。现实世界计算机世界宿舍楼内存空间房间内存单元每个1字节门牌号地址编号找到房间CPU通过地址访问内存其中每个内存单元相当于一个学生宿舍一个字节空间里面能放8个比特位就好比同学们住的八人间每个人是一个比特位。每个内存单元也都有一个编号(这个编号就相当于宿舍房间的门牌号)有了这个内存单元的编号CPU就可以快速找到一个内存空间。生活中我们把门牌号也叫地址在计算机中我们把内存单元的编号也称为地址。C语言中给地址起了新的名学叫:指针。内存单元的编号 地址 指针每个内存单元1个字节里面存放8个比特位。1.2 编址原理CPU通过地址总线发送地址信号到内存32位机器32根地址线 → 2³²种地址 → 指针占4字节64位机器64根地址线 → 2⁶⁴种地址 → 指针占8字节硬件层面已完成编址设计是约定好的共识二、指针变量和地址2.1 取地址操作符inta10;a;// 取出a的地址printf(%p\n,a);// %p 打印地址整型变量a占4个字节a取的是首字节地址较小的那个地址知道首地址就能顺藤摸瓜访问全部4个字节2.2 指针变量和解引用操作符*1指针变量 存放地址的变量inta10;int*paa;// pa是指针变量存放a的地址如何拆解指针类型int*paa;// int * → 整体是指针类型// int → 指向的对象是int类型// * → 说明pa是指针变量char类型指针示例charchw;char*pcch;// char* 指向char类型2解引用操作符* 通过地址找到指向的对象inta100;int*paa;*pa0;// 等价于 a 0通过地址把a改成0 这给了我们修改a的另一种途径让代码更灵活。2.3 指针变量的大小printf(%zd\n,sizeof(char*));// 432位或864位printf(%zd\n,sizeof(int*));// 同上printf(%zd\n,sizeof(double*));// 同上⚠️ 结论指针变量的大小和类型无关只与系统位数有关同一平台下所有指针大小都相同。三、指针变量类型的意义既然指针大小都一样为什么还要分不同类型3.1 指针的解引用权限不同// 代码1int* 解引用intn0x11223344;int*pin;*pi0;// 把n的4个字节全部改为0// 代码2char* 解引用intn0x11223344;char*pc(char*)n;*pc0;// 只把n的第一个字节改为0 结论指针的类型决定了在对指针进行解引用时一次能访问几个字节。3.2 指针±整数步长不同intn10;char*pc(char*)n;int*pin;printf(%p\n,pc);// 假设 0x1000printf(%p\n,pc1);// 0x1001跳1字节printf(%p\n,pi);// 假设 0x1000printf(%p\n,pi1);// 0x1004跳4字节 结论指针的类型决定了指针向前或向后走一步有多大距离。3.3 void* 指针泛型指针inta10;void*pa;// void*:无具体类型的指针可以接收任意类型的地址局限性❌ 不能直接解引用*p → 报错❌ 不能直接±整数p1 → 报错用途 函数参数中使用接收不同类型数据的地址实现泛型编程效果。四、const 修饰指针4.1 const 修饰变量constintn0;n20;// ❌ 报错n不能被修改但可以通过指针绕过限制不推荐constintn0;int*pn;*p20;// ✅ 能改但破坏了const的本意4.2 const修饰指针变量 速记口诀const 在 * 左边管内容在 * 右边管指针本身int*p;// 无限制constint*p;// const在*左边 → 指向的内容不可变int*constp;// const在*右边 → 指针本身不可变constint*constp;// 两边都有 → 内容和指针都不可变对比表格五、指针运算5.1 指针 ± 整数利用数组连续存储的特性遍历数组intarr[10]{1,2,3,4,5,6,7,8,9,10};int*parr[0];for(inti0;i10;i){printf(%d ,*(pi));// pi 就是指针整数}5.2 指针 - 指针得到两个地址之间相差的元素个数// 模拟实现strlenintmy_strlen(char*s){char*ps;while(*p!\0)p;returnp-s;// 指针相减得到长度}⚠️ 要求两个指针指向同一块连续空间才有意义。5.3 指针的关系运算比较地址的高低大小intarr[10]{1,2,3,4,5,6,7,8,9,10};int*parr[0];intszsizeof(arr)/sizeof(arr[0]);while(parrsz)// 指针比较{printf(%d ,*p);p;}六、野指针概念野指针就是指针指向的位置是未知的、随机的、不正确的。6.1 野指针三大成因① 指针未初始化int*p;// 局部变量未初始化默认随机值*p20;// ❌ 危险p指向未知位置② 指针越界访问intarr[10]{0};int*parr[0];for(inti0;i11;i)// 循环12次越界{*(p)i;// 最后两次访问越界 → 野指针}③ 指针指向的空间被释放int*test(){intn100;returnn;// ❌ 返回局部变量地址}intmain(){int*ptest();// p成为野指针printf(%d\n,*p);// ❌ 危险空间已释放}6.2 规避野指针四步走NULL的定义#defineNULL((void*)0)// 0地址不可读写正确示例int*pNULL;// 不知道指向哪就赋NULL// ... 使用时if(p!NULL)// 先检查{*p10;// 安全使用}七、assert断言7.1 基本用法#includeassert.hassert(p!NULL);// 条件为假时终止程序并报错7.2 特点7.3 开启/关闭#defineNDEBUG// 定义后禁用assert#includeassert.hDebug版本启用帮助排查问题Release版本自动优化掉不影响效率八、指针的使用和传址调用8.1 模拟实现 strlenintmy_strlen(constchar*str){intcount0;assert(str);// 确保指针非空while(*str)// 等价于 *str ! \0{count;str;}returncount;}8.2 传值调用 vs 传址调用Swap经典案例❌ 传值调用失败voidSwap1(intx,inty){inttmpx;xy;ytmp;// 只交换了形参不影响实参}intmain(){inta10,b20;Swap1(a,b);// 传值 → 交换失败// a10, b20没变}✅ 传址调用成功voidSwap2(int*px,int*py){inttmp*px;*px*py;*pytmp;// 通过地址直接修改main中的a和b}intmain(){inta10,b20;Swap2(a,b);// 传址 → 交换成功// a20, b10交换成功}8.3 对比总结 终极速记卡一页总结