常量和常量表达式的区别
#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中定义了布尔类型,在<stdbool.h>头文件中
#include <stdbool.h>
bool 类型本质上是无符号整数类型
注意:给布尔类型变量复制,非零会得true,零会得到false
类型转换
1.为什么需要进行类型转换
计算机硬件只能对相同类型的数据运算,计算机是没有数据类型的概念的
2.何时会发生类型转换
给定多的数据类型与需要的数据类型不匹配时
3.如何进行类型转换
隐式类型转换(编译器做的类型转换,不需要程序员手动指明)
short ,char只要参与运算就会转换位int类型进行运算(整数提升)
int和long类型进行运算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


//取模
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:就左移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语言是通过字符数组存储字符串字面量的
字符串的初始化



读写字符串
写:
printf + %s,puts---stringchar 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)动态内存分配
在头文件<stdlib.h>定义了三个动态内存分配的函数(在堆上分配内存空间)
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 <stdio.h>
#include <stdlib.h>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 <stdio.h>
#include <stdlib.h>
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 <stdio.h>
#include <math.h>
#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 <stdio.h>
#include <string.h>
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);
}