C语言基础(10)之指针(2)

news/2024/10/7 14:56:57 标签: c语言, 开发语言, visualstudio

        在上一篇文章中我们谈到了指针,并给老铁们讲解了什么是指针、指针类型、野指针以及指针运算等知识。在这篇文章中小编将继续带大家了解指针的相关知识点。

1. 指针和数组

        指针和数组之间又能有什么联系呢?在谈这个之前,我们先来讲讲指针和数组之间的关系

                1.指针变量就是指针变量,不是数组,指针变量的大小是4/8个字节,专门用来存放地址的。
                2.数组就是数组,不是指针,数组是一块连续的空间,可以存放1个或者多个类型相同的数据。

        那既然指针变量是指针变量,数组是数组,二者之间能有什么联系呢?来看看下面的例子:

        从程序的运行结果中可以得知,数组名和数组首元素的地址是一样的。那么可以得出的结论是:数组名表示的是数组首元素的地址。(2种情况除外,数组部分讲解了)

        既然可以把数组名当成地址存放到一个指针中,那我们使用指针来访问数组就成为可能。再看看如下例子:

#include<stdio.h>

int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	int* p = arr;  // 指针用来存放数组首元素的地址
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("&arr[%d] = %p <====> p+%d = %p\n", i, &arr[i], i, p + i);
	}

	return 0;
}

        我们将数组首元素的地址存放在指针p中。从运行结果来看,p+i 其实计算的是数组 arr 下标为i的地址。那我们就可以直接通过指针来访问数组。

        因此指针与数组的联系便是:在数组中,数组名其实就是数组首元素的地址(2种情况除外)。当我们知道数组首元素的地址的时候,因为数组又是连续存放的,所以通过指针就可以遍历访问数组,数组是可以通过指针来访问的。

        个人理解:数组名 == 地址 == 指针。

2. 二级指针

        在次之前,我们谈到的指针都是一级指针,那二级指针是干什么的呢?

        对于一级指针来说,一级指针变量也是变量,是变量就会在内存中开辟空间,是变量就会有地址。因此二级指针变量就是用来存放一级指针变量的地址。

#include<stdio.h>

int main()
{
	int a = 10;
	int* p = &a; // p是一级指针
	
	int** pp = &p; // pp就是二级指针变量

	*(*pp) = 100;

	printf("%d\n", **pp); // 解引用2次
	printf("%d\n", a);

	 同理
	//int*** ppp = &pp;
	
	return 0;
}

        对于上述代码,p是一级指针变量,pp是二级指针变量,int** 其中的 int* 是在说明pp指向的是int*的类型变量,即p的完整类型,*是说明pp是指针变量。

        由于二级指针变量存储的是一级指针变量的地址,因此我们对二级指针pp解引用(即 *pp ),实则访问的是一级指针变量p;所以如果想通过指针pp来找到变量a,那就需要再次进行解引用(即 **pp )。

        换句话说,*pp 等价于 p,对指针 p 解引用找到a,即 *p。所以 *p 等价于 **pp。故 **pp = 100  等价于  *p = 100  等价于  a = 100。

        那么三级指针该怎么写呢?讲到这相信很多老铁都会举一反三了,那便是 int*** ppp = &pp;

3. 指针数组

        指针数组?那究竟是指针还是数组呢?我们可以联想到整型数组、字符数组指的都是数组,那指针数组自然也是指数组了!

        那指针数组是怎样的呢?下面举例来说明:

整型数组:int arr1[5];


字符数组:char arr2[6];


指针数组:int* arr3[5];

所以:

        整型数组  ——  存放整型的数组。
        字符数组  ——  存放字符的数组。
        指针数组  ——  存放指针(地址)的数组。

#include<stdio.h>

int main()
{
	char arr1[] = "abcdef";
	char arr2[] = "hello xxx";
	char arr3[] = "cuihua";

	char* parr[] = { arr1,arr2,arr3 };

	printf("%c\n", *parr[1]); 

	printf("%c\n", *(parr[1] + 1)); 

	printf("%c\n", (*parr)[1]); 

	return 0;
}

        在上述代码中,数组parr便是指针数组了。在前面我们提到,数组名是数组首元素的地址,因此指针数组parr中存储的是三个字符数组首元素的地址,而不是存储的三个数组内的元素。

        那可能就有老铁好奇怎么通过指针数组来访问三个字符数组中的元素了,下面我将对上述代码中三个printf语句进行讲解,来帮助大家更进一步了解指针数组的的使用和解引用

                1. 对于 *parr[1],因为 [] 的优先级要比 * 高,所以parr先于 [] 结合,即取出数组parr中下标为1的元素,即是 数组arr2 首元素的地址,再对其进行解引用操作,所以取出的是 数组arr2 的首元素,为 h。

                2. 对于 *(parr[1] + 1),同样的 parr[1] 是 数组arr2 首元素的地址。我们可以假设 char* p = parr[1],则 p = arr2,所以 *(parr[1] + 1) 等价于 *(p + 1) 等价于 arr2[1]。故取出的是 数组arr2 中下标为1的元素,即是 e 。

                3. 对于 (*parr)[1],这里与第一点不同,因为有(),所以 parr 先于 * 结合。而指针数组也是数组,parr是数组名,故对数组名解引用 (*parr) 则是取出数组首元素,即是 数组arr1 首元素的地址。所以 *parr 等价于 arr1,即 (*parr)[1] 等价于 arr1[1],故结果为 b

        那指针数组有什么作用呢?如果我们想要打印数组arr1,arr2,arr3,此时指针数组parr就类似于二位数组,通过双层循环便可将三个数组的内容输出出来。

#include<stdio.h>

int main()
{
	int arr1[] = { 1,2,3,4,5 };
	int arr2[] = { 5,6,7,8,9 };
	int arr3[] = { 0,2,4,6,8 };

	// 指针数组
	int* parr[] = { arr1,arr2,arr3 };

	// 类似于(模拟)的二维数组
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		int j = 0;
		for (j = 0; j < 5; j++)
		{
			//printf("%d ", *(parr[i] + j));
			printf("%d ", parr[i][j]);
		}
		printf("\n");
	}

	return 0;
}

4. const 修饰指针的理解

        在之前我们谈到const 修饰的变量 变成了常量,即该变量的值不可修改。那么当const修饰指针时,指针是否可修改呢?下面我将通过两个例子来为各位老铁们讲解。

        如上两图中,我们用const修饰指针p。此时const 放在 * 的左边,再通过指针修改其指向的内容时,编译器无法编译通过会报错;而修改指针变量指向的对象时,代码正常运行。也就是说,当const 放在 * 的左边时,限制的是指针指向的内容,不能通过指针变量来改变指针指向的内容,但指针变量的本身是可以改变的。

        如上两图中,同样用const修饰指针变量p,但此时 const 放在了 * 的右边,我们通过指针修改其指向的内容时,编译器却能正常运行了,而修改指针变量所指向的对象时,编译器却报错。也就是说,当 const 放在 * 右边时,限制的时指针变量本身,指针变量本身是不能改变的,但是指针指向的内容是可以通过指针来改变的。

总结:

        1. 当const 放在 * 的左边时,限制的是指针指向的内容,不能通过指针变量来改变指针指向的内容,但指针变量的本身是可以改变的。(限制的是 *p)

        2. 当 const 放在 * 右边时,限制的时指针变量本身,指针变量本身是不能改变的,但是指针指向的内容是可以通过指针来改变的。(限制的是 p)

        不知道各位老铁们看完这篇文章后,有没有对指针有更进一步的了解呢?那我们下篇文章见吧!


http://www.niftyadmin.cn/n/5692951.html

相关文章

SpringBoot在线教育平台:设计与实现的深度解析

2相关技术 2.1 MYSQL数据库 MySQL是一个真正的多用户、多线程SQL数据库服务器。 是基于SQL的客户/服务器模式的关系数据库管理系统&#xff0c;它的有点有有功能强大、使用简单、管理方便、安全可靠性高、运行速度快、多线程、跨平台性、完全网络化、稳定性等&#xff0c;非常…

CUDA与TensorRT学习五:TensorRT API的基本使用

文章目录 一、MINISUT-model-build-infer二、build-model三、infer-model四、TensorRT-network-structure五、build-model-from-scratch六、build-trt-module七、custom-trt-module八、plugin-unit-test(pythoncpp) 一、MINISUT-model-build-infer 二、build-model 三、infer…

VSCode debug模式无法跳转进入内置模块

在使用VSCode调试python代码的时候&#xff0c; 需要查看第三方库的代码&#xff0c;进行调试。 但是VSCode默认是不进入的&#xff0c; 因此需要更改Debug配置&#xff1a; 在launch.json 里加入如下的代码&#xff1a; "justMyCode": false 这样就能进入第三方库…

计算机网络面试题——第三篇

1. TCP超时重传机制是为了解决什么问题 因为TCP是一种面向连接的协议&#xff0c;需要保证数据可靠传输。而在数据传输过程中&#xff0c;由于网络阻塞、链路错误等原因&#xff0c;数据包可能会丢失或者延迟到达目的地。因此&#xff0c;若未在指定时间内收到对方的确认应答&…

Kotlin真·全平台——Kotlin Compose Multiplatform Mobile(kotlin跨平台方案、KMP、KMM)

前言 随着kotlin代码跨平台方案的推出&#xff0c;kotlin跨平台一度引起不少波澜。但波澜终归没有掀起太大的风浪&#xff0c;作为一个敏捷型开发的公司&#xff0c;依然少不了Android和iOS的同步开发&#xff0c;实际成本和效益并没有太多变化。所以对于大多数公司来说依然风平…

CVSS 4.0 学习笔记

通用漏洞评分系统(CVSS)捕获了主要技术软件、硬件和固件漏洞的特征。其输出包括数字分数,表明与其他漏洞。 以下因素可能包括但不限于:监管要求、客户数量受影响、因违约造成的金钱损失、生命或财产受到威胁,或潜在漏洞的声誉影响。这些因素在CVSS评估范围之外。 CVSS的好…

视觉定位Revisit Anything

Revisit Anything: Visual Place Recognition via Image Segment Retrieval 项目地址 摘要&#xff1a; 准确识别重游地点对于嵌入代理的定位和导航至关重要。这要求视觉表现清晰&#xff0c;尽管摄像机视点和场景外观有很大变化。现有的视觉地点识别管道对“整个”图像进行编码…

Pytorch实现心跳信号分类识别(支持LSTM,GRU,TCN模型)

Pytorch实现心跳信号分类识别(支持LSTM,GRU,TCN模型&#xff09; 目录 Pytorch实现心跳信号分类识别(支持LSTM,GRU,TCN模型&#xff09; 1. 项目说明 2. 数据说明 &#xff08;1&#xff09;心跳信号分类预测数据集 3. 模型训练 &#xff08;1&#xff09;项目安装 &am…