欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 财经 > 创投人物 > C语言超详细指针知识(二)

C语言超详细指针知识(二)

2025/4/15 1:07:58 来源:https://blog.csdn.net/2302_79963723/article/details/147160795  浏览:    关键词:C语言超详细指针知识(二)

在上一篇有关指针的博客中,我们介绍了指针的基础知识,如:内存与地址,解引用操作符,野指针等,今天我们将更加深入的学习指针的其他知识。

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;
}

 代码运行结果:
 

我们发现没有产生预期的效果,为什么呢?调试观察一下:

 

我们发现在main函数内部,创建了num1和num2,num1的地址是0x0019f7e8,num2的地址是0x00197fdc,在调用Swap函数时,将num1和num2传递给了Swap函数,在Swap函数内部创建了形参x和y接收num1和num2的值,但是x的地址是0x0019f704,y的地址是0x0019f708,x和y确实接收到了num1和num2的值,不过x的地址和num1的地址不⼀样,y的地址和num2的地址不⼀样,相当于x和y是独⽴的空间,那么在Swap函数内部交换x和y的值,⾃然不会影响num1和num2,当Swap函数调⽤结束后回到main函数,num1和num2的没法交换。Swap1函数在使用的时候,是把变量本身直接传递给了函数,这种调用函数的⽅式我们之前在函数的时候就知道了,这种叫传值调用。
实参传递给形参的时候,形参会单独创建⼀份临时空间来接收实参,对形参的修改不影响实
参。

 所以上述代码实际是不符合题目要求的。

该怎样设计代码呢,既然我们把交换数值传到函数内无法实现交换,那如果传输的是地址呢,同一份内存空间还会不会失败?

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中单独放数组名,这里的数组名表示整个数组,计算的是整个数组的大小,单位是字节
• &数组名,这里的数组名表示整个数组,取出的是整个数组的地址(整个数组的地址和数组首元素的地址是有区别的)
除此之外,任何地方使用数组名,数组名都表示首元素的地址。

 这时有好奇的同学,尝试了如下代码:

三个打印结果一模一样,他就不会区分了,明明结果是一样,含义却不同吗?

我们再看下面这个代码:
 

我们发现&arr[0]和&arr[0]+1相差4个字节,arr和arr+1 也相差4个字节,是因为&arr[0] 和 arr 都是首元素的地址,+1就是跳过⼀个元素。
但是&arr 和 &arr+1相差40个字节,这就是因为&arr是整个数组的地址,+1 操作是跳过整个数组的。
到这里⼤家应该搞清楚数组名的意义了吧。
数组名是数组首元素的地址,但是有2个例外。

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.一维数组传参的本质

我们发现在函数内部无法正确获取数组的元素个数。这是为什么呢?

这时候就要学习数组传参的本质了 ,之前说过,数组名是数组首元素的地址,那么在数组传参的时候,传递的是数组名,本质上传递的其实是数组首元素地址,并不会传递整个数组,这两者是有区别的。

所以函数形参的部分理论上应该使用指针变量来接收首元素的地址。那么在函数内部我们写
sizeof(arr) 计算的是⼀个地址的大小(单位字节)而不是数组的大小(单位字节)。
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]就是一维整型数组的元素。

要注意,上述的代码虽然模拟出了二维数组的效果,实际上却不是真正的二维数组,因为每一行并非是连续的。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词