C语言学习笔记---指针(5)

目录

先回顾一下上一节的部分内容

数组指针:指向数组的指针

函数指针:指向函数的指针

再加深一下上节课讲过的代码的理解

函数指针数组

实现一个计算器

回调函数

qsort

1.测试qsort排序整型数据

2.测试qsort排列结构体数据


先回顾一下上一节的部分内容


数组指针:指向数组的指针

int main()
{
	int arr[10] = { 0,1,2,3,4,5,6,7,8,9 };
	int(*parr)[10] = &arr;
	//*号告诉我们parr存放了数组的地址的指针,这个数组的元素个数是10,数组中每个元素的类型是int
	//parr存放了数组的地址,而数组的地址是以首元素的地址为起点
	int i = 0;
	int sz = sizeof(arr) / sizeof(int);
	for (i = 0; i < sz; i++)
	{
		printf("%d ", *((*parr)+i)); 
		//*((*parr) + i)先解引用parr找到数组(以首元素的地址为起点),
		//然后再+i找到下标为i的地址,再解引用找到下标为i的元素的值
	}
	
	return 0;
}

函数指针:指向函数的指针

//先创建一个函数
int test(char*pc, int a)
{
	//......
	return 0;
}
//接下来创建一个指针指向test()这个函数
int main()
{
	int(*pf)(char*,int) = &test;
	//*号告诉我们pf存放了函数test的地址,这个函数的第一个参数类型是char*,第二个参数的类型是int,
	//这个函数的返回值类型是int

	//通过函数指针去调用这个函数
	(*pf)("name", 26);
	//pf存放了test函数的地址,解引用之后找到这个函数,然后给这个函数传参"name", 26
	//其实pf前面的*号可以不用写,因为pf本身存放的就是test函数的地址,可以不用解引用
	//由于函数名就是函数的地址
	//之前我们常规的写法是test("name", 26),也是直接通过函数名来调用函数,所以不用解引用
	//可以直接写成pf("name", 26)

	return 0;
}

再加深一下上节课讲过的代码的理解


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

signal(int, void(*)(int))是一个函数声明,
这个函数的第一个参数类型是int,第二个函数类型是函数指针类型,
也就是说第二个参数是存放了一个函数地址的指针(它的函数参数类型是int,返回值类型是void)

void(* )(int) 这个是signal函数的返回值类型(这个返回值是存放了一个函数地址的指针(它的函数参数类型是int,返回值类型是void))
总的来说,上面这整段代码是一个函数声明

如果写成这样的话就比较容易看出来:void(* )(int) signal( int, void(*)(int) )
但是对于返回值为函数指针类型的函数来说,这样写声明是错误的
所以必须将函数写进返回值类型中*后面,于是就成了这样:void(* signal( int,void(*)(int) ))(int)

补充一点
函数的声明,以下两种写法都是对的
int Add(int x,int y)
int Add(int, int)只指定参数类型,没有定义形参

如果要简化上述的void(* )(int) 指针类型,可以用typedef重命名
typedef void(* )(int) pf_t;//但是这种写法是错误的

对于函数指针类型的重命名,应该这样写
typedef void(* pf_t )(int);将重命名写进原名中*后面
重命名后,void(* signal( int,void(*)(int) ))(int)就可以这样写了:
pf_t signal(int,pf_t);

注意重命名时typedef void(* pf_t )(int)里面的pf_t不是函数指针变量的名字,而是重命名后函数指针类型的别名
但是如果将typedef去掉,则void(* pf_t )(int)里面的pf_t就变成函数指针变量名了

复习一下:extern是用来声明外部符号的,来自其他源文件的,要加extern,来自本文件的话可以不用加
 

新知识:


函数指针数组

//先类比一下:
//char* arr[5];//字符指针数组
//int* arr[6];//整型指针数组
//把指针可以放在数组中
//函数指针也是指针,是否也能放在数组中呢?yes!!!
//这种数组就是函数指针数组

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(*pf)(int,int) = Add;//pf是函数指针
	int (*pfarr[4])(int, int) = {Add,Sub,Mul,Div};//存放函数指针的数组-函数指针数组
	//在函数指针的指针变量后面加上[数组元素的个数]就变成了函数指针数组
	//函数个数不写也可以int (*pfarr[])(int, int)
	//int (*)(int, int)这是函数指针类型
	//以上就是一个存放了四个函数地址的数组

	//如何访问这个函数指针数组?
	int i = 0;
	for (i = 0; i < 4; i++)
	{
		//int ret =*pfarr[i](6, 2);
		//也可以把*去掉,函数指针去调用函数的时候,*号是个摆设,
		//比如我们平时写到调用函数是:函数名(),
		//而函数名存放的就是函数的地址,
		//那指针变量名pfarr存放的也是函数的地址,
		//我们通过函数名或者指针变量名里面存放的函数地址找到函数,然后传参
		//也可以通过pfarr解引用后找到函数,传参
		int ret=pfarr[i](6,2);//访问i为下标的元素
		//第一个元素(函数)是做加法
		//第二个元素(函数)是做减法
		//第三个元素(函数)是做乘法
		//第四个元素(函数)是做除法

		printf("%d\n", ret);//8 4 12 3
	}

	return 0;
}

实现一个计算器


//这个计算器能够计算整数的:加法,减法,乘法,除法

void menu()
{
	printf("********1.Add*********\n");
	printf("********2.Sub*********\n");
	printf("********3.Mul*********\n");
	printf("********4.Dev*********\n");
	printf("********0.Exit********\n");
}

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 input = 0;
	int x = 0;
	int y = 0;
	int ret = 0;
	do
	{
		
		menu();
		printf("请输入运算类型:\n");
		scanf("%d", &input);
		switch (input)//以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);//intput不是0时,进入循环,input为0时,退出循环
	return 0;
}

//以上代码可以优化以下,用函数指针数组来实现
//这里的函数指针数组,我们称为转移表

void menu()
{
	printf("********1.Add*********\n");
	printf("********2.Sub*********\n");
	printf("********3.Mul*********\n");
	printf("********4.Dev*********\n");
	printf("********0.Exit********\n");
}

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 input = 0;
	do
	{
		menu();
		printf("请输入运算类型:");
		scanf("%d", &input);
		if (input==0)
		{
			printf("退出计算器\n");
		}
		else if (input <=4)
		{
			int x = 0;
			int y = 0;
			printf("请输入两个操作数:");
			scanf("%d %d", &x, &y);//补充:scanf其实是有返回值,这里它的返回值是int,但是我们不关心它的返回值,编译器就会报警,不过不用管
			int (*pfarr[5])(int, int) = { NULL,Add,Sub,Mul,Div };
			int ret = pfarr[input](x, y);
			printf("%d\n", ret);
		}
		else
		{
			printf("输入错误,请重新输入\n");
		}
	} while (input);
	return 0;
}
//这样优化之后,如果还想添加其他的运算类型的话,需要改变的代码比较少也比较简便

//函数指针到底有什么用呢?

回调函数


//函数指针可以用来实现回调函数:在一个函数里面通过一个函数指针去调用另一个函数,则被调用的函数就叫回调函数
//这种调用函数的方式是间接的,不是直接的
//以上代码还可以用回调函数优化:

void menu()
{
	printf("********1.Add*********\n");
	printf("********2.Sub*********\n");
	printf("********3.Mul*********\n");
	printf("********4.Dev*********\n");
	printf("********0.Exit********\n");
}

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))//函数的返回值没有在主函数中赋值给谁的话,
//我们就不用返回值,直接写void,只让它执行这段代码,不需要返回值
{
	int x = 0;
	int y = 0;
	printf("请输入两个操作数:");
	scanf("%d %d", &x, &y);
	int ret = pf(x, y);//只有调用这里不同,可以用回调函数解决,将函数的地址作为参数传给另一个函数
	//pf存放了函数的地址,可以通过函数地址找到函数,传参x,y过去进行计算
	//如果传过来的是Add函数的地址,然后通过指针pf在这里调用Add函数,则Add就被称为回调函数
	printf("%d\n", ret);
}

int main()
{
	int input = 0;
	
	do
	{
		
		menu();
		printf("请输入运算类型:\n");
		scanf("%d", &input);
		switch (input)//以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");
			break;
		default:
			printf("输入错误,请重新输入\n");
			break;
		}
		
	} while (input);//intput不是0时,进入循环,input为0时,退出循环
	return 0;
}

qsort

//函数指针的使用:qsort
//qsort是库函数,这个函数可以完成任意类型数据的排序

怎么使用qsort进行排序呢?

//先复习一下冒泡排序 

void Bubble_arr(int arr[], int sz)//这段冒泡排序的缺陷是:只能排序整型数据
{
	int i = 0;//趟数
	for (i = 0; i < sz - 1; i++)
	{
		//一趟冒泡排序的过程
		//两两相邻元素的比较
		int j = 0;//下标
		for (j = 0; j < sz - 1 - i; j++)
		{
			if (arr[j] > arr[j + 1])
			{
				int tmp = 0;
				tmp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = tmp;
			}

		}
	}
}

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

int main()
{
	int arr[10] = { 1,4,7,8,5,2,3,6,9,0 };
	int sz = sizeof(arr) / sizeof(int);
	Print_arr(arr, sz);//打印
	Bubble_arr(arr, sz);//升序
	Print_arr(arr, sz);//打印
	
	return 0;
}

//前面说过由于冒泡排序Bubble_arr这个函数是参数写死了是int类型
//所以这段冒泡排序的缺陷是:只能排序整型数据
//但是qsort函数就能实现任意数据的排序

//先认识一个qsort函数的参数

void qsort
 (
	void* base,//base指向了要排序的数组的第一个元素
	size_t num,//base指向的数组中的元素个数(待排序的数组的元素的个数)
	size_t size,//bese指向的数组中元素的大小(单位是字节)
	int(*compar)(const void*, const void*)//函数指针-指针指向的函数是用来比较数组中的2个元素的,
	//比如该函数两个参数是(const void*p1, const void*p2),
	//如果p1大于p2,那函数就返回一个>0的数字,
	//如果p1小于p2,那函数就返回一个<0的数字,
	//如果p1等于p2,那函数就返回0
 
)

注意:使用qsort函数,要加上#include//qsort的头文件

//补充:两个整型元素比较大小直接可以用> < ==比较
//但是两个结构体的数据不能直接用> < ==来比较
//所以qsort函数参数中compar指针就是专门抽离出来实现比较的函数,类似于前面讲过的calc函数中的pf

//qsort使用举例
//1.使用qsort函数排列整型数据
//2.使用qsort函数排列结构体数据

1.测试qsort排序整型数据

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

int cmpr_int(const void* p1,const void* p2)
//p1 p2存的是arr数组中的某两个元素的地址,比较时,要解引用
{
	return *(int*)p1 - *(int*)p2;
	//void*指针不能直接解引用,比如void* p1中的p1不能直接解引用,要先强制转换类型再解引用
	//这里要解引用为数组里面元素的类型,即和数组元素的类型保持一致
	
	// 如果p1大于p2,那函数就返回一个>0的数字,
	// 如果p1小于p2,那函数就返回一个<0的数字,
	// 如果p1等于p2,那函数就返回0

	//那如果想要降序排列怎么办?

	//补充qsort函数里面的逻辑关系
	//void qsort
	//(
	//	void* base,//base指向了要排序的数组的第一个元素
	//	size_t num,//base指向的数组中的元素个数(待排序的数组的元素的个数)
	//	size_t size,//bese指向的数组中元素的大小(单位是字节)
	//	int(*compar)(const void*, const void*)//函数指针-指针指向的函数是用来比较数组中的2个元素的
	//)
	//{
	//	if (compar(x, y) > 0)//实现升序
	//	{
	//		//交换
	//	}
	//}

	//只需要对调位置,让返回值的值的逻辑是相反的就可以了:
	//return *(int*)p2 - *(int*)p1;
	//原先只要p1>p2,就调换位置,反过来就是只要p2>p1就调换位置
}

void test1()
{
	int arr[10] = { 1,4,7,8,5,2,3,6,9,0 };
	int sz = sizeof(arr) / sizeof(int);
	Print_arr(arr, sz);//打印
	qsort(arr, sz,sizeof(int),cmpr_int);//升序
	Print_arr(arr, sz);//打印
}

int main()
{
	//将一组数据排序为升序
	test1();//整型排序
	return 0;
}

2.测试qsort排列结构体数据

//先复习一下结构体:

struct stu //描述一个学生的信息可以用students来做结构体的标签名,struct stu这个东西就像做月饼的模具
{
	
	char name[20];//字符串的长度为20
	int age;
	float score;

}s3 = { "wangwu",33,66.0f }, s4 = { "翠花",18,100.0f };//要有分号,就算不在这里定义结构体变量和初始化结构体成员也要分号结束
//这里的s3和s4(是结构体变量)它们就像是用struct stu模具做出来的两个月饼,变量中成员的初始值相当于是月饼(结构体变量)的馅

//当然定义结构体变量和初始化结构体成员可以不用在这里进行,可以在主函数中进行:
int main()
{
	struct stu s1 = { "zhangsan",20,95.5f};
	struct stu s2 = { "lisi",18,87.5f };
    // 注意:在结构体后面定义结构体变量和初始化结构体成员时,结构体变量属于全局变量;
    //而在主函数中定义结构体变量和初始化结构体成员时,结构体变量属于局部变量

	//注意:一般初始化的时候要严格按照创建结构体中的成员列表中的顺序

    //打印结构体:
	printf("%-12s %-8d %-8f\n", s1.name, s1.age, s1.score);
	printf("%-12s %-8d %-8f\n", s2.name, s2.age, s2.score);
	printf("%-12s %-8d %-8f\n", s3.name, s3.age, s3.score);
	printf("%-12s %-8d %-8f\n", s4.name, s4.age, s4.score);
	//使打印结果对齐时,可以在占位符中间输入对齐的个数,左对齐就负号,右对齐正数(不用输)

	return 0;
}

//怎么比较2个结构体数据?-不能直接使用> < ==来比较
//1.可以按照名字比较
//2.可以按照年龄比较

//创建结构体:
struct stu
{
	char name;
	int age;
};


//通过年龄比较
int cmpr_stu_by_age(const void* p1, const void* p2)
{
	return ((struct stu*)p1)->age - ((struct stu*)p2)->age;
	//*(struct Stu*)p1.age 强制类型转换为结构体指针,p1存放的结构体的地址,解引用后找到结构体,用.号来直接访问这个结构体的某个成员
    //但是结构体指针我们一般不用解引用的方式,我们可以强制类型转换成结构体指针,由于p1存放的就是结构体的地址,我们可以用->号来间接访问结构体的某个成员
}

void test2()
{
	//结构体数组-初始化
	struct stu arr[] = { {"zhangsan",12},{"lisi",34},{"wangwu",23} };
	int sz=sizeof(arr) / sizeof(arr[0]);
	qsort(arr, sz, sizeof(arr[0]), cmpr_stu_by_age);//通过年龄比较
}

//通过名字比较
int cmpr_stu_by_name(const void* p1, const void* p2)
{
	return strcmp(((struct stu*)p1)->name, ((struct stu*)p2)->name);
	//但是名字是字符串,那两个字符串是怎么比较大小的?
	//两个字符串不能直接使用> < ==来比较
	//而是使用库函数strcmp-string compare,使用方法只需要将p1和p2指向的值传给strcmp函数就可以了
	//#include//strcmp的头文件
	
	//这个函数比较字符串时比较的不是字符串的长度,而是比较内容,
	//也就是比较对应位置上的字符的ASCII码值
	//比如:
	//abcdef
	//abq
	//由于c的ASCII码值小于q,所以"abq"大于"abcdef"

	// 使用库函数strcmp比较时
	// 如果p1大于p2,那函数就返回一个>0的数字,
	// 如果p1小于p2,那函数就返回一个<0的数字,
	// 如果p1等于p2,那函数就返回0
}

void test3()
{
	//结构体数组-初始化
	struct stu arr[] = { {"zhangsan",12},{"lisi",34},{"wangwu",23} };
	int sz = sizeof(arr) / sizeof(arr[0]);
	qsort(arr, sz, sizeof(arr[0]), cmpr_stu_by_name);//通过名字比较
}


int main()
{
	//将一组数据排序为升序
	test2();//通过年龄比较
	test3();//通过名字比较
	return 0;
}

//qsort是C语言定义好的库函数(用来排序任意类型的数据),
//作为C语言的使用者,我们只需要遵守语法规则去使用就OK了;
//qsort的底层用的是快速排序
 

预知后事如何,请听下回分解......

你可能感兴趣的:(C语言初阶学习笔记,c语言,学习,笔记,c++)