C语言进阶——数据类型与指针

一、数据类型介绍

数据类型名称大小打印
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次方。

举例说明:

  1. 十进制的5.75表示为二进制小数为:101.11(整数部分还是按照正常形式,注意小数部分,小数点后第一位表示2的-1次方,也就是0.5,所以小数部分为11,表示0.5+0.25)。
  2. 进行科学计数法表示:1.0111*2^2,因为是正数,所以符号位是0;
  3. 因此5.75的科学计数法表示为: (-1)^0   *   1.0111   *   2^2
  4. 对于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;
}
  • 26
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值