C语言进阶指针

目录

一、字符指针

 1.字符指针的一般用法

 1.1字符指针的另一种用法

 二、指针数组

 三、数组指针

1.数组指针的概念 

 2. &数组名和数组名的比较

 3.数组指针的作用实例

1.数组指针的使用

 4.指向二维数组的指针

 四、数组传参和指针传参

1.一维数组的传参

 2.二维数组的传参

 3.一级指针传参

 4.二级指针的传参

 1.二维数组的传参方法

 2.二维数组的二级指针地址传参

 五、函数指针 

1.指向函数的指针

 2.函数指针的使用

 六、函数指针数组

 1.函数指针数组的概念

 2.指针数组的传参和打印

3. 函数指针数组的使用

4.指向函数指针数组的指针

 七、回调函数

1.回调函数的概念

 2.演示qsort函数的使用


一、字符指针

 1.字符指针的一般用法

    char ch = 'w';
	char* pc = &ch; 
	*pc = 'a';
	printf("%c", ch);

 1.1字符指针的另一种用法

char* p = (char*)"abcdef"; //没有(char*),则"abcdef"表示的是一个常量字符串,是不能够放进p里面的
	//const char* p = "abcdef"; //写成这种形式也是可以的
	printf("%c\n", *p);

 p是指针变量,在x86的情况下只能存放4个字节

char* p = "abcdef"; 这句话的意思是 p存放的不是 abcdef 这个字符串,而相当于是 a 这个位置的地址,同理既然指向了a,而且字符串在内存中是连续存放的,也就间接的可以指向字符串的全部地址

在vs2022编译器环境下是需要用 这样的形式 char* p = (char*)"abcdef"; 表示强制类型转换,但是原理都是一样的

 二、指针数组

概念:指针数组是一个存放指针的数组,有:整型指针的数组,一级指针的数组,二级指针的数组

int* arr[10] ;//存放整型指针的数组
char* arr[5] ;//存放字符指针的数组

 举两个指针数组的使用例子:

    int a = 10;
	int b = 20;
	int c = 30;
	int* arr[10] = { &a,&b,&c };
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		printf("%d ", *(arr[i]));
	}

这里面的指针数组arr存放的是整型 a b c 的地址,返回类型是int型

    int arr1[5] = { 1,2,3,4,5 };
	int arr2[5] = { 1,1,3,5,5 };
	int arr3[5] = { 1,2,3,9,5 };

	int* prr[] = {arr1,arr2,arr3};
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		int j = 0;
		for (j = 0; j < 5; j++)
		{
			//printf("%d ", prr[i][j]);
			printf("%d ",*(prr[i]+j));

		}
		printf("\n");
	}

这里面的指针数组prr存放的是数组类型的数据,返回类型是int型

 三、数组指针

1.数组指针的概念 

 数组指针是专门用来存放一个数组的地址的

一个数组指针存放一个地址

 数组指针是数组还是指针?

答:数组指针是一个指针

 下面由一些常见的指针来引入数组指针

整型指针:int * point能够指向整型数据的指针。
浮点型指针:float * pf; 能够指向浮点型数据的指针

 那么数组指针应该是指向数组的指针

数组指针类型是: int (*)[size] ;

位置不能随便换

[size]表示的是 它指向的呢个数组arr,有size个元素,每个元素是int

给它加一则加的是size个元素大小的地址。

 例题:下面哪个代码是数组指针?

int *p1[10];

int (*p2)[10];

 由指针数组和数组指针的概念可以得到:p1是个数组,为指针数组,p2是一个指针,则为数组指针

 又如:   char* arr[20];

               p = &arr;

                //则类型为 char* (*p)[20]; p指向这20个元素,这20个元素全为char*类型

 2. &数组名和数组名的比较

 有一个数组:int arr[5] = {0};

  arr 和 &arr 分别是什么呢?

 我们知道arr是数组名,数组名表示数组的首元素地址/

那么&arr数组名到底是什么?

//例子:
	int arr[5] = { 0 };
	printf("%p\n", arr);
	printf("%p\n", &arr);

 

int arr[5] = { 0 };
printf("%p\n", arr+1);
printf("%p\n", &arr+1);

 从中我们可以看到,arr表示首元素地址,&arr中的arr表示整个数组的地址

那么在什么情况下数字名才能表示整个数组的地址呢

1.sizeof(数组名),这里面的数组名表示的是整个数组的大小,计算的是整个数组的大小

2.&数组名,这里的数组名表示的是整个数组,取出的是整个数组的地址

 3.数组指针的作用实例

1.数组指针的使用

#include<stdio.h>


void print(int (*p)[10], int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", *(*p + i));                     //*p 对p进行解引用,相当于拿到了数组的//全部元素,整个数组
			//                                           而整个数组相当与数组的首元素地址 //arr[0]
	}
}
//方法一打印print2
void print2(int arr[3][5], int c, int r)
{
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		int j = 0;
		for (j = 0; j < 5; j++)
		{
			printf("%d ", arr[i][j]);
		}
		printf("\n");
	}
}

//方法二打印print2
void print2(int(*p)[5],int c,int r)
{
	int i = 0;
		for (i = 0; i < 3; i++)
		{
			int j = 0;
			for (j = 0; j < 5; j++)
			{
				printf("%d ",*(*(p+i)+j));  //(*p+i)表示拿到了第i行的 ,也相当于第i行的数组名,p+i是指向第i行的
										//数组名表示首元素地址 *(p+i),表示第i行第1个元素的地址
					//再一次加j进行解引用则表示第i行第j个元素
					// 
					// 
				//写成printf("%d ", arr[i][j]);的形式也可以,是一摸一样的
				//原理:arr[i][j];
				//    arr[i]   --->    *(arr+i)
				//   --->   *(arr+i)[j];
				// 进一步表示: --->  *(*(arr+i)+j);
			}
			printf("\n");
		}
}
int main()
{
//int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	写一个函数打印arr数组的内容
	//int sz = sizeof(arr) / sizeof(arr[0]);
	//print(&arr, sz);//  <--  传入一个数组地址
	//                 其实有些麻烦 
	//                 而其实数组指针不是这样用的
int arr[3][5] = { {1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7} };
	//写一个函数,打印arr数组

	//方法一:
	//print2(arr, 3, 5);
	//方法二:
	print2(arr, 3, 5);   //二维数组的解释: 每一行代表一个元素 ,所以当二维数组的数组名表示首元素的地址的时候指的是第一行的地址
							//而第一行里面是5个整型的一维数组

	//print2(arr, 3, 5); 若写成&arr的话则表示的是整个二维数组的地址	
    return 0;
}

 4.指向二维数组的指针

&arr;
int (*ptr)[3][5] = &arr;

 四、数组传参和指针传参

1.一维数组的传参

如一个函数 传入数组arr:test(arr);

 则其参数形式有:

    test(arr);      // ---> *形参写成数组形式* 
	1.void test(int  arr[10])
	{}

	2.void test(int  arr[])
	{} // 形参部分的数组大小可以省略

	3.void test(int  arr[100])
	{}  //不建议,但是没错


	  //---> *形参写成指针形式*  (数组名表示首元素地址)

 也可有指针形式的传参

    test(arr)
	void test(int *p)
	{}

int*类型的写成数组形式传参是

test2(arr2); //---> 数组形式 (数组名表示首元素地址)

	void test2(int* arr[20])
	{}
	void test2(int* arr[])
	{}
	void test2(int* arr[200])
	{
	}

 int*类型的写成指针形式传参是

test2(arr2); //---> 指针形式

	void test2(int** p)
	{}

 2.二维数组的传参

 如:

 int arr[3][5] = { 0 };
test(arr);

//形参写成数组的形式:
	test(int arr[3][5])
	{
	}
	test(int arr[][5]) //行可以省略但是列不可以省略  //行可以不写也可以乱写,但是不建议乱写(仅限范围写大写小)
	{
	}
	

	//形参写成指针的形式://二维数组的首元素表示的第一行的地址
	void test(int (*p)[5])// p 是先是指向的第一行, [5] 表示的是p指向的呢一行有几个元素,类型是int类型
//此处的[5]是不能够省略的,没有5就不知道一行有几个元素了

//二级指针使用来存放一级指针的地址的,数组指针是用来存放数组的地址的!
//所以在二维数组当中不能用二级指针来传参

 3.一级指针传参

 如

 int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };

int *p=arr;

如果想要对元素内容进行打印还需要知道数组元素的个数

 int sz = sizeof(arr)/sizeof(arr[0]);

void test(int *ptr,int sz) //一级指针的传参就是用一级指针来接收
            //ptr 里面所存放的地址与p相同,也是指向数组首元素的地址
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{   //方法一:

		/*printf("%d ", *ptr);
		ptr++;*/

		//方法二:
		printf("%d ", *(ptr++));


int main()
{

int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int *p=arr;

	//如果想要对元素内容进行打印还需要知道数组元素的个数
	int sz = sizeof(arr)/sizeof(arr[0]);
	test(p,sz); //p是一级指针
}

 4.二级指针的传参

void test1(char** ppc) //二级指针的传参用二级指针来接收 ,没有什么其他的方法。
{

}
int main()
{
char a = 'w';
	char* pa = &a;
	char** ppa= &pa;
	// ppa 就是一个二级指针
	test1(ppa);

	//思考:
	//当一个函数的参数部分是一个二级指针的时候,函数能接收什么参数?


	//答:可以把 
	char ch = 'a';
	test1(&ch);//不可以
	// &&ch 也不可以 ,因为&是取的变量地址而 &ch 已经是一个常量了,所以不能再用 &&ch 了
 	char* pc = &ch;
	char** ppc = &pc;

	test1(&pc);//可以
	test1(ppc);//可以

	char* arr1[4]; //指针类型的数组
	test1(arr1);//可以

	//但是二维数组就不可以了

	char* arr2[3][5];

	test1(arr2);

	
}

 1.二维数组的传参方法

void test1(int(*p)[5])
	{

	}
	int main()
	{
		int arr3[3][5];
		test1(arr3); //这个是吧第一行的地址传过去了
		return 0;
	}

 2.二维数组的二级指针地址传参

void test2(int(*p)[3][5])
	{

	}
	int main()
	{
		int arr3[3][5];
		test2(&arr3); //这个是吧整个的二维数组地址传过去了 -- 一般不会这样写,这样写太麻烦了 
		return 0;
	}

 五、函数指针 

1.指向函数的指针

 int arr4[10];

 int(*p)[10] = &arr4; // p 是一个数组指针的变量 ,是用来存放数组的地址的

 由数组指针来引入函数指针

 &ADD; //取出函数的地址

printf("%p", &ADD);

printf("%p", ADD); //这两个都叫拿到函数的地址,虽然方法不一样,但是意义一样。

 如何 保存函数地址呢/

int (* pf)(int, int) = ADD; //这就是函数指针变量.

 *pf 说明是个指针 ,那么他指向的是什么呢 ,指向的是() ,里面是 int,int 类型 (ADD函数例子)

返回类型是 int

 呢么现在也可以举一例

照猫画虎的举一例

int test(char* str) {}

....

 int (*pt)(char*) = test;

 2.函数指针的使用

	//如上题
		int ADD(int x, int y)
		{
			return x + y;
		}
		//...
		int (*pf)(int, int) = ADD;
		int ret = (*pf)(2, 3);
		//之前的使用是: int ret = ADD(2,3);

		//但是
		int ret = pf(2, 3); //这个也是可以的 ,加*或者不加*都可以的,这个*其实是可以没有的,写多个*也都一样
		                    // 这里的*是没有实际意义的
		//如果加*的话不能去掉括号
		printf(" %d", ret);

		//int (*p)[10]  ---> int (*)[10] 是数组指针类型  , p 是 类型变量
		//int (*pf)(int, int)  --->    int (*)(int, int)  是函数指针类型 

 对此有两个例题:解释这两个代码的含义

//代码一:
		(*(void(*)())0)();

		//分析:
		// ( *( void(*)() )0 )();  ---> 
		//void(*)() //(函数指针类型) ---> 
		//( void(*)() ) //强制类型转换  --->
		//(类型)
		//( void(*)() ) 0 //对0进行强制类型的转换 // 把0转换成函数指针类型转换 函数类型为void ,参数为空的 --->
		// 此时0被当作函数的地址了
		// *(void(*)())0  //对0进行解引用  --->
		// (*(void(*)())0)();  //调用0地址处的这个函数

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

		//解释:signal是一个函数声明
		//signal(int, void(*)(int))  //一个函数名为signal的一个参数为int ,一个参数为 函数指针类型
		//    signal函数的返回类型为: void(*   )(int);  //void(*)(int)signal(int, void(*)(int)) 可以这样理解,但是这样的写法是不对的
	
		//可以将其简化
		//typedef void(*)(int) pf_t; 给这个函数指针类型重新起名为 pf_t ,但是这样写是错误的,下面的是对的
		typedef void(*pf_t)(int);

		//简化后的结果:
		pf_t signal(int, pf_t);

 六、函数指针数组

 1.函数指针数组的概念

指针数组字符指针数组

char* arr[5];

整型指针数组

int* arr2[4];

 由此来引出函数指针数组

//	//先创建四个函数
	int (*pf1)(int, int) = Add;
	int (*pf2)(int, int) = Sub;
	int (*pf3)(int, int) = Mul;
	int (*pf4)(int, int) = Div;

  //函数指针数组

 int (*pf[4])(int, int) = { Add,Sub,Mul,Div }; //pf 先和[4]结合了,所以他是个数组,类型是 int (*)(int, int)

//数组是一组相同类型的数组集合

 2.指针数组的传参和打印

	//指针数组的打印和传参;
	int i = 0;
	for (i = 0; i < 4; i++)
	{
		int ret=pf[i](8, 2);
		printf("%d\n", ret);
	}

3. 函数指针数组的使用

 设计一个计算器

 方法一:

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   2.Sub******\n");
	printf("*******3.Mul   4.Div******\n");
	printf("*******    0.exit   ******\n");
	printf("**************************\n");


}
int main()
{
	int input = 0;
	int x = 0;
	int y = 0;
	int ret = 0;
	do
	{
		menu();
		printf("请选择:>");
		scanf("%d", &input);
		printf("请输入两个操作数:\n");
		scanf("%d %d", &x, &y);
		switch (input)
		{
		case 1:
			printf("请输入两个操作数:\n");
			scanf("%d %d", &x, &y);
			ret = Add(x, y);
			printf("ret=%d\n", ret);
			break;
		case 2:
			printf("请输入两个操作数:\n");
			scanf("%d %d", &x, &y);
			ret = Sub(x, y);
			printf("ret=%d\n", ret);
			break;
		case 3:
			printf("请输入两个操作数:\n");
			scanf("%d %d", &x, &y);
			ret = Mul(x, y);
			printf("ret=%d\n", ret);
			break;
		case 4:
			printf("请输入两个操作数:\n");
			scanf("%d %d", &x, &y);
			ret = Div(x,y);
			printf("ret=%d\n", ret);
			break;
		case 0:

			printf("退出计算器\n");
			break;
		default:
			printf("选择错误!\n");
			break;
		}
		printf("ret=%d\n", ret);
	} while (input);

 这个代码过于冗余了,如果再增加新的算法的话会更加的冗余造成不必要的重复写入

 代码的改进

方法二:


int main()
{    
    int input = 0;
	int x = 0;
	int y = 0;
	int ret = 0;

	//把这个函数指针称作是 转移表
	int (*pfArr[5])(int, int) = { 0,Add,Sub,Mul,Div }; //为什么要放0,原因是在刚才的的功能里面,选择的1是加法,依次
														//其目的是为了更好的对应下标      
	do
	{
		menu();
		printf("请选择:>");
		scanf("%d", &input);
		
		if (input == 0)
		{
			printf("退出计算器\n");
		}
		else if (input >= 1 && input <= 4)
		{
			printf("请输入两个操作数:\n");
			scanf("%d %d", &x, &y);
			ret = pfArr[input](x, y);
			printf("ret=%d\n", ret);
		}
		else
		{
			printf("选择错误\n");
		}
		
		/*printf("ret=%d\n", ret);*/
	} while (input); 
	//好处
	//*****如果还想再增加其他的运算只需改动 函数指针数组就行了*****

	//局限性,只有函数的参数类型一致时函数才可以使用

	return 0;
}

4.指向函数指针数组的指针

概念:指向函数指针的数组的指针是一个指针
指针指向一个数组,数组的元素都是函数指针。

 如何定义?

//函数指针
	int (*pf)(int, int) = &ADD; 

	//函数指针数组
	int (*pf1[4])(int, int); 
	int (*(*p3)[4])(int ,int) = &pf1; //p3 就是一个指向函数指针数组的指针
	//						数组的地址放到p3的里面,
	int i = 0;
	for (i = 0; i < 4; i++)
	{
		int ret = (*p3)[i](3, 4);
		//如果要将p3写成[]的形式,那么p3只能写成p3[0]
		int ret = p3[0][i](3, 4);

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

 七、回调函数

1.回调函数的概念

解释:回调函数就是一个 通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方 直接调用的,而是在特定的事件或条件发生时由另外一方调用的,对于该事件或条件进行响应。
// //例如:写了一个A函数,而没有直接调用A函数,那么B函数里面必须要有一个指针来接收A函数,通过B函数来调用A函数,此时A函数成为回调函数

 用回调函数对上述计算机代码进行改进

#define  _CRT_SECURE_NO_WARNINGS 1
#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 clc(int(*pf)(int, int))
{
	int ret = 0;
	int x = 0;
	int y = 0;
	printf("请输入两个操作数:\n");
	scanf("%d %d", &x, &y);
	ret = pf(x, y);
	printf("ret = %d\n ", ret);
}
void menu()
{
	printf("**************************\n");
	printf("*******1.Add   2.Sub******\n");
	printf("*******3.Mul   4.Div******\n");
	printf("*******    0.exit   ******\n");
	printf("**************************\n");
}

int main()
{
	int input = 0;
	do
	{
		menu();
		printf("请选择:>");
		scanf("%d", &input);
		/*printf("请输入两个操作数:\n");
		scanf("%d %d", &x, &y);*/
		switch (input)
		{
		case 1:
			clc(Add);
			break;
		case 2:
			clc(Sub);
			break;
		case 3:
			clc(Mul);
			break;
		case 4:
			clc(Div);
			break;
		case 0:

			printf("退出计算器\n");
			break;
		default:
			printf("选择错误!\n");
			break;
		}
		/*printf("ret=%d\n", ret);*/
	} while (input);
}

 2.演示qsort函数的使用

 普通冒泡排序的函数实现

void bubble_sort(int arr[], int sz)
{
	int i = 0;
	for (i = 0; i < sz - 1; i++)  //第i趟
	{
		int j=0;
		for (j = 0; j < sz-1-i; j++) //i的下一趟,会少一项 
		{
			if (arr[j] > arr[j + 1])
			{
				int temp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = temp;
			}
		}
	}
}

用qsort函数实现

 void qsort(void* base,    
            size_t num,    
            size_t width, 
            int (*cmp)(const void* e1, const void* e2) );

//  qsort函数的实现
//类型为:
void qsort(void* base,     //base是目标数组(待排序数据)的起始位置
			size_t num,    //num是目标数组的元素个数
			size_t width,  //一个元素的字节大小
			int (*cmp)(const void* e1, const void* e2)  //函数指针
				//cmp是一个比较函数,e1和e2是待比较两个元素的地址
				//比较函数的要求是,qsort函数使用者,自定义的一个比较函数!
			
				//比较类型的情况:
				//如果  ,排序的是整型数据,用 > <
				// 如果排序的是结构体数据: 可能不方便直接使用><比较了
				// 需要使用者根据使用情况,提供一个函数,实现2个数的比较~	
 
	//待比较  如果 e1 < e2 则返回 <0  ;如果 e1 = e2 则返回 =0 ;如果e1 > e2 则返回  >0
   // void* 是一个无确切类型的指针,是无法直接进行解引用的
   //void* 是可以接受任意类型的地址,但是无法对void* 的变量进行解引用或者增量....其他等等
 );

 qsort函数实现冒泡与模拟实现qsort库函数

函数的解读

void test1()
{ //将上述的冒泡排序定义为test1
	int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
		//排序为升序;
		int sz = sizeof(arr) / sizeof(arr[0]);
		bubble_sort(arr,sz);
		print_arr(arr, sz);
}

//自定义一个函数
int cmp_int(const void* e1,const void* e2)
{
	//比较的实现
	/*if (*(int*)e1 > *(int*)e2)
	{
		return 1;
	}
	else if (*(int*)e1 == *(int*)e2)
	{
		return 0;
	}
	else
	{
		return -1;
	}*/  //但是这种写法过于复杂

	//可以改为:
	return (*(int*)e1 - *(int*)e2);
}
//冒泡排序的改进
void swap(char* buf1, char* buf2, int width)
//如果交换8和7的话
// 8 在内存中存放是: 08 00 00 00   //buf1指向8
//                       ——       // 这四个字节都要交换
// 7 在内存中存放是: 07 00 00 00   //buf2指向7

{
	int i = 0;
	for (i = 0; i < width; i++)
	{
		char temp = *buf1;
		*buf1 = *buf2;
		*buf2 = temp;
		buf1++;
		buf2++;
	}
}

void bubble_sort1(void* base, int num,int width , int (*cmp)(const void* e1, const void* e2))
{
	int i = 0;
	for (i = 0; i < num - 1; i++)  //第i趟
	{
		int j = 0;
		for (j = 0; j < num - 1 - i; j++) //i的下一趟,会少一项 
		{
			//if (arr[j] > arr[j + 1]) //比较
			

			//因为没有arr[]数组了,只有base,所以应该怎么传要比较的数字呢
			
			//用整型来分析
			// 9 8 7 6 5 4 3 2 1 0
			// base 在9的位置
			//则8的位置是在:(char*)base+width

			//则如果要比较8的地址和7的地址的话
			//我们虽然不能直接知道地址,但是我们知道下标j
			//所以 有 8 的地址:(char*)base+j*width
			//所以 有 7 的地址:(char*)base+(j+1)*width
			
			if(cmp((char*)base + j * width, (char*)base + (j + 1) * width)>0) //相邻两个地址的交换
			{
				//交换
				
				swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
			}
		}
	}
}

//void test2()
//{
//	int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
//	//排序为升序;
//	int sz = sizeof(arr) / sizeof(arr[0]);
//	qsort(arr, sz, sizeof(arr[0]),cmp_int);
//	print_arr(arr, sz);
//}
//
//使用qsort排序结构体
struct Stu
{
	char name[20];
	int age;
	double score;
};
int cmp_stu_by_age(const void* e1, const void* e2) //假设按照年龄来排序
{
	return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
}
int cmp_stu_by_name(const void* e1, const void* e2) //假设按照名字来排序
{
	//升序
	return strcmp(((struct Stu*)e1)->name ,((struct Stu*)e2)->name);
	//strcmp是用来比较字符串的,按照字典的顺序来比较的 ,如a b的顺序 a在b前面

	//降序
	//return strcmp(((struct Stu*)e2)->name, ((struct Stu*)e1)->name); 
}
void test3()
{
	struct Stu arr[3] = { {"zhangsan",20,55.5},{"lisi",30,88.0},{"wangwu",25,90.0}};
	int sz = sizeof(arr) / sizeof(arr[0]);
	//qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_age);

	//升序
	qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_name);
	//降序
	//qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_name);
}

void test4()
{
	int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
	//排序为升序;
	int sz = sizeof(arr) / sizeof(arr[0]);
	bubble_sort1(arr, sz, sizeof(arr[0]), cmp_int);
	print_arr(arr, sz);
}
void test5()
{
	struct Stu arr[3] = { {"zhangsan",20,55.5},{"lisi",30,88.0},{"wangwu",25,90.0} };
	int sz = sizeof(arr) / sizeof(arr[0]);
	//qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_age);

	//升序
	bubble_sort1(arr, sz, sizeof(arr[0]), cmp_stu_by_name);
	//降序
	//qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_name);
}
int main()
{
	test4();
	return 0;
}

 思考

qsort函数的作者,能不能想到我们使用qsort排序什么具体类型的数据呢?
答:什么类型的可以,根本不用想
设计成void*才可以接收任意类型的数据
num :要知道元素个数
width: 要知道一个元素的大小
函数指针:实现比较

为什么要设计成void const*呢?
首先我们并不知道base要放什么样类型的数据,我们只知道这个数据从哪开始
所以我们只进行未知类型的比较,void*也不知道什么类型,const是只让进行比较而不做修改

基于SSM框架的智能家政保洁预约系统,是一个旨在提高家政保洁服务预约效率和管理水平的平台。该系统通过集成现代信息技术,为家政公司、家政服务人员和消费者提供了一个便捷的在线预约和管理系统。 系统的主要功能包括: 1. **用户管理**:允许消费者注册、登录,并管理他们的个人资料和预约历史。 2. **家政人员管理**:家政服务人员可以注册并更新自己的个人信息、服务类别和服务时间。 3. **服务预约**:消费者可以浏览不同的家政服务选项,选择合适的服务人员,并在线预约服务。 4. **订单管理**:系统支持订单的创建、跟踪和管理,包括订单的确认、完成和评价。 5. **评价系统**:消费者可以在家政服务完成后对服务进行评价,帮助提高服务质量和透明度。 6. **后台管理**:管理员可以管理用户、家政人员信息、服务类别、预约订单以及处理用户反馈。 系统采用Java语言开发,使用MySQL数据库进行数据存储,通过B/S架构实现用户与服务的在线交互。系统设计考虑了不同用户角色的需求,包括管理员、家政服务人员和普通用户,每个角色都有相应的权限和功能。此外,系统还采用了软件组件化、精化体系结构、分离逻辑和数据等方法,以便于未来的系统升级和维护。 智能家政保洁预约系统通过提供一个集中的平台,不仅方便了消费者的预约和管理,也为家政服务人员提供了一个展示和推广自己服务的机会。同时,系统的后台管理功能为家政公司提供了强大的数据支持和决策辅助,有助于提高服务质量和管理效率。该系统的设计与实现,标志着家政保洁服务向现代化和网络化的转型,为管理决策和控制提供保障,是行业发展中的重要里程碑。
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值