C语言入门(十九):指针(5)

目录

⽬录

[1. 回调函数是什么?](#1. 回调函数是什么?)

[2. qsort使⽤举例](#2. qsort使⽤举例)

[3. qsort函数的模拟实现](#3. qsort函数的模拟实现)


1. 回调函数是什么?

回调函数就是⼀个通过函数指针调⽤的函数。

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

我们来利用回调函数来写一个简化的计算器

cpp 复制代码
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;
}

void calc( int(*pf)(int, int) )
{
	int x = 0;
	int y = 0;
	int ret = 0;

	printf("请输入数字:");
	scanf("%d %d", &x, &y);

	ret = pf(x, y);
	printf("结果为:%d\n",ret);
}

int main()
{
	int input = 0;

	do
	{
		printf("***********************\n");
		printf("  1.add         2.sub  \n");
		printf("  3.mul         4.div  \n");
		printf("         0.exit        \n");
		printf("请选择:\n");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			calc(add);  // ----calc中文意思是:计算
			break;
		case 2:
			calc(sub);
			break;
		case 3:
			calc(mul);
			break;
		case 4:
			calc(div);
			break;
		case 0:
			printf("退出计算机\n");
		default:
			printf("选择错误\n");
			break;
		}
	} while (input);

	return 0;
}

我们可以把调⽤的函数的地址以参数的形式 传递过去,使⽤函数指针接收,函数指针指向什么函数就调⽤什么函数,这⾥其实使⽤的就是回调函 数的功能

解析上面的代码:

下面的代码就是完成两个数的简单的加减乘除

cpp 复制代码
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;
}

这个代码就是我们创建的一个简单的计算器的按键面板

cpp 复制代码
		printf("***********************\n");
		printf("  1.add         2.sub  \n");
		printf("  3.mul         4.div  \n");
		printf("         0.exit        \n");

这个代码是利用switch语句来把我们的按键归为一个情况,1就是加入加号的代码里面去,后面的是一个意思,这里的default是输入了不是case里面的数字,而是输入了其他的,那么就会执行整个代码,即打印出''选择错误''这四个字

cpp 复制代码
switch (input)
		{
		case 1:
			calc(add);  // ----calc中文意思是:计算
			break;
		case 2:
			calc(sub);
			break;
		case 3:
			calc(mul);
			break;
		case 4:
			calc(div);
			break;
		case 0:
			printf("退出计算机\n");
		default:
			printf("选择错误\n");
			break;

这里代码意思是:调用calc(add),把add函数的地址传递给calc,然后去执行calc那里面的代码,这个就是回调函数的用法

后面的加减乘除是一样的,这里我就那+来解析

cpp 复制代码
calc(add);

这里的代码意思是:函数 int(*pf)(int, int) 表示pf是一个指向函数的指针,该函数接受两个int 参数并且返回給int

执行流程:选择+的情况,调用了calc(add)把add函数的地址传给calc ,然后calc函数通过pf(x,y)实际上调用的是add(x,y)

cpp 复制代码
void calc( int(*pf)(int, int) )
{
	int x = 0;
	int y = 0;
	int ret = 0;

	printf("请输入数字:");
	scanf("%d %d", &x, &y);

	ret = pf(x, y);
	printf("结果为:%d\n",ret);
}

2.qsort使⽤举例

2.1使⽤qsort函数排序整型数据

qsort中文意思是:快速排序

先使用冒泡排序来排序一组元素---升序

cpp 复制代码
void bubble_sort(int arr[], int sz)
{
	//趟数
	int i = 0;
	for (i = 0; i < sz - 1; i++)//假如有10个元素,一趟循坏我们只要走9次就行了,因为最后一个数字不需要在进行比较了
	{
		//一趟内部数字的两两比较
		int j = 0;
		for (j = 0; j < sz - 1 - i; j++)
		{
			if (arr[j] > arr[j + 1]) //如果前一项大于后一项则进入if语句
			{
				//前后比较,大的一项与小的一项进行换位置
				int tmp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = tmp;
			}
		}
	}
}

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

int main()
{
	int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
	int sz = sizeof(arr) / sizeof(arr[0]); //计算数组元素个数

	bubble_sort(arr, sz); //调用函数,完成元素的升序

	print_sort(arr, sz); //调用函数,打印排序后的元素

	return 0;
}

输出结果:

解析上面的代码:

这里的 int sz = sizeof(arr) / sizeof(arr[0]); 的作用是计算数组元素的个数

bubble_sort(arr, sz); //调用函数,完成元素的升序

print_sort(arr, sz); //调用函数,打印排序后的元素

cpp 复制代码
int main()
{
	int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
	int sz = sizeof(arr) / sizeof(arr[0]); //计算数组元素个数

	bubble_sort(arr, sz); //调用函数,完成元素的升序

	print_sort(arr, sz); //调用函数,打印排序后的元素

	return 0;
}

这里的代码意思是:利用print_sort函数以及for循环,将我们已经排序好的元素全部打印出来

cpp 复制代码
void print_sort(int arr[],int sz)
{
	int i = 0;
	for (i=0;i<sz;i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
}

这里的代码意思是:

if (arr[j] > arr[j + 1]) ---->如果前一项大于后一项则进入if语句
int tmp = arr[j];
arr[j] = arr[j + 1]; ----->前后比较,大的一项与小的一项进行换位置
arr[j + 1] = tmp;

cpp 复制代码
void bubble_sort(int arr[], int sz)
{
	//趟数
	int i = 0;
	for (i = 0; i < sz - 1; i++)//假如有10个元素,一趟循坏我们只要走9次就行了,因为最后一个数字不需要在进行比较了
	{
		//一趟内部数字的两两比较
		int j = 0;
		for (j = 0; j < sz - 1 - i; j++)
		{
			if (arr[j] > arr[j + 1]) //如果前一项大于后一项则进入if语句
			{
				//前后比较,大的一项与小的一项进行换位置
				int tmp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = tmp;
			}
		}
	}
}

代码解析:假如有10个元素,一趟循坏我们只要走9次就行了,因为最后一个数字不需要在进行比较了

cpp 复制代码
for (i = 0; i < sz - 1; i++)

利用qsort来进行升序(繁琐版)

cpp 复制代码
int cmp_int(const void* p1, const void* p2) //cmp_int 中文意思是:'整型比较'
{
	if (*(int*)p1 > *(int*)p2)   //(int*)---将p1,p2强行转换为int*
	{
		return 1; //两个数进行比较,如果前面的数大于后面一个数就换位置,返回数大于0,这里我们写1
	}
	else if (*(int*)p1 == *(int*)p2)
	{
		return 0; //两个数比较,如果相等,返回数为0
	}
	else
	{
		return -1; //与第一个相反,如果小于,则不用换位置,返回数小于0
	}
	
}
void print_arr(int arr[], int sz) //循环打印数组里面的元素
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
}
void test()
{
	int arr[] = { 2,3,6,7,9,4,0,1,5,8 };
	int sz = sizeof(arr) / sizeof(arr[0]); //计算数组的元素个数

	qsort(arr, sz, sizeof(arr[0]), cmp_int); //qsort函数的调用,还需要头文件 stdlib.h

	print_arr(arr, sz); //将排序好的元素打印出来
}

int main()
{
	test(); //写一段使用qsort函数的代码,来完成升序

	return 0;
}

输出结果:

解析代码:

cpp 复制代码
void test()
{
	int arr[] = { 2,3,6,7,9,4,0,1,5,8 };
	int sz = sizeof(arr) / sizeof(arr[0]); //计算数组的元素个数

	qsort(arr, sz, sizeof(arr[0]), cmp_int); //qsort函数的调用,还需要头文件 stdlib.h

	print_arr(arr, sz); //将排序好的元素打印出来
}

使用qsort函数的前提是需要包含一个头文件的,即<stdlib.h>

qsort 函数的标准格式

cpp 复制代码
void qsort(void *base, size_t num, size_t size,  int (*compar)(const void*, const void*));

参数说明:

  • base:指向要排序的数组的第一个元素的指针

  • num:数组中元素的个数

  • size:每个元素的大小(字节数)

  • compar:比较函数的指针

现在我们可以好好解析一下qsort函数的使用了

cpp 复制代码
qsort(arr, sz, sizeof(arr[0]), cmp_int);

参数对应:

  • arrvoid* base

  • szsize_t num // sz 是 size_t 类型

  • sizeof(arr[0])size_t size // sizeof 返回 size_t 类型

  • cmp_int → 函数指针参数 // 比较函数的地址

现在我们来解析下这个代码:

cpp 复制代码
int cmp_int(const void* p1, const void* p2) //cmp_int 中文意思是:'整型比较'
{
	if (*(int*)p1 > *(int*)p2)   //(int*)---将p1,p2强行转换为int*
	{
		return 1; //两个数进行比较,如果前面的数大于后面一个数就换位置,返回数大于0,这里我们写1
	}
	else if (*(int*)p1 == *(int*)p2)
	{
		return 0; //两个数比较,如果相等,返回数为0
	}
	else
	{
		return -1; //与第一个相反,如果小于,则不用换位置,返回数小于0
	}
	
}

解析:

  • (int*)p1:将 void* 指针转换为 int* 指针
  • *(int*)p1:解引用,获取实际的整数值

两个数进行比较,如果前面的数大于后面一个数就换位置,返回数大于0,这里我们写1

cpp 复制代码
return 1; 
return 0; 
return -1

现在我们来使用简化版的代码,将上面的代码全部简化

利用qsort来进行升序(简化版)

cpp 复制代码
int cmp_int(const void* p1, const void* p2)
{
	return *(int*)p1 - *(int*)p2; //如果需要降序,只需将p1和p2换个位置就行了
}

void print_arr(int arr[], int sz) //循环打印数组里面的元素
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
}
void test()
{
	int arr[] = { 2,3,6,7,9,4,0,1,5,8 };
	int sz = sizeof(arr) / sizeof(arr[0]); //计算数组的元素个数

	qsort(arr, sz, sizeof(arr[0]), cmp_int); //qsort函数的调用,还需要头文件 stdlib.h

	print_arr(arr, sz); //将排序好的元素打印出来
}

int main()
{
	test(); //写一段使用qsort函数的代码,来完成升序

	return 0;
}

上面的代码只改了这一串函数代码:

cpp 复制代码
int cmp_int(const void* p1, const void* p2)
{
	return *(int*)p1 - *(int*)p2; //如果需要降序,只需将p1和p2换个位置就行了
}

比较函数的返回值规则

  • 返回负数:p1 应该排在 p2 前面

  • 返回0:p1 和 p2 相等

  • 返回正数:p1 应该排在 p2 后面

这就是为什么 return (*(int*)p1 - *(int*)p2) 能实现升序排序的原因!

2.2 使⽤qsort排序结构数据

这里我们先回顾一下结构体的打印

cpp 复制代码
struct Stu
{
	char name[20];
	int age;
};

int main()
{
	struct Stu s = { "zhangsan",18 };
	printf("%s %d \n", s.name, s.age);

	return 0;
}

输出结果:

利用指针来进行结构体的打印

cpp 复制代码
struct Stu
{
	char name[20];
	int age;
};

void print(struct Stu* ps)
{
	//printf("%s %d \n", (*ps).name, (*ps).age); //利用解引用打印
	printf("%s  %d \n", ps->name, ps->age); //利用结构体成员访问符打印 ,这里的->是结构体成员的间接访问操作符
}                                                                  用法:   结构体指针->成员名

int main()
{
	struct Stu s = { "zhangsan",20 };
	print(&s);
	return 0;
}

输出结果:

这里的printf我们有两个打印的方法:

  • printf("%s %d \n", (*ps).name, (*ps).age); //利用解引用打印
  • printf("%s %d \n", ps->name, ps->age); //利用结构体成员访问符打印 ,这里的->是结构体成员的间接访问操作符

用法: 结构体指针->成员名

利用qsort函数排序结构体数据

按名字来比较

按照名字来排序,即字符串的比较

cpp 复制代码
struct Stu //结构体的创建
{
	char name[20];
	int age;
};

int cmp_stu_by_name(const void* p1, const void* p2) //按照名字来排序,即字符串的比较
{
	return strcmp(((struct Stu*)p1)->name, ((struct Stu*)p2)->name); //名字的比较,这里的 ->是结构体成员间接访问操作符
}        //由于p1 p2返回类型是void*,所以这里我们需要强行把它们转换为结构体指针

void test2()
{
	struct Stu arr[3] = { {"zhangsan",20},{"lisi",18} ,{"wangwu",35} };//结构体的初始化
	int sz = sizeof(arr) / sizeof(arr[0]);
	qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_name);//名字比较--字符串比较--需要使用strcmp函数进行比较,它需要一个头文件string.h
}

int main()
{
	test2(); //写一段使用qsort函数的代码,来进行结构体的排序

	return 0;
}

调试结果:

按年龄来比较

cpp 复制代码
按年龄来比较
struct Stu //结构体的创建
{
	char name[20];
	int age;
};

int cmp_stu_by_age(const void* p1, const void* p2) //按照名字来排序,即字符串的比较
{
	return ((struct Stu*)p1)->age- ((struct Stu*)p2)->age;
}       

void test2()
{
	struct Stu arr[3] = { {"zhangsan",20},{"lisi",18} ,{"wangwu",35} };//结构体的初始化
	int sz = sizeof(arr) / sizeof(arr[0]);
	qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_age);//名字比较--字符串比较--需要使用strcmp函数进行比较,它需要一个头文件string.h
}

int main()
{
	test2(); //写一段使用qsort函数的代码,来进行结构体的排序

	return 0;
}

调试结果:

解析代码:

定义了一个学生结构体,包含姓名和年龄两个成员

cpp 复制代码
struct Stu //结构体的创建
{
	char name[20];
	int age;
};

比较函数 cmp_stu_by_age

  • const void* p1, const void* p2:qsort 要求的参数格式

  • (struct Stu*)p1:将 void 指针转换为 struct Stu 指针

  • ((struct Stu*)p1)->age:访问结构体的 age 成员

  • return ... - ...:返回年龄差值,实现升序排序

cpp 复制代码
int cmp_stu_by_age(const void* p1, const void* p2) //按照名字来排序,即字符串的比较
{
	return ((struct Stu*)p1)->age- ((struct Stu*)p2)->age;
}       

测试函数 test2

  • 创建结构体数组初始化
  • 计算数组大小
  • 调用 qsort
cpp 复制代码
void test2()
{
	struct Stu arr[3] = { {"zhangsan",20},{"lisi",18} ,{"wangwu",35} };//结构体的初始化
	int sz = sizeof(arr) / sizeof(arr[0]);
	qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_age);//名字比较--字符串比较--需要使用strcmp函数进行比较,它需要一个头文件string.h
}

3. qsort函数的模拟实现

使⽤回调函数,模拟实现qsort(采⽤冒泡的⽅式)

完整代码如下:

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

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

cpp 复制代码
int int_cmp(const void * p1, const void * p2)
{
    return (*( int *)p1 - *(int *) p2);
}

知识点:

  • const void *:通用指针,可以指向任何数据类型

  • (int *)p1:将void指针强制转换为int指针

  • *(int *)p1:解引用获取整数值

  • 返回值>0:p1>p2,需要交换

  • 返回值=0:p1=p2,相等

  • 返回值<0:p1<p2,保持原位

交换函数 _swap

cpp 复制代码
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;
    }
}

核心知识点:

  • (char *)p1:转换为字节指针,便于按字节操作

  • *((char *)p1 + i):访问第i个字节的内容

  • 通过循环逐个字节交换,实现任意数据类型的交换

冒泡排序函数 bubble

cpp 复制代码
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);
            }
        }
    }
}

关键知识点:

地址计算

cpp 复制代码
(char *) base + j * size
  • (char *)base:转换为字节指针

  • j * size:计算第j个元素的偏移量

  • 示例:int数组,size=4,j=2 → 偏移8字节

回调函数调用:

cpp 复制代码
cmp((char *) base + j*size, (char *)base + (j + 1)*size)
  • 动态调用用户提供的比较函数

  • 实现通用排序算法的关键

int(*cmp)(void *, void *)

// ↑ ↑ ↑ ↑

// 返回 指针 参数1 参数2

// 类型 变量名 类型 类型

  • int(* ):表示这是一个指向返回int的函数的指针

  • cmp:函数指针变量的名称

  • (void *, void *):该函数接受两个void指针参数

通用地址计算机制

cpp 复制代码
(char *) base + j * size

支持任意数据类型的原理

cpp 复制代码
// 对于int数组:size = 4
(char *)base + j * 4

// 对于double数组:size = 8  
(char *)base + j * 8

// 对于结构体:size = sizeof(struct)
(char *)base + j * sizeof(struct Student)

主函数 main

cpp 复制代码
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);
    // ... 输出代码
    return 0;
}

函数调用:

cpp 复制代码
bubble(arr, 10, 4, int_cmp);
// 参数:数组首地址, 元素个数, 元素大小, 比较函数

以上就是我们的全部内容了

相关推荐
程序员Jared3 小时前
深入浅出C语言——程序环境和预处理
c语言
应茶茶3 小时前
从 C 到 C++:详解不定参数的两种实现方式(va_args 与参数包)
c语言·开发语言·c++
福尔摩斯张4 小时前
Linux信号捕捉特性详解:从基础到高级实践(超详细)
linux·运维·服务器·c语言·前端·驱动开发·microsoft
【蜡笔小新】5 小时前
《筑基篇》C语言基础2
c语言·开发语言
学习路上_write5 小时前
AD5293驱动学习
c语言·单片机·嵌入式硬件·学习
Herbert_hwt6 小时前
C语言一维数组完全指南:从基础到实战应用
c语言
你想知道什么?7 小时前
JNI简单学习(java调用C/C++)
java·c语言·学习
吃西瓜的年年7 小时前
3. C语言核心语法2
c语言·嵌入式硬件·改行学it
福尔摩斯张7 小时前
基于C++的UDP网络通信系统设计与实现
linux·c语言·开发语言·网络·c++·tcp/ip·udp
hkNaruto7 小时前
【规范】Linux平台C/C++程序版本发布调试规范手册 兼容银河麒麟
linux·c语言·c++