深入理解C语言指针——挑战C指针笔试题 _2(和bug郭一起学C系列)

举报
bug郭 发表于 2022/07/31 13:05:44 2022/07/31
1.1k+ 0 0
【摘要】 函数指针首先看一段代码:#include <stdio.h>void test(){ printf("hehe\n");}int main(){ printf("%p\n", test); //函数名 就是函数地址 printf("%p\n", &test); //&函数名 也是函数地址 return 0;}运行结果那么如何将test()函数指针保存起来呢...

函数指针

首先看一段代码:

#include <stdio.h>
void test()
{
    printf("hehe\n");
}
int main()
{
    printf("%p\n",  test); //函数名 就是函数地址
    printf("%p\n", &test); //&函数名 也是函数地址
    
    return 0;
}

运行结果
在这里插入图片描述那么如何将test()函数指针保存起来呢?

void test()
{
 printf("hehe\n");
}
//下面pfun1和pfun2哪个有能力存放test函数的地址?
void (*pfun1)();  //函数指针类型
void *pfun2();    //函数,函数的返回值是void*

函数指针类型
指针都是有类型的
整型指针 int*
数组指针 int (*)[]
函数指针 返回值 (*)(参数....)

#include<stdio.h>
int Add(int x, int y)
{
	return x + y;
}
int main()
{
	int (*pf)(int, int) = Add;
	int sum = (*pf)(3, 5);   //对函数指针解引用
	printf("sum = %d", sum);
	sum = pf(3, 5);          //因为 Add和&Add相同,即Add等价于 pf
	printf("sum = %d", sum);
	return 0;
}

在这里插入图片描述有趣的代码

//代码1
(*(void (*)())0)(); 
//void (*)()为函数指针
//(void (*)())0 将0强制类型抓换成函数指针的地址
//(*(void (*)())0)() *地址,调用0地址处的这个函数
//函数的返回值空,参数为空
//代码2
void (*signal(int , void(*)(int)))(int);
//void(*)(int) 函数指针,返回值void,参数int 
//void (*signal(int , void(*)(int)))(int)
// signal是函数名 
//返回值是void(*)(int)
// 参数int 和函数指针 void(*)(int)
//这是一个函数的声明

当我们看到代码2很难看懂这个代码!
可以简化吗?

void (*signal(int , void(*)(int)))(int);
//既然这个函数的返回值类型是 void(*)(int) 
//那我们可以写成
// void(*)(int) signal(int , void(*)(int));
//但是这样会语法错误 error

在这里插入图片描述函数指针类型重命名
在这里插入图片描述简化

typedef void(*ptr_t) (int);   //正确的类型重命名
 ptr_t signal(int, ptr_t);  //简化

在这里插入图片描述
上面的代码出自《C陷阱和缺陷》
有兴趣的伙伴可以尝试阅读!

函数指针数组

数组是一个存放相同类型数据的存储空间,那我们已经学习了指针数组
比如:

 int* arr[10]; //整型指针数组

那要把函数的地址存到一个数组中,那这个数组就叫函数指针数组,那函数指针的数组如何定义呢?

int (*parr1[10]])(); //
int *parr2[10]();
int (*)() parr3[10];

答案是:parr1 ·parr1· 先和[] 结合,说明parr1是数组,数组的内容是什么呢? 是int (*)()类型的函数指针。

函数指针数组的用途:
转移表
例子:(计算器)

#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 (*pf[4])(int, int) = { add,sub,mul,div };
    int sz = sizeof(pf) / sizeof(pf[1]);
    int i = 0;
    for (i = 0; i < 4; i++)
    {
        //实现5和3的加减乘除
        printf("%d\n", pf[i](5,3));
    }
    return 0;
}

在这里插入图片描述

//计算器优化
#include <stdio.h>
void menu()
{
    printf("*************************\n");
    printf("  1:add           2:sub  \n");
    printf("  3:mul           4:div  \n");
    printf("*************************\n");
    printf("请选择:");
}
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, y;
    printf("请输入两个操作数\n");
    scanf("%d%d", &x, &y);
    printf("%d\n", pf(x, y));
}
int main()
{
    int x, y;
    int input = 1;
    int ret = 0;
    int(*p[5])(int x, int y) = { 0, add, sub, mul, div }; //转移表
    do
    {
        menu();//菜单
        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;
}

在这里插入图片描述

指向函数指针数组的指针

看到这个指针是不是感觉头都大了,没事我们慢慢分析!

int* arr[3]; //整型指针数组
int*(*parr)[3]=&arr; //整型指针数组的地址放入parr
//parr就是指向整型指针数组的指针
//而我们只需要将整型指针换成函数指针就ok了
int(*pf)(int,int);//函数指针
int(*pfarr[3])(int,int);//函数指针数组
int(*(*ppfarr)[3])(int,int)=&pfarr; //&pfarr
//ppfarr是指针,指针指向一个数组,数组的类型为函数指针
//元素个数为3,函数的返回值为int,参数为int

博主就不带大家套娃了,就学到这里了!

回调函数

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

刚刚的计算器优化后就是回调函数,依赖于函数指针。
我们把加减乘除函数的函数指针,传参的形式,传给另一方调用,回调该加减乘除的函数,所以为回调函数!

qsort函数的使用:

在这里插入图片描述
这是MSDN中对qsort函数的解释!
我给大家介绍一下!
qsort
作用

Performs a quick sort.

qsort是快速排序函数库<stdlib.h> and <search.h>中,我们可以通过qsort函数实现任意类型数据的排序!
函数声明

void qsort( void *base, size_t num, size_t width, int (__cdecl *compare )(const void *elem1, const void *elem2 ) );

返回值

void

参数

void *base
待排序的内容的首地址
size_t num
待排序元素的个数
size_t width
待排序元素的大小
int (__cdecl *compare )(const void *elem1, const void *elem2 )
自己定义一个排序的方式,例如一个结构体数组,结构体元素,按照结构体里的什么类型的数据的类型排序,所以需要自己定义一个campare排序方式!
然后将函数指针传参到qsot中!

//qsort函数使用实例
#include<stdio.h>
struct peo 
{
	 char name[10];
	 char numb[12];

};
//自己定义一个排序的方式
int compare_char(const void* e1, const void* e2)
{
	return strcmp(((struct peo*)e1)->numb, ((struct peo*)e2)->numb); 
	//按照peo中numb排序
}

int main()
{
	//创建一个结构体数组
	struct peo peo1[3] = { {"张三","15779770910"},{"李四","17370126640"},{"王五","12669781134"} };
	int sz = sizeof(peo1) / sizeof(peo1[1]);
	qsort(peo1, sz, sizeof(peo1[1]), compare_char);
	for (int i = 0; i < sz; i++)
	{
		printf("%s\t%s\n",peo1[i].name,peo1[i].numb);
	}
	return 0;
}

在这里插入图片描述看完qsort函数使用演示,是不是学会了。

my_qsort函数模拟实现

模仿qsort的功能实现一个通用的冒泡排序!

我们通过qsort函数的使用,我们尝试一下模拟实现qosrt函数。

//模拟实现qsort
//模仿qsort的功能实现一个通用的冒泡排序

//void qsort(void* base, size_t num, size_t width, 
//	       int(__cdecl* compare)(const void* elem1, const void* elem2));

#include<stdio.h>
typedef struct student
{
	char name[5];
	int score;
}Student;
int compare_score(const void*e1,const void* e2)
{
	return ((Student*)e1)->score - ((Student*)e2)->score;
}
int compare_name(const void* e1, const void* e2)
{
	return strcmp(((Student*)e1)->name, ((Student*)e2)->name);
}
void swap(void* e1, void* e2, size_t width)
{
	//将元素一个一个字节交换
	int i = 0;
	for (i = 0; i < width; i++)
	{
		char tmp;
		tmp = *((char*)e1+i);
		*((char*)e1+i) = *((char*)e2+i);
		*((char*)e2+i) = tmp;
	}
}
void my_qsort(void* base, size_t num, size_t width, 
	int(*compare)(const void* e1, const void* e2))
{
	int i = 0, j = 0,flag = 1;
	//趟数
	for (i = 0; i < num-1; i++)
	{
		//每趟比较次数
		for (j = 0; j < num - i - 1; j++)
		{
			//比较
			//因为我们无法得知实参的类型,无法得知指针加减的步长
			//所以我们强制转换成char*类型指针,通过width元素大小进行访问 
			if (compare((char*)base + width * j, (char*)base + (j + 1) * width)>0)
			{
				//交换
				swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
				flag = 0;
			}
			
		}
		if (flag == 1)
			break;
	}
}
int main()
{
	Student student[5] = { {"李四",65},{"张三",78},{"王五",56},{"老王",100},{"小李",98} };
	my_qsort(student, sizeof(student) / sizeof(student[1]),sizeof(student[0]),compare_score);
	int i = 0;
	for (i = 0; i < 5; i++)
	{
		printf("%s %d\n", student[i].name, student[i].score);
	}
	return 0;
}

在这里插入图片描述

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

作者其他文章

评论(0

抱歉,系统识别当前为高风险访问,暂不支持该操作

    全部回复

    上滑加载中

    设置昵称

    在此一键设置昵称,即可参与社区互动!

    *长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

    *长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。