?? 7_結構與聯合.txt
字號:
char *name;
char sex;
float score;
} boy1={102,"Zhang ping",'M',78.5},*pstu;
main()
{
pstu=&boy1;
printf("Number=%d\nName=%s\n",boy1.num,boy1.name);
printf("Sex=%c\nScore=%f\n\n",boy1.sex,boy1.score);
printf("Number=%d\nName=%s\n",(*pstu).num,(*pstu).name);
printf("Sex=%c\nScore=%f\n\n",(*pstu).sex,(*pstu).score);
printf("Number=%d\nName=%s\n",pstu->num,pstu->name);
printf("Sex=%c\nScore=%f\n\n",pstu->sex,pstu->score);
}
本例程序定義了一個結構stu,定義了stu類型結構變量boy1 并
作了初始化賦值,還定義了一個指向stu類型結構的指針變量pstu。
在main函數中,pstu被賦予boy1的地址,因此pstu指向boy1 。
然后在printf語句內用三種形式輸出boy1的各個成員值。 從運行結
果可以看出: 結構變量.成員名
(*結構指針變量).成員名
結構指針變量->成員名 這三種用于表示結構成員的形式是完全等效的。結構數組指針變量
結構指針變量可以指向一個結構數組, 這時結構指針變量的值
是整個結構數組的首地址。 結構指針變量也可指向結構數組的一個
元素,這時結構指針變量的值是該結構數組元素的首地址。
設ps為指向結構數組的指針變量,則ps也指向該結構數組的0號
元素,ps+1指向1號元素,ps+i則指向i號元素。 這與普通數組的情
況是一致的。[例7.7]用指針變量輸出結構數組。struct stu
{
int num;
char *name;
char sex;
float score;
}boy[5]={
{101,"Zhou ping",'M',45},
{102,"Zhang ping",'M',62.5},
{103,"Liou fang",'F',92.5},
{104,"Cheng ling",'F',87},
{105,"Wang ming",'M',58},
};
main()
{
struct stu *ps;
printf("No\tName\t\t\tSex\tScore\t\n");
for(ps=boy;ps<boy+5;ps++)
printf("%d\t%s\t\t%c\t%f\t\n",ps->num,ps->name,ps->sex,ps->
score);
}
在程序中,定義了stu結構類型的外部數組boy 并作了初始化賦
值。在main函數內定義ps為指向stu類型的指針。在循環語句for 的
表達式1中,ps被賦予boy的首地址,然后循環5次,輸出boy 數組中
各成員值。 應該注意的是, 一個結構指針變量雖然可以用來訪問結構變量
或結構數組元素的成員,但是,不能使它指向一個成員。 也就是說
不允許取一個成員的地址來賦予它。因此,下面的賦值是錯誤的。 ps=&boy[1].sex;而只能是:
ps=boy;(賦予數組首地址)
或者是:
ps=&boy[0];(賦予0號元素首地址)結構指針變量作函數參數
在ANSI C標準中允許用結構變量作函數參數進行整體傳送。 但
是這種傳送要將全部成員逐個傳送, 特別是成員為數組時將會使傳
送的時間和空間開銷很大,嚴重地降低了程序的效率。 因此最好的
辦法就是使用指針,即用指針變量作函數參數進行傳送。 這時由實
參傳向形參的只是地址,從而減少了時間和空間的開銷。[例7.8]題目與例7.4相同,計算一組學生的平均成績和不及格人數。
用結構指針變量作函數參數編程。struct stu
{
int num;
char *name;
char sex;
float score;}boy[5]={
{101,"Li ping",'M',45},
{102,"Zhang ping",'M',62.5},
{103,"He fang",'F',92.5},
{104,"Cheng ling",'F',87},
{105,"Wang ming",'M',58},
};
main()
{
struct stu *ps;
void ave(struct stu *ps);
ps=boy;
ave(ps);
}
void ave(struct stu *ps)
{
int c=0,i;
float ave,s=0;
for(i=0;i<5;i++,ps++)
{
s+=ps->score;
if(ps->score<60) c+=1;
}
printf("s=%f\n",s);
ave=s/5;
printf("average=%f\ncount=%d\n",ave,c);
}
本程序中定義了函數ave,其形參為結構指針變量ps。boy 被定
義為外部結構數組,因此在整個源程序中有效。在main 函數中定義
說明了結構指針變量ps,并把boy的首地址賦予它,使ps指向boy 數
組。然后以ps作實參調用函數ave。在函數ave 中完成計算平均成績
和統計不及格人數的工作并輸出結果。
與例7.4程序相比,由于本程序全部采用指針變量作運算和處理,
故速度更快,程序效率更高。.topoic=動態存儲分配
在數組一章中,曾介紹過數組的長度是預先定義好的, 在整個
程序中固定不變。C語言中不允許動態數組類型。例如: int n;
scanf("%d",&n);
int a[n]; 用變量表示長度,想對數組的大小作動態說明, 這是錯誤的。
但是在實際的編程中,往往會發生這種情況, 即所需的內存空間取
決于實際輸入的數據,而無法預先確定。對于這種問題, 用數組的
辦法很難解決。為了解決上述問題,C語言提供了一些內存管理函數,
這些內存管理函數可以按需要動態地分配內存空間, 也可把不再使
用的空間回收待用,為有效地利用內存資源提供了手段。 常用的內
存管理函數有以下三個:
1.分配內存空間函數malloc調用形式: (類型說明符*) malloc (size) 功能:在內存的動態存儲區中分配一塊長度為"size" 字節的連續區
域。函數的返回值為該區域的首地址。 “類型說明符”表示
把該區域用于何種數據類型。(類型說明符*)表示把返回值強
制轉換為該類型指針。“size”是一個無符號數。例如: pc=(char *) malloc (100); 表示分配100個字節的內存空間,并強制轉換為字符數組類型,
函數的返回值為指向該字符數組的指針, 把該指針賦予指針
變量pc。
2.分配內存空間函數 calloc
calloc 也用于分配內存空間。調用形式: (類型說明符*)calloc(n,size) 功能:在內存動態存儲區中分配n塊長度為“size”字節的連續區域。
函數的返回值為該區域的首地址。(類型說明符*)用于強制類
型轉換。calloc函數與malloc 函數的區別僅在于一次可以分
配n塊區域。例如: ps=(struet stu*) calloc(2,sizeof (struct stu)); 其中的sizeof(struct stu)是求stu的結構長度。因此該語句
的意思是:按stu的長度分配2塊連續區域,強制轉換為stu類
型,并把其首地址賦予指針變量ps。
3.釋放內存空間函數free調用形式: free(void*ptr); 功能:釋放ptr所指向的一塊內存空間,ptr 是一個任意類型的指針
變量,它指向被釋放區域的首地址。被釋放區應是由malloc或
calloc函數所分配的區域:[例7.9]分配一塊區域,輸入一個學生數據。main()
{
struct stu
{
int num;
char *name;
char sex;
float score;
} *ps;
ps=(struct stu*)malloc(sizeof(struct stu));
ps->num=102;
ps->name="Zhang ping";
ps->sex='M';
ps->score=62.5;
printf("Number=%d\nName=%s\n",ps->num,ps->name);
printf("Sex=%c\nScore=%f\n",ps->sex,ps->score);
free(ps);
}
本例中,定義了結構stu,定義了stu類型指針變量ps。 然后分
配一塊stu大內存區,并把首地址賦予ps,使ps指向該區域。再以ps
為指向結構的指針變量對各成員賦值,并用printf 輸出各成員值。
最后用free函數釋放ps指向的內存空間。 整個程序包含了申請內存
空間、使用內存空間、釋放內存空間三個步驟, 實現存儲空間的動
態分配。鏈表的概念
在例7.9中采用了動態分配的辦法為一個結構分配內存空間。每
一次分配一塊空間可用來存放一個學生的數據, 我們可稱之為一個
結點。有多少個學生就應該申請分配多少塊內存空間, 也就是說要
建立多少個結點。當然用結構數組也可以完成上述工作, 但如果預
先不能準確把握學生人數,也就無法確定數組大小。 而且當學生留
級、退學之后也不能把該元素占用的空間從數組中釋放出來。 用動
態存儲的方法可以很好地解決這些問題。 有一個學生就分配一個結
點,無須預先確定學生的準確人數,某學生退學, 可刪去該結點,
并釋放該結點占用的存儲空間。從而節約了寶貴的內存資源。 另一
方面,用數組的方法必須占用一塊連續的內存區域。 而使用動態分
配時,每個結點之間可以是不連續的(結點內是連續的)。 結點之間
的聯系可以用指針實現。 即在結點結構中定義一個成員項用來存放
下一結點的首地址,這個用于存放地址的成員,常把它稱為指針域。
可在第一個結點的指針域內存入第二個結點的首地址, 在第二
個結點的指針域內又存放第三個結點的首地址, 如此串連下去直到
最后一個結點。最后一個結點因無后續結點連接,其指針域可賦為0。
這樣一種連接方式,在數據結構中稱為“鏈表”。圖7.3為鏈表的示
意圖。
在圖7.3中,第0個結點稱為頭結點, 它存放有第一個結點的首
地址,它沒有數據,只是一個指針變量。 以下的每個結點都分為兩
個域,一個是數據域,存放各種實際的數據,如學號num,姓名name,
性別sex和成績score等。另一個域為指針域, 存放下一結點的首地
址。鏈表中的每一個結點都是同一種結構類型。例如, 一個存放學
生學號和成績的結點應為以下結構:
struct stu
{ int num;
int score;
struct stu *next;
} 前兩個成員項組成數據域,后一個成員項next構成指針域, 它
是一個指向stu類型結構的指針變量。鏈表的基本操作
對鏈表的主要操作有以下幾種: 1.建立鏈表;
2.結構的查找與輸出;
3.插入一個結點;
4.刪除一個結點;下面通過例題來說明這些操作。[例7.10]建立一個三個結點的鏈表,存放學生數據。
為簡單起見, 我們假定學生數據結構中只有學號和年齡兩項。
可編寫一個建立鏈表的函數creat。程序如下:
#define NULL 0
#define TYPE struct stu
#define LEN sizeof (struct stu)
struct stu
{
int num;
int age;
struct stu *next;
};
TYPE *creat(int n)
{
struct stu *head,*pf,*pb;
int i;
for(i=0;i<n;i++)
{
pb=(TYPE*) malloc(LEN);
printf("input Number and Age\n");
scanf("%d%d",&pb->num,&pb->age);
if(i==0)
pf=head=pb;
else pf->next=pb;
pb->next=NULL;
pf=pb;
}
return(head);
}
在函數外首先用宏定義對三個符號常量作了定義。這里用 TYPE
表示struct stu,用LEN表示sizeof(struct stu)主要的目的是為了
在以下程序內減少書寫并使閱讀更加方便。結構stu定義為外部類型,
程序中的各個函數均可使用該定義。
creat函數用于建立一個有n個結點的鏈表,它是一個指針函數,
它返回的指針指向stu結構。在creat函數內定義了三個stu結構的指
針變量。head為頭指針,pf 為指向兩相鄰結點的前一結點的指針變
量。pb為后一結點的指針變量。在for語句內,用malloc函數?靡?
長度與stu長度相等的空間作為一結點,首地址賦予pb。然后輸入結
點數據。如果當前結點為第一結點(i==0),則把pb值 (該結點指針)
賦予head和pf。如非第一結點,則把pb值賦予pf 所指結點的指針域
成員next。而pb所指結點為當前的最后結點,其指針域賦NULL。 再
把pb值賦予pf以作下一次循環準備。
creat函數的形參n,表示所建鏈表的結點數,作為for語句的循
環次數。
圖7.4表示了creat函數的執行過程。[例7.11]寫一個函數,在鏈表中按學號查找該結點。TYPE * search (TYPE *head,int n)
{
TYPE *p;
int i;
p=head;
while (p->num!=n && p->next!=NULL)
p=p->next; /* 不是要找的結點后移一步*/
if (p->num==n) return (p);
if (p->num!=n&& p->next==NULL)
printf ("Node %d has not been found!\n",n
}
本函數中使用的符號常量TYPE與例7.10的宏定義相同,等于struct
stu。函數有兩個形參,head是指向鏈表的指針變量,n為要查找的
學號。進入while語句,逐個檢查結點的num成員是否等于n,如果不
等于n且指針域不等于NULL(不是最后結點)則后移一個結點,繼續循
環。如找到該結點則返回結點指針。 如循環結束仍未找到該結點則
輸出“未找到”的提示信息。[例7.12]寫一個函數,刪除鏈表中的指定結點。
刪除一個結點有兩種情況:
1. 被刪除結點是第一個結點。這種情況只需使head指向第二個結點
即可。即head=pb->next。其過程如圖7.5所示。2. 被刪結點不是第一個結點,這種情況使被刪結點的前一結點指向
被刪結點的后一結點即可。即pf->next=pb->next。其過程如圖7.
6所示。
函數編程如下:TYPE * delete(TYPE * head,int num)
{
TYPE *pf,*pb;
if(head==NULL) /*如為空表, 輸出提示信息*/
{ printf("\nempty list!\n");
goto end;}
pb=head;
while (pb->num!=num && pb->next!=NULL)
/*當不是要刪除的結點,而且也不是最后一個結點時,繼續循環*/
{pf=pb;pb=pb->next;}/*pf指向當前結點,pb指向下一結點*/
if(pb->num==num)
{if(pb==head) head=pb->next;
/*如找到被刪結點,且為第一結點,則使head指向第二個結點,
否則使pf所指結點的指針指向下一結點*/
else pf->next=pb->next;
free(pb);
printf("The node is deleted\n");}
else
printf("The node not been foud!\n");
end:
return head;
} 函數有兩個形參,head為指向鏈表第一結點的指針變量,num刪
結點的學號。 首先判斷鏈表是否為空,為空則不可能有被刪結點。
若不為空,則使pb指針指向鏈表的第一個結點。
進入while語句后逐個查找被刪結點。找到被刪結點之后再看是
否為第一結點,若是則使head指向第二結點(即把第一結點從鏈中刪
去),否則使被刪結點的前一結點(pf所指)指向被刪結點的后一結點
(被刪結點的指針域所指)。如若循環結束未找到要刪的結點, 則輸
出“末找到”的提示信息。最后返回head值。
[例7.13]寫一個函數,在鏈表中指定位置插入一個結點。
在一個鏈表的指定位置插入結點, 要求鏈表本身必須是已按某
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -