指针劝退指南??
文章目录内存和地址内存理解编址指针变量和地址取地址操作符()指针变量和解引用操作符(*)指针变量如何拆解指针类型解引用操作符(*)指针变量的大小指针变量类型的意义指针的解引用指针-整数void*指针指针运算指针-整数指针-指针指针的关系运算const修饰指针const修饰变量野指针野指针成因如何避免野指针指针变量初始化注意不要让指针越界指针变量不再使用时,及时置NULL,指针使用之前检查有效性避免返回局部变量的地址assert断言指针的使用和传址调用传值调用和传址调用总结1. 内存与地址基础2. 指针变量与操作符3. 指针类型的重要性4. 指针运算5. const修饰指针6. 野指针与安全使用7. 实用工具与技巧关键要点内存和地址内存在我们了解内存之前,可以用一个生活案例来帮助我们理解,在日常生活中我们网上购物后都要填写一个地址,如果我们不去填写地址那么我们购买的东西就不知道发到哪里去,有了地址商家就能快速的找到你并把货物给你.如果把上面例子应用到计算机中呢?我们直到计算机上的中央处理器(CPU)在处理数据的时候,需要的数据是在内存中读取的,处理后的数据也会放回到内存中,我们的电脑内存都是8G/16G/32G等,如何来高效的管理这些内存呢?其实是把这些内存划分为了一个个的内存单元.每个内存单元的大小为1个字节(8比特位),一个比特位可以存储一个2进制的位1或者0,一个内存单元就是一个字节,每个内存单元也有一个编号,有了这个内存单元的编号,cpu就可以根据编号快速找到所需要的内存空间在计算机中我们把内存单元的编号也称为地址,C语言中给地址起了个新名字:指针内存单元的编号 地址 指针理解编址首先,计算机内是有很多硬件单元的,硬件单元是要互相协同工作的,所谓的协同工作至少相互之间可以进行数据传递但是硬件与硬件之间是相互独立的如何进行数据传递呢?其实他们之间是通过线连起来进而进行数据传递当我们想要往内存中写入一些数据的时候控制总线会发出写入命令,然后地址总线会传输地址告诉我们位置,然后通过数据总线传入我们要写入的数据在计算机中的编址,并不是把每个字节的地址都记录下来存贮,而是通过硬件设计完成的我们可以简单理解32位的计算机有32根地址总线,每个线只有两种状态表示0或1脉冲有无),那么32根地址总线就能表示2^32种含义每一种含义都代表一个地址地址信息被下达给内存,在内存上,就可以找到该地址对应的数据,将数据在通过数据总线传入cpu内寄存器指针变量和地址取地址操作符()理解了内存和地址的关系,在C语言中创建变量就是向内存中申请空间例如上述代码就是创建了整型变量a,向内存中申请了四个字节用来存放整数10,其中每个字节都有地址那我们如何取得a的地址呢?这里就得学习一个操作符–取地址操作符intmain(){inta10;printf(%p,a);return0;}上面截图可以得知这个10中四个字节每个字节都有地址那么打印出来的到底是谁的地址呢?a取出的是a所占4个字节中地址较小的字节的地址虽然整型变量占用4个字节,我们只要知道了第一个字节地址,顺藤摸瓜就知道后面的地址指针变量和解引用操作符(*)指针变量如果我们想要把地址存放起来呢?应该存放在哪里?答案是:指针变量中intmain(){inta10;int*pa;//取出a的地址并存放在指针变量p中//指针变量也是变量,是存放指针的变量return0;}如何拆解指针类型我们上面看到的int *是指针变量p的类型,我们该如何理解指针的类型呢?inta10;int*pa;这里p的左边写的是int *, *是在说明p是指针变量而前面的int是在说明p执行的是整型(int)类型的对象chara10;char*pa;如果指向的是字符类型那么指针变量p的类型就是char *解引用操作符(*)我们把地址保存起来的目的是要使用的,那么我们如何使用呢?我们只要拿到了地址(指针)就可以通过地址(指针)找到地址指向的对象这里我们要学习一个操作符叫解引用操作符(*)intmain(){inta10;int*pa;*p20;printf(%d,a);return0;}上面代码中就使用了解引用操作符,*p的意思就是通过p中存放的地址找到了指向的空间,*p其实就是a这个变量,所以上述代码就是把变量a改成了20指针变量的大小32位平台下地址是32个bit位,指针变量大小位4个字节64位平台下地址是64个bit位,指针变量大小是8个字节注意指针变量的大小和类型是无关的,只要指针类型的变量在相同的平台下,大小都是相同的指针变量类型的意义指针的解引用intmain(){inta0x11223344;int*pa;*p0;return0;}intmain(){inta0x11223344;char*pa;*p0;return0;}对比上面两段代码我们可以看到第一段代码把四个字节都改为了0但是代码2只把第一个字节改为了0结论:指针的类型决定了对指针解引用的时候有多大的权限(一次能操作几个字节)比如char的指针解引用就只能访问一个字节,int的指针的解引用就只能访问四个字节指针±整数intmain(){inta0x11223344;char*pa;int*paa;printf(p %p\n,p);printf(p1 %p\n,p1);printf(pa %p\n,pa);printf(pa1%p\n,pa1);return0;}我们可以看出 char类型的指针变量1跳过一个字节,int类型的指针变量1跳过了4个字节,这就是指针变量的类型差异带来的变化,指针1跳过一个指针指向的元素结论:指针的类型决定了指针向前或向后走多大字节void*指针在指针类型中有一种特殊的类型是void *类型,可以理解为无具体类型的指针(或者泛型指针),这种类型的指针可以用来接收任意类型地址,但是也有局限性,void *类型的指针不能直接进行指针的±整数和解引用的运算intmain(){inta0x11223344;charow;char*pa;void*ba;bo;return0;}o我们可以看到void *类型的指针可以接收不同类型的地址,但是无法直接进行指针运算那么void * 类型的指针到底能干啥呢?一般void *类型的指针是使用在函数参数部分,用来接收不同类型数据的地址,这样的设计可以实现泛型编程的效果,使得一个函数来处理多种类型的数据指针运算指针的运算就是让我们去访问内存中的数据指针的基本运算有三种分别是:指针± 整数指针-指针指针的关系运算指针±整数因为数组在内存中是连续存在的,之要知道第一个元素的地址后面所有元素就都可以知道intmain(){inta[10]{1,2,3,4,5,6,7,8,9,0};intnumsizeof(a)/sizeof(a[0]);inti0;int*pa[0];for(i0;inum;i){printf(%d ,*(pi));}}intmain(){chara[]hello world;char*pa[0];while(*p)// /0的ascll码值为0{printf(%c ,*p);p;}return0;}指针-指针指针-指针的绝对值:得到的是两个指针之间的元素前提:两个指针指向了同一块空间,否则不能相减intmain(){inta[10]{0};printf(%lld,a[9]-a[0]);printf(%lls,a[0]-a[9]);return0;}数组随着下标的增长,地址是由低到高变化的指针的关系运算例如我们要打印一个数组intmain(){inta[10]{1,2,3,4,5,6,7,8,9,0};intusizeof(a)/sizeof(a[0]);int*pa;while(pau)//使用指针的关系运算比较大小{printf(%d ,*p);p;}}const修饰指针const修饰变量变量是可以修改的,如果把变量的地址交给一个指针变量,通过指针变量也可以修改这个变量,但是如果我们希望一个变量加上一些限制,不能被修改,该如何做呢?这就是const的作用const 常属性的意思就是不能改变的意思这里a本质上还是变量只是在语法层面上限制了a的修改如果我们通过指针去修改它的值也是可以修改的,但是我们并不想让它去修改,那么依旧是加上constconst在修饰变量的时候,const可以放在的左边,const限制的是pa指向的对象也就是pa不能修改但是pa不受限制也就是指针变量可以改变指向intmain(){inta10;intb20;constint*paa;//此时限制的是*pa,但是没有限制指针变量p,我们可以去改变指针变量的指向pab;*pa30;//err}const 也可以放在的右边,此时const限制的是pa,也就是pa的指向不能改变了但是pa不受限制,即pa指向的内容intmain(){inta10;intb20;int*constpaa;//此时限制的是pa,但是没有限制*p,我们可以去改变a的值pab;//err*pa30;}野指针野指针就是指针指向的位置是不可知的(随机的,不正确的,没有明确限制的)即指针指向的空间是不属于当前程序的野指针成因指针未初始化intmain(){int*p;//指针没有进行初始化,p是局部变量,局部变量没有进行初始化,它里面存放的是一个随机值*p10;}指针越界访问intmain(){inta[10]{0};int*pa;inti0;intosizeof(a)/sizeof(a[0]);for(i0;io;i)//循环到第11次的时候p就是野指针{pi;printf(%d,*p);}return0;}指针指向的空间释放inttest(){inti100;//i为局部变量当test函数执行完毕后i就被销毁了returni;}intmain(){int*atest();printf(%d,*a);//这里*a就是野指针return0;}如何避免野指针指针变量初始化明确知道指针变量要初始化为什么值,直接初始化intmain(){inta10;int*paa;}不知道指针变量应该指向哪里可以给指针赋值NULLNULL是C语言中定义的一个标识符常量,值为0,0也是个地址int*paNULL;//此处null是空指针,意思是p没有指向有效的空间暂时不要使用它注意不要让指针越界一个程序向内存中申请了哪些空间,通过指针也就只能访问到哪些空间不能超出范围访问,否则就是越界访问指针变量不再使用时,及时置NULL,指针使用之前检查有效性当指针变量指向一块区域的时候,我们可以通过指针访问该区域,后期不再使用这个指针访问空间的时候,我们可以把该指针置为NULL.因为有一个约定俗成的一个规则:只要是NULL指针就不去访问,同时使用指针之前可以判断指针是否为NULLintmain(){inta[10]{1,2,3,4,5,6,7};//如果我们想要把数组的前三个元素改为4inti0;int*paa;for(i0;i3;i){*pa4;pa;}paNULL;//这里已经不使用指针变量pa了给它置为NULL//如果我们现在又想使用pa呢?我们再给它指向我们想要使用的paa;if(paNULL){printf(pa是空指针);}else{.....;}return0;}避免返回局部变量的地址当我们创建一个局部变量的时候要注意它的生命周期,如果在它的生命周期外此时这个数据已经被销毁了,我们再去指向这个地址就已经不是我们想要的数据了这里我们要分清返回局部变量和返回局部变量的地址的区别返回局部变量的地址int*test(){intn100;returnn;}intmain(){intatest();pritnf(%d,*a);return0;}返回局部变量inttest(){into10;returno;}intmain(){intatest();printf(%d,a);return0;}assert断言assert.h头文件定义了宏assert()用于在运行时确保程序符合指定条件,如果不符合就报错终止运行,这个宏常常被称为**“断言”**assert(p!NULL);上面代码在程序运行到这一行语句时,验证变量p是否等于NULL,如果不等于程序将继续运行,否则就会终止运行,并且给出报错信息提示assert()宏接受一个表达式作为参数,如果该表达式为真(返回值非0),assert()不会产生任何作用,程序继续运行,如果该表达式为假(返回值为0),assert()就会报错,在标准错误流stderr中写入一条错误信息,显示没有通过的表达式以及包含这个表达式的文件名和行号assert()的使用对于我们是非常友好的,使用assert()可以自动表示文件和出问题的行号,还有一种无需更改代码就能开启或关闭assert()的机制,如果程序已经没有问题,不需要断言就在#include assert.h语句前定义一个宏NDEBUG#includeNDEBUG#includeassert.h然后重新编译程序编译器会禁用文件中所有的assert()语句,如果程序又出现问题可以移除这条#include NDEBUG指令再次编译,就重新启用了assert()语句assert()的缺点是因为引入了额外的检查增加了程序的运行时间指针的使用和传址调用传值调用和传址调用例如:写一个函数,交换两个整型变量的值voidswit(intc,intd){intz0;zc;cd;dz;}intmain(){inta10;intb20;printf(交换前a%d b%d\n,a,b);swit(a,b);printf(交换后a%d b%d\n,a,b);}这就是传值调用,对于我们传过去的仅仅是数值我们通过监视可以看到实参的地址和传到函数中后的地址是独立的因此我们无法通过这个来实现我们的需求,那如何能实现呢?这个时候就需要指针来实现voidswit(int*pa,int*pb){intz0;z*pa;*pa*pb;*pbz;}intmain(){inta10;intb20;printf(交换前a%d b%d\n,a,b);swit(a,b);printf(交换后a%d b%d\n,a,b);}我们可以看到使用指针顺利完成了任务,这里调用函数的时候是将变量的地址传递给了函数,这种函数调用方式叫做:传址调用传址调用,可以让函数和主调函数之间建立真正的联系,在函数内部可以修改主调函数中的变量,所以在未来函数中只需要主调函数中的变量值来实现计算,就可以使用传值调用,如果函数内部要修改主调函数中的变量的值,就需要传址调用.总结本文全面介绍了C语言中指针的核心概念和使用方法主要内容总结如下1. 内存与地址基础内存单元计算机内存被划分为一个个字节大小的内存单元每个单元都有唯一编号地址指针本质内存单元的编号就是地址在C语言中称为指针2. 指针变量与操作符取地址操作符()获取变量的内存地址指针变量专门用于存储地址的变量声明格式为类型 *变量名解引用操作符(*)通过指针访问或修改其指向的内存内容3. 指针类型的重要性解引用权限指针类型决定了通过解引用能操作的内存字节数指针运算步长指针类型决定了指针加减整数时的步长大小void*指针通用指针类型可接收任意类型地址但不能直接进行算术运算4. 指针运算指针±整数在连续内存空间如数组中移动指针指针-指针计算两个指针之间的元素个数指针关系运算比较指针的大小关系5. const修饰指针const在*左边限制指针指向的内容不可修改const在*右边限制指针本身不可修改指向const在*两边指针指向和内容都不可修改6. 野指针与安全使用野指针成因未初始化、越界访问、指向已释放空间避免方法初始化指针、及时置NULL、使用前检查有效性、避免返回局部变量地址7. 实用工具与技巧assert断言运行时检查程序条件辅助调试传址调用通过指针实现函数内修改主调函数变量建立真正的数据联系关键要点指针是C语言的精髓提供了直接操作内存的能力理解指针类型对内存操作的影响至关重要正确管理指针生命周期避免野指针问题const修饰符提供了额外的类型安全保护传址调用是实现函数间数据交互的有效方式掌握指针需要理论与实践相结合建议通过实际编程练习加深理解从简单的指针操作开始逐步掌握复杂的数据结构和算法实现。