
大家好,很高兴又和大家见面了!!!
在上一个篇章中,我们介绍了指针、指针变量以及野指针的相关知识点。在今天的篇章中,我们将探讨一下指针是如何进行运算的。
对于指针的运算,共有三种运算方式:
对于不同类型的指针加/减整数,实质上就是指针加减对应数据类型所占空间大小与整数的乘积。
如:
char*类型的指针进行整数的加减,就是指针加减char类型所占空间大小与整数的乘积;int*类型的指针进行整数的加减,就是指针加减int类型所占空间大小与整数的乘积; 即:

在数组篇章中我们介绍过,数组元素在内存中是从低地址到高地址进行连续存放的,下面我们来看一段代码:
//指针-指针
int main()
{
int arr[10] = { 0 };
int* p1 = &arr[0];//首元素地址
int* p2 = &arr[9];//最后一个元素地址
printf("%d\n", p2 - p1);
return 0;
}现在我们将数组首元素地址与最后一个元素的地址分别提取出来并存放在指针p1和指针p2中,下面我们看一下,p2-p1会得到什么结果:

这个结果似乎与数组元素下标之间的差值是一样的。如果真的是这样的话,那我们来测试一下下标为2的元素与下标为7的元素的指针的差值是不是5;

从测试结果中可以看到,确实如此,在数组中数组元素地址之间的差值与下标的差值相等;
那问题来了,这个差值的含义是什么呢?
我们知道,数组的下标就代表数组的元素,数组下标是从0开始的,那我可不可以认为数组的下标就是代表数组元素前面的元素个数呢?
根据这个逻辑,我们可以很容易得出:元素下标之间的差值就是两个元素之间的个数。
比如这里的下标为7的元素与下标为2的元素的下标之间的差值,就是从下标为2的元素到下标为7的元素之间的个数。
它们之间总共有下标为:2/3/4/5/6的5个元素;
同理,首元素与最后一个元素的元素下标之间的差值,就是从首元素到最后一个元素之间的元素个数。
它们之间总共有下标为:0/1/2/3/4/5/6/7/8的9个元素;
我应该有表述清楚这个逻辑吧!相信大家也都能够理解了。这个结论有什么用呢?下面我们继续来介绍;
在介绍数组时,我们有介绍过一个内容——字符串。 在介绍字符串时我们提到了一个计算字符串长度的库函数——strlen; 下面我们就来通过MSDN来看一下这个库函数究竟是怎么使用的:

从资料卡中我们可以看到strlen函数需要传入一个字符指针类型的参数,并返回一个size_t的值,返回值为字符串中的字符数不包括\0。也就是说这个函数就是在自动帮我们计算字符串中\0前面的字符的个数。
对字符串在数组篇章中我们主要介绍了以下几点内容:
""引起的一个或多个字符叫做字符串;""中自带一个字符\0;\0在字符串中是位于字符串的末尾,也就是字符串的最后一个字符;\0是字符串的终止符;也就是说如果将字符串放在字符数组中,\0就是数组中的最后一个元素,\0的下标就代表着它前面的元素个数。如果我能够知道\0的下标,那我就能得出这个字符串的字符个数也就是字符串的长度,这样我是不是就能自己实现strlen这个函数了呢?
现在我们来整理一下我们已知的信息:
\0;现在通过这三条信息,我们可以尝试着通过寻找\0的下标的方式来实现strlen函数。下面我们来一步一步的分析如何实现strlen这个函数;
\0的数组下标实现一个strlen函数,那我们就需要从函数的返回类型、函数的参数以及函数的实现这三步出发,下面我们一步一步的进行分析;
为了避免出现冲突,我们将模拟实现的strlen函数命名为my_strlen ,根据我们的需求,我们现在需要返回的是\0的下标,也就是一个整型值,也就是说我现在定义的函数返回类型应该是一个int型的函数,即:int my_strlen();
函数的参数我们现在需要思考的是我如何能找到\0的下标? 通过已知信息,我们可以大致想象一下,有两种方式——通过下标来寻找\0的下标、通过寻找\0来寻找\0的下标;
\0的下标。那现在问题来了我如何知道这个下标是不是\0的下标呢?好像这个方式无法实现,那我们再来看另一种方式;\0来寻找元素下标的话,那我就需要通过判断此时的元素是否是\0,如果不是,再判断下一个元素,直到找到\0为止;像这样看的话好像可行性很高,那我们现在就需要知道如何找到数组中的每个元素了;对于如何找到数组中的元素,这个问题我相信大家心里都是有一个比较明确的方式了——我们可以通过数组元素的地址来找到数组中的元素。
我们知道在内存中数组元素是从低地址到高地址进行连续存放的,相邻的两个元素的地址之间的相差的大小刚好为数组元素的类型所占空间的大小。
也就是在字符数组中,两个相邻的元素之间的地址之间相差的大小为1; 在整型数组中,两个相邻的元素之间的地址之间相差的大小为4;
也就是我只要对数组元素的地址加上一个元素的数据类型所占空间大小那就能得到下一个元素的地址了。
从指针+-整数的结论我们可以得到:
那现在首元素我是知道的,我们可以通过取地址操作符&将首元素的地址取出来,再传给函数my_stlen;这样我们是不是就可以通过指针+1的方式来找到后面的所有元素了呢?
在数组中我们也介绍过数组名就是数组首元素的地址,所以此时的实参我们需要传入的是数组名,那么形参就需要通过指针来进行接收也就是char* name;
现在我们以及确定了函数的返回类型以及函数的参数,我们现在只需要理清函数的实现思路就可以了; 在探讨函数参数时我们已经有了一个大致的思路:
\0;\0为止;从这个思路中,我们可以明确此时的函数是需要通过循环语句实现的,下面我们开始编写代码:
//strlen函数的模拟实现——寻找`\0`的数组下标
int my_strlen(char* ch)
{
int i = 0;//数组下标
while (*ch != '\0')//判断元素是否为\0
{
ch++;//地址+1,找到下一个元素
i++;//下标+1,找到下一个元素的下标
}
//当结束循环时,说明已经找到了\0,此时我们只需要将下标返回给函数就行
return i;
}下面我们来测试一下:

从测试结果中我们可以看到,此时确实模拟实现了strlen函数。这里看似我们是通过找到\0的下标来实现的,实质上是因为首元素下标为0,最后一个元素的下标减去首元素的下标还是为最后一个元素的下标。因此这种实现方式我们实质上是通过最后一个元素下标减去首元素下标实现的;
这时有朋友就会说了,既然指针-指针的结果与下标之间的差值相同,那我能不能通过\0的指针减去首元素的指针来实现strlen函数呢?当然可以了,下面我们来通过指针-指针的方式实现;
我们想要通过指针-指针的方式实现的话,那我们就需要记录首元素的指针以及\0的指针才行,接下来我们就通过my_strlen2来实现strlen函数:
//strlen函数的模拟实现——指针-指针
int my_strlen2(char* ch)
{
char* last = ch;
//ch——首元素的指针
//last——\0的指针
while (*last != '\0')//判断元素是否为\0
last++;//地址+1,找到下一个元素地址
//当结束循环时,说明已经找到了\0,此时我们只需要将\0与首元素的指针的差值返回给函数就行
return last - ch;
}可以看到,相比于通过下标实现,此时的代码相对来说就简洁了一点,下面我们来测试一下:

可以看到通过这种指针-指针的方式我们也很好的模拟实现了strlen函数。
可以看到,这两种实现方式都是通过迭代实现的,下面我们来拓展一下思维,通过函数递归来实现strlen函数
此时如果通过递归实现的话,我们就需要思考如何进行递归。
我们现在的起点是首元素,终点是
\0,从起点到终点我们需要做的事情就是两个:
\0;也就是说如果我要递归的话,那我递归一次就能找到下一个元素;
如果我递归n次才找到\0,那就说明此时的字符串长度为n;
我如果能将递归的次数记录下来,是不是就可以得到字符串的长度了;
有了具体的思路,下面我们就开始实现,此时我们将函数命名为my_strlen3:
//strlen函数的模拟实现——函数递归
int my_strlen3(char* ch)
{
if (*ch == '\0')
//当找到\0时,就要开始回归了,此时的递进次数为0
return 0;
//当没找到\0时,需要先将指针自增,再进行递进
return 1 + my_strlen3(++ch);
}接下来咱们来验证一下:

可以看到,此时我们也很好的实现了strlen函数。
经过这个模拟strlen函数的实战练习,我相信大家对指针+-整数以及指针-指针的运算方式都已经熟悉了。在开始介绍下一种运算之前我们需要注意:
为了更好的说明第二点,下面我们来看这两个例子:

可以看到,此时变量a和变量b的指针相减的结果并不是说a到b之间有三个元素,大家如果理解了函数栈帧的话,那就应该知道,此时的结果是代表变量a与变量b之间有3个空间;

在这个情况下,它们两个的差值为61,这是说明此时变量a与变量b的地址之间有61个空间;
对于获取两个变量之间的空间个数意义不大,所以指针-指针的应用主要是在数组中进行使用;
接下来我们来看一下指针的关系运算;
既然指针与指针可以进行相减,那么指针之间也是能够比较大小的,下面我们来看一个例子:

可以看到,此时指针pa与指针pb之间正常的进行了大小的比较,从比较结果中我们得知pa的值大于等于pb的值。
通过这个例子,我相信大家很容易就能理解这个运算。这种运算方式可以让我们在进行条件判断时又多了一种新的思路:
下面我们来做一道题:
编写函数,通过指针来完成对数组的初始化
如果这里没有说通过指针来完成的话,那我们按照之前的思路就是通过数组下标来访问数组的每一个元素,从而实现对数组的初始化,代码如下:
//编写函数,完成对数组的初始化
void init_arr(int arr[], int sz)
{
for (int i = 0; i < sz; i++)
arr[i] = 0;
}现在我们可以通过指针的方式来进行,代码如下:
//编写函数,通过指针来完成对数组的初始化
void init_arr2(int* arr, int sz)
{
for (int* p = arr + sz; p > arr;)
*(--p) = 0;
}在这个代码中,我们通过将数组最后一个元素的下一个地址赋值给指针p,此时的p的地址肯定是比首元素的地址大的,所以此时我们通过先将p进行自减1,再来解引用完成初始化。当p的地址自减为首元素地址并完成初始化之后再进行判断时,此时条件不成立,结束循环;
这一题就是一个简单的使用指针的关系运算的例子,大家只需要通过这个例子知道指针的这种运算方式,并在之后的解题过程中对解题方式有一个新的解题思路那就可以了。
指针的运算咱们就介绍到这里,希望大家通过这个章节的内容能够对指针的运算有一个新的理解。接下来我会继续给大家带来指针的相关知识,大家记得关注哦!最后,感谢各位的翻阅,咱们下一篇再见!