常量和常量表达式的区别
#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;
}
}
标准输入输出
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语言把字符类型当作小的整数类型来使用,因此可以对字符执行算术运算和比较运算
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操作函数
读写字符
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
类型进行运算(整数提升)
int
和long
类型进行运算int
类型会转换为long
类型进行运算,
long
类型和longlong
类型运算long
类型会转换位longlong
类型,
longlong
类型和float
类型运算,longlong
类型会转换为floa
t类型
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
//取模
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)
)
读写字符串
写:
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)动态内存分配
在头文件
定义了三个动态内存分配的函数(在堆上分配内存空间)
void* malloc(size_t size);
分配size
个字节的内存,不会对分配的内存块清零;若分配不成功,返回空指针void* calloc(size_t num,size_t size);
为num
个元素分配内存空间,每个元素的大小为size
个字节,并对内存块清零;若分配不成功,返回空指针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);
}