常量和常量表達式的區別
#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
類型會轉換為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
:就左移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---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)動態內存分配
在頭文件<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);
}