刚开始学习C语言的时候,感觉最难理解的就是指针,什么指针变量,变量指针,指向指针的变量,指向变量的指针?一堆概念,搞得人云里雾里的,今天不讨论这些概念的问题,从最底层来分析C语言中为什么要使用指针,指针存在的意义又是什么呢?

  首先从一个简单的例子来看,写一段代码来交换x、y的值。

void main( void )
{u8 x = 10, y = 20;u8 temp;__asm( "sim" );                             //禁止中断SysClkInit();delay_init( 16 );LED_GPIO_Init();Uart1_IO_Init();Uart1_Init( 9600 );ADC_GPIO_Init();__asm( "rim" );                             //开启中断while( 1 ){LED = ~LED;printf( "x = %d,y = %d\r\n", x, y );temp = x;x = y;y = temp;printf( "---> x = %d,y = %d \r\n\r\n\r\n", x, y );delay_ms( 200 );}
}

在STM8单片机中,交换x、y的值,并将值打印出来,打印结果如下:

通过第三个变量temp很轻松的就将x、y的值交换了。

但是为了程序的美观性,不想再主程序中写这么多的代码,于是决定用一个函数在外部来实现这个功能。

void swap( u8 x, u8 y )
{u8 temp;temp = x;x = y;y = temp;
}void main( void )
{u8 x = 10, y = 20;__asm( "sim" );                             //禁止中断SysClkInit();delay_init( 16 );LED_GPIO_Init();Uart1_IO_Init();Uart1_Init( 9600 );ADC_GPIO_Init();__asm( "rim" );                             //开启中断while( 1 ){LED = ~LED;printf( "x = %d,y = %d\r\n", x, y );sawp( x, y );printf( "---> x = %d,y = %d \r\n\r\n\r\n", x, y );delay_ms( 200 );}
}

在主函数外面定义一个函数,专门用来交换x,y的值。程序运行结果如下:

此时发现x和y的值并没有交换,这是怎么回事?那么在交换函数内部,也将x和y的值打印出来。修改代码如下:

void swap( u8 x, u8 y )
{u8 temp;printf( "in: x = %d,y = %d\r\n", x, y );temp = x;x = y;y = temp;printf( "in: ---> x = %d,y = %d \r\n\r\n\r\n", x, y );
}

打印结果如下:

可以看到在交换函数内部,x和y的值已经交换了,但是在函数外部,x和y的值并没有交换。这是为什么呢?单步调试直接观察x和y在单片机内部分存储情况。

进入主程序之后,首先观察主函数内的x和y在内存中的存储情况,x值为10,也就是16进制的0x0A,在内存中0x000009的位置存储,y的值为20,也就是16进制的0x14,在内存中0x00000B,位置存储。
接下里进入到swap函数中。为了方便观察,将swap函数内部的x和y替换成了m和n。


可以看出在进入子函数之后,m和n的地址和值,都和main函数中的x和y一样。接下来交换m和n的值。

交换完成后发现,内存中0x000009位置的值和0x00000B位置的值亚发生了交换。然后退出子函数,返回到main函数中。

  这时候奇怪的事情发生了,刚才内存中交换的值又变回来了?这是为什么呢?在这里就不得不说在C语言中关于局部变量的问题,局部变量在C语言中是没有固定的存储位置的,它是由系统的堆栈统一来管理的,当进入函数内部时,系统就会给这些局部变量临时分配一个存储空间存储它的值,当程序要离开函数时,系统就会将局部变量的值保存在堆栈中,然后变量的存储位置就被释放了。当程序进入另一个函数中时,又会给这个函数内部局部变量分配空间,这时候可能就会出现两个函数中的局部变量都使用了同一个内存空间。此时这个内存空间的值改变后,并不影响上一个函数中局部变量的值,因为上一个函数中的局部变量值此时在堆栈中存放。当程序要离开当前的这个函数时,又会将当前的局部变量值保存在堆栈中。回到上一个函数后,又将堆栈中存储的值恢复给变量,此时变量的地址又是临时申请的,可能此刻申请的地址值还是和上一次一样。但是并不代表这个地址就永远属于这个局部变量。

  这个就很类似于超市中的储物柜,你要进去超市买东西,先将东西存到一个柜子中,买完东西后,又将东西存储物柜取了出来。然后隔了几个小时,又要去这个超市买东西,又需要将东西存起来,但是此时存储的柜子编号还是上上一次存储时一样。但是这并不能代表这个柜子的编号就是专属于你的了。它只是储物柜临时分配给你的空间,当你取出东西后这个空间就会被系统收回,如果你下一次还需要用,系统又会自动给你分配,但是这两次分配的刚好是一个编号而已。

  那么要如何解决这种变量交换的问题呢?有两种方法,第一种就是直接将要交换的这个两个变量定义为全局变量,让它在程序运行的过程中独占一个地址空间,这样就不会有其他变量来使用这个位置了。但是这样的话就会比较浪费内存空间,只使用了一次,但是却要永久的占用。第二种方法就是直接使用指针。

  下面将代码改成使用指针的方式。

void swap( u8 *m, u8 *n )
{u8 temp;   temp = *m;*m = *n;*n = temp;
}
void main( void )
{u8 x = 10, y = 20;__asm( "sim" );                             //禁止中断SysClkInit();delay_init( 16 );LED_GPIO_Init();Uart1_IO_Init();Uart1_Init( 9600 );ADC_GPIO_Init();__asm( "rim" );                             //开启中断while( 1 ){LED = ~LED;printf( "x = %d,y = %d\r\n", x, y );sawp( &x, &y );printf( "---> x = %d,y = %d \r\n\r\n\r\n", x, y );delay_ms( 200 );}
}

在向swap函数传递参数的时候需要使用&符号。打印输出结果


此时x和y的值已经成功交换了。现在也将子函数内部的交换情况打印一下。

void swap( u8 *m, u8 *n )
{u8 temp;printf( "in: m = %d,n = %d\r\n", m, n );temp = *m;*m = *n;*n = temp;printf( "in: ---> m = %d,n = %d \r\n\r\n\r\n", m, n );
}


  从输出的结果来看,怎么m和n的值一个时1021,一个时1020.这个值是什么呢?直接单步调试看。


  此时main函数中x的地址变成了0x0003FD,y的地址变成了0x0003FC.接着进入子函数。


  这时可以看出m的值为0x03FD,n的值为0x03FC.*m的值为0x0A也是就10,*0x14也就是20.
接下来开始交换值。


  可以看出m和n的值没变,但是m和n的值交换了。接下来回到主函数中。

此时主函数中x和y的值也交换了。

  那刚才串口打印出来的1021和1020是什么呢?1021的十六进制是0x03FD,1020的十六进制是0x03FC.也就是说刚才打印的m的值和x的地址一样,n的值和y的地址一样。

  那为什么m和n会变成x和y地址,*m 和 *n又会变成 x 和 y 的值。这里就要说指针的本质了。在存储器内部,它是不认识什么变量和指针的,对于存储空间来说,它只有地址和值。也就是说在什么地址处,存储什么值。对于普通的变量来说,变量的名称就会被编译器编译成地址,也就是说x和y就是它自己地址的别名。x和y的值就是地址中对应的值。

  当操作普通变量x和y的时候,系统默认操作的就是它的值。而指针刚好和它相反,指针默认是把地址作为它的值,当操作指针的时候,默认操作的就是地址。为了将普通变量和指针进行区分,那么如果要使用指针的时候,就需要给它贴一个标签,告诉系统,我这个是特殊变量,它是直接操作地址的,不是操作值的。

  所以在定义指针的时候给变量前面加一个*号,就表示告诉系统,我这个是特殊的。比如定义了一个int *m。就表示告诉系统,当我默认操作m的时候,你就给我它的地址,而不要给我它的值。当需要取值的时候就需要添加上标签 *m,告诉系统我现在要取的值,不是地址,这是特殊情况,不要把默认的地址给我。

  当要将普通变量传递给指针时,因为直接操作变量默认就是普通变量的值,而指针存贮的是地址,所以当普通变量和指针传递数据的时候,也要给普通变量添加一个标签 & ,这个符号就告诉系统,我现在不要默认的值,我要的是特殊情况的地址。

所以将x和y传递给指针的时候,前面要加&符号。

swap( &x, &y );对应的就是 swap( u8 *m, u8 *n );

  刚才上面不是说了吗,指针默认的是地址,加上*号就是值了。那么这样直接传递过去不就是相当于 *m = &x 了吗?

由于这个子函数是定义和传值在一起操作了,省略了一步,标准操作应该是。

int *m;

m=&x;

  先定义一个指针,然后将普通变量的特殊情况,也就是取普通变量的地址,传递给指针的默认情况。这样m的默认情况下就代表的值x的地址,而x的值就是*m。

如果定义变量和给变量赋值在一条语句时,上面的代码就可以简写为

int *m = &x;

  所以上面的函数 swap( &x, &y ); 给指针传递值的时候,指针的定义和赋值是在一条语句完成的, swap( u8 *m, u8 *n ); 这是一种常用的简写形式。

在swap函数内部操作 *m 也就相当于直接操作的是x,操作 *n 就是直接操作的y,所以交换 *m 和 *n的值,就相当于交换x和y的值。

  在没有使用指针时通过子函数交换,此时传递的是变量的值,相当于把变量的值拷贝了一份,给了子程序。

  而有了指针之后,相当于将变量的地址直接给了子函数。相当于给变量x和y又起了一个别名。操作别名的时候,也就相当于直接操作的是x。

  由此可见,指针只是为了方便编写程序而设置的一种给变量起别名的方法,也就是相当于给自己柜子配了了一把钥匙。只要别人有这个钥匙,也就可以打开你的柜子。所以指针在使用的时候会有危险性。

  如果系统中有关键数据,那么如果这个数据用指针传递给了外部函数,那么当外部函数修改数据的时候,系统就会存在风险。有可能外部函数修改了一个值,而这个值是非法的,自己的系统就奔溃了。所以在使用指针的时候一定要注意安全性问题。

  通过上面的例子,相信对指针就有了更深层次的理解了。它是为了方便操作变量而设置的特殊情况,是被贴了标签的变量。至于什么指针变量,变量指针,那都是起的名字而已,搞不清楚这些概念也不用去纠结,只要在使用的时候,知道如何使用就行了。

C语言小知识---为什么要使用指针相关推荐

  1. 纸上得来终觉浅(c语言小知识总结)

    纸上得来终觉浅(c语言小知识总结) 1.数组的初始定义 对于一个初始定义的数组,内部的值是随机的,若用{}(哪怕其中没有元素)也会让数组内元素初始化,默认为0. 若是用循环语句进行赋值,在一个长度为2 ...

  2. eem二级c语言题库哪种比较好,c语言小知识,供初学者参考

    1 用预处理指令 define 声明一个常数 用以表明 1 年中有多少秒 忽略闰年问题 define SECONDS PER YEAR 60 60 24 365 UL 2 写一个 标准 宏 MIN 这 ...

  3. C语言小知识---递归函数的使用

      C语言允许函数自身调用自身,这种调用就被称为递归.好多人刚开始学习递归的时候,往往被一层层嵌套调用搞糊涂了,搞不清楚到底是怎么调用的?现在就通过一个小例子来演示一下,递归调用时,函数是如何运行的. ...

  4. C语言小知识:typedef\函数模板\

    (1)typedef用法: typedef为C语言的关键字,作用是为一种数据类型定义一个新名字.这里的数据类型包括内部数据类型(int,char等)和自定义的数据类型(struct等).在编程中使用t ...

  5. C语言小知识---特殊的字符串

      字符串在程序中很常见,和整数.小数.字符其他数据类型一样,是代码中必不可少的一部分.可是字符串却又和其他类型不完全一样,还有自己的个性.   下面直接通过一段代码来看. 定义了一个字符1.一个字符 ...

  6. C语言小知识---奇葩的小数

      看到这个标题,好多人可能会想,小数有什么奇葩的,这不和整数一样,加减乘除计算起来也没啥区别呀?奇葩在哪里?   下面通过一个简单的例子来看一看,定义一个小数一个整数,然后打印出来. 核心代码其实只 ...

  7. C语言小知识---printf()函数

      说起printf()函数,写代码的同学肯定都很熟悉,这是C语言中标准的打印函数,在调试代码或者信息输出的时候会经常用到. 其中printf函数的转换说明如下: 转换说明修饰符 printf()中的 ...

  8. C语言小知识---数据类型

      看到这个题目好多人肯定会迷惑,数据类型有什么可说的,这不是编程的基础吗?凡是会写代码的肯定都熟悉数据类型,不就是char,int,float,double这些每天都用成百上千次的的类型吗?各位看官 ...

  9. C语言小知识---printf()函数转换符的意义

      printf()函数大家已经很熟悉了,它的转换符在打印数据的时候也会经常使用,比如%c,%d,%f等.那么为什么打印的时候一定需要转换符呢?系统难道不能自动识别吗?转换符存在的意义又是什么?    ...

最新文章

  1. java foreach order_Java 8流中的forEach vs forEachOrdered
  2. mysql 查看数据库占用空间的大小
  3. 全球无人车头部三强格局明确,百度自动驾驶估值400亿美金
  4. java创建医生的对象_基于安卓Android的作物医生App设计开发(MySQL)(含录像)
  5. Faster R-CNN教程
  6. 前端学习(2307):react之props和state
  7. Python Cookbook手记I
  8. mysql可视化工具路径访问_windows开启3306端口并用可视化工具访问远程mysql(授权访问)...
  9. TurboMail邮件服务器推动邮件领域的进一步发展
  10. C++轻量级微服务_『高级篇』docker容器来说什么是微服务(三)
  11. 刷机后IMEI丢失如何能刷回来
  12. java并发编程-CAS算法
  13. 【时空智友】“采购入库单” 增加导入Excel模板的方式
  14. 学会了,不会ps也能更换自己的证件照底色,制作自己的证件照
  15. Java程序员,你必须得知道并发编程概念
  16. 使用leafcutter 做可变剪切分析流程
  17. VMware:在部分链上无法执行所调用的函数,请打开父虚拟磁
  18. 原生Vue实现二维码扫一扫,兼容PC、安卓、IOS
  19. Word背景默认为绿色,如何更改默认为白色
  20. spring(春天)

热门文章

  1. Android 获取网络错误
  2. 如何在Linux桌面环境下自动启动程序?
  3. asp.net 将此项目作为引用添加将导致循环依赖项
  4. 解决过帐时提示“无法获取内部公司交易记录的帐户”
  5. 微软Windows Server 2008认证体系详细介绍
  6. Annotation 使用备忘2
  7. Import declarations are not supported by current JavaScript version
  8. 浏览器标准模式和怪异模式
  9. CentOS系统缺少库文件解决办法
  10. eclipse中设定文档注释