C語言結構體

一、結構體的概念

前面的教程中我們講解了變量和數組(array),變量是一個一個定義的,數組是一組具有相同類型的變量的集合。但在實際的工作和生活中,爲了表達一個數據集,需要用不同數據類型的變量。例如超女基本信息,姓名爲字符串,身高和年齡爲整數,體重爲浮點數,身材和顏值爲字符串,因爲數據集各要素的數據類型不同,不能用一個數組來存放。

用我們之前學過的知識,如果要存放超女信息,可以用多個變量,如下:

  char name[51];   // 姓名
  int  age;        // 年齡
  int  height;     // 身高,單位:cm
  double weight;     // 體重,單位:kg
  char sc[31];     // 身材,火辣;普通;飛機場
  char yz[31];     // 顏值,漂亮;一般;歪瓜裂棗

這種方式有一個缺陷,如果某數據集有100個要素,就要定義100個變量,對100個變量初始化,把100個變量作爲函數的參數傳遞,實在太麻煩。

在C語言中,使用結構體(struct)來存放一組不同類型的數據,語法如下:

struct 結構體名
{
  結構體成員變量一的聲明;
  結構體成員變量二的聲明;
  結構體成員變量三的聲明;
  ......
  結構體成員變量四的聲明;
};

結構體是一個集合,是一種構造的數據類型,是程序員爲了描述一個數據集自己定義出來的數據類型。結構體的成員(member)可以是任意類型的變量,也可以是結構體變量。以超女爲例:

struct st_girl
{
  char name[51];   // 姓名
  int  age;        // 年齡
  int  height;     // 身高,單位:cm
  int  weight;     // 體重,單位:kg
  char sc[31];     // 身材,火辣;普通;飛機場
  char yz[31];     // 顏值,漂亮;一般;歪瓜裂棗
};

二、結構體變量

結構體是一種程序員自定義的數據類型,是模板,可以用它來定義變量。例如:

struct st_girl queen, princess, waiting, workers;

定義了四個結構體變量,queen王后、princess王妃,waiting宮女和workers侍者。

三、佔用內存的情況

理論上講結構體的各個成員在內存中是連續存放的,和數組非常類似,但是,結構體的佔用內存的總大小不一定等於全部成員變量佔用內存大小之和。在編譯器的具體實現中,爲了提高內存尋址的效率,各個成員之間可能會存在縫隙。用sizeof可以得到結構體佔用內容在總大小,sizeof(結構體名)或sizeof(結構體變量名)都可以。

示例(book90.c)

/*
 * 程序名:book90.c,此程序用於演示C語言的結構體佔用內存的情況
 * 作者:C語言技術網(www.freecplus.net) 日期:20190525
*/
#include <stdio.h>

struct st_girl
{
  char name[50];     // 姓名
  int  age;          // 年齡
  int  height;       // 身高,單位:釐米cm
  char sc[30];       // 身材,火辣;普通;飛機場。
  char yz[30];       // 顏值,漂亮;一般;歪瓜裂棗。
};

int main()
{
  struct st_girl queen;
  printf("sizeof(struct st_girl) %d\n",sizeof(struct st_girl));
  printf("sizeof(queen) %d\n",sizeof(queen));
}

運行效果

在這裏插入圖片描述

從上面的示例可以看出,struct st_girl全部成員變量佔用的內存是50+4+4+30+30=118,但是結構體佔用的內存是120。

注意,C語言提供了結構體成員內存對齊的方法,可以使結構體成員變量之間的內存沒有空隙,我暫時不介紹。

四、結構體的變量名

和數組不一樣,結構體變量名不是結構體變量的地址,結構體變量名就是變量名,就象int ii一樣,只是不能直接輸出,直接輸出沒有意義。取地址要用&,不用鑽牛角尖,不直接輸出就行了。

  struct st_girl stgirl;
  printf("%d\n",stgirl);   // 沒有意義。
  printf("%p\n",stgirl);   // 沒有意義,結構體變量名不是結構體變量的地址。
  printf("%p\n",&stgirl);  // 這纔是結構體的地址。

五、結構體初始化

採用memset函數初始化結構體,全部成員變量的值清零。

memset(&queen,0,sizeof(struct st_girl));

memset(&queen,0,sizeof(queen));

注意事項,如果把一個結構體的地址傳給子函數,子函數用一個結構體指針(如struct st_girl *pst)來存放傳入的結構體的地址,那麼,在子函數中只能用以下方法來初始化結構體:

memset(pst,0,sizeof(struct st_girl));

不能用以下方法來初始化結構體:

memset(pst,0,sizeof(pst));

因爲子函數中用sizeof(pst),得到的不是結構體佔用內存的字節數,而是結構體指針變量佔用內存的字節數(8字節)。

六、成員的訪問(使用)

使用使用圓點.運算符可以訪問(使用)結構的成員,結構體成員變量的使用與普通變量的使用相同。

示例(book92.c)

/*
 * 程序名:book92.c,此程序演示結構體的訪問(使用)。
 * 作者:C語言技術網(www.freecplus.net) 日期:20190525
*/
#include <stdio.h>
#include <string.h>

struct st_girl
{
  char name[50];     // 姓名
  int  age;          // 年齡
  int  height;       // 身高,單位:釐米cm
  char sc[30];       // 身材,火辣;普通;飛機場。
  char yz[30];       // 顏值,漂亮;一般;歪瓜裂棗。
};

int main()
{
  struct st_girl queen;  // 定義結構體變量
  
  // 初始化結構體變量
  memset(&queen,0,sizeof(struct st_girl));

  // 對結構體每個成員賦值
  strcpy(queen.name,"武則天");
  queen.age=28;
  queen.height=168;
  strcpy(queen.sc,"火辣");
  strcpy(queen.yz,"漂亮");

  printf("姓名:%s\n",queen.name);
  printf("年齡:%d\n",queen.age);
  printf("身高:%d\n",queen.height);
  printf("身材:%s\n",queen.sc);
  printf("顏值:%s\n",queen.yz);
}

運行效果

在這裏插入圖片描述

七、結構體數組

結構體可以被定義成數組變量,本質上與其它類型的數組變量沒有區別。

struct st_girl princess[6];
memset(princess,0,sizeof(princess));
strcpy(princess[0].name,"楊玉環");
princess[0].age=18;
strcpy(princess[1].name,"西施");
princess[1].age=28;
……

在實際開發中,我們很少用結構體數組,C++標準庫的vector容器是一個動態的結構體數組,比結構體數組更方便。

八、結構體指針

結構體是一種自定義的數據類型,結構體變量是內存變量,有內存地址,也就有結構體指針。

在指針章節中我們已經學習過,採用不同數據類型的指針指向不同數據類型的變量的地址,這一規則也適用於結構體。如下:

struct st_girl queen;
struct st_girl *pst=& queen;

通過結構體指針可以使用結構體成員,一般形式爲:

(*pointer).memberName

或者:

pointer->memberName

第一種寫法中,圓點.的優先級高於*,(*pointer)兩邊的括號不能少。如果去掉括號寫作*pointer.memberName,那麼就等效於*(pointer.memberName),這樣意義就完全不對了。

第二種寫法中,->是一個新的運算符,習慣稱它爲“箭頭”,有了它,可以通過結構體指針直接使用結構體成員;這也是->在C語言中的唯一用途。

上面的兩種寫法是等效的,程序員通常採用後面的寫法,這樣更加直觀。

示例(book93.c)

/*
 * 程序名:book93.c,此程序演示結構體的指針
 * 作者:C語言技術網(www.freecplus.net) 日期:20190525
*/
#include <stdio.h>
#include <string.h>

struct st_girl
{
  char name[50];     // 姓名
  int  age;          // 年齡
};

int main()
{
  struct st_girl *pst,queen;  // 定義結構體變量
  
  // 初始化結構體變量
  memset(&queen,0,sizeof(struct st_girl));

  pst=&queen;

  // 對結構體每個成員賦值
  strcpy(pst->name,"武則天");
  pst->age=28;

  printf("姓名:%s,年齡:%d\n",queen.name,queen.age);
  printf("姓名:%s,年齡:%d\n",pst->name,pst->age);
  printf("姓名:%s,年齡:%d\n",(*pst).name,(*pst).age);
}

運行效果

在這裏插入圖片描述

九、結構體的複製

在C語言中,結構體的成員如果是基本數據類型(int、char、double)可以用=號賦值,如果是字符串,字符串不是基本數據類型,可以用strcpy函數賦值,如果要把結構體變量的值賦給另一個結構體變量,有兩種方法:1)一種是把結構體變量成員的值逐個賦值給另一個結構體變量的成員,這種方法太笨,沒人使用;2)另一種方法是內存拷貝,C語言提供了memcpy(memory copy的簡寫)實現內存拷貝功能。

函數聲明:

void *memcpy(void *dest, const void *src, size_t n);

參數說明:

src 源內存變量的起始地址。

dest 目的內存變量的起始地址。

n 需要複製內容的字節數。

函數返回指向dest的地址,函數的返回值意義不大,程序員一般不關心這個返回值。

示例(book94.c)

/*
 * 程序名:book94.c, 此程序演示採用memcpy函數複製結構體
 * 作者:C語言技術網(www.freecplus.net) 日期:20190525
*/
#include <stdio.h>
#include <string.h>

struct st_girl
{
  char name[50];     // 姓名
  int  age;          // 年齡
};

void main()
{
  struct st_girl girl1,girl2;

  strcpy(girl1.name,"西施");  // 對girl1的成員賦值
  girl1.age=18;

  // 把girl1的內容複製到girl2中
  memcpy(&girl2,&girl1,sizeof(struct st_girl));

  printf("girl1.name=%s,girl1.age=%d\n",girl1.name,girl1.age);
  printf("girl2.name=%s,girl2.age=%d\n",girl2.name,girl2.age);
}

運行效果

在這裏插入圖片描述
大家可能想起了strcpy函數,與memcpy有相似之處,實際上這兩個函數從功能和實現原理上完本不同,甚至不應該放在一起比較。

1)複製的內容不同,strcpy只能複製字符串,而memcpy可以複製任意內容,例如字符數組、整型、結構體、類等。

2)用途不同,通常在複製字符串時用strcpy,而需要複製其他類型數據時則一般用memcpy。

3)複製的方法不同,strcpy不需要指定長度,它遇到被複制字符的串結尾符0才結束,memcpy則是根據其第3個參數決定複製的長度。

十、結構體作爲函數的參數

結構體是多個變量集合,作爲函數參數時就可以傳遞整個集合,也就是所有成員。如果結構體成員較多,函數參數的初始化和賦值的內存開銷會很大,影響程序的運行效率。所以最好的辦法就是傳遞結構體變量的地址。

示例(book95.c)

/*
 * 程序名:book95.c,此程序演示結構體作爲函數的參數。
 * 作者:C語言技術網(www.freecplus.net) 日期:20190525
*/
#include <stdio.h>
#include <string.h>

struct st_girl
{
  char name[50];     // 姓名
  int  age;          // 年齡
};

// 對結構體賦值的函數
void setvalue(struct st_girl *pst);

int main()
{
  struct st_girl queen;  // 定義結構體變量
  
  // 初始化結構體變量
  memset(&queen,0,sizeof(struct st_girl));

  setvalue(&queen);  // 調用函數,傳結構體的地址

  printf("姓名:%s,年齡:%d\n",queen.name,queen.age);
}

void setvalue(struct st_girl *pst)
{
  // 對結構體每個成員賦值
  strcpy(pst->name,"武則天");
  pst->age=28;
}

運行效果

在這裏插入圖片描述

十一、枚舉和共同體

C語言還有兩種數據結構:枚舉和共同體,這兩種數據結構的應用太少了,少到我已經記不起它們的定義,二十年來,我從未使用過,也不介紹了。

十二、memset和bzero函數

1、memset函數

memset 函數是內存空間賦值函數,用來給某一塊內存空間進行賦值的。

包含在<string.h>頭文件中。

函數的聲明如下:

void *memset(void *s, int v, size_t n);  

s爲內存空間的地址,一般是數組名或結構體的地址。

v爲要填充的值,填0就是初始化。

n爲要填充的字節數。

在實際開發中,程序員用memset函數對數組或結構體清零,在之前的章節中,我們已經用過很多次了。

2、bzero函數

bzero函數是內存空間清零。

包含在<string.h>頭文件中。

函數的聲明如下:

void bzero(void *s, size_t n); 

s爲內存空間的地址,一般是數組名或結構體的地址。

n爲要清零的字節數。

如果要對數組或結構體清零,用memset和bzero都可以,沒什麼差別,看程序員的習慣。

十三、課後作業

編寫示例程序,把本章節介紹的知識點全部演示一遍,用程序演示可以加深您的理解和映象。

十四、版權聲明

C語言技術網原創文章,轉載請說明文章的來源、作者和原文的鏈接。
來源:C語言技術網(www.freecplus.net)
作者:碼農有道

如果文章有錯別字,或者內容有錯誤,或其他的建議和意見,請您留言指正,非常感謝!!!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章