一、数据类型介绍
数据类型 | 名称 | 大小 | 打印 |
char | 字符型 | 1B | %c |
short | 短整型 | 2B | %hd |
int | 整型 | 4B | %d |
long | 长整型 | 4B | %ld |
long long | 更长的整型 | 8B | |
float | 单精度浮点型 | 4B | %f |
double | 双精度浮点型 | 8B | %lf |
类型的意义:
(1)使用这个类型开辟内存空间的大小(大小决定了使用范围);
(2)如何看待内存空间的视角。(比如int和float,虽然内存大小相同,但是整数和浮点数在内存中的存储方式是不同的)
二、数据类型的类别划分
2.1 整型类
char//字符的本质是ASCII码值,是整型,所以划分到整形类
unsigned char
signed char
short
unsigned short [int]
signed short [int]
int
unsigned int
signed int
long
unsigned long [int]
signed long [int]
无符号数(unsigned):生活中有些数据是没有负数的,例如身高,体重。此刻最好用无符号数来定义。
2.2 浮点型类
//浮点型类,只要是表示小数就可以使用浮点型
float//精度低,存储的数值范围较小
double//精度高,存储的数据的范围更大。
2.3 构造类型(自定义类型)
//自定义类型——根据自己的需求创建不同的类型
int arr[5]; //数组类型
struct Stu {}; //结构体类型
enum {}; //枚举类型
union MyUnion{};//联合类型
2.4 指针类型
int* pa;
char* pc;
2.5 空类型(void)
void表示空类型(无类型),通常应用于函数的返回类型、函数的参数、指针变量。
void test(void) {
//第一个void表示函数没有返回值;
//第二个void表示函数不需要参数;
}
三、整型在内存中的存储
一个变量的创建是要在内存中开辟空间,空间的大小是根据不同的类型而决定的。
3.1 原码、补码、反码
整形的二进制表示有三种表达形式:
(1)正整数的原码反码补码是相同的——直接根据二进制转换就行(符号位0为正)
(2)负整数的原码反码补码需要进行计算;
1. 原码:直接通过正负的形式写出的二进制序列就是原码;
2. 反码:原码的符号位不变,其他位按位取反得到的就是反码;
3. 补码:反码+1就是补码;
(3)整数内存中存放的是补码的二进制序列;
3.2 大小端
3.2.1 大小端介绍
#include<stdio.h>
int main() {
int a = 20;
//00000000000000000000000000010100-补码
//0x00 00 00 14
//内存中存储:14 00 00 00
//(以二进制位存储,但是用vs显示的时候显示的是十六进制)
int b = -10;
//11111111111111111111111111110110-补码
//0xff ff ff f6
//内存中存储:f6 ff ff ff
}
为什么内存中的显示和我们计算出来的是相反着的?为什么以两位十六进制序列为一组?
(1)在计算机的内存存储中,有低地址和高地址,数据在在内存中的存储有很多种形式,只要存储进去之后,拿出来还是原来的数据就可以,为了减少工作量和复杂程度,最终被认可的是两种形式:(以0x11223344举例)
1. (低地址)11 22 33 44(高地址)——大端字节序存储;
2. (高地址)11 22 33 44(低地址)——小端字节序存储;
补充:对于0x11223344来说,越往左位数越高,相当于十进制的152,百位上的1要比十位上的5位数高,同理0x11223344中位数的大小:11 > 22 > 33 > 44
(2)大端字节序存储(简称大端存储)
把一个数据的高位字节序的内容存放在低地址处,把低位字节序的内容放在高地址处,就是大端字节序存储;
(3)小端字节序存储(简称小端存储)
把一个数据的高位字节序的内容存放在高地址处,把低位字节序的内容放在低地址处,就是小端字节序存储;
3.2.2 大小端机器的判定
判断一个机器是大端机器还是小端机器:
#include<stdio.h>
int check_sys() {
int a = 1;
//如果是大端存储:00 00 00 01
//如果是小端存储:01 00 00 00
return *(char*)&a;
//进行指针的强制类型转换,利用类型的访问权限不同,进行检查。
//取a的地址(int*)转换成(char*),访问权限由4个字节变为1个字节;
//如果是大端机器访问到的是00,小端机器访问到的是01,即大0小1,返回值正确。
//此处是简便书写,可以用if else语句。
}
int main() {
int ret = check_sys();
if (ret == 1) {
printf("小端机器");
}
else {
printf("大端机器");
}
return 0;
}
3.3 数据存储——整型练习
#include<stdio.h>
int main() {
char a = -1; //-1
//对-1的补码进行截断得到——11111111
//对截断得到的a型提升:11111111111111111111111111111111;
//此刻是补码,还原成原码得到:
//10000000000000000000000000000001——得到-1
signed char b = -1; //-1
//同上
unsigned char c = -1; //255
//截断得到的其实还是11111111,但是因为c是wnsigned char
//所以整型提升时全部补上0,得到:
//00000000000000000000000011111111——即255
printf("a=%d ; b=%d ; c=%d\n", a, b, c);
return 0;
}
#include<stdio.h>
#include<windows.h>
int main() {
unsigned int i;
for (i = 9; i >= 0; i--) {
printf("%u\n", i);
Sleep(1000);//休眠1000ms,方便观察打印的内容
}
//9/8/7/6/5/4/3/2/1/0/4294967295/4294967294......
//因为无符号数没有负数,因此循环不停止
//当i<0后,会按照-1,-2的补码进行打印,因为是无符号数,对应4294967295/4294967294......
return 0;
}
四、浮点型在内存中的存储
4.1 一个例子
常见的浮点数:3.14159,1E10=1.0 *(10的10次方);
#include<stdio.h>
int main() {
int n = 9;
float* pFloat = (float*)&n;
printf("n的值为:%d\n", n); //n的值为:9
printf("*pFloat的值为:%f\n",*pFloat); //*pFloat的值为:0.000000
*pFloat = 9.0;
printf("n的值为:%d\n", n); //n的值为:1091567616
printf("*pFloat的值为:%f\n", *pFloat);//*pFloat的值为:9.000000
return 0;
}
由上面的例子我们可以看到,为什么n和*pFloat明明存储的是一个数,为什么会不一样呢?这是因为浮点数的存储规则和整数不同。
4.2 IEEE754规则
4.2.1 IEEE753规则介绍
浮点数根据国家标准IEEE754规则都可以表示为如下的形式:
(1)任何一个二进制浮点数都可以表示成:(-1)^S * M * 2^E ;
(2)(-1)的S次方表示符号位,当S=0时为正数,当S=1时为负数;
(3)M表示有效数字,大于等于1,小于2;
(4)2的E次方表示指数为,相当于十进制科学计数法中的10的n次方。
举例说明:
- 十进制的5.75表示为二进制小数为:101.11(整数部分还是按照正常形式,注意小数部分,小数点后第一位表示2的-1次方,也就是0.5,所以小数部分为11,表示0.5+0.25)。
- 进行科学计数法表示:1.0111*2^2,因为是正数,所以符号位是0;
- 因此5.75的科学计数法表示为: (-1)^0 * 1.0111 * 2^2
- 对于5.75而言,转换后,S=0,M=1.0111,E=2;
4.2.2 浮点数的存储
IEEE754规定(存放时):
(1)对于32位浮点数,最高位是符号位S,接着8位是指数E,剩下的23位是有效数字M。
(2)对于64位浮点数,最高位是符号位S,接着11位是指数E,剩下的52位是有效数字M。
(3)特殊事项:
1. 对于有效数字M, 因为进行科学计数法转换之后,M一定会表示为1.xxxxx,所以IEEE754规定,在计算机内部保存时,默认这个数的第一位总是1,因此可以舍去。所以在保存时只保存小数部分即可。
例如:5.75保存时,有效数字位1.0111,在计算机中只保存0111,把小数点前的1舍去,在取出来的时候,默认把1再加上。
2. 对于指数E,因为机器默认指数E是无符号整数,因此约定有一个中间值E,用真实值+中间数=存储的数,比如:2的-1次方,存储进32位机器就是126;
32位机器中,指数位8位,中间数为127;64位机器中,指数位11位,中间数1023。
#include<stdio.h>
int main() {
float f = 5.75;//S=0;M=1.0111;E=2;
//存储M时,向后补零;
//S=0;M=0111 0000 0000 0000 0000 000,E=2+127=129=10000001;
//因此表示为:0100 0000 1011 1000 0000 0000 0000 0000
//转换成16进制表示为:0x40 b8 00 00
return 0;
}
4.2.3 浮点数的读取
IEEE754规定(取出时):
(1)对于E(默认32位机器):
1. 当不全为0或不全为1的时候:将指数E的计算值减去真实值127,得到真实值,再将有效数字M前加上第一位的1.0;
2. 当E全为0的时候:此刻指数E直接等于1-127为真实值,有效数字不再加上第一位的1。表示0或者接近0的很小的数字。
3. 当E全为1的时候:当有效数字M全为0,则表示正/负无穷大。
#include<stdio.h>
int main() {
int n = 9;
float* pFloat = (float*)&n;
//存放进去:0000 0000 0000 0000 0000 0000 0000 1001
printf("n的值为:%d\n", n); //n的值为:9
//n读出:0000 0000 0000 0000 0000 0000 0000 1001——即9
printf("*pFloat的值为:%f\n",*pFloat); //*pFloat的值为:0.000000
//f读出:0 00000000 00000000000000000001001
//S=0;M=00000000000000000001001;E=00000000
//根据规则:*pFloat的值是一个很接近于0的很小的正数,打印出来为0.000000
*pFloat = 9.0;
//存放进去:0 10000010 00100000000000000000000
printf("n的值为:%d\n", n); //n的值为:1091567616
//n读出:01000001000100000000000000000000
//正数;转换为:1091567616
printf("*pFloat的值为:%f\n", *pFloat);//*pFloat的值为:9.000000
//f读出:0 10000010 00100000000000000000000
return 0;
}
五、指针回顾
(1)我们通常说的指针指的是指针变量,指针是用来存放地址的,地址唯一标识一块内存空间;
(2)指针的大小是固定的4/8个字节(4是32位平台,8是64为平台);
(3)指针是有类型的,指针的类型决定了指针的 +-整数的步长,也决定了指针解引用操作的时候的权限;
六、字符指针、指针数组与数组指针
6.1 字符指针
int main() {
char ch = 'w';//创建空间ch,存放一个字符w
char* pc = &ch;//将ch的地址赋予给指针pc
*pc = 'b';//修改pc指向的空间的数值,即将ch的‘w’改成‘b';
printf("%c\n", ch);
const char* p1 = "abc";//被直接初始化,就是赋予只读区的值的地址。
const char* p2 = "abc";//为了防止被修改导致系统警告,用const限制不可修改
//创建指针p1,p2,将字符串“abc”的首地址赋予给p;
//不可修改——因为abc是系统内只读区的存储数据,没有修改权限
char arr1[] = "abc";
char arr2[] = "abc";
if (p1 == p2)
printf("p1==p2");
else
printf("p1!=p2");
//结果:相等
if (arr1 == arr2)
printf("arr1==arr2");
else
printf("arr1!=arr2");
//结果:不等
return 0;
}
(1)被直接赋予给p1的“abc”是放在内存的只读数据区中的常量字符串,只能读取不能修改,没有多份;而p1和p2存放的是常量字符串abc的首地址,在只读数据区中相同,因此相等;
(2)数组存放的“abc”是开辟了两个独立的空间,在空间中存放了对应的数据;因此arr1不等于arr2,是不相等的两个存储空间。
6.2 指针数组
指针数组——是数组,用来存放指针的数组。int* arr[]——用来存放整型指针的数组。
#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* parr[3] = { arr1,arr2,arr3 };//相当于一个二维数组。
//遍历打印指针数组
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 5; j++) {
printf("%d ", *(parr[i] + j));
}
printf("\n");
}
return 0;
}
6.3 数组指针
6.3.1 数组指针与指针数组的区别
(1)int* p3[10] = { arr0,arr1,arr2 }; 指针数组——是数组
这里的[]表示的是p3这个指针数组的元素的个数,而不是指针指向的数组的元素个数。(2)int* p2 = &arr;——错误写法
&arr取出的是整个数组的地址,而不是单个元素的地址
二级指针是用来存放一级指针变量的地址的,这里不是指针变量,无法用二级指针存储(3)int(*p2)[5] = &arr0; 数组指针——是指针
这里的[]表示的是指针指向的数组的元素个数。指向arr[5],因此这里是[5]这里的数据类型是:int(*)[5],是一个指向含有五个元素的一维整型数组的指针。
#include<stdio.h>
int main() {
int arr0[5] = { 1,2,3,4,5 };
int arr1[5] = { 2,3,4,5,6 };
int arr2[5] = { 3,4,5,6,7 };
int* p = arr0;//此处的*不是解引用,而是指针变量的标志
int* p3[10] = { arr0,arr1,arr2 };
int(*p2)[5] = &arr0;
int i = 0;
int sz = sizeof(arr0) / sizeof(arr0[0]);
for (i = 0; i < sz; i++) {
printf("%d ", (*p2)[i]);
printf("%d ", *(*p2+i));
}
return 0;
}
6.3.2 数组指针的常见应用
void print1(int (*p)[5],int r,int c){
for (int i = 0; i < r; i++) {
for(int j = 0; j < c; j++) {
printf("%d ", *(*(p + i) + j));
}
printf("\n");
}
printf("\n");
for (int i = 0; i < r; i++) {
for (int j = 0; j < c; j++) {
printf("%d ", (*(p + i))[j]);
}
printf("\n");
}
printf("\n");
for (int i = 0; i < r; i++) {
for (int j = 0; j < c; j++) {
printf("%d ", p[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 };
print1(arr,3,5);
return 0;
}
几种打印形式,通过对指针和数组的分析,可以得到:
*(*(p + i) + j) = (*(p + i) ) [ j ] = *( p [ i ] + j ) = p [ i ] [ j ]
6.3.3 几种容易混淆的概念的区分
int main() {
int arr[5]; //arr是整型数组
int* parr1[10]; //arr1是整型指针数组
int (*parr2)[10]; //arr2是整型数组指针
int (*parr3[10])[5]; //arr3是存放数组指针的数组
}
int (*parr3[10])[5] ; 可以类比于int arr3 [10] ;
(1)int (*)[5]是数组类型,类比于int;
(2)parr3[10]是数组,类比于arr3[10];
(3)arr3是一个存放整型数据的数组;parr3是一个存放整型数组指针的数组。
七、数组参数与指针参数
7.1 一维数组的传参
#include<stdio.h>
void test(int arr[]){} //正确
void test(int arr[10]) {} //正确
void test(int *arr) {} //正确
void test2(int *arr2[]) {} //正确
void test2(int **arr2) {} //正确
int main() {
int arr[10] = { 0 };
int* arr2[20] = { 0 };
test(arr);
test2(arr2);
}
7.2 二维数组的传参
#include<stdio.h>
void test(int arr[3][5]) {} //正确
//void test(int arr[][]) {} 形参的二维数组,行可以省略,列不能省略
void test(int arr[][5]) {} //正确
//void test(int *arr) {} 一个指针无法存放一行地址
//void test(int* arr[5]) {} 这里给出的参数是指针数组,是数组,不是指针
void test(int (*arr)[5]) {} //正确
//void test(int **arr) {} 一维数组的地址无法放到二级指针中。
int main() {
int arr[3][5];
test(arr);//二维数组的数组名,实际上代表的是首元素的地址,即第一行的地址。
}
7.3 一级指针传参
#include<stdio.h>
void print(int* p, int sz) {
for ( int i = 0; i < sz; i++) {
printf("%d\n", *(p + i));
}
}
int main() {
int arr[10] = { 0,1,2,3,4,5,6,7,8,9 };
int* p = arr;
int sz = sizeof(arr) / sizeof(arr[0]);
//
print(p, sz);
return 0;
}
那么一级指针可以接受什么参数呢?这个和指针的类型有关,比如说void print(int* p),
可以接收的参数是整型指针的参数,只要本质是一个整型指针的都可以接受。
其他同理,二级指针同理
7.4 二级指针传参
#include<stdio.h>
void test(int** str) {
printf("num=%d\n", **str);
}
int main() {
int a = 10;
int* p = &a;
int** pp = &p;
test(pp);
test(&p);
return 0;
}
八、函数指针
8.1 函数指针的定义
#include<stdio.h>
int Add(int x, int y) {
return x + y;
}
int main() {
printf("%p\n", &Add);
printf("%p\n", Add);
//函数名没有特殊含义,就是函数的地址
int (*pA)(int, int) = &Add;
int (*pB)(int, int) = Add;
int ret = (*pA)(2, 3);//调用:
int ret = pB(2, 3);
printf("%d\n", ret);
return 0;
}
(1)函数指针就是指向函数的指针,函数名和、&函数名没有区别,因此对于函数指针的定义可以看做:函数返回类型 (*函数指针名)(函数的参数类型) = &函数名;这里的&函数名也可以直接写函数名。
(2)函数指针的调用和函数的调用差不多,函数是通过函数名(参数列表)进行调用,而函数指针是通过(*函数指针)(参数列表)进行调用的,这里也可以直接使用 函数指针(参数列表)进行调用。
(3)函数指针调用的时候*其实没有意义,但是加上之后更加容易理解函数指针。因此加不加*都可以
8.2 两个复杂的程序代码
int main() {
( *( void(*)() ) 0 )();
}
(1) void(*)():是一个函数指针类型;
(2) (void(*)())0:将int类型的变量0强制类型转换成无返回值的函数指针类型;
(3)*(void(*)())0: * 是指针的标志;
(4)(*(void(*)())0)():进行函数指针的调用 ;
总结:以上代码试一次函数调用,调用的是0作为地址处的函数。
1. 把0强制类型转换成:无参的、返回值是void的函数的地址;
2. 调用0地址处的这个函数。
typedef void(*pf_t)(int);//把void(*)int类型重命名为pf_t类型;
int main() {
void(*signal(int,void(*)(int)))(int);
pf_t signal(int, pf_t);
return 0;
}
对于void( *signal( int , void(*)(int) ) )(int);的分析
(1)void(*)(int)——函数指针类型;
(2)signal( int , void(*)(int) )——有一个signal函数,他的返回值是int类型和无返回值的函数指针类型;
(3)void( *signal( int , void(*)(int) ) )(int);————有一个signal函数指针,他的返回值是int类型和无返回值的函数指针类型;
总结:以上是一次函数声明,有一个函数指针signal,signal函数指针指向的函数的参数是int,返回值是一个指向的函数参数是int且返回值是void的函数指针。
以上代码在书写和阅读的时候非常繁琐,可以用typedef类型重定义进行书写,注意:
一般的类型重定义就是typedef 数据类型 重定义数据类型名,但此处的书写略有不同,此处的重定义数据类型名要写在(*)内。
8.3 函数指针的应用——计算器
#include<stdio.h>
void test() {
printf("---------------------------\n");
printf("---- 1.加法 ----\n");
printf("---- 2.减法 ----\n");
printf("---- 3.乘法 ----\n");
printf("---- 4.除法 ----\n");
printf("---- 5.退出 ----\n");
printf("---------------------------\n");
}
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 Cal(int (*cal)(int,int)) {
int a = 0, b = 0;
printf("请输入你要进行计算的两个整数:");
scanf("%d %d", &a, &b);
printf("\n");
printf("计算的结果为:%d\n", cal(a,b));
}
int main() {
test();
int x;
printf("请输入你要进行的运算法则序号:");
scanf("%d", &x);
printf("\n");
do {
switch (x) {
case 1:
Cal(&Add);
break;
case 2:
Cal(&Sub);
break;
case 3:
Cal(&Mul);
break;
case 4:
Cal(&Div);
break;
case 0:
printf("退出计算器\n");
break;
default:
printf("输入错误,请重新输入");
break;
}
} while (x != 0);
return 0;
}
8.4 函数指针数组(转移表)
在进行上述计算器的书写的时候,我们既然已经学过了指针数组,也学过了数组指针,还知道了函数指针,那么函数指针作为指针,能不能编写一个一函数指针为元素的数组呢,这样进行计算器程序的编写的时候,就可以直接调用数组,而不需要使用switch语句进行调用,如此,使用函数指针数组可以对上述计算器进行简化。
函数指针数组的定义方式为:int (*pfArr[] ) ( int,int) = { };
注意:从数组指针与指针数组开始,定义的方式就变得看似很麻烦,但是实际上掌握了一定的技巧,还是比较简单的,这里可以停下来思考一下,定义有什么相同的的地方,通过自己的理解进行记忆。
#include<stdio.h>
void test() {
printf("---------------------------\n");
printf("---- 1.加法 ----\n");
printf("---- 2.减法 ----\n");
printf("---- 3.乘法 ----\n");
printf("---- 4.除法 ----\n");
printf("---- 0.退出 ----\n");
printf("---------------------------\n");
}
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 x = 0, a = 0, b = 0;
int (*pfArr[5])(int, int) = { 0,Add,Sub,Mul,Div };
do {
test();
printf("请输入你要进行的运算法则序号:");
scanf("%d", &x);
printf("\n");
if (x == 0) {
printf("退出计算器\n");
}
else if (x > 0 && x <= 4) {
printf("请输入你要进行计算的两个整数:");
scanf("%d %d", &a, &b);
printf("\n");
printf("计算的结果为:%d\n", pfArr[x](a, b));
}
else {
printf("输入错误,请重新输入");
}
} while (x != 0);
return 0;
}
8.5 指向函数指针数组的指针
int main() {
//函数指针数组
int (*pfArr[])(int, int) = { 0,Add,Sub,Mul,Div };
//指向函数指针数组的指针
int (*(*ppfArr)[5])(int, int) = &pfArr;
}
九、回调函数
9.1 回调函数
回调函数就是一个通过函数指针调用的函数。
如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数的时候,我们就说这个叫做回调函数。
回调函数不是由函数的实现方直接调用,而是在特定的时间或条件发生的时候由另外的一方调用的,用于对该事件或条件进行响应。(如8,3中的Cal函数,就是一个回调函数)
9.2 qsort函数的使用
(1)qsort——这个函数可以排序任意类型的数据——对应库函数:<stdlib.h>
void qsort(void* base, //你要排序的数据的起始位置
size_t num, //待排序的数据元素的个数
size_t width,//待排序的数据元素的大小(单位是字节)
int(*cmp)(const void* e1, const void* e2)//函数指针—指向自定义比较函数
);(2)void* 是一个无具体类型的指针,可以接受任意类型的地址,但同样因为void*是无具体类型的指针,所以不能直接进行解引用操作,也不能+-整数;如果想使用的话可以使用强制类型转换成对应类型的指针进行使用。
(3)对于int(*cmp)(const void* e1, const void* e2)函数指针指向的函数有以下规定:
①函数的返回:如果e1>e2则返回负数,e1=e2则返回0,e1<e2则返回正数;
②默认升序,如果需要降序的话则将e2放在前面,e1放在后面。
9.2.1 用qsort函数进行整数的升序降序
#include<stdio.h>
#include<stdlib.h>
int cmp1_int(const void* e1, const void* e2) {
return (*(int*)e2 - *(int*)e1);
}
int cmp2_int(const void* e1, const void* e2) {
return (*(int*)e1 - *(int*)e2);
}
int main() {
//降序
int arr1[] = { 0,1,2,3,4,5,6,7,8,9 };
int sz1 = sizeof(arr1) / sizeof(arr1[0]);
qsort(arr1, sz1, sizeof(arr1[0]), cmp1_int);
for (int i = 0; i < sz1; i++) {
printf("%d ", arr1[i]);
}
printf("\n");
//升序
int arr2[] = { 9,8,7,6,5,4,3,2,1,0 };
int sz2 = sizeof(arr2) / sizeof(arr2[0]);
qsort(arr2, sz2, sizeof(arr2[0]), cmp2_int);
for (int i = 0; i < sz2; i++) {
printf("%d ", arr2[i]);
}
return 0;
}
9.2.2 用qsort函数进行结构体数据排序
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
struct stu {
char name[20];
int age;
};
int cmp_stu_name(const void* e1, const void* e2) {
return strcmp(((struct stu*)e1)->name, ((struct stu*)e2)->name);
}
void print_stu(struct stu* s) {
printf("%s,%d\n",s->name,s->age);
}
int main() {
struct stu s[] = { {"zhangsan",15},{"lisi",16},{"wangwu",14} };
int sz = sizeof(s) / sizeof(s[0]);
qsort(s, sz, sizeof(s[0]), cmp_stu_name);
for (int i = 0; i < sz; i++) {
print_stu(&s[i]);
}
return 0;
}
9.3 冒泡排序
对C语言初阶,数组与操作符2.2节所提到的冒泡排序进行优化:
利用回调函数,模仿qsort函数编写冒泡排序实现所有类型数据的排序:(以整型数组排序举例)
#include<stdio.h>
void Swap(char* buf1, char* buf2, int width) {
for (int i = 1; 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)) {
for (int i = 0; i < sz - 1; i++) {
int flag = 1;
for(int j = 0; j < sz - i - 1; j++) {
if (cmp((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 == 1) {
break;
}
}
}
int cmp_int(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]);
bubble_sort(arr, sz, sizeof(arr[0]), cmp_int);
for (int i = 0; i < sz; i++) {
printf("%d ", arr[i]);
}
return 0;
}