对进阶指针的追忆

目录

思维导图

指针前言

一:字符指针

二:指针数组

三:数组指针

四:数组参数 && 指针参数

五:函数指针

六:函数指针数组

七:函数指针数组的指针

八:回调函数


 思维导图:

指针前言:

学过C语言的各位“铁汁们”,想必都知道,C语言的“灵魂”所在就是指针啦!

指针其实就是用来存放某个东西的“地址”(也可以理解为:指针是打开一个房间门的一把钥匙)!注意这是形象化的理解哈,站在计算机的角度理解就是:一串一串的数字。计算机的内存被划分无数个的小单元,每一个小单元(可以理解为一个房间)都对应一个唯一的地址标识,换言之就是指针(地址),通过指针就可以进入此房间,进行合理的一些操作。

1)关于地址的由来:(对于32为平台而言)地址是由32个 0 或者 1组成的一串二进制序列

2)指针大小是固定的:4个字节(32为平台下)或者是8个字节(64为平台下)

3)指针的类型决定了指针在 加上或者减去一个整数的时候,他解引用时候的权限

4)不管是一级指针还是二级指针亦或是高级指针,他们都是指针(注意哈:咱可不要站在门缝里看指针,把指针看扁了),是指针就是一个变量,都是用来存放地址的。

5)指针的运算:指针-指针的绝对值得到的是元素的个数

1.字符指针

可以借助类比的思想,对于整形指针想必我们应不陌生吧。

整形指针:用来存放整形数据的指针

那么顾名思义,字符指针就是用来存放字符的数据类型的指针呗

那接下来,话不多说,举几个栗子就可以 get 到了 。

1)

2)

显然是不可能滴!

指针p的大小是4个字节(32位平台上)而字符串“hello world”所占大小是11个字节。

此时只是把首字符串h 的地址放在p指针里面了,通过记住此时字符h 的地址,以便后续的操作。

那接下来小试牛刀一把,以加强对此知识点的理解。

对于当前的程序,输出的结果是啥???

分析:

数组名表示数组首元素的地址,数组名 arr1和 数组名 arr2 是2个完全不一样的数组,所以 输出: arr1 and  arr2  are not same

虽然这两个数组的首元素都是字符 h ,但是他们所表示的含义是不一样的。

对于常量字符串 hello ,是存放在代码区域的(只能指向读的操作),其中字符指针str1  ,str2 都是指向这个常量字符串的,站在计算机内存角度,相同的代码没有必要存放2份,所以输出:str1 and str2 are same

运行结果如下:

2.指针数组
2.1 定义

依然借助类比思想,通过对整形指针数组的理解,开头知道

指针数组:是存放指针的数组

数组:存储一组相同类型数据的集合

2.2 使用

3.数组指针
3.1定义

数组指针:首先他是指针,是指向数组的指针

3.2使用

1)

在这里,想必有不少的初学者对于数组指针  和  指针数组 这两个分不清吧!

其实只要把主语定位准确就好

数组指针旨在强调指针,只不过是指向数组的指针罢了

指针数组旨在强调数组,只不过是用来存放指针的数组罢了

对于以下2种写法,各位看看哪一个是数组指针呢

 第46行:这是指针数组;去掉p之后就是数组的类型,这个数组有3个元素。每个元素是int* 类型

第47行:是一个数组指针;去掉p之后,就是指针p的类型,指向一个数组的地址,这个数组有3个元素,每个元素都是int

2)数组指针具体场景的使用:二维数组

想必大家对这个二维数组的访问,应该感到并不陌生吧,甚至说再熟悉不过了。双层for循环,通过控制二维数组的行和列来便利访问当前数组罢了。 

接下来看看借助数组指针如何来遍历

 

二维数组名的理解:

1)他依然表示数组首元素的地址;只不过是表示第一行元素的地址(换言之就是:以数组指针的形式表示的)

2)可以把二维数组看作是由一个个一维数组组成的元素

3.3 数组名 与 &数组名的不同

数组名:代表数组首元素的地址;但是有2 个特殊情况。

第一个:sizeof(数组名):求的是整个数组的地大小(单位是字节)

第二个:&数组名,取出的是整个数组的地址(注意只不过在打印的时候是从当前首元素地址开始打印,但二者含义是不一样是)。

结合下面的栗子,有助于更好的理解!

4.数组传参和指针传参
4.1一维数组传参

思考:实参传递  arr,  对应的形参应该以啥形式来接收???

第85行:形参以数组的形式接收,支持(注意:数组可以不指定大小

第87行:形参以数组的形式接收只不过相比较上面指定了数组的大小,支持

第89行:形参以指针的形式接收,支持;数组名就是表示数组首元素的地址嘛

第91行:形参以指针数组的形式接收,不支持

 注意:

当实参以数组名的形式传参的时候,形参要是写成数组的形式,可以指定数组大小也可以不指定数组的大小;因为此时虽然是以数组的形式传参,但是其实底层是以指针的方式来接收的

4.2二维数组传参

此时arr:是一个二维数组的数组名

第99行:当实参传递数组名,和对应的列数时候,形参也是以同样方式接受的,是支持的

第101行:未指定二维数组的列,不支持

第103行:支持

第105:不支持,形参此时是一个一级整形指针而实参是一个数组指针

第107:不支持,形参是一个二级指针,而实参是一个数组指针

第109行:支持,形参实参类型都是:数组指针

 注意:

对于二维数组而言:在传参的时候 ,行可以省,但是二维数组的列不能省二维数组名表示数组首元素的地址(也就是第一行元素的地址)

4.3一级指针传参
#include <stdio.h>
void print(int *p, int sz)
{
 int i = 0;
 for(i=0; i<sz; i++)
 {
 printf("%d\n", *(p+i));
 }
}
int main()
{
 int arr[10] = {1,2,3,4,5,6,7,8,9};
 int *p = arr;
 int sz = sizeof(arr)/sizeof(arr[0]);
 //一级指针p,传给函数
 print(p, sz);
 return 0;
}

 分析:实参传递的是一个整形指针;

当形参的参数是指针的时候,对应的实参可以是什么类型???

1)指针

2)一个普通的变量

3)一维数组名

4)常量字符串

4.4二级指针传参

当形参是二级指针的时候,对应的实参可以传那些类型???

1)二级指针

2)二维数组名

3)存放一级指针的指针的地址

5.函数指针

还是老问题,借助类比的思想:

数组指针:是存放数组的指针

函数指针:就是存放函数的指针

5.1 对&函数名与函数名的理解

通过对结果的分析可知:&函数名和 函数名所代表的含义一样都表示当前函数的地址

注意这里是不同于 &数组名 和 数组名的含义

 有了对函数名的理解以及,接下来看一下下面2行代码所表示的含义吧!!!

 分析:

第一行代码:调用0地址处的函数

第二行代码:是signal 函数的声明

5.2 typedef 关键字的使用 

1)typedef 作用:对类型进行重新命名

比如说:unsigned char 这个数据类型名字过长,使用typedef 可以对unsigned char 进行重新命名为 :un_c(也就是 说,给unsigned char 起一个别名,只不过还是同一个数据类型,只不过称呼不一样啦)

2)举例

但是若是对函数指针进行重命名的话,稍许有点不太一样

6.函数指针数组

要想弄清楚函数指针数组,那就必须对函数 以及函数指针掌握透彻。

函数指针是啥???

函数名 和 

 &函数名 又是啥 ???

顾名思义:函数指针数组:是用来存放函数指针的一个数组,数组的元素是一个个函数指针

6.1函数指针数组的使用

 

 写一个计算器的程序:

普通版本:

int add(int x, int y)
{
	return x + y;
}
int sub(int x, int y)
{
	return x - y;
}
int mul(int x, int y)
{
	return x * y;
}
int div(int x, int y)
{
	return x / y;
}
void Menu()
{
	printf("******************************\n");
	printf("*** 1. add    2. sub   *******\n");
	printf("*** 3. mul    4. div   *******\n");
	printf("*** 0. exit            *******\n");
	printf("******************************\n");
}
int main()
{
	//实现一个计算器的功能:一般写法
	int input = 0;
	int x, y, ret = 0;
	do
	{
		Menu();
		printf("输入您的选择:>");
		scanf("%d", &input);
		if (input >= 1 && input <= 4)
		{
			printf("输入2个操作数:>");
			scanf("%d %d", &x, &y);
			switch (input)
			{
			case 1:
				ret = add(x, y);
				printf("ret = %d\n", ret);

				break;
			case 2:
				ret = sub(x, y);
				printf("ret = %d\n", ret);

				break;
			case 3:
				ret = mul(x, y);
				printf("ret = %d\n", ret);

				break;
			case 4:
				ret = div(x, y);
				printf("ret = %d\n", ret);

				break;
			}
		}
		else if (input == 0)
		{
			printf("退出计算器\n");
		}
		else
		{
			printf("输入错误,重新输入\n");
		}
	} while (input);
	return 0;
}

分析:随着用户需求的不断更改,对计算器的更能也需要不断完善,比如实现 2个数据的 按位与,按位或等等。这将会致使case 语句越来越冗余,而且复用率极低,不是一个很好的代码。 

借助函数指针的版本:

int add(int x, int y)
{
	return x + y;
}
int sub(int x, int y)
{
	return x - y;
}
int mul(int x, int y)
{
	return x * y;
}
int div(int x, int y)
{
	return x / y;
}
void Menu()
{
	printf("******************************\n");
	printf("*** 1. add    2. sub   *******\n");
	printf("*** 3. mul    4. div   *******\n");
	printf("*** 0. exit            *******\n");
	printf("******************************\n");
}
int main()
{
	int input = 0,ret = 0;
	int x, y;
	do
	{
		Menu();
		int (*p[5]) (int, int) = { NULL,add,sub,mul,div };
		printf("输入您的选择:>");
		scanf("%d", &input);

		if (input >= 1 && input <= 4)
		{
			printf("输入2个操作数:>");
			scanf("%d %d", &x, &y);
			ret = p[input](x, y);
			printf("ret = %d\n", ret);
		}
		else if (input == 0)
		{
			printf("退出计算器\n");
		}
		else
			printf("输入错误,重新输入\n");
	} while (input);
	return 0;
}

1)之所以把函数指针数组大小设置为5而不是4:就是为了保持与case 语句的一致。

2)函数名:表示函数的地址 

7.指向函数指针数组的指针

还是老问题:函数指针数组的指针 是 指向函数指针数组的 指针

8.回调函数
8.1回调函数定义

首先回调函数是借助一个函数指针来调用的函数。当把函数的地址作为参数传递给一个指针,来间接调用要想调用的函数,此时这个被调用的函数就是回调函数

8.2回调函数应用

1)还是借助计算器实现的那个程序进行解释:一个计算器的功能不止一个(可以进行加法运算,减法运算,乘法运算,除法运算,按位与……)。那有没有一个函数可以实现多种功能:不仅可以进行加法运算,还可以进行减法运算,乘法运算…… 这就是回调函数该发挥作用啦!

初始版本的计算器代码:

其实不难发现:对于圈出部分,过于冗余了,可复用性并不高

 借助回调函数实现的计算器代码:

int Add(int x, int y)
{
	return x + y;
}
int Sub(int x, int y)
{
	return x - y;
}
int Mul(int x, int y)
{
	return x * y;
}
int Div(int x, int y)
{
	return x / y;
}
int Calc(int (*p)(int, int ))
{
	int x, y;
	printf("请输入2个操作数:>");
	scanf("%d %d", &x, &y);

	return p(x, y);
}
int main()
{
	int input;
	int ret = 0;
	
	do
	{
		Menu();
		printf("请输入你的选择:>");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			
			ret =Calc(Add);//此时被间接调用的Add函数就成为回调函数
			printf("ret = %d\n", ret);
			break;
		case 2:
			ret = Calc(Sub);

			printf("ret = %d\n", ret);
			break;
		case 3:
			ret = Calc(Mul);

			printf("ret = %d\n", ret);
			break;
		case 4:
			ret = Calc(Div);
			printf("ret = %d\n", ret);
			break;
		case 0:
			printf("退出计算器\n");
			break;
		default:
			printf("输入错误,重新输入\n");
			break;
		}
	} while (input);
	return 0;
}

使用回调函数模拟实现qsort()(注意是基于冒泡思想实现的qsort函数)

1)冒泡核心思想:两两相邻元素进行交换(满足一定条件的)

2)qsort( )函数的初步使用

使用此函数对int 类型数据进行排序(注意qsort函数默认是以升序来排列的)

对于compare()这个函数,需要注意以下几点:

第一:此函数返回类型是int  类型

第二:此函数的参数类型必须是const void *

第三:对于void*类型的指针是不能直接进行解引用的,需要先进行类型强转,再进行解引用 

qsort()可以用来排序任意类型的数据

对int 类型数据排序

int cmp(void* p1, void* p2)
{
	return (*(int*)p1 - *(int*)p2);
}

int main()
{
	int arr[] = { 7,4,1,2,5,8,9,6,3,0 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	qsort(arr, sz, 4, cmp);
	for (int i = 0; i < sz; ++i)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

对结构体类型数据排序:

typedef struct student
{
	int age;
	char name[20];
	char sex;
}stu;int Cmp_name(const void* p1, const void* p2)//注意对于void*类型指针不能直接解引用
{
	return strcmp(((stu*)p1)->name, ((stu*)p2)->name);//注意字符串比较大小只能实用strcmp()函数
}
int main()
{
	stu arr[] = { {10,"lisi",'m'},{5,"bai",'w'},{15,"duan",'w'}};
	int sz = sizeof(arr) / sizeof(arr[0]);
	qsort(arr, sz, sizeof(stu), Cmp_name);
}
8.3 基于冒泡函数思想模拟qsort() 实现

完整代码:

int cmp(const void* p1, const void* p2)
{
	return (*(int*)p1 - *(int*)p2);
}
typedef struct student
{
	int age;
	char name[20];
	char sex;
}stu;
int Cmp_age(const void* p1,const void* p2)
{
	return (((stu*)p1)->age -( (stu*)p2)->age);// 注意 (stu*)p2->age错误的
}
int Cmp_name(const void* p1, const void* p2)//注意对于void*类型指针不能直接解引用
{
	return strcmp(((stu*)p1)->name, ((stu*)p2)->name);//注意字符串比较大小只能实用strcmp()函数
}
void Swap(char* p1, char* p2, int size)
{
	//注意是一个字节一个字节交换
	for (int i = 0; i < size; ++i)
	{
		char tmp = *p1;
		*p1 = *p2;
		*p2 = tmp;
		++p1;
		++p2;
	}
}
void Qsort(void* base, int num, int size, int(*cmp)(const void* p1, const void* p2))
{
	for (int i = 0; i < num - 1; ++i)
	{
		for (int j = 0; j < num - i - 1; ++j)
		{
			//相邻2元素进行比较
			//注意这里必须强转成char*类型指针
			if (cmp(((char*)base + j * size) ,((char*)base + (j + 1) * size) )  > 0) //注意这里条件必须注明是>0 还是<0
			{
				//交换
				Swap(((char*)base + j * size), ((char*)base + (j + 1) * size), size);
			}
		}
	}
}
int main()
{
	int arr[10] = { 7,4,1,2,5,8,9,6,3,10 };
	Qsort(arr, 10, sizeof(arr[0]), cmp);
	stu arr1[] = { {10,"lisi",'m'},{5,"bai",'w'},{15,"duan",'w'} };
	//Qsort(arr1, 3, sizeof(stu), Cmp_name);
	Qsort(arr1, 3, sizeof(stu), Cmp_age);//注意最后一个函数指针对应的函数需要使用者自己实现
	for (int i = 0; i < 10; ++i)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

 结语:

首先非常感谢各位老铁可以看到最后,想必收获满满吧!(有疑惑也是正常的,毕竟知识量太大了,俺也是整了不止一时半会才写完这篇博客的)对于指针这部分的学习,是真的及其重要!

对于后期数据结构以及C嘎嘎的学习,起着至关重要的作用,为了检验大家对指针这一知识点的学习,大家可以看看关于指针和数组相关面试的题型,链接在此,各位自取哈~~~

 https://blog.csdn.net/X_do_myself/article/details/134366577

今日的share到此就结束了,希望各位友友们可以支持一下~~~

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

json-server服务使用教程

目录标题 安装 json-server启动 json-server 本地服务 安装 json-server npm install -g json-server0.17.4json-server -v报错请参考&#xff1a;执行json-server -v报错 因为在此系统上禁止运行脚本。 启动 json-server 本地服务 查看本机IP&#xff1a;ipconfig Shift右…

MySQL-日志-优化

目录 介绍一下mysql 的日志 redo log 和binlog 的区别及应用场景 redo log 和 binlog 在恢复数据库有什么区别? redo log 是怎么实现持久化的? redo log除了崩溃恢复还有什么其他作用? &#xff08;顺序写&#xff09; redo log 怎么刷入磁盘的知道吗&#xff1f; 两阶…

JVM学习(day1)

JVM 运行时数据区 线程共享&#xff1a;方法区、堆 线程独享&#xff08;与个体“同生共死”&#xff09;&#xff1a;虚拟机栈、本地方法栈、程序计数器 程序计数器 作用&#xff1a;记录下次要执行的代码行的行号 特点&#xff1a;为一个没有OOM&#xff08;内存溢出&a…

深度学习DeepLearning二元分类 学习笔记

文章目录 类别区分变量与概念逻辑回归Sigmoid函数公式决策边逻辑损失函数和代价函数逻辑回归的梯度下降泛化过拟合的解决方案正则化 类别区分 变量与概念 决策边置信度阈值threshold过拟合欠拟合正则化高偏差lambda&#xff08;λ&#xff09; 线性回归受个别极端值影响&…

TemuAPI接口:获取商品详情功能

temu作为拼多多海外的跨境电商平台&#xff0c;已经在海外电商领域崭露头角&#xff0c;越来越多的外贸人选择temu作为发展平台。今天的接口可以用于获取temu平台的商品详情&#xff0c;包括价格、商品图片、规格、评论等内容&#xff0c;如有需要&#xff0c;请点击文末链接或…

AFL安装和初步使用

代码漏洞 AFL安装和初步使用 0. 前言1. 下载2. 安装3. 初步使用1&#xff09;准备文件2&#xff09;插桩编译C生成二进制3&#xff09;开始Fuzz&#xff08;1&#xff09;正常执行&#xff08;2&#xff09;**可能出现的问题**&#xff08;3&#xff09;正常结果 AFL安装和初步…

【Linux网络】数据链路层【下】{MAC/MTU/ARP/ICMP/NAT/PING/代理服务器原理}

文章目录 1.逐步深入数据链路层1.1MAC帧1.2由集线器到交换机1.3认识MTU 2.ARP 地址解析协议/RARP逆地址解析协议3.DNS(Domain Name System)域名从输入url后到能看到网页 发生了什么【典中典】 4.ICMP协议&#xff1a;一个网络层协议有了TCP&#xff0c;为什么还要用ICMPICMP协议…

论文研读:ViT-V-Net—用于无监督3D医学图像配准的Vision Transformer

目录 摘要 介绍 方法 VIT-V-Net体系结构 损失函数 图像相似性度量 变形场正则化 结果与讨论 摘要 在过去的十年里&#xff0c;卷积神经网络(ConvNets)在各种医学成像应用中占据了主导地位并取得了最先进的性能。然而&#xff0c;由于缺乏对图像中远程空间关系的理解&a…

解决Ubuntu 22.04 vscode搜狗拼音输入无法输入中文

关闭vscode 编辑~/.bashrc&#xff0c;添加以下内容 export GTK_IM_MODULExim export QT_IM_MODULExim export XMODIFIERSimfcitx source ~/.bashrc && code 重新加载环境变量后启动code&#xff0c;即可以正常使用搜狗拼音输入法了

读人工智能全传11人工智能会出什么错

1. 人工智能会出什么错 1.1. 一些报道是公正合理的&#xff0c;不过坦白地说&#xff0c;大部分报道都愚蠢得无可救药 1.2. 一些报道颇有知识性和引导性&#xff0c;而大部分则是杞人忧天式的恐吓 1.3. 滑稽的报道迎合了大众对人工智能的“终结者式恐惧” 1.3.1. 我们创造出…

win10系统更新后无法休眠待机或者唤醒,解决方法如下

是否使用鼠标唤醒 是否使用鼠标唤醒 是否使用键盘唤醒

C# .net6使用Hangfire

首先我们先来了解什么是Hangfire&#xff1f; Hangfire 是一个用于 .NET 的任务调度库&#xff0c;允许你在后台运行任务&#xff0c;而不需要依赖外部的任务队列服务或复杂的基础设施。它简化了后台任务的创建、调度和管理过程&#xff0c;使得在 .NET 应用程序中处理长期运行…

昇思25天学习打卡营第25天 | ResNet50迁移学习

ResNet50迁移学习 https://gitee.com/mindspore/docs/blob/r2.2/tutorials/application/source_zh_cn/cv/transfer_learning.ipynb 在实际应用场景中&#xff0c;由于训练数据集不足&#xff0c;所以很少有人会从头开始训练整个网络。普遍的做法是&#xff0c;在一个非常大的…

嵌入式C++、Qt/QML和MQTT:智能工厂设备监控系统的全流程介绍(附代码示例)

1. 项目概述 本项目旨在开发一套先进的智能工厂设备监控系统&#xff0c;集成嵌入式技术、工业通信协议和人机界面等多项技术&#xff0c;实现对工厂设备的全方位实时监控、高精度数据采集和智能化分析。该系统将显著提升工厂设备的运行效率&#xff0c;大幅降低维护成本&…

Python数据分析-Excel和 Text 文件的读写操作

1.Excel和 Text 文件的读写操作 1. Text 文件读写包 import sys print(sys.argv[0]) print(__file__) print(sys.path[0]) qopen(sys.path[0] "\out.txt","w",encodingutf-8) q.write(这个是测试一下) q.close() print(done)open 语句可以打开的创建text…

案例 | 人大金仓助力山西政务服务核心业务系统实现全栈国产化升级改造

近日&#xff0c;人大金仓支撑山西涉企政策服务平台、政务服务热线联动平台、政务网、办件中心等近30个政务核心系统完成全栈国产化升级改造&#xff0c;推进全省通办、跨省通办、综合业务受理、智能审批、一件事一次办等业务的数字化办结进程&#xff0c;为我国数字政务服务提…

大鲸鱼—docker 基本概念及安装使用

目录 一、docker前言 1.什么是Docker&#xff1f; 2.Docker的宗旨 3.容器的优点 4.Docker与虚拟机的区别 5.Docker核心概念 镜像 容器 仓库 6.为什么要用容器 7.容器越来越受欢迎的原因 8.容器在内核中支持2种重要技术 二、Docker安装 三、Docker 镜像操作 1.搜…

<数据集>水稻叶片病害识别数据集<目标检测>

数据集格式&#xff1a;VOCYOLO格式 图片数量&#xff1a;1448张 标注数量(xml文件个数)&#xff1a;1448 标注数量(txt文件个数)&#xff1a;1448 标注类别数&#xff1a;3 标注类别名称&#xff1a;[BrownSpot,RiceBlast,BacterialBlight] 序号类别名称图片数框数1Rice…

uniapp微信小程序 TypeError: $refs[ref].push is not a function

我的写法 this.$refs.addPopup.open();报错 打印出来是这样的 解决 参考未整理 原因 在当前页面使用的v-for循环 并且循环体内也有组件使用了ref&#xff08;而我没有把每个ref做区别命名&#xff09; 这样就导致了我有很多同名的ref&#xff0c;然后就报错了 解决办法&a…

Java类与对象

类是对现实世界中实体的抽象&#xff0c;是对一类事物的描述。 类的属性位置在类的内部、方法的外部。 类的属性描述一个类的一些可描述的特性&#xff0c;比如人的姓名、年龄、性别等。 [public] [abstract|final] class 类名 [extends父类] [implements接口列表] { 属性声…