C语言指针深入浅出1
目录1.内存和地址1.1 内存1.2 对编址的理解2.指针变量和地址2.1 取地址操作符2.2 指针变量和解引用操作符*2.2.1 指针变量2.2.2 拆解指针类型2.2.3 解引用操作符2.3 指针变量的大小3. 指针变量类型的意义3.1 指针的解引用3.2 指针 -整数3.3 void* 指针4. 指针运算4.1 指针 - 整数4.2 指针 - 指针4.3 指针的关系运算1.内存和地址1.1 内存说起内存我们可以举一个生活例子用宿舍楼找房间的例子。在一栋有很多房间的宿舍楼里如果房间没有编号找人就只能一间一间找效率很低。为了更快找到目标房间可以按照楼层给房间编号例如一楼101、102、103……二楼201、202、203……这样一来只要知道房间号就能快速定位房间。对应到计算机中宿舍楼可以理解为内存房间可以理解为内存中的存储单元房间号就相当于内存地址。所以内存地址的作用就是帮助计算机快速找到数据存放的位置。对应到计算机中计算机上的CPU中央处理器在处理数据时需要的数据是放在内存中读取的处理后的数据是放在内存中读取的电脑的内存一般是8GB/16GB/32GB等电脑将内存划分为一个一个的内存单元每个内存单元的大小取1个字节。注意一个比特位可以存储一个二进制位的位1或者0。1Byte8bit1KB1024Byte1MB1024KB1GB1024MB1TB1024GB1PB1024TB每个内存单元就像一个学生宿舍里面可以存放数据。一个字节有8 个比特位就像一个宿舍住 8 个人每个人代表 1 个比特位。每个内存单元都有一个编号这个编号就像宿舍门牌号。CPU 通过这个编号就能快速找到对应的内存位置。在计算机中内存单元的编号叫做地址。在 C 语言中地址又叫做指针。内存地址的编号地址指针三者之间等价。1.2 对编址的理解对编址的理解要先理解在计算机内有许多的硬件单元硬件单元是要互相协同工作的相互之间能够进行数据传递计算机中用线连起来对编址的理解就要了解地址总线。CPU 访问内存时会先给出一个地址。内存根据这个地址找到对应位置再把数据交给 CPU。地址是通过地址总线传递的。地址总线由多根线组成每根线只能表示0 或 1。如果有 32 根地址线就可以表示232个不同地址也就是说CPU 最多可以找到 232个内存单元。编址就是给内存中的每个存储单元分配一个唯一编号。如果实在不理解看下图2.指针变量和地址2.1 取地址操作符在C语言中变量创建的本质就是向内存中申请空间。上述代码中创建了整型变量a, 向内存中申请了4个字节用来存放整数10每个字节都有地址。上述的代码就是创建了整型变量a内存中申请4个字节⽤于存放整数10其中每个字节都有地址上图中4个字节的地址分别是//因为是在X64平台下所以有16位如果在X86平台下就是8位0x0000003C588FF8C40x0000003C588FF8C80x0000003C588FF8CC0x0000003C588FF8D0讲解了这么多要想得到a的地址就要学习取地址操作符intmain(){inta11;a;// - 取出a的地址printf(%p\n,a);return0;}a 取出的是a所占4个字节中地址较小的字节的地址虽然整型占4个字节我们只需知道第一个字节顺藤摸瓜访问就可以访问到4个字节的数据。2.2 指针变量和解引用操作符*2.2.1 指针变量我们通过取地址操作符拿到变量的地址是一个数值这个数值有时候是需要存储起来的方便后期进行使用我们就可以将地址存放在指针变量中。例如:intmain(){inta10;int*paa;//取出a的地址并存储到指针变量pa中return0;}指针变量也是一种变量 指针变量是用来存放地址的存放在指针变量中的值都会理解成地址。2.2.2 拆解指针类型指针变量pa的类型是int*,我们应该这样来理解指针变量2.2.3 解引用操作符我们将地址通过指针变量保存起来要通过解引用操作符*来进行访问。解引用操作符*的作用是通过指针中保存的地址找到该地址对应的变量。2.3 指针变量的大小指针变量是用来存放地址的地址有多大指针变量就需要多大的空间。32 位平台指针大小通常是4 字节64 位平台指针大小通常是8 字节。结论32位平台下X86环境下地址是32个bit位指针变量的大小是4个字节64位平台下X64环境下地址是64个bit位指针变量的大小是8个字节注意指针变量的大小和类型是无关的只要是指针类型的变量在相同平台下大小都是相同的。3. 指针变量类型的意义3.1 指针的解引用//代码1intmain(){inta0x11223344;int*paa;*pa0;return0;}//代码2intmain(){inta0x11223344;char*paa;*pa0;return0;}结论指针的类型决定了对指针解引用的时候有多大的权限一次能访问几个字节。比如char*的指针解引用就只能访问一个字节而int*的指针的解引用就可以访问四个字节。3.2 指针 -整数intmain(){inta0x11223344;int*paa;char*pca;printf(pa %p\n,pa);printf(pa 1 %p\n,pa1);printf(pc %p\n,pc);printf(pc 1 %p\n,pc1);return0;}结论指针类型决定了指针向前一步或者向后一步走多大步长。3.3 void* 指针void*指针就是一种通用指针他可以保存任意类型变量的地址例如int、char、float,可以理解为无意义的指针或者叫泛型指针但是void*类型的指针不能直接运算或取值使用前要先转成具体类型指针。intmain(){inta10;int*pa;char*paa;return0;}上述代码中将⼀个int类型的变量的地址赋值给⼀个char*类型的指针变量。编译器给出了⼀个警告是因为类型不兼容如果使用void*就没问题。下面一段代码intmain(){inta10;charbw;int*pa;void*paa;void*pbb;pa100;pbx;return0;}VS编译代码的结果从上述中我们可以看到void*类型的指针可以接受不同的地址但是不能直接进行指针的运算。一般void*类型的指针是使用在函数参数的部分用来接收不同数据的地址这样的设计可以实现泛型编程的效果使得一个含函数用来处理多个数据。4. 指针运算指针运算一般分为三种分别是指针 -整数指针-指针指针的关系运算4.1 指针 - 整数这里需要回顾一下数组的知识点如果忘记的话点击链接就能跳转了深入浅出数组。数组在内存中是连续存放的只要知道第一个元素的地址顺藤摸瓜就能找到后面的所有元素。intarr[10]{1,2,3,4,5,6,7,8,9,10};//第一种写法intmain(){intarr[10]{1,2,3,4,5,6,7,8,9,10};int*parr[0];//指针变量p取数组arr中的首元素for(inti0;i10;i){printf(%d ,*(pi));//*p i 就是指针变量指向数组下标为i所对应元素的地址}return0;}//第二种写法intmain(){intarr[10]{1,2,3,4,5,6,7,8,9,10};int*parr[0];//指针变量p取数组arr中的首元素for(inti0;i10;i){printf(%d ,*p);//先打印p所指向对象的指针p;//p p 1}return0;}4.2 指针 - 指针指针减指针得到的是指针与指针之间元素的个数。也可以换一种方式来理解就是指针 - 一个整数得到的还是指针将指针放在等号的同一边就变成了指针减指针。前提两个指针指向同一块空间。intmain(){intarr[10]{1,2,3,4,5,6,7,8,9,10};printf(%d\n,arr[9]-arr[0]);return0;}介绍完指针减指针之后我们来模拟实现一下strlen函数。回顾一下strlen函数相关介绍strlensize_tmy_strlen(char*p){char*startp[0];//存储数组首元素的地址while(*p!\0)//当指针变量指到\0时循环结束p;returnp-start;//指针减指针}intmain(){charstr[20]abcdef;// [a b c d e f \0]size_tlenmy_strlen(str);//传数组名传的就是数组的地址printf(%zu\n,len);}我在配合一张图片让知识理解的更深。4.3 指针的关系运算常见的关系运算有以下操作符!指针就是地址地址就是内存单元的编号编号是一个数值随着下的标增长地址是由小到大变化的 所以指针与指针之间可以用来进行关系比较。intmain(){intarr[]{1,2,3,4,5,6,7,8,9,10};int*parr;//将数组的地址存给指针变量pintszsizeof(arr)/sizeof(arr[0]);//计算的是数组的大小//注意因为数组在内存中是连续存放的所以arr[sz]编译不会报错因为在内存中arr[sz]//的地址是已经存在的如果是直接使用arr[sz]不加取地址操作符就构成了越界访问while(parr[sz])//运用了指针的关系运算{printf(%d ,*p);p;}return0;}完