0基础了解指针的进阶以及回调函数的使用(重点是:用冒泡排序模拟qsort库函数)

目录

1.字符指针

2.指针数组

3.数组指针

数组指针的使用

4. 数组参数、指针参数

4.1 一维数组传参

 4.2二维数组传参

​编辑

 4.3 一级指针传参

 4.4 二级指针传参

 5. 函数指针

 6.函数指针数组

函数指针的用途

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

8. 回调函数

(1)qsort库函数的使用

(2)用冒泡排序的底层逻辑模拟qsort函数的使用

9.总结


1.字符指针

字符指针  char*来表示0基础了解指针的进阶以及回调函数的使用(重点是:用冒泡排序模拟qsort库函数)_第1张图片

 还有另一种存放字符串

0基础了解指针的进阶以及回调函数的使用(重点是:用冒泡排序模拟qsort库函数)_第2张图片

 存放字符串并不是把真的字符串放到char*指针里而是将首元素地址放到char*中

0基础了解指针的进阶以及回调函数的使用(重点是:用冒泡排序模拟qsort库函数)_第3张图片

这里str3和str4指向的是一个同一个常量字符串。C/C++会把常量字符串存储到单独的一个内存区域,当几个指针。指向同一个字符串的时候,他们实际会指向同一块内存。但是用相同的常量字符串去初始化不同的数组的时候就会开辟出不同的内存块。所以str1和str2不同,str3和str4不同。 

2.指针数组

指针数组是一个存放指针的数组。 int *存放整形指针的数组     char*存放字符指针的数组

0基础了解指针的进阶以及回调函数的使用(重点是:用冒泡排序模拟qsort库函数)_第4张图片

3.数组指针

数组指针:指的是指向数组的指针,其本质还是指针。

0基础了解指针的进阶以及回调函数的使用(重点是:用冒泡排序模拟qsort库函数)_第5张图片

 第一个int *p1[10];[]优先级比较高,p1先跟[]结合,为一个数组,其数组的类型是int *,是一个指针数组。

第二个有括号p2先跟*结合是一个指针变量,指向一个数组,叫做数组指针。

数组指针的使用

void print_arr(int(*arr)[5], int row, int col)
{
	int i = 0;
	for (i = 0; i < row; i++)
	{
		int j = 0;
		for (j = 0; j < col; j++)
		{
			printf("%d ", arr[i][j]);
		}
		printf("\n");
	}
}
int main()
{
	int arr[3][5] = { 1,2,3,4,5,6,7,8,9,10 };
	print_arr(arr, 3, 5);
	return 0;
}

以我们的知识可以知道数组名表示数组的首元素地址而二维数组的首元素地址就是一维数组,而该类型就是为数组指针,在打印时就解引用,也可以另一种写法。

void print_arr(int(*p)[5], int row, int col)
{
	int i = 0;
	for (i = 0; i < row; i++)
	{
		int j = 0;
		for (j = 0; j < col; j++)
		{
			printf("%d ", *( * (p + i) + j));
		}
		printf("\n");
	}
}
int main()
{
	int arr[3][5] = { 1,2,3,4,5,6,7,8,9,10 };
	print_arr(arr, 3, 5);
	return 0;
}

可以分析下列代码的意义

  1. int arr[5];  整型数组有五个元素
  2. int *parr1[10]; 是一个指针数组,10个元素的数组,类型为整型指针。
  3. int (*parr2)[10]; 是一个数组指针,该指针指向10个元素的数组,数组的类型时int型。
  4. int (*parr3[10])[5];  想要理解这个可以先把数组名取出来取出parr3[10], 剩余的就是该类型int(*)[5]数组指针,所以就表示数组parr3有十个元素,每个元素的类型是一个数组指针。

4. 数组参数、指针参数

4.1 一维数组传参

数组传参的两种形式

0基础了解指针的进阶以及回调函数的使用(重点是:用冒泡排序模拟qsort库函数)_第6张图片

 4.2二维数组传参

二维数组的两种传参形式

0基础了解指针的进阶以及回调函数的使用(重点是:用冒泡排序模拟qsort库函数)_第7张图片

 4.3 一级指针传参

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]);
	print(p, sz);
	return 0;
}

int *p=arr,指针变量p表示数组的首元素地址。传参传指针。比较简单,就不多赘述了。

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

int *p    可以接收整型数组,整型变量,以及该地址

0基础了解指针的进阶以及回调函数的使用(重点是:用冒泡排序模拟qsort库函数)_第8张图片

 4.4 二级指针传参

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

二级指针传参就用二级指针接受显而易见。

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

  二级指针类型的数,以及以及一级指针类型的数组

0基础了解指针的进阶以及回调函数的使用(重点是:用冒泡排序模拟qsort库函数)_第9张图片

 5. 函数指针

指向函数的指针,存放函数地址的指针

#include 
void test()
{
	printf("hehe\n");
}
int main()
{
	printf("%p\n", test);
	printf("%p\n", &test);
	return 0;
}

test,&test都可以存放函数的地址

0基础了解指针的进阶以及回调函数的使用(重点是:用冒泡排序模拟qsort库函数)_第10张图片

 这就是函数指针的基本定义

再看两个代码表示其含义

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

第一个代码:看着无法下手可以先从0下手,0是一个整数int类型发现前面括号里为void(*)()是函数指针类型,可以发现是把0当做一个地址强制类型转换为函数指针类型然后解引用,调用该函数,该函数无参。

第二个代码:先从signal下手signal(int ,void(*)(int))是一个函数,有两个参数一个参数是int,另一个参数是函数指针类型,将其取出剩余void(*)(int)发现是函数指针类型,是该函数的类型。理解了这个函数,可以这样解释

signal函数两个参数,一个参数是整型类型,另一个参数是函数指针类型,指向函数参数是int,返回类型是void,并且其函数的返回值也是函数指针类型,指向函数的参数是int类型,返回值是void空类型。

有一个东西可以让这个函数可以更好的理解有一个函数typedef叫做重命名函数。

 6.函数指针数组

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

int (*parr[10])();

parr先于[]结合成为一个数组,并且他的类型是int(*)()函数指针

函数指针数组,每个返回值相同,以及参数类型相同的函数可以放在一个函数指针数组。

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 a = 10;
	int b = 20;
	int(*p[4])(int, int) = { add,sub,mul,div };
	int i = 0;
	for (i = 0; i < 4; i++)
	{
		printf("%d\n", p[i](a, b));
	}
	return 0;
}

函数指针的用途

用于转移表

//正常代码写计算器,发现太复杂
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("**************************");
	printf("****1.add     2.sub    ***");
	printf("****3.mul     4.div    ***");
	printf("****0.exit             ***");
	printf("**************************");
}
int main()
{
	int ret = 0;
	int x = 0;
	int y = 0;
	int input = 0;
	do
	{
		menu();
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			printf("输入操作数:");
			scanf("%d %d", &x, &y);
			ret = add(x, y);
			printf("ret = %d\n", ret);
			break;
		case 2:
			printf("输入操作数:");
			scanf("%d %d", &x, &y);
			ret = sub(x, y);
			printf("ret = %d\n", ret);
			break;
		case 3:
			printf("输入操作数:");
			scanf("%d %d", &x, &y);
			ret = mul(x, y);
			printf("ret = %d\n", ret);
			break;
		case 4:
			printf("输入操作数:");
			scanf("%d %d", &x, &y);
			ret = div(x, y);
			printf("ret = %d\n", ret);
			break;
		case 0:
			printf("退出程序\n");
			break;
		default:
			printf("输入错误\n");
			break;
		}
	} while (input);
	return 0;
}

在每一个switch case语句都重复输入输出,这是用函数指针数组就会变得非常简单

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 (*ptarr[5])(int, int) = { 0,add,sub,mul,div };//函数指针数组-------转移表,跟菜单保持一致,第一个元素初始化为0;
	do
	{
		menu();
		printf("请输入");
		scanf("%d", &input);
		if (input == 0)
		{
			printf("退出程序\n");
			break;
		}
		if (input >= 1 && input <= 4)
		{
			printf("请输入两个操作数\n");
			scanf("%d %d", &x, &y);
			ret = ptarr[input](x, y);
			printf("%d\n", ret);
		}
		else
		{
			printf("输入错误,请重新输入\n");
		}
	}while (input);
	return 0;
}

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

本质是指针,指针指向函数指针数组数组的内容是函数指针。

int (*(*ptarr)[5])(int, int );

ptarr是一个数组指针,指针指向的数组有5个元素,每个元素的类型是函数指针int(*)(int,int)

8. 回调函数

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

(1)qsort库函数的使用

例子:了解qsort函数的定义,用冒泡排序的思想写一个排序函数,可以排序任何类型。

 可以发现库函数qsort可以排序任何类型的数据

可以在cplusplus.com网站上查看qorst库函数的基本信息( https://legacy.cplusplus.com/reference/cstdlib/rand/?kw=rand)

0基础了解指针的进阶以及回调函数的使用(重点是:用冒泡排序模拟qsort库函数)_第11张图片

//qsort函数的参数介绍
void qsort (void* base,//待排序数据的首元素的地址
            size_t num, //待排序数据的元素个数
            size_t size,//待排序数据的每个元素的大小
            int (*compar)(const void*,const void*)//比较两个元素大小的函数指针
);

用qsort排序整型数组

//升序
int cmp_int(const void* p1, const void* p2)
{
	return *(int*)p1 - *(int*)p2;

}
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,0 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	//bobble_sort(arr, sz);
	qsort(arr, sz, sizeof(arr[0]), cmp_int);
	for (int i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}
//降序
int cmp_int(const void* p1, const void* p2)
{
	return *(int*)p2 - *(int*)p1;

}
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,0 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	//bobble_sort(arr, sz);
	qsort(arr, sz, sizeof(arr[0]), cmp_int);
	for (int i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

用qsort比较结构体数据

struct stu
{
	char name[20];
	int age;
};
int cmp_stu_by_name(const void* e1, const void* e2)
{
	return strcmp(((struct stu*)e1)->name, ((struct stu*)e2)->name);
}
int main()
{
	struct stu arr[3] = { {"zhangsan",18} ,{"lisi",17},{"wangwu",19} };
	//按照名字比较
	int sz = sizeof(arr) / sizeof(arr[0]);
	qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_name);
	return 0;
}
struct stu
{
	char name[20];
	int age;
};
int cmp_stu_by_age(const void* e1, const void* e2)
{
	return ((struct stu*)e1)->age-((struct stu*)e2)->age;
}
int main()
{
	struct stu arr[3] = { {"zhangsan",18} ,{"lisi",17},{"wangwu",19} };
	//按照年龄比较
	int sz = sizeof(arr) / sizeof(arr[0]);
	qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_age);
	return 0;
}

(2)用冒泡排序的底层逻辑模拟qsort函数的使用

现在理解了qsort函数的使用,但是要用冒泡排序的思想实现一个任意数据都能排序的函数

原本冒泡排序只能排列整型数据(如下方代码)

//bubble_sort 函数只能排序整型数据
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);
}

冒泡排序内部函数(排序整型)

void swap(char* buf1, char* buf2, int width)
{
	for (int 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*, const void*))
{
	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);
			}
		}
	}
}
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)
{
	return *(int*)e1 - *(int*)e2;
}
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,sizeof(arr[0]),cmp_int);
	print(arr, sz);
}

排序结构体

void swap(char* buf1, char* buf2, int width)
{
	for (int 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*, const void*))
{
	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);
			}
		}
	}
}
struct stu
	{
		char name[20];
		int age;
	};
int cmp_struct_by_name(const void* e1, const void* e2)
{
	return  strcmp(((struct stu*)e1)->name, ((struct stu*)e2)->name);
}
int main()
{
	struct stu arr[3] = { {"zhangsan",18} ,{"lisi",17},{"wangwu",19} };
	int sz = sizeof(arr) / sizeof(arr[0]);
	bubble_sort(arr, sz, sizeof(arr[0]), cmp_struct_by_name);
	return 0;
}

9.总结

最难的就是qsort函数的使用,和用冒泡排序的底层逻辑模拟qsort函数,其实还是理解逻辑,不能死记硬背,理解qsort()库函数的参数。

注意:可能有点难以理解,但是多看几遍,熟悉代码逻辑,就会迎刃而解。

让我们共同进步,加油!!!!如果觉得还不错记得一键三连

你可能感兴趣的:(c的进阶,数据结构,开发语言,c语言)