在c語言中,結構體數據類型與共用體數據類型都屬於構造類型。共用體與結構體數據類型在定義上十分相似,但它們在存儲空間的占用分配上有本質的區別。結構體變量是各種類型數據的集合,各成員占據不同的存儲空間,而共用體變量的所有成員占用相同的存儲空間,在某一時刻只有一個成員起作用。
(1)共用體類型的定義
定義共用體類型的一般形式:
union 共用體類型名
{
數據類型 成員名1;
數據類型 成員名2;
數據類型 成員名3;
......
};
以上定義了一個名為data的共用體類型。它說明該類型由三個不同類型的成員組成,這些成員共享同一塊存儲空間。
(2)共用體變量的定義
與結構體變量的定義類似;
(3)共用體變量的引用和初始化
1.引用共用體變量中的一個成員
引用共用體變量的成員的一般形式
共用體變量名.成員名
共用體指針變量->成員名
第一種引用方式應用於普通共用體變量,第二種引用方式應用於共用體指針變量。
union data a,*p=&a;
2.共用體類型變量的整體引用
可以將一個共用體變量作為一個整體賦給另一個同類型的共用體變量。例如:
union data a,b;
......
a=b;
3.共用體變量的初始化
在共用體變量定義的同時只能用第一個成員的類型值進行初始化,共用體變量初始化的一般形式:
union 共用體類型名 共用體變量={第一個成員的類型名};
例如:
union data a={8};
請注意:
1)在對共用體變量進行初始化時,盡管只能給第一個成員變量賦值,但必須用花括號括起來。
2)不能對共用體變量名賦值,不能通過引用變量名得到其成員的值。
3)不可以在定義共用體變量時對它初始化。(意思可能是在定義共用體類型的同時定義結構體變量時,不能初始化,以后細細探索。)
說明
*共用體變量的地址及其各成員的地址都是同一地址,因為各成員地址的分配都是從共用體變量空間的起點開始的。
*不能使用共用體變量作為函數參數,也不能使用函數返回共用體變量,但可以使用它指向共用體變量的指針(與結構體變量用法相似);
*共用體變量可以出現在結構體類型的定義中,也可以定義結構體數組。
例子:輸入學生的不同類型的成績(百分制,等級制),運行程序后屏幕輸出學生的成績表。以flag為標志不同類型的成績,flag=0作為百分制成績的標志,非0是等級制成績的標志。
1 #include<stdio.h> 2 3 struct c 4 { 5 char flag; 6 int num; 7 union stu 8 { 9 int score; 10 char s; 11 }cg; 12 }pe[3]; 13 14 int main(){ 15 int i,m; 16 for(i=0;i<3;i++){ 17 printf("在輸入成績之前先輸入0或1代表是百分制還是等級制!\n"); 18 scanf("%d",&pe[i].flag); 19 if(pe[i].flag==0){ 20 printf("請連續輸入學號和百分制成績,以逗號間隔!\n"); 21 scanf("%d,%d",&pe[i].num,&pe[i].cg.score); 22 }else{ 23 printf("請連續輸入學號和等級制成績,以逗號間隔!\n"); 24 scanf("%d,%c",&pe[i].num,&pe[i].cg.s); 25 } 26 } 27 for(i=0;i<3;i++){ 28 if(pe[i].flag==0){ 29 printf("%4d%4d\n",pe[i].num,pe[i].cg.score); 30 }else 31 printf("%4d %c\n",pe[i].num,pe[i].cg.s); 32 } 33 return 0; 34 }
在這個例子中需要注意的有兩點:
1.在同時用是scanf輸入兩個數據時最好自定義它們的分隔方法,例如逗號。
2.在共用體類型的定義中我原先用的是 char s[10],而不是char s;但雖然在是scanf語句中用%c來接收s但並不報錯,只是輸出結果會出問題。這里要注意%S和%C的正確使用。
(4)枚舉類型
當一個變量的取值只限定為幾種可能時,就可以使用枚舉類型。枚舉類型是將可能的取值一一列出來,那么變量的取值范圍也就在列舉值的范圍之內。
1.枚舉類型的說明:
枚舉類型說明的一般形式:
enum 枚舉類型名{枚舉值1,枚舉值2......};
例如:
enum flag{true,false};//只允許兩個值
enum weekday{sun,mon,tue,wed,thu,fri,sat};//只允許7個值
枚舉類型的說明只是規定了枚舉的類型和該類型只允許取哪幾個值,它並不分配內存。
2.枚舉類型變量的定義
1)進行枚舉類型說明的同時定義枚舉類型變量。
enum flag{true,false}a,b;
2)用無名枚舉類型
enum {true,false} a,b;
3)枚舉類型說明和枚舉型變量定義分開。
enum flag{true,flase};
enum flag a,b;
以上三種形式的定義其作用是相同的,他們所定義的枚舉變量a和b,都只能取true或者false兩個值。在編譯時,為變量的定義分配內存,一個枚舉類型變量所占的空間與int型相同。
說明:
*枚舉類型本身就是常量,不允許對其進行賦值操作;
例如:true=1;false=0;都是錯誤的。
*在c語言中枚舉值被處理成一個整型的常量,此常量的值取決於說明各枚舉值排列的先后次序,第一個枚舉值序號為0,因此它的值為0,以后依次加一。
例如:enum weekday{sun,mon,tue,wed,thu,fri,sat}workday;其中sun的值為0,sat的值為6。
要想使sun的值為7,mon的值為1,則可以這樣指定{sun=7,mon=1,tue,wed,thu,fri,sat};對於沒有指定值的元素,其取值規則仍按所處的順序取。
*枚舉值之間可以相互比較
tue>wed。這實際上是在進行2>3的比較
*整數不能直接賦值給枚舉變量
例如:workday=3;
這是錯誤的。因為workday是枚舉類型的變量,而3是整型變量,不同的數據類型在賦值時,必須將賦值號右邊強制轉換為左邊變量的類型后再賦值。
例如:workday=(enum weekday)(5-3);
示例:盒子里有若干個五種顏色(紅,黃,藍,白,黑)的彩球。每次從盒子里取出3個球,問得到3種不同顏色的球的可能取法,並輸出每種組合的3種顏色。
1 #include<stdio.h> 2 3 int main(){ 4 enum color{red=0,yellow=1,blue=2,white=3,black=4}; 5 //i,j,k用來標識抽出的每種球的顏色,t用來遍歷他們三個! 6 enum color i,j,k,t; 7 //n用來記錄總的可能,lp是用來在每種可能中的循環變量 8 int n=0,lp; 9 for(i=red;i<=black;i++) 10 { 11 for(j=red;j<=black;j++){ 12 if(i!=j){ 13 for(k=red;k<=black;k++){ 14 if((k!=i)&&(k!=j)) 15 { 16 printf("%-4d",n=n+1); 17 for(lp=1;lp<=3;lp++){ 18 switch(lp) 19 { 20 case 1:t=i;break; 21 case 2:t=j;break; 22 case 3:t=k;break; 23 default:break; 24 } 25 switch(t){ 26 case red: printf("%10s","red");break; 27 case yellow: printf("%10s","yellow");break; 28 case blue: printf("%10s","blue");break; 29 case white: printf("%10s","white");break; 30 case black: printf("%10s","black");break; 31 } 32 } 33 printf("\n"); 34 } 35 } 36 printf("total: %5d\n",n); 37 } 38 } 39 } 40 return 0; 41 }
上述代碼產生了這個錯誤:
原因是在c語言中無法進行枚舉類型的自增自減運算,因為在i++時,i自動被轉化為整型,需要進行強制類型轉換才能進行重新賦值給i。
解決方法有:
1:將代碼 enum color i,j,k,t;改為int i,j,l,t;
2或者將i++改為i=(enum color)(i+1);
1 #include<stdio.h> 2 3 int main(){ 4 enum color{red,yellow,blue,white,black}; 5 //i,j,k用來標識抽出的每種球的顏色,t用來遍歷他們三個! 6 enum color i,j,k,t;//(1)或者直接改為int i,j,k,t; 7 //n用來記錄總的可能,lp是用來在每種可能中的循環變量 8 int n=0,lp; 9 //(2)進行強制類型轉換 10 for(i=red;i<=black;i=(enum color)(i+1)) 11 { 12 for(j=red;j<=black;j=(enum color)(j+1)){ 13 if(i!=j){ 14 for(k=red;k<=black;k=(enum color)(k+1)){ 15 if((k!=i)&&(k!=j)) 16 { 17 printf("%-4d",n++); 18 for(lp=1;lp<=3;lp++){ 19 switch(lp) 20 { 21 case 1:t=i;break; 22 case 2:t=j;break; 23 case 3:t=k;break; 24 default:break; 25 } 26 switch(t){ 27 case red: printf("%10s","red");break; 28 case yellow: printf("%10s","yellow");break; 29 case blue: printf("%10s","blue");break; 30 case white: printf("%10s","white");break; 31 case black: printf("%10s","black");break; 32 } 33 } 34 printf("\n"); 35 } 36 } 37 } 38 } 39 } 40 printf("total: %5d\n",n); 41 return 0; 42 }
總而言之,課本上的代碼也不是全對,總是遇到各種問題,不過總算是解決了。
不過根據錯誤來看,應該還可以用++操作符重載的方式來解決這個問題。以后再看。
(5)用typedef定義類型
在c語言中,可以用typedef定義新的類型名來代替已有的類型名。定義的一般形式為:
typedef 類型名 新名稱;
typedef是類型定義中的關鍵字,“類型名”是c語言中的已有的類型(如int,float),“新名稱”是用戶自定義的新名,新名稱在習慣上用大些字母表示。
舉例:
1)定義一個新的結構體類型DATE:
typedef struct
{
int month;
int day;
int year;
}DATE;
DATE d1,d2;
2)typedef int ARR[10];定義ARR是整型數組類型
ARR m,n;//m和n都被定義為一維數組,包含10個元素。
3)typedef char *STR;//定義STR是字符指針類型
STR p,x[10];//定義p為字符指針變量,x為字符指針數組。
4)typedef int *PI;//定義PI為整型指針類型
PI p,*q,r[20];//定義p為整型指針變量,q為指向int型指針的指針變量,r為具有20個指向整型類型元素的指針數組。
說明:
*用typedef可以聲明各種類型名,但不能用來定義變量
*用typedef只是對已經存在的類型增加一個類型的新名稱,而沒有構造新的類型。
*如果在不同源文件中使用同一類型數據,常用typedef說明這些數據類型,並把它們單獨放在一個文件中,然后在需要時,用#include命令把他們包含進來。
(6)鏈表及其簡單操作
數組是一批有先后次序的元素構成的序列,可以用下標隨意訪問元素而且操作簡單。但在利用數組存放數據時必須事先定義固定的數組長度,勢必會造成內存空間的浪費。而且不利於插入和刪除數據。
鏈表是結構體類型的重要應用之一,也是一種有先后次序的序列,但是鏈表中的元素可以根據需要動態開辟內存單元。鏈表常被看作是與數組互補存在的一種數據構成方式。
1)鏈表的概念
在鏈表中,所有數據元素都被保存在一個據有相同數據結構的節點中,節點是鏈表的基本存儲單位。一個節點與一個數據元素對應。每個節點在內存中使用一塊連續的存儲空間。把線性表的元素存放到一個由這種節點組成的鏈式存儲中,每個節點之間可以占用不連續的內存空間,節點與節點之間通過指針鏈接在一起,這種存儲方式叫做鏈表。
鏈表中每個節點至少由兩個部分組成,即數據域和指針域。節點的定義需要采用結構類型,其一般形式為
struct node{
int data; //數據域
struct node *link;//指針域
} ;
在實際應用中,要建立一個鏈表通常包括三部分內容:
頭指針,頭節點,數據節點。
鏈表的存儲空間可以在程序的運行期間動態分配,如果需要向鏈表中插入一個結點,只需調用C語言的動態空間分配函數(alloc,malloc函數)動態申請一個存儲結點存放相應信息,並把新申請的結點插入到鏈表的適當位置上。刪除一個結點意味着該結點被系統回收。C語言中free函數用來動態回收結點。
鏈表存儲結構具有如下特點:
*插入、刪除運算靈活方便,不需要移動結點,只需改變節點中指針域的值即可。
*可以實現動態分配和擴展空間,當表中數據是動態產生的時侯,最好使用鏈表。
*查找操作只能從鏈表的頭結點開始順序查找,不適合有大量頻繁查找的表。
2)鏈表的基本操作
鏈表的基本操作有:建立鏈表、遍歷鏈表、向鏈表中插入或刪除結點、求鏈表長度等,這里僅介紹有關鏈表最基本的概念和相關操作。后續將會在數據結構的博客中詳細介紹。
1.建立鏈表
建立鏈表首先要定義一個包含數據域和指針域的結構類型,然后定義一個指向表頭結點的頭指針,最后通過調用malloc函數動態申請結點的方法建立整個鏈表。
例子:程序中統計90分以上的成績個數,當學號輸入為零時將結束鏈表的建立。
1 #include<stdio.h> 2 #include<stdlib.h> 3 typedef struct student{ 4 int num; 5 int score; 6 struct student *link; 7 } NODE; 8 9 NODE *creat(){ 10 NODE *head=NULL,*p1=NULL,*p2=NULL; 11 head=p1=p2=(NODE *)malloc(sizeof(NODE)); 12 printf("請輸入學號和成績:\n"); 13 scanf("%d,%d",&p1->num,&p1->score); 14 p1->link=NULL; 15 while(p1->num!=0){ 16 p1=(NODE *)malloc(sizeof(NODE)); 17 p1->link=NULL; 18 printf("請輸入學號和成績:\n"); 19 scanf("%d,%d",&p1->num,&p1->score); 20 p2->link=p1; 21 p2=p1; 22 } 23 return head; 24 } 25 int count(NODE *p){ 26 int sum=0; 27 while(p!=NULL){ 28 if(p->score>=90){ 29 sum++; 30 } 31 p=p->link; 32 } 33 return sum; 34 } 35 int main(){ 36 NODE *p; 37 p=creat(); 38 printf("\n the count of 90 =%d\n",count(p)); 39 return 0; 40 }
這里用一個頭指針指向該鏈表,然后用兩個指針變量的交替操作增加鏈表的長度,過程如下:
2.鏈表的遍歷
所謂鏈表的遍歷就是逐一訪問鏈表中的每個結點:
設線性鏈表頭指針為h,設指針變量p指向不同的結點。沿着鏈表開頭向后查找結點中學號為x的結點。若找到則返回鏈表中該結點的位置,否則返回空地址。
NODE *search(NODE *L,int x){ NODE *p; p=L->link; while(p!=NULL&&p->num!=x){ p=p->link; } return (p); }
3.鏈表的插入
要在單鏈表中的兩個數據元素a和b之間插入一個數據元素x,首先將指針p指向結點a,然后生成一個新的結點,使其數據域為x,並使結點a中的link域指向新結點x,x的link域則指向結點b,即完成插入操作。
插入操作的程序如下:
void listInsert_L(NODE *L,int i,int x){ NODE *p=L,*s; int j=0; //尋找第i個結點 while(p&&j<i-1){ p=p->link; ++j; } //插入位置錯誤 if(!p||j>i-1){ return 0; } s=(NODE *)malloc(sizeof(NODE)); s->num=x; s->link=p->link; p->link=s; return (1); }
4.線性鏈表的刪除
在a和b和c相鄰的單鏈表中刪除中間結點b的操作,只需將結點a的link域指向結點c,並釋放接待你占用的存儲空間即可。
單鏈表的刪除操作的程序如下:
void ListDelete(NODE *L,int i,int,x){ NODE *p=L,*q; int j=0; while(p->link&&j<i-1){ p=p->link; ++p; //尋找第i個結點,並令p指向該前驅 } if(!(p->link)||j>i-1) return (0);//刪除位置錯誤 q=p->link; //刪除並釋放結點 p->link=q->link; free(q); return (1); }
求鏈表長度
由於鏈表的長度不是預先確定,而是動態建立的,因此求鏈表的長度需要遍歷鏈表的所有結點才能完成.
int Length(){ NODE *p=L; int count=0; while(p!=NULL){ count++; p=p->link; } return (count); }
鏈表實例:某班有20名學生,每名學生的數據包括學號、姓名、3門課的成績,從鍵盤輸入20名學生的數據,要求打印出3門課的總平均成績,以及最高分的學生的數據。
1 #include<stdio.h> 2 #include<stdlib.h> 3 #include<time.h> 4 #include<string.h> 5 6 struct student{ 7 long num; 8 char name[10]; 9 int course[3]; 10 int aver; 11 struct student *next; 12 }; 13 typedef struct student NODE; 14 15 int main(){ 16 srand(time(NULL)); 17 NODE *head=NULL; 18 NODE *p1=NULL,*p2=NULL; 19 int i; 20 for(i=0;i<20;i++){ 21 int j; 22 if(i==0){ 23 head=p2=p1=(NODE *)malloc(sizeof(NODE)); 24 }else{ 25 p1=(NODE *)malloc(sizeof(NODE)); 26 } 27 p1->num=14001+i; 28 int sum=0; 29 for(j=0;j<3;j++){ 30 p1->course[j]=rand()%80+20; 31 } 32 for(j=0;j<3;j++){ 33 sum+=p1->course[j]; 34 } 35 p1->aver=sum/3; 36 char s[10]; 37 itoa(i+1,s,10); 38 strcpy(p1->name,"student"); 39 strcat(p1->name,s); 40 if(i!=0){ 41 p2->next=p1; 42 p2=p1; 43 } 44 } 45 printf("下面打印出20名學生的信息:\n"); 46 p1=head; 47 NODE *maxNode=head; 48 while(p1!=NULL){ 49 if(maxNode->aver<p1->aver){ 50 maxNode=p1; 51 } 52 printf("num=%ld,name=%9s,aver=%d\n",p1->num,p1->name,p1->aver); 53 p1=p1->next; 54 } 55 printf("平均分最高的學生信息為:\n"); 56 printf("num=%d,name=%9s,course[0]=%d,course[2]=%d,course[3]=%d,aver=%d", 57 maxNode->num,maxNode->name,maxNode->course[0],maxNode->course[1],maxNode->course[2],maxNode->aver); 58 return 0; 59 }
值得注意的地方:
1.c語言中是沒有string類型的,所以當用到字符串時都是用char類型的數組;
2.strcpy(s1,s2)是s2中內容加到s1上;
3.strcat(s1,s2)是將s2的內容加到s1的尾部;
4.itoa(a,str,10);將10進制整數轉化為字符串后存到str中;