




版權說明:本文檔由用戶提供并上傳,收益歸屬內容提供方,若內容存在侵權,請進行舉報或認領
文檔簡介
第九章結構體和共用體9.1結構體9.2嵌套結構9.3結構體型數組9.4結構體型指針9.5結構體與函數9.6內存的動態分配9.7共用體(聯合)9.8位段9.9類型定義9.10程序設計舉例習題9.1結構體9.1.1結構體類型在第六章我們討論了使用數組的好處。我們按順序讀入了5個學生某門課的成績,并按相反的順序輸出。我們并沒有定義score1,score2,score3,score4,score5,而是定義了一個可以存儲全部5門課成績的數組,即floatscore[5],針對數組元素score[0],…,score[4]編程實現了對5門課成績的處理。正是因為這些數據項的數據類型完全相同,并且具有相同的含義,所以可以用數組來組織它們。然而,在實際處理中,待處理的信息往往是由多種類型組成的,如有關學生的數據,不僅有學習成績,還應包括諸如學號(長整型)、姓名(字符串類型)、性別(字符型)、出生日期(字符串型)等。再如有關職工的管理程序,所處理的對象——職工的信息同樣包含了職工編號、職工姓名、工種等多種類型數據。就目前所學知識,我們只能將各個項定義成互相獨立的簡單變量或數組,無法反映它們之間的內在聯系。對此,應該有一種新的類型,就像數組將多個同類型數據組合在一起一樣,能將這些具有內在聯系的不同類型的數據組合在一起,C語言提供了“結構體(Structure)”類型來完成這一任務。9.1.2結構體類型的定義結構體類型不像整型一樣已經由系統定義好,可以直接用來定義整型變量,而是需要編程者自己先定義結構體類型(即結構體的組成)。C語言提供了關鍵字struct來標識所定義的是結構體類型,具體的類型名要編程者根據需要自己定義。結構體類型規定了該結構體所包含的成員。結構體類型的定義形式及其例子如圖9.1所示。圖9.1結構體類型定義語法說明:
(1)關鍵字struct告訴我們尾隨的是一個結構體。結構體類型的名字為student,名字也稱為標記(Tag),用于識別不同的結構體類型。關鍵字和類型名字組合成一種新的類型標識符,其地位如同通常的int,char等,其用途是可以定義該結構體型變量。結構體類型定義之后,就可以像其它類型一樣使用了。類型名或者標記的起名規則遵從標識符命名規則。
(2)成員列表為本結構體類型所包含的若干個成員的列表,必須用{}括起來,并以分號結束。每個成員的形式如下:類型標識符成員名;如上例中的
longintnum;
charname[20];成員(如num)又可稱為成員變量,也是一種標識符,成員的類型可以是除該結構體類型自身外,C語言允許的任何數據類型。結構體類型structstudent中包含四個成員,分別是:學號num(長整型)、姓名name(字符數組)、性別sex(字符型)、年齡(整型)。
(3)同一結構體類型中的各成員不可互相重名,但不同結構體類型間的成員可以重名,并且成員名還可以與程序中的變量重名,因為它們代表不同的對象。初學者為清晰起見,最好都不重名。9.1.3結構體型變量的定義結構體類型的定義本身不會創建任何變量。它只是一種模板(Template),限定了這種類型的結構體變量的組成樣式。有了結構體類型,便可以在此類型基礎上定義變量了。結構體類型變量因其所基于的類型是自定義的,所以有多種形式的定義方法。下面以表示職工工資單的結構體類型structstaff為例來說明三種定義變量的方法。形式一:類型、變量分別定義。
structstaff
{charname[20]; /*姓名*/
chardepartment[20]; /*部門*/
intsalary; /*工資*/
intcost; /*扣款*/
intrealsum; /*實發工資*/
};
structstaffworker1,worker2;
/*定義兩個staff結構體類型的變量*/形式二:類型、變量一起定義。
structstaff
{charname[20];
chardepartment[20];
intsalary;
intcost;
intrealsum;
}worker1,worker2;形式三:省略類型名的變量定義。
struct
{charname[20];
chardepartment[20];
intsalary;
intcost;
intrealsum;
}worker1,worker2;其中以第一種形式最好,概念清楚,易于擴充移植。定義了結構體型變量之后,結構體類型名的任務就完成了,在后續的程序中不再對其操作,而只對這些變量(如worker1,worker2)進行賦值、存取或運算等操作。在編譯時,結構體類型并不分配空間,只對結構體類型的變量分配空間。結構體型變量所占內存空間是各成員變量所占內存單元的總和,一般占一片連續的存儲單元,如worker1,worker2兩個變量占內存情況如圖9.2所示。由于worker1,worker2兩個變量都是staff這種結構體類型,因此它們的內存布局和組成都是一樣的,都是從staff結構體類型的模子“印”出來的。變量的具體取值在初始化或賦值時裝入。圖9.2結構體型變量在內存中的存儲形式9.1.4結構體型變量及其成員的引用定義結構體型變量的目的是為了在后續程序中對它進行操作。但首先要注意的是結構體型變量是一種聚合型變量。可訪問的對象有如下兩個:
·
結構體類型的變量名本身代表變量的整體,即整個結構體。
·成員名代表結構體型變量的各個組成成員。上述兩個對象均可在程序中引用。
(1)變量成員的引用方法:C語言提供了訪問結構體類型變量的成員運算符“.”(稱為點運算符),可以將結構成員與結構變量名聯系起來。訪問結構成員的形式為:結構體變量名.成員名點運算符是一個二元運算符,它處于最高優先級,結合性為從左至右。例如,假設已經定義了結構體類型structstaff的兩個變量worker1、worker2,則
worker1.salary就可以訪問或引用結構體變量worker1的成員salary,見圖9.3。圖9.3點運算符這樣,結構成員就可以和普通變量一樣參加各種運算,且所引用的成員與其所屬類型的普通變量一樣可進行該類型所允許的任何運算。例如:
worker1.realsum=worker1.salary-worker1.cost;
worker2.salary=worker1.salary;
scanf("%s",);
scanf("%d",&worker1.cost);注意,取結構成員的地址時,“&”要放在結構變量名前面而不是結構成員名前面,即不能寫成:
scanf("%d",worker1.&cost);另外,“&”和“.”同時出現時,由于運算符“.”的優先級高于“&”,因此對&worker1.cost而言,是先進行“.”運算,即先取成員,然后再取該成員的地址,就是說worker1.cost與&(worker1.cost)等價。又如:“structstudentstu1,stu2;”之后,變量stu1,stu2成員的引用可以是:
stu2.num=stu2.num+1;
stu1.age++;/*等價于(stu1.age)++*/
scanf("%ld",&stu1.num);
(2)相同結構體類型的結構體變量可以通過整體引用來賦值:如“worker2=worker1;”即將變量worker1的所有成員的值一一對應地賦給(拷貝)變量worker2的各成員。但是,注意下列用法是錯誤的:
worker2==worker1
worker2!=worker1
C語言不允許對結構體型變量進行任何邏輯運算。如果要對結構體型變量進行比較,只能是逐個成員進行比較。另外,也不可以對結構體型變量進行整體的輸入/輸出,如:
printf("%s",worker1);是錯誤的。結構體型變量只能對每個成員逐個進行輸入或輸出。
(3)結構體型變量占據的一片存儲單元的首地址稱為該結構體型變量的地址,其每個成員占據的若干單元的首地址稱為該成員的地址,兩個地址均可引用。如:
scanf("%d",&worker1.salary);
printf("0x%x",&worker1);雖然兩者均是地址,但若將其賦給指針,相應的指針類型是不同的。&worker1.salary應賦給一個整形指針,而&worker1應賦給一個結構體型指針,二者不可混用。兩指針類型也不可強制轉換。結構體型變量的地址主要用于作為函數的參數,在函數之間傳遞結構體型數據。9.1.5結構體型變量的初始化如前節所述,可以用讀入函數或賦值語句對結構體型變量的成員逐個賦值,但更方便的是直接以初值表的形式賦值,稱為初始化。例如:已定義結構體類型staff如前,則worker1的初始化:
structstaffworker1={"Wang_Wei","see",1200,100,1100};此時結構體型變量worker1在內存的布局如圖9.4所示。所有結構體型變量,不管是全局變量還是局部變量,自動變量還是靜態變量均可如此初始化。圖9.4結構體變量的成員在內存中的布局例9.1
利用結構體類型,編程計算一名同學5門課程的平均分。
#include<stdio.h>
voidmain()
{
structstuscore
{
charname[20];
floatscore[5];
floataverage;
};
structstuscorex={“Wang_Wei”,90.5,85,
70,90,98.5};
inti;
floatsum=0;
for(i=0;i<5;i++)
sum+=x.score[i];
x.average=sum/5;
printf(“Theaveragescoreof%sis%4.1f\n”,
,x.average);
}9.1.6應用舉例
例9.2
將例9.1改為鍵入任意多人的5門課的成績,打印平均分。
#include<stdio.h>
#include<conio.h>
voidmain()
{
structstuscore
{charname[20];
floatscore[5];
floataverage;
}x;
inti;
floatsum;
charrep;
while(1)
{
printf("\nDoyouwanttocontinue?(Y/N)");
rep=getche();
if(rep==′N′||rep==′n′)break;
sum=0;
printf("\nInputname(asXu_jun)and5scores
(alldepartbyspace);\n");
scanf(“%s”,); /*輸入名字*/
for(i=0;i<5;i++) /*輸入5門課成績*/
scanf("%f",&x.score[i]);
for(i=0;i<5;i++)
sum+=x.score[i];
x.average=sum/5; /*計算平均分*/
printf(“Theaveragescoreof%sis%4.1f\n”,
,x.average); /*打印輸出*/
}
}運行結果:
Doyouwanttocontinue?(Y/N)Y↙
Inputname(asXu_jun)and5scores(alldepartbyspace);
Guo_Yong8089.59987.566↙
TheaveragescoreofGuo_Yongis84.4
Doyouwanttocontinue?(Y/N)Y↙
Inputname(asXu_jun)and5scores(alldepartbyspace);
Liu_Ying8788899998↙
TheaveragescoreofLiu_Yingis92.2
Doyouwanttocontinue?(Y/N)N↙9.2嵌套結構允許一個結構體包含另一個結構體或者把一個結構體成員定義為數組,有時是很方便的。我們把成員之一定義為其它結構體類型,稱為結構體類型的嵌套(Nest)。例如我們前面定義了一個student結構體類型,其中的age(年齡)成員可以用birthday(生日)來代替,而birthday的數據類型則是如圖9.5所定義的date結構體類型。圖9.5結構體類型的嵌套定義其中的成員變量birthday是結構體類型structdate型,形成了結構體類型的嵌套定義。另外,在student結構體類型中,還定義了一個成員,其類型是一指向結構體類型student自身的指針,我們稱這種結構為遞歸結構或自嵌套結構。在student類型的定義中,此種指向student自身的指針成員是允許的,也是鏈表、隊列或數等復雜的數據結構所必需的。若為自身類型的普通成員變量則是不允許的,即student結構體類型中不可出現structstudent類型的非指針變量。嵌套結構的變量定義和一般的結構變量定義是相同的,如:
structstudentstu3;定義了一個名字為stu3的student結構體類型的變量。同樣可以在定義變量stu3時進行初始化:
structstudentstu3={99010,"Liu_Ping",′m′,{1987,10,2}};必須注意的是,由于結構體變量stu3的birthday成員是結構體類型,因此該成員的初始值必須用括號括起來,而各個成員的初始值之間還是用逗號分隔。如果要訪問嵌套結構體變量的成員,必須使用點運算符兩次(若是多級嵌套定義,則需用若干“.”運算符,逐級引用到最低級)。對student結構體型變量stu3的成員引用如下所示,其含義見圖9.6。
stu3.birthday.year;/*其值為1987*/
stu3.birthday.month;/*其值為10*/注意,不能對stu3.birthday進行操作,因其不是最低級。我們同樣還可以把date結構體類型的定義嵌到student結構體類型當中,如:
structstudent
{
longintnum;
charname[20];
charsex;
structdate
{
intyear; /*年*/
intmonth; /*月*/
intday; /*日*/
}birthday;
structstudent*ps;
};這兩種結構體類型的嵌套定義是等價的。圖9.6嵌套結構和點運算符9.3結構體型數組如前所述,一個結構體型變量只能存放由該結構體類型所定義的一條記錄。如“structstudentstu1;”中的變量stu1中存放了由結構體類型structstudent決定的學生1的記錄,即學生的學號、姓名、性別、年齡。但正如數組概念的引出一樣,我們一般要編程處理的不只是一個學生的數據記錄,而是很多學生。這時就要用到結構體型數組stu[50],每個數組元素stu[i]都可以存儲一個學生的記錄。結構體型數組在構造樹、表、隊列等數據結構時特別方便。9.3.1結構體型數組的定義相似于整型數組inta[3],結構體型數組定義的不同之處是要先定義好結構體類型。例如:
structstudent定義如前,然后“structstudentstu[3];”就定義了一個structstudent結構體型數組stu,數組元素stu[0],stu[1],stu[2]分別是三個structstudent結構體型變量。又如:“structstaffworker[100];”定義了另一個結構體類型structstaff的數組worker,數組元素worker[0],worker[1],…,worker[99]分別是100個structstaff結構體型變量。因此,定義結構數組和定義結構變量完全類似,也可以用直接定義、間接定義和一次性定義的方式,只要在結構名的后面加上數組維界說明符即可。9.3.2結構體型數組的初始化結構體型數組和普通數組一樣可以初始化,只是每個元素的初值為由{}括起來的一組數據,初始化形式是定義數組時,后面加上={初值列表},如:structstudentstu[2]={{99010,“Wang_Yan”,′f′,20},
{99011,"Li_Li",′m′,19}};
一個結構數組元素相當于一個結構變量。因此,訪問結構數組元素的成員與訪問結構變量的成員具有相同的規則。例如:
stu[i].num表示結構數組第i+1個元素中成員num的值;
&stu[i].num表示結構數組第i+1個元素中成員num的地址;
stu[i].name表示結構數組第i+1個元素中成員name的首地址;
stu[i].name[j]表示結構數組第i+1個元素中成員name第j+1個字符。因為“.”運算符優先于“&”和“*”運算符,所以,&stu[i].num與&(stu[i].num)等價;stu[i].name[j]與*&stu[i].name[j]等價。
例9.3
用結構體型數組初始化建立一工資登記表。然后鍵入其中一人的姓名,查詢其工資情況。
#include<string.h>
#include<stdio.h>
voidmain()
{
structstaff
{charname[20];
chardepartment[20];
intsalary;
intcost;
}worker[3]={ /*初始化*/
{"Xu_Guo","part1",800,200},
{"Wu_Xia","part2",1000,300},
{"Li_Jun","part3",1200,350}
};
inti;
charxname[20];
printf("\nInputtheworker\′sname:");
scanf(“%s”,xname);/*輸入職工名字*/
for(i=0;i<3;i++)
if(strcmp(xname,worker[i].name)==0)
/*查詢是否有此職工*/
{
printf("****%s****",xname);
printf(“\nsalary:%6d”,worker[i].
salary);
printf("\ncost:%6d",worker[i].cost);
printf(“\npayed:%6d”,worker[i].
salary-worker[i].cost);
}
}運行結果:
Inputtheworker′sname:Wu_Xia↙****Wu_Xia****
salary:1000
cost:300
payed:7009.4結構體型指針定義一個指針,讓它存放一個結構體型變量或結構體型數組的首地址,它就是指向該結構體型變量或結構體型數組的指針,我們統稱之為結構體型指針。由于結構體類型并非全新的數據類型,它只是對以前所學的多種類型的“封裝”:將一組相關的不同類型的數據看成一個整體。所以引入結構體型指針,可以達到:
(1)非常有效的函數參數傳遞。
(2)提高數組的訪問效率。結構體型指針特別之處還在于:利用它可以建立動態變化的數據結構,動態地、合理地分配內存。這一點將在下一節詳細介紹。9.4.1指向結構體型變量的指針定義結構體型指針和定義變量指針類似,一般形式為:[存儲類型]結構體類型*指針1,*指針2,…;例如:
staticstructstaff*sp;
/*定義了一個靜態的structstaff結構類型的指針sp*/
structstudent*p1;
/*定義了一個structstudent結構類型的指針p*/
structstudentstu1,*p2;
/*定義了一個自動的student結構體類型變量stu1和指針p2*/其中,staff和student是已經定義過的結構體類型名。上面定義的結構指針只說明了指針的類型,還未確定它的指向,是一種無定向的指針,必須通過初始化或賦值,把實際存在的某個結構體型變量或結構體型數組的首地址賦給它以后,才可確定它的具體指向,從而使它與相應的變量或數組建立聯系。例如:
p2=&stu1;/*賦值方式*/或者
structstudent*p2=&stu1;/*初始化方式*/之后,p2才真正指向了結構體型變量stu1,如圖9.7所示。圖9.7結構體型指針初始化定義了一個指向指定結構的結構指針以后,就可以用結構指針來訪問結構成員。例如p2指向structstudent型結構,用(*p2).num、(*p2).name、(*p2).age等就可以訪問其成員。其中,“*”是訪問地址運算符,“.”是取成員運算符,由于“.”的優先級高于“*”,因此(*p2).num中的圓括號就不能省略。若寫成*p2.num,則表示*(p2.num),如果num不是指針(地址量),則這種寫法就是非法的。也就是說,用指針訪問結構成員,可用如下形式:
(*指針名).成員名這與前面介紹過的用結構名訪問結構成員的形式:結構名.成員名是等效的。由于指向結構體型的指針使用得非常頻繁,故C語言提供了另一種簡便的取結構體成員運算符“->”,稱為指向成員運算符(或箭頭運算符)。它由一個減號和一個大于號組成。因此用指針訪問結構成員,又可以寫成:指針名->成員名運算符“->”和“.”都是訪問結構成員運算符,并同處于最高優先級,其結合性也都是從左到右。假設已說明
structstudentstu1,*sp=&stu1;若要訪問其成員name,則下面三個形式等效:
(*sp).namesp->name顯然,當用結構名訪問結構成員時用“.”比較方便,當用指針訪問結構成員時用“->”較為方便。注意以下表達式的含義(指向運算符->的優先級高于++):
sp->num:得到sp指向的結構體型變量中的成員num的值,假設其值為990120。
sp->num++:等價于((sp->num)++),得到sp指向的結構體型變量中的成員num的值,用完該值后對它加1。
++sp->num:等價于++(sp->num),得到sp指向的結構體型變量中的成員num的值,使之先加1,再使用。從以下用三條連續的輸出語句的執行結果可清楚地看到其含義:
printf(“%d\n”,sp->num); 輸出990120
printf(“%d\n”,sp->num++); 輸出990120
printf(“%d\n”,++sp->num); 輸出990122現舉例說明結構體型變量成員的三種引用形式。例9.4
顯示某人的工資信息。
#include<string.h>
#include<stdio.h>
voidmain()
{
structstaff
{
charname[20];
chardepartment[20];
intsalary;
};
structstaffw1,*p;
p=&w1;
strcpy(,"Li_Li");
strcpy((*p).department,"part1");
p->salary=1000;
printf(“%s%s%d\n”,,w1.department,
w1.salary);
printf(“%s%s%d\n”,(*p).name,(*p).department,
(*p).salary);
printf(“%s%s%d\n”,p->name,p->department,
p->salary);
}運行結果:
Li_Lipart11000
Li_Lipart11000
Li_Lipart11000可見,最后三行的輸出結果都是一樣的。9.4.2指向結構體型數組的指針指向結構體型數組的指針完全類似于第八章所述的指向普通數組的指針。一個結構體型數組可以用結構體型指針來訪問,既方便了數組元素的引用,又提高了數組的利用率。
例9.5
顯示工資表。
#include<stdio.h>
structstaff
{
charname[20];
intsalary;
};
voidmain()
{
structstaff*p;
structstaffworker[3]={{"Wang_Li",600},
{"Li_Ping",700},
{"Liu_Yuan",800}};
for(p=worker;p<worker+3;p++)printf(“%s\′ssalaryis%dyuan\n”,
p->name,p->salary);
}運行結果:
Wang_Li′ssalaryis600yuan
Li_Ping′ssalaryis700yuan
Liu_Yuan′ssalaryis800yuan其中,p是指向structstaff結構體類型數據的指針。for語句中p=worker使指針p實際地指向了數組worker,即p中存放的是數組worker的起始地址,等價于p=&worker[0],如圖9.8所示。在第一次循環中,程序輸出了worker[0]的各成員,然后執行p++,進行第二次循環。如前所述,C語言中的指針加1,并不是實際的內存地址值加1,而是指向了下一個數據,對于結構體型數組,則指向了下一個元素,即相當于p=&worker[1](即圖中p′),這樣循環體內的printf語句輸出的便是worker[1]的各成員。以此類推,在輸出了worker[2]之后p的值加到了worker+3,便不滿足循環條件p<worker+3了,程序結束。圖9.8指針與結構體型數組對于指向數組的指針,用得更多的是指針值本身的加1移動,所以要特別注意:
(++p)->salary:先使p自加1,指向下一元素,得到worker[1].salary的值,即700。
p++->salary:先得到worker[0].salary的值,即600,然后使p加1,指向worker[1]。
(p++)->salary:完全同上。括號不改變++操作符以后的操作性質。如在例9.5的worker數組初始化,且p=worker之后,連續執行以下四條語句,輸出結果為:
printf(“%d\n”,p->salary); 輸出600
printf(“%d\n”,(++p)->salary); 輸出700
printf(“%d\n”,p++->salary); 輸出700
printf(“%d\n”,(p++)->salary); 輸出8009.5結構體與函數結構體與函數之間的關系主要是:
·結構體作為函數參數。此時,結構體型變量、結構體型變量的成員和結構體指針都可以作為函數的參數進行傳遞。
·
結構體作為函數的返回值。9.5.1結構體作為函數參數在編程處理結構體型數據時,常常需要將一個結構體型變量的值或一個結構體型數組傳遞給另一個函數,此時,同樣遵循第七章中有關值傳遞和地址傳遞的規律?!爸祩鬟f”的一種是將結構體型變量的各個成員作為實際參數,這和普通變量作實參的含義是一樣的,形式參數為相應類型的普通變量。例9.6
用子函數求出worker數組中每個工人的實發工資。
/*傳遞結構體型變量的成員*/
#include<stdio.h>
structstaff
{
charname[20];
chardepartment[20];
intsalary;
intcost;
intrealsum;
}worker[3]={
{"Wang_Li","part1",1000,200},
{"Li_Ping","part2",1500,300},
{"Liu_Yuan","part3",2000,600}};
intgetreal(intsalary,intcost);/*函數聲明,注意其形參*/
voidmain()
{
structstaff*p;
intrealsum;
for(p=worker;p<worker+3;p++)
{
realsum=getreal(p->salary,p->cost);
/*函數調用,計算實發工資*/
printf(“%sof%sshouldbepayed%dyuan\n”,
p->name,p->department,realsum);
}
}
intgetreal(intsalary,intcost)
/*函數實現*/
{
returnsalary-cost;
}運行結果:
Wang_Liofpart1shouldbepayed800yuan
Li_Pingofpart2shouldbepayed1200yuan
Liu_Yuanofpart3shouldbepayed1400yuan此例中子函數完成的任務很簡單,只是為了說明主、子函數間形、實參是如何傳遞的。數組名worker代表了數組worker的首地址。注意,函數getreal的兩個參數是一般的整數類型,它既不知道,也不關心實參究竟是不是結構的成員,它只要求它們是int類型?!爸祩鬟f”的另一種是用結構體型變量作實參,將結構體變量所占的內存單元的內容全部順序傳遞給形參。形參也必須是同類型的結構體型變量。這兩種傳遞都是單向的,子函數對形參的處理改變不能影響作為實參的結構體型變量或者其成員的值,即處理結果都是帶不回來的。對于結構體型數據,數值傳遞法的不恰當之處是當結構體型變量的成員很多,或有一些成員是數組時,數據量很大,將全部成員一個個傳遞,既浪費時間又浪費空間,程序的運行效率會大大降低。所以,用結構體型指針作函數參數,即地址傳遞效果就比較好。例9.7
用子函數求出worker數組中每個工人的實發工資。
/*傳遞結構體型變量*/
#include<stdio.h>
structstaff
{
charname[20];
chardepartment[20];
intsalary;
intcost;
intrealsum;
}worker[3]={
{"Wang_Li","part1",1000,200},
{"Li_Ping","part2",1500,300},
{"Liu_Yuan","part3",2000,600}};
intgetreal(structstaff);
voidmain()
{
structstaff*p;
intrealsum;
for(p=worker;p<worker+3;p++)
{
realsum=getreal((*p));/**p表示數組元素*/
printf("%sof%sshouldbepayed%dyuan\n“,
p->name,p->department,
realsum);
}
}
intgetreal(structstaffss)/*結構體型變量作為形參*/
{
returnss.salary-ss.cost;
}運行結果同上?!暗刂穫鬟f”即實際參數為具體的結構體型變量的地址或者結構體型數組名,將結構體型變量或數組的地址傳給形參,形參為結構體型指針或結構體型數組名。這種傳遞是“雙向”的。實際上是實參、形參指向了同一片存儲空間,不需要開辟大量的存放數據的空間,更不需要費時間傳遞大量的數據,且被調函數對該區的處理結果是可以“返回”給主調函數的。這已經在第七章詳細闡述了,此處再以結構體型數據為例來說明之。例9.8
用子函數求出worker數組中每個工人的實發工資。
/*傳遞結構體類型指針*/
#include<stdio.h>
#defineNUM3
structstaff
{
charname[20];
chardepartment[20];
intsalary;
intcost;
intrealsum;
}worker[NUM]={
{"Wang_Li","part1",1000,200},
{"Li_Ping","part2",1500,300},
{"Liu_Yuan","part3",2000,600}};
voidgetreal(structstaff*);
voidmain()
{
structstaff*p;
for(p=worker;p<worker+NUM;p++)
{
getreal(p);
printf("%sof%sshouldbepayed%dyuan\n“,
p->name,p->department,p->realsum);
}
}
voidgetreal(structstaff*ps)
/*用結構體指針作為形參*/
{
ps->realsum=ps->salary-ps->cost;
/*雙向傳遞,處理結果*/
}運行結果同上。9.5.2結構體作為函數的返回值由于數組和結構體類型都屬于數據結構,有些讀者可能會認為,C語言對它們的處理應該是相似的。事實上,學習C語言的時候一定要摒棄這種想法,因為C語言處理結構體類型的方法與處理簡單數據類型的方法相似,但絕對與處理數組的方法不同。函數在返回數組時,無法返回整個數組的值,只能返回某個元素或者元素的地址。而函數在處理結構體類型時,可以將其模塊化為簡單類型來計算結果。即使結構體包含數組,函數也不是返回數組的首地址,而是直接返回結構體所有成員的值。在例9.9中,模擬了時間在某個特定周期之后進行更新的情形。假設使用的是24小時的時鐘。例中new_time()函數可以根據原始時間和自前一次更新開始時間流逝的秒數,返回本次的更新時間值。如果time_now是21:58:32,secs的值為97,那么通過調用new_time(time_now,secs),執行的返回結果就應該是22:00:09。整個調用過程如圖9.9所示。圖9.9結構體類型值作為函數的輸入參數和返回結果
例9.9
模擬時間更新程序。
#include<stdio.h>
structtime{
/*時間結構體*/
inthour,minute,second;/*小時:分:秒*/
};
structtimenew_time(structtime,int);
/*更新函數,返回為time結構體*/
voidmain()
{
structtimet1,t2={21,58,32};
intsecs=97;
t1=new_time(t2,secs);
printf(“newtime:%d:%d:%d\n”,t1.hour,
t1.minute,t1.second);
}
structtimenew_time(structtimett,/*當前時間*/
intelapsed_secs)/*已經過去的時間(秒數)*/
{
intnew_hour,new_min,new_sec;
new_sec=tt.second+elapsed_secs;
tt.second=new_sec%60; /*更新秒*/
new_min=tt.minute+new_sec/60;
tt.minute=new_min%60; /*更新分*/
new_hour=tt.hour+new_min/60;
tt.hour=new_hour%24; /*更新小時*/
return(tt);
}9.6內存的動態分配9.6.1動態分配內存的意義隨著計算機的發展,軟件的功能越來越強,相應地,軟件的規模就越來越大,而計算機內存就成了寶貴的資源,所以我們在編制復雜程序時就要考慮盡量節省內存。在上節的例9.8中,用了符號常量NUM來代表要處理的人數,人數不同時只需改動第一行。但若編制的是應用軟件,提供給用戶的應是可執行文件,無法根據具體情況來改變NUM的值。這時,NUM設成多少合適呢?當然,可以根據軟件使用者來定,比如:100人的車間,設計成#defineNUM100;1000人的工廠,設計成#defineNUM1000。但對于通用的商業軟件,無法預知使用者的情況。若為了程序的通用性,按內存的允許設置成一個很大的數組,那么對于很多只處理少量信息的用戶就造成了內存的極大浪費,這是非常不合理的。這時,能夠根據程序運行后的實際情況動態地分配適量的內存空間就顯得非常重要!9.6.2開辟和釋放內存區的函數為了實現內存的動態分配,C語言提供了一些程序執行后才開辟和釋放某些內存區的函數。
1.malloc()函數它的函數原型為:
void*malloc(unsignedsize);其功能是在內存的動態存儲區中分配長度為size個字節的連續空間。
2.free(p)函數該函數表示釋放由p指向的內存區,使這部分內存可以分配給其它變量。下面以兩個例子來說明上述兩函數的用法。
例9.10
分配一個能放置雙精度數的空間。
#include<stdlib.h>
main()
{
double*p;
p=(double*)malloc(sizeof(double));/*注1*/
if(p==0)
{
printf("mallocerror\n");
exit(0);
}*p=78.786;
printf("*p=%f\n",*p);
}運行結果:*p=78.786000此例中,存雙精度數的空間不是在程序編譯時分配的,而是通過調用malloc()函數在程序執行時才分配的。另外,對注1行有兩點說明:
(1)從malloc()函數原型可以得知,其返回值為void*型,現在是對雙精度型分配空間,所以要將其返回值強行轉換為double*型。
(2)程序中出于易于移植的考慮,使用了sizeof(double)作為malloc()函數的實參。因為不同機器上的雙精度所占的字節數可能不同,用這種方法不論在哪種機器上都能為雙精度型數據分配大小正確的空間。例9.11
改進上例,在使用后釋放動態分配的空間。
#include<stdlib.h>
main()
{
double*p,*q;
p=(double*)malloc(sizeof(double));
if(p==0){
printf("mallocerror\n");
exit(0);
}
printf("p=0x%x*p=%4.1f\n",p,*p=100);
free(p);
q=(double*)malloc(sizeof(double));
if(q==0){
printf("mallocerror\n");
exit(0);
}
*q=10.;
printf("q=0x%x*q=%4.1fp=0x%x*p=%4.1f\n",
q,*q,p,*p);
}運行結果:
p=0X4E7*p=100.0
q=0X4E7*q=10.0p=0X4E7*p=10.0指針p、q均為相同的地址值(具體值可能不是0X4E7),表明已經釋放的由指針p所指的空間又重新分配給了指針q。由于指針p的內容沒變,故指針p、q都指向同一空間。從第二行的結果可驗證之。在多次調用malloc()函數開辟內存空間的過程中,可能有另一種動態變化的數據也要在此分配空間,或者前邊已分配的某個空間已被釋放,又可重新被分配。因此多次開辟的內存空間的地址是不連續的。這一點與數組完全不同。另外兩個相關函數是calloc()及realloc(),其原型分別為:
void*calloc(unsignedn,unsignedsize);
void*realloc(void*p,unsignedsize);
calloc()的功能是分配n個大小為size個字節的連續空間,它實際上是用于動態數組的分配。
realloc()的功能是將p所指出的已分配的內存空間重新分配成大小為size個字節的空間。它用于改變已分配的空間的大小,可以增減單元數。9.6.3鏈表概述我們在9.6.1節中所提出的問題,用上面介紹的幾個函數,可以這樣解決:程序運行前不開辟任何存儲空間,在程序執行之后,第一步調用malloc()函數,例如,(structstaff*)
malloc(sizeof(structstaff))開辟一個structstaff結構體型變量所需的內存空間;第二步讀入一個工人的數據信息。然后重復執行這兩步,可以開辟任意多個structstaff型變量的內存空間,讀入相應個工人的數據信息,形成若干個內存塊,使內存的分配成為動態的。如前節所述,這些用malloc()函數開辟的內存塊一般是不連續的?,F在的問題是如何來組織管理這些不連續的內存塊?比如說想將多個工人的工資打印出來,過去用數組靜態分配空間時,做法是將第一人信息所占空間的首地址給p,輸出p->salary之后,將p加1再輸出下一人的,但現在各個空間是不連續的,p加1就不一定是下一人的信息所占空間的首地址。這個地址是隨機的,必須有一指針專門記錄下來,才能把兩人的內存塊聯系起來。所以必須在structstaff類型定義中加一個指向該結構體類型的指針,專門用來記錄下一個內存塊的首地址。即
structstaff{charname[20];
intsalary;
structstaff*next;
};其中的指針next將存放下一個內存塊的首地址,也是專門用于連接兩個內存塊的指針。像這樣的一個結構體型變量可用來形成鏈表。每個該結構體型數據稱為鏈表的一個結點,結點必須包含數值信息和下一個結點地址兩個部分,缺一不可。由前一個結點的指針成員指向下一個結點,可將若干個結點串接在一起,就構成了鏈表。鏈表是將分散在內存中的相關信息塊通過指針鏈接在一起的一種數據結構,是一種重要的常見數據結構。利用這種數據結構可以動態地分配內存空間。鏈表有單向、雙向、環形等多種,我們只以最簡單的單向鏈表為例來介紹,如圖9.10所示。
head稱為“頭指針”,它應該是與結點類型相同的結構體型指針,其中存放一個地址,該地址為鏈表中第一個結點的首地址。圖9.10單向鏈表示意圖
A、B、C、D、…、N為各結點的實際數據信息,其具體成員的個數和內容由實際情況而定;每個結點的最后一個成員next為結點類型的結構體型指針,存放下一個結點的首地址,即指向下一個結點。這些指針逐個指向下一個結點,并將這些地址不連續的結點“串”在一起,形成了鏈表。鏈表的長短可以是內存允許范圍內的任意多個。
NULL為表尾標志,單向鏈表由head指向第一個結點,第一個結點的next成員又指向第二個結點,直到最后一個結點。該結點不再指向其它結點,稱為“表尾”,它的地址部分即存放一個NULL(“表示空地址”),標志鏈表到此結束。有了以上的知識,便可以進行鏈表的處理了。9.6.4建立鏈表所謂建立鏈表即是從無到有的形成一個鏈表。建立鏈表的思想很簡單:逐個地輸入各結點的數據,同時建立起各結點的關系。這種建立關系可能是正掛、倒掛或插入,下面介紹前兩種。建立鏈表方法一:正掛——先建立鏈頭,讓鏈頭指向首先開辟并輸入數據的第一個結點;然后開辟并輸入第二個結點數據,將其“掛”到第一個結點之后;接著開辟第三個結點并輸入實際數據,將其“掛”在第二個結點之后……即按輸入順序將結點“掛”接在一起。在實際編程時,還有些細節要考慮,如是否為空鏈等。針對9.6.1節提出的問題,我們建立一個職工工資鏈表,現定義結點類型如下:
structstaff
{
charname[20];
intsalary;
structstaff*next;
};
(為了簡化程序,減少了原有的數據成員項)在形成鏈表的過程中,首先要定義頭指針,并且還需要兩個工作指針p1、p2,其定義如下:
structstaff*head,*p1,*p2;p1用于指向新開辟的結點,p2用于指向建鏈過程中已建鏈表的最后一個結點。首先考慮算法如圖9.11所示。圖9.11正向建立鏈表子函數流程圖具體步驟描述如下:
(1)開始時先建一個空鏈表:head=NULL;形如:。
(2)開辟第一個結點空間并由p1指向,即“p1=(structstaff*)(malloc(LEN));”,LEN為結點結構體類型staff的一個變量所占字節數。之后,執行語句:
scanf("%s%d",p1->name,&p1->salary);讀入其有效數據(以工資大于0為有效數據),執行“head=p1;”,將其掛到鏈頭上(如虛線所示,其后的鏈表圖類似)。HeadNULL形如:其中worker1代表第一個工人的姓名;至于head的具體內容是什么,即p1的值是多少,由系統決定,我們無需關心。
(3)移動p2,使其指向最后一個結點,即執行p2=p1。形如:
(4)再開辟下一個結點的空間由p1指向,即再次執行:
p1=(structstaff*)malloc(LEN);讀入有效數據后,執行“p2->next=p1;”,將其掛至鏈尾。
(5)重復(3)、(4)兩步,直至所讀數據無效,即p2所指為真正尾結點,此時令p2->next=NULL,建鏈結束。形如:相應程序如下(附一個遍歷顯示子函數print(),以便查看建鏈后鏈表內各結點的情況)。
例9.12
正向建立鏈表程序清單。
#include<stdlib.h>
#defineNULL0
#defineLENsizeof(structstaff)
structstaff
{
charname[20];
intsalary;
structstaff*next;
};
intn;
main()
{
structstaff*creat1(); /*二函數聲明*/
voidprint(structstaff*p);
structstaff*head;
head=creat1(); /*調子函數建立鏈表*/
print(head);
/*從頭開始顯示鏈表各結點的數據信息*/
}
structstaff*creat1()
{
structstaff*head,*p1,*p2;
n=0;
p1=(structstaff*)malloc(LEN);/*開辟第一結點*/
printf("Inputtheworker\′snamesalary(salary=0
end):\n");
scanf(“%s%d”,p1->name,&p1->salary);
/*讀入第一結點數據*/
head=NULL;/*建空鏈*/
while(p1->salary>0)
{
n=n+1; /*結點數加1*/if(n==1)head=p1; /*“掛鏈”*/elsep2->next=p1;
p2=p1; /*移動指針p2*/p1=(structstaff*)malloc(LEN);/*開辟下一結點空間*/scanf(“%s%d”,p1->name,&p1->salary);
/*讀入數據*/}
p2->next=NULL; /*數據無效置鏈尾*/
return(head); /*返回鏈頭*/
}
voidprint(structstaff*head)
{
structstaff*p;
p=head; /*p指向鏈頭*/
while(p!=NULL)
/*未至鏈尾,則顯示結點數據信息*/{
printf("%s\′ssalaryis%d\n“,p->name,p->salary);
p=p->next; /*p后移一結點*/}}其中定義的建鏈函數creat1()的返回值為structstaff結構體型指針,由它帶回所建鏈表的起始地址(即return(head)中的head——頭指針);n為結點個數。程序運行情況:
Inputtheworker′snamesalary(salary=0end):
W11000↙ (輸入)
W22000↙
W33000↙
W0↙
W1′ssalaryis1000
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯系上傳者。文件的所有權益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網頁內容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
- 4. 未經權益所有人同意不得將文件中的內容挪作商業或盈利用途。
- 5. 人人文庫網僅提供信息存儲空間,僅對用戶上傳內容的表現方式做保護處理,對用戶上傳分享的文檔內容本身不做任何修改或編輯,并不能對任何下載內容負責。
- 6. 下載文件中如有侵權或不適當內容,請與我們聯系,我們立即糾正。
- 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 斜井火災應急救援預案(3篇)
- 文物景區火災應急預案(3篇)
- 森林火災應急方案預案(3篇)
- 家庭中的火災應急預案(3篇)
- 2025年考研俄語(二外)模擬試卷(動詞體用法解析全攻略)
- 父女恩斷協議書
- 工廠勞務外包協議書
- 甘肅技術協議書
- 小區住宅安全協議書
- 監理解除協議書
- 廣東省高等學校“千百十工程”第六批繼續培養對象和第
- 人教版三年級數學上冊口算題卡
- 小數乘整數的教學設計 小數乘整數教學設計一等獎(十四篇)
- 玻璃鋼管道施工方案
- 錐坡工程量計算(支持斜交、溜坡計算)
- 康復醫學-康復治療技術
- 企業清產核資工作底稿
- LY/T 1675-2006馬尾松毛蟲監測與防治技術規程
- GB/T 708-2006冷軋鋼板和鋼帶的尺寸、外形、重量及允許偏差
- GB/T 14337-2008化學纖維短纖維拉伸性能試驗方法
- L4-《采購與供應策略》-講義課件
評論
0/150
提交評論