目录
6.2.3指针变量不再使用时,及时置NULL,指针使用之前检查有效性
?楼:101,102,103...
?楼:201,202,203....
...
bit - ?特位
byte - 字节
KB
MB
GB
TB
PB
1byte = 8bit
1KB = 1024byte
1MB = 1024KB
1GB = 1024MB
1TB = 1024GB
1PB = 1024TB
? 首先,必须理解,计算机内是有很多的硬件单元,而硬件单元是要互相协同工作的。所谓的互相协同工作,至少相互之间要能够进行数据传递。
? ?但硬件与硬件之间是相互独立的,那么如何通信呢?答案很简单,就是用“线”连起来。
? 而CPU和内存之间也是有大量的数据交互的,所以,两者必须也用线连起来。
? 不过,我们今天关心一组线,叫做地址总线。
? ?CPU访问内存中的某个字节空间,必须知道这个字节在内存的什么位置,而因为内存中字节很多,所以需要给内存进行编址。
? ?计算机中的编址,并不是把每个字节的地址记录下来,而是通过硬件设计完成的。
? ?钢琴,吉他上面没有写“都瑞米发嗦啦”这样的信息,但是演奏者照样能够精准找到每一个琴弦的每一个位置,这是为何?因为制造商已经在乐器硬件层面设计好了,并且所有演奏者都知道。本质是一种约定出来的共识!
? ?硬件的地址也是如此。
? ?我们可以简单理解为,32位机器有32根地址总线,每根线只有两态,表示0,1【电脉冲的有无】,那么一根线就能表示2种含义,2根线就能4种含义,以此类推,32根线,就能表示2的32次方种含义。而且32根地址总线产生32个信息,转化为二进制,组成32位机器下的一个地址。
? 地址被下达给内存,在内存上,就可以找到该地址对应的数据,将数据再通过数据总线传入CPU内寄存器。
图示:
? 从内存中拿数据的过程叫,读:要读一个数据,通过地址定位到内存,通过数据总线给CPU
? 把数据放入内存的过程叫,写:例如:CPU产生一个数字100的数据,要将100放到内存里,这是控制总线发出write的效果信号,地址总线给一个地址,表示这个数据放在内存的这个位置。
int a = 10;
int * pa = &a;
调试我们可以看到,代码1会将n的4个字节全部改为0,但是代码2只是将n的第?个字节改为0。 结论:指针的类型决定了,对指针解引用的时候有多大的权限(?次能操作几个字节)。
比如: char* 的指针解引用就只能访问一个字节,?而int* 的指针的解引用就能访问四个字节。
第一个代码将a的四个字节全部改成0,但是第二个字节只是将a的第一个字节改成0.
结论:指针的类型决定了,对指针解引用的使用有多大的权限(一次能操作几个字节)
? 先看一段代码,调试观察地址的变化
我们可以看出,char*类型的指针变量+1,跳过一个字节,而int*类型的指针变量+1跳过4字节。这就是指针变量的类型差异带来的变化。
结论:指针的类型决定了指针向前或者向后走一步有多大(距离)
应用:指针对数组的初步应用
? 在指针类型中有一种特殊类型是void*类型,可以理解为无具体类型的指针(或者叫泛型指针),这种类型的指针可以用来接受任意类型地址。但是也是也有局限性,void*类型的指针不能直接进行指针的+-指针和解引用运算。
使用void*类型指针进行解引用和指针加减整数时,VS编译器下的报错
这里可以看出,void*类型的指针可以接受不同类型的地址,但是无法直接进行指针运算。
那么,void*类型的指针到底有什么用呢?
一般的,void*类型的指针使用在函数参数部分,用来接收不同类型的地址,这样的设计可以实行泛型编程,使得同一个函数可以处理多种类型的数据,在后续对指针的介绍中会详细介绍。
变量是可以修改的,如果把变量的地址交给一个指针变量,通过指针变量也可以修改这个变量。但是如果我们希望一个变量加上一些限制,不能被修改,怎么做呢?这就是const的作用。
上述代码中const使a不能被修改的,但是a的本质还是变量,const只是在语法上做了限制,本质不变,习惯上a为常变量。
此时,我们修改a的值,就不符合const的语法,就会报错,致使无法直接修改a的值。
但是,如果我们饶过a,使用a的地址,去修改a的值就能做到了,虽然这是在打破语法规则。
我们可以看出这里a的值确实修改了,但是我们还是要想一下,我们用const修饰n的本质就是为了a不被修改,但是如果pa拿到了a的地址就能改变a,这样就改变了const的限制了,这肯定使不合理,与我们的初心是相违背的,所以应该让pa拿到a的地址也无法改变a,那该怎么弄呢?
让我们分析以下代码,以此来找到上述问题的答案。
代码一:
在没有const修饰的情况下,pn拿到n的地址后,可以通过pn修改n的值,并且可以改变pn指向的对象,让pn存放m的地址。
代码二:
通过代码二,可以看出,当const修饰在*左边时,不管const在int的左边还是右边,都不能通过pn来改变n的值,但是可以改变pn的指向对象,让pn存放m的地址。
即:const限制的是*pn,不能通过pn来改变pn指向的空间的内容;不限制pn,所以pn可以存放n的地址,也可以存放m的地址
代码三:
通过代码三,可以看出,当const放在*右边时,可以通过pn来改变n的值,但是不能改变pn的指向对象,pn只能存放n的地址,不能再变存放其他变量的地址。
即:cosnt限制的是pn,pn没办法再指向其他变量;没有限制*pn,可以通过pn来修改p所指的对象
代码四:
通过代码四,可以看出,当const修饰*两边时,*pn和pn都受到限制。
结论:const修饰指针变量时
1.const如果放在*的左边,修饰的是指针指向的内容,保证指针指向的内容不能通过指针来改变,但是指针变量本身的内容可变。
2.const如果放在*的右边,修饰的是变量本身,保证了指针变量的内容不能修改,但是指针指向的内容可以通过指针修改。
指针的基本运算有三种,分别是:
1.指针+-整数
2.指针-指针
3.指针的关系运算
因为数组再内存是连续存放的,只要知道第一个元素的地址,顺藤摸瓜就能找到后面所有元素
我们先看一段代码:
由此可以看出,指针减指针是指指针和指针之间的个数,(可以理解为:从arr[0]到arr[9]要走几步),那如果是低地址减高地址呢?
由此可以看出上面的结论并不完全全面。
结论:指针减指针的绝对值是指针和指针之间的个数(前提:两个指针指向同一个空间)
应用:模拟strlen函数
首先,回顾一下strlen函数的使用
strlen统计的是字符串'\0'之前的字符个数
模拟strlen函数:
指针(地址)比较大小
概念:野指针就是指针指向的位置是不可知的(随机的,不正确的,没有明确限制的)
1.指针未初始化
2.指针越界访问
3.指针指向的空间释放
当出函数以后,n原本的所在地址里存放的100就已经销毁了,此时p指向这个空间就是非法空间,这样看结果没有什么不对的,但是一旦在打印之前,随便加点什么,破坏了函数的栈,打印的值就不确定了
如果明确知道指针只想哪里就直接赋值,如果不知道指针指向指向哪里,可以给指针赋值NULL。NULL是C语言的一个标识符常量,值是0,0也是地址,这个地址是无法使用的,读写该地址会报错。
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
assert(p!=NULL)
#define NDEBUG
#include <assert.h>
int my_strlen(const char * str)
{
int count = 0;
assert(str);
while(*str)
{
count++;
str++;
}
return count;
}
int main()
{
int len = my_strlen("abcdef");
printf("%d\n", len);
return 0;
}