C++学习day--17 二级指针、数组指针、指针数组

news2024/11/15 3:41:53

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。

总结就是:对指针的解引用就是找到该指针保存的地址,然后取出改地址空间的内容!!

 二级指针的用途:

1. 普通指针可以将变量通过参数“带入”函数内部, 但没办法将内部变量“带出”函数 。可能你不理解,下面给一个代码例子你就知道了。

 这幅图呢就是说学习了指针后,我们可以通过指针传递来改变形参的值。但是不能把局部函数里面的局部变量的值带出来!这句话什么意思??我们看个代码试试:

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、数组与指针的区别:

数组:数组是用于储存多个相同类型数据的集合。
指针:指针是一个变量,但是它和普通变量不一样,它存放的是其它变量在内存中的地址。
1. 赋值
数组:只能一个一个元素的赋值或拷贝
指针:指针变量可以相互赋值
2. 表示范围
数组有效范围就是其空间的范围,数组名使用下表引用元素,不能指向别的数组
指针可以指向任何地址,但是不能随意访问,必须依附在变量有效范围之内
3. sizeof
数组:
数组所占存储空间的内存: 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、传参

普通数组传参:

数组传参时,会退化为指针 !
1 )退化的意义: C 语言只会以值拷贝的方式传递参数,参数传递时,如果只拷贝整个数
组,效率会大大降低,并且在参数位于栈上,太大的数组拷贝将会导致栈溢出。
(2)因此, C 语言将数组的传参进行了退化。将整个数组拷贝一份传入函数时,将数组名
看做常量指针, 传数组首元素的地址 这也是为什么传递数组的时候要单独传递数组的长度的原因了,因为在子函数里sizeof(数组名)/sizeof(数据类型)始终为1(在32位平台始终为1,在64位平台上始终为2)。所以要单独传递数组长度

 

#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那个二级指针,我们说了,它传递过来的是首元素地址,而首元素本身又是一个指针,因此需要两次解引用操作才能访问到最终的值

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/823891.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

操作系统使用免密登录

服务器免密登录 背景 在工作中使用密码登录有时候会出现这样或者那样的不方便&#xff0c;一是密码要输入&#xff0c;如果明文输入则不安全&#xff0c;二则一旦修改密码要重新分发到有权限的小伙伴 场景 在之前的工作中有很多场景需要免密等登录&#xff0c;使用免密带来…

Lvs missing port问题实例

我正在「拾陆楼」和朋友们讨论有趣的话题,你⼀起来吧? 拾陆楼知识星球入口 LVS相关文章链接: LVS 流程 SVS 流程 LVS extract net方法

QMLDay2:圆角按钮,关联键盘左右键,鼠标点击。状态切换控制。

QMLDay2 test1 作用&#xff1a; 圆角按钮&#xff0c;关联键盘左右键&#xff0c;鼠标点击。状态切换控制。 代码&#xff1a; import QtQuick 2.15 import QtQuick.Window 2.15 import QtQuick.Controls 2.15Window {width: 640height: 480visible: truecolor: "wh…

H.265/HEVC 速率控制

文章目录 速率控制视频编码速率控制速率控制的基本原理缓冲机制速率控制技术 H.265/HEVC 速率控制1. 目标比特分配2. 量化参数确定 速率控制 目前实际的视频编码率失真优化过程包括两部分&#xff1a;速率控制部分将视频序列分成编码单元&#xff0c;考虑编码单元的相关性通过…

C#,数值计算——t-分布(Student distribution)的计算方法与源程序

在概率论和统计学中&#xff0c;学生t-分布&#xff08;Students t-distribution&#xff09;经常应用在对呈正态分布的总体的均值进行估计。它是对两个样本均值差异进行显著性测试的学生t测定的基础。t检定改进了Z检定&#xff08;en:Z-test&#xff09;&#xff0c;不论样本数…

【PyQt实现复现框CheckBox】

PyQt实现复现框CheckBox 1 安装环境2 CtrlN&#xff0c;新建Main Window窗口&#xff0c;保存为checkBox.ui文件3 CheckBox的三种状态4 实现通用复选框的选中状态设置用户权限功能 1 安装环境 1&#xff09;Python环境安装PyQt5、PyQt-sip、PyQt5Designer、PyQt5-tools 2&…

【搜索】BFS中的最短路模型

算法提高课笔记 目录 单源最短路迷宫问题题意思路代码 武士风度的牛题意思路代码 抓住那头牛题意思路代码 多源最短路矩阵距离题意思路代码 双端队列BFS电路维修题意思路代码&#xff08;加了注释&#xff09; BFS可以解决边权为1的最短路问题&#xff0c;下面是相关例题 单源…

Mybatis 知识点

Mybatis 知识点 1.1 Mybatis 简介 1.1.1 什么是 Mybatis Mybatis 是一款优秀的持久层框架支持定制化 SQL、存储过程及高级映射Mybatis 几乎避免了所有的 JDBC 代码和手动设置参数以及获取结果集MyBatis 可以使用简单的 XML 或注解来配置和映射原生类型、接口和 Java 的 POJO…

PyTorch深度学习实战(8)——批归一化

PyTorch深度学习实战&#xff08;8&#xff09;——批归一化 0. 前言1. 批归一化原理2. 批归一化优势3. 批归一化对模型训练的影响3.1 未使用批归一化&#xff0c;且输入值较小3.2 使用批归一化&#xff0c;且输入值较小3.3 使用批归一化&#xff0c;且输入值较大 小结系列链接…

Redis—环境搭建

Redis—环境搭建 &#x1f50e;Centos 安装 Redis5创建符号链接修改配置文件启动 Redis停止 Redis &#x1f50e;Centos 安装 Redis5 Centos8 安装 Redis5 yum install -y redisCentos7 安装 Redis5 Centos7 中 yum 源提供的 Redis 版本是 Redis3(有点老), 因此先安装 scl 源 …

算法综合篇专题二:滑动窗口

“在混沌想法中&#xff0c;最不可理喻念头。” 1、长度最小的子数组 (1) 题目解析 (2) 算法原理 class Solution { public:int minSubArrayLen(int target, vector<int>& nums) {int n nums.size();int sum 0;int len INT_MAX;for(int left0,r…

mysql进阶-用户的创建_修改_删除

1. 使用mysql单次查询 [rootVM-4-6-centos /]# mysql -h localhost -P 3306 -p mytest -e "select * from book1"; Enter password: ------------------------------------------- | id | category_id | book_name | num | ----------------------------…

数据结构 | 基本数据结构——队列

目录 一、何谓队列 二、队列抽象数据类型 三、用Python实现队列 四、模拟&#xff1a;传土豆 五、模拟&#xff1a;打印任务 5.1 主要模拟步骤 5.2 Python实现 一、何谓队列 队列是有序集合&#xff0c;添加操作发生在“尾部”&#xff0c;移除操作则发生在“头部”。新…

【Javascript】基础知识

文章目录 01 变量的声明02 数据类型字符串型boolean类型undefined null类型symbol类型超大整数 bigint数组类型普通对象 01 变量的声明 02 数据类型 复习: 声明 ​ 声明变量关键词 ​ let ​ const ​ 变量名 >变量命名规范 ​ 英文 数字 _ $不要以数字开头 ​ 见名知意 ​…

深度学习之tensorboard可视化工具

(1)什么是tensorboard tensorboard是TensorFlow 的一个可视化工具包&#xff0c;提供机器学习实验所需的可视化和工具&#xff0c;该工具的功能如下&#xff1a; 跟踪和可视化指标&#xff0c;例如损失和精度可视化模型图&#xff08;操作和层&#xff09;查看权重、偏差或其…

【Java多线程学习4】volatile关键字及其作用

说说对于volatile关键字的理解&#xff0c;及的作用 概述 1、我们知道要想线程安全&#xff0c;就需要保证三大特性&#xff1a;原子性&#xff0c;有序性&#xff0c;可见性。 2、被volatile关键字修饰的变量&#xff0c;可以保证其可见性和有序性&#xff0c;但是volatile…

uniApp 对接安卓平板刷卡器, 读取串口数据

背景: 设备: 鸿合 电子班牌 刷卡对接 WS-B22CS, 安卓11; 需求: 将刷卡器的数据传递到自己的App中, 作为上下岗信息使用, 以完成业务; 对接方式: 1. 厂家技术首先推荐使用 接收自定义广播的方式来获取, 参考代码如下 对应到uniApp 中的实现如下 <template><view c…

python数据可视化Matplotlib

1.绘制简单的折线图 # -*- coding: utf-8 -*- import matplotlib.pyplot as pltinput_values [1, 2, 3, 4, 5] squares [1, 4, 9, 16, 25] plt.style.use(seaborn) fig, ax plt.subplots() ax.plot(input_values, squares, linewidth3) # 线条粗细# 设置图表标题并给坐标…

2023年第四届“华数杯”数学建模思路 - 复盘:光照强度计算的优化模型

文章目录 0 赛题思路1 问题要求2 假设约定3 符号约定4 建立模型5 模型求解6 实现代码 0 赛题思路 &#xff08;赛题出来以后第一时间在CSDN分享&#xff09; https://blog.csdn.net/dc_sinor?typeblog 1 问题要求 现在已知一个教室长为15米&#xff0c;宽为12米&#xff0…

less的使用

less的介绍&#xff1a; less使用 1、 less使用的第一种用法&#xff0c;起变量名&#xff0c;变量名区分大小写&#xff1a; 这里我们定义一个粉色变量 我想使用直接把变量拿过来就行 2、vscode使用插件&#xff0c;直接将Css文件转换less文件&#xff1a; 3、第二种用法&…