栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

C语言指针(特别篇)

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

C语言指针(特别篇)

作者:一只大喵咪1201
专栏:《C语言学习》
格言:你只管努力,剩下的交给时间!

C语言指针(特别篇)
  • 回调函数
    • 力库函数qsort
      • void*类型的指针变量
      • 多种类型数据的排序
    • 力模拟实现qsort函数
      • 多种类型数据的排序
  • C语言指针结语

在本喵前面的文章《C语言(上、中、下)》三篇文章中由浅入深详细的讲解了指针的类型以及用法,基于这三篇文章的基础上,特别提出一个概念——回调函数。

回调函数

概念:

  • 回调函数是通过函数指针调用一个函数。把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。

注意

  • 回调函数不是由该函数的实现方直接调用,也就是不通过函数名直接使用。
  • 是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
力库函数qsort

有一个int类型的数组,我们将其实现升序排列。

#include 

void bubble_sort(int arr[], int sz)
{
	int i = 0;
	for (i = 0; i < sz - 1; i++)
	{
		int j = 0;
		int flag = 1;
		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;
				flag = 0;//只要发生交换就将标志位写0
			}
		}
		//判断是否完成交换
		if (flag)
		{
			break;
		}
	}
}
int main()
{
	int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	//冒泡法升序排列
	bubble_sort(arr, sz);
	//打印数组中的元素
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

通过冒泡排序的方法,实现了将原本降序排列的数组重排成升序排列的数组。

在C语言的库函数中,有一个排序函数供我们使用,那就是qsort函数。
我们来看一下它的定义:


这是它的函数声明以及需要引用的头文件。

这是对qsort函数形参的介绍。

  • qsort函数是通过快排的方式实现排序的
void qsort(void* base, size_t num, size_t width, int(__cdecl* compare)(const void* elem1, const void* elem2));
  • 参数分别是
  1. 要排数据的起始地址
  2. 要排数据的个数
  3. 要排数据中每个数据的大小
  4. 排序的方式

排序方式是由用户自己定义的,将比较方式写成一个函数,再将函数的地址作为实参传给qsort函数就可以使用。所以它有一个形参的类型是函数指针变量类型,用于接收比较函数的地址。

int(__cdecl* compare)(const void* elem1, const void* elem2)
  • 该函数指针指向的函数中
  • 俩个形参是const void*类型的指针变量类型
  • 返回类型是int类型,也就是指针变量elem1和emel2指向的俩个数的差值
    如果elem1指向的数大于elem2指向的数,则返回一个大于0的数。
    如果elem1指向的数等于elem2指向的数,则返回一个0.
    如果elem1指向的数小于elem2指向的数,则返回一个小于0的数。
  • 其中elem1指向的是需要排序数据中的前一个数,elem2指向的是需要排序数据中的后一个数。

那么形参中const void*类型的指针变量又是什么意思呢?

void*类型的指针变量

const只是为了起修饰作用,将指针变量修饰成一个常变量,使得 * emel1或者 * emel2无法通过解引用来修改其中的值。

void* 类型的指针是一种无类型的指针

当需要一个指针变量,但又暂时不知道它是什么类型的指针变量的时候,就可以将其写成一个void * 类型的指针。
在知道了需要的指针变量类型后,就可以通过强制类型转换将void * 类型的指针变量转换成我们需要的。

如

(int*)emel1

就可以将void * 类型的指针变量emel1转换成为int * 类型的指针变量。

我们使用qsort函数实现上面的数组的升序排序。

#include 
#include 

int int_cmp(const void* e1, const void* e2)
{
	return (*(int*)e1 - *(int*)e2);
}
int main()
{
	int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	int width = sizeof(arr[0]);
	qsort(arr, sz, width, int_cmp);

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


与我们使用冒泡排序的结果相同。

多种类型数据的排序

有没有想过,为什么用户自己定义的比较函数中形参类型的指针是void * 类型的呢?
qsort库函是的作者在写的时候肯定是不知道用户要比较的数据类型是什么,所以就暂且设置成一个void * 类型的指针变量,当用户在自己写比较函数的时候,将void * 类的指针变量转换成自己需要的即可。

qsort函数是可以排序任意类型的数据的

下面举一些例子:

  1. char类型数据的排序
#include 
#include 

int char_cmp(const void* e1, const void* e2)
{
	return (*((char*)e2) - *((char*)e1));
}
int main()
{
	char arr[] = "abcdef";
	int sz = sizeof(arr) / sizeof(arr[0]) - 1;//字符结束标志''不需要排序
	int width = sizeof(arr[0]);
	qsort(arr, sz, width, char_cmp);
	printf("%sn", arr);
	return 0;
}


将字符串按照ASCII码值的大小降序排列。

  1. 结构体类型数据的排列
#include 
#include 

struct stu
{
	char name[20];
	int age;
};
int struct_cmp_age(const void* e1, const void* e2)
{
	return (((struct stu*)e1)->age - ((struct stu*)e2)->age);//转变后的指针必须用括号括起来
}
int main()
{
	struct stu s[3] = { {"zhangsan",18},{"lisi",25},{"wangwu",21} };
	int sz = sizeof(s) / sizeof(s[0]);
	int width = sizeof(s[0]);
	qsort(s, sz, width, struct_cmp_age);
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%s,%dn", s[i].name, s[i].age);
	}
	return 0;
}


按照年龄从小到大将三个人排序。
将比较方式修改为按照名字比较后

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

程序的其他内容不动。

按照首字母的ASCII码值升序将三个人排序。

力模拟实现qsort函数

我们在上面自己写了一个冒泡排序的程序,接下来我们利用冒泡排序的是思想实现一个qsort库函数功能的函数my_qsort函数。
参照着库函数qsort的函数声明

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

以整型数组升序排列为例:

#include 

//由于不知道传过来的数据类型是什么,所以先写成void*类型的指针变量
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 my_qsort(void* base, int sz, int width, int (*pf)(const void*, const void*))
{
	int i = 0;
	for (i = 0; i < sz - 1; i++)
	{
		int j = 0;
		int flag = 1;
		for (j = 0; j < sz - 1 - i; j++)
		{
			//char*类型的指针变量加上数据宽度就得出每个数据的起始地址
			if (pf((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
			{
				//将俩个数据的起始地址传给交换函数
				Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
				flag = 0;
			}
		}
		if (flag)
		{
			break;
		}
	}
}


int int_cmp(const void* e1, const void* e2)
{
	return (*(int*)e1 - *(int*)e2);
}
int main()
{
	int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	int width = sizeof(arr[0]);
	my_qsort(arr, sz, width, int_cmp);

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

主要的难点在于my_qsort函数的实现。

  • 由于并不知道要排序的数据类型是什么,所以要写成void * 类型
  • 在函数内部,由于void * 类型的指针变量是无法解引用以及使用的,所以将其转化成为最小单位的指针变量char * 类型的。

当知道需要排序的数组是int类型后,在最小单位的基础上加上一个域宽就可以得到下一个数据的首地址。

数组中的元素如上图那样存在内存中。

  • 将数组名当作实参传递个my_qsort函数后,开始进行冒泡排序
  • 当调用比较函数的时候
  • 将数组中俩个int类型数据的首地址,也就是图中(char*)base + j * width和(char*)base + (j + 1) * width)俩个地址传递给比较函数,该函数是通过函数指针调用的。
  • 将比较后的结果进行判断后,如果需要交换,将(char*)base + j * width和(char*)base + (j + 1) * width)俩个地址传递给Swap交换函数。
  • 在Swap函数中,通过域宽width,也就是int数据类型的大小控制着循环的进行。
  • 在循环中将俩组数据成对交换,直到实现两个int类型数据的交换。
  • 直到实现了所有数据的交换。

如此一来我们就实现了qsort功能的函数,并且使用的是冒泡排序的思想,而不是快排的思想。

多种类型数据的排序

像库函数qsort那样,我们自己写的my_qosrt函数也可以实现不同数据类型的实现,在上面写my_qsort函数的过程中已经排列了int类型的数据,接下来我是排序一下别的数据类型。

  1. char类型数据的排序
int char_cmp(const void* e1, const void* e2)
{
	return (*(char*)e2 - *(char*)e1);
}
int main()
{
	char arr[] = "abcdef";
	int sz = sizeof(arr) / sizeof(arr[0]);
	int width = sizeof(arr[0]);
	my_qsort(arr, sz, width, char_cmp);
	printf("%sn", arr);
	return 0;
}


与使用库函数qsort的结果一样。

  1. 结构体类型数据排序
struct stu
{
	char name[20];
	int age;
};
int struct_cmp_age(const void* e1, const void* e2)
{
	return (((struct stu*)e1)->age - ((struct stu*)e2)->age);//转变后的指针必须用括号括起来
}
int main()
{
	struct stu s[3] = { {"zhangsan",18},{"lisi",25},{"wangwu",21} };
	int sz = sizeof(s) / sizeof(s[0]);
	int width = sizeof(s[0]);
	my_qsort(s, sz, width, struct_cmp_age);
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%s,%dn", s[i].name, s[i].age);
	}
	return 0;
}


与库函数qsort的结果一样,也是将三个人按照年龄从小到大排列。
将比较函数修改为按照名字排序后

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


同样与库函数qsort的结果一样,按照名字首字母的ASCII码值从小到大排列。

以上俩种数据类型的排序中,my_qsort函数和Swap函数没有写出来,它们的具体内容参照写my_qsort函数过程即可。

在写my_qsort函数过程中,充分使用了回调函数,主要的应用就是在比较函数上,无论是我们自己模拟的my_qsort还是库函数qsort,在用户自定义好比较函数后,调用该函数都是使用的回调函数的方式。

C语言指针结语

C语言的特点主要在于指针的灵活使用,在通过《C语言指针(上篇、中篇、下篇、特别篇)》四篇文章的介绍,将指针由浅入深,由表即里详细的讲述了一遍,涉及到的各种指针类型以及它们的用法可以应付我们在编程过程中遇到的绝对大多数场景。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/960017.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号