指针!!C语言(第三篇)

news2024/11/19 8:21:25

目录

一. 二维数组传参的本质

二. 函数指针变量和函数指针数组

三. typedef关键字

四. 转移表

五. 回调函数以及qsort使用举例


一. 二维数组传参的本质

🍟首先我们先回顾一下二维数组是怎样传参的?我们需要传入数组名以及行数和列数,这样才能将一个二维数组传入一个函数中,除了这样我们还能怎么办?首先认识一下二维数组传参的本质,我们知道二维数组其实是一维数组组成的,所以二维数组的数组名也是数组首元素的地址二维数组的首元素的地址就是它的第一行

所以,根据这个规则我们的传参就可以有另一种方式,如下:
//二维数组传参的本质
void test1(int(*p)[5], int r, int c)
{
	for (int i = 0; i < r; i++)
	{
		for (int j = 0; j < c; j++)
		{
			printf("%d ", *(*(p + 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} };
	test1(arr, 3, 5);
	return 0;
}

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

二. 函数指针变量和函数指针数组

函数指针变量

🍔什么是函数指针变量呢? 根据前面学习整型指针,数组指针的时候,我们的类比关系,我们不难得出结论: 函数指针变量应该是用来存放函数地址的,未来通过地址能够调用函数的。 那么函数是否有地址呢?毋庸置疑肯定是有的。那么函数地址有什么不一样的地方呢?

首先是函数指针变量的创建:假如现在有一个简单的函数:int Add(int x,int y);{return x+y;},那么它的函数指针变量就是:int (*p) (int,int)=Add,变量int后面的x和y可写可不写:

另外对于函数指针而言,&函数名和函数名都是函数的地址,并无区别。

下面给大家一段代码演示:


int add(int x, int y)
{
	return x + y;
}

int main()
{
	int(*pf)(int, int) = &add;
	printf("%d ", (*pf)(2, 3));
	printf("%d ", (*pf)(4, 5));
	return 0;
}

函数指针数组: 

数组是一个存放相同类型数据的存储空间,所以要把函数的地址存到一个数组中,那这个数组就叫函数指针数组,那函数指针的数组如何定义呢?首先我们要定义一个数组,也就说假如这个变量名是pf,pf要先和[ ]结合,来证明pf是数组,像这样int (*pf[3])();数组的内容是什么呢?是 int (*)() 类型的函数指针。eg: int (*pf[4])(int,int)={    };大括号里存放的是数组。

三. typedef关键字

🧀 typedef 是用来类型重命名的,可以将复杂的类型,简单化
比如,你觉得 unsigned int 写起来不方便,如果能写成 uint 就方便多了,那么我们可以使用:
typedef unsigned int uint;   将unsigned int 重命名为uint
如果是指针类型,能否重命名呢?其实也是可以的,比如,将 int* 重命名为 ptr_t ,这样写:
typedef int * ptr_t ;
但是对于数组指针和函数指针稍微有点区别: 比如我们有数组指针类型 int(*)[5] ,需要重命名为 parr_t ,那可以这样写:
typedef int (* parr_t )[5];→  新的类型名必须在*的右边
函数指针类型的重命名也是⼀样的,比如,将 void(*)(int) 类型重命名为 pf_t ,就可以这样写:
typedef void (* pfun_t )( int );→ 新的类型名必须在*的右边
这些就是typedef关键字的重命名用法。

四. 转移表

转移表其实就是函数指针数组的用途之一,那么我们要如何理解呢?我们通过实现一个计算机来说明,在我们没有学习函数指针数组的时候我们的一般实现如下:

#include <stdio.h>
int add(int a, int b)
{
	return a + b;
}
int sub(int a, int b)
{
	return a - b;
}
int mul(int a, int b)
{
	return a * b;
}
int div(int a, int b)
{
	return a / b;
}
int main()
{
	int x, y;
	int input = 1;
	int ret = 0;
	do
	{
		printf("*************************\n");
		printf("***** 1:add 2:sub ******* \n");	
		printf("***** 3:mul 4:div *******\n");
		printf("*****    0:exit    ******\n");
		printf("*************************\n");
		printf("请选择:");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			printf("输⼊操作数:");
			scanf("%d %d", &x, &y);
			ret = add(x, y);
			printf("ret = %d\n", ret);
			break;
		case 2:
			printf("输⼊操作数:");
			scanf("%d %d", &x, &y);
			ret = sub(x, y);
			printf("ret = %d\n", ret);
			break;
		case 3:
			printf("输⼊操作数:");
			scanf("%d %d", &x, &y);
			ret = mul(x, y);
			printf("ret = %d\n", ret);
			break;
		case 4:
			printf("输⼊操作数:");
			scanf("%d %d", &x, &y);
			ret = div(x, y);
			printf("ret = %d\n", ret);
			break;
		case 0:
			printf("退出程序\n");
			break;
		default:
			printf("选择错误\n");
			break;
		}
	} while (input);
	return 0;
}

 我们可以看到在一般实现中我们分别要创建不同的函数然后再分别调用不同的函数来满足我们不同的计算需求,但是其实我们又会发现在我们的运算中,我们的程序看起来有点相似,但又不完全一样,看起来有些许繁琐,所以我们有没有什么办法让代码更加简单一点呢?这时候就要用到我们的函数数组指针了。如下:

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 main()
{
	int x = 0;
	int y = 0;
	int input = 0;
	int ret = 0;
	int(*pf[5])(int, int) = { 0,add,sub,mul,div };
	do
	{
		printf("*************************\n");
		printf("***** 1:add 2:sub *******\n");
		printf("***** 3:mul 4:div *******\n");
		printf("*****    0:exit    ******\n");
		printf("*************************\n");
		printf("请选择:");
		scanf("%d", &input);
		if ((input <= 4 && input >= 1))
		{
			printf("输⼊操作数:");
			scanf("%d %d", &x, &y);
			ret = (*pf[input])(x, y);
			printf("ret = %d\n", ret);
		}
		else if (input == 0)
		{
			printf("退出计算器\n");
		}
		else
		{
			printf("输⼊有误\n");
		}
		
	} while (input);
	return 0;
}

int(*pf[5])(int, int) = { 0,add,sub,mul,div }; 我们将四个不同的运算放在一个函数指针数组中,然后在实际的选择中,当我们需要哪一个运算的时候我们使用解引用*pf[input](x,y),当我们input选择1的时候,就是调用下标为1的元素,即add,也就说现在我们直接调用了add这个函数,又传入了两个参数x和y,这样就避免了大量的重复代码,使我们的代码更加高效。而且这也是函数指针数组的一个很好的例子。

五. 回调函数以及qsort使用举例

学到现在我们今天的重点就来了,也就说我们马上要讲的和回调函数以及对qsort的认识和使用举例,首先来认识一下什么是回调函数,回调函数就是一个通过函数指针调用的函数

如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数
时,被调用的函数就是回调函数 。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
我们可以把调用的函数的地址以参数的式传递过去,使用函数指针接收,函数指针指向什么函数就调用什么函数,这里其实使用的就是回调函数的功能。

我们还是以计算机的实现来说明这个问题,首先还是拿来我们的一般实现:

#include <stdio.h>
int add(int a, int b)
{
	return a + b;
}
int sub(int a, int b)
{
	return a - b;
}
int mul(int a, int b)
{
	return a * b;
}
int div(int a, int b)
{
	return a / b;
}
int main()
{
	int x, y;
	int input = 1;
	int ret = 0;
	do
	{
		printf("*************************\n");
		printf("***** 1:add 2:sub ******* \n");	
		printf("***** 3:mul 4:div *******\n");
		printf("*****    0:exit    ******\n");
		printf("*************************\n");
		printf("请选择:");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			printf("输⼊操作数:");
			scanf("%d %d", &x, &y);
			ret = add(x, y);
			printf("ret = %d\n", ret);
			break;
		case 2:
			printf("输⼊操作数:");
			scanf("%d %d", &x, &y);
			ret = sub(x, y);
			printf("ret = %d\n", ret);
			break;
		case 3:
			printf("输⼊操作数:");
			scanf("%d %d", &x, &y);
			ret = mul(x, y);
			printf("ret = %d\n", ret);
			break;
		case 4:
			printf("输⼊操作数:");
			scanf("%d %d", &x, &y);
			ret = div(x, y);
			printf("ret = %d\n", ret);
			break;
		case 0:
			printf("退出程序\n");
			break;
		default:
			printf("选择错误\n");
			break;
		}
	} while (input);
	return 0;
}

下面是我们使用回调函数的方法:

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 calc(int(*pf)(int, int))
{
	int ret = 0;
	int x, y;
	printf("输⼊操作数:");
	scanf("%d %d", &x, &y);
	ret = pf(x, y);
	printf("ret = %d\n", ret);
}

int main()
{
	int input = 0;
	int ret = 0;
	do
	{
		printf("*************************\n");
		printf("***** 1:add 2:sub *******\n");
		printf("***** 3:mul 4:div *******\n");
		printf("*****    0:exit    ******\n");
		printf("*************************\n");
		printf("请选择:");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			calc(add);
			break;
		case 2:
			calc(sub);
			break;
		case 3:
			calc(mul);
			break;
		case 4:
			calc(div);
			break;
		case 0:
			printf("退出程序\n");
			break;
		default:
			printf("选择错误\n");
			break;
		}
	} while (input);
	return 0;
}

从代码中我们可以看出来我们创建了一个calc函数里面放入了函数指针,在后面的使用中我们使用calc这个函数来调用我们要使用的函数,比如当我们输入1的时候,是calc(add),也就说现在指针pf指向的是add这个函数,就实现了加法。

🍳qsort使用举例:

我们来认识一个新的函数叫做qsort函数,在之前我们讲过使用冒泡排序来实现对于数值的升序或降序排列,但是都有就局限性,比如只能对整型数值进行排列,现在我们使用qsort函数可以对任意类型的数据进行排序

下面我们就使用qsort函数来实现一些排列其他类型的数据,首先可以排列整型数据

#include <stdio.h>
//qosrt函数的使⽤者得实现⼀个⽐较函数
int int_cmp(const void * p1, const void * p2)
{
 return (*( int *)p1 - *(int *) p2);
}
int main()
{
 int arr[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };
 int i = 0;
 qsort(arr, sizeof(arr) / sizeof(arr[0]), sizeof (int), int_cmp);
 for (i = 0; i< sizeof(arr) / sizeof(arr[0]); i++)
 {
 printf( "%d ", arr[i]);
 }
 printf("\n");
 return 0;
}

 int int_cmp(const void *p1, const void *p2);
 其返回值具有以下含义:
- 如果返回值小于 0( <0 ),那么 p1 所指向元素会被排在 p2 所指向元素的前面。
- 如果返回值等于 0( =0 ),那么 p1 所指向元素与 p2 所指向元素的顺序不确定。
- 如果返回值大于 0( >0 ),那么 p1 所指向元素会被排在 p2 所指向元素的后面
所以qsort函数是默认升序排列的。另外还有一点要提醒大家就是使用函数指针时候要注意我们使用的是void类型的指针,但是void类型是不能进行比较和计算的,所以我们要进行强制类型转换之后再进行计算

🍜使用qsort排序结构数据

除了整型之外,我们还可以排列其他的类型数据,比如比较字符串,结构题体等,让我们实现一下代码:

//比较结构体
//创建一个结构体
#include <stdlib.h>
struct Stu //学⽣
{
	char name[20];//名字
	int age;//年龄
};

int cmp_name(const void* p1, const void* p2)
{
	return strcmp(((struct Stu*)p1)->name, ((struct Stu*)p2)->name);
}

void test1()
{
	struct Stu s[] = { {"zhangsan", 20}, {"lisi", 30}, {"wangwu", 15} };
	qsort(s, sizeof(s) / sizeof(s[0]), sizeof(s[0]), cmp_name);
	for (int i = 0; i < 3; i++)
	{
		printf("%s ", s[i]);
	}
}

int main()
{
	test1();
	return 0;
}

 

这里说明一下,strcmp函数是用来比较字符串的,它的头文件是#include <string.h>,比较的是ASCII码值,还有就是结构体比较除了使用操作符(.)之外,还可以直接使用箭头(→)的方式

🍢qsort函数的模拟实现:

那么qsort函数究竟是怎样实现的呢?我们可以使用回调函数,模拟实现qsort(采用冒泡的方式)。

int int_cmp(const void* p1, const void* p2)
{
	return (*(int*)p1 - *(int*)p2);
}
void _swap(void* p1, void* p2, int size)
{
	int i = 0;
	for (i = 0; i < size; i++)
	{
		char tmp = *((char*)p1 + i);
		*((char*)p1 + i) = *((char*)p2 + i);
		*((char*)p2 + i) = tmp;
	}
}
void bubble(void* base, int count, int size, int(*cmp)(void*, void*))
{
	int i = 0;
	int j = 0;
	for (i = 0; i < count - 1; i++)
	{
		for (j = 0; j < count - i - 1; j++)
		{
			if (cmp((char*)base + j * size, (char*)base + (j + 1) * size) > 0)
			{
				_swap((char*)base + j * size, (char*)base + (j + 1) * size,size);
			}
		}
	}
}

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

这个函数 int_cmp  用于比较两个 void*  类型的指针所指向的整数。通过将指针强制转换为 int*  类型,然后计算它们所指向的值的差值来确定大小关系。返回值大于 0 表示 p1  所指的值大于 p2  所指的值,小于 0 表示 p1  所指的值小于 p2  所指的值,等于 0 表示两者相等。 

_swap  函数用于交换两个指针所指向的内存区域的值。它通过一个循环,逐个字节地交换两个区域的内容。循环的次数由 size  参数决定,以确保交换指定大小的数据。

bubble  函数实现了冒泡排序的逻辑。
 -  void* base  表示要排序的数组的起始地址。
-  int count  是数组中的元素个数。
-  int size  是每个元素的大小。
-  int(*cmp)(void*, void*)  是一个函数指针,用于指定比较元素大小的规则。
函数通过两层循环来比较相邻的元素。如果相邻元素的顺序不符合比较规则(即  cmp  函数返回值大于 0 ),就调用  _swap  函数交换它们的位置。

 

 

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

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

相关文章

VS C++ Project(项目)的工作目录设置

如果只是简单创建一个VS CProject或者MFC Project&#xff0c;可能很多时候&#xff0c;只关心将Project放在硬盘的那个位置&#xff0c;与Project目录相关的的其他问题&#xff0c;并不引人注意&#xff0c;我们也不是十分在意。有时我们不得不进行工作目录方面的设置&#xf…

Javascript前端面试(七)

JavaScript 部分 1. JavaScript 有哪些数据类型&#xff0c;它们的区别&#xff1f; JavaScript 共有八种数据类型&#xff0c;分别是 Undefined、Null、Boolean、 Number、String、Object、Symbol、BigInt。 其中 Symbol 和 BigInt 是 ES6 中新增的数据类型&#xff1a; ●Sym…

卷积神经网络(一)---原理和结构

在介绍卷积神经网络之前&#xff0c;先提出三个观点&#xff0c;正是这三个观点使得卷积神经网络能够真正起作用。 1. 局部性 对于一张图片而言&#xff0c;需要检测图片中的特征来决定图片的类别&#xff0c;通常情况下这些特征都不是由整张图片决定的&#xff0c;而是由一些…

暑期集训周报三

题解 SMU Summer 2024 Contest Round 8-CSDN博客 CSDN 积累题目 dp 1.花店橱窗 思路&#xff1a;用dp[i][j]表示第i个放在j位置上的最大价值&#xff0c;那么我们可以枚举i-1被放在了区间[i-1,j-1]的那个位置&#xff0c;找到最大价值部分&#xff0c;然后更新dp[i][j]&a…

gitee的远程连接与公钥SSH的连接

目录 1. 登录注册gitee1.1 登录注册1.2 创建仓库 2. 远程连接3. 公钥连接4. 参考链接 1. 登录注册gitee 1.1 登录注册 gitee官网 进入后进行登录注册 1.2 创建仓库 2. 远程连接 在你想要上传文件的文件夹中进行git初始化&#xff08;我在其他文章已经写过&#xff0c;链接…

FastAPI(七十六)实战开发《在线课程学习系统》接口开发-- 课程详情

源码见&#xff1a;"fastapi_study_road-learning_system_online_courses: fastapi框架实战之--在线课程学习系统" 这个接口用户可以不登录&#xff0c;因为我们的课程随意浏览 那么我们梳理下这里的逻辑 1.根据课程id判断课程是否存在 2.课程需要返回课程的详情 3…

X-AnyLabeling标注软件使用方法

第一步 下载 官方X-AnyLabeling下载地址 github&#xff1a;X-AnyLabeling 第二步 配置环境 使用conda创建新的虚拟环境 conda create -n xanylabel python3.8进入环境 conda activate xanylabel进入X-AnyLabeling文件夹内&#xff0c;运行下面内容 依赖文件系统环境运行环…

【把玩数据结构】详解队列

目录 队列介绍队列概念队列的结构生活中的队列 队列的实现队列的初始化队列的销毁队尾入队列队头出队列获得队头元素获得队尾元素统计队列元素个数 队列介绍 队列概念 队列&#xff1a;只允许在一端进行插入数据操作&#xff0c;在另一端进行删除数据操作的特殊线性表。队列遵…

Python 代码中的 yield 到底是什么鬼?

在Python编程中&#xff0c;有一个强大而神秘的关键字&#xff0c;那就是yield。初学者常常被它搞得晕头转向&#xff0c;而高级开发者则借助它实现高效的代码。到底yield是什么&#xff1f;它又是如何在Python代码中发挥作用的呢&#xff1f;让我们一起来揭开它的面纱。 Pyth…

Chrome浏览器设置暗黑模式 - 护眼模式 - 亮度调节 - DarkReader - 地址栏和书签栏设置为黑色背景

效果图 全黑 浅灰 &#xff08;DarkReader设置开启亮色亮度-25&#xff09; 全白 前言 主要分两部分需要操作&#xff0c; 1&#xff09;地址栏和书签栏 》 需要修改浏览器的外观模式 2&#xff09;页面主体 》 需要安装darkreader插件进行设置 步骤 1&#xff09;地址栏和…

Unity UGUI 实战学习笔记(3)

仅作学习&#xff0c;不做任何商业用途 不是源码&#xff0c;不是源码! 是我通过"照虎画猫"写的&#xff0c;可能有些小修改 不提供素材&#xff0c;所以应该不算是盗版资源&#xff0c;侵权删 拼UI 提示面板的逻辑 using System.Collections; using System.Col…

加密溢出问题

今天编写程序&#xff0c;使用一个非常简单的对256取模的运算&#xff0c;但是总是得不到正确的结果。 后来发现&#xff0c;是数据的值的范围问题。 例如&#xff0c;处理图像时&#xff0c;值的范围是【0,255】. 异或等等运算都是没有问题的。 但是&#xff0c;如果进行加法…

websocket通信问题排查思路

websocket通信问题排查思路 一、websocket连接成功&#xff0c;但数据完全推不过来。 通过抓包发现&#xff0c;是回包时间太长超过了1分钟导致的。这种通常是推送数据的线程有问题导致的。 正常抓包的情况如下&#xff1a; 二、大量数据可以正常推送成功&#xff0c;不定时…

C++从入门到起飞之——内存管理(万字详解) 全方位剖析!

&#x1f308;个人主页&#xff1a;秋风起&#xff0c;再归来~&#x1f525;系列专栏&#xff1a;C从入门到起飞 &#x1f516;克心守己&#xff0c;律己则安 目录 1. C/C内存分布 2. C语言中动态内存管理方式&#xff1a;malloc/calloc/realloc/free 3. C内存管理…

AI大模型大厂面试真题:「2024大厂大模型技术岗内部面试题+答案」

AI大模型岗的大厂门槛又降低了&#xff01;实在太缺人了&#xff0c;大模型岗位真的强烈建议各位多投提前批&#xff0c;▶️众所周知&#xff0c;2025届秋招提前批已经打响&#xff0c;&#x1f64b;在这里真心建议大家6月7月一定要多投提前批&#xff01; &#x1f4bb;我们…

数字音频工作站(DAW)软件FL Studio 24.1.1.4234中文版

在数字化音乐制作的浪潮中&#xff0c;FL Studio 24.1.1.4234的发布无疑又掀起了一股新的热潮。这款由Image-Line公司开发的数字音频工作站&#xff08;DAW&#xff09;软件&#xff0c;以其强大的功能和易用的界面&#xff0c;赢得了全球无数音乐制作人的青睐。本文将深入探讨…

git cherry-pick用法

git cherry-pick 如何将我另一个分支上的某个提交合并到新的分支上 首先切换到新分支上git cherry-pick <commit_hash>例如&#xff1a;git cherry-pick f8a70c9

Linux--Socket编程TCP

前文&#xff1a;Socket套接字编程 TCP的特点 面向连接&#xff1a;TCP 在发送数据之前&#xff0c;必须先建立连接。可靠性&#xff1a;TCP 提供了数据传输的可靠性。面向字节流&#xff1a;TCP 是一个面向字节流的协议&#xff0c;这意味着 TCP 将应用程序交下来的数据看成是…

简单的数据结构:栈

1.栈的基本概念 1.1栈的定义 栈是一种线性表&#xff0c;只能在一端进行数据的插入或删除&#xff0c;可以用数组或链表来实现&#xff0c;这里以数组为例进行说明 栈顶 &#xff1a;数据出入的那一端&#xff0c;通常用Top表示 栈底 :相对于栈顶的另一端&#xff0c;也是固…

【无标题】shell脚本的基本命令+编写shell脚本

shell脚本 一.shell基础 1.shell概念 2.shell脚本 3.shell脚本编写注意事项 二.编写shell脚本 1.编写一个helloworld脚本&#xff0c;运行脚本 [rootshell ~]# vim helloworld.sh #!/bin/bash //声明 echo "hello world!" ls -lh /etc/ 运行脚本(四种方式)&…