细谈C语言指针第四篇目——特殊的指针与回调函数

今天想给大家介绍在C语言指针里面一组容易被混淆的概念,和一组理解难度稍微有点大的概念,同时也会给大家补充一个叫const修饰指针的知识点,最后这也是我们这个系列给大家带来的最后一篇博客了。

今天要介绍的这两组概念呢,它对于我们的初学者来说可能比较陌生,但是如果你希望在C语言学习的过程走得更远的话,请务必对它们有所了解。它们分别是:

  • 指针数组数组指针的概念;
  • 函数指针回调函数的概念;

现在,就请跟着我们的步伐一起来揭露它们神秘的面纱吧!

目录

一、指针数组和数组指针:

(1)认识指针数组:

(2)指针数组的应用:

(3)认识数组指针:

(4)对数组指针定义的理解:

二、函数指针和回调函数:

(1)认识函数指针:

(2)函数指针在回调函数中的应用:

(3)用冒泡排序来模拟实现qsort函数:


一、指针数组和数组指针:

你别看这两个的名字几乎没有区别的一组名词,在C语言里面,这却是两个截然不同的概念:其中“指针数组”它所描述的是一个数组,而“数组指针”它所描述的是一个指针。根据我们第三篇目的阐述,大家想必也已经清楚知道:指针和数组,这是两个不可以等同而论的概念。

接下来,我们将就指针数组和数组指针分别给大家进行介绍:

(1)认识指针数组:

前面我们学习的int数组,它是用来存放int数据类型的数组,double数组是用来存放double数据类型的数组,字符数组是用来保存字符数据类型的数组。那以此类推,我们不难得知指针数组也是一个数组,是用来保存指针类型的数组。

我们知道从不同的一个基本数据类型出发,可以引申出不同的指针类型。同样的,如果用数组把它们存储起来。一定也会有各种各样不同的指针数组。我们接下来将以char类型,int类型,double类型这三种基本类型为例,写出其所对应的指针数组的定义格式。如下图所示:

//arr1是一个数组,这个数组有十个元素,每个元素是char*类型的指针。
char* arr1[10];

//arr2是一个数组,这个数组有十个元素,每个元素是int*类型的指针。
int* arr2[10];

//arr3是一个数组,这个数组有十个元素,每个元素是double*类型的指针。
double* arr3[10];

arr1,arr2,arr3都是指针数组,这是因为它们的元素类型都是指针类型。显然从上面的例子我们不难总结出指针数组定义的基本格式:

指针类型 数组名[数组长度];

注:这里的指针类型就是我们常说的char*,int*,float*,double*等等。

(2)指针数组的应用:

我们学习了指针数组之后可以用来干什么呢?诶,我们可以一起来看一下以下两个应用场景:

场景一:使用指针数组模拟“伪二维数组”:

#include<stdio.h>

int main()
{
	int arr1[] = { 1,2,3,4,5 };
	int arr2[] = { 2,3,4,5,6 };
	int arr3[] = { 3,4,5,6,7 };
	int* arr[] = { arr1,arr2,arr3 };
	for (int i = 0; i < 3; i++)
	{
		for (int j = 0; j < 5; j++)
		{
			printf("%d ", arr[i][j]);
		}
		printf("\n");
	}
	return 0;
}

这个程序如果你去运行的话,你将会得到下面这样的运行结果:

你会发现这不就是一个二维数组吗,诶,我想说这还真不是,只是产生了形如二维数组的效果。为什么呢?因为它和我们一般定义的二维数组至少存在以下两个方面的不同:

  • 二维数组的每一列有多少个元素是固定死的,但是通过这种方式写出来的形如二维数组的形式的,它的每一列元素到底有多少个那是很随意的。换句话说,只要是一个一维数组的数组名都可以作为int*类型数组的数组元素。而和这个一维数组具体有多少个元素无关。
  • 第二点,我们在第三篇目就给大家验证了二维数组的元素在计算机中是连续存储的。但是通过这种方式定义出来的形如二维数组形式的数组。它的元素只在其对应的一维数组里面连续。整体的数据是不连续的。但是值得注意的是arr1,arr2,arr3之间是相互连续的。

这也是为什么我们说这样模拟出来的二维数组仅仅是一个“伪二维数组”的原因。

场景二:使用指针数组模拟“字符串数组”:

对于C++和Java这样的面向对象的编程语言,它们都有一个string类型的,专门用于保存字符串的数据类型。然后用一个string的数组很方便地就可以保存一系列字符串。那对于C语言呢,它要实现一个string的数组,它应该怎么做。

学了指针数组之后,你完全可以用下面这种形式的代码,在C语言里面来实现我们的string数组:

#include<stdio.h>

int main()
{
	char* str[] = { "hello world","welcome to China","nobody's perfect" };
	for (int i = 0; i < 3; i++)
	{
		printf("%s\n", str[i]);
	}
	return 0;
}

运行截图:

你会发现通过我们的指针数组确实模拟出了我们当前的这么一个效果。

(3)认识数组指针:

首先我们先来考虑一个问题:即关于如何得到一整个数组的地址的问题。

在细谈C语言第三篇目:我们已经有给大家提及一个概念说&arr,这里的arr作为数组名表示的是整个数组。并且我们当时进行的测试表明:&arr+1跳过了40个字节(由数组长度决定)的内存空间,我们知道指针类型决定了一个指针加一减一,它到底跳过了多少个字节,我们到目前为止给大家介绍的指针类型里面,最大的double*类型的一次加一也只能跳过8个字节……

我们急需要一个新的指针类型的出现,来保存我们当前&arr的结果。于是数组指针也就出现了!

和我们以前学习过的指针类型一样,int*类型的指针保存int数据类型的地址,double*类型的指针保存double数据类型的地址,二级指针如int**它用来保存一级指针变量int*的地址。以此类推,那所谓的数组指针也是一个指针,是用来保存一个数组地址的指针。

通过&arr拿到了整个数组的地址之后,我要保存起来,于是这个时候我们就要用到数组指针了。以int类型的数组为例,给大家进行一个说明,如下图所示:

int arr[10];
int (*p)[10] = &arr;

这组代码里面的指针p就是一个数组指针。你会发现它和我们的指针数组在写法上只有一个区别,也就是符号“*”有没有和p结合在一起。还有要特别注意的一点是:这里的[]里面的关于数组长度的描述是不能缺少的存在,你缺少了这个,那么这将是一个不完整的数据类型。

也就是下面这两种都是在定义指针数组时,我们小伙伴可能无意之中会犯的错误:

错误一:

int (*p)[ ] = &arr;

分析:忘记在[]里面写上数组长度。

错误二:

int *(p)[10] = &arr;

分析:把*写在了p的外面,或干脆根本就没带上()。即*p没有结合在一起。这个时候的p不是数组指针,而是一个指针数组,这个数组每个元素是int*,且数组有十个元素。

(4)对数组指针定义的理解:

那么怎么去理解我们这里的数组指针的定义呢,那假设就以上面的int (*p)[10]为例给大家介绍一下吧;

我们去分析一个数组指针的时候,我们不妨先把定义划分成下面三个部分:

int (*p)[10]

分析:

  1. 我们先来看到红色的(*p)部分,它告诉我们的用户说p变量是一个指针;
  2. 然后我们来看到紫色的[10]部分,它告诉我们的用户说p指针,它所指向的对象是一个数组,这个被指向的数组中有十个元素;
  3. 最后的int部分则表明,这个p指针所指向的数组每个元素都是int类型的数据。

上面的描述还是太过啰嗦了,我们总结成一句话就是说:变量p是一个数组指针,指向的数组有10个元素,每个元素都是int类型的数据。

那想必通过上面这个例子的学习,我们对于数组指针的定义为什么是这样的有了一定的了解了。那现在小明同学这里有一个问题,小明定义了一个数组指针:char* str[5],然后他想用一个指针变量把&str1的结果保存,请问你会怎么定义这个数组指针呢?

大家不妨先思考一下,我们在博客的最后再给大家揭晓这一题的答案。

二、函数指针和回调函数:

(1)认识函数指针:

函数指针顾名思义,就是保存函数地址的指针。看到这个,可能已经有小伙伴很惊讶表示说:函数也有地址的吗?,我想说,其实函数也是有地址的。那在继续讨论我们接下来的话题之前,我们先来了解一下函数地址的概念。

函数地址:一个函数的地址就是函数名本身,同时&函数名也表示函数的地址。

我们通过下面这个程序来简单验证上述的结论:

那函数地址我就搞懂了,那就是函数名亦或是&函数名。那函数指针的出现就是为了保存我们这里的函数名或者&函数名,也就是我们函数的地址。

那函数指针定义的基本格式是怎样的呢,那我们就以上面的函数Add函数为例,来给大家介绍一下函数指针定义的基本格式。如下图所示:

int (*pf1)(int, int) = Add;

分析:

  1. (*pf)告诉我们的用户pf是一个指针;
  2. (*pf)后面紧跟一个小括号,表明这个指针指向的是一个函数,小括号里头仅保留了函数各个参数的类型声明,告诉我们用户说指针所指向的这个函数各个参数的类型具体是什么?(这里的参数名你不想省略也可以,也是正确的定义方式)
  3. 最后在(*pf)最前面的int是说,我这个指针所指向的函数最后的返回值是int类型;

总结:pf是一个指向函数的指针,指向的函数有两个int类型的参数,且它的返回值是int类型。

有前面对于数组指针分析的铺垫,我想大家对于上面的分析理解起来也问题不大。只不过现在有一些小伙伴的问题可能又来了:那你曾经说过,我们的指针类型决定了一个指针加一或减一操作可以跳过多少个字节,那一个函数指针加一或者减一会跳过多少个字节的空间呢?有小伙伴可能想着想着就回去测试了一下,诶,你会发现我们的函数指针是不能进行加一减一操作的!如图所示下面的这种写法是错误的:

C语言规定一个函数是不能被sizeof计算大小的,所以也就无法进行这里的加一或者减一操作。那同样的,从理论上而言,也是不能进行解引用操作的,但是实际上呢编译器是不对这一部分进行检查的。也就是说即便有一天你无意之中对一个函数指针使用了我们的解引用操作符“*”,那编译器也是不进行解引用操作的,这没有任何问题。我想这也是出于对程序员在写代码时便捷性的考虑。

因此现在我们的指针变量pf里面保存的就是函数Add的地址了。那我们怎么来使用我们的函数指针呢。那我们不妨在这里和我们的函数调用联系起来,从我们现在的角度来看待C语言里面的函数调用,它的基本格式应该是这样的:

函数名(相关参数)  <==>    函数地址(相关参数)

那既然函数在调用的时候是我们的函数地址+函数调用操作符,那pf里面放的是函数Add的地址啊,那以后我想调用我们的Add函数的时候,我可不可以通过pf来进行一个函数的调用呢?答案是:可以的!如下图所示:

在这个程序里面一方面告诉我们我们可以用一个保存这个函数地址的函数指针来调用这个函数。另一方表明对函数指针的解引用操作是徒劳的,是没有意义的。编译器不会给你执行解引用操作,但是也不会给你报错。

(2)函数指针在回调函数中的应用:

但是函数指针的应用就止步于此了吗?如果仅仅是为了保存函数的地址,或者说只是为了给我们的函数调用时起个新的别名,我想也大可不必。也确实是这样的。函数指针大展身手的实际上是在回调函数的领域。

那什么是回调函数呢?

对于回调函数我们是这么来解释的说:它依赖于函数指针,只有了解了函数指针才能实现我们这里的回调函数。更加详细一点来说:

当你学了函数指针之后,你将有办法将一个指向A函数的函数指针作为另一个函数B的参数传递过去,并有办法在函数B里面通过指向A的函数指针去调用函数A。这种通过函数指针去调用另一个函数的过程我们称之为回调函数。

嗯……概念就是这样的,那实际学习和工作中我们是怎么去应用这里的回调函数来便捷管理我们的开发的呢,那我们在这里不妨给大家介绍两种场景,帮助大家加深对这方面知识的理解。

场景一:设计一个计算器程序的例子!

假设有一天小明的团队在设计一款计算器程序,这个计算器程序可以实现任意两个数之间的加减乘除。现在小明的团队有A,B,C,D加上小明恰好五个程序员。那他们就分工说:A程序员写加法,B程序员写减法,C程序员写乘法,D程序员写我们的除法,最后每个程序员完成了各个功能的开发之后,交给了我们的小明程序员做最后功能上的整合。

我们的小明程序员简单看了A,B,C,D四个程序员设计的程序框架后,发现了虽然这四个程序员各自在功能实现的细节上有所不同,但是他们在函数的参数和返回值部分都是统一的:都用两个int的数据类型做形参,都是以double作为他们各个函数的返回类型。那小明当机立断,当即决定用一个函数就实现了我们整个计算器函数的包装,那这个函数是被如何定义的呢:

是这样的:void Calculator(int a, int b, double (*pf)(int, int));

以后用户在使用计算器程序的时候,你要用加法亦或是减法,你需要哪个功能,你就把对应的实现了我们这个功能的函数名作为Calculator的第三个参数传过来就可以了,然后我再在Calculator里面通过函数指针去调用我们这里的函数就可以了。这样就很方便。

场景二:qsort函数与我们的泛型编程!

其实关于回调函数,在我们的C语言标准库里面有这么一个函数,这个函数的功能就是对数组里面的各个元素进行一个排序的,而且是针对任意的数据类型,可以自由地选择你是要升序还是要降序的。对,你没有听错,C语言里面是专门提供了一个帮助我们开发人员排序的这么一个函数的。那这个函数就是我们接下来要给大家介绍的qsort函数。

这个函数是包含在头文件#include<stdlib.h>里面的,那这个函数具体详细的描述是怎样的呢,这里我们给大家提供一个网址,在这个网址里面详细介绍了这个函数各个参数的意义,以及它是如何使用的:https://legacy.cplusplus.com/reference/cstdlib/qsort/?kw=qsort。只不过都是英语的介绍,因为这是官方的cplusplus文档查阅网站,那这都是国外的。

那部分小伙伴可能就表示:我看不太懂啊英语,啊别急这里,我们就先把函数的原型写在下面,供大家随时地参考:

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

这个函数有四个参数,每个参数的含义具体是什么呢?我们给大家写在了下面:

  1. void* base:表示待排序数组的首元素的地址;
  2. size_t num:表示待排序数组的元素个数是多少;
  3. size_t size:表示待排序数组中每个元素的大小是多大,以字节为单位;
  4. int(*compar)(const void*, const void*):是用户自己写的,用于比较元素间大小的函数。

关于第一个参数void*,在前面我们有给大家介绍说:如果一个函数的参数部分设计成void*的指针类型,那么这个参数可以接受任意数据类型的指针或者说地址。这也是这个qsort函数可以对任意数据进行排序的关键所在。

后面两个size_t类型数据没什么好说的,其中的size_t就是无符号整数类型。

最后一个参数是一个函数指针,这个函数指针的意思是说:我所指向的函数有两个const void *类型的参数,同时以int作为返回值。我相信大家对于这块函数指针的理解已经基本没问题了,估计难以理解的是const修饰指针,这是一个什么东西呢?实际上是下面的这种情况:

  • const修饰变量:

将一个变量修改成变量,这个量任然是变量(最典型的测试说明:在C99之前,只有常量可以作为定义数组时的长度,而变量不可以),但是是不能被修改值的变量。

但是同时话又说回来这里的不能被修改,也只是表面上做到了对它的限制,实际上你用一个指针保存这个变量的地址,然后对这个指针进行解引用,也能做到对它值的修改。

  • const修饰指针变量:

const关键字除了能够修饰变量以外,还可以用来修饰指针变量,这个时候以符号 “*” 为界,分以下两种情况进行讨论:

First--------const放在符号 “*” 的左边: 

const int* p = &a;

这个时候的const限制的是指针所指向的内容,即指针所指向的内容不能被修改。换句话说不允许通过指针变量p对变量a里面的内容进行修改。

Second--------const放在符号 “*” 的右边: 

int* const p = &a;

如果你这样写,那const则限制的是指针本身,即指针本身的值不允许被修改。换句话说指针p不能指向一块新的地址空间,只能保存变量a的地址空间。

对于函数qsort而言,你用户提供的数据比较函数compar,你只需要做一件事就可以了,就是拿出两个参数指针p1,p2所指向的内容的值,然后做个比较就可以了。显然,这个过程其实我们并不期望去修改指针所指向的内容,因此这里我们使用了const对我们指针所指向的内容进行了一个限制,

话又说回来,这个函数我应该怎么去使用它呢,这里的比较函数compar又应该如何去设计呢,我想还是要自己亲自去实践一下才能深刻体会。下面是对于qsort函数博主在做测试时写的代码,仅供参考:

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

//一个用于表示学生的结构体:
struct Stu
{
	int age;      //学生年龄
	char name[20];//学生姓名
};

//练习使用qsort函数排序整型数组:

//第一步:先设计我们的compar函数,下面同理:
int cmp_int(const void* p1, const void* p2)
{
	return *(int*)p1 - *(int*)p2;
}
void test1()
{
	int arr[10] = { 1,3,2,4,5,7,8,6,9,10 };
	printf("排序之前的arr数组是:\n");
	for (int i = 0; i < sizeof arr / sizeof(int); i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
	qsort(arr, sizeof arr / sizeof(int), sizeof(int), cmp_int);
    printf("排序之后的arr数组是:\n");
	for (int i = 0; i < sizeof arr / sizeof(int); i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
}

//练习qsot函数排序结构体数组,基于年龄:
int cmp_Stu_age(const void* p1, const void* p2)
{
	return ((struct Stu*)p1)->age - ((struct Stu*)p2)->age;
}

void test2()
{
	struct Stu arr[3] = { {12,"ZhangSan"},{29,"LiSi"},{15,"WangWu"} };
	printf("排序之前的arr数组是:\n");
	for (int i = 0; i < sizeof arr / sizeof(struct Stu); i++)
	{
		printf("%d ", arr[i].age);
	}
	printf("\n");
	qsort(arr, sizeof arr / sizeof(struct Stu), sizeof(arr[0]), cmp_Stu_age);
	printf("排序之后的arr数组是:\n");
	for (int i = 0; i < sizeof arr / sizeof(struct Stu); i++)
	{
		printf("%d ", arr[i].age);
	}
	printf("\n");
}

//练习使用qsot函数排序结构体数组,基于姓名:
int cmp_Stu_name(const void* p1, const void* p2)
{
	return strcmp(((struct Stu*)p1)->name, ((struct Stu*)p2)->name);
}

void test3()
{
	struct Stu arr[3] = { {12,"ZhangSan"},{29,"LiSi"},{15,"WangWu"} };
	printf("排序之前的arr数组是:\n");
	for (int i = 0; i < sizeof arr / sizeof(struct Stu); i++)
	{
		printf("%s ", arr[i].name);
	}
	printf("\n");
	qsort(arr, sizeof arr / sizeof(struct Stu), sizeof(arr[0]), cmp_Stu_name);
	printf("排序之后的arr数组是:\n");
	for (int i = 0; i < sizeof arr / sizeof(struct Stu); i++)
	{
		printf("%s ", arr[i].name);
	}
	printf("\n");
}

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

在这个里面所作的测试用例都是升序排列的,你如果希望得到降序的结果,其实也不难,只要改变compar函数里指针p1和p2的运算顺序就可以了! 这里就先不演示了,大家感兴趣地话可以去试一试。

通过练习使用qsort函数,你会发现这不就是一个妥妥的利用函数指针知识实现的一个排序函数吗。

(3)用冒泡排序来模拟实现qsort函数:

首先说明一点:C语言标准库里面的qsrot函数底层用的是快速排序。

尽管我们可能还不太清楚快速排序是怎么一回事,但是我们现在已经学习和了解了函数指针,也知道一个最基本的排序算法——冒泡排序,能不能用当前知识来模拟实现一个qsort函数呢?答案是可以的。

这里就不详细讲解程序的具体实现思路了,但是对于程序中一些重要的,可能卡脖子的地方我们还是进行一个提示说明:

  • 一般的冒泡排序比较整数的,判断大小很简单,直接使用大于小于符号就可以解决我们这里的问题,但是我们是面向所有数据类型做的一个排序算法(这种编程或者说设计程序的方法也叫泛型编程),所以我们这里使用第四个参数函数指针所指向的函数来进行一个大小的判断;
  • 其次是要思考如何让我们的指针一次跳过一个数据类型的大小空间的问题,虽然我事先并不清楚这个函数它操作的那个数组的对象是什么类型的,但是这个问题我想在你知道了它的一个元素具体有多大之后,想要解决也不是问题;
  • 最后是关于交换两个变量的值,只要涉及排序就不可避免要交换两个变量的值,但是这次情况有点特殊,我事先并不知道用户的数据类型是什么,我只能根据用户提供的数据来确定这个要交换的元素具体相对于起始位置的偏移量,仅仅知道这个你有办法交换两个数组里元素的值吗。

示例代码:

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<string.h>

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

/*
工具函数:
*/
//负责打印数组元素:
void print_arr(const int* arr,size_t sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
}

//负责交换两个变量的值: 
void swap(char* buf1, char* buf2, size_t sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{//交换交换的是地址里面的内容,而不是地址本身
		char tmp = *buf1;
		*buf1 = *buf2;
		*buf2 = tmp;
		buf1++;
		buf2++;
	}
}

//负责打印Student的age:
void print_Stu_age(struct Stu* str, size_t num)
{
	int i = 0;
	for (i = 0; i < num; i++)
	{
		printf("%d ", str[i].age);
	}
	printf("\n");
}

//负责打印Student的name:
void print_Stu_name(struct Stu* str, size_t num)
{
	int i = 0;
	for (i = 0; i < num; i++)
	{
		printf("%s ", str[i].name);
	}
	printf("\n");
}


/*
模拟实现bubble_sort()函数,实现所有基本数据类型的排序,包括结构体的排序
*/
void bubble_sort(void* base, size_t num, size_t sz,int (*cmp)(const void*,const void*))
{
	int i = 0;
	int j = 0;
	for (i = 0; i < num - 1; i++)
	{
		for (j = 0; j < num - 1 - i; j++)
		{
			if (cmp((char*)base+j*sz,(char*)base+(j+1)*sz)>0)
			{
				swap((char*)base + j * sz, (char*)base + (j + 1) * sz, sz);
			}
		}
	}
}

/*
test1测试函数一:测试整型数组
*/
int cmp_int(const void* e1, const void* e2)
{
    //这是一个降序排序版本控制的示例:
	return *(int*)e2 - *(int*)e1;
}
void test1()
{
	int arr[10] = { 12,13,45,82,37,46,51,66,36,68 };
	int num = sizeof(arr) / sizeof(int);
	print_arr(arr,num);
	bubble_sort(arr,num,sizeof(int),cmp_int);
	print_arr(arr,num);
}

/*
test2,test3测试函数二,三:测试结构体数组
*/
int cmp_Stu_by_int(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);
}
void test2()
{
	struct Stu arr[] = {{"lisi",32},{"gouge",34},{"chenlaohan",47},{"liubei",41}};
	size_t len = sizeof(arr) / sizeof(arr[0]);
	print_Stu_age(arr, len);
	bubble_sort(arr, len, sizeof(arr[0]), cmp_Stu_by_int);
	print_Stu_age(arr, len);
}
void test3()
{
	struct Stu arr[] = { {"lisi",32},{"gouge",34},{"chenlaohan",47},{"liubei",41} };
	size_t len = sizeof(arr) / sizeof(arr[0]);
	print_Stu_name(arr, len);
	bubble_sort(arr, len, sizeof(arr[0]), cmp_Stu_by_name);
	print_Stu_name(arr, len);
}

int main()
{
	//测试bubble_sort排序整型数组:
	printf("测试bubble_sort函数排序整型数组:\n");
	test1();

	//测试bubble_sort排序结构体数组(基于年龄->int类型):
	printf("\n测试bubble_sort排序结构体数组(基于年龄->int类型):\n");
	test2();

	//测试bubble_sort排序结构体数组(基于姓名->char [20]类型):
	printf("\n测试bubble_sort排序结构体数组(基于姓名->char [20]类型):\n");
	test3();
	return 0;
}

到此为止,我们的博客也接近尾声,可能还有很多讲解不够透彻或理解不到位的地方,也是诚挚欢迎广大读者朋友在讨论区进行指正和批评。

最后也给大家揭晓一下前面给大家遗留下的那个问题的答案:

char* (*pstr)[5] = &str;

我想这对于大家应该是没有问题的了,最后其实还有两篇关于指针章节的一个总结,还请大家多多关注吖😘!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值