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

「C++ 40 周年」主题征文大赛(有机会与C++之父现场交流!) 10w+人浏览 395人参与

目录

⽬录

1. 回调函数是什么?

2. qsort使⽤举例

3. qsort函数的模拟实现


1. 回调函数是什么?

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

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

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

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;
}

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

解析上面的代码:

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

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;
}

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


		printf("***********************\n");
		printf("  1.add         2.sub  \n");
		printf("  3.mul         4.div  \n");
		printf("         0.exit        \n");

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

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那里面的代码,这个就是回调函数的用法

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

calc(add);

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

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

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中文意思是:快速排序

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

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); //调用函数,打印排序后的元素


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循环,将我们已经排序好的元素全部打印出来


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;

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次就行了,因为最后一个数字不需要在进行比较了

for (i = 0; i < sz - 1; i++)

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

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;
}

输出结果:

解析代码:

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 函数的标准格式

void qsort(void *base, size_t num, size_t size,  int (*compar)(const void*, const void*));

参数说明:

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

  • num:数组中元素的个数

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

  • compar:比较函数的指针

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

qsort(arr, sz, sizeof(arr[0]), cmp_int);

参数对应:

  • arr → void* base

  • sz → size_t num     // sz 是 size_t 类型

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

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

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

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

return 1; 
return 0; 
return -1

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

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

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;
}

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

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排序结构数据

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

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

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

	return 0;
}

输出结果:

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

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函数排序结构体数据

按名字来比较

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


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;
}

调试结果:

按年龄来比较


按年龄来比较
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;
}

调试结果:

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

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 ... - ...:返回年龄差值,实现升序排序

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

测试函数 test2

  • 创建结构体数组初始化: 
  • 计算数组大小
  • 调用 qsort
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(采⽤冒泡的⽅式)

完整代码如下:

#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

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

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

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);
            }
        }
    }
}

关键知识点:

地址计算

(char *) base + j * size
  • (char *)base:转换为字节指针

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

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

回调函数调用:

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

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

int(*cmp)(void *, void *)
// ↑   ↑   ↑        ↑
// 返回 指针 参数1   参数2
// 类型 变量名 类型    类型

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

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

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

通用地址计算机制

(char *) base + j * size

 支持任意数据类型的原理

// 对于int数组:size = 4
(char *)base + j * 4

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

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

主函数 main

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;
}

函数调用:

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

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

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值