深入解剖指针篇(3)

news2024/10/7 13:24:17

个人主页(找往期文章) :我要学编程(ಥ_ಥ)-CSDN博客

目录

二级指针

指针数组

指针数组模拟二维数组

字符指针变量 

数组指针

数组指针初始化 

二维数组传参的本质 

函数指针

函数指针的使用

typedef关键字

函数指针数组 


二级指针

指针变量也是变量,是变量就有地址,那指针变量的地址存放在哪里?答案是存放在二级指针里头。 指针变量的地址存放在哪里?这句话的主语是地址,问的是地址存放在哪里?我们从前面的学习知识可以知道地址是存放在指针里。只不过这里用了指针变量这个定语来修饰罢了。

上面这个图就是二级指针创建的流程图。

1. *ppa 通过对ppa中的地址进行解引用,这样找到的是 pa , *ppa 其实访问的就是 pa。

2. **ppa 先通过 *ppa 找到 pa ,然后对 pa 进行解引用操作, *pa ,那找到的是 a 。

至于这个**ppa的两颗*的理解:

这个是通过分解成一级指针变量来理解的。

至于ppa+-整数,能访问几个字节,是取决于 int* 的 。这个是指针是4/8个字节。因此ppa+-整数,能访问4/8个字节。

指针数组

指针数组是指针还是数组? 我们类比⼀下,整型数组,是存放整型的数组,字符数组是存放字符的数组。 那指针数组呢?是存放指针的数组。

指针数组的每个元素都是用来存放地址(指针)。如下图:

指针数组的每个元素是地址,又可以指向一块区域。 

指针数组模拟二维数组

 当我们想要打印二维数组的所有元素时,我们是使用下标引用操作符([ ])来实现的。现在学习了指针,那么可以用指针来实现吗?答案是可以的。我们先用一个数组来存放另外几个数组的地址,再通过地址来找到对应的数组,最后再通过打印一维数组的方法来实现。

#include <stdio.h>
void Print(int** p, int sz, int sz1)//arr的类型是int*
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		int j = 0;
		for (j = 0; j < sz1; j++)
		{
			//printf("%d ", p[i][j]);//下标引用的方法
			printf("%d ", *(*(p + i) + j));//指针引用的方法
		}
		printf("\n");
	}
}
int main()
{
	int arr1[] = { 1,2,3,4,5 };
	int arr2[] = { 2,3,4,5,6 };
	int arr3[] = { 3,4,5,6,7 };
	int sz1 = sizeof(arr1) / sizeof(arr1[0]);
	int* arr[] = { arr1,arr2,arr3 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	Print(arr, sz, sz1);
	return 0;
}

上述的代码模拟出二维数组的效果,实际上并非完全是二维数组,因为每一行并非是连续的。

字符指针变量 

在指针的类型中我们知道有一种指针类型为字符指针 char*。

一般使用方式:

要注意这个ch的内容不可更改,因为这个ch中存放的是一个常量字符串。常量字符串的内容不可更改。但是存放在数组里的字符串可以更改。即数组内容可以更改。

还有一种使用方式如下:

这个 str 中存放的是hello world 的首字符的h,而不是存放hello world 。因为说到底这个str是一个指针变量,存放的是一个地址:这个字符串的地址,然而这个字符串的地址起始就是h的地址,因此就存放的是h的地址。至于在打印这个字符串时,为什么不用解引用?其实是因为str指向的内容就是这个字符串。如果我们还去解引用的话,就会把这个首字符给打印出来。

例如:

《剑指offer》中收录了一道和字符串相关的笔试题,我们一起来观摩⼀下:

#include <stdio.h>
int main()
{
	char str1[] = "hello bit.";
	char str2[] = "hello bit.";
	const char* str3 = "hello bit.";
	const char* str4 = "hello bit.";
	if (str1 == str2)
		printf("str1 and str2 are same\n");
	else
		printf("str1 and str2 are not same\n");

	if (str3 == str4)
		printf("str3 and str4 are same\n");
	else
		printf("str3 and str4 are not same\n");

	return 0;
}

答案如下:

我们思考一下是为什么? 首先,str1与str2都是一个地址,在内存中,不可能有两个一模一样的地址。就比如:我们在生活中点外卖,如果在地图上有两个一模一样的地址,那么外卖小哥怎么会知道送去哪一个地方呢?这就产生了错误。因此就打印 str1 and str2 are not same 。接下来就看str3与str4,它们都是一个字符指针,指向的也都是同一个字符串。而我们刚刚知道了这个字符指针存放的是字符串首元素的地址。这个字符串是同一个,那么它们的首元素地址也是一样的。即str3与str4都是指向这个地址,所以str3等于str4,打印 str3 and str4 are same 。(C/C++会把常量字符串存储到单独的一个内存区域,因此str3与str4都是指向这个内存区域)。

数组指针

数组指针是指向数组的指针。我们已经知道了整形指针: int * p;,存放的是整形变量的地址,能够指向整形数据的指针。那数组指针变量应该是:存放的应该是数组的地址,能够指向数组的指针变量。

想要知道答案,首先就得明白 * 和 [ ] 这两个操作符的优先级。

C 运算符优先级 - cppreference.com   这个是运算符优先级和结合性的网址。

由此可知:上面一个是数组,存放整形指针变量的,因此称为指针数组。下面一个是指针(()使p与*先结合——>指针),指向的是一个数组,因此称为数组指针。既然知道是指针了,那么怎么解读这个指针呢?如图所示:

数组指针初始化 

数组指针是指向数组的指针,存放的是数组的地址,那怎么获得数组的地址呢?就是我们之前学习的 &数组名 。这个就是得到的整个数组的地址。

这里可能会有小伙伴有疑惑:这个数组指针的元素个数能不能省略? 答案是不能。因为我们在数组里学过可以省略,但是这个不是数组,而是指针。举例:

二维数组传参的本质 

我们还没有学习指针之前,二维数组传参是这样的:

#include <stdio.h>
void Print(int arr[3][5], int row, int col)//用数组传参的方式接收
{
	int i = 0;
	for (i = 0; i < row; i++)
	{
		int j = 0;
		for (j = 0; j < col; j++)
		{
			printf("%d ", arr[i][j]);
		}
		printf("\n");
	}
}
int main()
{
	int arr[3][5] = { {1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7} };
	//打印数组
	Print(arr, 3, 5);
	return 0;
}

我们这个是用形参的方式来接收的。如果用指针呢?我们知道数组名是首元素的地址,在二位数组中,我们学过把二位数组看成几个一维数组组成。那么就可以在一维数组的层面把二维数组中的一维数组看成一个一个的元素。那么就可以推出来,在一维数组的层面,二维数组的数组名是首元素的地址,也就是二维数组中第一个一维数组的地址。

#include <stdio.h>
void Print(int(*p)[5], int row, int col)
{
	int i = 0;
	for (i = 0; i < row; i++)
	{
		int j = 0;
		for (j = 0; j < col; j++)
		{
			printf("%d ", *(*(p + i) + j));
			//(p+i)中的p是整个(第一个)一维数组的地址,+1,跳过的是整个一维数组。
			//因为int(*)[5]是数组指针的类型,这个是+1,就是跳过的。
			//*p访问的是第一个数组,而*(*p+j)访问的就是第一个数组里的元素。
		}
		printf("\n");
	}
}
int main()
{
	int arr[3][5] = { {1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7} };
	//打印数组
	Print(arr, 3, 5);
	//数组名是首元素的地址,也就是整个(第一个)一维数组的地址。
	//整个(第一个)一维数组的地址要用数组指针来接收。
	return 0;
}

有的小伙伴可能会疑惑为什么 int(*)[5]是数组指针的类型?这个我在数组知识点这篇文章写过,大家可以去看看。意味着二维数组传参本质上也是传递了地址,传递的是第一行这个一维数组的地址

总结:二维数组传参,形参的部分可以写成数组,也可以写成指针形式。

函数指针

什么是函数指针呢? 根据前面学习整型指针,数组指针的时候,我们可以类比关系,我们不难得出结论: 函数指针应该是用来存放函数地址的,未来通过地址能够调用函数的。我们可以先看看函数的地址是啥样?是不是和数组是一样的?

void test()
{
	printf("hehe\n");
}
int main()
{
	printf("%p\n", &test);
	return 0;
}

看来&函数名是能够把函数的地址地址取出来,那么就看看这个函数名是否能代表函数的地址? 

如此看来,这个函数名也是代表函数的地址。

总结:函数的地址用两种方法可以取出:1.  &函数名   2.   函数名

既然能把函数的地址取出,就肯定要放到函数指针里头。怎么存放呢?

还可以写成这样:

函数指针的使用

通过函数指针调用指针指向的函数。

#include <stdio.h>
int Add(int x, int y)
{
	return x + y;
}
int main()
{
	int a = 0;
	int b = 0;
	scanf("%d%d", &a, &b);
	int (*p)(int, int) = Add;
	int ret = (*p)(3,4);//相当于Add(3, 4);
	printf("%d\n", ret);
}

然而我们会发现这个代码也是可以的:

这也就意味着这个 * 其实有没有都无所谓。这个函数指针的变量名就相当于这个函数名。

typedef关键字

 typedef 是用来类型重命名的,可以将复杂的类型,简单化。

比如,你觉得 unsigned int 写起来不方便,如果能写成 uint 就方便多了,那么我们可以使用:

typedef unsigned int uint//将无符号整型重新命名为uint,这个就代表无符号整型

如果是指针类型,能否重命名呢?其实也是可以的,比如,将 int* 重命名为 ptr_t ,这样写: 

typedef int* ptr_t

但是对于数组指针和函数指针稍微有点区别。比如我们有数组指针类型 int(*)[5] ,需要重命名为 parr_t ,那可以这样写:

typedef int(*parr_t)[5]; //新的类型名必须在*的右边

函数指针类型的重命名也是一样的,比如,将 void(*)(int) 类型重命名为 pf_t ,就可以这样写: 

typedef void(*pf_t)(int)///新的类型名必须在*的右边

函数指针数组 

函数指针数组是一个数组,用来存放函数指针的。那么函数指针的数组如何定义呢?

p先和 [ ] 结合,说明 p 是数组,数组的内容是什么呢? 是 int (*)(int) 类型的函数指针。 

好啦,这就是C语言深入解剖指针第三篇的全部内容了!下期见!

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

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

相关文章

Linux内存管理:(十一)页面分配之慢速路径

文章说明&#xff1a; Linux内核版本&#xff1a;5.0 架构&#xff1a;ARM64 参考资料及图片来源&#xff1a;《奔跑吧Linux内核》 Linux 5.0内核源码注释仓库地址&#xff1a; zhangzihengya/LinuxSourceCode_v5.0_study (github.com) 1. 水位管理和分配优先级 页面分配…

0202-2-存储器管理

第四章:存储器管理 存储器的层次结构 多层结构的存储系统 存储器的多层结构 CPU寄存器主存辅存 可执行存储器 寄存器和主存的总称访问速度快&#xff0c;进程可以在很少的时钟周期内用一条load或store指令完成存取。 主存储器与寄存器 高速缓存和磁盘缓存 程序的装入和链…

《金融时报》:直面“雪球”风波 究竟影响几何?

“他们给我推荐的时候说是只要市场不大跌&#xff0c;我就能按照年化20%获得收益&#xff0c;当时我看大盘走势&#xff0c;也认为跌那么多的概率不大。”李先生告诉《金融时报》记者&#xff0c;他当初被银行客户经理推荐“雪球”产品并头脑一热买了的时候&#xff0c;以为按照…

springboot集成 mysql快速入门demo

一、mysql环境搭建 采用docker-compose搭建&#xff0c;配置如下&#xff1a; docker-compose.yml version: 3 services:mysql:image: registry.cn-hangzhou.aliyuncs.com/zhengqing/mysql:5.7 # 原镜像mysql:5.7container_name: mysql_3306 …

C++入坑基础知识点

当学习了C语言之后&#xff0c;很多的小伙伴都想进一步学习C&#xff0c;但两者有相当一部分的内容都是重叠的&#xff0c;不知道该从哪些方面开始入门C&#xff0c;这篇文章罗列了从C到C必学的入门知识&#xff0c;学完就算是踏入C的大门了。 1. 命名空间 写C的时候&#xff…

LeetCode 热题 100 | 链表(上)

目录 1 基础知识 1.1 空指针 1.2 结构体 1.3 指针访问 1.4 三目运算符 2 160. 相交链表 3 206. 反转链表 4 234. 回文链表 菜鸟做题第三周&#xff0c;语言是 C 1 基础知识 1.1 空指针 使用 nullptr 来判断是否为空指针&#xff1a; if (headA nullptr) …

鸿蒙开发有必要学吗?看完这篇再决定吧

在科技的潮流中&#xff0c;每一次新操作系统的诞生都是对旧秩序的挑战与新机遇的孕育。鸿蒙操作系统的出现&#xff0c;无疑是近年来科技界最引人注目的事件之一。自华为于2019年正式推出鸿蒙系统以来&#xff0c;这一我们自主研发的操作系统不仅在国内引起巨大反响&#xff0…

常见的6种软件测试用例设计方法

常见的软件测试用例设计方法&#xff0c;个人认为主要是下面这6种&#xff1a; 流程图法&#xff08;也叫场景法&#xff09;等价类划分法边界值分析判定表正交法错误推测法 这6种常见方法中&#xff0c;我分别按照定义、应用场景、使用步骤、案例讲解这4个部分进行讲解。 所…

MySQL查询数据(十)

MySQL查询数据&#xff08;十&#xff09; 一、SELECT基本查询 1.1 SELECT语句的功能 SELECT 语句从数据库中返回信息。使用一个 SELECT 语句&#xff0c;可以做下面的事&#xff1a; **列选择&#xff1a;**能够使用 SELECT 语句的列选择功能选择表中的列&#xff0c;这些…

不一样的味觉体验:精酿啤酒与烤肉的绝妙搭配

在繁华的都市生活中&#xff0c;人们总是在寻找那份与众不同的味觉享受。当夏日的微风轻轻拂过&#xff0c;你是否想过&#xff0c;与三五好友围坐在一起&#xff0c;拿着Fendi Club啤酒与烤肉的绝妙搭配&#xff0c;畅谈生活点滴&#xff0c;感受那份惬意与自在&#xff1f; F…

2401Idea用GradleKotlin编译Java控制台中文出乱码解决

解决方法 解决方法1 在项目 build.gradle.kts 文件中加入 tasks.withType<JavaCompile> {options.encoding "UTF-8" } tasks.withType<JavaExec> {systemProperty("file.encoding", "utf-8") }经测试, 只加 tasks.withType<…

基于单片机的智能燃气灶控制系统设计

摘要&#xff1a;针对传统燃气灶存在不能防干烧、不能进行温度检测、不能进行火力自动调节等问题&#xff0c;设计了一种基于单片机控制的智能燃气灶&#xff0c;它通过单片机进行控制&#xff0c;由开关模块、测温模块、语音播报模块、火力控制模块和防空烧模块五个模块组成&a…

Lazysysadmin

信息收集 # nmap -sn 192.168.1.0/24 -oN live.port Starting Nmap 7.94 ( https://nmap.org ) at 2024-01-30 21:10 CST Nmap scan report for 192.168.1.1 (192.168.1.1) Host is up (0.00075s latency). MAC Address: 00:50:56:C0:00:08 (VMware) Nma…

Flutter解析后台发来的jwt字段数据,了解jwt是否过期

前言 了解JWT是什么可以看这一篇博文 JWT&#xff08;JSON Web Token&#xff09;详解以及在go-zero中配置的方法-CSDN博客 流程 采用jwt_decoder库 添加至pubspec.yaml jwt_decoder: ^2.0.1 解析字段 查看是否过期 获取过期时间和token颁发的年龄

面试八股文(2)

文章目录 1.ArrayList和LinkedList区别2.HashMap和HashTable区别3.线程的创建方式4.Java中异常处理5.Java序列化中某些字段不想进行序列化&#xff1f;6.Java序列化7.静态方法和实例方法8.List、Set、Map三者区别9.ArrayList和Vector区别10.HashMap和HashSet区别 1.ArrayList和…

【C++】 C++入门— 基于范围的 for 循环

C 基于范围的for循环1 使用样例2 使用条件3 完善措施 Thanks♪(&#xff65;ω&#xff65;)&#xff89;谢谢阅读&#xff01;下一篇文章见&#xff01;&#xff01;&#xff01; 基于范围的for循环 1 使用样例 使用for循环遍历数组&#xff0c;我们通常这么写&#xff1a; …

博云科技与中科可控全面合作,探索前沿金融科技新机遇

2024年1月26日&#xff0c;博云科技与中科可控在昆山高新区成功举办合作签约仪式。昆山市委常委、昆山高新区党工委书记孙道寻、中科可控董事长聂华、博云科技董事长花磊等领导出席了本次签约仪式。 中科可控将利用其在先进计算和智造领域的优势&#xff0c;为博云科技提供有关…

el-table添加(取消,确认)

点击添加输入添加项&#xff0c;但是不想添加了&#xff0c;就点击取消&#xff0c;但是在打开之前输入的数据还在&#xff0c;在点击取消的时候数据清空 页面 数据没有清空的时候&#xff0c;点击取消之后&#xff0c;在打开数据还在 数据清空之后&#xff0c;在打开数据是没…

寒假作业2月2号

第一章 命名空间 一&#xff0e;选择题 1、编写C程序一般需经过的几个步骤依次是&#xff08;C &#xff09; A. 编辑、调试、编译、连接 B. 编辑、编译、连接、运行 C. 编译、调试、编辑、连接 D. 编译、编辑、连接、运行 2、所谓数据封装就是将一组数据和与这组数据有关…

为期 90 天的免费数据科学认证(KNIME)

从 2 月 1 日开始&#xff0c;KNIME 官方将免费提供 KNIME 认证 90 天。 无论您是刚刚迈入数据科学领域、已经掌握了一些技术&#xff0c;还是正在构建预测模型&#xff0c;都可以参加为期 90 天的 KNIME 认证挑战赛&#xff0c;完成尽可能多的认证并获得数据科学技能免费认证。…