指针大佬,久仰大名!终于学到你,第一次接触,多多指教哦
1.内存和地址
1.1内存
CPU(中央处理器)在处理数据时,需要的数据是在内存中读取的,处理后的数据也需要再放回内存中,为了高效管理数据,我们会把内存划分为一个个内存单元。
每个内存单元的大小是1个字节(能放8个比特位)。
每个内存单元都有一个编号,我们也成为地址,即内存单元的编号==地址==指针。
1.2如何理解编址
什么是编址?CPU访问内存中的某个字节空间,必须知道这个字节空间在内存中的什么位置,而内存中的字节很多,所以需要给内存进行编址。
计算机中的编址,并不是把每个字节的地址记录下来,而是通过硬件设计完成的。
计算机内有很多硬件单元,这些硬件单元是需要彼此协同工作的,他们之间通过线连起来进行数据传输,同理,CPU和内存之间也需要进行数据传输,所以两者也必须用线连起来。
我们今天重点关注地址总线,我们可以简单理解,32位机器有32根地址总线,每根线只有两态,表示0和1,电脉冲有无,那么一根线就能表示两种含义,2根线表示4种含义,32根线表示2的32次方种含义,每一种含义都代表一个地址。
地址信息被下达给内存,在内存上,就可以找到该地址对应的数据,将数据再通过数据总线传入CPU内寄存器。
2.指针变量和地址
2.1取地址操作符&
在C语言中创建变量,其实就是向内存申请空间。
a是整型数据,int类型占四个字节,此时我们打印的是其第一个字节的地址,但是只要知道了第一个字节的数据,顺藤摸瓜访问到4个字节的数据也是可行的。
2.2指针变量和解引用操作符(*)
2.2.1指针变量
我们通过取地址操作符&拿到的地址是一个数值,比如:0x006FFD70,这个数据有时需要存储起来,供以后方便使用的,我们把这样的地址值存放在指针变量中。
2.2.2如何拆解指针类型
p左边的*是在说明p是指针变量,而前面的int是在说明p指向的是整型int类型的对象。
同理类比,变量是什么类型,变量的地址就要放在什么类型的指针变量中。
eg:char ch='w';
char*pc=&ch;
2.2.3解引用操作符
我们通过取地址符拿到了地址,就可以通过地址找到地址指向的对象,为了找到此对象,我们学习解引用操作符*
老师说运用指针改变变量a写代码就会更加灵活,以后慢慢理解。
2.3指针变量的大小
前面的内容我们提到,32位机器假设有32根地址总线,每根地址线发出来的电信号转换成数字信号后是0或1,那我们就把32根地址线产生的2进制序列当成一个地址,那么一个地址就是32个比特位,需要四个字节才能储存,所以此时指针变量的大小就是4个字节空间。同理如果是64位机器,一个地址就是由64位2进制组成的序列,储存起来就需要8个字节的空间,那么指针变量的大小就是8个字节空间。
x86环境下指针变量的大小都是4个字节,x64环境下都是8个字节
所以指针变量的大小与所指变量类型没有关系
3.指针变量类型的意义
3.1指针的解引用
指针的类型决定了,对指针解引用的时候有多大权限(一次能操作几个字节)
对比这两段代码,在调试中我们可以发现,代码1会将n的四个字节都变成0,而代码2只会将第一个字节变成0.
比如:char*的指针解引用就只能访问一个字节,而int *的指针的解引用就能访问4个字节,short *指针的解引用访问2个字节。
3.2指针加减整数
char*类型的指针变量+1:跳过一个字节
int*类型的指针变量+1:跳过4个字节
指针类型决定了指针向前或向后走一步有多大距离
3.3void*指针
在指针类型中,有一种特殊类型是void*类型,可以理解为无具体类型的指针(也称泛指指针),它可以接受任何类型的地址,但是也有局限性,void*类型的指针不能直接进行+-整数和解引用运算
void类型的指针可以接受不同类型的地址,但是无法直接进行指针运算。
一般void*类型的指针是使用在函数参数部分,用来接收不同类型的数据的地址,这样的设计可以实现泛型编程的效果。
4.const修饰指针
4.1const修饰变量
(const是C语言的一个关键字,可以用来修饰指针)
变量是可以修改的,如果把变量的地址交给一个指针变量,则通过指针变量也可以修改这个变量,但是如果我们想做一些修饰,使变量不能被改变,就要用到const发挥作用。
n本质上还是变量,加入const 我们只是在语法上进行了修饰,所以我们习惯上称n是常变量
但是如果我们绕过n,使用n的地址去改变变量n,这样就打破了语法规则,就可以修改n了。
4.2const修饰指针变量
const修饰指针时,可以放在*左边,也可以放在*右边
- const如果放在*的左边,eg:int const*p,const int *p,修饰的是指针指向的内容,它可以保证指针指向的内容不能通过指针来改变,但是指针变量本身的内容可变。
- const如果放在*的右边,修饰的是指针变量本身,保证了指针变量的内容不能修改,但是指针指向的内容可以通过指针改变。
5.指针运算
指针的基本运算有三种:
- 指针加减整数
- 指针-指针
- 指针的关系运算
5.1指针加减整数
因为数组在内存中是连续存在的,使用只要知道第一个元素的地址,就可以顺藤摸瓜找到后面所有的元素。
5.2指针-指针
前提条件:两个指针指向同一块空间。
因为字符串的地址是连续的,所以后面的地址减前面的地址就是字符串长度
指针减指针的绝对值得到的是指针之间的元素个数。
5.3指针的关系运算(其实就是指针/地址的大小比较)
6.野指针
概念:野指针就是指针指向的位置是不可知的(随机的,不正确的,没有明确限制的)
6.1野指针成因
1.指针未初始化
2.指针越界访问
这里的*(p++)=i,表示每循环一次,就把i的地址存入指针变量p中,而所存数据的内存大小,大过了指针变量p向数组arr申请的内存大小。
3.指针指向的空间释放
6.2如何规避野指针
6.2.1指针初始化
如果明确知道指针指向哪里就直接赋值地址,如果指针不知道指向哪里,可以给指针赋值NULL.
NULL是C语言中定义的一个标识符常量,值是0,0也是地址,这个地址是无法使用的,读写改地址会报错。
初始化为下:
6.2.2小心指针越界
一个程序向内存申请了哪些空间,通贵哦指针也就只能访问哪些空间,不能超出范围访问,超出了就是越界访问。
6.2.3指针变量不再使用时,及时置NULL,指针使用前检查有效性
one rule:只要是NULL指针就不去访问,同时使用指针之前可以判断指针是否是NULL。