网站伪静态作用,上海市住房和城乡建设部网站官网,阿里云市场网站建设,中国国家培训网目录 前言
一、内存与地址的关系
二、指针变量
三、野指针
四、const
五、传值调用与传址调用
总结 前言 本文主要介绍C语言指针的一些基础知识#xff0c;为后面深入理解指针打下基础#xff0c;因此本文内容主要包括内存与地址的关系#xff0c;指针的基本语法… 目录 前言
一、内存与地址的关系
二、指针变量
三、野指针
四、const
五、传值调用与传址调用
总结 前言 本文主要介绍C语言指针的一些基础知识为后面深入理解指针打下基础因此本文内容主要包括内存与地址的关系指针的基本语法指针运算野指针还有const修饰指针和assert断言的使用最后还会讲到指针的传址调用希望对大家有所帮助。 一、内存与地址的关系 指针作为C语言的核心知识那么指针究竟是什么呢 首先指针其实就是地址而地址是内存中一个个内存单元的编号我们知道计算机上CPU中央处理器在处理数据的时候需要的数据是在内存中读取的处理后的数据也会放回内存中那我们买电脑的时候电脑上内存有8GB/16GB/32GB等这些内存就是程序运行时需要用到的内存为了更高效的管理与使用这些内存于是就将这些内存分为一个个内存单元每个内存单元的大小取一个字节也就是8个比特位⼀个比特位可以存储一个2进制的位1或者0每个内存单元也都有一个编号有了这个内存单元的编号CPU就可以快速找到一个内存空间在计算机中我们把内存单元的编号也称为地址。C语言中给地址起了新的名字指针所以我们可以这样理解内存单元的编号 地址 指针 如图 二、指针变量 指针变量就是储存地址的变量 1. 取地址操作符 操作符是一个单目操作符用来取出一个变量的地址 比如创建 int a观察其地址 图中画红线的部分就为a变量在内存中的地址以及储存的数据即0x004FF82C为地址0a 00 00 00为以16进制储存的数据其中 0a 表示的就是以16进制保存的十进制数字10因为一个16进制数需要4个比特位表示0a就是两个16进制数占了8个比特位刚好为一个字节而0a 00 00 00 四个这样的就刚好表示4个字节至于为什么0a在前面这是编译器自己的规则 2.指针变量创建
对于一般的指针变量的创建 (类型) * 变量名; 这样就将变量 a ,b 的地址存储在对应类型的指针变量里面其余的如float、double等可以以此类推
注意这里的*号表示其变量为指针变量 3.解引用操作符 * * 解引用操作符为一个单目操作符它可以通过地址找到其对应的数据 因为指针变量存储的就是地址所以指针变量搭配*就可以找到其对应的数据进行操作
如 我们可以通过解引用操作符修改指针对应的变量数据 4.指针变量类型的意义
指针变量的大小和类型无关只要是指针变量在同一个平台下大小都是一样的比如在32位平台上指针类型的变量大小都是4个字节64位平台上为8个字节以下在32位上演示
因此指针变量的类型有什么意义呢 其实这个意义非常重要指针的类型决定了对指针解引用的时候有多大的权限一次能操作几个字节。比如 char* 的指针解引用就只能访问⼀个字节而 int* 的指针的解引用就能访问四个字节 如我们再次创建一个变量a。注程序每次运行时分配的地址不一样 除了a变量的地址不一样其他和上面一样为 0a 00 00 00它表示的是10并且每两位表示一个字节而一个字节表示一个内存单元因此如上的0x0099FC8C其实表示的是储存0a的地址我们可以一列一列的观察其内存 因为a为整形变量占4个字节因此其在内存中为4个连续的内存单元如上标记的区域此时如果我们创建一个整形指针变量接收a的地址那么我们解引用该指针就可以操作这四个字节
如 注90 01 00 00 在读取时是以 00000190也就是190为16进制
十进制刚好为400 此时变量a可以被正常修改而如果我们以字符类型的指针接收a的地址后我们一次只能修改一个字节
如 16进制28等于十进制40如上我们貌似也能正常修改整形变量a的数组但实际上只要我们修改的数值大于两位16进制能表达的最大数字就不能正常修改a的数值
如 我们只能改变一个字节也就是char类型的指针一次只能修改一个字节 这就是指针类型的意义当然不止如此指针变量的类型还决定了指针加减整数的时候一次跳过多少字节下面就会讲到 5.指针 - 整数 先说结论指针加减整数会使指针前进或后退n个字节而指针的类型决定了指针向前或者向后走一步有多大距离 也就是说指针类型决定了指针加减1时的步长比如char*指针它一次只能跳过一个字节它加减n也就是向前或向后跳过n*sizeof(char)个字节 比如 注此处不能int *parr arr不能写成arr下篇指针进阶文章我会讲到
此处我们就利用了循环来使数组首元素地址依次跳过 i个int类型大小的字节实现了循环打印数组元素
此处我们有几处需要注意的点
数组的元素在内存中是连续存放的并且地址由低到高不了解的可以参考我主页的数组文章此处我们发现如果把 *(arri) 换成 arr[ i ] 也就是我们之前的写法达到的效果是一样的这是因为 *(arri) 是完全等价于 arr[ i ] 也就是说当编译器遇到 arr[ i ] 时会把它解读为 *(arri)按这样理解因为 arri 等于 iarr 也就是可以写成 *(iarr)进而可以写成 i[arr] 我们可以验证一下答案是完全可以的但是平时不建议这样写因为可读性不如 arr[ i ]。总结就是 [ ] 操作符其实也是解引用的效果只不过多了加法的作用 6.指针 - 指针 对于指针 - 指针这个运算来说只有两指针指向的是同一块连续的内存区域时才有意义 我们可以通过指针 - 指针来计算数组两元素地址之间有多少个元素
如 那么为什么是9个而不是10个呢
我们可以画图来理解 画图我们就可以很直观的感受到arr[9] 的元素没有被计算到 三、野指针 1.概念 野指针就是指针指向的位置是不可知的随机的、不正确的、没有明确限制的。 野指针成因
指针未初始化主要是创建在函数中的指针变量没有进行初始化造成指针指向的地址是随机值此时指针指向的地址随机不能对其进行解引用。指针越界访问这种主要出现在数组中指针指向的地址超出了数组所在的内存区域指向了一个不确定的地址指针指向的空间被释放这种主要发生在指针变量指向的地址是已经被释放的内存空间地址被释放的空间不属于该程序虽然可能引用不会导致报错但是不安全 2.如何规避野指针 野指针的危害有访问违规、数据损坏、内存泄露、安全风险等。 野指针的危害众多、因此我们的代码中需要规避野指针那么如何规避野指针呢
指针变量初始化时如果没有需要赋值的地址就先赋值为NULL指针变量不再使用时及时置NULL指针使用之前检查有效性当指针变量指向一块区域的时候我们可以通过指针访问该区域后期不再使用这个指针访问空间的时候我们可以把该指针置为NULL。因为约定俗成的⼀个规则就是只要是NULL指针就不去访问 同时使用指针之前可以判断指针是否为NULL。避免返回局部变量的地址 除了以上的方法还有一个常用的方法
assert 断言 assert.h 头文件定义了宏 assert() 用于在运行时确保程序符合指定条件如果不符合就报错终止运行。这个宏常常被称为“断言” 比如assert (p ! NULL); 上面代码在程序运行到这一行语句时验证变量 p 是否等于 NULL 。如果确实不等于NULL 继续运行否则就会终止运行并且给出报错信息提示。
程序 assert() 宏接受⼀个表达式作为参数。如果该表达式为真返回值非零 assert()不会产生任何作用程序继续运行。如果该表达式为假返回值为零 assert() 就会报错在标准错误流 stderr 中写入一条错误信息显示没有通过的表达式以及包含这个表达式的文件名和行号。
如 assert() 的使用对程序员是非常友好的使用 assert() 有几个好处它不仅能自动标识文件和出问题的行号还有一种无需更改代码就能开启或关闭 assert() 的机制。如果已经确认程序没有问 题不需要再做断言就在 #include assert.h语句的前面定义一个宏 NDEBUG
如下assert()就会失去作用 如果想再次启用assert()只需要注释掉第一行的宏就行
assert() 的缺点因为引入了额外的检查增加了程序的运行时间。 一般我们可以在 Debug 中使用在 Release 版本中选择禁用 assert 就行在 VS 这样的集成开发环境中Release 版本中assert()直接就是自动优化掉了。这样在debug版本写有利于程序员排查问题在Release 版本不影响用户使用时程序的效率。 四、const const的作用被const修饰的变量不能被直接修改 如 程序在还未运行时已经发出错误警告
虽然不能直接修改但是还能通过指针变量间接修改 但是如果const修饰的是指针变量就分以下两种情况 const如果放在*的左边修饰的是指针指向的内容保证指针指向的内容不能通过指针来改变。 但是指针变量本身的内容可变。const如果放在*的右边修饰的是指针变量本身保证了指针变量的内容不能修改但是指针指向的内容可以通过指针改变。 如 五、传值调用与传址调用 传值调用与传址调用这个主要针对的是自定义函数的参数也就是说 函数参数为非指针类型调用时传入非指针类型参数即为传值调用函数参数为指针类型调用时传给函数地址即为传址调用 那么这两个有什么区别呢
其实主要是传值调用时在函数内部修改形参不会影响实参而在传址调用时修改形参也同样会会修改实参 比如这个例题编写一个函数交换两个整形变量的内容 此前我们在主函数中只需要再创建一个变量通过三者交换即可达成题目这样的效果但如果我们在自定义函数里面函数参数为两个整形变量分别接收需要交换内容的两个实参使用一样的方法是达不到一样的效果的这时候我们只需要使用传址调用即可
如 通过传给函数实参的地址在函数中用指针变量的形参接收就可以在函数中解引用该指针变量来修改对应的实参变量的内容
这就是传值调用与传址调用的不同 另外如果函数的参数为数组类型其实也是指针变量给函数传参时一般传入的就是数组名因为数组名就是数组首元素的地址至于详细原因我会在指针进阶中讲到 总结 以上就是本文的全部内容希望对大家有所帮助下一篇我会继续写指针的进阶篇感谢大家的支持