系列文章:
目录
1.数组名的理解
在编程随闪--指针(一)中,我们使用指针访问数组内容时,有这样的代码:
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int *p = &arr[0];
这里我们使用 &arr[0] 拿到了数组第一个元素(首元素的地址)。其实,数组名本来就是地址,而且是数组首元素的地址(首元素第一个字节的地址),我们可以测试一下:
#include<stdio.h>
int main()
{
int arr[10] = {0};//int 大小为4字节
printf(" arr = %p\n", arr);
printf("&arr[0] = %p\n", arr);
return 0;
}

图1.1 arr和&arr[0]结果对比图
不难发现,数组名和数组首元素的地址输出的结果是一样的。数组名就是数组首元素(第一个元素)的地址,严格意义上来说,数组名是数组首元素(以整型数组为例子,整型int为4字节)中第1个字节的地址(文章后续将一直以数组首元素中第一个字节的地址叙述,这样可以帮助你更好的理解,当然是以整型数组int4字节为例子)。
那么接下来又有一个问题,既然数组名是数组首元素的地址(首元素中第一个字节的地址),那么下列代码的值为?
#include <stdio.h>
//本代码为32位平台下运行
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
printf("%d\n", sizeof(arr));//。sizeof()是求所填参数的字节大小,为40字节大小
return 0;
}
之前不是说数组名是数组首元素的地址(首元素中第一个字节的地址)吗。那既然是地址,64位平台下应该是8字节,32位平台下是4字节啊。40字节是怎么回事?
数组名是数组首元素的地址(首元素中第一个字节的地址),这是毋庸置疑的,但是有俩种例外:
1,sizeof(数组名):sizeof中单独放数组名,这里的数组名(sizeof中的数组名)代表整个数组,计算的是整个数组的大小。
2,&数组名:这里的数组名也表示整个数组,取出的是整个数组的起始地址(整个数组的起始地址和数组首元素的地址是有区别的,虽然结果相同,但是含义是不一样的)
除了上述俩种情况,任何地方使用数组名,数组名均表示数组首元素的地址(首元素第一个字节的地址)。
#include <stdio.h>
int main()
{
int arr[10] = {0};
printf("&arr[0] = %p\n", &arr[0]);//数组首元素地址(首元素中第一个字节的地址)
printf(" arr = %p\n", arr);//数组名
printf(" &arr = %p\n", &arr);//&数组名(整个数组的地址,其值和首元素地址相同)
return 0;
}

图1.2 &arr[0]、arr和&arr结果对比图
你会发现,虽然结果都是一样的。但是,含义是不同的,是有区别的。当进行加1操作时你会发现:
#include<stdio.h>
int main()
{
int arr[10] = { 0 };
printf("&arr[0] = %p\n", &arr[0]);//数组首元素地址
printf("&arr[0] + 1 = %p\n", &arr[0] + 1);//这里的1为一个指针数据类型大小,int*为4字节,char*为1字节
printf(" arr = %p\n", arr);//数组名
printf(" arr + 1 = %p\n", arr + 1);
printf(" &arr = %p\n", &arr);//&数组名
printf(" &arr + 1 = %p\n", &arr + 1);
return 0;
}

图1.3 +1字节大小对比图
数组名表示数组首元素的第一个字节的地址(以int数据类型为例,int*)。int*类型的指针+1,跳过4个字节(一个整型)到达数组中下一个整型元素,这就是&arr[0] 和&arr[0] + 1相差4字节的原因。arr 和 arr + 1同理。
&arr[0] 和arr的地址相同,进行加1操作后的地址也相同,&arr虽然地址和前俩个相同,但是加1操作后的地址就不同了(指针+1跳过几个字节和指针的类型是相关的)。这是因为&arr取的是整个数组的地址(一个整型元素4字节,10个为40字节),其加1操作是跳过了整个数组(10个元素)的字节(40字节)。&arr的值和&arr[0]的值相同是因为取的都是首元素中第一个字节的地址(起点虽然相同,但是+1跨越幅度不同,因为其指针的数据类型不同)

图1.4 &arr和arr +1对比图
2.使用指针访问数组
有了上面知识的支持,再结合数组的特点(内存中连续存放),我们就可以很方便的使用指针来访问数组。
#include<stdio.h>
int main()
{
int arr[10] = { 0 };
int sz = sizeof(arr) / sizeof(arr[0]);//整个数组字节大小 / 首元素字节大小,得到数组元素个数
int* p = arr;//数组名代表首元素第一个字节的地址,p指针变量存放的是地址
//输入10个值
for (int i = 0; i < sz; i++)
{
//scanf("%d",&arr[i]);//&arr[i]等价于p + i,p为起始地址,i为跳过整型的个数
scanf("%d", p + i);//arr + i也可以,只要排除那俩种特例,数组名就表示数组首元素地址
}
//输出10个值
for (int i = 0; i < sz; i++)
{
//printf("%d ",arr[i]);
printf("%d ",*(p + i));//p变量存放数组首元素的地址,i为跳过整型元素的个数
//p + i为第i个元素的地址,解引用得到该地址的值,*(arr + i)等价于*(p + i)
}
return 0;
}

图2.1 p + i情况图
通过上述代码可以得知,*(arr + i)等价于arr[ i ],那么*(p + i)也可以写成p[ i ]。数组名和一个指针变量是一样的,数组名表示首元素的地址(排除俩种特例的情况下),地址其实就是指针。
数组就是数组,在内存中是一块连续空间,数组的大小和数组元素以及元素的数据类型相关;指针变量就是指针变量,这个变量的大小就是4或8字节。
数组名是数组首元素的地址,可以使用指针来访问数组。
*(arr + i)中,加法支持交换律,那么 *(arr + i) 也可以写成 *(i + arr),又*(arr + i)等价于arr[ i ],那么arr[ i ]也可以写成 i [ arr ]。[ ]下标引用操作符和 +、-一样都是操作符;1 + 2 可以交换为 2 + 1,那么arr[ i ]也可以写为i[ arr ]。arr[ i ]可以转换成 *(arr + i),那么i[ arr ]也可以转换为*(i + arr) 。虽然这些转换是可行的,但是不建议代码中这样操作。这里只是告诉你原理,来帮助你更好的理解数组名和指针。
3.一维数组传参的本质
数组我们学过了,之前也讲了,数组是可以传递给函数的 。具体可以看这篇文章C语言--函数,这里我们讨论⼀下数组传参的本质。首先从⼀个问题开始,我们之前都是在函数外部计算数组的元素个数,那我们可以把数组传给⼀个函数后,函数内部求数组的元素个数吗?
//数组传参,形参是可以写成数组的形式的,在语法层面上初学更容易理解,其本质上还是指针变量
//void Print(int arr[10], int sz)
//{
// /*
// 数组传参的本质是传递数组首元素的地址,所以形参访问的数组和实参访问的数组是同一个数组
// 也就是说,形参不会再次创建一个数组空间,所以形参的数组是可以省略掉数组大小的([]中的值)
// */
// for (int i = 0; i < sz; i++)
// {
// printf("%d ",arr[i]);
// }
//}
//指针形式
void Print(int *p,int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ",*(p + i));
}
}
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int sz = sizeof(arr) / sizeof(arr[0]);
Print(arr,sz);//arr数组名代表数组首元素地址
return 0;
}
我们知道,数组名是数组首元素的地址,那么在数组传参的时候,传递的数组名就是数组首元素的地址,那既然是地址,就必然要用指针变量(指针)来接受。
总的来说,一维数组传参,函数形参部分可以写成数组的形式,也可以写成指针变量(指针)的形式。
4.二级指针
指针变量(指针)也是变量,是变量就有地址,那么指针变量(指针)的地址存放在哪里呢?答案是二级指针(二级指针变量)。
#include<stdio.h>
int main()
{
int a = 10;
int* p = &a;//p指针变量为一级指针变量,存放整型变量a的地址。
/*
p指针变量的数据类型为int*
int* p = &a;代码中,p左边的第一个*表示p变量为指针变量,最左边的int表示p指针变量所存储的数据(整型变量a的地址)解引用后的数据类型,即int表示为a变量的数据类型。
*/
int** pp = &p;//pp指针变量为二级指针,存放一级指针变量p的地址
/*
pp指针变量的数据类型为int**
int** pp = &p代码中,pp左边第一个*表示pp变量为指针变量,最左边的int*表示pp指针变量所存储的数据(一级指针变量p的地址)解引用后的数据类型,即int*表示为p变量的数据类型。
*/
return 0;
}

图4.1 一、二级指针变量的地址和其变量空间所存储的值
#include<stdio.h>
int main()
{
int a = 10;
int* p = &a;
int** pp = &p;
printf("%p\n",*pp);//二级指针pp存放的是一级指针p的地址,对pp进行一次解引用得到p指针变量的值(a变量的地址)
printf("%d\n",**pp);//再次对pp进行一次解引用(对得到的a变量地址解引用得到a变量的值)
printf("%p\n",&a);//打印a变量的地址
return 0;
}
5.指针数组
指针数组是数组还是指针?这里考一考读者的阅读理解,哈哈。我公布正确答案了:指针数组是数组 。我们可以类比一下,整形数组是存放整型数据的数组(整型数组的每个元素都是整型),字符数组是存放字符数据的数组(字符数组的每个元素都是字符)。那么指针数组呢?自然是存放指针数据的数组(指针数组的每个元素都是指针(地址))。

图5.1 整型数组和字符数组中元素的数据类型

图5.2 指针数组中元素的数据类型
5.1指针数组模拟二维数组
#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};//数组名表示首元素地址
int sz = sizeof(arr) / sizeof(&arr);//sizeof(&数组名)表示首元素的地址
int sz1 = sizeof(arr1) / sizeof(arr1[0]);
//printf("%d",sz);
for (int i = 0; i < sz; i++)
{
for (int j = 0; j < sz1; j++)
{
printf("%d ",arr[i][j]);
//printf("%d ", *(*(arr + i) + j));内存的访问本质上都是指针运算
}
printf("\n");
}
return 0;
}

图5.3 指针数组元素内存分布图
6.字符指针变量
在指针变量(指针)的类型中,有一种指针类型为字符指针char* 。
#include<stdio.h>
int main()
{
char arr[10] = "abcde";//"abcde\0\0\0\0\0"
char* p1 = arr;//数组名代表首元素地址
*p1 = 'z';
const char* p2 = "abc";//"abc"为字符串常量,这里把常量字符串的首字符地址放入p2
//*p2 = 'z';
printf("%s\n",p1);//%s打印字符串需要提供一个起始地址
return 0;
}
//剑指offer字符串相关例题
#include<stdio.h>
int main()
{
char str1[] = "hello world.";
char str2[] = "hello world.";
const char* str3 = "hello world.";
const char* str4 = "hello world.";
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;
}
7.数组指针变量(数组指针)
数组指针(指针变量)是数组还是指针(指针变量)?这里又来考验读者的阅读理解能力了。数组指针(指针变量)是指针(指针变量),该指针(指针变量)存放的是数组的地址 。
//下面哪个是数组指针?
int *p1[10];
int (*p2)[10];//是数组指针
7.1数组指针变量初始化
数组指针变量(指针)是用来存储数组的地址,那么如何获取数组的地址?这在第一节中有讲到,&数组名获取到的就是整个数组的地址(只不过起始地址和首元素地址一样)。
int arr[10] = {0};
&arr;//得到的就是数组的地址
如果要存放整个数组的地址,就得存放在数组指针变量(指针)中,
int arr[10] = {0};
int (*p)[10] = &arr;//数组的地址存储在数组指针变量(指针)p中
8.二维数组传参的本质
有了对数组指针的理解,我们就能讲一下二维数组传参的本质。在这之前,二维数组传参给一个函数时,是这样的:
#include<stdio.h>
//Print()函数中形参参数为数组形式
void Print(int arr[][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 ",arr[i][j]);
}
printf("\n");
}
}
int main()
{
int arr[3][5] = { {1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7} };
Print(arr,3,5);
return 0;
}
二维数组的数组名也是表示二维数组首元素的地址,但是,二维数组的首元素和一维数组的首元素可不是一个概念。二维数组的“首元素”是二维数组中的第一行一维数组中的全部元素,如上例中的{1,2,3,4,5}就是二维数组中的首元素,{2,3,4,5,6}为二维数组中第二个元素,{3,4,5,6,7}为二维数组中第三个元素。
那么二维数组的首元素已经知道了,首元素的地址也就知道了,二维数组中第一行一维数组整个数组的地址就是二维数组的首元素地址。
#include<stdio.h>
//Print()函数中形参参数为数组指针形式
void Print(int (*arr)[5], int r, int c)
{
int i = 0;
for (i = 0; i < r; i++)
{
int j = 0;
for (j = 0; j < c; j++)
{
//arr表示第一行一维数组的整个数组地址,好比一维数组中&数组名,当*解引用一次后
//就变成了数组名(可理解为*、&相互抵消),数组名代表一维数组首元素的地址
//首元素地址+j是后续数组元素地址,再进行一次解引用后得到该地址的值
printf("%d ", *(*(arr + i) + j));//arr[i][j]
}
printf("\n");
}
}
int main()
{
int arr[3][5] = { {1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7} };
Print(arr,3,5);
return 0;
}
在这里,我们再回顾一下二维数组的知识,二维数组其实可以看做每个元素是一维数组的数组,也就是二维数组的每个元素是一个一维数组,那么二维数组的首元素就是其中第一个元素的地址(第一行一维数组的整个数组的地址)。
图8.1 二维数组示例图
9.函数指针变量(函数指针)
函数指针变量(函数指针),通过之前的学习,我们知道它是一个存放函数地址的指针变量(指针),看到这也许你会有疑问:函数也有地址吗?这里我们可以验证:
#include<stdio.h>
void Print(int x, int y)
{
printf("test\n");
}
int main()
{
printf("Print = %p\n",Print);//%p为打印地址的占位符
printf("&Print = %p\n",&Print);
return 0;
}
图9.1 函数地址图
通过测试,我们发现确实打印出了地址,那就说明函数是有地址的,且&函数名和函数名都是表示函数的地址(没有函数首元素这一说哈)。
函数指针变量(函数指针)的写法和数组指针变量(数组指针)的写法很相似。
#include<stdio.h>
void Print(int x, int y)
{
printf("test\n");
}
int main()
{
void (*pf)(int , int) = &Print;//pf是专门用来存储函数地址的,pf就是函数指针变量
/*
void (*pf ) (int x, int y)
| | ------------
| | |
| | pf指向函数的参数类型和个数的交代
| 函数指针变量名
pf3指向函数的返回类型
int (*) (int x, int y) //pf函数指针变量(函数指针)的类型,去掉变量名就是该变量的类型
*/
return 0;
}
#include<stdio.h>
int Add(int x, int y)
{
return x + y;
}
int main()
{
int (*pf)(int ,int) = &Add;//pf指针变量存储函数的地址
int b = Add(3,5);//通过函数名调用
printf("%d\n",b);
int c = (*pf)(4, 5);//通过函数指针来调用
printf("%d\n", c);
int d = (pf)(5, 6);//pf左边的*是摆设,因为函数指针中&函数名和函数名都表示函数的地址,&也就是摆设了
//数组指针中,*不是摆设是因为&数组名和数组名所表示的含义是不同的
printf("%d\n", d);
return 0;
}
9.1函数指针变量(函数指针)的使用
(*(void (*)())0)();
/*
void(*)()是函数指针的类型,(void(*)())表示强制类型转换
0原本是整型,强制类型转换为void(*)()的函数指针类型
(*(void (*)())0)表示解引用强制类型转换后0这个地址,(*(void (*)())0)()后面的()表示无参函数的参数
整行代码的意思是调用0地址处放着无参,且函数返回类型为void的函数
*/
void (*signal(int , void(*)(int)))(int);//非常难以理解,但就是这样写的
/*
void(*)(int)是函数指针类型,(int , void(*)(int))是一个函数的参数
signal(int , void(*)(int)),signal是函数名,void(* )(int)是signal的函数返回类型
整行代码是一个函数声明,void(* )(int) signal(int , void(*)(int));//不允许这样写,只是表明其用意
*/
10.typedef关键字
typedef是用来类型重命名的,可以将复杂的类型简单化。比如,你觉得unsigned int写起来太麻烦了,那么你可以这样写:
typedef unsigned int u_int;//将unsigned int类型重命名为u_int类型
typedef unsigned int u_int;
typedef int* pint_t;
//int(*p)[5]p是指针变量,typedef int(*p)[5] pf_t这是不对的
typedef int(*parr_t)[5];//parr_t要写在()里面,parr_t等价于int(*)[5]类型
//typedef void(*)(int) pf_t这是不允许的
typedef void(*pf_t)(int);//pf_t要写在()里面,pf_t等价于void(*)(int)类型
pf_t signal(int,pf_t);
//void (*signal(int , void(*)(int)))(int);
int main()
{
unsigned int a = 0;
u_int b = 0;
pint_t p1 = NULL;
int* p2 = NULL;
parr_t parr1 = NULL;
int(*parr2)[5] = NULL;
return 0;
}
11.函数指针数组
数组是一个存放相同类型数据的存储空间,那么函数指针数组是一个数组,数组中存放的是函数指针(函数的地址),也就是说数组中元素的类型是函数指针类型。
int(*)() arr[5];//arr是函数指针数组,其中有五个元素(函数指针),元素的类型为int(*)()类型
11.1函数指针数组的应用
函数指针数组的用途:转移表
//计算器的一般实现
#include<stdio.h>
int add(int a, int b)
{
return a + b;
}
int sub(int a, int b)
{
return a - b;
}
int mul(int a, int b)
{
return a * b;
}
int div(int a, int b)
{
return a / b;
}
int main()
{
int x, y;
int input = 1;
int ret = 0;
do
{
printf("*************************\n");
printf(" 1:add 2:sub \n");
printf(" 3:mul 4:div \n");
printf(" 0:exit \n");
printf("*************************\n");
printf("请选择:");
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;
}
//使用函数指针数组实现转移表
#include<stdio.h>
int add(int a, int b)
{
return a + b;
}
int sub(int a, int b)
{
return a - b;
}
int mul(int a, int b)
{
return a * b;
}
int div(int a, int b)
{
return a / b;
}
int main()
{
int x, y;
int input = 1;
int ret = 0;
int(*p[5])(int x, int y) = { 0, add, sub, mul, div }; //转移表,存放函数指针(函数地址)
do
{
printf("*************************\n");
printf(" 1:add 2:sub \n");
printf(" 3:mul 4:div \n");
printf(" 0:exit \n");
printf("*************************\n");
printf("请选择:");
scanf("%d", &input);
if ((input <= 4 && input >= 1))
{
printf("输⼊操作数:");
scanf("%d %d", &x, &y);
ret = (*p[input])(x, y);
printf("ret = %d\n", ret);
}
else if (input == 0)
{
printf("退出计算器\n");
}
else
{
printf("输⼊有误\n");
}
} while (input);
return 0;
}
12.回调函数
回调函数就是⼀个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另⼀个函数,当这个指针被用来调用其所指向的函数时,被调用的函数就是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的⼀方调用的,⽤于对该事件或条件进行响应。
针对11.1节函数指针数组的应用相关代码可以使用回调函数稍加优化:
#include<stdio.h>
int add(int a, int b)
{
return a + b;
}
int sub(int a, int b)
{
return a - b;
}
int mul(int a, int b)
{
return a * b;
}
int div(int a, int b)
{
return a / b;
}
void cal(int(*pf)(int,int))
{
int x, y;
int ret = 0;
printf("输入操作数:");
scanf("%d %d",&x,&y);
ret = (*pf)(x, y);
printf("ret = %d\n",ret);
}
int main()
{
int input = 1;
do
{
printf("*************************\n");
printf(" 1:add 2:sub \n");
printf(" 3:mul 4:div \n");
printf("*************************\n");
printf("请选择:");
scanf("%d", &input);
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;
}
12.1qsort()函数的使用
qsort()函数是库函数,直接可以用来排序数据。底层使用的是快速排序的方式。
void qsort (void* base,
size_t num,
size_t size,
int (*compar)(const void*,const void*));
/*
qsort()函数使用者-知道要排序的是什么数据,这些数据如何比较,所以要提供俩个元素的比较函数
*/
12.1.1使用qsort()函数排序整型数据
#include<stdio.h>
#include<stdlib.h>
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 Print_arr(int arr[],int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ",arr[i]);
}
}
void test01()
{
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);
}
int main()
{
//使用qsort()函数排序整形数据
test01();
return 0;
}
12.1.2使用qsort()函数排序结构体数据
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
/*
void qsort (void* base,
size_t num,
size_t size,
int (*compar)(const void*,const void*));
*/
//void*是无具体类型的指针,该类型指针不能直接解引用,也不能进行+-整数运算
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 Print_arr(int arr[],int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ",arr[i]);
}
}
void test01()
{
int arr[] = {9,0,7,6,1,4,3,2,5,8};
int sz = sizeof(arr) / sizeof(arr[0]);
qsort(arr,sz,sizeof(arr[0]),cmp_int);
Print_arr(arr,sz);
}
struct Stu
{
char name[20];
int age;
};
//结构体元素比较
//1,按名字比较--字符串比较
//2,按年龄比较--整型比较
int cmp_Stu_by_name(const void* p1,const void* p2)
{
//strcmp()函数是按照对应字符串中的字符的ASCII码值比较的
return strcmp(((struct Stu*)p1)->name, ((struct Stu*)p2)->name);
}
int cmp_Stu_by_age(const void* p1, const void* p2)
{
return ((struct Stu*)p1)->age - ((struct Stu*)p2)->age;
}
void test02()
{
struct Stu arr[3] = { {"zhangsan",20} ,{"lisi",35} ,{"wangwu",18}};
int sz = sizeof(arr) / sizeof(arr[0]);
qsort(arr,sz,sizeof(arr[0]),cmp_Stu_by_age);
}
int main()
{
//使用qsort()函数排序整形数据
//test01();
//使用qsort()函数排序结构体数据
test02();
return 0;
}
12.1.3qsort()函数的模拟实现
模拟qsort()函数来实现一个冒泡排序的函数,该函数可以排序任意类型数据。
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
//#include<assert.h>
/*
void qsort (void* base,
size_t num,
size_t size,
int (*compar)(const void*,const void*));
*/
//void*是无具体类型的指针,该类型指针不能直接解引用,也不能进行+-整数运算
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 Print_arr(int arr[],int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ",arr[i]);
}
}
void test01()
{
int arr[] = {9,0,7,6,1,4,3,2,5,8};
int sz = sizeof(arr) / sizeof(arr[0]);
qsort(arr,sz,sizeof(arr[0]),cmp_int);
Print_arr(arr,sz);
}
struct Stu
{
char name[20];
int age;
};
//结构体元素比较
//1,按名字比较--字符串比较
//2,按年龄比较--整型比较
int cmp_Stu_by_name(const void* p1,const void* p2)
{
//strcmp()函数是按照对应字符串中的字符的ASCII码值比较的
return strcmp(((struct Stu*)p1)->name, ((struct Stu*)p2)->name);
}
int cmp_Stu_by_age(const void* p1, const void* p2)
{
return ((struct Stu*)p1)->age - ((struct Stu*)p2)->age;
}
void test02()
{
struct Stu arr[3] = { {"zhangsan",20} ,{"lisi",35} ,{"wangwu",18}};
int sz = sizeof(arr) / sizeof(arr[0]);
qsort(arr,sz,sizeof(arr[0]),cmp_Stu_by_age);
}
void Swap(char * p1,char* p2,size_t width)
{
//width为一个元素的字节大小
//一个字节一个字节的进行交换,最小字节单位进行交换
int i = 0;
for (i = 0; i < width; i++)
{
char tmp = *p1;
*p1 = *p2;
*p2 = tmp;
p1++;
p2++;
}
}
void Bubble_sort(void* base, size_t sz,size_t width,int(*cmp)(const void* p1,const void* p2))
{
//趟数
int i = 0;
for (i = 0; i < sz - 1; i++)
{
//每趟的比较次数
int j = 0;
for (j = 0; j < sz - 1 - i; j++)
{
//(char*)base + j * width,(char*)base + (j + 1) * width,char*是最小的指针类型,是最万无一失的
//比较arr[j]和arr[j + 1]
if ((*cmp)((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
{
Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
}
}
}
}
void test03()
{
/*int arr[] = { 9,0,7,6,1,4,3,2,5,8 };
int sz = sizeof(arr) / sizeof(arr[0]);
Bubble_sort(arr, sz, sizeof(arr[0]), cmp_int);
Print_arr(arr, sz);*/
struct Stu arr[3] = { {"zhangsan",20} ,{"lisi",35} ,{"wangwu",18} };
int sz = sizeof(arr) / sizeof(arr[0]);
Bubble_sort(arr, sz, sizeof(arr[0]), cmp_Stu_by_age);
}
int main()
{
//使用qsort()函数排序整形数据
//test01();
//使用qsort()函数排序结构体数据
//test02();
test03();
return 0;
}
写道这里,本文也就结束了,希望读完这篇文章你可以学到新的知识。如果有不懂的可以评论区留言,我会第一时间解答。
&spm=1001.2101.3001.5002&articleId=164430378&d=1&t=3&u=ba5052446e4e4a8480336890ec89803a)
796

被折叠的 条评论
为什么被折叠?



