指针的进阶

目录

1. 字符指针

1.1 使用

1.1.1  对字符使用    一般我们对字符指针的使用如下:

1.1.3 对字符数组使用

1.2 面试题

2. 指针数组

2.1 基本使用

2.2 一维数组模拟二维数组

2.3 总结

3. 数组指针

3.1 定义

3.2 数组名&数组名

3.3特殊

3.4 如何使用

3.4.1 举个例子(一维数组)

3.4.2 举个栗子(二维数组)

3.5 小结

4. 数组传参和指针传参

4.1 一维数组传参

4.2 二维数组传参

4.3 一级指针传参

4.4 二级指针传参

5. 函数指针

5.1 引入介绍

5.2     (*(void (*)() )0 )();

5.3 void (*signal(int , void(*)(int)))(int);

6. 函数指针数组——转移表

6.1 介绍

6.2 应用——写一个整数计算器

6.2.1 函数主体

6.2.2 各函数块的编写

6.2.3 各函数详细编写

6.2.4 完善改进

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

8. 回调函数

8.1 定义及介绍

8.2 举个例子

9 qsort函数

9.1 对冒泡排序的回顾

9.2 qsort 详解(可排序任意类型的数据)

9.3 整型排序

9.3.1 整型升序排序

9.3.2 整型降序排序

9.4 结构体排序

9.4.1 按name排序

9.4.2 按年龄排序

9.5 用冒泡排序模拟qsort

9.5.1 模拟整数排序

9.5.2 模拟结构体按name排序

9.5.3 模拟结构体按age排序


这个章节,我们继续探讨指针的高级主题


1. 字符指针

1.1 使用

  1.1.1  对字符使用    一般我们对字符指针的使用如下:

#include <stdio.h>
int mian()
{
	char ch = 'w';
	char* pc = &ch;


	return 0;
}

       

 1.1.2 对字符串使用

         而字符指针可以对字符串进行定义,但是只指向字符串的首元素地址,解引用访问时,只访问首地址的内容,即第一个元素,代码如下:

#include <stdio.h>
int mian()
{
	char* ps = "abcdef";
	printf("%c\n", *ps);
	return 0;
}

         

        此处存在问题,如果用的是2022版本的vs,此时会警告,当我们使用这种方式去初始化指针,我们对ps内存放的字符进行修改,我们发现程序会崩溃,常量字符串不可以修改

int main()
{
	char ch = 'w';

	char* ps = "abcdef";
	*ps = 'w';

	printf("%c\n", *ps);
	return 0;
}

         

        我们可以在前面用const修饰一下,这样就可以啦,如下:

int main()
{
	char ch = 'w';
	const char* ps = "abcdef";
//	*ps = 'w';

	printf("%s\n", ps);
	return 0;
}
        打印的时候,打印字符串%s,给出首地址即可            printf("%s\n", ps);

1.1.3 对字符数组使用

        如果想修改,我们可以定义字符数组:

#include <stdio.h>

int main()
{
	char arr[] = "abcdef";
	char* p = arr;

	return 0;
}

1.2 面试题

#include <stdio.h>
int main()
{
	char str1[] = "hello you.";
	char str2[] = "hello you.";
	const char* str3 = "hello you.";
	const char* str4 = "hello you.";
	if (str1 == str2)
		printf("str1 and str2 are same\n");
	else
		printf("str1 and str2 are not same\n");

	if (str3 == str4)
		printf("str3 and str4 are same\n");
	else
		printf("str3 and str4 are not same\n");

	return 0;
}

        我们来详细解释一下这个题目,const修饰的指针str3\str4是无法改变的,既然存的是一样的并且无法改变的内容,为了优化内存,只开辟同一个空间来存储内容,所指向的是同一个地址;但是str1\str2是两个独立的空间,所指向的地址也是两个独立空间的不同的地址,此题比较的不是内容,比较的是地址,图解如下:

        如果要比较字符串的内容,要用strcmp,此处不过多介绍。

2. 指针数组

2.1 基本使用

       我们初始化一个指针数组:

int main()
{
	char* arr[5] = { "zbcdef","zhangsan","hehe","wangcai","ruhua" };

	return 0;
}

         我们将其打印:

int main()
{
	char* arr[5] = { "zbcdef","zhangsan","hehe","wangcai","ruhua" };
	int i = 0;
	for (i = 0; i < 5; i++)
	{
		printf("%s\n", arr[i]);
	}

	return 0;
}

2.2 一维数组模拟二维数组

int main()
{
	int arr1[5] = { 1,2,3,4,5 };
	int arr2[5] = { 2,3,4,5,6 };
	int arr3[5] = { 3,4,5,6,7 };
	int arr4[5] = { 5,6,7,8,9 };

	int* arr[4] = { arr1,arr2,arr3,arr4 };

	return 0;
}

         将其打印:

//用一维数组模拟二维数组
int main()
{
    int arr1[] = { 1,2,3,4,5};
    int arr2[] = { 2,3,4,5,6};
    int arr3[] = { 3,4,5,6,7};
    int arr4[] = { 4,5,6,7,8};
 
    int* arr[4] = { arr1,arr2,arr3,arr4 };
    int i = 0;
    for (i = 0; i < 4; i++)
    {
        int j = 0;
        for (j = 0; j < 5; j++)
        {
            printf("%d ", arr[i][j]);
    //同数组的引用,不需要解引用,我们拿到的也是数组首地址,再对其操作
    //arr[j]=*(arr+j),可以看成已经解引用过
        }
        printf("\n");
    }
    return 0;
}
 

2.3 总结

int* arr1[10]; //整形指针的数组

char * arr2 [ 4 ]; // 一级字符指针的数组
char ** arr3 [ 5 ]; // 二级字符指针的数组

3. 数组指针

3.1 定义

        指向数组地址的指针


//整型指针——指向整型的指针——存放整型变量的地址
//int* p1;
//字符指针——指向字符的指针——存放字符变量的地址
//char* p2;
//数组指针——指向数组的指针——存放数组的地址


int main()
{
	int a = 10;
	int* p1 = &a;

	char ch = 'w';
	char* p2 = &ch;

	int arr[10] = { 1,2,3,4,5 };
	int (* pa)[10] = &arr;//取出的是数组的地址,存放到pa中,pa是数组指针变量
	//int(*)[10]——数组指针类型

    return 0;
}

        数组指针定义时,(*pa)说明这是指针,然后结合【10】说明是数组,int说明其指向的是整型数组,说明这是指向整型数组的指针。int(*)[10]——数组指针类型

int ( * p )[ 10 ];
// 解释: pa 先和 * 结合,说明 p 是一个指针变量,然后指着指向的是一个大小为 10 个整型的数组。所以 pa 是一个 指针,指向一个数组,叫数组指针。
// 这里要注意: [] 的优先级要高于 * 号的,所以必须加上()来保证 pa 先和 * 结合。

3.2 数组名&数组名

int main()
{
	int arr[10];
	printf("%p\n", arr);

	printf("%p\n", &arr[0]);
	printf("%p\n", &arr);

	return 0;
}

         

        数组的首地址,数组首元素的地址,数组的地址均指向同一个地址,从数值上看是一样的。我们再来看看区别:

int main()
{
	int arr[10];
	printf("%p\n", arr);
	printf("%p\n", arr+1);

	printf("%p\n", &arr[0]);
	printf("%p\n", &arr[0]+1);

	printf("%p\n", &arr);
	printf("%p\n", &arr+1);

	return 0;
}

         

        指针的加减跳过的字节取决于指针的类型

3.3特殊

//数组名是数组首元素的地址

两个例外

1.sizeof(数组名)(此时表示整个数组)

2.&数组名

3.4 如何使用

3.4.1 举个例子(一维数组)

void printf1(int arr[10], int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);

	}
	printf("\n");
}

int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int sz = sizeof(arr) / sizeof(arr[0]);


	printf1(arr, sz);

	return 0;
}

        我们在传首地址时,相当于传*arr,然后我们可以修改为:

void printf1(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,2,3,4,5,6,7,8,9,10 };
	int sz = sizeof(arr) / sizeof(arr[0]);


	printf1(arr, sz);

	return 0;
}

        我们进而修改为数组指针来使用,如下:

void printf1(int(*p)[10], int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", (*p)[i]);//p内存放arr首地址,*p==*&arr==arr
						  //为了取出数组元素,(*p)[i]==arr[i];
	}
	printf("\n");
}

int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int sz = sizeof(arr) / sizeof(arr[0]);

	printf1(&arr, sz);

	return 0;
}

        p内存放arr首地址,*p==*&arr==arr;为了取出数组元素,(*p)[i]==arr[i];

但是一维数组里,我们使用数组指针终究是麻烦的,一般不适合用于一维数组情况。

3.4.2 举个栗子(二维数组)

         我们打印一个二维数组:

数组传参,数组接收:

printf3(int arr[3][5], int r, int c)
{
	int i = 0, j = 0;
		for (i = 0; i < r; i++)
		{
			for (j = 0; j < c; j++)
			{
				printf("%d ", arr[i][j]);
			}
			printf("\n");
		}
}


void test2()
{
	int arr[3][5] = { {1,2,3,4,5},{2,3,4,5,6,},{3,4,5,6,7} };
	printf3(arr,3,5);
}


int main()
{
	test2();

	return 0;
}

数组传参,指针接收(数组指针常用形式)

二维数组的首元素,就是二维数组的第一行;

arr是二维数组的数组名,数组名是数组首元素的地址,arr就是第一行的地址

#include<stdio.h>

void printf4(int(*p)[5],int r,int c )
{
	int i = 0;
	for (i = 0; i < r; i++)
	{
		int j = 0;
		for (j = 0; j < c; j++)
		{
			printf("%d ", * (*(p + i) + j));//p+i指向某一行,解引用找出某一行
						  //对某一行的首地址加j,再解引用找出元素
		}
		printf("\n");
	}
}


void test2()
{
	int arr[3][5] = { {1,2,3,4,5},{2,3,4,5,6,},{3,4,5,6,7} };
	printf4(arr,3,5);
}
int main()
{
	test2();
	return 0;
}

3.5 小结

int arr[5];                整形数组,五个整型元素的数组
int *parr1[10];        指针数组,十个指针元素的数组
int (*parr2)[10];      数组指针,指向数组的指针

 

int (*parr3[10])[5];  数组

        parr3有十个元素,是存放数组指针的数组

4. 数组传参和指针传参

4.1 一维数组传参

#include <stdio.h>
void test(int arr[])//ok?		正确,数组传参,形参可以是数组,也可以是指针
							//	数组传参,传的不是整个数组,不需要创建一个数组,可以不写大小
{}
void test(int arr[10])//ok?		正确,多写了大小,虽然大小没有实际作用
{}
void test(int* arr)//ok?		正确,形参是指针
{}
void test2(int* arr[20])//ok?	正确,形参是数组,同实参,也可以省略数组大小
{}
void test2(int** arr)//ok?		正确,详细见图
{}
int main()
{
	int arr[10] = { 0 };		//整型数组

	int* arr2[20] = { 0 };		//整形指针数组
	test(arr);
	test2(arr2);
}

4.2 二维数组传参

void test(int arr[3][5])//ok?	形参是数组,正确
{}
void test(int arr[][])//ok?	形参是数组,错误,不能均省略
{}
void test(int arr[][5])//ok?	形参是数组,正确,可以只有列数
{}
//总结:二维数组传参,函数形参的设计只能省略第一个[]的数字。
//因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。
//这样才方便运算。
void test(int* arr)//ok?		形参是指针,一维数组
{}							  //指向arr第一行的首地址,操作会产生问题

void test(int* arr[5])//ok?	此处arr与【5】结合,是数组
{}							  //我们想要形参是指针

void test(int(*arr)[5])//ok?	形参指针,每个指针指向一个含五个元素的整型数组
{}
void test(int** arr)//ok?		错误,传出去arr是行地址,一维数组的地址
{}							  //不能用二级指针,二级指针接收一级指针的地址
int main()
{
	int arr[3][5] = { 0 };
	test(arr);
}

4.3 一级指针传参

#include <stdio.h>
void print(int* p, int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d\n", *(p + i));
	}
}
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9 };
	int* p = arr;
	int sz = sizeof(arr) / sizeof(arr[0]);
	//一级指针p,传给函数
	print(p, sz);
	return 0;
}

当一个函数的参数部分为一级指针的时候,函数能接收什么参数(即传什么参数)?

void test(int* p)//参数是一级指针时
{

}

int main()
{
	int a = 0;
	test(&a);//ok	传变量地址

	int* ptr = &a;
	test(ptr);//    传一级指针本身

	int arr[10];
	test(arr);//	传一维数组数组名

}

4.4 二级指针传参

#include <stdio.h>
void test(int** ptr)
{
 printf("num = %d\n", **ptr); 
}
int main()
{
 int n = 10;
 int*p = &n;
 int **pp = &p;
 test(pp);
 test(&p);
 return 0;
}

当函数的参数为二级指针的时候,可以接收什么参数?

void test(int** p)
{

}

int main()
{
	int** ptr = ;
	test(ptr);//ok	传二级指针

	int* p2 = ;
	test(&p2);//	传一级指针地址

	int* arr[10];
	test(arr);//	传指针数组的数组名
}

5. 函数指针

5.1 引入介绍

int Add(int x, int y)
{
	return x + y;
}

int main()
{
	int arr[10] = { 0 };
	printf("%p\n", &arr);
	printf("%p\n", arr);

	printf("%p\n", Add);
	printf("%p\n", &Add);

	return 0;
}

         数组名与取地址数组名的值相同,但是意义不一样,一个是数组首元素的地址,一个是数组的地址

        对于函数名,两个没有区别,函数名与取地址函数名拿到的都是函数地址。

对于函数指针,假定指针pf指向函数Add,声明这是一个指针变量,(*pf),其类型是int函数,且函数的参数是int,我们可以写出  int(*pf)(int x,int y)=&Add;

	//pf就是函数指针变量
	int (*pf)(int x, int y) = &Add;
	//int (*pf)(int x, int y) = Add;或者这样写

        我们定义了一个函数指针,那么我们如何调用它呢?

int Add(int x, int y)
{
	return x + y;
}

int main()
{

	//pf就是函数指针变量
	int (*pf)(int x, int y) = &Add;

	int sum=(*pf)(3, 5);//对pf解引用找到Add函数,使用Add函数
	printf("%d\n", sum);

	return 0;
}

        我们考虑到Add是存放于pf中的,类比int sum=Add(3,5);int sum=(*pf)(3,5);

我们能否去掉前面的解引用符号,直接使用pf代替Add呢?

int Add(int x, int y)
{
	return x + y;
}

int main()
{
	
	//pf就是函数指针变量
	int (*pf)(int x, int y) = &Add;

	int sum=pf(3, 5);
	printf("%d\n", sum);

	return 0;
}

         所以也是可以的,由此可知此处*没有实际作用,也可以多写几个,加*帮助理解,但是语法上没有用。

int Add(int x, int y)
{
	return x + y;
}

int main()
{
	//pf就是函数指针变量
	int (*pf)(int x, int y) = &Add;

	int sum=(*******pf)(3, 5);
	printf("%d\n", sum);

	return 0;
}

5.2     (*(void (*)() )0 )();

        我们来解读一下这个有意思的代码

int main()
{
	(*(void (*)() )0 )();
	
	return 0;
}

        0前面的()内放置void (*)();我们对其补充void (*p)(),这是一个函数指针,且函数的返回类型为void。
        所以0前面的括号内放置了一个类型,而类型放在()里,强制类型转换。
        (void (*)() )0——把0当做一个函数的地址,0强制类型转换成像void (*)()的函数类型的地址,0地址内存放这样的函数。
        前面加*去找到这个地址内存放的这样类型的函数,并调用函数,无参加上()。

        把0直接转换成一个void(*)()的函数指针,然后去调用0的地址处的函数,前面解引用的*同5.1的内容,是可以省略的

5.3 void (*signal(int , void(*)(int)))(int);

int main()
{
	void ( *signal(int, void(*)(int) ) )(int);

	return 0;
}


         上述代码是一个函数声明,声明的函数叫signal,signal函数的第一个参数是int类型,第二个参数是一个函数指针类型,该函数指针指向的函数参数是int,返回类型是void,signal函数的返回类型也是函数指针类型,该函数指针指向的函数参数是int,返回类型是void。

     

          如何简化呢?

	//void ( *signal(int, void(*)(int) ) )(int);
	//函数类型及返回类型都是void(*)(int),我们typedef 一下

	//typedef void(*)(int) pf_t;但是语法要求pf_t应该放置于*旁边
	typedef void(* pf_t)(int);

	pf_t signal(int, pf_t);

6. 函数指针数组——转移表

6.1 介绍

int* p;//整型指针

int* arr【5】;//整型指针数组

函数指针

int(*pf)(int,int);

函数指针数组

int (*pfArr[4])(int,int);

其可以存放多个参数相同,及返回类型相同的函数地址

int Add(int x, int y)
{
	return x + y;

}

int Sub(int x, int y)
{
	return x - y;

}

int main()
{
	int(*pfArr[2])(int, int) = { Add,Sub };

	return 0;
}

我们尝试调用这个函数指针数组

int Add(int x, int y)
{
	return x + y;

}

int Sub(int x, int y)
{
	return x - y;

}

int main()
{
	int(*pfArr[2])(int, int) = { Add,Sub };

	int ret = pfArr[0](2, 3);
	printf("%d\n", ret);

	ret = pfArr[1](2, 3);
	printf("%d\n", ret);

	return 0;
}

6.2 应用——写一个整数计算器

6.2.1 函数主体

        为了能够不断地计算,我们可以利用do while循环,为了使计算器的打印更明确,我们写一个菜单函数,如下:

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);
		switch (input)
		{
		case 1:
			break;
		case 2:
			break;
		case 3:
			break;
		case 4:
			break;
		case 0:
			break;
		default:
			break;
		}
	}while(input)
	return 0;
}

6.2.2 各函数块的编写

一、Add函数的初步编写

        我们如果需要键入操作1,那么我们调用Add函数,此时我们还需键入两个操作数,x,y,操作后的结果,我们用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;
	int x = 0;
	int y = 0;
	int ret = 0;

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

二、 其他函数块的初步编写

        同理,我们可以编写如下的部分

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("请输入两个操作数:>");
		scanf("%d %d", &x, &y);
		switch (input)
		{
		case 1:
			ret=Add(x, y);
			printf("%d\n", ret);
			break;
		case 2:
			ret = Sub(x, y);
			printf("%d\n", ret);
			break;
		case 3:
			ret = Mul(x, y);
			printf("%d\n", ret);
			break;
		case 4:
			ret = Div(x, y);
			printf("%d\n", ret);
			break;
		case 0:
			printf("退出计算器\n");
			break;
		default:
			printf("选择错误\n");
			break;
		}
	}while(input)
	return 0;
}

6.2.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("请输入两个操作数:>");
		scanf("%d %d", &x, &y);

		switch (input)
		{
		case 1:
			ret = Add(x, y);
			printf("%d\n", ret);
			break;
		case 2:
			ret = Sub(x, y);
			printf("%d\n", ret);
			break;
		case 3:
			ret = Mul(x, y);
			printf("%d\n", ret);
			break;
		case 4:
			ret = Div(x, y);
			printf("%d\n", ret);
			break;
		case 0:
			printf("退出计算器\n");
			break;
		default:
			printf("选择错误\n");
			break;
		}
	} while (input);
	return 0;
}

 6.2.4 完善改进

       我们发现其中一个问题,当我们只输入一个操作数的时候,其不报错,我们可以对其进行修改,修改后代码如下:

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

		switch (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);
	return 0;
}

        而且我们的代码日后若想增加更多的功能,其会越写越长。我们需要简化代码。我们可以定义一个函数指针数组——    int (*pfArr[5])(int ,int) = { 0,Add,Sub,Mul,Div };

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;

	int (*pfArr[5])(int ,int) = { 0,Add,Sub,Mul,Div };


	do
	{
		menu();
		printf("请选择:>");
		scanf("%d", &input);
		printf("请输入两个操作数:>");
		scanf("%d %d", &x, &y);

		ret=pfArr[input](x, y);
		printf("%d\n", ret);

	} while (input);
	return 0;
}

        此时代码能够正常运行,但是会有一些问题:

       

          当我们输入0时,其也让我们输入两个操作数,我们对input的内容进行条件判断

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;

	int (*pfArr[5])(int ,int) = { 0,Add,Sub,Mul,Div };


	do
	{
		menu();
		printf("请选择:>");
		scanf("%d", &input);
		if (input == 0)
		{
			printf("退出计算器\n");
			break;
		}
		if (input >= 1 && input <= 4)
		{
			printf("请输入两个操作数:>");
			scanf("%d %d", &x, &y);

			ret = pfArr[input](x, y);
			printf("%d\n", ret);
		}
		else
		{
			printf("选择错误\n");
		}
		
	} while (input);
	return 0;
}

        此时计算器的编写已经比较完善了,也方便后续对计算器功能的增加

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

函数指针

int (*pf)(int ,int)

函数指针数组

int (*pfArr[4])(int ,int)

指向函数指针数组的指针

应该是什么什么=&pfArr;

函数指针数组的指针类型,我们在函数指针数组的类型上进行修改int (*ptr[4])(int ,int)

改名称int (*(*ptr)[4])(int ,int)

其是指针,不是数组,所以可得int (*(*ptr)[4])(int ,int) =&pfArr;

8. 回调函数

8.1 定义及介绍

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

8.2 举个例子

        我们还是用计算器作为我们的例子,我们回到第一次修改之后的计算器代码,如下:

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

		switch (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);
	return 0;
}

         

        我们发现红色框内的代码十分相似,代码显得重复繁冗,我们尝试用calc函数来代替红色框内的代码,但是红色框内的计算方式不同,我们同样的需要去调用计算的函数,我们去调用函数的地址,而函数的地址,应该放入函数的指针,假定为p,参数是int ,int ,返回类型是int ,void calc(int(*p)(int,int)),例如我们取Add地址,即void calc(int(*Add)(int,int)),
我们调用p即调用相对应的传地址的计算函数,ret = p(x, y);,例如Add,即ret=Add(x.y)代码修改如下:

#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 menu()
{
	printf("************************\n");
	printf("**** 1.add     2.sub ***\n");
	printf("*****3.mul     4.div ***\n");
	printf("*****0.exit          ***\n");
	printf("************************\n");
}

void calc(int(*p)(int,int))
{
	int x = 0;
	int y = 0;
	int ret = 0;
	printf("请输入两个操作数:>");
	scanf("%d %d", &x, &y);
	ret = p(x, y);
	printf("%d\n", ret);
}

int main()
{
	int input = 0;
	

	do
	{
		menu();
		printf("请选择:>");
		scanf("%d", &input);
		printf("请输入两个操作数:>");
		scanf("%d %d", &x, &y);

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

         当p去调用Add时,Add就是回调函数;当p去调用Sub时,Sub就是回调函数。

简单的说,如下图

 

9 qsort函数

qsort函数——c语言标准库提供的排序函数,该函数的排序是快速排序

9.1 对冒泡排序的回顾

        图解冒泡排序

void bubble_sort(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 = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = tmp;
			}
		}
	}
}

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

int main()
{
	//冒泡排序
	//对整型数据进行排序
	int arr[] = { 2,1,3,7,5,9,6,8,0,4 };
	int sz = sizeof(arr) / sizeof(arr[0]);

	bubble_sort(arr, sz);
	print(arr, sz);


	return 0;
}

        而bubble_sort冒泡排序的缺陷也非常明显,其只能对整数进行冒泡排序,所以我们来看看qsort函数。

9.2 qsort 详解(可排序任意类型的数据)

        我们检索一下它的用法:

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



void qsort(void* base,
	size_t num,
	size_t size,
	int (*compar)(const void*, const void*)//函数指针//compar比较,*,其是个指针,
									  //指向函数,函数参数是const void* const void*
											  //函数返回类型是int 

使用时要引入头文件#include<stdlib.h>

 base 是指向待排序的数组的第一个对象的指针,该对象将转换为 .void*

num 数组中由指向的元素数。即上文的sz

size 数组中每个元素的大小(以字节为单位)。指向比较两个元素的函数的指针。
compar 此函数被重复调用 by 以比较两个元素。

        对于void* base,由于qsort函数可以排序任何类型,而base是指向待排序数组的第一个对象的指针,由于待排序的数组可以是任意类型,所以我们不能直接对其定义指针类型,例如int*,char*等,这样我们一旦去处理其他类型的时候都会出现问题,由于传递的指针可能是int,char等类型,我们可以用void*来处理,void*可以接收任意指针类型的地址

        但是其解引用的时候,需要先强制类型转换,再解引用,例如        void*p=&i;

*(int* )p=200;也不能直接p++的操作。

        num,我们排序的时候需要知道待排序元素的个数,得清楚自己需要排多少。

        size,我们需要知道待排序元素的大小,因为我们不知道元素类型,所以当我们知道元素的大小,就知道其偏移量,就能够找到下一个元素的地址,找到下一个元素。

        compar比较两个元素的大小的函数指针,会比较才能排序。

比较:

两个整型使用关系运算符比较大小;

两个字符串,使用strcmp比较大小;

两个结构体,也得制定比较方式;

由于比较的方式不同,所以比较的时候需要用不同的方法,用哪个传哪个,所以增加一个compar函数,其需要待比较的两个元素的指针,返回类型是int

9.3 整型排序

9.3.1 整型升序排序

    

    我们来尝试写一下整型升序排序代码,如下:

#include<stdio.h>
#include<stdlib.h>

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

int cmp_int(const void* e1, const void* e2)
{
	//此处明确e1,e2类型,可强制转换类型
	if (*(int*)e1 > *(int*)e2)
		return 1;
	else if (*(int*)e1 < *(int*)e2)
		return -1;
	else
		return 0;
}
//测试qsort函数功能
void test2()
{
	int arr[] = { 2,1,3,7,5,9,6,8,0,4 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	qsort(arr, sz, sizeof(arr[0]), cmp_int);
	print(arr, sz);

}

int main()
{
	test2();
	return 0;
}

        此处比较写得比较繁冗,我们对其cmp_int 部分修改,如下:

#include<stdio.h>
#include<stdlib.h>

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

int cmp_int(const void* e1, const void* e2)
{
	//此处明确e1,e2类型,可强制转换类型
	return (*(int*)e1 - *(int*)e2);
	
}
//测试qsort函数功能
void test2()
{
	int arr[] = { 2,1,3,7,5,9,6,8,0,4 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	qsort(arr, sz, sizeof(arr[0]), cmp_int);
	print(arr, sz);

}

int main()
{
	test2();
	return 0;
}

         

    

9.3.2 整型降序排序

    我们再进一步修改成整型降序排列的代码,如下:

#include<stdio.h>
#include<stdlib.h>

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

int cmp_int(const void* e1, const void* e2)
{
	//此处明确e1,e2类型,可强制转换类型
	return (*(int*)e2 - *(int*)e1);
	
}
//测试qsort函数功能
void test2()
{
	int arr[] = { 2,1,3,7,5,9,6,8,0,4 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	qsort(arr, sz, sizeof(arr[0]), cmp_int);
	print(arr, sz);

}

int main()
{
	test2();
	return 0;
}

9.4 结构体排序

9.4.1 按name排序

        我们再来尝试写一下结构体数据的排序,我们假设一个含有name与年龄的结构体,我们按其名字进行排序:

        

        我们需要用到strcmp函数,其返回值同qsort,可以直接return,不需要if else判断,其使用需要引入#include<string.h>头文件


#include<stdio.h>
#include<string.h>
#include<stdlib.h>


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


int cmp_by_name(const void* e1, const void* e2)
{
	return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);
}


//测试qsort排序结构体数据
void test3()
{
	struct Stu s[] = { {"zhangsan",20},{"lisi",55},{"wangwu",40} };
	//按照名字比较
	int sz = sizeof(s) / sizeof(s[0]);
	qsort(s, sz, sizeof(s[0]), cmp_by_name);
}


int main()
{
	//test2();
	test3();
	return 0;
}

9.4.2 按年龄排序

一、升序

#include<stdio.h>
#include<string.h>
#include<stdlib.h>

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


int cmp_by_age(const void* e1, const void* e2)
{
	return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
}

//测试qsort排序结构体数据
void test3()
{
	struct Stu s[] = { {"zhangsan",20},{"lisi",55},{"wangwu",40} };

	int sz = sizeof(s) / sizeof(s[0]);
	//按年龄
	qsort(s, sz, sizeof(s[0]), cmp_by_age);

}


int main()
{
	//test2();
	test3();
	return 0;
}

二、降序

#include<stdio.h>
#include<string.h>
#include<stdlib.h>

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


int cmp_by_age(const void* e1, const void* e2)
{
	return ((struct Stu*)e2)->age - ((struct Stu*)e1)->age;
}

//测试qsort排序结构体数据
void test3()
{
	struct Stu s[] = { {"zhangsan",20},{"lisi",55},{"wangwu",40} };

	int sz = sizeof(s) / sizeof(s[0]);
	//按年龄
	qsort(s, sz, sizeof(s[0]), cmp_by_age);

}


int main()
{
	//test2();
	test3();
	return 0;
}

9.5 用冒泡排序模拟qsort

9.5.1 模拟整数排序

        我们学习了qsort的逻辑结构,我们能否用此逻辑结构来完善改进我们的冒泡排序呢?

       

        我们接收的可能是任意类型的数据,所以红框部分的代码需要修改,我们考虑到比较的地址指向的类型不确定,所以base参数的类型我们写作void*,其指向待比较数据的地址;

我们还需知道比较的个数sz;

比较的数据的大小width,单位是字节;

不同类型数据的比较方法不同,我们还需一个比较函数,来实现不同类型数据的比较,该cmp函数的参数是const void* e1,const void* e2,其返回类型是int.

         冒泡排序的内部只需修改比较部分,我们比较的两个参数该如何找到,首先我们对base强制类型转换成char类型,然后该指针类型已经转换完成后,我们再对其+-,比如(char*)base+j*width与(char*)base+(j+1)*width进行比较。找到 j 下标的元素,与 j+1 下标的元素进行比较。

        交换部分,如果是再创建一个变量,由于我们不知道内部存储什么类型的变量,我们无法明确创建什么类型的变量,我们考虑数据存储时,小端存储内四个字节,我们将对应字节内部的数据进行交换即可,即两个整型的相互交换,所以我们写一个Swap函数,来交换我们需要交换的数据,同时我们也需要知道数据所占的字节大小,即width。所以我们可以写出

void Swap(char* buf1,char* buf2,int width)
{

}
Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);

         

            图解:

        具体代码如下:

#include<stdio.h>

void Swap(char* buf1,char* buf2,int width)
{
	int i = 0;
	for (i = 0; i < width; i++)
	{
		char tmp = *buf1;
		*buf1 = *buf2;
		*buf2 = tmp;
		buf1++;
		buf2++;
	}
}


void bubble_sort(void* base,int sz,int width,int (*cmp)(const void* e1,const void* e2))
{
	int i = 0;
	//趟数
	for (i = 0; i < sz - 1; i++)
	{
		//一趟冒泡排序的过程
		int j = 0;
		for (j = 0; j < sz - 1 - i; j++)
		{
			if (cmp((char*)base + j * width, (char*)base + (j + 1) * width)>0)
			{
				//交换
				Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);

			}
		}
	}
}

int cmp_int(const void* e1, const void* e2)
{
	//此处明确e1,e2类型,可强制转换类型
	return (*(int*)e1 - *(int*)e2);

}

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

	//冒泡排序
	//对整型数据进行排序
void test4()
{
	int arr[] = { 2,1,3,7,5,9,6,8,0,4 };
	int sz = sizeof(arr) / sizeof(arr[0]);

	bubble_sort(arr, sz, sizeof(arr[0]), cmp_int);
	print(arr, sz);
}

int main()
{

	test4();

	return 0;
}

9.5.2 模拟结构体按name排序

        图解:

#include<stdio.h>


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


void Swap(char* buf1,char* buf2,int width)
{
	int i = 0;
	for (i = 0; i < width; i++)
	{
		char tmp = *buf1;
		*buf1 = *buf2;
		*buf2 = tmp;
		buf1++;
		buf2++;
	}
}


void bubble_sort(void* base,int sz,int width,int (*cmp)(const void* e1,const void* e2))
{
	int i = 0;
	//趟数
	for (i = 0; i < sz - 1; i++)
	{
		//一趟冒泡排序的过程
		int j = 0;
		for (j = 0; j < sz - 1 - i; j++)
		{
			if (cmp((char*)base + j * width, (char*)base + (j + 1) * width)>0)
			{
				//交换
				Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);

			}
		}
	}
}

int cmp_int(const void* e1, const void* e2)
{
	//此处明确e1,e2类型,可强制转换类型
	return (*(int*)e1 - *(int*)e2);

}

int cmp_by_name(const void* e1, const void* e2)
{
	return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);
}


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

void test5()
{
	struct Stu s[] = { {"zhangsan",20},{"lisi",55},{"wangwu",40} };
	//按照名字比较
	int sz = sizeof(s) / sizeof(s[0]);
	bubble_sort(s, sz, sizeof(s[0]), cmp_by_name);
}

int main()
{

	test5();

	return 0;
}

9.5.3 模拟结构体按age排序

#include<stdio.h>


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


void Swap(char* buf1,char* buf2,int width)
{
	int i = 0;
	for (i = 0; i < width; i++)
	{
		char tmp = *buf1;
		*buf1 = *buf2;
		*buf2 = tmp;
		buf1++;
		buf2++;
	}
}


void bubble_sort(void* base,int sz,int width,int (*cmp)(const void* e1,const void* e2))
{
	int i = 0;
	//趟数
	for (i = 0; i < sz - 1; i++)
	{
		//一趟冒泡排序的过程
		int j = 0;
		for (j = 0; j < sz - 1 - i; j++)
		{
			if (cmp((char*)base + j * width, (char*)base + (j + 1) * width)>0)
			{
				//交换
				Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);

			}
		}
	}
}

int cmp_int(const void* e1, const void* e2)
{
	//此处明确e1,e2类型,可强制转换类型
	return (*(int*)e1 - *(int*)e2);

}

int cmp_by_age(const void* e1, const void* e2)
{
	return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
}

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

void test5()
{
	struct Stu s[] = { {"zhangsan",20},{"lisi",55},{"wangwu",40} };

	int sz = sizeof(s) / sizeof(s[0]);
	bubble_sort(s, sz, sizeof(s[0]), cmp_by_age);
}

int main()
{

	test5();

	return 0;
}

  • 19
    点赞
  • 5
    收藏
  • 打赏
    打赏
  • 9
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

komorebi-filpped

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值