結構體類型

聚合數據類型

C++提供了許多種基本的數據類型(如int、float、double、char等)供用戶使用。但是由於程序需要處理的問題往往比較複雜,而且呈多樣化,已有的數據類型顯得不能滿足使用要求。因此C++允許用戶根據需要自己聲明一些類型,用戶可以自己聲明的類型還有結構體類型(structure)、共用體類型(union)、枚舉類型(enumeration)、類類型(class)等,這些統稱為用戶自定義類型(user-defined type,UDT)。

概述


在C語言中,結構體(struct)指的是一種數據結構,是C語言中聚合數據類型(aggregate data type)的一類。結構體可以被聲明為變數、指針或數組等,用以實現較複雜的數據結構。結構體同時也是一些元素的集合,這些元素稱為結構體的成員(member),且這些成員可以為不同的類型,成員一般用名字訪問。
在一個組合項中包含若干個類型不同(當然也可以相同)的數據項。C和C++允許用戶自己指定這樣一種數據類型,它稱為結構體。它相當於其他高級語言中的記錄(record)。例如,可以通過下面的聲明來建立數據類型。
struct Student//聲明一個結構體類型Student
{
int num;//包括一個整型變數num
char name[20];//包括一個字元數組name,可以容納20個字元
char sex;//包括一個字元變數sex
int age;//包括一個整型變數age
float score;//包括一個單精度型變數
char addr[30];//包括一個字元數組addr,可以容納30個字元
};//最後有一個分號
這樣,程序設計者就聲明了一個新的結構體類型Student(struct是聲明結構體類型時所必須使用的關鍵字,不能省略),它向編譯系統聲明:這是一種結構體類型,它包括num,name,sex,age,score,addr等不同類型的數據項。應當說明Student是一個類型名,它和系統提供的標準類型(如int、char、float、double一樣,都可以用來定義變數,只不過結構體類型需要事先由用戶自己聲明而已。
聲明一個結構體類型的一般形式為
struct結構體類型名{成員表列};
結構體類型名用來作結構體類型的標誌。上面的聲明中Student就是結構體類型名。大括弧內是該結構體中的全部成員(member),由它們組成一個特定的結構體。上例中的num,name,sex,score等都是結構體中的成員。在聲明一個結構體類型時必須對各成員都進行類型聲明即類型名成員名;每一個成員也稱為結構體中的一個域(field)。成員表列又稱為域表。
成員名的定名規則與變數名的定名規則相同
聲明結構體類型的位置一般在文件的開頭,在所有函數(包括main函數)之前,以便本文件中所有的函數都能利用它來定義變數。當然也可以在函數中聲明結構體類型。
在C語言中,結構體的成員只能是數據(如上面例子中所表示的那樣)。
C++對此加以擴充,結構體的成員既可以包括數據(即數據成員),又可以包括函數(即函數成員),以適應面向對象的程序設計。

定義


前面只是指定了一種結構體類型,它相當於一個模型,但其中並無具體數據,系統也不為之分配實際的內存單元為了能在程序中使用結構體類型的數據,應當定義結構體類型的變數,並在其中存放具體的數據。
定義
(1)、先聲明結構體類型再定義變數名如上面已定義了一個結構體類型Student,可以用它來定義結構體變數。如
struct Student student1,student2;
以上定義了student1和student2為結構體類型struct Student的變數,即它們具有struct Student類型的結構。在定義了結構體變數后,系統會為之分配內存單元。例如student1和student2在內存中各佔63個位元組
(4+20+1+4+4+30=63)。
但是這裡需要注意:名義上計算大小為63,根據不同編譯器,內存存儲會有所不同,在存儲該結構體時會按照內存對齊進行相關處理,系統默認對齊係數為4(即按int類型對齊,粗略認識可以認為每相鄰兩個數據成員存儲是大小是4的整數倍,請參考下面對比結構體),詳情請參考內存對齊,因此該結構體實際大小為:68,具體計算如下:
{
int num;//整型,4個位元組
char name[20];//字元數組20個位元組,4的整數倍,取20位元組
char sex;//字元類型,一個位元組,往下不能湊齊4個位元組,因此取4個位元組
int age;//以下同理4個位元組
float score;//4個位元組
char addr[30];//字元數組30個位元組,取4整數倍,因此為32
}
故實際大小為:4+20+4+4+4+32=68
對比結構體:1
{
int num;//整型,4個位元組
char name[19];//字元數組19個位元組,往下能取4的整數倍,取20位元組
char sex;//存放在上面字元數組char name[19]後面,湊成20位元組,為了表示存在這個數據,在這裡給個0
int age;//以下同理4個位元組
float score;//4個位元組
char addr[30];//字元數組30個位元組,取4整數倍,因此為32
}
故實際大小為:4+20+0+4+4+32=64
對比結構體:2{
int num;//整型,4個位元組
char name[20];//字元數組19個位元組,往下能取4的整數倍,取20位元組
char sex;//字元類型,一個位元組,往下能湊上一個字元數據位元組,單任不能湊齊4個位元組,因此取4個位元組
char test;//新增加的數據類型,已往上存放,為了表示存在這個數據,這裡給個0
int age;//以下同理4個位元組
float score;//4個位元組
char addr[30];//字元數組30個位元組,取4整數倍,因此為32
}
故實際大小為:4+20+4+0+4+4+32=68
(2)、在聲明類型的同時定義變數
例如:
struct Student//聲明結構體類型Student
{
int num;
char name[20];
char sex;
int age;
float score;
char addr[30];
}student1,student2;//定義兩個結構體類型Student的變數student1,student2
這種形式的定義的一般形式為
struct結構體名
{
成員表列
}變數名表列;
(3)、直接定義結構體類型變數
其一般形式為
struct//注意沒有結構體類型名
{
成員表列
}變數名表列;
這種方法雖然合法,但很少使用。提倡先定義類型后定義變數的第(1)種方法。
在程序比較簡單,結構體類型只在本文件中使用的情況下,也可以用第(2)種方法。
關於結構體類型,有幾點要說明:
(1)、不要誤認為凡是結構體類型都有相同的結構。實際上,每一種結構體類型都有自己的結構,可以定義出許多種具體的結構體類型。
(2)、類型與變數是不同的概念,不要混淆。只能對結構體變數中的成員賦值,而不能對結構體類型賦值。在編譯時,是不會為類型分配空間的,只為變數分配空間。
(3)、對結構體中的成員(即“域”),可以單獨使用,它的作用與地位相當於普通變數。
(4)、成員也可以是一個結構體變數。
struct Date//聲明一個結構體類型Date
{
int month;
int day;
int year;
};
struct Student
//聲明一個結構體類型Student
{
int num;
char name[20];
char sex;
int age;
Date birthday;
char addr[30];
}student1,student2;
//定義student1和student2為結構體類型Student的變數Student的結構。
(5)、結構體中的成員名可以與程序中的變數名相同,但二者沒有關係。
例如,程序中可以另定義一個整型變數num,它與student中的num是兩回事,互不影響。

初始化

和其他類型變數一樣,對結構體變數可以在定義時指定初始值。如
struct Student
{
int num;
char name[20];
char sex;
int age;
float score;
char addr[30];
}student1={10001,"Zhang Xin",'M',19,90.5,"Shanghai"};
這樣,變數student1中的數據如圖7.2中所示。也可以採取聲明類型與定義變數分開的形式,在定義變數時進行初始化:
student2= student1;

變數


在定義了結構體變數以後,當然可以引用這個變數。
(1)、可以將一個結構體變數的值賦給另一個具有相同結構的結構體變數。
如上面的student1和student2都是student類型的變數,可以這樣賦值:
student1= student2;
(2)、可以引用一個結構體變數中的一個成員的值。
例如,student1.num表示結構體變數student1中的成員的值,如果student1的值如圖7.2所示,
則student1.num的值為10001。
引用結構體變數中成員的一般方式為
結構體變數名。成員名
例如可以這樣對變數的成員賦值:
student1.num=10010;
(3)、如果成員本身也是一個結構體類型,則要用若干個成員運算符,一級一級地找到最低一級的成員。
例如,對上面定義的結構體變數student1,可以這樣訪問各成員:
student1.num(引用結構體變數student1中的num成員)
如果想引用student1變數中的birthday成員中的month成員,不能寫成student1.month,
必須逐級引用,即
student1.birthday.month=12;
(引用結構體變數student1中的birthday成員中的month成員)
(4)、不能將一個結構體變數作為一個整體進行輸入和輸出。
例如,已定義student1和student2為結構體變數,並且它們已有值。不能企圖這樣輸出結構體變數中的各成員的值:
cin>>student1;
只能對結構體變數中的各個成員分別進行輸入和輸出。
(5)、對結構體變數的成員可以像普通變數一樣進行各種運算(根據其類型決定可以進行的運算種類)。例如
student2.score=student1.score;
sum=student1.score+student2.score;
student1.age++;
++student1.age;
由於“.”運算符的優先順序最高,student1.age++相當於(student1.age)++。++是對student1.age進行自加運算,而不是先對age進行自加運算。
(6)、可以引用結構體變數成員的地址,也可以引用結構體變數的地址。如
cout<<&student1;//輸出student1的首地址
cout<<&student1.age;//輸出student1.age的地址
結構體變數的地址主要用作函數參數,將結構體變數的地址傳遞給形參
例、引用結構體變數中的成員。
using namespace std;
struct Date//聲明結構體類型Date
{int month;
int day;
int year;
};
{int num;
char sex;
Date birthday;//聲明birthday為Date類型的成員
float score;
}student1,student2={10002,″Wang Li″,′f′,5,23,1982,89.5};
//定義Student 類型的變數student1,student2,並對student2初始化
int main( )
{student1=student2;//將student2各成員的值賦予student1的相應成員
cout<
cout<
cout<
return 0;}
運行結果如下:
10002 Wang Li f 5/23/1982 89.5 7.1.4

數組


一個結構體變數中可以存放一組數據(如一個學生的學號、姓名、成績等數據)。如果有10個學生的數據需要參加運算,顯然應該用數組,這就是結構體數組。結構體數組與以前介紹過的數值型數組的不同之處在於:
每個數組元素都是一個結構體類型的數據,它們都分別包括各個成員項。
定義
定義結構體數組和定義結構體變數的方法相仿,定義結構體數組時只需聲明其為數組即可。如
{int num;
char name[20];
char sex;
int age;
float score;
char addr[30];
};
Student stu[3];//定義Student類型的數組stu
也可以直接定義一個結構體數組,如
struct Student
{int num;
char name[20];
char sex;
int age;
float score;
char addr[30];
}stu[3];
struct
{ int num;
char name[20];
char sex;
int age;
float score;
char addr[30];
}stu[3];
結構體數組的初始化與其他類型的數組一樣,對結構體數組可以初始化。如
struct Student
{
int num;
char name[20];
char sex;
int age;
float score;
char addr[30];
}stu[3]={
{10101,″Li Lin″, ′M′, 18,87.5, ″103 Beijing Road″},
{10102,″Zhang Fun″,′M′,19,99, ″130 Shanghai Road″},
{10104,″Wang Min″,′F′, 20,78.5, ″1010 Zhongshan Road″}};
定義數組stu時,也可以不指定元素個數,即寫成以下形式:
stu[ ]={{…},{…},{…}};
編譯時,系統會根據給出初值的結構體常量的個數來確定數組元素的個數。
一個結構體常量應包括結構體中全部成員的值。當然,數組的初始化也可以用以下形式:
Student stu[ ]={{…},{…},{…}};//已事先聲明了結構體類型Student
由上可以看到,結構體數組初始化的一般形式是在所定義的數組名的後面加上 ={初值表列};

應用舉例

下面舉一個簡單的例子來說明結構體數組的定義和引用。
例7.2對候選人得票的統計程序。設有3個候選人,最終只能有1人當選為領導。今有10個人參加投票,從鍵盤先後輸入這10個人所投的候選人的名字,要求最後輸出這3個候選人的得票結果。可以定義一個候選人結構體數組,包括3個元素,在每個元素中存放有關的數據。
程序如下:
#include
struct Person//聲明結構體類型Person
{
char name[20];
int count;
}Person leader[3]={″Li″,0,″Zhang″,0,″Fun″,0};
//定義Person類型的數組,內容為3個候選人的姓名和當前的得票數
int main( )
{
int i,j;
char leader_name[20];
//leader_name為投票人所選的人的姓名
for(i=0;i<10;i++) {cin>>leader_name;
//先後輸入10張票上所寫的姓名
for(j=0;j<3;j++)//將票上姓名與3個候選人的姓名比較
if(strcmp(leader_name,leader[j].name)==0) leader[j].count++;
//如果與某一候選人的姓名相同,就給他加一票
}
cout<
for(i=0;i<3;i++)//輸出3個候選人的姓名與最後得票數
return 0;
}
運行情況如下:
Zhang↙(每次輸入一個候選人的姓名)
Li↙
Fun↙
Li↙
Zhang↙
Li↙
Zhang↙
Li↙
Fun↙
Wang↙
Li:4 (輸出3個候選人的姓名與最後得票數)
Zhang:3
Fun:2
程序定義一個全局的結構體數組leader,它有3個元素,每一元素包含兩個成員,即name(姓名)和count(得票數)。在定義數組時使之初始化,使3位候選人的票數都先置零。
在這個例子中,也可以不用字元數組而用string方法的字元串變數來存放姓名數據,程序可修改如下:
#include
#include
using namespace std;
struct Person
{
string name;//成員name為字元串變數
int count;
};
int main( )
{
Person leader[3]={″Li″,0,″Zhang″,0,″Fun″,0};
int i,j;
string leader_name;// leader_name為字元串變數
for(i=0;i<10;i++)
{
cin>>leader_name;
for(j=0;j<3;j++)
if(leader_name==leader[j].name) leader[j].count++
//用“==”進行比較
}
cout<
for(i=0;i<3;i++)
{cout<
return 0;
}

指針


一個結構體變數的指針就是該變數所佔據的內存段的起始地址。可以設一個指針變數,用來指向一個結構體變數,此時該指針變數的值是結構體變數的起始地址。
指針變數也可以用來指向結構體數組中的元素。

類型一

指向結構體變數的指針引用結構體變數中的成員
下面通過一個簡單例子來說明指向結構體變數的指針變數的應用。
例 指向結構體變數的指針的應用。
#include
#include
using namespace std;
int main( )
{
struct Student
//聲明結構體類型student
{ int num;
char sex;
float score;
};
Student stu;
//定義Student類型的變數stu
Student *p=&stu;
//定義p為指向Student類型數據的指針變數並指向stu
stu.num=10301;
//對string變數可以直接賦值
stu.sex=′f′;
stu.score=89.5;
cout<
stu.score<
cout<

num<<″ ″<<(*p).name<<″ ″<<(*p).sex<<″ ″<<

(*p).score<
return 0;
}
程序運行結果如下:
10301 Wang Fun f 89.5 (通過結構體變數名引用成員)
10301 Wang Fun f 89.5 (通過指針引用結構體變數中的成員)
兩個cout語句輸出的結果是相同的。
為了使用方便和使之直觀,C++提供了指向結構體變數的運算符->,
例如p->num表示指針p當前指向的結構體變數中的成員num。
p->num 和(*p).num等價。
同樣,p->name等價於(*p).name。
也就是說,以下3種形式等價:
①、結構體變數。成員名。如stu.num。
②、(*p).成員名。如(*p).num。
③、p->成員名。如p->num。
“->”稱為指向運算符。
請分析以下幾種運算:
p->n得到p指向的結構體變數中的成員n的值。
p->n++得到p指向的結構體變數中的成員n的值,用完該值后使它加1。
++p->n得到p指向的結構體變數中的成員n的值,並使之加1,然後再使用它。

類型二

用結構體變數和指向結構體變數的指針構成鏈表
鏈表是一種常見的重要的數據結構。
鏈表有一個“頭指針”變數,以head表示,它存放一個地址。該地址指向一個元素。
鏈表中的每一個元素稱為“結點”,每個結點都應包括兩個部分:
一是用戶需要用的實際數據,
二是下一個結點的地址。
可以看到鏈表中各元素在內存中的存儲單元可以是不連續的。要找某一元素,可以先找到上一個元素,根據它提供的下一元素地址找到下一個元素。
可以看到,這種鏈表的數據結構,必須利用結構體變數和指針才能實現。
可以聲明一個結構體類型,包含兩種成員,一種是用戶需要用的實際數據,另一種是用來存放下一結點地址的指針變數。
例如,可以設計這樣一個結構體類型:
struct Student
{
int num;
float score;
Student *next;//next指向Student結構體變數
};
其中成員num和score是用戶需要用到的數據,相當於圖7.8結點中的A,B,C,D。next是指針類型的成員,它指向Student類型數據(就是next所在的結構體類型)。用這種方法就可以建立鏈表。
每一個結點都屬於Student類型,在它的成員next中存放下一個結點的地址,程序設計者不必知道各結點的具體地址,只要保證能將下一個結點的地址放到前一結點的成員next中即可。
下面通過一個例子來說明如何建立和輸出一個簡單鏈表。
例、建立一個簡單鏈表,它由3個學生數據的結點組成。輸出各結點中的數據。
#define NULL 0
#include
struct Student
{
long num;
float score;
struct Student *next;
};
int main( )
{
Student a,b,c,*head,*p;
a.num=31001;
a.score=89.5;//對結點a的num和score成員賦值
b.num=31003;
b.score=90;//對結點b的num和score成員賦值
c.num=31007;
c.score=85;//對結點c的num和score成員賦值
head=&a;//將結點a的起始地址賦給頭指針head
a.next=&b;//將結點b的起始地址賦給a結點的next成員
b.next=&c;//將結點c的起始地址賦給b結點的next成員
c.next=NULL;//結點的next成員不存放其他結點地址
p=head;//使p指針指向a結點
do
{
cout<num<<″ ″<score<
p=p->next;//使p指向下一個結點
}while (p!=NULL);//輸出完c結點后p的值為NULL
return 0;
}
請讀者考慮:
①各個結點是怎樣構成鏈表的。
②p起什麼作用?
本例是比較簡單的,所有結點(結構體變數)都是在程序中定義的,不是臨時開闢的,也不能用完后釋放,這種鏈表稱為靜態鏈表。對各結點既可以通過上一個結點的next指針去訪問,也可以直接通過結構體變數名a,b,c去訪問。
動態鏈表則是指各結點是可以隨時插入和刪除的,這些結點並沒有變數名,只能先找到上一個結點,才能根據它提供的下一結點的地址找到下一個結點。只有提供第一個結點的地址,即頭指針head,才能訪問整個鏈表。如同一條鐵鏈一樣,一環扣一環,中間是不能斷開的。

函數參數


將一個結構體變數中的數據傳遞給另一個函數,有下列3種方法:
(1)、用結構體變數名作參數。一般較少用這種方法。
(2)、用指向結構體變數的指針作實參,將結構體變數的地址傳給形參。
(3)、用結構體變數的引用變數作函數參數。
下面通過一個簡單的例子來說明,並對它們進行比較。
例有一個結構體變數stu,內含學生學號、姓名和3門課的成績。要求在main函數中為各成員賦值,在另一函數print中將它們的值輸出。

方法一

用結構體變數作函數參數
#include
#include
using namespace std;
{ int num;
float score[3];
};
int main( )
{
void print(Student);//函數聲明,形參類型為結構體Student
Student stu;//定義結構體變數
stu.num=12345;//以下5行對結構體變數各成員賦值
stu.score[0]=67.5;
stu.score[1]=89;
stu.score[2]=78.5;
print(stu);//調用print函數,輸出stu各成員的值
return 0;
}
void print(Student st)
{
cout<
<<″ ″ <
}
運行結果為
12345 67.5 89 78.5

方法二

用指向結構體變數的指針作實參
#include
#include
using namespace std;
struct Student
{
int num; string name;//用string類型定義字元串變數
float score[3];
}stu={12345,″Li Fung″,67.5,89,78.5};//定義結構體student變數stu並賦初值
int main( )
{
void print(Student *);//函數聲明,形參為指向Student類型數據的指針變數
Student *pt=&stu;//定義基類型為Student的指針變數pt,並指向stu
print(pt);//實參為指向Student類數據的指針變數
return 0;
}
//定義函數,形參p是基類型為Student的指針變數
void print(Student *p)
{
cout<num<<″ ″<name<<″ ″<score[0]<<″ ″ <<
p->score[1]<<″ ″<score[2]<
}
調用print函數時,實參指針變數pt將stu的起始地址傳送給形參p(p也是基類型為student的指針變數)。這樣形參p也就指向stu,見圖7.10。
在print函數中輸出p所指向的結構體變數的各個成員值,它們也就是stu的成員值。在main函數中也可以不定義指針變數pt,而在調用print函數時以&stu作為實參,把stu的起始地址傳給實參p。
圖7.10

方法三

用結構體變數的引用作函數參數
#include
#include
using namespace std;
struct Student
{
int num;
string name;
float score[3];
}stu={12345,″Li Li″,67.5,89,78.5};
void main( )
{
void print(Student &);
//函數聲明,形參為Student類型變數的引用
print(stu);
//實參為結構體Student變數
}
//函數定義,形參為結構體Student變數的引用
void print(Student &stud)
{
cout<
<<″ ″ <
}
程序(1)用結構體變數作實參和形參,程序直觀易懂,效率是不高的。
程序(2)採用指針變數作為實參和形參,空間和時間的開銷都很小,效率較高。但程序(2)不如程序(1)那樣直接。
程序(3)的實參是結構體Student類型變數,而形參用Student類型的引用,虛實結合時傳遞的是stu的地址,因而效率較高。它兼有(1)和(2)的優點。
引用變數主要用作函數參數,它可以提高效率,而且保持程序良好的可讀性。在本例中用了string方法定義字元串變數,在某些C++系統中不能運行這些程序,讀者可以修改程序,使之能在自己所用的系統中運行。

分配


在軟體開發過程中,常常需要動態地分配和撤銷內存空間,例如對動態鏈表中結點的插入與刪除。
在C語言中是利用庫函數malloc和free來分配和撤銷內存空間的。
sizeof
C++提供了較簡便而功能較強的運算符new和delete來取代malloc和free函數。
注意: new和delete是運算符,不是函數,因此執行效率高。
雖然為了與C語言兼容,C++仍保留malloc和free函數,但建議用戶不用malloc和free函數,而用new和delete運算符。
new運算符的例子:
new int;//開闢一個存放整數的存儲空間,返回一個指向該存儲空間的地址(即指針)
new int(100);//開闢一個存放整數的空間,並指定該整數的初值為100,返回一個指向該存儲空間的地址
new char[10];//開闢一個存放字元數組(包括10個元素)的空間,返回首元素的地址
new int[5][4];//開闢一個存放二維整型數組(大小為5*4)的空間,返回首元素的地址
float *p=new float (3.14159);//開闢一個存放單精度數的空間,並指定該實數的初值為//3.14159,將返回的該空間的地址賦給指針變數p
new運算符使用的一般格式為
new 類型 [初值]
用new分配數組空間時不能指定初值。如果由於內存不足等原因而無法正常分配空間,則new會返回一個空指針NULL,用戶可以根據該指針的值判斷分配空間是否成功。
delete運算符使用的一般格式為
delete [ ] 指針變數
例如要撤銷上面用new開闢的存放單精度數的空間(上面第5個例子),應該用
delete p;
前面用“new char[10];”開闢的字元數組空間,如果把new返回的指針賦給了指針變數pt,則應該用以下形式的delete運算符撤銷該空間:
delete [] pt;//在指針變數前面加一對方括弧,表示是對數組空間的操作
例 開闢空間以存放一個結構體變數。
#include
#include
using namespace std;
struct Student //聲明結構體類型Student
{ string name;
int num;
char sex;
};
int main( )
{ Student *p; //定義指向結構體類型Student的數據的指針變數
p=new Student; //用new運算符開闢一個存放Student型數據的空間
p->name=″Wang Fun″; //向結構體變數的成員賦值
p->num=10123;
p->sex='m';
cout<name<num
<sex<
delete p;//撤銷該空間
return 0;
}
運行結果為
Wang Fun 10123 m
在動態分配/撤銷空間時,往往將這兩個運算符和結構體結合使用,是很有效的。可以看到:
要訪問用new所開闢的結構體空間,無法直接通過變數名進行,只能通過指針p進行訪問。如果要建立一個動態鏈表,必須從第一個結點開始,逐個地開闢結點並輸入各結點數據,通過指針建立起前後相鏈的關係。
  • 目錄