C語言07- 結(jié)構(gòu)體、聯(lián)合體、枚舉

15:結(jié)構(gòu)體、聯(lián)合體、枚舉

15.1:結(jié)構(gòu)體

15.1.1:結(jié)構(gòu)體的定義與使用

定義一個結(jié)構(gòu)有4種形式,建議用typedef:

1: 
struct 結(jié)構(gòu)名 {
    成員表列
};
struct 結(jié)構(gòu)名 變量名; 

2,
struct 結(jié)構(gòu)名 {
    成員表列
} 變量名1,變量名2;

3,
struct {
    成員表列
} 變量名1,變量名2;

4,
typedef struct _結(jié)構(gòu)名 {
    成員表列
} 結(jié)構(gòu)名, *P結(jié)構(gòu)名;

結(jié)構(gòu)名 變量名;

例子:
typedef struct _student {
    int id;
    int age;
    char name[20];
    char sex;
    float score;
} student, *pstudent;

student s1 = {1001, 19, "tom", 'm', 80.5};
student s2 = {};
s2.id=8;
s2.age=16;
strcpy_s(s2.name,"jerry");
s2.sex='M';
s2.weight=47.05f;

// . 結(jié)構(gòu)體的成員訪問
printf("id:%d, age:%d, name:%s, sex:%cm %f\n", s1.id; s1.age, s1.name, s1.sex, s1.score);
printf("id:%d, age:%d, name:%s, sex:%cm %f\n", s2.id; s2.age, s2.name, s2.sex, s2.score);

student *ps1 = &s1;
pstudent ps2 = &s2; 

// -> 結(jié)構(gòu)體的指針訪問
printf("id:%d, age:%d, name:%s, sex:%cm %f\n", ps1->id; ps1->age, ps1->name, ps1->sex, ps1->score);
printf("id:%d, age:%d, name:%s, sex:%cm %f\n", ps2->id; ps2->age, ps2->name, ps2->sex, ps2->score);

sizeof(ps1);//等價于sizeof(student *);ps1是個指針,x86是4個字節(jié),x64是8個字節(jié)。
sizeof(student *);//x86是4個字節(jié),x64是8個字節(jié)。
sizeof(*ps1);//等價于sizeof(student)的大?。?6字節(jié)
sizeof(student);/36字節(jié)

*與->、.運(yùn)算符 關(guān)系
->與.運(yùn)算符比*的優(yōu)先級高,所以沒有括號,先算->和.

15.1.2:結(jié)構(gòu)體中的指針與數(shù)組

結(jié)構(gòu)體中成員是一個字符指針,如果要給此成員單獨(dú)賦值,那么需要單獨(dú)為name指定內(nèi)存。

結(jié)構(gòu)體中可以包含指針成員。如:

typedef struct _student {
    int age;
    char sex;
    char \*name;//name成員是一個字符指針,如果要利用name來表示一個人的名字信息,那么需要單獨(dú)為name指定內(nèi)存。
}student , \*pstudent;

//如:
student s1={30,’M’,”tom”};//將s1.name指向了靜態(tài)常量區(qū)中的”tom”字符串首地址。

student s2;
s2.age=25;
s2.sex=’F’;
s2.name=(char *)malloc(16);//為s2.name在堆上分配了16個字節(jié)的內(nèi)存,然后把名字拷貝進(jìn)內(nèi)存。
strcpy_s(s2.name,16,”susan”);

//結(jié)構(gòu)體中的指針,可以指向同一個類型的結(jié)構(gòu)體地址.
//鏈表
typedef struct _node {
    int value;
    struct _node \*next;//next指針指向了同樣類型的結(jié)構(gòu)體地址
}node, \*pnode;

//二叉樹
typedef struct _btree {
    int value;
    struct _btree \*left;
    struct _btree \*right;
}btree, \*pbtree;

下圖在鏈表_node中,鏈表的頭結(jié)點(diǎn)中的next指針指向下一個結(jié)點(diǎn),下一個結(jié)點(diǎn)中的next指針又指向下下一個結(jié)點(diǎn),最后一個結(jié)點(diǎn)的next指針為NULL。這樣,只要知道了鏈表的頭結(jié)點(diǎn),就可以將鏈表中的所有結(jié)點(diǎn)遍歷一次。


image.png

結(jié)構(gòu)體的定義中,經(jīng)常會出現(xiàn)下面3種不同的成員定義方法:

image.png

  1. 第一種定義方法:name[]是一個包含1個元素的字符數(shù)組,與length成員一起用來存放一個變長的數(shù)據(jù),即在name后面還有l(wèi)ength-1個字節(jié)的變長數(shù)據(jù)。此種結(jié)構(gòu)體經(jīng)常用于存放變長數(shù)據(jù)。
  2. 第二種定義方法:name[]是一個64個元素的字符數(shù)組,因此name[]在結(jié)構(gòu)體中占用的內(nèi)存空間是定長的。此種結(jié)構(gòu)體用來存放數(shù)據(jù)的時候,數(shù)據(jù)不能超過指定的長度,而且容易造成部分內(nèi)存空間的浪費(fèi)。
  3. 第三種定義方法:name是一個字符指針,在結(jié)構(gòu)體中占4個字節(jié)(X86)或者8個字節(jié)(X64),必須為name指定新的內(nèi)存空間,才能保存相應(yīng)的數(shù)據(jù)。

15.1.3:結(jié)構(gòu)體淺拷貝深拷貝寫時拷貝

  1. 淺拷貝(shallow copy):是指在被拷貝的對象中有指針的情況下,只是將目標(biāo)指針設(shè)置為被拷貝指針的值(地址),而不會為目標(biāo)指針分配一個新的內(nèi)存,并把數(shù)據(jù)從被拷貝指針?biāo)傅膬?nèi)存中拷貝到這個內(nèi)存中來。
  2. 深拷貝(deep copy):是為目標(biāo)指針申請一個新的內(nèi)存,然后將數(shù)據(jù)從被拷貝指針?biāo)傅膬?nèi)存中拷貝到這個新申請的內(nèi)存中來。采用深拷貝,釋放內(nèi)存的時候就不會出現(xiàn)在淺拷貝時重復(fù)釋放同一內(nèi)存的錯誤。

在結(jié)構(gòu)體中默認(rèn)是淺拷貝

typdef struct _struct1 {
       int a;
       char b;
}struct1;

struct1 s1 = {1, ‘a(chǎn)’};
struct1 s2 = s1;//因?yàn)榻Y(jié)構(gòu)體中不包含指針和數(shù)組,那么這個賦值是沒有任何問題的。

//下面的結(jié)構(gòu)體定義:
typdef struct _struct2 {
       int a;
       char *p;
}struct2;//結(jié)構(gòu)體中包含了一個指針成員p。那么試分析下面的代碼:

struct2 s1;

s1.a = 10;
s1.p = (char *)malloc(100);
strcpy_s(s1.p,100,”hello world”);
struct1 s2 = s1;

free(s1.p);//這個時候s2.p所指向的內(nèi)存已經(jīng)在執(zhí)行完free(s1.p)之后就被釋放了,因此s2.p就是一個野指針了,因?yàn)樗赶虻闹羔槂?nèi)存已經(jīng)被釋放掉了。
s1.p=NULL;
image.png
s1.a = 10;
s1.p = (char *)malloc(100);
strcpy_s(s1.p,100,”hello world”);

struct1 s2;
s2.a=s1.a;
s2.p=(char *)malloc(100);
memcpy(s2.p,s1.p,100);

free(s1.p);
s1.p=NULL;//即使執(zhí)行了free(s1.p)之后,也不會對s2.p造成任何的影響
image.png

寫時拷貝(copy on write):其實(shí)就是對指針?biāo)赶虻膬?nèi)存用引用計數(shù);釋放其實(shí)是將引用計數(shù)減一,當(dāng)引用計數(shù)為0,釋放內(nèi)存。

image.png

image.png

15.1.4:結(jié)構(gòu)體應(yīng)用

結(jié)構(gòu)體數(shù)組:結(jié)構(gòu)體數(shù)據(jù)與結(jié)構(gòu)體指針是可以存放在數(shù)組中的

typedef struct _student {
       int age;
       char sex;
       char name[20];
} student, *pstudent;

void structPoint(void) {
       student *stu3[2];//結(jié)構(gòu)體指針數(shù)組(返回指針的數(shù)組)
       student stus[3]={{25,'M',"tom"},{22,'F',"lucy"},{23,'M',"david"}};//結(jié)構(gòu)體數(shù)組與初始化
       student stu2[2];//結(jié)構(gòu)體數(shù)組

       for(int i=0;i<2;i++) {//初始化結(jié)構(gòu)體數(shù)組stu2
                printf("Please input the age,sex,name(<=20)\n");
              fflush(stdin);
              scanf_s("%d %c %s",&stu2[i].age,&stu2[i].sex,stu2[i].name,20);
       }

       for(int i=0;i<2;i++) {//初始化結(jié)構(gòu)體指針數(shù)組stu3
              stu3[i]=&stu2[i];
       }

       for(int i=0;i<2;i++) {//遍歷結(jié)構(gòu)體指針數(shù)組stu3
              printf("stu3:age:%d,sex:%c,name:%s\n",
                     stu3[i]->age,stu3[i]->sex,stu3[i]->name);
       }
       
       printf("\n");
       
       for(int i=0;i<3;i++) {//遍歷結(jié)構(gòu)體數(shù)組stus
              printf("stus:age:%d,sex:%c,name:%s\n",stus[i].age,stus[i].sex,stus[i].name);
       }
 

       for(int i=0;i<2;i++){//遍歷結(jié)構(gòu)體數(shù)組stu2
              printf("stu2:age:%d,sex:%c,name:%s\n",                     stu2[i].age,stu2[i].sex,stu2[i].name);
       }

       return 0;
}

15.1.5:sizeof計算結(jié)構(gòu)體長度

據(jù)對齊分為自然對齊和強(qiáng)制對齊兩種方式:

  1. 自然對齊:各個類型自然對齊,即其內(nèi)存地址必須是其類型本身的整數(shù)倍。結(jié)構(gòu)體對齊到其中成員最大長度類型的整數(shù)倍。
  2. 強(qiáng)制對齊:預(yù)編譯語句將定義的變量強(qiáng)制對齊到n

自然對齊應(yīng)該遵守如下兩條規(guī)則:

  1. 數(shù)據(jù)成員對齊規(guī)則:在默認(rèn)情況下,各成員變量存放的起始地址相對于結(jié)構(gòu)的起始地址的偏移量:sizeof(它的基本類型)或其倍數(shù);如果該成員為非基本成員,則為其子成員中最大的基本類型的整數(shù)倍。
  2. 整體對齊規(guī)則:結(jié)構(gòu)的總大小也有個約束條件:最大sizeof(基本類型)的整數(shù)倍
typedef struct _a {
     char      c1;
     long      i;
     char      c2;
     double   f;
}a;

typedef struct _b {
     char      c1;
     char      c2;
     long      i;
     double   f;
}b;

//sizeof運(yùn)行結(jié)果如下:
     sizeof of double, long, char = 8, 4, 1
     sizeof of a, b = 24, 16

為什么對齊?:cpu在一個時鐘周期內(nèi)存取數(shù)據(jù),效率高。

強(qiáng)制對齊
遵守如下兩條對齊規(guī)則:

  1. 數(shù)據(jù)成員對齊規(guī)則:n字節(jié)對齊就是說變量存放的起始地址的偏移量:min(sizeof(基本類型),n)或其倍數(shù)。
  2. 整體對齊規(guī)則: 結(jié)構(gòu)的總大小也有個約束條件:min(最大的sizeof(基本類型),n)的倍數(shù)。
#pragma pack(push) //保存對齊狀態(tài)
#pragma pack(n) //定義結(jié)構(gòu)對齊到n
       //定義結(jié)構(gòu)//
#pagman pack(pop)//恢復(fù)對齊狀態(tài)


#pragma pack(push)
#pragma pack(8)
struct s1 {
    short a;//2
    long b;//4
};

struct s2 {
    char c;//1
    s1 d;//
    long long e;//8
};

struct s3 {
    char c;
    short a;
    long b;
    long long e;
};
#pragma pack(pop)

//sizeof(s1)=8 sizeof(s2)=24 sizeof(s3)=16
/*
s1:由于#pragma pack(8)要求8字節(jié)對齊,但s1中所有成員都比8小,所以各個成員只需要按照自然對齊即可,所以a占2個字節(jié),b占4個字節(jié),a后面需要補(bǔ)齊2字節(jié),才能使long類型的b自然對齊。然后b占4個字節(jié),因此整個s1結(jié)構(gòu)共占用:2+2(pad)+4=8字節(jié)。

s2:c占1個字節(jié),d為s1結(jié)構(gòu)體成員,s1結(jié)構(gòu)體成員根據(jù)對齊規(guī)則,按照其中最大成員long的4字節(jié)對齊,所以c之后需要補(bǔ)3字節(jié)才能讓d達(dá)到4字節(jié)對齊。對于x86,long long數(shù)據(jù)類型為8字節(jié),所以存完d后,需要在后面補(bǔ)4個字節(jié),讓e 8字節(jié)對齊。因此s2結(jié)構(gòu)共占用:1+3(pad)+8+4(pad)+8=24。

s3:c占1個字節(jié),由于a為short類型,所以c之后需要填充1個字節(jié),才能讓a對齊。此時,b已經(jīng)對齊,占4個字節(jié)。而e大小為8個字節(jié),已經(jīng)對齊。所以s3結(jié)構(gòu)共占用:1+1(pad)+2+4+8=16。
*/ 

15.1.6:??臻g對齊

  1. 棧形參部分:按照4字節(jié)(x86)或者8字節(jié)(x64)對齊
  2. 局部變量區(qū)間:變量位置可能變化,char1個字節(jié),short2個字節(jié)...

結(jié)構(gòu)體內(nèi)部對齊規(guī)律不變


image.png

15.2:聯(lián)合體(UNION)

聯(lián)合體(union,又叫共用體):使幾種不同類型的變量存放到同一段內(nèi)存單元中。即使用覆蓋技術(shù),幾個變量互相覆蓋重疊。

定義形式:

1:
union 共用體名 {
    數(shù)據(jù)類型 成員名;
    數(shù)據(jù)類型 成員名;
    ...
} 變量名;

2:
union 共用體名 {
    數(shù)據(jù)類型 成員名;
    數(shù)據(jù)類型 成員名;
    ...
};

3:
typedef union {
    數(shù)據(jù)類型 成員名;
    數(shù)據(jù)類型 成員名;
    ...
} 共用體名;

4:
typedef union _共用體名 {
    數(shù)據(jù)類型 成員名;
    數(shù)據(jù)類型 成員名;
    ...
}共用體名,*P共用體名;

聯(lián)合體定義使用時注意點(diǎn):

  1. union中可以定義多個成員,union的大小由最大的成員的大小決定
  2. union成員共享同一塊大小的內(nèi)存,一次只能使用其中的一個成員。
  3. 對某一個成員賦值,會覆蓋其他成員的值,因?yàn)樗麄児蚕硪粔K內(nèi)存。
  4. union中各個成員存儲的起始地址都是相對于基地址的偏移都為0。

聯(lián)合體和結(jié)構(gòu)體區(qū)別:

  1. 聯(lián)合體和結(jié)構(gòu)體都由多個不同的數(shù)據(jù)類型成員組成,但在任何同一時刻,聯(lián)合體只存放了一個被選中的成員,而結(jié)構(gòu)體的所有成員都存在。
  2. 對于聯(lián)合體的不同成員賦值, 會對其它成員重寫, 原成員值被覆蓋, 而對于結(jié)構(gòu)體的不同成員賦值是互不影響的。
  3. 結(jié)構(gòu)體里可以含有union成員,union里也可以含結(jié)構(gòu)體成員。

15.2.1:聯(lián)合體和結(jié)構(gòu)體相互包含

//聯(lián)合體里包含結(jié)構(gòu)體:
typedef union _Demo {
       int a;
       struct {
              int b;
              char c;
       }s;
       float f;
}Demo;

//結(jié)構(gòu)體中包含聯(lián)合體:
typedef struct _demo {
       union {    
              int a;
              char b;
       }c;
       int d;

}Demo2;

15.2.2:Union的應(yīng)用

判斷系統(tǒng)是低位優(yōu)先還是高位優(yōu)先

typedef union {
      char c;
      int a;
} U;

int is_integer_lower_store() {
       U u;
       u.a = 1;

       return u.c;
}

15.3:枚舉(enum)

枚舉(enum):當(dāng)一個變量的值被限于列出來的值的范圍內(nèi),那么這個變量就可以被定義為一個枚舉類型的變量。

定義形式:

1:
enum 枚舉名 {
    值1,//如果不額外指定則第一個標(biāo)識等于整數(shù)0,后續(xù)依次加1
    值2,
    值3=7,//注意,值3的值不能被指定為0,1,否則會和值1,值2沖突
    值4,//這個時候,值4的值是8
    ....
    值n
};
enum 枚舉名 變量名;

2:
typedef enum  _枚舉名 {
    值1,
    值2,
    值3,
    值4,
    ....
    值n  
} 枚舉名, *P枚舉名;
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容