1、二级指针
二级指针的定义:
int guizi1 = 888;int *guizi2 = &guizi1; //1 级指针,保存 guizi1 的地址int **liujian = &guizi2; //2 级指针,保存 guizi2 的地址, guizi2 本身是一个一级指针变量
定义的时候有几个*就是几级指针。 二级指针就是保存一级指针地址的指针!二级指针只能保存一级指针的地址,不能保存普通变量的地址!
看代码:
#include <stdlib.h>
int main(void) {
int guizi2 = 888; //存枪的第 2 个柜子
int* guizi1 = &guizi2; //存第 2 个柜子地址的第一个柜子
int** liujian = &guizi1; //手握第一个柜子地址的刘建
printf("刘建打开第一个柜子,获得第二个柜子的地址:0x%p\n", *liujian);
printf("guizi2 的地址:0x%p\n", &guizi2);
int* tmp;
tmp = *liujian;
printf("访问第二个柜子的地址,拿到枪:%d\n", *tmp);
printf("刘建一步到位拿到枪:%d\n", **liujian); //缩写成 **liujian
system("pause");
return 0;
}
运行结果:
这个代码是二级指针的应用,有点不是好理解。我们看图说话:
指针变量liujian是一个二级指针,那么对二级指针进行解引用得到什么?我们知道二级指针存的是一级指针guizi1的地址,那么对二级指针进行解引用就是找到一级指针guizi1的地址,然后取出改地址空间里面的内容,里面的内容是变量guizi2的地址。因此这里对二级指针liujian进行一次解引用就得到guizi2的地址。
同理如果对指针变量liujian进行两次解引用那么就得到最后的变量guizi2的值888。
总结就是:对指针的解引用就是找到该指针保存的地址,然后取出改地址空间的内容!!
二级指针的用途:
这幅图呢就是说学习了指针后,我们可以通过指针传递来改变形参的值。但是不能把局部函数里面的局部变量的值带出来!这句话什么意思??我们看个代码试试:
void boy_home(int* meipo) {
static int boy = 23;
*meipo = boy;
}
int main(void) {
int* meipo = NULL;
boy_home(meipo);
printf("boy: %d\n", *meipo);
system("pause");
return 0;
}
运行结果会是什么呢?是不是23?有不少人说是,也有人说不是,还有很多人不知道结果具体是什么?那我们运行看看结果:
居然没有值??当然我是在VS2022上运行的,如果你在VC2010上运行可能不是这个结果,可能师哥随机值。但是不是23,这是为什么?指针不是址传递吗?为什么没有传递过来?或者说要使结果为23应该怎么做?
我们把代码改为如下试试:
void boy_home(int** meipo) {
static int boy = 23;
*meipo = &boy;
}
int main(void) {
int* meipo = NULL;
boy_home(&meipo);
printf("boy: %d\n", *meipo);
system("pause");
return 0;
}
运行结果:
惊奇地发现结果为23?为什么呢?
如果我还没说你就知道原因,恭喜你,指针你掌握地很好了。现在我来说说为什么,虽然第一种形参是传递了一个指针过去,但是你要记住,形参和实参最终不是一个东西,函数调用完,形参变量的空间是会被释放掉的!!因此第一个代码,实际上是这样的:
主函数main里的meipo和子函数by_home(int* meipo)形参的meipo不是一块空间,因此子函数里面的meipo指向boy以后,函数调用结束后就被释放掉了。而实际数函数的meipo并没有赋值成功。我们现在把数函数的meipo的地址(二级指针)传递过去,那么形参接受的就是二级指针,此时如下:
假如main函数里的meipo地址是0X200,那么实参收到的也是0X200,此时再对它解引用就是操作主函数meipo的值。这就是二级指针的用法。不知道大家理解了没有,如果没理解在仔细看看。当然二级指针的用法可读性不强,后面学习了引用会发现就好多了。
2、多级指针
注意多级指针一般指的是3级及其以上的指针。但是要注意,其实前面也提到过:
多级指针只能指向次一级指针,三级指针只能指向二级指针的地址,四级指针只能指向三级指针的地址,不能来个三级指针指向一级指针的地址或者指向一个变量的地址!
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int guizi1 = 888;
int* guizi2 = &guizi1; //普通指针
int** guizi3 = &guizi2; //二级指向一级
int*** guizi4 = &guizi3; //三级指向二级
int**** guizi5 = &guizi4; //四级指向三级
printf("柜子 2 拿枪: %d\n", *guizi2);
printf("柜子 3 拿枪: %d\n", **guizi3);
printf("柜子 4 拿枪: %d\n", ***guizi4);
printf("柜子 5 拿枪: %d\n", ****guizi5);
system("pause");
return 0;
}
运行结果:
说白了,几级指针定义的时候就有几个*,但是要一步解引用也是要加几个*才能拿到最终的值。这里多提一嘴,在实际项目开发中,基本用不到三级指针,更别提四级、五级指针了。掌握好二级指针和一级指针才是重点!当然多级指针在面试题中出现的很多。掌握知识点即可!
3、数组指针和指针数组
指针数组是一个数组:
定义: 类型 *指针数组名[元素个数] ;
现在给一个练习,有12个女生的身高,找出最大升高,和第二大身高的值。
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int girls[4][3] = {
{173, 158, 166},
{168, 155, 171},
{163, 164, 165},
{163, 164, 172} };
int* qishou[2];//定义一个有两个元素的指针数组,每个元素都是一个指针变量
//qishou[0]保存最大值,qishou[1]保存第二最大值
if (girls[0][0] > girls[0][1]) {
qishou[0] = &girls[0][0];
qishou[1] = &girls[0][1];
}
else {
qishou[0] = &girls[0][1];
qishou[1] = &girls[0][0];
}
int i = 0, j = 0;
for (i = 0; i < 4; i++) {
if (i == 0) { //因为第一行占据两个元素了,所有从第三个元素开始比
j = 2;
}
else {
j = 0;
}
for (; j < 3; j++) {
//当前候选者比第二大值还小,不用比了,直接结束本次循环
if (*qishou[1] > girls[i][j]) {
continue;
}
//当前候选者比第二值大,但是比最大值小
if (girls[i][j] <= *qishou[0]) {
qishou[1] = &girls[i][j];
}
else { //当前候选者比最大值还大
qishou[1] = qishou[0];
qishou[0] = &girls[i][j];
}
}
}
printf("最高女兵的身高: %d , 次高女兵的身高: %d\n", *qishou[0], *qishou[1]);
return 0;
}
运行结果:
这道题就是用一个指针数组来存储最大值和次最大值。至于代码逻辑是什么意思,自己可以思考看看,还是有点逻辑哦。
数组指针:
int (*p)[3]; //定义一个指向三个成员的数组的指针int *p[3];//定义一个指针数组也就是说[ ]比*d的优先级更高。区分和前面const修饰谁一样的,看指针变量离[ ]近还是*近,默认比[ ]近。谁近就修饰谁!
数组法: (*p)[j]指针法: *((*p)+j)
用数组指针访问元素试试:
#include <stdio.h>
#include <stdlib.h>
int main()
{
int A[4][3] = {
{173, 158, 166},
{168, 155, 171},
{163, 164, 165},
{163, 164, 172} };
int(*p)[3]; //定义一个指向三个成员的数组的指针,这是一个二级的指针
p = &A[0]; //
//第一种 数组下标法
for(int i=0; i<4; i++){
for(int j=0; j<3; j++){
printf(" %d", (*p)[j]); //(*p) 等同于 a[0] ,a[0][0]等同于 (*p)[0]
}
printf("\n");
p++;
}
return 0;
}
运行结果:
对这个代码有两点要说明,int (*p)[3]是一个数组指针,指向一个数组,并且指向的数组必须是有三个元素。也就是说数组指针定义的时候有几个元素指向的数组也必须是几个元素,否则报错!如下:
其次说数组指针是一个二级指针,这怎么理解呢?之前在讲多级指针的时候,说定义的时候有几个*就是几级指针, 但是现在我们要拓展一下,只要是在定义的时候,遇到*或者遇到[ ]指针级别都会加1,因此这是个二级指针,而且给数组指针赋值时,得赋值整个数组的地址,不能赋值数组首元素地址!如下:
正确做法:
虽然数组地址和数组首元素地址在值上是一样的,但是意义不一样,通过数组指针我们就体会到了!!而且数组指针+1是跳变整个数组的大小!!数组指针一般用在二维数组居多,通过数组指针指向二维数组的行指针,然后行指针加1跳变整个数组到第二个行指针!
再说一点,大家可能没注意,在打印的时候用的是(*p)[0],可以用*p[0]代替吗??
我们把代码改成这样子试试:
发现结果完全不一样?因为[ ]优先级比*更高,所以p先和[ j ]结合,而p=&A[0],那么就是:
*(&A[0])[j],而后面p[j]每次跳变是整个行指针,因此先打印173、168、163,接着第一次循环结束后,p指向了&A[1],等价于*(&A[1][j]),依然是每次跳变一个行指针,所以打印168、163、163,然后指针又指向&A[2],等价于*(&A[2][j]),但是循环三次会越界,谁也不知道后面的是什么值。所以打印随机值,也就是越界了!!很危险!
因此数组指针怎么定义的就怎么解引用!!
(*p)[j] 不等于 *p[j]!!,前面那种才是正确的用法!!
还是上面那个代码,找出值最小的那个值,代码如下:
#include <stdio.h>
#include <stdlib.h>
int main()
{
int A[4][3] = {
{173, 158, 166},
{168, 155, 171},
{163, 164, 165},
{163, 164, 172} };
int(*p)[3]; //定义一个指向三个成员的数组的指针,这是一个二级的指针
p = &A[0]; //只能赋值整个数组的地址,不能赋值首元素地址!!
int* boy = NULL;
boy = &(*p)[0];//首元素地址
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 3; j++) {
if (*boy > *((*p) + j)) {
boy = (*p) + j;
}
}
p++;//数组指针加1是跳变整个行数组,到下一个数组
}
printf("最小的值是: %d\n", *boy);
return 0;
}
运行结果:
已知道p是一个数组指针,那么*(*p+j)是什么呢,我们说了数组指针p是一个二级指针,*p相当于得到数组的地址,也就是一个一级指针,一级指针加减就是在本数组内跳变,最后在解引用得到最终的值。相当于:
*p=A[0]
(*p+j)=&A[0][j]
*(*p+j)=A[0][j]
4、数组与指针的区别:
数组:只能一个一个元素的赋值或拷贝指针:指针变量可以相互赋值
数组有效范围就是其空间的范围,数组名使用下表引用元素,不能指向别的数组指针可以指向任何地址,但是不能随意访问,必须依附在变量有效范围之内
数组所占存储空间的内存: sizeof (数组名)数组的大小: sizeof (数组名) /sizeof (数据类型)
在 32 位平台下,无论指针的类型是什么, sizeof (指针名)都是 4.在 64 位平台下,无论指针的类型是什么, sizeof (指针名)都是 8.
5、数组指针和指针数组的区别
int *qishou[2];// 定义一个有两个元素的指针数组,每个元素都是一个指针变量int girl1= 167;int girl2 = 171;qishou[0] = &girl1;qishou[1] = &girl2;
int (*p)[3]; // 定义一个指向三个成员的数组的指针访问元素的两种方式:int A[4][3]={{173, 158, 166},{168, 155, 171},{163, 164, 165},{163, 164, 172}};p = &A[0];
数组法: (*p)[j]指针法: *((*p)+j)
6、传参
普通数组传参:
#include <stdio.h>
#include <stdlib.h>
//形参不指定数组大小,用数组的形式传递参数,不需要指定参数的大小,
//因为在一维数组传参时,形参不会真实的创建数组,传的只是数组首元素的地址。
void method_1(int arr[], int len)
{
int len2 = sizeof(arr) / sizeof(arr[0]);
printf("%d\n", len2);
for (int i = 0; i < len; i++) {
printf(" arr[%d] = %d\n", i, arr[i]);
}
}
int main()
{
int arr[10] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
method_1(arr, 10);
return 0;
}
运行结果:(32位平台上)
//方式三: 一维数组传参退化,用指针进行接收,传的是数组首元素的地址
void method_3(int* arr, int len)
{
for (int i = 0; i < len; i++) {
printf(" arr[%d] = %d\n", i, arr[i]);
}
}
int main()
{
int arr[10] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
method_3(arr, 10);
return 0;
}
运行结果:
也就是说接受传递数组,形参可以是一个数组,数组大小可以指明也可以不指名,但是要额外传递数组的长度。形参也可以是一个普通的指针,来保存地址,因为传递的是首元素地址,因此是可以用普通的指针来接受的。
指针数组传参:
// <指针数组传参>
//方式一: 指针数组传参,声明成指针数组,不指定数组大小
void method_4(int* arr[], int len)
{
for (int i = 0; i < len; i++) {
printf(" arr[%d] = %d ", i, *arr[i]);
}
printf("\n");
}
//方式二: 指针数组传参,声明成指针数组,指定数组大小
void method_5(int* arr[10])
{
for (int i = 0; i < 10; i++) {
printf(" arr[%d] = %d ", i, *arr[i]);
}
printf("\n");
}
//方式三: 二维指针传参
//传过去是指针数组的数组名,代表首元素地址,而数组的首元素又是一个指针,就表示二级指针,用二级指针接收
void method_6(int** arr, int len)
{
for (int i = 0; i < len; i++) {
printf(" arr[%d] = %d ", i, *(*(arr + i)));
}
printf("\n");
}
int main()
{
int arr[10] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
int* arr_p[10] = { 0 };
for (int i = 0; i < 10; i++) {
arr_p[i] = &arr[i];
}
method_4(arr_p, 10);
method_5(arr_p);
method_6(arr_p, 10);
}
给出了三个子函数展示了指针数组怎么传参,其实说实话,传递数组我们就普通传递就行了,这中指针数组在实际开发中是很少这样传递的。这里讲一下方法6那个二级指针,我们说了,它传递过来的是首元素地址,而首元素本身又是一个指针,因此需要两次解引用操作才能访问到最终的值!