1、define宏定義以#號開頭的都是編譯預處理指令,它們不是C語言的成分,但是C程序離不開它們,#define用來定義一個宏,程序在預處理階段將用define定義的來內(nèi)容進行了替換。因此在程序運行時,常量表中并沒有用define定義的常量,系統(tǒng)不為它分配內(nèi)存。
define定義的常量,預處理時只是直接進行了替換,它用來將一個標識符定義為一個字符串,該標識符被稱為宏名,被定義的字符串稱為替換文本。因此在編譯時它不對宏的定義進行檢查,作用域不影響對常量的訪問 。它的常量值只能是字符串或數(shù)字。
該命令有兩種格式:一種是簡單的常量宏定義, 另一種是帶參數(shù)的宏定義。
不帶參數(shù)的宏:#define《 名字 》《 值 》
要注意,沒有結(jié)尾的分號,因為不是C的語句,名字必須是一個單詞,值可以是各種東西,宏定義是用宏名來表示一個字符串,在宏展開時又以該字符串取代宏名,這只是一種簡單的代換,字符串中可以含任何字符,可以是常數(shù),也可以是表達式,預處理程序?qū)λ蛔魅魏螜z查。如有錯誤,只能在編譯已被宏展開后的源程序時發(fā)現(xiàn)。
注意。宏定義不是說明或語句,在行末不必加分號,如加上分號則連分號也一起置換。宏定義其作用域為宏定義命令起到源程序結(jié)束。如要終止其作用域可使用#undef命令
帶參數(shù)的宏 :
像函數(shù)的宏,一般的定義形式 :帶參宏定義的一般形式為:「#define 宏名」(形參表)字符串,也是沒有結(jié)尾的分號,可以帶多個參數(shù) ,#define NB(a,b)((a)》(b)?(b):(a)), 也可以組合(嵌套)使用其他宏,注意 帶參數(shù)宏的原則 一切都要有括號,參數(shù)出現(xiàn)的每個地方都要有括號。帶參數(shù)的宏在大型的程序的代碼中使用非常普遍,在#和##這兩個運算符的幫助下可以很復雜,如“產(chǎn)生函數(shù)”,但是有些宏會被inline函數(shù)代替(C++的函數(shù))
使用宏好處:
“提高運行效”。定義的標識符不占內(nèi)存,只是一個臨時的符號,預編譯后這個符號就不存在了。在簡單的程序使用帶參數(shù)的宏定義可完成函數(shù)調(diào)用的功能,又能減少系統(tǒng)開銷,提高運行效率。正如C語言中所講,函數(shù)的使用可以使程序更加模塊化,便于組織,而且可重復利用。
“方便程序的修改”。使用宏定義可以用宏代替一個在程序中經(jīng)常使用的常量。注意,是“經(jīng)常”使用的。這樣,當需要改變這個常量的值時,就不需要對整個程序一個一個進行修改,只需修改宏定義中的常量即可。且當常量比較長時,使用宏就可以用較短的有意義的標識符來代替它,這樣編程的時候就會更方便,不容易出錯。因此,宏定義的優(yōu)點就是方便和易于維護。
//例子:/求球的體積#include《stdio.h》#include《math.h》//對于半徑為 r 的球,其體積的計算公式為 V =4/3*Π*r^3 //這里取Π為3.14//這里給定r的值,求V#define PI 3.14int main(void) {
double r;
scanf(“%lf”, &r);
double sum = 0;
sum = (4.0 / 3.0) * PI*pow(r, 3);//這里用PI替換掉了 3.14
printf(“%f.2”, sum);
}
#include《stdio.h》//合例子:輸入數(shù)字查看是星期幾int main(void) {
enum week {Mon = 1, Tue, Wed, Thu, Fri, Sat, Sun}today;
//在這里我們給Mon賦值了一,后面Tue以后的都相應加一賦值
scanf(“%d”, &today);
switch (today) {
case Mon: puts(“Monday”); break;
case Tue: puts(“Tuesday”); break;
case Wed: puts(“Wednesday”); break;
case Thu: puts(“Thursday”); break;
case Fri: puts(“Friday”); break;
case Sat: puts(“Saturday”); break;
case Sun: puts(“Sunday”); break;
default: puts(“no day”);
}
return 0;
}
2、enum枚舉枚舉型是一個集合,集合中的元素(枚舉成員)是一些命名的整型常量,元素之間用逗號,隔開。它是一種用戶定義的數(shù)據(jù)類型,它用關(guān)鍵字enum以如下語法來聲明,:enum 枚舉類型名字,{名字0,。。。,名字n};第一個枚舉成員的默認值為整型的0,后續(xù)枚舉成員的值在前一個成員上加1 (當然這個是可以自定義成員值的)
枚舉類型名字通常并不真的使用,要用的是在它大括號里邊的名字,因為它們就是常量符號,它們的類型是int,值則是依次從零到n,如 enum week { Monday,Tuoesday,Wedenday}; 就創(chuàng)建了三個常量,Monday的值是0,Tuoesday是1,Wedenday是2,當需要一些可以可以排列起來的常量值的時候,定義枚舉的意義就是給了這些常量名字。
雖然枚舉類型可以當類型使用,但是實際上并不常用,但是如果是有意是排比名字,用枚舉比宏定義方便,枚舉比用好些,因為枚舉有int類型,在C 語言中,枚舉類型是被當做 int 或者 unsigned int 類型來處理的,既然枚舉也是一種數(shù)據(jù)類型,所以它和基本數(shù)據(jù)類型一樣也可以對變量進行聲明,枚舉也可以用typedef關(guān)鍵字將枚舉類型定義成別名,并利用該別名進行變量聲明
注意:
1、同一個程序中不能定義同名的枚舉類型,不同的枚舉類型中也不能存在同名的命名常量
2、枚舉成員)是「常量」而不是變量,這個一定要搞清楚,因為枚舉成員的是常量,所以不能對它們賦值,只能將它們的值賦給其他的變量
3、枚舉類型的定義和變量的聲明分開:如果對枚舉型的變量賦整數(shù)值時,需要進行類型轉(zhuǎn)換
enum DAY { MON=1, TUE, WED, THU, FRI, SAT, SUN };//枚舉常量的值是可以在這里進行自定義的 MON=1//基本數(shù)據(jù)類型的賦值 :
int a, b, c;
a = 1;
b = 2;
c = 3;
//使用枚舉賦值 :
enum DAY yesterday, today, tomorrow;//枚舉定義變量
yesterday = MON;
today = TUE;
tomorrow = WED;
// today = (enum DAY) (yesterday + 1);//強制類型轉(zhuǎn)換// tomorrow = (enum DAY) 30; //強制類型轉(zhuǎn)換//tomorrow = 3; //錯誤
枚舉在用switch-case結(jié)構(gòu)中使用非常方便。
//綜合例子:輸入數(shù)字查看是星期幾#include 《stdio.h》//枚舉是define的代替 它是一個集合 //和switch連用很方便int main() {
enum week { Mon = 1,Tues, Wed, Thurs, Fri, Sat, Sun } day; //day可放到這
//enum week day;定義
scanf(“%d”, &day);
switch (day) {
case Mon: puts(“Monday”); break;//1 puts代替printf輸出字符串
case Tues: puts(“Tuesday”); break;// 2
case Wed: puts(“Wednesday”); break;// 3
case Thurs: puts(“Thursday”); break;// 4
case Fri: puts(“Friday”); break;// 5
case Sat: puts(“Saturday”); break;// 6
case Sun: puts(“Sunday”); break;// 7
default: puts(“Error!”);
}
return 0;
}
枚舉型是預處理指令#define的替代,枚舉和宏其實非常類似,宏在「預處理階段」將名字替換成對應的值,枚舉在「編譯階段」將名字替換成對應的值,其中一個枚舉常量的占的字節(jié)數(shù)為4個字節(jié),恰好和int類型的變量占的字節(jié)數(shù)相同
3、struct 結(jié)構(gòu)體struct即結(jié)構(gòu)體,C程序中經(jīng)常需要用相關(guān)的不同類型的數(shù)據(jù)來描述一個數(shù)據(jù)對象。例如,描述學生的綜合信息時,需要使用學生的學號、姓名、性別等不同類型的數(shù)據(jù)時,像這種數(shù)據(jù)類型總是在一起出現(xiàn),那么我們不如把這些變量裝入同一個“文件夾”中,這時用的關(guān)鍵字struct聲明的一種數(shù)據(jù)類型就是表示這個“文件夾”的使用。
那么在說明和使用之前必須先定義它,也就是構(gòu)造它。如同在說明和調(diào)用函數(shù)之前要先定義一樣。結(jié)構(gòu)體是一種集合,它里面包含了多個變量或數(shù)組,它們的類型可以相同,也可以不同,每個這樣的變量或數(shù)組都稱為結(jié)構(gòu)體的成員,結(jié)構(gòu)體也是一種數(shù)據(jù)類型,它由程序員自己定義,可以包含多個其他類型的數(shù)據(jù),成員又稱為成員變量,它是結(jié)構(gòu)體所包含的若干個基本的結(jié)構(gòu)類型,必須用“{}”括起來,并且要以分號結(jié)束,每個成員應表明具體的數(shù)據(jù)類型,成員一般用名字訪問。
結(jié)構(gòu)體和數(shù)組類似,也是一組數(shù)據(jù)的集合,整體使用沒有太大的意義。數(shù)組使用下標[ ]獲訪問元素,結(jié)構(gòu)體使用點號。訪問單個成員。通過這種方式可以獲取成員的值,也可以給成員賦值
數(shù)組:a[0]=10; 結(jié)構(gòu)體:today.day (指針結(jié)構(gòu)體用-》訪問) 結(jié)構(gòu)體的成員可以包含其他結(jié)構(gòu)體,也可以包含指向自己結(jié)構(gòu)體類型的指針,而通常這種指針的應用是為了實現(xiàn)一些更高級的數(shù)據(jù)結(jié)構(gòu)如鏈表和樹等。
聲明定義結(jié)構(gòu):
struct關(guān)鍵字+結(jié)構(gòu)體的標志名+大括號里邊是成員+}后面的聲明此結(jié)構(gòu)變量+末尾分號,一般有這些:
struct week{定義一 struct{定義二 struct week {定義三
int x; int x; int x;
char y; char y; int y;
}; }p1,p2;//在這里聲明變量 }p1,p2;
//p1和p2都是一種無名結(jié)構(gòu), //常用的一種結(jié)構(gòu)定義聲struct week p1,p2; // 里邊有X和y 訪問一樣用。 明形式 //聲明變量p1,p2,里邊都是week的值 //里邊有x和y的值 //用。訪問 :p1.x p2.x
// p1.y, p2.y
對于第一和第三種形式,都聲明了結(jié)構(gòu)名week,但是第二種沒有聲明結(jié)構(gòu)名,只是定義了兩個結(jié)構(gòu)變量,
這種叫無名結(jié)構(gòu)
無名結(jié)構(gòu): 可以定義無名結(jié)構(gòu)體類型的變量。編譯器對無名結(jié)構(gòu)體的處理是隨機生成一個不重復的變量名。
無名結(jié)構(gòu)的定義方式就是定義無名結(jié)構(gòu)體時必須定義該結(jié)構(gòu)體類型的至少一個變量。
優(yōu)點:無名結(jié)構(gòu)體的妙用就是可以避免相同類型的結(jié)構(gòu)體的重復定義,
這樣可以對每一個具體類型的隊列都可以定義一個結(jié)構(gòu)體來管理該隊列的頭尾指針,
即使定義多個相同具體類型的隊列也不會引發(fā)重復定義的編譯錯誤。這樣定義了兩個隊列,
其元素類型均為int類型,同時各得到了一個維護隊列頭尾指針的結(jié)構(gòu)體
缺點:這里定義了一個無名的結(jié)構(gòu)體,同時聲明了三個此種類型的變量。
但是,因為沒有名字,我們在這句之后,無法內(nèi)再定義與那三種變量相同類型的變量了。
除非你再容次去定義一個這樣的相同的結(jié)構(gòu)體類型。
還有一個重要的原因就是沒有辦法在其他位置定義我們所需要的結(jié)構(gòu)體變量,
每次需要新定義結(jié)構(gòu)體變量的時候都必須要找到最開始結(jié)構(gòu)體代碼書寫的位置才能定義新的結(jié)構(gòu)體
所以實際編程中無名結(jié)構(gòu)并不常用
注意:1、結(jié)構(gòu)體本身并不會被作為數(shù)據(jù)而開辟內(nèi)存,真正作為數(shù)據(jù)而在內(nèi)存中存儲的是這種結(jié)構(gòu)體所定義的變量。
2、先聲明結(jié)構(gòu)體類型,再定義該類型的變量,聲明結(jié)構(gòu)體類型,不分配空間定義結(jié)構(gòu)體類型變量,就要分配內(nèi)存空間
3、量使用占為少的類型,如,在可能的時候使用short代替int,「按數(shù)據(jù)類型本身占用的位置從大到小排」
4、除了可以對成員進行逐一賦值,也可以在定義時整體賦值:p1={struct week}{5,10}; 相當于 p1.x=5,p1.y=10; p1=p2 表示 p1.x=p2.x , p1.y=p2.y; 不過整體賦值僅限于定義結(jié)構(gòu)體變量的時候,在使用過程中只能對成員逐一賦值 5、結(jié)構(gòu)體變量不能相加,相減,也不能相互乘除,但結(jié)構(gòu)體可以相互賦值,也就是說,可以將一個結(jié)構(gòu)體變量賦值給另一個結(jié)構(gòu)體變量。但是前提是這兩個結(jié)構(gòu)體變量的結(jié)構(gòu)體類型必須相同
結(jié)構(gòu)體的運算:要訪問整個結(jié)構(gòu),直接用結(jié)構(gòu)變量的名字,對于整個結(jié)構(gòu),可以做賦值,取地址,也可以傳遞給函數(shù)參數(shù)
結(jié)構(gòu)體數(shù)值
嵌套的結(jié)構(gòu)體:
結(jié)構(gòu)體相互引用:
一個結(jié)構(gòu)體A中包含一個或多個與結(jié)構(gòu)體B相關(guān)的成員, 且結(jié)構(gòu)體B中也包含一個或多個與結(jié)構(gòu)體A相關(guān)的成員稱為結(jié)構(gòu)體的互引用。 但是要注意:如果已經(jīng)定義了兩個結(jié)構(gòu)A和B ,在定義結(jié)構(gòu)體A的成員b時,結(jié)構(gòu)體B對A還未可見,故此時編譯器會報數(shù)據(jù)類型B未定義 解決的辦法是使用不完整聲明:
strcut A;//不完整聲明
strcut B;//不完整聲明
strcut _A{ strcut _B{
int x; int x;
int y; int y;
struct _B a; struct _A b; //在結(jié)構(gòu)B中定義了一個名為b的和A結(jié)構(gòu)一樣類型的結(jié)構(gòu)變量
//其中可以用點訪問 A.a.x B.b.x
}A; }B;
//但是注意這種方式犯了一個和上面第一個嵌套結(jié)構(gòu)的錯誤,就是結(jié)構(gòu)體A和B都是直接包含了對方,
正確的用法還是使用指針:
strcut _A{ strcut _B{
int x; int x;
int y; int y;
struct _B *a; struct _A *b; //在結(jié)構(gòu)B中定義了一個名為b的和A結(jié)構(gòu)一樣類型的結(jié)構(gòu)指針
//其中指針要用-》訪問 A.a-》x B.b-》x
}A; }B;
//但是注意這種方式犯了一個和上面第一個嵌套結(jié)構(gòu)的錯誤,就是結(jié)構(gòu)體A和B都是直接包含了對方,正確的用法還是使用指針:
strcut _A{ strcut _B{
int x; int x;
int y; int y;
struct _B *a; struct _A *b; //在結(jié)構(gòu)B中定義了一個名為b的和A結(jié)構(gòu)一樣類型的結(jié)構(gòu)指針
//其中指針要用-》訪問 A.a-》x B.b-》x
}A; }B;
//所以使用互引用要注意:至少有一個結(jié)構(gòu)必須在另一個結(jié)構(gòu)體中以指針的形式被引用。
結(jié)構(gòu)體函數(shù)與函數(shù)參數(shù)
結(jié)構(gòu)體做函數(shù)形參:
整個結(jié)構(gòu)可以作為參數(shù)的值傳入函數(shù),這時候是在函數(shù)內(nèi)新建一個結(jié)構(gòu)變量,并復制調(diào)用者結(jié)構(gòu)的值,也可以返回一個值,這和數(shù)組完全不同 用結(jié)構(gòu)體變量作實參時,采取的也是“值傳遞”方式,將 結(jié)構(gòu)體變量所占的內(nèi)存單元的內(nèi)容(結(jié)構(gòu)體變量成員列表) 全部順序傳遞給形參,這里形參也得是結(jié)構(gòu)體變量。
#include《stdio.h》typedef struct _node {
int n;
char a[100];
}NODE;
void add(NODE a);//這種形式只是用來做值的傳遞int main(void) {
//以傳值方式傳遞結(jié)構(gòu)需要對整個結(jié)構(gòu)做一份拷貝
NODE t;
scanf(“%d %d”, &t.a[0], &t.n);//輸入1 3
printf(“1-%d %d
”,t.a[0],t.n);//輸出 1 3
add(t);
printf(“3-%d %d
”, t.a[0], t.n);//輸出1 3//也就是說在add函數(shù)里邊做修改根本就影響不了主函數(shù)這邊的值
}
void add(NODE a) {
a.a[0] = 100;//在這里能接受到NODE結(jié)構(gòu)里邊的成員
a.n = 666;
printf(“2-%d %d
”, a.a[0], a.n);//輸出100 666
}
****//解決辦法是用指針(也是經(jīng)常用的方式):****#include《stdio.h》typedef struct _node {
int n;
char a[100];
}NODE;
int add(NODE a);//這種形式只是用來做值的傳遞int main(void) {
//以傳值方式傳遞結(jié)構(gòu)需要對整個結(jié)構(gòu)做一份拷貝
NODE t;
scanf(“%d %d”, &t.a[0], &t.n);//輸入1 3
printf(“1-%d %d
”,t.a[0],t.n);//輸出 1 3
add(&t);//這里傳進去的是t的地址
printf(“3-%d %d
”, t.a[0], t.n);//輸出100 666//傳進去的是地址,所以就可以達到訪問同一個變量的操作
}
int add(NODE *) {//定義一個結(jié)構(gòu)指針
a.a[0] = 100;//在這里能接受到NODE結(jié)構(gòu)里邊的成員
a.n = 666;
printf(“2-%d %d
”, a.a[0], a.n);//輸出100 666
return a;//這里返回的是指針 所以能達到訪問主函數(shù)里邊調(diào)用的值
//使用指針才可以用返回值
}
//常用的方式
另一種做法
結(jié)構(gòu)體做函數(shù):
/*上面的第一個的方案,把一個結(jié)構(gòu)傳入了函數(shù),然后在函數(shù)中操作,但是沒有返回回去
問題在于傳入函數(shù)的是外面那個結(jié)構(gòu)的克隆體,而不是指針,傳入結(jié)構(gòu)和傳入數(shù)組是不同的,
解決辦法是在這個輸入函數(shù)中,在里邊創(chuàng)建一個臨時的結(jié)構(gòu)變量,然后把這個結(jié)構(gòu)返回給調(diào)用者*/#include《stdio.h》typedef struct _node {
int x;
int y;
}NODE;
struct _node add();//定義結(jié)構(gòu)類型的函數(shù)int main(void) {
NODE a;
a.x = 0;
a.y = 0;
printf(“1-%d %d
”, a.x, a.y);// 0 0
a = add();//函數(shù)調(diào)用 /把n的值又返回到a
printf(“3-%d %d
”, a.x, a.y);//所以在這里的時候值已經(jīng)被改變
return 0;
}
struct _node add() {
NODE n;
scanf(“%d”, &n.x);//輸入1 3
scanf(“%d”, &n.y);
printf(“2-%d %d
”, n.x, n.y);//在這里的時候賦值就成功了
//return n;//把n的值帶回出去
}
//這種方法也能達到“改變“的效果,但是往往開銷內(nèi)存較大,所以一般情況都是使用指針比較方便
用結(jié)構(gòu)體變量名作參數(shù),這種傳遞方式是單向的,如果在執(zhí)行被調(diào)函數(shù)期間改變了形參(也是結(jié)構(gòu)體變量)的值,該值不能返回主調(diào)函數(shù),這往往造成使用上的不便,因此一般少用這種方法。
和本地變量一樣。在函數(shù)內(nèi)部聲明的結(jié)構(gòu)只能在函數(shù)內(nèi)部使用,所以通常在函數(shù)外部聲明一個結(jié)構(gòu)類型的,這樣就可以被多個函數(shù)所使用
//結(jié)構(gòu)做函數(shù)參數(shù)例子 (輸入今天計算明天)#include《stdio.h》#include《stdbool.h》//利用布爾數(shù)據(jù)類型struct date {
int year;
int month;
int day;
};
bool If(struct date p);//判斷是否是閏年int number(struct date c);//判斷是否是此月最后一天int main(void) {
struct date today,tomorrow;
printf(”年-月-日
“);
scanf(”%d %d %d“, &today.year, &today.month, &today.day);
//前面兩個判斷 是否此月最后一天 是否此年此月最后一天
if (today.day==number(today)&&today.month!=12) {//首月1號
tomorrow.day = 1;
tomorrow.month =today.month+1;
tomorrow.year = today.year;
}
else if (today.day == number(today) && today.month == 12) {//下一年
tomorrow.day = 1;
tomorrow.month = 1;
tomorrow.year =today.year+1;
}
else {
tomorrow.day =today.day+1;
tomorrow.month = today.month;
tomorrow.year = today.year;
}
printf(”明天是%d-%d-%d
“, tomorrow.year, tomorrow.month, tomorrow.day);
return 0;
}
int number(struct date c)//這里的形參接收的today結(jié)構(gòu)體數(shù)據(jù)
{
int day;
const int a[12] = { 31,28,31,30,31,30,31,31,30,31,30,31 };//這個月最大的天數(shù)
if (c.month==22&&If(c)) {//查看是否是二月并且是潤年
day = 29;//是潤年
}
else {
day = a[c.month - 1];
}
return day;
}
bool If(struct date p) {//這里的形參接收的today結(jié)構(gòu)體數(shù)據(jù)
//潤年的特點,能被4整除,但不能被100整數(shù),能被100整除,但是不能被400整除
if (p.year % 4 == 0 && p.year / 100 != 0 || p.year % 400 == 0) {
return true;
}
else {
return false;
}
}
//結(jié)構(gòu)體做函數(shù)例子 (計算下一秒)#include《stdio.h》struct time {
int hour;
int minute;
int second;
};
struct time times(struct time now);//利用結(jié)構(gòu)做函數(shù)返回值,形參也是使用結(jié)構(gòu)體做為傳值int main(void) {
struct time nows[5] = {
{11,50,20},{13,25,59},{12,59,59},{23,59,59},{00,00,00},
};
int i;
for (i = 0; i 《 5; i++) {
printf(”時間是 %d:%d:%d
“, nows[i].hour, nows[i].minute, nows[i].second);
nows[i] = times(nows[i]);
printf(”下一秒是 %d:%d:%d
“, nows[i].hour, nows[i].minute, nows[i].second);
}
return 0;
}
struct time times(struct time now) {
now.second++;
if (now.second == 60) {//60秒
now.minute++;
now.second = 0;
if (now.minute == 60)//60分
{
now.hour++;
now.minute = 0;
now.second = 0;
if (now.hour == 24) {//零點
now.hour=0;
now.minute = 0;
now.second = 0;
}
}
}
return now;//返回類型必須也函數(shù)類型一致,換句話說只有結(jié)構(gòu)體類型才能返回結(jié)構(gòu)體類型
}
結(jié)構(gòu)體數(shù)組
結(jié)構(gòu)體數(shù)組,是指數(shù)組中的每個元素都是一個結(jié)構(gòu)體。在實際應用中,C語言結(jié)構(gòu)體數(shù)組常被用來表示一個擁有相同數(shù)據(jù)結(jié)構(gòu)的群體,比如一個班的學生、一個車間的職工等。結(jié)構(gòu)體可以存儲不同的數(shù)據(jù)類型,將他們互相聯(lián)系起來。結(jié)構(gòu)體數(shù)組可以連續(xù)存儲多個結(jié)構(gòu)體,和數(shù)組作用相似。比如想定義同一個最小外接矩形的四個坐標值,并給予這個矩形一個特征編號。當需要存儲多個最小外接矩形的信息時,就需要動態(tài)申請一個結(jié)構(gòu)體數(shù)組
定義結(jié)構(gòu)體數(shù)組的方法很簡單,同定義結(jié)構(gòu)體變量是一樣的,只不過將變量改成數(shù)組。或者說同前面介紹的普通數(shù)組的定義是一模一樣的:struct student tp[10]; 這就定義了一個結(jié)構(gòu)體數(shù)組,共有 10 個元素,每個元素都是一個結(jié)構(gòu)體變量,都包含所有的結(jié)構(gòu)體成員。
結(jié)構(gòu)體數(shù)組的初始化與前面講的數(shù)值型數(shù)組的初始化也是一樣的,數(shù)值型數(shù)組初始化的方法和需要注意的問題在結(jié)構(gòu)體數(shù)組的初始化中同樣適用,因為不管是數(shù)值型數(shù)組還是結(jié)構(gòu)體數(shù)組都是數(shù)組。
//例子: //尋找學生中 學號最大的# include 《stdio.h》# include 《string.h》struct STU
{
char name[20];
int age;
char sex[20];
char num[20];
};
void OutputSTU(struct STU stu[]); //函數(shù)聲明, 該函數(shù)的功能是輸出成績最大的學生信息int main(void)
{
int i;
struct STU stu[5];
for (i = 0; i 《 2; ++i)
{
printf(”請按照名字、年齡、性別、學號(1-9數(shù)字)輸入第%d個學生的信息:“, i + 1);
scanf(”%s %d %s %s“, stu[i].name, &stu[i].age, stu[i].sex, stu[i].num);/*%c前面要加空格, 不然輸入時會將空格賦給%c*/
}
OutputSTU(stu);
return 0;
}
void OutputSTU(struct STU stu[])
{
struct STU stumax = stu[0];//讓臨時結(jié)構(gòu)stumax保存第一個學生的信息
int j;
for (j = 1; j 《 2; ++j)//第一個學生依次和后面的學生比較
{
if (strcmp(stumax.num, stu[j].num) 《 0) //strcmp函數(shù)的使用 s1》s2:1 s1《s2:-1
{
stumax = stu[j];//讓臨時結(jié)構(gòu)保存那個學生的信息
}
}
printf(”學生姓名:%s 學生年齡:%d 學生性別:%s 學生分數(shù):%s
“, stumax.name, stumax.age, stumax.sex, stumax.num);
}
結(jié)構(gòu)體指針
和數(shù)組不同,結(jié)構(gòu)變量的名字并不是結(jié)構(gòu)變量的地址,必須使用&運算符 strcut node *tp=&nb; 指針一般用-》訪問結(jié)構(gòu)體里邊的成員
指針變量非常靈活方便,可以指向任一類型的變量 ,若定義指針變量指向結(jié)構(gòu)體類型變量,則可以通過指針來引用結(jié)構(gòu)體類型變量。
這里說明:結(jié)構(gòu)體和結(jié)構(gòu)體變量是兩個不同的概念:結(jié)構(gòu)體是一種數(shù)據(jù)類型,是一種創(chuàng)建變量的模板,編譯器不會為它分配內(nèi)存空間,就像 int、float、char 這些關(guān)鍵字本身不占用內(nèi)存一樣;結(jié)構(gòu)體變量才包含實實在在的數(shù)據(jù),才需要內(nèi)存來存儲。所以用一個結(jié)構(gòu)體去取一個結(jié)構(gòu)體名的地址,這種寫法是錯誤的,也不能將它賦值給其他變量。
#include《stdio.h》struct point {
int x;
int y;
};
struct point *gt(struct point*p);//結(jié)構(gòu)指針函數(shù)void print(const struct point *p);//結(jié)構(gòu)指針void out(struct point p);//普通的結(jié)構(gòu)體做函數(shù)參數(shù)int main(void) {
struct point y = { 0,0 };//以point結(jié)構(gòu)定義一個y的結(jié)構(gòu)變量
//以下三種調(diào)用 等價
//注意gt是一個結(jié)構(gòu)體的指針函數(shù)
gt(&y); //這是一個函數(shù)的返回結(jié)果函數(shù) //取y結(jié)構(gòu)的地址傳入函數(shù)
out(y);
out(*gt(&y)); // (里邊)的都是做為參數(shù) *gt(&y)做為指針返回值 這個函數(shù)它的返回用指針表示
print(gt(&y)); //gt(&y)是一個返回值 這樣表示的是利用gt函數(shù)的返回值在print函數(shù)里邊操作
//*get(&y) = (struct point){ 1,2 }; //這也可以做的
}
struct point* gt(struct point*p) {// *p要的是&y的地址
scanf(”%d“, &p-》x);
scanf(”%d“, &p-》y);
printf(”a=%d,%d
“, p-》x, p-》y);//用-》來訪問指針結(jié)構(gòu)里邊的成員
return p;// 用完指針后 返回指針
}
void out(struct point p) {
printf(”b=%d,%d
“, p.x, p.y);
}
void print(const struct point *p) {//加上const表示不再改動參數(shù)
printf(”c=%d,%d
“, p-》x, p-》y);
}
指向結(jié)構(gòu)體數(shù)組的指針:
在之前講數(shù)值型數(shù)組的時候可以將數(shù)組名賦給一個指針變量,從而使該指針變量指向數(shù)組的首地址,然后用指針訪問數(shù)組的元素。結(jié)構(gòu)體數(shù)組也是數(shù)組,所以同樣可以這么做。我們知道,結(jié)構(gòu)體數(shù)組的每一個元素都是一個結(jié)構(gòu)體變量。如果定義一個結(jié)構(gòu)體指針變量并把結(jié)構(gòu)體數(shù)組的數(shù)組名賦給這個指針變量的話,就意味著將結(jié)構(gòu)體數(shù)組的第一個元素,即第一個結(jié)構(gòu)體變量的地址,也即第一個結(jié)構(gòu)變量中的第一個成員的地址賦給了這個指針變量
typedef 別名
typedef是在編程語言中用來為復雜的聲明定義簡單的別名,新的名字是某種類型的別名,這樣做改善了程序的可讀性,它與宏定義有些差異。它本身是一種存儲類的關(guān)鍵字,與auto、extern、mutable、static、register等關(guān)鍵字不能出現(xiàn)在同一個表達式中。
typedef為C語言的關(guān)鍵字,功能是用來聲明一個已有的數(shù)據(jù)類型的新名字,比如 typedef int last ; 這就使得last成為 int 類型的別名 這樣last這個名字就可以代替int出現(xiàn)在變量定義和參數(shù)聲明的地方了
typedef也有一個特別的長處:它符合范圍規(guī)則,使用typedef定義的變量類型其作用范圍限制在所定義的函數(shù)或者文件內(nèi)(取決于此變量定義的位置),而宏定義則沒有這種特性。
結(jié)構(gòu)體的內(nèi)存對齊方式(存儲空間)
結(jié)構(gòu)體內(nèi)存對齊:一個結(jié)構(gòu)體變量定義完之后,其在內(nèi)存中的存儲并不等于其所包含元素的寬度之和,元素是按照定義順序一個一個放到內(nèi)存中去的,但并不是緊密排列的。從結(jié)構(gòu)體存儲的首地址開始,每個元素放置到內(nèi)存中時,它都會認為內(nèi)存是按照自己的大小來劃分的,因此元素放置的位置一定會在自己寬度的整數(shù)倍上開始。
內(nèi)存對齊可以大大提升內(nèi)存訪問速度,是一種用空間換時間的方法。內(nèi)存不對齊會導致每次讀取數(shù)據(jù)都會讀取兩次,使得內(nèi)存讀取速度減慢。
cpu把內(nèi)存當成是一塊一塊的,塊的大小可以是2,4,8,16 個字節(jié),因此CPU在讀取內(nèi)存的時候是一塊一塊進行讀取的,塊的大小稱為內(nèi)存讀取粒度。
如果結(jié)構(gòu)體內(nèi)存在長度大于處理器位數(shù)的元素,那么就以處理器的倍數(shù)為對齊單位;否則,如果結(jié)構(gòu)體內(nèi)的元素的長度都小于處理器的倍數(shù)的時候,便以結(jié)構(gòu)體里面最長的數(shù)據(jù)元素為對齊單位。
另外 結(jié)構(gòu)體的內(nèi)存地址就是它第一個成員變量的地址 isa永遠都是結(jié)構(gòu)體中的第一個成員變量 所以結(jié)構(gòu)體的地址也就是其isa指針的地址
內(nèi)存對齊簡介
由于內(nèi)存的讀取時間遠遠小于CPU的存儲速度,這里用設定數(shù)據(jù)結(jié)構(gòu)的對齊系數(shù),即犧牲空間來換取時間的思想來提高CPU的存儲效率。
內(nèi)存對齊”應該是編譯器的“管轄范圍”。編譯器為程序中的每個“數(shù)據(jù)單元”安排在適當?shù)奈恢蒙?。但是C語言的一個特點就是太靈活,太強大,它允許你干預“內(nèi)存對齊”。如果你想了解更加底層的秘密,“內(nèi)存對齊”對你就不應該再模糊了。這也是一個大小端模式的問題
每個特定平臺上的編譯器都有自己的默認“對齊系數(shù)”(也叫對齊模數(shù))。程序員可以通過預編譯命令#pragma pack(n)來改變這一系數(shù),其中的n就是你要指定的“對齊系數(shù)”。
規(guī)則:
1、數(shù)據(jù)成員對齊規(guī)則:結(jié)構(gòu)(struct)(或聯(lián)合(union))的數(shù)據(jù)成員,第一個數(shù)據(jù)成員放在offset為0的地方,以后每個數(shù)據(jù)成員的對齊「按照#pragma pack指定的數(shù)值和這個數(shù)據(jù)成員自身長度中,比較小的那個進行」。
2、結(jié)構(gòu)(或聯(lián)合)的整體對齊規(guī)則:在數(shù)據(jù)成員完成各自對齊之后,結(jié)構(gòu)(或聯(lián)合)本身也要進行對齊,對齊將「按照#pragma pack 指定的數(shù)值和結(jié)構(gòu)(或聯(lián)合) 最大數(shù)據(jù)成員長度中,比較小的那個進行」對齊。
3、結(jié)合1、2可推斷:當#pragma pack的n值等于或超過所有數(shù)據(jù)成員長度的時候,這個n值的大小將不產(chǎn)生任何效果。
#pragma pack(n) 設定變量以n字節(jié)為對齊方式:
「作用」:指定結(jié)構(gòu)體、聯(lián)合以及類成員
「語法」:#pragma pack( [show] | [push | pop] [, identifier], n )
1,pack提供數(shù)據(jù)聲明級別的控制,對定義不起作用;
2,調(diào)用pack時不指定參數(shù),n將被設成默認值;
n:可選參數(shù);指定packing的數(shù)值,以字節(jié)為單位;缺省數(shù)值是8,合法的數(shù)值分別是1、2、4、8、16。其他參數(shù)都是可選的可先不了解
每個成員分別對齊,即每個成員按自己的方式對齊,并最小化長度;規(guī)則就是每個成員按其類型的對齊參數(shù)(通常是這個類型的大小)和指定對齊參數(shù)中較小的一個對齊。
大小端:
如:int 11 22 33 44
在存儲的時候
大端:11 22 33 44
0 1 2 3
低地址----》 高地址
小端:44 33 22 11
0 1 2 3
低地址----》 高地址
大小端的差異在于存放順序不同
常見的操作系統(tǒng)是小端,通訊協(xié)議是大端。
//結(jié)構(gòu)體例子:使用尾插法創(chuàng)建鏈表#include《stdio.h》//單鏈表的創(chuàng)建typedef struct _node {
int nb;//數(shù)值
struct _node *nxte;//定義一個指向下一個的節(jié)點的指針
}NODE;
typedef struct _link{//利用這個結(jié)構(gòu)體 封裝 首尾節(jié)點
NODE *head;
NODE *qt;
}link;
void add(link *phead, link *qt, int n);//定義函數(shù)將 首尾指針傳入int main(void) {
link head, q;//定義一個結(jié)構(gòu),連指針都不是的
head.head = q.qt = NULL;//初始化
int n;
for (scanf(”%d“, &n); n != -1; scanf(”%d“, &n)) {
add(&head, &q, n);//將地址 值傳入
}
NODE *t;
t = head.head;//利用臨時結(jié)構(gòu)將鏈表輸出
for (; t; t = t-》nxte) {
printf(”%d “, t-》nb);
}
return 0;
}
//尾插法void add(link *phead, link *qt, int n) {
NODE *p = (NODE*)malloc(sizeof(NODE));//為新結(jié)點開辟空間
p-》nb = n;
p-》nxte = NULL;
if (phead-》head == NULL) {//判斷首結(jié)點是否為空
phead-》head = p;//是空的就讓首結(jié)點等于新結(jié)點
}
else {//不為空時,讓尾結(jié)點依次跑到后面去
qt-》qt-》nxte = p;
}
qt-》qt = p;
}
4、union 共用體(聯(lián)合體)在進行某些算法的C語言編程的時候,需要使幾種不同類型的變量存放到同一段內(nèi)存單元中。也就是使用覆蓋技術(shù),幾個變量互相覆蓋。這種幾個不同的變量共同占用一段內(nèi)存的結(jié)構(gòu),在C語言中 以關(guān)鍵字union聲明的一種數(shù)據(jù)結(jié)構(gòu),這種被稱作“共用體”類型結(jié)構(gòu),也叫聯(lián)合體。
“聯(lián)合”與“結(jié)構(gòu)”有一些相似之處。但兩者有本質(zhì)上的不同。在結(jié)構(gòu)中各成員有各自的內(nèi)存空間,一個結(jié)構(gòu)體變量的總長度大于等于各成員長度之和。而在“聯(lián)合”中,各成員共享一段內(nèi)存空間,一個聯(lián)合變量的長度等于各成員中最長的長度。注意這里所謂的共享不是指把多個成員同時裝入一個聯(lián)合變量內(nèi),而是指該聯(lián)合變量可被賦予任一成員值,但每次只能賦一種值,賦入新值則沖去舊值,共用體變量中起作用的成員是最后一次存放的成員,在存入一個新成員后,原有成員就失去作用,共用體變量的地址和它的各成員的地址都是同一地址
一個聯(lián)合類型必須經(jīng)過定義之后,才能把變量說明為該聯(lián)合類型:
注意:1、不能把共用體變量作為函數(shù)參數(shù),也不能是函數(shù)帶回共用體變量,但可以使專用指向共用體變量的指針
2、所有成員占用同一段內(nèi)存,修改一制個成員會影響其余所有成員。
共用體的訪問:共用體訪問成員的值時一般使用。運算符,指針時用-》運算符(和結(jié)構(gòu)體是一樣的)
typedef union _node {
int a;
double b;
char c;
union _node *p;
}NODE;
int main(void) {
NODE a;//定義變量
NODE t;
a.b;//用。訪問
t.p-》a;//指針用-》訪問
}
聯(lián)合的使用規(guī)則幾乎和結(jié)構(gòu)體strtct的規(guī)則用法一樣,只不過是內(nèi)部表示的不同。
補充:
還有一個是無名聯(lián)合體,它是和無名結(jié)構(gòu)體的工作原理是相同的
#include《stdio.h》//簡單的例子#include《string.h》typedef union _node{
int a;
double b;
char c[20];
}NODE;
int main(void) {
NODE a;//這里只定義一個變量
a.a = 666;
printf(”%d
“, a.a);
a.b = 9.99;
printf(”%f
“, a.b);
strcpy(a.c, ”hello world!“);
printf(”%s
“, a.c);
//我們看到,三個都被完整的輸出了,因為在同一時刻,只有一個成員是有效的
}
輸出:
6669.990000
hellow world!
共用體的作用:
1、節(jié)省內(nèi)存,有兩個很長的數(shù)據(jù)結(jié)構(gòu),不會同時使用,比如一個表示老師,一個表示學生,如果要統(tǒng)計教師和學生的情況用結(jié)構(gòu)體的話就有點浪費了!用結(jié)構(gòu)體的話,只占用最長的那個數(shù)據(jù)結(jié)構(gòu)所占用的空間,就足夠了!
2、實現(xiàn)不同類型數(shù)據(jù)之間的類型轉(zhuǎn)換,遇到各種類型的數(shù)據(jù)共用存儲空間,很方便的實現(xiàn)了不同數(shù)據(jù)類型之間的轉(zhuǎn)換,不需要顯示的強制類型轉(zhuǎn)換。
其他:1、確定CPU的模式:大端、小端模式確定
大小端不同,則存儲的方式也存在差別,比如int需要4個字節(jié),而char只需要1個字節(jié),根據(jù)1個字節(jié)所在的具體位置即可判定CPU的模式
2、寄存器的定義,實現(xiàn)整體的訪問和單項的訪問//共用體綜合例子:根據(jù)輸入的數(shù)據(jù)類型輸出需要的相應的數(shù)據(jù)#include《stdio.h》#include《string.h》//數(shù)據(jù)類型輸出 5*4 m n n的第幾個x union node {
int a;
double b;
char c[30];
}add[10000];
char p[10000][30]; //保存的字符串數(shù)組int main(void) {
int n, m;
scanf(”%d %d“, &n, &m);
int x;
double y;
char t[50];
int i, j;
for (i = 0; i 《 n; i++) {//輸入
scanf(”%s“, &p[i]);//作為字符串數(shù)組,需要取地址
if (strcmp(”INT“, p[i]) == 0) {//整形
scanf(”%d“, &x);
add[i].a = x;
}
else if(strcmp(”DOUBLE“,p[i])==0){//浮點
scanf(”%lf“, &y);
add[i].b = y;
}
else if (strcmp(”STRCING“, p[i]) == 0) {//字符串
scanf(”%s“, t);
strcpy(add[i].c, t);
}
}
for (i = 0; i 《 m; i++) {//輸出
scanf(”%d“, &j);
if (strcmp(”INT“, p[j]) == 0) {
printf(”%d
“, add[j].a);
}
else if (strcmp(”DOUBLE“, p[j]) == 0)
{
printf(”%f
“, add[j].b);
}else if(strcmp(”STRING“,p[j])==0)
{
printf(”%s
“, add[j].c);
}
}
return 0;
}
//輸入:/*
5 4
INT 456
DOUBLE 123.56
DOUBLE 0.476
STRING welcomeToC
STRING LemonTree
0
1
2
4
*///輸出:/*
456
123.56
0.48
LemonTree
*/
編輯:jq1、define宏定義以#號開頭的都是編譯預處理指令,它們不是C語言的成分,但是C程序離不開它們,#define用來定義一個宏,程序在預處理階段將用define定義的來內(nèi)容進行了替換。因此在程序運行時,常量表中并沒有用define定義的常量,系統(tǒng)不為它分配內(nèi)存。
define定義的常量,預處理時只是直接進行了替換,它用來將一個標識符定義為一個字符串,該標識符被稱為宏名,被定義的字符串稱為替換文本。因此在編譯時它不對宏的定義進行檢查,作用域不影響對常量的訪問 。它的常量值只能是字符串或數(shù)字。
該命令有兩種格式:一種是簡單的常量宏定義, 另一種是帶參數(shù)的宏定義。
不帶參數(shù)的宏:#define《 名字 》《 值 》
要注意,沒有結(jié)尾的分號,因為不是C的語句,名字必須是一個單詞,值可以是各種東西,宏定義是用宏名來表示一個字符串,在宏展開時又以該字符串取代宏名,這只是一種簡單的代換,字符串中可以含任何字符,可以是常數(shù),也可以是表達式,預處理程序?qū)λ蛔魅魏螜z查。如有錯誤,只能在編譯已被宏展開后的源程序時發(fā)現(xiàn)。
注意。宏定義不是說明或語句,在行末不必加分號,如加上分號則連分號也一起置換。宏定義其作用域為宏定義命令起到源程序結(jié)束。如要終止其作用域可使用#undef命令
帶參數(shù)的宏 :
像函數(shù)的宏,一般的定義形式 :帶參宏定義的一般形式為:「#define 宏名」(形參表)字符串,也是沒有結(jié)尾的分號,可以帶多個參數(shù) ,#define NB(a,b)((a)》(b)?(b):(a)), 也可以組合(嵌套)使用其他宏,注意 帶參數(shù)宏的原則 一切都要有括號,參數(shù)出現(xiàn)的每個地方都要有括號。帶參數(shù)的宏在大型的程序的代碼中使用非常普遍,在#和##這兩個運算符的幫助下可以很復雜,如“產(chǎn)生函數(shù)”,但是有些宏會被inline函數(shù)代替(C++的函數(shù))
使用宏好處:
“提高運行效”。定義的標識符不占內(nèi)存,只是一個臨時的符號,預編譯后這個符號就不存在了。在簡單的程序使用帶參數(shù)的宏定義可完成函數(shù)調(diào)用的功能,又能減少系統(tǒng)開銷,提高運行效率。正如C語言中所講,函數(shù)的使用可以使程序更加模塊化,便于組織,而且可重復利用。
“方便程序的修改”。使用宏定義可以用宏代替一個在程序中經(jīng)常使用的常量。注意,是“經(jīng)常”使用的。這樣,當需要改變這個常量的值時,就不需要對整個程序一個一個進行修改,只需修改宏定義中的常量即可。且當常量比較長時,使用宏就可以用較短的有意義的標識符來代替它,這樣編程的時候就會更方便,不容易出錯。因此,宏定義的優(yōu)點就是方便和易于維護。
//例子:/求球的體積#include《stdio.h》#include《math.h》//對于半徑為 r 的球,其體積的計算公式為 V =4/3*Π*r^3 //這里取Π為3.14//這里給定r的值,求V#define PI 3.14int main(void) {
double r;
scanf(“%lf”, &r);
double sum = 0;
sum = (4.0 / 3.0) * PI*pow(r, 3);//這里用PI替換掉了 3.14
printf(“%f.2”, sum);
}
#include《stdio.h》//合例子:輸入數(shù)字查看是星期幾int main(void) {
enum week {Mon = 1, Tue, Wed, Thu, Fri, Sat, Sun}today;
//在這里我們給Mon賦值了一,后面Tue以后的都相應加一賦值
scanf(“%d”, &today);
switch (today) {
case Mon: puts(“Monday”); break;
case Tue: puts(“Tuesday”); break;
case Wed: puts(“Wednesday”); break;
case Thu: puts(“Thursday”); break;
case Fri: puts(“Friday”); break;
case Sat: puts(“Saturday”); break;
case Sun: puts(“Sunday”); break;
default: puts(“no day”);
}
return 0;
}
2、enum枚舉枚舉型是一個集合,集合中的元素(枚舉成員)是一些命名的整型常量,元素之間用逗號,隔開。它是一種用戶定義的數(shù)據(jù)類型,它用關(guān)鍵字enum以如下語法來聲明,:enum 枚舉類型名字,{名字0,。。。,名字n};第一個枚舉成員的默認值為整型的0,后續(xù)枚舉成員的值在前一個成員上加1 (當然這個是可以自定義成員值的)
枚舉類型名字通常并不真的使用,要用的是在它大括號里邊的名字,因為它們就是常量符號,它們的類型是int,值則是依次從零到n,如 enum week { Monday,Tuoesday,Wedenday}; 就創(chuàng)建了三個常量,Monday的值是0,Tuoesday是1,Wedenday是2,當需要一些可以可以排列起來的常量值的時候,定義枚舉的意義就是給了這些常量名字。
雖然枚舉類型可以當類型使用,但是實際上并不常用,但是如果是有意是排比名字,用枚舉比宏定義方便,枚舉比用好些,因為枚舉有int類型,在C 語言中,枚舉類型是被當做 int 或者 unsigned int 類型來處理的,既然枚舉也是一種數(shù)據(jù)類型,所以它和基本數(shù)據(jù)類型一樣也可以對變量進行聲明,枚舉也可以用typedef關(guān)鍵字將枚舉類型定義成別名,并利用該別名進行變量聲明
注意:
1、同一個程序中不能定義同名的枚舉類型,不同的枚舉類型中也不能存在同名的命名常量
2、枚舉成員)是「常量」而不是變量,這個一定要搞清楚,因為枚舉成員的是常量,所以不能對它們賦值,只能將它們的值賦給其他的變量
3、枚舉類型的定義和變量的聲明分開:如果對枚舉型的變量賦整數(shù)值時,需要進行類型轉(zhuǎn)換
enum DAY { MON=1, TUE, WED, THU, FRI, SAT, SUN };//枚舉常量的值是可以在這里進行自定義的 MON=1//基本數(shù)據(jù)類型的賦值 :
int a, b, c;
a = 1;
b = 2;
c = 3;
//使用枚舉賦值 :
enum DAY yesterday, today, tomorrow;//枚舉定義變量
yesterday = MON;
today = TUE;
tomorrow = WED;
// today = (enum DAY) (yesterday + 1);//強制類型轉(zhuǎn)換// tomorrow = (enum DAY) 30; //強制類型轉(zhuǎn)換//tomorrow = 3; //錯誤
枚舉在用switch-case結(jié)構(gòu)中使用非常方便。
//綜合例子:輸入數(shù)字查看是星期幾#include 《stdio.h》//枚舉是define的代替 它是一個集合 //和switch連用很方便int main() {
enum week { Mon = 1,Tues, Wed, Thurs, Fri, Sat, Sun } day; //day可放到這
//enum week day;定義
scanf(“%d”, &day);
switch (day) {
case Mon: puts(“Monday”); break;//1 puts代替printf輸出字符串
case Tues: puts(“Tuesday”); break;// 2
case Wed: puts(“Wednesday”); break;// 3
case Thurs: puts(“Thursday”); break;// 4
case Fri: puts(“Friday”); break;// 5
case Sat: puts(“Saturday”); break;// 6
case Sun: puts(“Sunday”); break;// 7
default: puts(“Error!”);
}
return 0;
}
枚舉型是預處理指令#define的替代,枚舉和宏其實非常類似,宏在「預處理階段」將名字替換成對應的值,枚舉在「編譯階段」將名字替換成對應的值,其中一個枚舉常量的占的字節(jié)數(shù)為4個字節(jié),恰好和int類型的變量占的字節(jié)數(shù)相同
3、struct 結(jié)構(gòu)體struct即結(jié)構(gòu)體,C程序中經(jīng)常需要用相關(guān)的不同類型的數(shù)據(jù)來描述一個數(shù)據(jù)對象。例如,描述學生的綜合信息時,需要使用學生的學號、姓名、性別等不同類型的數(shù)據(jù)時,像這種數(shù)據(jù)類型總是在一起出現(xiàn),那么我們不如把這些變量裝入同一個“文件夾”中,這時用的關(guān)鍵字struct聲明的一種數(shù)據(jù)類型就是表示這個“文件夾”的使用。
那么在說明和使用之前必須先定義它,也就是構(gòu)造它。如同在說明和調(diào)用函數(shù)之前要先定義一樣。結(jié)構(gòu)體是一種集合,它里面包含了多個變量或數(shù)組,它們的類型可以相同,也可以不同,每個這樣的變量或數(shù)組都稱為結(jié)構(gòu)體的成員,結(jié)構(gòu)體也是一種數(shù)據(jù)類型,它由程序員自己定義,可以包含多個其他類型的數(shù)據(jù),成員又稱為成員變量,它是結(jié)構(gòu)體所包含的若干個基本的結(jié)構(gòu)類型,必須用“{}”括起來,并且要以分號結(jié)束,每個成員應表明具體的數(shù)據(jù)類型,成員一般用名字訪問。
結(jié)構(gòu)體和數(shù)組類似,也是一組數(shù)據(jù)的集合,整體使用沒有太大的意義。數(shù)組使用下標[ ]獲訪問元素,結(jié)構(gòu)體使用點號。訪問單個成員。通過這種方式可以獲取成員的值,也可以給成員賦值
數(shù)組:a[0]=10; 結(jié)構(gòu)體:today.day (指針結(jié)構(gòu)體用-》訪問) 結(jié)構(gòu)體的成員可以包含其他結(jié)構(gòu)體,也可以包含指向自己結(jié)構(gòu)體類型的指針,而通常這種指針的應用是為了實現(xiàn)一些更高級的數(shù)據(jù)結(jié)構(gòu)如鏈表和樹等。
聲明定義結(jié)構(gòu):
struct關(guān)鍵字+結(jié)構(gòu)體的標志名+大括號里邊是成員+}后面的聲明此結(jié)構(gòu)變量+末尾分號,一般有這些:
struct week{定義一 struct{定義二 struct week {定義三
int x; int x; int x;
char y; char y; int y;
}; }p1,p2;//在這里聲明變量 }p1,p2;
//p1和p2都是一種無名結(jié)構(gòu), //常用的一種結(jié)構(gòu)定義聲struct week p1,p2; // 里邊有X和y 訪問一樣用。 明形式 //聲明變量p1,p2,里邊都是week的值 //里邊有x和y的值 //用。訪問 :p1.x p2.x
// p1.y, p2.y
對于第一和第三種形式,都聲明了結(jié)構(gòu)名week,但是第二種沒有聲明結(jié)構(gòu)名,只是定義了兩個結(jié)構(gòu)變量,
這種叫無名結(jié)構(gòu)
無名結(jié)構(gòu): 可以定義無名結(jié)構(gòu)體類型的變量。編譯器對無名結(jié)構(gòu)體的處理是隨機生成一個不重復的變量名。
無名結(jié)構(gòu)的定義方式就是定義無名結(jié)構(gòu)體時必須定義該結(jié)構(gòu)體類型的至少一個變量。
優(yōu)點:無名結(jié)構(gòu)體的妙用就是可以避免相同類型的結(jié)構(gòu)體的重復定義,
這樣可以對每一個具體類型的隊列都可以定義一個結(jié)構(gòu)體來管理該隊列的頭尾指針,
即使定義多個相同具體類型的隊列也不會引發(fā)重復定義的編譯錯誤。這樣定義了兩個隊列,
其元素類型均為int類型,同時各得到了一個維護隊列頭尾指針的結(jié)構(gòu)體
缺點:這里定義了一個無名的結(jié)構(gòu)體,同時聲明了三個此種類型的變量。
但是,因為沒有名字,我們在這句之后,無法內(nèi)再定義與那三種變量相同類型的變量了。
除非你再容次去定義一個這樣的相同的結(jié)構(gòu)體類型。
還有一個重要的原因就是沒有辦法在其他位置定義我們所需要的結(jié)構(gòu)體變量,
每次需要新定義結(jié)構(gòu)體變量的時候都必須要找到最開始結(jié)構(gòu)體代碼書寫的位置才能定義新的結(jié)構(gòu)體
所以實際編程中無名結(jié)構(gòu)并不常用
注意:1、結(jié)構(gòu)體本身并不會被作為數(shù)據(jù)而開辟內(nèi)存,真正作為數(shù)據(jù)而在內(nèi)存中存儲的是這種結(jié)構(gòu)體所定義的變量。
2、先聲明結(jié)構(gòu)體類型,再定義該類型的變量,聲明結(jié)構(gòu)體類型,不分配空間定義結(jié)構(gòu)體類型變量,就要分配內(nèi)存空間
3、量使用占為少的類型,如,在可能的時候使用short代替int,「按數(shù)據(jù)類型本身占用的位置從大到小排」
4、除了可以對成員進行逐一賦值,也可以在定義時整體賦值:p1={struct week}{5,10}; 相當于 p1.x=5,p1.y=10; p1=p2 表示 p1.x=p2.x , p1.y=p2.y; 不過整體賦值僅限于定義結(jié)構(gòu)體變量的時候,在使用過程中只能對成員逐一賦值 5、結(jié)構(gòu)體變量不能相加,相減,也不能相互乘除,但結(jié)構(gòu)體可以相互賦值,也就是說,可以將一個結(jié)構(gòu)體變量賦值給另一個結(jié)構(gòu)體變量。但是前提是這兩個結(jié)構(gòu)體變量的結(jié)構(gòu)體類型必須相同
結(jié)構(gòu)體的運算:要訪問整個結(jié)構(gòu),直接用結(jié)構(gòu)變量的名字,對于整個結(jié)構(gòu),可以做賦值,取地址,也可以傳遞給函數(shù)參數(shù)
結(jié)構(gòu)體數(shù)值
嵌套的結(jié)構(gòu)體:
結(jié)構(gòu)體相互引用:
一個結(jié)構(gòu)體A中包含一個或多個與結(jié)構(gòu)體B相關(guān)的成員, 且結(jié)構(gòu)體B中也包含一個或多個與結(jié)構(gòu)體A相關(guān)的成員稱為結(jié)構(gòu)體的互引用。 但是要注意:如果已經(jīng)定義了兩個結(jié)構(gòu)A和B ,在定義結(jié)構(gòu)體A的成員b時,結(jié)構(gòu)體B對A還未可見,故此時編譯器會報數(shù)據(jù)類型B未定義 解決的辦法是使用不完整聲明:
strcut A;//不完整聲明
strcut B;//不完整聲明
strcut _A{ strcut _B{
int x; int x;
int y; int y;
struct _B a; struct _A b; //在結(jié)構(gòu)B中定義了一個名為b的和A結(jié)構(gòu)一樣類型的結(jié)構(gòu)變量
//其中可以用點訪問 A.a.x B.b.x
}A; }B;
//但是注意這種方式犯了一個和上面第一個嵌套結(jié)構(gòu)的錯誤,就是結(jié)構(gòu)體A和B都是直接包含了對方,
正確的用法還是使用指針:
strcut _A{ strcut _B{
int x; int x;
int y; int y;
struct _B *a; struct _A *b; //在結(jié)構(gòu)B中定義了一個名為b的和A結(jié)構(gòu)一樣類型的結(jié)構(gòu)指針
//其中指針要用-》訪問 A.a-》x B.b-》x
}A; }B;
//但是注意這種方式犯了一個和上面第一個嵌套結(jié)構(gòu)的錯誤,就是結(jié)構(gòu)體A和B都是直接包含了對方,正確的用法還是使用指針:
strcut _A{ strcut _B{
int x; int x;
int y; int y;
struct _B *a; struct _A *b; //在結(jié)構(gòu)B中定義了一個名為b的和A結(jié)構(gòu)一樣類型的結(jié)構(gòu)指針
//其中指針要用-》訪問 A.a-》x B.b-》x
}A; }B;
//所以使用互引用要注意:至少有一個結(jié)構(gòu)必須在另一個結(jié)構(gòu)體中以指針的形式被引用。
結(jié)構(gòu)體函數(shù)與函數(shù)參數(shù)
結(jié)構(gòu)體做函數(shù)形參:
整個結(jié)構(gòu)可以作為參數(shù)的值傳入函數(shù),這時候是在函數(shù)內(nèi)新建一個結(jié)構(gòu)變量,并復制調(diào)用者結(jié)構(gòu)的值,也可以返回一個值,這和數(shù)組完全不同 用結(jié)構(gòu)體變量作實參時,采取的也是“值傳遞”方式,將 結(jié)構(gòu)體變量所占的內(nèi)存單元的內(nèi)容(結(jié)構(gòu)體變量成員列表) 全部順序傳遞給形參,這里形參也得是結(jié)構(gòu)體變量。
#include《stdio.h》typedef struct _node {
int n;
char a[100];
}NODE;
void add(NODE a);//這種形式只是用來做值的傳遞int main(void) {
//以傳值方式傳遞結(jié)構(gòu)需要對整個結(jié)構(gòu)做一份拷貝
NODE t;
scanf(“%d %d”, &t.a[0], &t.n);//輸入1 3
printf(“1-%d %d
”,t.a[0],t.n);//輸出 1 3
add(t);
printf(“3-%d %d
”, t.a[0], t.n);//輸出1 3//也就是說在add函數(shù)里邊做修改根本就影響不了主函數(shù)這邊的值
}
void add(NODE a) {
a.a[0] = 100;//在這里能接受到NODE結(jié)構(gòu)里邊的成員
a.n = 666;
printf(“2-%d %d
”, a.a[0], a.n);//輸出100 666
}
****//解決辦法是用指針(也是經(jīng)常用的方式):****#include《stdio.h》typedef struct _node {
int n;
char a[100];
}NODE;
int add(NODE a);//這種形式只是用來做值的傳遞int main(void) {
//以傳值方式傳遞結(jié)構(gòu)需要對整個結(jié)構(gòu)做一份拷貝
NODE t;
scanf(“%d %d”, &t.a[0], &t.n);//輸入1 3
printf(“1-%d %d
”,t.a[0],t.n);//輸出 1 3
add(&t);//這里傳進去的是t的地址
printf(“3-%d %d
”, t.a[0], t.n);//輸出100 666//傳進去的是地址,所以就可以達到訪問同一個變量的操作
}
int add(NODE *) {//定義一個結(jié)構(gòu)指針
a.a[0] = 100;//在這里能接受到NODE結(jié)構(gòu)里邊的成員
a.n = 666;
printf(“2-%d %d
”, a.a[0], a.n);//輸出100 666
return a;//這里返回的是指針 所以能達到訪問主函數(shù)里邊調(diào)用的值
//使用指針才可以用返回值
}
//常用的方式
另一種做法
結(jié)構(gòu)體做函數(shù):
/*上面的第一個的方案,把一個結(jié)構(gòu)傳入了函數(shù),然后在函數(shù)中操作,但是沒有返回回去
問題在于傳入函數(shù)的是外面那個結(jié)構(gòu)的克隆體,而不是指針,傳入結(jié)構(gòu)和傳入數(shù)組是不同的,
解決辦法是在這個輸入函數(shù)中,在里邊創(chuàng)建一個臨時的結(jié)構(gòu)變量,然后把這個結(jié)構(gòu)返回給調(diào)用者*/#include《stdio.h》typedef struct _node {
int x;
int y;
}NODE;
struct _node add();//定義結(jié)構(gòu)類型的函數(shù)int main(void) {
NODE a;
a.x = 0;
a.y = 0;
printf(“1-%d %d
”, a.x, a.y);// 0 0
a = add();//函數(shù)調(diào)用 /把n的值又返回到a
printf(“3-%d %d
”, a.x, a.y);//所以在這里的時候值已經(jīng)被改變
return 0;
}
struct _node add() {
NODE n;
scanf(“%d”, &n.x);//輸入1 3
scanf(“%d”, &n.y);
printf(“2-%d %d
”, n.x, n.y);//在這里的時候賦值就成功了
//return n;//把n的值帶回出去
}
//這種方法也能達到“改變“的效果,但是往往開銷內(nèi)存較大,所以一般情況都是使用指針比較方便
用結(jié)構(gòu)體變量名作參數(shù),這種傳遞方式是單向的,如果在執(zhí)行被調(diào)函數(shù)期間改變了形參(也是結(jié)構(gòu)體變量)的值,該值不能返回主調(diào)函數(shù),這往往造成使用上的不便,因此一般少用這種方法。
和本地變量一樣。在函數(shù)內(nèi)部聲明的結(jié)構(gòu)只能在函數(shù)內(nèi)部使用,所以通常在函數(shù)外部聲明一個結(jié)構(gòu)類型的,這樣就可以被多個函數(shù)所使用
//結(jié)構(gòu)做函數(shù)參數(shù)例子 (輸入今天計算明天)#include《stdio.h》#include《stdbool.h》//利用布爾數(shù)據(jù)類型struct date {
int year;
int month;
int day;
};
bool If(struct date p);//判斷是否是閏年int number(struct date c);//判斷是否是此月最后一天int main(void) {
struct date today,tomorrow;
printf(”年-月-日
“);
scanf(”%d %d %d“, &today.year, &today.month, &today.day);
//前面兩個判斷 是否此月最后一天 是否此年此月最后一天
if (today.day==number(today)&&today.month!=12) {//首月1號
tomorrow.day = 1;
tomorrow.month =today.month+1;
tomorrow.year = today.year;
}
else if (today.day == number(today) && today.month == 12) {//下一年
tomorrow.day = 1;
tomorrow.month = 1;
tomorrow.year =today.year+1;
}
else {
tomorrow.day =today.day+1;
tomorrow.month = today.month;
tomorrow.year = today.year;
}
printf(”明天是%d-%d-%d
“, tomorrow.year, tomorrow.month, tomorrow.day);
return 0;
}
int number(struct date c)//這里的形參接收的today結(jié)構(gòu)體數(shù)據(jù)
{
int day;
const int a[12] = { 31,28,31,30,31,30,31,31,30,31,30,31 };//這個月最大的天數(shù)
if (c.month==22&&If(c)) {//查看是否是二月并且是潤年
day = 29;//是潤年
}
else {
day = a[c.month - 1];
}
return day;
}
bool If(struct date p) {//這里的形參接收的today結(jié)構(gòu)體數(shù)據(jù)
//潤年的特點,能被4整除,但不能被100整數(shù),能被100整除,但是不能被400整除
if (p.year % 4 == 0 && p.year / 100 != 0 || p.year % 400 == 0) {
return true;
}
else {
return false;
}
}
//結(jié)構(gòu)體做函數(shù)例子 (計算下一秒)#include《stdio.h》struct time {
int hour;
int minute;
int second;
};
struct time times(struct time now);//利用結(jié)構(gòu)做函數(shù)返回值,形參也是使用結(jié)構(gòu)體做為傳值int main(void) {
struct time nows[5] = {
{11,50,20},{13,25,59},{12,59,59},{23,59,59},{00,00,00},
};
int i;
for (i = 0; i 《 5; i++) {
printf(”時間是 %d:%d:%d
“, nows[i].hour, nows[i].minute, nows[i].second);
nows[i] = times(nows[i]);
printf(”下一秒是 %d:%d:%d
“, nows[i].hour, nows[i].minute, nows[i].second);
}
return 0;
}
struct time times(struct time now) {
now.second++;
if (now.second == 60) {//60秒
now.minute++;
now.second = 0;
if (now.minute == 60)//60分
{
now.hour++;
now.minute = 0;
now.second = 0;
if (now.hour == 24) {//零點
now.hour=0;
now.minute = 0;
now.second = 0;
}
}
}
return now;//返回類型必須也函數(shù)類型一致,換句話說只有結(jié)構(gòu)體類型才能返回結(jié)構(gòu)體類型
}
結(jié)構(gòu)體數(shù)組
結(jié)構(gòu)體數(shù)組,是指數(shù)組中的每個元素都是一個結(jié)構(gòu)體。在實際應用中,C語言結(jié)構(gòu)體數(shù)組常被用來表示一個擁有相同數(shù)據(jù)結(jié)構(gòu)的群體,比如一個班的學生、一個車間的職工等。結(jié)構(gòu)體可以存儲不同的數(shù)據(jù)類型,將他們互相聯(lián)系起來。結(jié)構(gòu)體數(shù)組可以連續(xù)存儲多個結(jié)構(gòu)體,和數(shù)組作用相似。比如想定義同一個最小外接矩形的四個坐標值,并給予這個矩形一個特征編號。當需要存儲多個最小外接矩形的信息時,就需要動態(tài)申請一個結(jié)構(gòu)體數(shù)組
定義結(jié)構(gòu)體數(shù)組的方法很簡單,同定義結(jié)構(gòu)體變量是一樣的,只不過將變量改成數(shù)組?;蛘哒f同前面介紹的普通數(shù)組的定義是一模一樣的:struct student tp[10]; 這就定義了一個結(jié)構(gòu)體數(shù)組,共有 10 個元素,每個元素都是一個結(jié)構(gòu)體變量,都包含所有的結(jié)構(gòu)體成員。
結(jié)構(gòu)體數(shù)組的初始化與前面講的數(shù)值型數(shù)組的初始化也是一樣的,數(shù)值型數(shù)組初始化的方法和需要注意的問題在結(jié)構(gòu)體數(shù)組的初始化中同樣適用,因為不管是數(shù)值型數(shù)組還是結(jié)構(gòu)體數(shù)組都是數(shù)組。
//例子: //尋找學生中 學號最大的# include 《stdio.h》# include 《string.h》struct STU
{
char name[20];
int age;
char sex[20];
char num[20];
};
void OutputSTU(struct STU stu[]); //函數(shù)聲明, 該函數(shù)的功能是輸出成績最大的學生信息int main(void)
{
int i;
struct STU stu[5];
for (i = 0; i 《 2; ++i)
{
printf(”請按照名字、年齡、性別、學號(1-9數(shù)字)輸入第%d個學生的信息:“, i + 1);
scanf(”%s %d %s %s“, stu[i].name, &stu[i].age, stu[i].sex, stu[i].num);/*%c前面要加空格, 不然輸入時會將空格賦給%c*/
}
OutputSTU(stu);
return 0;
}
void OutputSTU(struct STU stu[])
{
struct STU stumax = stu[0];//讓臨時結(jié)構(gòu)stumax保存第一個學生的信息
int j;
for (j = 1; j 《 2; ++j)//第一個學生依次和后面的學生比較
{
if (strcmp(stumax.num, stu[j].num) 《 0) //strcmp函數(shù)的使用 s1》s2:1 s1《s2:-1
{
stumax = stu[j];//讓臨時結(jié)構(gòu)保存那個學生的信息
}
}
printf(”學生姓名:%s 學生年齡:%d 學生性別:%s 學生分數(shù):%s
“, stumax.name, stumax.age, stumax.sex, stumax.num);
}
結(jié)構(gòu)體指針
和數(shù)組不同,結(jié)構(gòu)變量的名字并不是結(jié)構(gòu)變量的地址,必須使用&運算符 strcut node *tp=&nb; 指針一般用-》訪問結(jié)構(gòu)體里邊的成員
指針變量非常靈活方便,可以指向任一類型的變量 ,若定義指針變量指向結(jié)構(gòu)體類型變量,則可以通過指針來引用結(jié)構(gòu)體類型變量。
這里說明:結(jié)構(gòu)體和結(jié)構(gòu)體變量是兩個不同的概念:結(jié)構(gòu)體是一種數(shù)據(jù)類型,是一種創(chuàng)建變量的模板,編譯器不會為它分配內(nèi)存空間,就像 int、float、char 這些關(guān)鍵字本身不占用內(nèi)存一樣;結(jié)構(gòu)體變量才包含實實在在的數(shù)據(jù),才需要內(nèi)存來存儲。所以用一個結(jié)構(gòu)體去取一個結(jié)構(gòu)體名的地址,這種寫法是錯誤的,也不能將它賦值給其他變量。
#include《stdio.h》struct point {
int x;
int y;
};
struct point *gt(struct point*p);//結(jié)構(gòu)指針函數(shù)void print(const struct point *p);//結(jié)構(gòu)指針void out(struct point p);//普通的結(jié)構(gòu)體做函數(shù)參數(shù)int main(void) {
struct point y = { 0,0 };//以point結(jié)構(gòu)定義一個y的結(jié)構(gòu)變量
//以下三種調(diào)用 等價
//注意gt是一個結(jié)構(gòu)體的指針函數(shù)
gt(&y); //這是一個函數(shù)的返回結(jié)果函數(shù) //取y結(jié)構(gòu)的地址傳入函數(shù)
out(y);
out(*gt(&y)); // (里邊)的都是做為參數(shù) *gt(&y)做為指針返回值 這個函數(shù)它的返回用指針表示
print(gt(&y)); //gt(&y)是一個返回值 這樣表示的是利用gt函數(shù)的返回值在print函數(shù)里邊操作
//*get(&y) = (struct point){ 1,2 }; //這也可以做的
}
struct point* gt(struct point*p) {// *p要的是&y的地址
scanf(”%d“, &p-》x);
scanf(”%d“, &p-》y);
printf(”a=%d,%d
“, p-》x, p-》y);//用-》來訪問指針結(jié)構(gòu)里邊的成員
return p;// 用完指針后 返回指針
}
void out(struct point p) {
printf(”b=%d,%d
“, p.x, p.y);
}
void print(const struct point *p) {//加上const表示不再改動參數(shù)
printf(”c=%d,%d
“, p-》x, p-》y);
}
指向結(jié)構(gòu)體數(shù)組的指針:
在之前講數(shù)值型數(shù)組的時候可以將數(shù)組名賦給一個指針變量,從而使該指針變量指向數(shù)組的首地址,然后用指針訪問數(shù)組的元素。結(jié)構(gòu)體數(shù)組也是數(shù)組,所以同樣可以這么做。我們知道,結(jié)構(gòu)體數(shù)組的每一個元素都是一個結(jié)構(gòu)體變量。如果定義一個結(jié)構(gòu)體指針變量并把結(jié)構(gòu)體數(shù)組的數(shù)組名賦給這個指針變量的話,就意味著將結(jié)構(gòu)體數(shù)組的第一個元素,即第一個結(jié)構(gòu)體變量的地址,也即第一個結(jié)構(gòu)變量中的第一個成員的地址賦給了這個指針變量
typedef 別名
typedef是在編程語言中用來為復雜的聲明定義簡單的別名,新的名字是某種類型的別名,這樣做改善了程序的可讀性,它與宏定義有些差異。它本身是一種存儲類的關(guān)鍵字,與auto、extern、mutable、static、register等關(guān)鍵字不能出現(xiàn)在同一個表達式中。
typedef為C語言的關(guān)鍵字,功能是用來聲明一個已有的數(shù)據(jù)類型的新名字,比如 typedef int last ; 這就使得last成為 int 類型的別名 這樣last這個名字就可以代替int出現(xiàn)在變量定義和參數(shù)聲明的地方了
typedef也有一個特別的長處:它符合范圍規(guī)則,使用typedef定義的變量類型其作用范圍限制在所定義的函數(shù)或者文件內(nèi)(取決于此變量定義的位置),而宏定義則沒有這種特性。
結(jié)構(gòu)體的內(nèi)存對齊方式(存儲空間)
結(jié)構(gòu)體內(nèi)存對齊:一個結(jié)構(gòu)體變量定義完之后,其在內(nèi)存中的存儲并不等于其所包含元素的寬度之和,元素是按照定義順序一個一個放到內(nèi)存中去的,但并不是緊密排列的。從結(jié)構(gòu)體存儲的首地址開始,每個元素放置到內(nèi)存中時,它都會認為內(nèi)存是按照自己的大小來劃分的,因此元素放置的位置一定會在自己寬度的整數(shù)倍上開始。
內(nèi)存對齊可以大大提升內(nèi)存訪問速度,是一種用空間換時間的方法。內(nèi)存不對齊會導致每次讀取數(shù)據(jù)都會讀取兩次,使得內(nèi)存讀取速度減慢。
cpu把內(nèi)存當成是一塊一塊的,塊的大小可以是2,4,8,16 個字節(jié),因此CPU在讀取內(nèi)存的時候是一塊一塊進行讀取的,塊的大小稱為內(nèi)存讀取粒度。
如果結(jié)構(gòu)體內(nèi)存在長度大于處理器位數(shù)的元素,那么就以處理器的倍數(shù)為對齊單位;否則,如果結(jié)構(gòu)體內(nèi)的元素的長度都小于處理器的倍數(shù)的時候,便以結(jié)構(gòu)體里面最長的數(shù)據(jù)元素為對齊單位。
另外 結(jié)構(gòu)體的內(nèi)存地址就是它第一個成員變量的地址 isa永遠都是結(jié)構(gòu)體中的第一個成員變量 所以結(jié)構(gòu)體的地址也就是其isa指針的地址
內(nèi)存對齊簡介
由于內(nèi)存的讀取時間遠遠小于CPU的存儲速度,這里用設定數(shù)據(jù)結(jié)構(gòu)的對齊系數(shù),即犧牲空間來換取時間的思想來提高CPU的存儲效率。
內(nèi)存對齊”應該是編譯器的“管轄范圍”。編譯器為程序中的每個“數(shù)據(jù)單元”安排在適當?shù)奈恢蒙?。但是C語言的一個特點就是太靈活,太強大,它允許你干預“內(nèi)存對齊”。如果你想了解更加底層的秘密,“內(nèi)存對齊”對你就不應該再模糊了。這也是一個大小端模式的問題
每個特定平臺上的編譯器都有自己的默認“對齊系數(shù)”(也叫對齊模數(shù))。程序員可以通過預編譯命令#pragma pack(n)來改變這一系數(shù),其中的n就是你要指定的“對齊系數(shù)”。
規(guī)則:
1、數(shù)據(jù)成員對齊規(guī)則:結(jié)構(gòu)(struct)(或聯(lián)合(union))的數(shù)據(jù)成員,第一個數(shù)據(jù)成員放在offset為0的地方,以后每個數(shù)據(jù)成員的對齊「按照#pragma pack指定的數(shù)值和這個數(shù)據(jù)成員自身長度中,比較小的那個進行」。
2、結(jié)構(gòu)(或聯(lián)合)的整體對齊規(guī)則:在數(shù)據(jù)成員完成各自對齊之后,結(jié)構(gòu)(或聯(lián)合)本身也要進行對齊,對齊將「按照#pragma pack 指定的數(shù)值和結(jié)構(gòu)(或聯(lián)合) 最大數(shù)據(jù)成員長度中,比較小的那個進行」對齊。
3、結(jié)合1、2可推斷:當#pragma pack的n值等于或超過所有數(shù)據(jù)成員長度的時候,這個n值的大小將不產(chǎn)生任何效果。
#pragma pack(n) 設定變量以n字節(jié)為對齊方式:
「作用」:指定結(jié)構(gòu)體、聯(lián)合以及類成員
「語法」:#pragma pack( [show] | [push | pop] [, identifier], n )
1,pack提供數(shù)據(jù)聲明級別的控制,對定義不起作用;
2,調(diào)用pack時不指定參數(shù),n將被設成默認值;
n:可選參數(shù);指定packing的數(shù)值,以字節(jié)為單位;缺省數(shù)值是8,合法的數(shù)值分別是1、2、4、8、16。其他參數(shù)都是可選的可先不了解
每個成員分別對齊,即每個成員按自己的方式對齊,并最小化長度;規(guī)則就是每個成員按其類型的對齊參數(shù)(通常是這個類型的大小)和指定對齊參數(shù)中較小的一個對齊。
大小端:
如:int 11 22 33 44
在存儲的時候
大端:11 22 33 44
0 1 2 3
低地址----》 高地址
小端:44 33 22 11
0 1 2 3
低地址----》 高地址
大小端的差異在于存放順序不同
常見的操作系統(tǒng)是小端,通訊協(xié)議是大端。
//結(jié)構(gòu)體例子:使用尾插法創(chuàng)建鏈表#include《stdio.h》//單鏈表的創(chuàng)建typedef struct _node {
int nb;//數(shù)值
struct _node *nxte;//定義一個指向下一個的節(jié)點的指針
}NODE;
typedef struct _link{//利用這個結(jié)構(gòu)體 封裝 首尾節(jié)點
NODE *head;
NODE *qt;
}link;
void add(link *phead, link *qt, int n);//定義函數(shù)將 首尾指針傳入int main(void) {
link head, q;//定義一個結(jié)構(gòu),連指針都不是的
head.head = q.qt = NULL;//初始化
int n;
for (scanf(”%d“, &n); n != -1; scanf(”%d“, &n)) {
add(&head, &q, n);//將地址 值傳入
}
NODE *t;
t = head.head;//利用臨時結(jié)構(gòu)將鏈表輸出
for (; t; t = t-》nxte) {
printf(”%d “, t-》nb);
}
return 0;
}
//尾插法void add(link *phead, link *qt, int n) {
NODE *p = (NODE*)malloc(sizeof(NODE));//為新結(jié)點開辟空間
p-》nb = n;
p-》nxte = NULL;
if (phead-》head == NULL) {//判斷首結(jié)點是否為空
phead-》head = p;//是空的就讓首結(jié)點等于新結(jié)點
}
else {//不為空時,讓尾結(jié)點依次跑到后面去
qt-》qt-》nxte = p;
}
qt-》qt = p;
}
4、union 共用體(聯(lián)合體)在進行某些算法的C語言編程的時候,需要使幾種不同類型的變量存放到同一段內(nèi)存單元中。也就是使用覆蓋技術(shù),幾個變量互相覆蓋。這種幾個不同的變量共同占用一段內(nèi)存的結(jié)構(gòu),在C語言中 以關(guān)鍵字union聲明的一種數(shù)據(jù)結(jié)構(gòu),這種被稱作“共用體”類型結(jié)構(gòu),也叫聯(lián)合體。
“聯(lián)合”與“結(jié)構(gòu)”有一些相似之處。但兩者有本質(zhì)上的不同。在結(jié)構(gòu)中各成員有各自的內(nèi)存空間,一個結(jié)構(gòu)體變量的總長度大于等于各成員長度之和。而在“聯(lián)合”中,各成員共享一段內(nèi)存空間,一個聯(lián)合變量的長度等于各成員中最長的長度。注意這里所謂的共享不是指把多個成員同時裝入一個聯(lián)合變量內(nèi),而是指該聯(lián)合變量可被賦予任一成員值,但每次只能賦一種值,賦入新值則沖去舊值,共用體變量中起作用的成員是最后一次存放的成員,在存入一個新成員后,原有成員就失去作用,共用體變量的地址和它的各成員的地址都是同一地址
一個聯(lián)合類型必須經(jīng)過定義之后,才能把變量說明為該聯(lián)合類型:
注意:1、不能把共用體變量作為函數(shù)參數(shù),也不能是函數(shù)帶回共用體變量,但可以使專用指向共用體變量的指針
2、所有成員占用同一段內(nèi)存,修改一制個成員會影響其余所有成員。
共用體的訪問:共用體訪問成員的值時一般使用。運算符,指針時用-》運算符(和結(jié)構(gòu)體是一樣的)
typedef union _node {
int a;
double b;
char c;
union _node *p;
}NODE;
int main(void) {
NODE a;//定義變量
NODE t;
a.b;//用。訪問
t.p-》a;//指針用-》訪問
}
聯(lián)合的使用規(guī)則幾乎和結(jié)構(gòu)體strtct的規(guī)則用法一樣,只不過是內(nèi)部表示的不同。
補充:
還有一個是無名聯(lián)合體,它是和無名結(jié)構(gòu)體的工作原理是相同的
#include《stdio.h》//簡單的例子#include《string.h》typedef union _node{
int a;
double b;
char c[20];
}NODE;
int main(void) {
NODE a;//這里只定義一個變量
a.a = 666;
printf(”%d
“, a.a);
a.b = 9.99;
printf(”%f
“, a.b);
strcpy(a.c, ”hello world!“);
printf(”%s
“, a.c);
//我們看到,三個都被完整的輸出了,因為在同一時刻,只有一個成員是有效的
}
輸出:
6669.990000
hellow world!
共用體的作用:
1、節(jié)省內(nèi)存,有兩個很長的數(shù)據(jù)結(jié)構(gòu),不會同時使用,比如一個表示老師,一個表示學生,如果要統(tǒng)計教師和學生的情況用結(jié)構(gòu)體的話就有點浪費了!用結(jié)構(gòu)體的話,只占用最長的那個數(shù)據(jù)結(jié)構(gòu)所占用的空間,就足夠了!
2、實現(xiàn)不同類型數(shù)據(jù)之間的類型轉(zhuǎn)換,遇到各種類型的數(shù)據(jù)共用存儲空間,很方便的實現(xiàn)了不同數(shù)據(jù)類型之間的轉(zhuǎn)換,不需要顯示的強制類型轉(zhuǎn)換。
其他:1、確定CPU的模式:大端、小端模式確定
大小端不同,則存儲的方式也存在差別,比如int需要4個字節(jié),而char只需要1個字節(jié),根據(jù)1個字節(jié)所在的具體位置即可判定CPU的模式
2、寄存器的定義,實現(xiàn)整體的訪問和單項的訪問//共用體綜合例子:根據(jù)輸入的數(shù)據(jù)類型輸出需要的相應的數(shù)據(jù)#include《stdio.h》#include《string.h》//數(shù)據(jù)類型輸出 5*4 m n n的第幾個x union node {
int a;
double b;
char c[30];
}add[10000];
char p[10000][30]; //保存的字符串數(shù)組int main(void) {
int n, m;
scanf(”%d %d“, &n, &m);
int x;
double y;
char t[50];
int i, j;
for (i = 0; i 《 n; i++) {//輸入
scanf(”%s“, &p[i]);//作為字符串數(shù)組,需要取地址
if (strcmp(”INT“, p[i]) == 0) {//整形
scanf(”%d“, &x);
add[i].a = x;
}
else if(strcmp(”DOUBLE“,p[i])==0){//浮點
scanf(”%lf“, &y);
add[i].b = y;
}
else if (strcmp(”STRCING“, p[i]) == 0) {//字符串
scanf(”%s“, t);
strcpy(add[i].c, t);
}
}
for (i = 0; i 《 m; i++) {//輸出
scanf(”%d“, &j);
if (strcmp(”INT“, p[j]) == 0) {
printf(”%d
“, add[j].a);
}
else if (strcmp(”DOUBLE“, p[j]) == 0)
{
printf(”%f
“, add[j].b);
}else if(strcmp(”STRING“,p[j])==0)
{
printf(”%s
“, add[j].c);
}
}
return 0;
}
//輸入:/*
5 4
INT 456
DOUBLE 123.56
DOUBLE 0.476
STRING welcomeToC
STRING LemonTree
0
1
2
4
*///輸出:/*
456
123.56
0.48
LemonTree
*/
編輯:jq
-
cpu
+關(guān)注
關(guān)注
68文章
10889瀏覽量
212401 -
數(shù)據(jù)
+關(guān)注
關(guān)注
8文章
7103瀏覽量
89287 -
C語言
+關(guān)注
關(guān)注
180文章
7614瀏覽量
137249
原文標題:C語言知識總結(jié):宏,枚舉,結(jié)構(gòu)體,共用體
文章出處:【微信號:gh_c472c2199c88,微信公眾號:嵌入式微處理器】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
相關(guān)推薦
評論