在上一篇有关指针的博客中,我们介绍了指针的基础知识,如:内存与地址,解引用操作符,野指针等,今天我们将更加深入的学习指针的其他知识。
1.指针的使用和传址调用
1.1strlen的模拟实现
库函数strlen的功能是求字符串长度,统计的是字符串中\0之前的字符个数
函数原型如下:
size_t strlen (const char* str);
参数str接收一个字符串的起始地址,然后开始统计字符串中\0之前的字符个数,最终返回长度。关于strlen函数的详细介绍网页:
strlen - C++ Referencehttps://legacy.cplusplus.com/reference/cstring/strlen/?kw=strlen我们要实现该函数的模拟实现,就要从字符串起始地址开始逐个遍历字符,只要不是\0字符,计数器就加一, 直到\0结束。
#include<stdio.h>
#include<assert.h>int my_strlen(char* str)
{assert(str != NULL);char* pz = str;while (*pz != '\0'){pz++;}return pz - str;
}int main()
{int len = my_strlen("abcdef");printf("%d\n", len);return 0;
}
在代码里,我们用到了assert断言,他帮助我们判断str是否为空指针,如果是,程序会报错。
1.2传值调用和传址调用
void Swap(int x, int y)
{int temp = x;x = y;y = temp;
}int main()
{int num1 = 10;int num2 = 20;printf("交换前:num1 = %d,num2 = %d\n", num1, num2);Swap(num1, num2);printf("交换后:num1 = %d,num2 = %d\n", num1, num2);return 0;
}
代码运行结果:
我们发现没有产生预期的效果,为什么呢?调试观察一下:
实参传递给形参的时候,形参会单独创建⼀份临时空间来接收实参,对形参的修改不影响实参。
所以上述代码实际是不符合题目要求的。
该怎样设计代码呢,既然我们把交换数值传到函数内无法实现交换,那如果传输的是地址呢,同一份内存空间还会不会失败?
void Swap(int* px, int* py)
{int* temp = px;*px = *py;*py = *temp;
}int main()
{int num1 = 10;int num2 = 20;printf("交换前:num1 = %d,num2 = %d\n", num1, num2);Swap(&num1, &num2);printf("交换后:num1 = %d,num2 = %d\n", num1, num2);return 0;
}
查看结果,我们发现交换成功了。
调用Swap函数的时候是将变量的地址传递给了函数,这种函数调用方法叫:传址调用。
传址调用,可以让函数和主调函数之间建立真正的联系,在函数内部可以修改主调函数中的变量;所以未来函数中只是需要主调函数中的变量值来实现计算,就可以采用传值调用。如果函数内部要修改主调函数中的变量的值,就需要传址调用。
2.数组名的理解
在上篇博客我们在使用指针访问数组的内容时,有这样的代码:
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int* p = arr;
这里我们使用arr的方式拿到了数组第一个元素的地址,有些人可能会有疑惑?不应该&arr[0]才是数组第一个元素的地址吗,其实,大部分情况这两种写法是一样的意思,并没有含义的不同,我们来做一个测试。
我们发现数组名和数组首元素的地址打印出的结果一模一样 ,数组名就是数组首元素的地址。
这时候可能有同学有疑问?数组名如果是数组首元素的地址,那下面的代码又该如何理解?
我们看到上述代码的结果是40,如果arr是数组首元素的地址,其结果应该是4/8才对。
其实数组名就是数组首元素的地址是对的,但是有两个例外:
• sizeof(数组名),sizeof中单独放数组名,这里的数组名表示整个数组,计算的是整个数组的大小,单位是字节• &数组名,这里的数组名表示整个数组,取出的是整个数组的地址(整个数组的地址和数组首元素的地址是有区别的)除此之外,任何地方使用数组名,数组名都表示首元素的地址。
这时有好奇的同学,尝试了如下代码:
三个打印结果一模一样,他就不会区分了,明明结果是一样,含义却不同吗?
我们再看下面这个代码:
3.使用指针访问数组
int main()
{int arr[10] = {0};int* p = arr;int sz = sizeof(arr) / sizeof(arr[0]);for (int i = 0; i < sz; i++){scanf("%d", p + i);}for (int i = 0; i < sz; i++){printf("%d\n", *(p + i));printf("%d\n", p[i]);//这一行代码效果与上一行代码效果完全相等}return 0;
}
在该代码中,将*(p+i)换成p[i]也是能够正常打印的,所以本质上p[i] 是等价于 *(p+i)。
4.一维数组传参的本质
我们发现在函数内部无法正确获取数组的元素个数。这是为什么呢?
这时候就要学习数组传参的本质了 ,之前说过,数组名是数组首元素的地址,那么在数组传参的时候,传递的是数组名,本质上传递的其实是数组首元素地址,并不会传递整个数组,这两者是有区别的。
void test(int arr[])
{int sz2 = sizeof(arr) / sizeof(arr[0]);printf("sz2 = %d\n", sz2);
}void test(int* p)
{int sz2 = sizeof(p) / sizeof(p[0]);printf("sz2 = %d\n", sz2);
}
这两者是等效的。
总结:⼀维数组传参,形参的部分可以写成数组的形式,也可以写成指针的形式。
5.二级指针

如上图,a是整型变量,他的地址存放在pa中,pa的类型是int*,pa的地址又存放在ppa中,ppa的类型是int**,这就是二级指针。
对于二级指针的运算有:
int a = 20;
*ppa = &a;
*ppa 通过对ppa中的地址进⾏解引用,这样找到的是 pa , *ppa 其实访问的就是 pa。
**ppa = 30;
//等价于*pa = 30
//等价于a = 30
**ppa 先通过 *ppa 找到 pa ,然后对 pa 进⾏解引⽤操作: *pa ,那找到的是 a 。
6.指针数组
6.1指针数组概念
这是一个典型的指针数组,他的每一个元素都是地址,只想某一块区域。
6.2指针数组模拟二维数组
int main()
{int arr1[5] = { 1,2,3,4,5 };int arr2[5] = { 2,3,4,5,6 };int arr3[5] = { 3,4,5,6,7 };int* arr[3] = { arr1,arr2,arr3 };for (int i = 0; i < 3; i++){for (int j = 0; j < 5; j++){printf("%d ", arr[i][j]);//printf("%d ", *(*(arr+i)+j));//两行效果一致}printf("\n");}return 0;
}
arr[i]是访问arr数组中的元素,arr[i]找到的数组元素指向了一个一维整型数组,所以arr[i][j]就是一维整型数组的元素。