首页 > 其他分享 >深入C语言指针,使代码更加灵活(三)

深入C语言指针,使代码更加灵活(三)

时间:2024-03-18 20:00:01浏览次数:22  
标签:return 函数 int 代码 C语言 printf 函数指针 void 指针

一、函数指针

1.1 函数的地址

在讲解函数指针变量之前,我们先思考一下什么是函数指针变量,我们可以同数组指针变量进行类比:

数组指针—是指针—是存放指向数组的指针,是存放数组地址的指针;
函数指针—是指针—是存放指向函数的指针,是存放函数地址的指针;

数组是有地址的,那么函数是否也有地址呢?

我们来做个测试:

#include <stdio.h>
void test()
{
 printf("hehe\n");
}
int main()
{
 printf("test: %p\n", test);
 printf("&test: %p\n", &test);
 return 0;
}

运行结果:

我们发现:确实打印出来了地址,所以函数是有地址的,并且同数组名是数组首元素地址一样,函数名也是函数的地址,我们可以通过 &函数名 的方式来获得函数的地址。

1.2 函数指针变量

如果我们要将函数的地址存放起来,就得创建函数指针变量,而函数指针变量的写法和数组指针也有许多相似之处:

函数的返回值类型(*指针名)(函数的参数类型)

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>

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

int main()
{
	int (*pf)(int x, int y) = &add;
	//int——表示pf指向函数的返回类型
	//pf——函数指针变量名
	//int x, int y——pf指向函数的参数类型和个数的交代

	int ret = (*pf)(3, 5);

	printf("%d\n", ret);
	return 0;
}

运行结果为 8

1.3 函数指针的使用

  参考如下代码:

    int (*pf)(int a, int b) = &Add;
    int ret1 = (*pf)(3, 5);//相当于Add(3,5)
    int ret2 = pf(3, 5);//相当于Add(3,5)
  1. 对pf解引用相当于通过pf找到Add函数名,然后输入参数进行使用。

  2. 而我们知道&Add==Add,所以我们也能通过直接使用函数指针变量来调用函数。

  • 但是函数指针变量不能像其他指针变量进行±运算

1.4 两段有趣的代码

  • 代码1:
(*(void (*)())0)();

首先我们从里往外拆分,在这里,我们把0强制类型转换成函数指针类型,这个函数指针参数是无参,返回值类型是void,然后通过解引用去调用函数,我们可以将其简化为pf。void (*)() — 是函数指针,参数是无参,返回类型是void。

(void (*)()) — 函数指针外面加上括号,表示强制类型转换。

(*(pf)0)();//简化后

这下我们比较容易看出这段代码是先将0强制类型转换为函数指针类型,然后对其解引用。解引用之后相当于调用在0地址的函数,因为其参数为空所以只有一个单独的()。

  • 代码2:
void (*signal(int , void(*)(int)))(int);

首先signal与()结合说明其是一个函数名,它有两个参数,一个整型,另一个是函数指针类型。

我们将signal(int ,void(*)(int))单独拿出来,这段代码只剩void(*)(int),这就说明该函数的返回类型是一个函数指针,指向一个参数为int,返回为void的函数。

可能有小伙伴觉得这种写法太复杂了,想简化成下面这种形式:

void (*)(int) signal(int , void(*)(int))

很遗憾,上面这种写法是错误的

事实上,在C语言中有一个关键字叫typedef,我们可以用它来将复杂的类型简单化。

1.4.1 typedef关键字

我们可以用typedef关键字来简化signal函数:

	typedef void(*pfun_t)(int);//将void(*)(int)简化
	pfun_t signal(int, pfun_t);//化简之后

二、计算器

2.1 函数指针数组

学习了函数指针数组的创建,可能小伙伴们会想,函数指针数组到底有什么用呢?别着急,函数指针的用途可大了,比如说,我们要写一段代码来实现计算器。

我们可以采用一般写法:

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>

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****\n");
	printf("*****2.sub****\n");
	printf("*****3.mul****\n");
	printf("*****4.div****\n");
	printf("*****0.exit****\n");
	printf("**************\n");
}
int main()
{
	int x = 0;
	int y = 0;
	int ret = 0;
	int input = 0;
	do{
		menu();
		printf("请选择:");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			printf("请输入两个数:");
			scanf("%d %d", &x, &y);
			ret = add(x, y);
			printf("%d\n", ret);
			break; 
		case 2:
			printf("请输入两个数:");
			scanf("%d %d", &x, &y);
			ret = sub(x, y);
			printf("%d\n", ret);
			break;
		case 3:
			printf("请输入两个数:");
			scanf("%d %d", &x, &y);
			ret = mul(x, y);
			printf("%d\n", ret);
			break;
		case 4:
			printf("请输入两个数:");
			scanf("%d %d", &x, &y); 
			ret = div(x, y);
			printf("%d\n", ret);
			break;
		case 0:
			printf("退出计算器\n");
			break;
		default:
			printf("选择错误,重新选择\n");
			break;
		}
	} while (input);
}

我们发现,确实能够实现计算器的加减乘除功能,但是我们也观察到,随着计算器功能增加,代码也会越来越长。显然,这样的代码显得太冗余了,我也需要对其进行改造。而要进行改造,我们就不得不利用函数指针!

改造后:

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>

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******\n");
	printf("*****2.sub******\n");
	printf("*****3.mul******\n");
	printf("*****4.div******\n");
	printf("*****0.exit*****\n");
	printf("****************\n");
}
int main()
{
	//函数指针的数组
	int(*parr[])(int, int) = { 0, add, sub, mul, div };

	int x = 0;
	int y = 0;
	int ret = 0;
	int input = 0;
	do{
		menu();
		printf("请选择:");
		scanf("%d", &input);
		if (input >= 1 && input <= 4)
		{
			printf("请输入两个数:");
			scanf("%d %d", &x, &y);
			ret = parr[input](x, y);
			printf("%d\n", ret);
		}
		else if (input == 0)
		{
			printf("退出计算器\n");
		}
		else
		{
			printf("选择错误,重新选择\n");
		}
	} while (input);
}

我们发现,结果依然是正确的,但是这样的代码就没有了上面那样的冗余,我们通过一个下标,在函数指针数组里面找到了一个函数的地址,然后通过这个地址去调用这个函数,直接传参得出结果,这个效率就快得多。

但这种写法也存在一定的局限性,它里面只能存放相同类型的函数,即只能计算整数,不能计算浮点数!

2.2 回调函数

看到上面我们写的第一种计算器的方式:

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>

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****\n");
	printf("*****2.sub****\n");
	printf("*****3.mul****\n");
	printf("*****4.div****\n");
	printf("*****0.exit****\n");
	printf("**************\n");
}
int main()
{
	int x = 0;
	int y = 0;
	int ret = 0;
	int input = 0;
	do{
		menu();
		printf("请选择:");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			printf("请输入两个数:");
			scanf("%d %d", &x, &y);
			ret = add(x, y);
			printf("%d\n", ret);
			break;
		case 2:
			printf("请输入两个数:");
			scanf("%d %d", &x, &y);
			ret = sub(x, y);
			printf("%d\n", ret);
			break;
		case 3:
			printf("请输入两个数:");
			scanf("%d %d", &x, &y);
			ret = mul(x, y);
			printf("%d\n", ret);
			break;
		case 4:
			printf("请输入两个数:");
			scanf("%d %d", &x, &y);
			ret = div(x, y);
			printf("%d\n", ret);
			break;
		case 0:
			printf("退出计算器\n");
			break;
		default:
			printf("选择错误,重新选择\n");
			break;
		}
	} while (input);
}

我们发现代码中存在许多重复的部分那有没有什么方法来简化一下代码呢?

那就是我们今天要介绍的回调函数

那什么是回调函数呢?唉,别着急,我们还是先举一个例子,用calc函数来代替上面计算器代码中冗余的部分:

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>

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******\n");
	printf("*****2.sub******\n");
	printf("*****3.mul******\n");
	printf("*****4.div******\n");
	printf("*****0.exit*****\n");
	printf("****************\n");
}

void calc(int(*p)(int, int))
{
	int x = 0;
	int y = 0;
	int ret = 0;
	printf("请输入两个数:");
	scanf("%d %d", &x, &y);
	ret = p(x, y);
	printf("%d\n", ret);
}

int main()
{
	int x = 0;
	int y = 0;
	int ret = 0;
	int input = 0;
	do{
		menu();
		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);
}

回调函数其实就是通过函数指针调用的函数!

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

简单理解就是,我们通过函数指针来调用其所指向的函数,就被称为回调函数。

三、qsort函数

3.1 qsort函数的使用

在C语言库中,有一个qsort的库函数,它可以用来排序任意类型的数据。它的详细介绍可以参考cplusplus网站:qsort,这里我们只需要掌握它的参数类型、返回值即可。

声明:void qsort(void *base, size_t nitems, size_t size, int (*compar)(const void , const void))

  1. base – 指向要排序的数组的第一个元素的指针。
  2. nitems – 由 base 指向的数组中元素的个数。
  3. size – 数组中每个元素的大小,以字节为单位。
  4. compar – 用来比较两个元素的函数。

作用:对数组元素进行排序(升序)

返回值:void

细心的小伙伴可能会发现,我们这里出现了一个新的指针类型 void*,这是究竟是一种什么类型的指针呢?

void* 也是一种指针类型,这种指针类型我们称之为通用指针类型。void* 类型的指针变量,可以接收任意类型数据的地址。既然void* 可以接收任意类型数据的地址,那么它的“大小”也是未知的,我们无法对p进行加减、解引用等常规操作。

3.1.1 qsort对整型数组的排序

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>

int cmp_int(const void* e1, const void* e2)//这个函数能够比较e1和e2指向的两个元素,并给出返回值(回调函数)
{
	return *(int*)e1 - *(int*)e2;
}

void print_arr(int arr[], int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
}

//test1测试qsort函数排序整型数据
void test1()
{
	int arr[] = { 8, 2, 6, 4, 5, 2, 7, 1, 9 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	qsort(arr, sz, sizeof(arr[0]), cmp_int);
	print_arr(arr, sz);
}

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

运行结果如下:

3.1.2 qsort对结构体的排序

(一)按年龄来比较

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>

struct stu
{
	char name[20];//名字
	int age;//年龄
};

//test2测试qsort函数排序结构体数据
int cmp_stu_by_age(const void* e1, const void* e2)
{
	return ((struct stu *)e1)->age - ((struct stu *)e2)->age;
}

void print(struct stu* s, int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%s %d\n", s[i].name, s[i].age);
	}
}

void test2()
{
	struct stu s[] = { { "zhangsan", 20 }, { "lisi", 30 }, { "wangwu", 15 } };
	int sz = sizeof(s) / sizeof(s[0]);
	qsort(s, sz, sizeof(s[0]), cmp_stu_by_age);
	print(s, sz);
}

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

 (二) 按名字来比较(ASCII码)

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

struct stu
{
	char name[20];//名字
	int age;//年龄
};

//test2测试qsort函数排序结构体数据
int cmp_stu_by_name(const void* e1, const void* e2)
{
	return strcmp(((struct stu *)e1)->name, ((struct stu *)e2)->name);
}

void print(struct stu* s, int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%s %d\n", s[i].name, s[i].age);
	}
}

void test2()
{
	struct stu s[] = { { "zhangsan", 10 }, { "lisi", 30 }, { "wangwu", 15 } };
	int sz = sizeof(s) / sizeof(s[0]);
	qsort(s, sz, sizeof(s[0]), cmp_stu_by_name);
	print(s, sz);
}

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

3.2 模拟实现qsort函数

讲解完qsort函数的使用,相信小伙伴们对其也有了一定的理解,但仅仅学会用是远远不够的,想要彻底地掌握,我还必须明白它的底层逻辑,这就需要我们去模拟实现qsort函数。

首先我们要理解qsort函数是怎么进行排序的,qsort函数排序和我们之前学过的冒泡排序有类似之处,冒泡排序也是通过比较两个元素大小来确定谁在前、谁在后。但是冒泡排序仅限于比较整型元素,不能对各种类型的变量进行排序,因此,我们可以把qsort函数看作是冒泡排序的一种拓展。

代码示例如下:

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>


void Swap(char* buf1, char* buf2, int width)
{
	int i = 0;
	for (i = 0; i < width; i++)
	{
		char tmp = *buf1;
		*buf1 = *buf2;
		*buf2 = tmp;
		buf1++;
		buf2++;
	}
}


bublle_arr(void* base, int sz, int width, int(*cmp)(const void* e1, const void* e2))
{
	int i = 0;
	for (i = 0; i < sz - 1; i++)
	{
		int j = 0;
		for (j = 0; j < sz - 1 - i; j++)
		{
			if (cmp((char*)base + j*width, (char*)base + (j + 1)*width) > 0)
			{
				Swap((char*)base + j*width, (char*)base + (j + 1)*width, width);
			}
		}
	}
}


int cmp_int(const void* e1, const void* e2)
{
	return (*(int*)e1) - (*(int*)e2);
}

void test1()//排序整型类型数据
{
	int arr[] = { 7, 5, 3, 6, 9, 8, 1, 2, 0 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	bublle_arr(arr, sz, sizeof(arr[0]), cmp_int);
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
}

struct stu
{
	char name[20];
	int age;
};

int cmp_name(const void* e1, const void* e2)
{
	return strcmp(((struct stu*)e1)->name, ((struct stu*)e2)->name);
}


void test2()//排序结构体数据
{
	struct stu s[] = { { "zhangsan", 33 }, { "lisi", 45 }, { "wangwu", 25 } };
	int sz = sizeof(s) / sizeof(s[0]);
	bublle_arr(s, sz, sizeof(s[0]), cmp_name);
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%s %d", s[i].name, s[i].age);
		printf("\n");
	}
}

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

标签:return,函数,int,代码,C语言,printf,函数指针,void,指针
From: https://blog.csdn.net/x3262551980/article/details/136654457

相关文章

  • 基于YOLOv8/YOLOv7/YOLOv6/YOLOv5的血细胞检测与计数系统(Python+PySide6界面+训练代码
    摘要:本文介绍了一种基于深度学习的血细胞检测系统系统的代码,采用最先进的YOLOv8算法并对比YOLOv7、YOLOv6、YOLOv5等算法的结果,能够准确识别图像、视频、实时视频流以及批量文件中的血细胞。文章详细解释了YOLOv8算法的原理,并提供了相应的Python实现代码、训练数据集,以及基于PySid......
  • 基于YOLOv8/YOLOv7/YOLOv6/YOLOv5的犬种识别系统(附完整代码资源+UI界面+PyTorch代码)
    摘要:本文介绍了一种基于深度学习的犬种识别系统系统的代码,采用最先进的YOLOv8算法并对比YOLOv7、YOLOv6、YOLOv5等算法的结果,能够准确识别图像、视频、实时视频流以及批量文件中的犬种。文章详细解释了YOLOv8算法的原理,并提供了相应的Python实现代码、训练数据集,以及基于PySide6的......
  • 基于YOLOv8/YOLOv7/YOLOv6/YOLOv5的疲劳驾驶检测系统(Python+PySide6界面+训练代码)
    摘要:本研究详述了一种采用深度学习技术的疲劳驾驶检测系统,该系统集成了最新的YOLOv8算法,并与YOLOv7、YOLOv6、YOLOv5等早期算法进行了性能评估对比。该系统能够在各种媒介——包括图像、视频文件、实时视频流及批量文件中——准确地识别疲劳驾驶行为。文章深入阐述了YOLOv8算法的......
  • 用C语言实现二分查找
    //二分查找,前提必须是有序#define_CRT_SECURE_NO_WARNINGS#include<stdio.h>intmain(){ intarr[]={1,2,3,4,5,6,7,8,9,10}; intsz=sizeof(arr)/sizeof(arr[0]);//求数组长度 intk=7;//要查找的数 intleft=0; intright=sz-1; intmid=0; i......
  • 学习笔记——C语言基本概念&运算符——(2)
    目录一、运算符1.1赋值运算符1.2算数运算符 1.3关系运算符1.5位运算符1.6 自增自减运算符1.7  复合运算符1.8逗号运算符1.9 三目运算符1.10 sizeof运算符附录:运算符优先性表一、运算符1>.按照操作数目分类:单目运算符,双目运算符,三目运算符。2>......
  • C语言随记————简单算法
    1.问题:如何在C语言中实现一个简单的线性查找算法? 答案:线性查找算法可以通过遍历数组的每个元素,逐一比较来查找目标值。以下是一个简单的实现示例:intlinearSearch(intarr[],intn,intx){for(inti=0;i<n;i++){if(arr[i]==x)re......
  • P1712 [NOI2016] 区间 线段树+双指针
    //Problem://P1712[NOI2016]区间////Contest:Luogu//URL:https://www.luogu.com.cn/problem/P1712//MemoryLimit:250MB//TimeLimit:1000ms////PoweredbyCPEditor(https://cpeditor.org)#include<iostream>#include<algorithm......
  • 深入了解指针(3)【数组指针变量】【函数指针变量】【typedef关键字】
    一.数组指针变量1.什么是数组指针变量?说到数组指针,那必然要说一下指针数组,指针数组是什么呢?指针数组是一种数组,只不过这种数组存放的是地址(指针)。那把这两个词反过来,数组指针是什么?它是指针变量,还是数组?答案是:指针变量。这个指针有些特殊,它存放的是数组的地址,它是能够指向数......
  • Pandas:如何让你的代码性能飙升
    在数据分析相关的工作中,Pandas无疑是一个强大的工具,它的易用性和灵活性广受青睐。然而,随着数据量的不断增长和计算需求的日益复杂,Pandas代码的性能问题也逐渐浮出水面。如何让Pandas代码运行得更快、更高效,成为了每一个人使用者都需要面对的挑战。今天,本文就一个简化版的实际分析......
  • Nodgd 亲笔代码!!!
    AVL:#include<bits/stdc++.h>usingnamespacestd;structNode{intkey;intson[2],hei;}node[12345678];inttotal;structAVL{introot;voidupdate(inti){node[i].hei=max(node[node[i].son[0]].hei,node[node[i].s......