指针的指针(二)
实例
这里接上一章指针的指针(一)
这里有几个例子程序,用于说明指针表达式的一些常用用法。我们先看下面这张图片,text是一个char类型的二维数组,而cp是指向这个二维数组的一个指针数组,strings是指向cp地址的指针
接下来,我们看一下下面这段代码,我们要在text这个二维数组中查找一个字符,查找方法实现在find_char这个函数中
#include <stdio.h> #include <assert.h> #define TRUE 1 #define FALSE 0 int find_char(char **strings, int value) { assert(strings != NULL); //<8> while (*strings != NULL) //<9> { while (**strings != '\0') //<10> { if (*(*strings)++ == value) //<11> { return TRUE; } } strings++; //<12> } return FALSE; } int main(int argc, char const *argv[]) { char text[6][10] = {"spring", "struts", "hibernate", "flask", "numpy", "sklearn"}; //<1> char *cp[6]; //<2> int i = 0; for (; i < 6; i++) { cp[i] = text[i]; //<3> } char **strings = &cp[0]; //<4> char *before = cp[0]; //<5> printf("before find_char:\n"); for (i = 0; i < 6; i++) { printf("cp[%d] = %p\n", i, cp[i]); //<6> } int res = find_char(strings, 'g'); //<7> printf("res = %d\n", res); printf("after find_char:\n"); for (i = 0; i < 6; i++) { printf("cp[%d] = %p\n", i, cp[i]); //<13> } char *after = cp[0]; //<14> printf("before = %p\nafter = %p\ndiff = %d\n", before, after, (after - before)); //<15> return 0; }
现在我们来分析一下上面的程序,下面的标号与程序注释中的标号一一对应
- 我们先声明一个名称为text的二维数组,这个数组里有6个字符串,每一个字符串都是字符数组
- 声明一个名为cp的指针数组
- 将指针数组中的每个元素,指向text每个字符串的起始地址
- 声明一个指向指针的指针strings,用指针数组cp的起始位置(即&cp[0])来初始化
- 这里声明一个指针before,保存cp[0]最开始指向的位置
- 打印指针数组每个元素指向的位置
- 传入指向指针的指针stings,和我们要查找的字符'g'
- 这里,我们进入到find_char()函数中,检查传入指向指针的指针是否为NULL,
- 如果strings保存的值不为空,则进入循环
- 简单分析一下**strings,从右到左,strings保存的是一个指针数组的初始位置,所以*strings首先间接访问到cp指针数组的第一个值,这里是一个地址,**strings则是再次对cp数组第一个元素所保存的地址进行间接访问,访问到text第一个字符串的第一个元素,为一个字符,这里判断当前字符是否为'\0',如果是则代表到了字符串的末尾了。如果**strings当前的指向的字符不为'\0',则进入循环
- *(*strings)++是一个很有趣的表达式,我们来分析一下,首先是*strings参与运算,strings指向的是cp第一个元素的地址,所以*strings则是指向cp第一个元素的值,然后再进行++运算,这里先返回一个*string的值的拷贝,再对*strings指向的那个值+1,即在cp第一个元素所保存的值+1,这时候cp已经指向text下一个字符串的起始地址,而前一个*拿着之前(*string)++的运算结果,这里还是指向text的第一个地址,然后用*进行间接访问,获取该地址的值,比较是不是要查找的value元素。(*string)++会一直循环,即cp第一个元素指向的地址不断往后,一直到*(*strings)++指向一个'\0',代表当前的字符串(即数组)结束
- string++则是将string当前保存的地址指向后一个地址
- 执行完find_char()函数后,我们打印cp指针数组中每一个元素的值,如果不出意料,cp元素第一个值已经和执行find_char()之前不一样了
- 初始化一个指针after,将cp指针第一个元素的值赋给它,用于和之前的before比较
- 打印before、after的值,并计算指针之间的距离
下面,我们运行一下代码看一下效果:
# gcc main.c -o main # ./main before find_char: cp[0] = 0x7fffadcd5250 cp[1] = 0x7fffadcd525a cp[2] = 0x7fffadcd5264 cp[3] = 0x7fffadcd526e cp[4] = 0x7fffadcd5278 cp[5] = 0x7fffadcd5282 res = 1 after find_char: cp[0] = 0x7fffadcd5256 cp[1] = 0x7fffadcd525a cp[2] = 0x7fffadcd5264 cp[3] = 0x7fffadcd526e cp[4] = 0x7fffadcd5278 cp[5] = 0x7fffadcd5282 before = 0x7fffadcd5250 after = 0x7fffadcd5256 diff = 6
确实如我们之前所分析,cp[0]这个元素所保存的值在find_char()执行之前和执行之后有所变化。
上面的程序确实可以在一个二维数组中查找一个字符,但它并不完美,因为主方法main()中的cp变量的值改变了,如果cp这个值在后续还要用到的话,那我们又要重新声明一个指针数组或者复用cp指针数组,将text中每个字符串的地址挨个挨个赋值过去,有没有办法在查找的时候不改变cp变量的值呢?肯定是有的,下面让我们看另外一个find_char()方法,这里省略了main主函数
int find_char(char **strings, char value) { char *string; //<1> while ((string = *strings++) != NULL) //<2> { while (*string != '\0') //<3> { if (*string++ == value) //<4> { return TRUE; } } } return FALSE; }
- 先声明一个指针string
- *strings++这个表达式,首先string++先返回一个strings保存的值的一份拷贝,然后对strings所保存的值+1,指向下一个内存地址,然后再用*进行之前strings所指向的地址间接访问,这里仍然访问的是ch的第一个元素,尽管这时候strings已经指向ch的的第二个元素了
- 通过上一个步骤,string的值为ch指针数组中的一个元素,即为地址,再用*进行间接访问,访问到text中的元素的第一个字符,判断不为'\0'则代表字符串还未结束
- *string++首先是返回string所保存的值的一份拷贝,再对string+1,将string的值指向原先的下一个地址,然后*拿着原先的拷贝进行间接访问,一直循环到找到想要的字符为止
这里,我们将现在的find_char()函数替换上一个find_char()函数,再看看运行结果
# gcc main.c -o main # ./main before find_char: cp[0] = 0x7ffd0e28f970 cp[1] = 0x7ffd0e28f97a cp[2] = 0x7ffd0e28f984 cp[3] = 0x7ffd0e28f98e cp[4] = 0x7ffd0e28f998 cp[5] = 0x7ffd0e28f9a2 res = 1 after find_char: cp[0] = 0x7ffd0e28f970 cp[1] = 0x7ffd0e28f97a cp[2] = 0x7ffd0e28f984 cp[3] = 0x7ffd0e28f98e cp[4] = 0x7ffd0e28f998 cp[5] = 0x7ffd0e28f9a2 before = 0x7ffd0e28f970 after = 0x7ffd0e28f970 diff = 0
这里我们可以看到,执行find_char()函数后,没有再改变ch变量的值