引入
内存和地址
大家都知道,计算机将信息储存在内存中,那么计算机是如何从内存中读取数据的呢?就像我们上课找教室一样,我们根据教室的门牌号来找到正确的教室才能上课。那么计算机中有没有一个门牌号呢?
计算机中,内存被分成一个一个的内存单元,每个内存单元的大小取一个字节,每一个内存单元都被编了号,这个编号就是地址(指针),所以 内存的编号 == 地址 == 指针 。计算机中的编址,并不是把每个字节的地址记录下来,通过硬件完成。
CPU和内存之间的数据传输是通过三组总线连接的,分别是地址总线、数据总线和控制总线。CPU通过地址总线发送内存地址,通过控制总线发送读写控制信号,通过数据总线发送读取或写入的数据。当CPU想要从内存中读取数据时,它首先将要读取的内存地址发送给内存控制器,这个过程是通过地址总线完成的。CPU还需要使用控制总线发送读取指令,告诉内存控制器它想要读取内存中的数据。内存控制器收到这些信息后,就会访问相应的内存地址,并将数据通过数据总线发送回CPU。
同样地,当CPU想要向内存中写入数据时,它将要写入的内存地址和数据发送给内存控制器,这个过程也是通过地址总线和数据总线完成的。CPU还需要使用控制总线发送写入指令,告诉内存控制器它想要将数据写入到内存中的特定地址。内存控制器将数据存储在内存中,并向CPU发送确认信号。
指针变量
取地址操作符
在C语言中,创建变量实质上是向内存中申请一块空间,使用取地址操作符(&),可以获得变量的地址。
这样的一个变量b称为指针变量,它的类型为int *,一般的,去除指针名,就是指针的类型。例如char*b的类型为 char *。知道了变量的地址(指针)后,我们可以通过指针来调整指针指向的变量
这是如何做到的呢?*(解引用操作符),通过*对指针变量解引用,指向储存a的空间,以此来调整a的值。*b = 3即 a = 3。
指针的大小
拿64位机器来举例子,64位机器有64根地址总线,每一根线都储存0或1这两种信息,总共可以储存2的64次方种不同的信息,所有的地址都有64位,64个比特位即为8个字节。
注意:指针变量的大小,与指针变量的类型无关,指针变量的大小只与硬件有关,32位机器中,地址为32个比特位,4个字节;64位机器中,地址位64位,8个字节。
既然指针的变量与指针的大小无关,那么指针的类型有什么用呢?
观察下面两段代码:
代码1中,n的值被改成了0;而代码2中,n的值却变成了287453952。n的4个字节被全部改为了0(int类型有4个字节,32位);而代码2却只将n的第一个字节改成了0。
由此可以推断处,指针类型可以决定指针解引用有多大的权限(能够操作几个字节)。例如int *可以访问4个字节,而char *只能访问1个字节。
指针+-整数
观察下面一段代码:
我们可以观察到,pc + 1与pc的地址差1一个字节;而pi + 1与pi的地址相差4个字节。
结论:指针的类型决定了指针向前(向后)一步移动的大小。
void *指针:
有指针参与的运算中,不同类型的指针不兼容,我们不能将char *类型的地址赋值给int *,这样编译器会报警告。而用void *类型的指针可以解决这个问题,不同类型变量的地址都可以赋值给void *类型的指针。但是void *类型也有缺陷,void *类型的指针不能进行+-运算,也不能进行解引用操作。所以我们非特殊情况不适用void *类型的指针;在一些特殊的情况下我们会使用void *类型指针,例如在我们定义一个函数,不知道要传入的参数是什么类型时就可以使用。
const修饰指针
const是常属性,不能被修改了,变量还是变量,只是在语法下不能改变(只是不能直接改)。
const修饰指针变量有两种情况:
1.const在*的左边:
此时限制的是*p,意思是不能通过p来改变p指向的对象的内容
但是p本身可以改变,P可以改变指向的对象,p可以指向其他对象
2.const在*右边
此时恰恰与在*左边相反,限制的是p,不能修改P本身的值,但是p指向的内容是可以改变的
指针运算
指针+-整数:
数组的元素在内存中是连续存储的,所以只要知道第一个元素的地址就能够顺藤摸瓜找到后面的元素
指针+-整数:
通过对指针(p + i)解引用来访问数组的内容,*(p + i) ==arr[i].
指针-指针 :
自定义一个函数,利用指针求出字符串的长度,此处数组名arr表示首元素的地址,将地址传给指针p后,会顺着地址一次访问后面地址,直到遇到\0。
指针的关系运算:
野指针
野指针的成因:指针未初始化;
指针未初始化,地址为随机地址,在工作中我们要尽可能规避野指针。
指针的越界访问:
当i = 10是,此时访问的地址是非法的,随机的。此时为野指针
指针指向的空间释放:
在这段代码中,test
函数试图返回一个指向局部变量 n
的指钨。当 test
函数执行完毕并返回,n
就会被释放,因此返回的指针将指向无效的内存地址。
如何规避野指针
指针初始化:
如果不知道初始化什么值,可以将指针置为空(NULL)。区分指针为空和野指针:
指针为空(Null Pointer):指针为空意味着指针没有指向任何有效的内存地址,通常表示为NULL。在许多编程语言中,包括C、C++等,将指针初始化为NULL或者赋予NULL值可以表示指针为空。对空指针进行解引用操作将导致程序崩溃或未定义行为。
野指针(Wild Pointer):野指针是指指针指向的内存地址是未知的、随机的或者无效的内存地址。通常情况下,野指针是未经初始化的指针,或者是指向已经释放的内存的指针。对野指针进行解引用操作同样会导致程序崩溃或者产生不可预测的结果。
小心指针越界:
注意访问的范围,不要超出程序申请的内存范围。
当变量不在使用及时置为空
不要返回局部变量的地址
使用assert断言:
assert()在头文件assert.h中可以用来判断条件是否成立
若条件成立,程序继续执行,若不成立,结束运行。