文章目录一、指针类型的意义1. 指针的解引用2.指针的运算(1) 指针-整数(2) 指针-指针(3) 指针的关系运算二、void*类型的指针三、const修饰的指针1.const修饰变量2.const修饰指针变量四、指针在函数中的使用1.函数的传值调用2.函数的传址调用一、指针类型的意义●既然指针变量的大小和类型无关只要是指针变量在同一平台下大小都是一样的那为什么还要有各种各样的指针类型呢?我们通过下面的例子来理解指针类型的意义。1. 指针的解引用对比下面两段代码在调试时观察内存及变量值的变化。#includestdio.h//代码1intmain(){intn0x11223344;int*pn;*p0;return0;}在代码1中通过逐过程(F10)调试由上图可知系统为整型变量n申请了4个字节的空间存放值0x11223344整型变量n的地址0x010FFAAC存放在了指针变量p中通过对指针p解引用将变量n的值赋为0由上图可知内存中原先4个字节存储的0x11223344也全部变为0了。#includestdio.h//代码2intmain(){intn0x11223344;char*p(char*)n;*p0;return0;}在代码2中还是同样的整型变量nn中存储的还是0x11223344这个值通过逐语句(F10)调试起来我们发现变量n的地址和代码1中的地址不一样虽然是同样的变量同样的值但是当vs编译器再次调试起来的时候系统会重新随机为变量n分配内存空间内存空间的地址也就不一样。随着程序运行结束这块空间会释放还给操作系统。待下一次程序运行时会重新为变量分配空间。但是这里将变量n的地址存储在了char*类型的指针变量中而变量n的地址又是一个int*类型的指针所以这里就将n的地址强制类型转换为了char*的指针再对指针p解引用将n的值赋为0发现只有n的第一个字节被改为了0其他三个字节的值没变。这跟代码1中的不一样代码1中是将n的4个字节的值全部改为了0。●所以这里得到一个结论指针的类型决定了对指针解引时有多大的权限(一次能访问几个字节)。像 char* 的指针解引用就只能访问一个字节而 int* 的指针解引用会访问四个字节。2.指针的运算(1) 指针±整数先看一段代码#includestdio.hintmain(){intn10;char*pc(char*)n;int*pin;printf(n %p\n,n);printf(pc %p\n,pc);printf(pc1 %p\n,pc1);printf(pi %p\n,pi);printf(pi1 %p\n,pi1);return0;}程序运行结果由上图可以看出char* 类型的指针变量1跳过1个字节int*类型的指针变量1跳过了4个字节。这就是指针变量的类型差异带来的变化。指针加1是加了一个指针所指向对象类型的大小(单位字节)而不是地址直接加一个1。所以指针加上一个整数表示指针加上这个整数乘以指针所指向对象类型的大小即指针整数*sizeof(类型)。而指针减去一个整数也是一样的道理即指针-整数*sizeof(类型)。结论: 指针的类型决定了指针向前或者向后走一步有多大(距离)。需要注意的是在数组中指针±整数可能会出现越界访问的问题。(2) 指针-指针指针与指针之间不适合做加法运算因为地址加地址没有意义但是指针减指针就不一样了指针减指针得到的是两个指针之间的元素个数但前提是这两个指针一定要指向同一个数组#includestdio.hintmain(){intarr[10]{1,2,3,4,5,6,7,8,9,0};int*p1arr[0];int*p2arr[9];printf(%d\n,p2-p1);return0;}程序运行结果(3) 指针的关系运算这里只举一个指针之间比较大小的例子后面会遇到很多指针的关系运算。#includestdio.hintmain(){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;}return0;}程序运行的结果二、void*类型的指针在指针类型中有一种特殊类型的指针叫void *类型可以理解为无具体类型的指针(或者叫泛型指针)这种类型的指针可以用来接受任意类型的指针(地址)。但是也有局限性void*类型的指针不能直接进行指针±整数和解引用的运算。例如#includestdio.hintmain(){intn10;int*pin;char*pcn;return0;}在上面的代码中创建了一个整型变量n那它的地址就要用 int* 类型的指针变量来存储上面指针变量pi是int*类型的用来存储变量n的地址没有问题但是pc是char*类型的指针变量用来存储整型变量n的地址编译器就会报警告因为类型不兼容。如果使用 void* 类型的指针变量来接收n的地址就不会出现上面的警告#includestdio.hintmain(){intn10;int*pin;void*pn;return0;}但是对 void* 类型的指针解引用编译器就会直接报错#includestdio.hintmain(){intn10;int*pin;void*pn;*p0;return0;}可以看到上面是在vs2022编译器中运行的结果void*类型的指针可以接收不同类型的地址但是无法直接对其进行解引用。那么void*类型的指针到底有什么用呢?一般void*类型的指针是使用在函数参数的部分用来接收不同类型数据的地址这样的设计可以实现泛型编程的效果。使得一个函数用来处理多种类型的数据后面还会深入讲解这部分的知识。三、const修饰的指针1.const修饰变量变量是可以修改的如果把变量的地址交给一个指针变量通过指针变量的解引用可以修改这个变量。但是如果我们希望给一个变量加上一些限制不能被修改那要怎么做呢?这就要使用const来修饰变量#includestdio.hintmain(){intm0;m10;//m可以被修改constintn0;n20;//n不可以被修改return0;}上面的代码运行后会报错由上图可知变量n被const修饰n就不能被修改了n本质上是变量只不过被const修饰后在语法上就加了限制只要我们在代码中直接对n进行修改就不符合语法规则就会报错致使没法修改n。那有没有其他办法可以修改被const修饰的变量的值呢如果不直接通过n去赋值而通过n的地址去修改n的值就可以做到。这样做是在打破语法规则。#includestdio.hintmain(){constintn0;printf(n %d\n,n);int*pn;*p10;printf(n %d\n,n);return0;}程序运行的结果可以看到通过这样的方法确实能把n的值改掉我们知道const修饰n就是为了让n的值不被修改那如果p拿到n的地址就能修改n这样就打破了const的限制这是不合理的所以应该让p即使拿到n的地址也不能修改n。看下面2.const修饰指针变量const修饰指针变量可以放在*的左边也可以放在*的右边但是意义不一样int*p;//没有const修饰constint*p;//const放在*的左边做修饰int*constp;//const放在*的右边做修饰那const放在*号左边或是右边具体的作用是什么通过下面四段代码来具体学习#includestdio.h//代码1无const修饰的情况intmain(){intn10;intm20;int*pn;*p0;pm;return0;}由上图的运行结果可知运行成功这可以知道在没有对变量进行const修饰时无论是对指针p解引用还是改变指针变量p中存储的地址都没有问题。#includestdio.h//代码2const放在*的左边intmain(){intn10;intm20;constint*pn;*p0;pm;return0;}由上图代码2运行的结果报错出错的是*p 0这条语句而p m这条语句没有问题。#includestdio.h//代码3const放在*的右边intmain(){intn10;intm20;int*constpn;*p0;pm;return0;}由上图代码3运行的结果报错出错的是p m这条语句而*p 0这条语句没有问题。#includestdio.h//代码4*的左右两边都有constintmain(){intn10;intm20;intconst*constpn;*p0;pm;return0;}由上图可知代码4中在*的左右两边都加上const修饰的后程序运行的结果报错出错的是 p m 和 *p 0 这两条语句。综合上面的情况const修饰指针变量p的时候●const如果放在*的左边修饰的是指针变量p所指向的内容表示指针变量p所指向的内存单元里面的内容不能修改但是指针变量p是可修改的。●const如果放在*的右边修饰的是指针变量p所以指针变量p不可修改但指针变量p所指向的内容是可修改的。●const如果既放在*的左边又放在*的右边那就既修饰指针变量p指向的内容也修饰指针变量p指针变量p所指向的内容和指针变量p都不能被修改。再补充一点const放在*的左边时无论是放在类型的前面还是放在类型的后面都是等价的。四、指针在函数中的使用1.函数的传值调用现在写一个swap函数用来交换两个整型变量的值一般我们会想到的方法是#includestdio.hvoidswap1(intx,inty){inttmpx;xy;ytmp;}intmain(){inta10;intb20;printf(交换前a%d b%d\n,a,b);swap1(a,b);printf(交换后a%d b%d\n,a,b);return0;}运行结果通过上面的代码及运行结果发现a和b的值并没有交换这是为什么呢我们通过逐语句(F11)调试来看我们发现在main函数内部创建了变量a和ba的地址是0x005cfe6cb的地址是0x005cfe60在调用swap1函数时将a和b传递给了swap1函数在swap1函数内部创建了形参x和y接收a和b的值但是x的地址是0x005cfd88y的地址是0x005cfd8cx和y确实接收到了a和b的值不过x的地址和a的地址不一样y的地址和b的地址也不一样相当于x和y是独立的空间那么在swap1函数内部交换x和y的值自然不会影响a和b当swap1函数调用结束后回到main函数a和b的值就没有交换。swap1函数在使用的时候是把实参a、b的值赋给了形参x、y这种调用函数的方式叫做传值调用。结论:函数的传值调用是直接将实参的值传递给形参实参传递给形参的时候形参会单独创建一份临时空间来接收实参即形参此时是实参的一份临时拷贝对形参的修改不会影响实参。2.函数的传址调用对于上面通过swap1函数交换两个变量的值如果函数是传值调用的话形参和实参之间就没有建立真正的联系。不能够交换两个变量的值那怎么办呢我们现在要解决的就是当调用swap函数的时候swap函数内部操作的就是main函数中的a和b直接将a和b的值交换。那么这里就可以使用指针在main函数中将a和b的地址传递给swap函数swap函数里边通过地址间接的操作main函数中的a和b就可以达到交换的效果了。#includestdio.hvoidswap2(int*px,int*py){inttmp*px;*px*py;*pytmp;}intmain(){inta10;intb20;printf(交换前a%d b%d\n,a,b);swap2(a,b);printf(交换后a%d b%d\n,a,b);return0;}运行结果由上图可看出通过调用swap2函数成功的将a和b的值交换了。通过将变量的地址(指针)传递给函数让函数和函数外边的变量建立起了联系也就是函数内部可以直接操作函数外部的变量。像这种将变量的地址传递给函数的方式叫做传址调用。结论函数的传址调用是把函数外部创建变量的地址传递给函数参数的一种调用函数的方式。所以在创建和调用函数时如果只是需要主调函数中的变量值来实现计算就可以采用传值调用。如果想要在函数内部修改主调函数中的变量值就需要传址调用。