C语言基础

常量和常量表达式的区别

#define N 4;又是常量,又是常量表达式,其在编译期预处理阶段就会直接替换
const int M = 5;只是常量,不是常量表达式 ,其是存储在一块内存区域之内的,但是存储的值不能改变
常量表达式:在编译期间就可以直接求值的表达式
常量:在运行期间不能改变的
常量表达式可以用于指定数组长度,还可以用于switch语句的标签

#define N 5
const int M = 4;
int main() {
	int arr[N];//正确
	int arr[M];//错误,M不是常量表达式
	
	int i;
	scanf("%d",&i);
	switch (i) {
		case N://正确
			printf("N = %d\n",N);
			break;
		case M://错误,M不是常量表达式
			printf("M =  %d\n",M);
			break;
	}
}

标准输入输出
C语言基础_第1张图片
scanf本质上是一个“模式匹配函数”,试图把输入的字符与转换说明进行匹配,从左到右一次处理转换说明,如果成功,则继续处理后面的字符串,如果失败,则立即返回,返回值表示处理转换说明成功的个数

int main() {
	int i,f;
	int k = scanf("%d %d",&i,&f);//输入的必须为int类型
	float d;
	//尽量避免下面这种写法
	k = scanf("i = %d,d = %f",&i,&d);//输入格式必须为--- i = 17,d = 3,1415---这样的格式,否则字符串匹配不上也我无法输入
	return 0;
}

转换说明:
(1)表示匹配的规则
(2)表示将字符数据转换成对应的二进制数据

格式串:普通字符,其他字符(精确匹配),空白字符(匹配任意多个空白字符,包括零个)

注意事项:
scanf匹配%d,%f(进行数值匹配的时候,会匹配前面的空白字符)

读写整数
%u 无符号十进制整数
%o 无符号八进制整数
%x 无符号十六进制整数
%d 有符号十进制整数
读写短整数,在u,o,x,d前面添加h(short)
读写长整数,u,o,x,d前面添加l
读写长长整形,在u,o,x,d前面添加ll

int main() {
	unsigned a;
	scanf("%u",&a);
	printf("%u\n",a);
	printf("%o\n",a);
	printf("%x\n",a );
	short s;
	scanf("%hd",&s);
	printf("%hd",s);
}

浮点数数据类型
浮点数包括float(4字节),double(8字节),long double(用于高精度计算中,一般用不到)

浮点数常量又多种表示方法,要么包含小数点,要么包含字母E(e)
浮点数常量默认是double,如果需要表示float ,在浮点数常量后买你添加字母F(f)

57.0
57.
5.70e1( 表示以10为底的数,表示5.70*10^1)
.57e2

读写浮点数

%f : float
%lf : double(注意l不能大写)
double i;
scanf("%lf",&i);
printf("%lf",&i);

字符数据类型
char类型大小为1个字节,并且使用ASCII编码表示,ASCII编码用7位表示128个字符(最高位都为0)
C语言基础_第2张图片
C语言把字符类型当作小的整数类型来使用,因此可以对字符执行算术运算和比较运算

int i = 'a';
char ch = 'A';
ch = ch + 1;
//将大写转换位小写
if(ch >= 'A' || ch <= 'Z') {
	ch = ch + 'a' - 'A';
}

不能直接输入的字符----转义字符
字符转义序列:

\a 警报,响铃
\b 回退
\f 换页
\n 换行
\r 回车
\t 水平制表符
\v 垂直制表符
\\ backslash
\? question mark
\' single quote
\" single quote

数字转义序列
八进制形式表示:以\开头,后面接最多3个八进制数 \o, \101 ‘A’
十六进制表示形式:以\x开头,后面接十六进制数字 \x0,\x41 ‘A’

printf("%c",'\101');//输出A
printf("%c",'\x41');//输出A

字符处理函数
character classification分类函数
需要导入头文件ctype.h
characet manipulation操作函数
C语言基础_第3张图片

读写字符
scanf/printf配合%c来读写字符
注意事项:%c不会忽略前面的空格字符,可以在%c前面添加一个空格来实现忽略前面的空格,这样的操作还可以忽略换行

scanf(" %c",&ch);

getchar(把字符写到stdin中)/putchar(把字符写到stdout中),效率比scanf/printf效率要高

ch = getchar();//读入字符到stdin
putchar(ch);//将字符传到stdout

布尔类型
c99中定义了布尔类型,在头文件中

#include 

bool 类型本质上是无符号整数类型
注意:给布尔类型变量复制,非零会得true,零会得到false

类型转换
1.为什么需要进行类型转换
计算机硬件只能对相同类型的数据运算,计算机是没有数据类型的概念的
2.何时会发生类型转换
给定多的数据类型与需要的数据类型不匹配时
3.如何进行类型转换
隐式类型转换(编译器做的类型转换,不需要程序员手动指明)

short ,char只要参与运算就会转换位int类型进行运算(整数提升)
intlong类型进行运算int类型会转换为long类型进行运算,
long类型和longlong类型运算long类型会转换位longlong类型,
longlong类型和float类型运算,longlong类型会转换为float类型
float类型和double类型运算,float类型会转换位double类型
注意:不要将有符号整数和无符号整数进行运算,因为他们会进行类型转换,就会使用补码进行运算,运算的结果可能就会出错

显示转换(强制类型转换):可以让程序员更精确的控制类型转换

//强制类型转换的好处
//1.使用显示转换实现计算浮点数的小数部分
double d = 3.14,f;
f = d - (int)d;
//2.增强代码的可读性,表明肯定会发生的转换
float = f = 3.14;
//...
int i = (int)f;
//3.对类型转换进行更精确的控制
int divided = 4,divisor = 3;
double = quo;
//quo = divided / divisor;//1.0
quo = (double)divided / divisor//1.3333333....

//4.避免溢出
long long millisPerday = 24*60*60*1000;//每天的秒
long long nanosPerday = (long long)24*60*60*1000*1000*1000;//每天的毫秒,在前面加上强制类型转换就不会出错
printf("%lld\n",nanosPerday/millisPerday);//没有加(long long)强制类型转换时输出-21,因为24,60,1000这些数据都是int类型的数据,虽然我们最终的结果没有超过longlong类型的长度,但是超过了int类型的长度,int类型进行运算会先将结果存储在int类型的长度变量里,再将其转换为longlong类型,因此变换之前就超过int类型的长度就会出先计算错误

Typedef
我们可以使用typedef给类型起别名
格式

typedef type_name alias;
typedef int Bool;//Bool为int数据类型的别名

(1)typedef和宏定义的区别
宏定义是在预处理阶段进行处理的(简单的文本替换),编译器是不能够识别宏定义的,因此如果宏定义中有错误编译器就不能给出一些友好提示;编译器能够识别typedef定义的别名,如果发生错误,就能给出一些友好提示信息
定义类型:使用typedef不要使用宏定义
(2)给类型起别名的好处
a.增加代码的可读性
b.增加代码的可移植性

sizeof运算符
在编译阶段就进行计算的,因此他是一个常量表达式,可以表示数组的长度
作用:计算某一类型的数据所占内存空间的大小(以字节为单位)

int i = 3;
//语法
sizeof(type_name);
int arr[sizeof(i)];//正确表示

运算符以及优先级
注意事项:
(1)+、-、*、/可以用于浮点数,但是%必须要求两个操作数都是整数
(2) 两个整数相除,其结果为整数
(3)a%b的结果可能为负,符号与a的符号相同,满足a%b = a - (a/b)*b
C语言基础_第4张图片

C语言基础_第5张图片

//取模
bool is_odd(int n) {
	return n%2 ==1;//错误的,如果n = -1,那么会返回true
}
bool is_odd_1(innt n) {
	return n%2 != 0;
}
//更高效算法
bool is_odd_2(int n) {
	return n & 0x1;//让n和1进行按位与,最后是结果是1那么就是奇数,结果为0那么就是偶数
}

位运算符

<<、>>、&、|、^、~
移位运算符
i<:就左移j位,在左边补0
i>>j:将i右移j位,若i为为无符号整数或者非负数,则左边补0,若i为负数,他的行为是由实现定义的,有的左边会补0,有的左边补1
为了代码的可移植,最好不要对有符号整数进行移位运算

//s左移两位
short s = 13;
printf("s << 2 = %d\n",s<<2);//52
//左移两位0000 0000 0000 1101   --->  0000 0000 0011 0100
//若没有发生溢出,左移J位,相当于乘以2^J

//s右移两位
short s1 = 13;
printf("s1 >> 2 = %d\n",s>>2);//3
// 0000 0000 0000 1101   --->   0000 0000 0000 0011
//右移j位,相当于除于2^j(向下取整)

按位运算符


short i = 3,j = 4;
//按位取反
~i(取反):0000 0000 0000 0011  ---> 1111 1111 1111 1100 (-4)
i&j(按位与):0000 0000 0000 0011 & 0000 0000 0000 0100  --->   0000 0000 0000 0000 (0)
i|j(按位或):0000 0000 0000 0011 | 0000 0000 0000 0100  --->   0000 0000 0000 0111 (7)
i^j(异或):0000 0000 0000 0011 ^ 0000 0000 0000 0100  --->   0000 0000 0000 0111 (7)
//异或性质:
a ^ 0 = a
a ^ a = 0
a ^ b =  b ^ a (交换律)
a^(b^c) = (a^b)^c (结合律) 
//1.如何判断一个整数是否为2的幂
//方法1
bool isPowerO2(unsigned int n) {
	unsigned int i = 1;
	while(i < n) {
		i <<=1;	
	}
	return i == n;
}
//方法2
bool is PowerOf2(unsigned int n) {
	return (n & n-1) == 0;
}
//2.的幂的二进制表示有神什么特征:只有一个1
// 0000 0000 0100 0000 & 0000 0000 0011 1111   --->  0000 0000 0000 0000 (0)

//2.给定一个不为零的整数,找出值为一且权重最低的位
//输入:0011 0100   输出::4
((n ^ n-1) + 1) >> 1
n & (-n)  : 0011 0100 (n) & 1100 1100 (-n) ---> 0000 0100 (4)

//3. 给定一个整数数组,里面的数都是成对的,只有一个数例外,请找出这个数
int arr[] = {1,2,3,4,5,4,3,2,1};
printf("%d\n",findSingleNumber(arr,9));

int findSingleNumber(int arr[],int a) {
	int singleNum = 0;
	for(int i = 0;i < n;i++) {
		singleNum ^= arr[i];
	}
	return singleNum;
}

数组
为什么在大多数语言中,数组的索引都是从0开始的?
原因是方便寻址
若索引从0开始:i_addr = base_addr + i*sizeof(element_type)
若索引从1开始:i_addr = base_addr + (i-1)*sizeof(element_type);每次寻址多执行一次减法运算
为什么数组的效率一般会优于链表?
(1)数组的内存空间是连续的,而链表的内存空间不连续,数组可以更好的利用CPU的cache(预读,局部性原理)
(2)数组只需要存储数据,链表不仅经要存储数据,还需要存储指针域,数组的内存使用率更高

数组的声明:int arr[size]
注意事项:size必须是整形的常量表达式,在编译期间能计算出数组的大小
数组的初始化:

#define SIZE(a) (sizeof(a)/sizeof(a[0]))//计算数组长度
int arr[10] = {0,1,2,3,4,5,6,7,8,9};
int arr[10] = {1,2,3};//其余䛾初始化为0,{1,2,3,0,0,0,0,0,0,0}
int aar[10] = {0};//将数组所有元素初始化为0
int arr[] = {1,2,3,4,5,6,7,8,9,10};//数组长度编译器自行推断,这里的长度是10

二维数组的初始化:
二维数组是以行优先的形式进行初始化的

int matrix[3][4] = {{1,2,3,4},{2,2,3,4},{3,2,3,4}};//
int matrix[3][4] = {1,2,3,4,2,2,3,4,3,2,3,4};//不建议
int matrix[3][4] = {0};
int matrix[][4] = {{1,2,3,4},{2,2,3,4},{3,2,3,4}};//编译器自行判断行的大小
//注意事项:不能省略列的大小

常量数组:

const int arr[10] = {0,1,2,3,4,5,6,7,8,9};//数组元素不能发生改变,存放静态数据

生成随机数:

srand((unsigned)time(NULL));//利用时间设置随机数种子
for(int i = 0;i < 10;i++) {
	printf("%d\n",rand());//rand默认随机数种子为1
}

一维数组作为参数传递的时候会退化为指向第一个元素的指针,好处
(1)可以避免数据复制,
(2)可以修改原始数组的值,
(3)函数调用会更加灵活
数组作为参数传递的时候会丢失类型信息,数组长度

//传递数组的时候将数组长度一并传递
int sum_arr(int arr[],int length);

二维数组在作为参数传递的时候不能省略列的信息,只能省略行的信息

int main() {
	int materix[3][4] = {{1,2,3,4},{2,2,3,4},{3,2,3,4}};
	printf("%d\n",sum_arr(materix,3));
	return 0;
}
int sum_arr(int materix[][4],int n){
	int sum = 0;
	for(int i = 0;i < n;i++) {
		for(int j = 0;j < 4;j++) {
			sum += materix[i][j];
		}
	}
	return sum;
}

如果我们要在main函数里面退出程序可以使用return语句,当我们不在 main函数里面但是想退出程序我们可以使用exit函数exit(EXIT-SUCCESS) 或者exit(0)—正常退出,exit(EXIT-FAILUE)或exit(1)—异常退出,

#define EXIT-SUCCESS 0
#define EXIT-FAILUE 1

指针

计算机最小的寻址单位:字节
变量的地址:变量第一个字节的地址
指针建档的来说,指针就是地址
指针变量:存放地址的变量,有时候把指针变量称为指针

声明指针时,需要指明基础类型

int *p;//p为变量名;变量的类型是int *,而不是int 

两个基本操作:取地址运算符&和解引用*
取地址运算符:int i = 1; int *p = &i;取出变量i的地址赋值给int *
解引用运算符:通过解引用运算符访问指针指向的对象 printf("%d\n",*p);//1
p相当于i的别名,修改p相当于修改i
i直接访问(访问一次内存)
*p间接访问(访问两次内存)

野指针
未初始化的指针或者是指向位置区域的指针
野指针会造成位置错误

//野指针
int *p;
int *q = 0x7f;

指针和数组

指针的算术运算
(1)指针加上一个整数
(2)指针减去一个整数
(3)两个指针相减(指向同一个数组里面的元素)
指针的算术运算是以元素的大小为单位的,而不是以字节为单位

int arr[10] = {0,1,2,3,4,5,6,7,8,9};
int* p = &arr[2];
int* q = p+3;
p += 6;
printf("*p = %d,*q = %d\n",*p,*q);//8,5
------------------------------------------------------------------------------

//指针的比较(两个指针指向同一个数组的元素)
// p - q > 0 <=> p > q
// p - q = 0 <=> p = q
// p - q < 0 <=> p < q
int* p = &arr[2];
int* q = &arr[5];
printf("p - q = %d\n",p - q);//-3
printf("q - p = %d\n",q - p);//3
------------------------------------------------------------------------------

//用指针处理数组
int sum = 0;
for(int* p = &arr[0]; p < &arr[10];p++){//&arr[10]只会计算arr[10]的地址,不会访问arr[10],因此不会发生数组越界
	sum += *p;
}
printf("sum = %d\n",sum);//45
------------------------------------------------------------------------------


int sum = 0;
int* p = &arr[0];
while(p < &arr[10]) {
	sum += *p++;
}
printf("sum = %d\n",sum);

*和++的组合
*p++,*(p++)表达式的值为*p,p自增
(*p)++表达式的值为*p,*p自增
*++p,*(++p)表达式的值为*(p+1),p自增
++*p,++(*p)表达式的值为*p+1,*p自增
*和–的组合和上面类似

数组名作可以作为指向索引为0的元素的指针

int arr[10] = {0,1,2,3,4,5,6,7,8,9};
*arr = 100;
*(arr +7) = 700;
//arr[10] = {100,1,2,3,4,5,700,7,8,9}
int sum = 0;
for(int*p = arr;p < arr+9;p++) {
	sum += *p;
}
sum = 0;
for(int i = 0;i < 10;i++) {
	sum += (arr+i);
}

指针也可以作为数组名来使用(可以对指针使用[]运算符) p[i] == *(p+i)

总结:指针和数组之间的关系:
(1)可以利用指针处理数组(指针的算术运算)
(2)数组名可以做指向该数组索引为0元素的指针
(3)指针也可以作为数组名(可以对指针使用[]运算符,p[i] == *(p+i)

字符串

C语言是通过字符数组存储字符串字面量的
字符串的初始化
C语言基础_第6张图片

C语言基础_第7张图片
C语言基础_第8张图片

读写字符串

写:printf + %s, puts---string

char name[] = "Allen";
printf("%s\n",name);
puts(name);//写完字符串后,会在后面添加额外的换行符  puts(name)等价于printf("%s\n",name)

读:scanf + %s,gets,gets_s

#define N = 100
char name[N];
scanf("%s",name);//读取规则:会跳过前面的空白字符,读取字符存入到数组中,知道遇到空白字符位置,然后会在后面添加空字符’\0‘
//注意事项
//(1)永远不会包含空白字符
//(2)sacnf不会检查数组越界
gets(name);//不会跳过前面的空白字符,读取字符存入到数组中,知道遇到换行符为止,然后再后面添加’\0‘
//注意事项:
//同样gets也不会检查数组越界
gets_s(name,n);//n表示数组的长度,所以最多能够读n-1个字符,第n个位空字符’\0‘,会检测数组越界,遇到换行符渡或者检测到数组越界就停止读入

字符串的库函数
(1)size_t strlen(const char* s)获取字符串的长度,不包含空字符’\0‘,使用const常量指明再strlen中不会修改s指向的内容,传入参数

printf("%d\n",strlen("abc"));//3
printf("%d\n",strlen(""));//0

(2)int strcmp(const char* s1,const char* s2)比较字符串,按照字典顺序比较s1和s2,“abc” > "acb","abc" >"abd","abc" < "abcd",
如果s1 > s2,则返回正数
如果s1 = s2,则返回零
如果s1 < s2,则返回负数

(3)char* strcpy(char* s1,const char* s2)【不安全的,因为不会检查数组越界】把s2指向的字符串复制到s1指向的数组中,不会检查数组越界,s1变量没有携带const关键字,表明通常会在函数中修改s1指向的内容,传入传出参数
char* strncpy(char* *dest,const char* src,size_t count)【安全的】指定目标数组中可以接收的大小

char s1[10];
strncpy(s1,"Hello",4);//s1 ==> Hell不会写入空字符'\0'
strncpy(s1,"Hello",6);//s1 ==>Hello \0
strncpy(s1,"Hello",8);//s1 ==>Hello \0 \0 \0 \0 后面会添加额外的空字符,直到写入8个字符

(4)char* strcat(char* dest,const char* src)将字符串src的内容追加到字符串dest的末尾,并返回dest(不会检查数组越界)

(5)char* strncat(char* dest,const char* src,size_t coant)会在dest末尾添加src中的coant个字符,因为strncat总是会写入‘\0’因此我们一般会这样调用strncat(s1,s2,sizeof(s1)-strlen(s1)-1),给空字符预留一个空间

char s1[10] = "Hello";
strcat(s1," world");//s1 ===>Hello' 'world \0
strncat(s1," world",2);//s1 ===>Hello' 'world \0
strncat(s1," world",7);//s1 ===>Hello' 'world \0

字符串惯用法
自己编写strlen函数

size_t my_strlen(const char* s) {
	size_t n;
	for(int n = 0; *s != '\0';s++) {
		n++;
	}
	return n;
}
//改进后
size_t my_strlen(const char* s) {
	char *p = s;
	while(*p++);
	return p-s-1;//s指向了空字符后面的字符
}
size_t my_strlen(const char* s) {
	char *p = s;
	while(*p){
		p++;
	}
	return p-s;
}

自己编写strcat函数

char* my_strcat(char* s1,const char* s2) {
	char* p = s1;
	while(*p) {
		p++;
	}
	while(*s2 != '\0') {
		*p = *s2;
		p++;
		s2++;
	}
	*p = '\0';
	return s1;
}
//改进后
char* my_strcat(char* s1,const char* s2) {
	char* p = s1;
	while(*p) {
		p++;
	}
	while(*p++ = *s2++);
	return s1;
}

字符串数组

char* planets[] = {"Mecury","Venus","Earth,"Mars","Jupitor","Saturn","Uranus","Neptune","Pluto"};

结构体

c语言的结构体相当于其他高级语言中的类,c语言只能在结构体中定义数据

//定义结构体
struct students{
	int id;
	char name[25];
	bool gender;
	int chinese;
}
int main() {
	//1.初始化结构体
	struct students t1 = {1,"liuyifei",false,100};
	struct students t2 = {2,"huasheng",true};//未被初始化的成员都会被赋值未0
	//2.对结构体的操作
	printf("name = %s\n",t1.name);
}
//为了避免拷贝数据,我们往往会传递一个指向结构体的指针
void printf_student(struct student_s* s){
	printf("%d %s %d %d\n",(*s).id,(*s).name,(*s).gender,(*s).chinese)
}
//C语言程序一般时通过指针去引用结构体的,C语言提供了一个特别的运算符->
void printf_student(struct student_s* s){
	printf("%d %s %d %d\n",s->id,s->name,s->gender,s->chinese)
}
//3.使用typede为结构体起别名
typedef struct students{
	int id;
	char name[25];
	bool gender;
	int chinese;
} student_s;//别名student_s

注意事项:当结构体作为参数或者返回值时,会拷贝整个结构体中的数据

指针的高级应用

(1)动态内存分配
在头文件定义了三个动态内存分配的函数(在堆上分配内存空间)

  1. void* malloc(size_t size);分配size个字节的内存,不会对分配的内存块清零;若分配不成功,返回空指针
  2. void* calloc(size_t num,size_t size);num个元素分配内存空间,每个元素的大小为size个字节,并对内存块清零;若分配不成功,返回空指针
  3. void* realloc(void* ptr,size_t size);调整先前分配内存块的大小,如果分配成功,返回指向新内存
    注意事项:ptr应该指向先前使用动态内存分配函数分配的内存块

空指针:不指向任何对象的指针(用宏NULL表示,其值为0)

#include 
#include 
 int main(void) {
 	char* s1 = "Hello ";
 	char* s2 = "world! ";
 	char* s = my_strcat(s1,s2);
 	puts(s1); //Hello 
 	puts(s2); //world!
 	puts(s);//Hello world!
 	return 0;
 }
 char* my_strcat(const char* s1,const char* s2){
	char* s = (char *)malloc(strlen(s1) + strlen(s2) + 1);
	if(s == NULL) {//未分配成功
		//做错误处理
		return NULL;
	}
	strcpy(s,s1);//将s1赋值到s中
	strcat(s,s2);//将s2复制到s末尾
	return s;
 }
 //以上代码没有释放内存空间

如果申请的内存空间没有释放,就可能造成内存泄漏现象
分配的内存没有指针指向它,导致不能访问的内存被称为垃圾
如果程序中存在垃圾,这种现象称为内存泄漏
如何避免内存泄漏?
使用void free(void* ptr);进行内存释放,ptr必须是之前申请的内存空间的指针

(2)指向指针的指针(二级指针)

#include 
#include 
typedef struct node_s{
	int val;
	struct node_s* next; 
} Node;
void add_to_list(Node** ptr_list,int val);
int main(){
	Node* list = NULL:
	add_to_list(&list,1);
	add_to_list(&list,2);
	add_to_list(&list,3);
	add_to_list(&list,4);
	return 0;
}
void add_to_list(Node** ptr_list,int val){
	Node* newNode = (Node*)malloc(sizeof(Node));
	if(newNode === NULL) {
		printf("Error:malloc failed in add_to_list.\n");
		exit(1);
	}
	//头插法
	newNode->val = val;
	newNode->next = *ptr_list;
	*ptr_list = newNode;
}

(3)指向函数的指针(函数指针)

#include 
#include 
#define PI 3.1415926
double average(double (*f)(double),double a,double b);
int main(void){
	double avg = average(sin,0,PI);
	printf("%lf",avg);
	return 0;
}
double average(double (*f)(double),double a,double b){
	return (*f)((a+b)/2);
}
//简便写法
//double average(double f(double),double a,double b){
//	return f((a+b)/2);
//}

qsort
void qsort(void *ptr,size_t count,size_t sizre,int (*comp)(const void *,const void *))
ptr---->指向要排序的数组
count---->数组中元素的个数
size---->元素的大小
comp---->比较函数,如果第一个参数大于第二个参数返回值正值,如果第一个参数等于第二个参数返回零,如果第一个参数小于第二个参数,返回负值
可以对任何类型的数组进行排序,不管元素类型是什么

#include 
#include 
typedef struct student_s{
	int number;
	char name[25];
	int chinese; 
	int math;
	int english;
} Studet;
int compare(const void* p1,const void* p2);

#define SIZE(a) (sizeof(a)/sizeof(a[]0]))
int main(void) {
	Student students[5] = {{1,"liuyifei",100,100,100},{2,"wangyuyan",99,100,100},{3,"zhaolinger",100,99,100},{4,"xiaolongnv",100,100,99},{5,"baixiuzhu",100,100,99}};
	qsort(students,SIZE(students),sizeof(Student),compare);
	return 0;
}
//比较规则:总分从高到低,语文成绩(高-->低)数学成绩(高-->低),英语成绩(高-->低),姓名(字典顺序从小到大进行比较)
int compare(const void* p1,const void* p2) {
	Student* s1 = (Student*)p1;
	Student* s2 = (Student*)p2;
	int total1 = s1->chinese + s1->english + s1->math;
	int total2 = s2->chinese + s2->english + s2->math;
	if(total1 != total2) {
		return total2 - total1;
	}
	if(s1->chinese != s2->chinese){
		return s2->chinese - s1->chinese;
	}
	if(s1->math != s2->math){
		return s2->math - s1->math;
	}
	if(s1->english != s2->english){
		return s2->english - s1->english;
	}
	return strcmp(s1->name,s2->name);
}

你可能感兴趣的:(C语言,c语言,算法,数据结构)