[TOC]

零基础C语言,深入理解指针(2)

1 指针的运算

指针的基本运算有三种:
第一种是指针加减整数。
第二种是指针减指针。
第三种是指针的关系运算。

1.1 指针加减整数

我们知道数组在内存中是连续存放的。如图:

在这里插入图片描述

所以,我们可以用数组来演示一下 , 如下图:
在这里插入图片描述
把数组 arr 的首元素地址(数组名就是数组首元素的地址)放到指针变量 p 里面,然后输出 p + i 解引用,我们发现输出了整个数组。换句话说,指针加一访问了下一个整形的地址,跳过了一个整形。

所以,指针 + n ,就会跳过 n 个相应的类型,比如整形指针 + 1 ,就会指向跳过一个整形(四个字节)的地址 ;字符指针 + 1,就会指向跳过一个字符(一个字节)的地址。

指针减整数同理。

1.2 指针 - 指针

指针减指针运算可以把指针类比为日期,日期减日期是什么?是这两个日期之间的天数。比如一月三十一号减去一月一号等于三十天。
指针减去指针就是,两个指针指向的地址之间的元素个数。如图:

在这里插入图片描述
指针 pa 指向第五个元素,指针 p 指向第一个元素。所以 pa - p 的结果就是 5 。

1.3 指针的关系运算

指针的关系就是地址的高低的关系,和整形变量的大小或者类似的是一样的·。如图:

在这里插入图片描述
我们知道数组元素的地址是由低到高的,pa指向第五个元素,p指向第一个元素,所以 p 的地址小于 pa 的地址。

2 野指针

野指针的意思就是指针指向的地方是未知的,随机的。

2.1 野指针的成因:

1.指针未初始化:
在这里插入图片描述
如图,指针为初始化,指向的地方就是随机的。
2.越界访问:
在这里插入图片描述
数组 arr 只有十个元素,指针 p 指向了第十二个元素,这时 p 就是野指针。
3.指针指向的空间释放
在这里插入图片描述
如图,指针指向的是 test 函数里的 n 的地址,而 n 是 test 函数里面的局部变量,出了 test 函数就销毁了,这时,指针 p 就是野指针。

2.2 如果规避野指针:

1.指针初始化

如果知道指针指向哪里,那么就直接赋值,如果不知道指针指向哪里,就可以赋值一个空指针 NULL 。NULL 是C语⾔中定义的⼀个标识符常量,大小是0 ,但是这个地址不能使用,使用会报错。

2.避免越界访问

一个变量申请了多少的空间,如果指向这个变量的指针超出了空间范围,就是越界访问。

3.当指针变量不再使用时,及时置成 NULL,指针使用之前检查有效性

当指针变量指向⼀块区域的时候,我们可以通过指针访问该区域,后期不再使⽤这个指针访问空间的时候,我们可以把该指针置为NULL。因为约定俗成的⼀个规则就是:只要是NULL指针就不去访问,同时使⽤指针之前可以判断指针是否为NULL。

4.避免返回局部变量的地址
如上文例子3,避免就好了。

3 assert 断言

assert.h 头文件,定义了宏 assert (),如果 assert 的条件成立,程序正常运行。如果不成立,程序就报错终止运行,并且在标准错误流 stdrr 中,显示没有通过的表达式和行号。

assert(),接受一个表达式,如果表达式为真(非零),程序正常运行;如果表达式为假(零),程序就终止报错。并且在如:

1
assert(p!=NULL)

以上代码就是判断 q 是不是空指针,如果 p 是空指针,程序就会终止报错。

如果想要关闭 assert 断言的话,只需在<assert.h>前面,# define NDBUG,如:

1
2
#define NDEBUG
#include <assert.h>

然后重新编译运行就好了。如果要重新打开的话,就把 define 这句注释掉就好了。

assert 的缺点是会占用程序运行的时间,因为多了一次判断嘛。

还有一点 assert 断言只会在 debug 状态下起作用,在 relese 状态下,是直接关闭的,被优化掉了。

4 指针的使用和传址调用

4.1 strlen 函数的模拟实现

我们知道 strlen 函数是用来计算字符串长度的库函数,

我们用这个例子来演示一下指针的使用:

在这里插入图片描述
可以看到,我们定义了一个 my _strlen 函数,函数的返回类型是 size_t ,参数是 const char * 。

因为字符串长度不吭呢是负数,所以我们函数的返回类型设置成 size_t ,size_t 就是一种无符号整形的类型。

参数设置为 const char * ,因为我们传过来字符串数组 arr 。我们只想测量它的长度,而不想改变它的内容,所以所以我们用 const 修饰一下。

函数的逻辑就是,传过来 arr 数组的首元素地址,然后只要 * arr != ‘\0’, arr 就 ++,count 这个计数器也 ++。地址++ 就访问下一个元素的地址,然后再解引用判断,循环起来,到 ‘\0’ ,停下来。

4.2 传址调用

函数在使用的需要传参,如果把变量的地址当成参数传给函数,就叫传址调用。那么它和传值调用有什么区别呢?

假设我们写一个函数,要完成 a 和 b 两个整数的数值交换,我们把 a 和 b 的值传给swap 函数,如下图:
在这里插入图片描述
我们发现 swap 函数并没有起作用。如果我们把 a 和 b 的地址传给 swap 函数呢?
如下图:

在这里插入图片描述
我们发现成功交换。

那么这是为什么呢?我们说函数的形参在没有传值过来时是不会向内存申请空间的,当有值传过来时,才向内存申请空间来储存。换句话说,在传值调用时,形参的地址和实参是不一样的,所以函数里面对形参的处理是不会反应在实参身上的。

如果我们把实参的地址传给形参,函数里对形参的处理就是对实参地址的处理,所以就可以反应在实参上。

本篇完。