18-C語言:第19天筆記

C語言:第19天筆記

內容提要

  • 構造類型
    • 結構體
    • 共用體/聯合體

構造類型

數據類型

  1. 基本類型/基礎類型/簡單類型
    • 整型
      • 短整型:short – 2字節
      • 基本整型:int – 4字節
      • 長整型:long – 32位系統4字節/ 64位系統8字節
      • 長長整型:long long 8字節(大多數現代機器,舊機器可能超過8字節),C99新增
      • 注意:以上類型又分為signed(默認) 和 unsigned
    • 浮點型
      • 單精度型:float --4字節
      • 雙精度型:double --8字節
      • 長雙精度型:long double – 16字節(視平臺而定),C99新增
    • 字符型: char --1字節
  2. 指針類型
    • 數據類型*:32位系統4字節,64位系統8字節
    • void*:通用指針類型(萬能指針)32位系統4字節,64位系統8字節
  3. 空值類型
    • void:無返回值,無形參(不能修飾變量)
  4. 構造類型(自定義類型)
    • 結構體類型:struct
    • 共用體/聯合體類型:union
    • 枚舉類型:enum

結構體

結構體定義【定義類型】
  • **定義:**自定義數據類型的一種,關鍵字struct。

  • 語法:

    struct 結構體名 // 結構體名:自定義的數據類型名字  類似于int,double之類的
    {數據類型1 成員名稱1;  // 結構體中的變量叫做成員數據類型2 成員名稱2;...
    };
    

    注意:結構體中定義的變量,稱之為成員變量(成員屬性)

  • 格式說明:

    • 結構體名:合法的標識符,建議首字母大寫(所謂的結構體名,就是自定義類型的類型名稱)
    • 數據類型n:C語言支持的所有類型(包括函數,函數在這里用函數指針表示)
    • 成員名稱n:合法的表示,就是變量的命名標準
    • 數據類型n 成員名稱n:類似于定義變量,定義了結構體中的成員
  • 注意:

    • 結構體在定義的時候,成員不能賦值,示例:

      struct Cat
      {int age = 5;      // 錯誤,結構體定義的時候,并未在內存中申請空間,因此無法進行賦值double height;    // 正確void (*run)(void);// 正確
      };
      
  • 常見的定義格式:

    • 方式1:常規定義(命名結構體,只定義數據類型)

      struct Student
      {int num;           // 學號char name[20];     // 姓名char sex;          // 性別int age;           // 年齡char address[100]; // 籍貫void (*info)(void);// 信息輸出(函數指針)
      };
      
    • 方式2:匿名結構體(常用于作為其他結構體的成員使用)

      struct Dog // 命名結構體
      {char *name;        // 姓名int age;           // 年齡struct // 匿名結構體{// 定義結構體時不能省略成員,否則編譯報錯int year; // 年int month;// 月int day;  // 日} bithday;         // 年齡,匿名結構體一定要提供成員名稱,否則無法訪問
      };
      

      注意:定義匿名結構體的同時必須定義結構體成員,否則編譯報錯;結構體可以作為另一個結構體的成員。

      總結:

      • 結構體可以定義在局部位置(函數作用域、塊作用域),也可以定義在全局位置(推薦,可以復用)
      • 全局位置的結構體名和局部位置的結構體名可以相同,遵循就近原則(和變量的定義同理)
    • 結構體類型的使用:

      利用結構體類型定義變量、數組,也可以作為函數的返回值和參數;結構體類型的使用與基本數據類型的使用類似。

結構體變量定義【定義變量】
三種形式定義結構體變量

說明:結構體變量也被稱作結構體對象或者結構體實例。

  • 第一種方式:

    ① 先定義結構體(定義數據類型)

    ② 然后定義結構體變量(定義變量)

    示例:

    // 定義結構體(定義數據類型)
    struct A
    {int a;char b;
    };// 定義結構體實例/變量(定義變量)
    struct A x;   // A就是數據類型,x就是變量名
    struct A y;   // A就是數據類型,y就是變量名
    
  • 第二種方式:

    ① 在定義結構體的同時,定義結構體變量(同時定義數據類型和變量)

    語法:

    struct 結構體名
    {數據類型1 數據成員1;...
    } 變量列表;
    

    示例:

    struct A
    {int a;char b;
    } x, y; // A就是數據類型,x,y就是變量名
    

    此時定義了一個結構體,x和y就是這個結構體類型的變量。

  • 第三種方式:

    ① 在定義匿名結構體的同時,定義結構體變量。

    示例:

    struct
    {int a;char b;
    } x, y;
    

    此時定義了一個沒有名字的結構體(匿名結構體),x,y是這個結構體類型的變量。

匿名結構體
  • 優點:少寫一個結構體名稱
  • 缺點:只能使用一次,定義結構體類型的同時必須定義變量。
  • 應用場景:
    • 當結構體的類型只需要使用一次,并且定義類型的同時定義了變量。
    • 作為其他結構體的成員使用。
定義結構體同時變量初始化

說明:定義結構體的同時,定義結構體變量并初始化

struct Cat
{int age;char color[20];
} cat;
  • 結構體成員部分初始化,大括號不能省略
  • 結構體成員,沒有默認值,是隨機值,和局部作用域的變量一致。

案例:

#include <stdio.h>/*** 先定義結構體,再定義結構體變量(實例)*/ 
void fun1()
{// 定義結構體struct A{int a;char b;};// 定義結構體變量struct A x;struct A y;struct A x1,y1;}/*** 定義結構體的同時定義結構體變量*/ 
void fun2()
{struct A{int a;char b;} x,y;struct A z;struct A x1,y1;
}/*** 定義匿名結構體的同時定義結構體變量*/ 
void fun3()
{struct{int a;char b;} x,y;struct{int a;char b;} z;
}int main(int argc,char *argv[])
{fun1();fun2();fun3();return 0;
}
結構體變量的使用【變量使用】
結構體變量訪問成員
  • 語法:

    結構體變量名(實例名).成員名;
    

    ① 可以通過訪問成員進行賦值(存數據)

    ② 可以通過訪問成員進行取值(取數據)

  • 結構體變量未初始化,結構體成員的值是隨機的(和局部作用域的變量和數組同理)

結構體變量定義時初始化成員
  • 建議用大括號{}標明數據的范圍。
  • 結構體成員初始化,可以部分初始化(和數組類似),部分初始化時一定要帶大括號標明數據范圍。未初始化的成員使用清零操作。
案例

#include <stdio.h>/** 定義全局的結構體,方便被多個函數訪問*/
struct Dog
{char *name;        // 名字int    age;        // 年齡char   sex;        // 性別 M:公,W:母void (*eat)(void); // 吃狗糧
};void eat()
{printf("狗狗正在吃狗糧!\n");
}/*** 方式1:先定義,再初始化   ---> 結構體變量訪問成員*/
void fun1()
{// 定義結構體變量struct Dog dog;// 結構體變量成員賦值dog.name = "旺財";dog.age = 5;dog.sex = 'M';dog.eat = eat;// 結構體變量成員訪問printf("%s,%d,%c\n", dog.name, dog.age, dog.sex);// 訪問成員方法(函數)dog.eat();
}/*** 方式2:定義的同時初始化,給變量成員初始化*/
void fun2()
{// 定義結構體變量的同時,給變量成員初始化struct Dog d1 = {"旺財", 5, 'M', eat}; // 完整初始化,按照順序賦值struct Dog d2 = {.name = "萊德", .sex = 'M'}; // C99之后,支持部分成員初始化,未初始化的成員自動填充0struct Dog d3 = {"旺財"}; // C99之后,可以默認初始化第一個成員,其他成員用0填充// 結構體變量成員訪問printf("%s,%d,%c\n", d1.name, d1.age, d1.sex);printf("%s,%d,%c\n", d2.name, d2.age, d2.sex);printf("%s,%d,%c\n", d3.name, d3.age, d3.sex);}int main(int argc, char *argv[])
{fun1();printf("\n------------\n");fun2();return 0;
}
結構體數組的定義【數組定義】
什么時候需要結構體數組

比如:我們需要管理一個學生對象,只需要定義一個struct Student yifanjiao;

假如:我們需要管理一個班的學生對象,此時就需要定義一個結構體數組struct Student stus[50];

定義

存放結構體實例的數組,稱之為結構體數組。

四種形式定義結構體數組
  • 第一種方式:

    // 第一步:定義一個結構體數組
    struct Student
    {char      *name;     // 姓名int         age;     // 年齡float scores[3];     // 三門課程的成績
    } stus[3];// 第二步:賦值
    stus[0].name = "張三";
    stus[0].age = 21;
    stus[0].scores[0] = 89;
    stus[0].scores[1] = 99;
    stus[0].scores[2] = 87;stus[1].name = "李四";
    stus[1].age = 22;
    stus[1].scores[0] = 66;
    stus[1].scores[1] = 77;
    stus[1].scores[2] = 88;
    
  • 第二種方式:

    // 第一步:定義一個學生結構體(定義數據類型)
    struct Student
    {char      *name;     // 姓名int         age;     // 年齡float scores[3];     // 三門課程的成績
    };// 第二步:定義結構體實例(定義結構體實例)
    struct Student zhangsan = {"張三", 21, {89,99,87}};
    struct Student lisi     = {"李四", 22, {66,77,88}};// 第三步:定義結構體數組
    struct Student stus[] = {zhangsan, lisi};
    
  • 第三種方式:

    // 第一步:定義一個學生結構體(定義數據類型)
    struct Student
    {char      *name;     // 姓名int         age;     // 年齡float scores[3];     // 三門課程的成績
    };// 第二步:定義結構體數組并初始化成員
    struct Student stus[] = {{"張三", 21, {89,99,87}},{"李四", 22, {66,77,88}}
    };
    
  • 第四種方式:

    // 第一步:定義一個結構體數組,并初始化
    struct Student
    {char      *name;     // 姓名int         age;     // 年齡float scores[3];     // 三門課程的成績   
    } stus[] = {{"張三", 21, {89,99,87}},{"李四", 22, {66,77,88}}
    };
    
結構體數組的訪問【數組訪問】

語法:

結構體成員.成員名
結構體指針 -> 成員名     // -> 結構體指針成員訪問符

舉例:

// 方式1:結構體成員訪問
(*p).成員名
// 方式2:結構體指針訪問
p -> 成員名

案例:

#include <stdio.h>/* 定義全局的Student結構體 */
struct Student
{int          id;         // 編號char      *name;         // 姓名int         age;         // 年齡float scores[3];         // 三門課成績void (*info)(char*,int); // 信息輸出
};void info(char* name, int age)
{printf("大家好,我是%s,今年%d歲!\n", name, age);
}int main(int argc, char *argv[])
{// 定義結構體實例并初始化struct Student zhangsan = {1, "張三", 21, {78,88,98}, info};struct Student lisi     = {2, "李四", 22, {90,98,91}, info};// lisi.info = info// 定義結構體數組并初始化struct Student stus[] = {zhangsan, lisi};// 計算數組的大小int len = sizeof(stus) / sizeof(stus[0]);// 用一個指針進行遍歷struct Student *p = stus;// 表格-表頭printf("序號\t姓名\t年齡\t語文\t數學\t英語\n");for (; p < stus + len; p++){// 結構體成員訪問,不推薦// printf("%d\t%s\t%d\t%.2f\t%.2f\t%.2f\t\n",(*p).id, (*p).name, (*p).age, (*p).scores[0], (*p).scores[1], (*p).scores[2]);// (*p).info((*p).name, (*p).age);// 結構體指針訪問,推薦printf("%d\t%s\t%d\t%.2f\t%.2f\t%.2f\t\n",p->id, p->name, p->age, p->scores[0], p->scores[1], p->scores[2]);// 函數調用p->info(p->name, p->age);}printf("\n");return 0;
}

結構體類型

結構體數組
案例
  • 需求:對候選人得票的統計程序。設有3個候選人,每次輸入一個得票的候選人名字,要求最后輸出個人得票的結果。

  • 案例:

    
    #include <stdio.h>
    #include <string.h>#define LEN 3/* 定義一個候選人結構體 */
    struct Person
    {char name[20];     // 名字int count;         // 票數
    };/*** 定義候選人數組,并初始化*/
    struct Person persons[LEN] = {{"衛宇星", 0},{"焦藝凡", 0},{"楊家輝", 0}	
    };int main(int argc, char *argv[])
    {// 定義循環變量register int i,  j;// 創建一個數組,用來接收控制臺錄入的候選人名字char leader_name[20];// 使用一個for循環,默認10個人參與投票printf("世紀美男投票系統!\n");for (i = 0; i < 10; i++){printf("請輸入您覺得最帥的那位哥哥的名字:\n");scanf("%s", leader_name);// 從候選人列表中匹配被投票的人 count++for (j = 0; j < LEN; j++){// 判斷兩個字符串是否相等  strcmpif (strcmp(leader_name, persons[j].name) == 0){persons[j].count++; // 票數+1}}}printf("\n");printf("\n投票結果:\n");struct Person *p = persons; // 指針p指向數組persons的第一個元素// 遍歷數組:使用指針變量來遍歷數組for (; p < persons + LEN; p++){printf(" %s:%d\n",p->name, p->count);}printf("\n");// 遍歷數組:使用指針來遍歷數組for (i = 0; i < LEN; i++){printf(" %s:%d\n",(persons + i)->name, (persons + i)->count);}printf("\n");// 遍歷數組:使用下標來遍歷數組for (i = 0; i < LEN; i++){printf(" %s:%d\n",persons[i].name, persons[i].count);}return 0;
    }
    
結構體指針
  • **定義:**指向結構體變量或者結構體數組的起始地址的指針叫做結構體指針。

  • 語法:

    struct 結構體名 *指針變量列表;
    
  • 舉例:

    
    #include <stdio.h>// 定義一個Dog結構體
    struct Dog
    {char name[20];int age;
    };int main(int argc, char *argv[])
    {// 創建Dog實例struct Dog dog = {"茍富貴", 5};// 基于結構體變量的結構體指針struct Dog *p = &dog;printf("%s,%d\n", p->name, p->age);// 創建Dog數組struct Dog dogs[] = {{"茍富貴", 5},{"勿相忘", 6}};// 基于結構體數組元素的結構體指針struct Dog *p1 = dogs;int len = sizeof(dogs)/sizeof(dogs[0]);for (; p1 < dogs + len ; p1++){printf("%s,%d\n", p1->name, p1->age);}return 0;
    }
    
結構體成員的訪問
結構體成員訪問
  • 結構體數組名訪問結構體成員

    • 語法:

       結構體數組名 -> 成員名;(*結構體數組名).成員名; // 等價于上面寫法
      
    • 舉例:

       printf("%s:%d\n",persons->name, persons->count);
      
  • 結構體成員訪問符

    • .:左側是結構體變量,也可以叫做結構體對象訪問成員符,右側是結構體成員。

    • ->:左側是結構體指針,也可以叫做結構體指針訪問成員符,右側是結構體成員。

    • 舉例:

       struct Person *p = persons;  // p就是結構體指針for(; p < persons + len; p++)printf("%s:%d\n",p->name,p->count);
      
  • 訪問結構體成員有兩種類型,三種方式:

    • **類型1:**通過結構體變量(對象|示例)訪問成員

       struct Stu{int id;char name[20];} stu; // 結構體變量// 訪問成員stu.name;
      
    • **類型2:**通過結構體指針訪問成員

      • **第1種:**指針引用訪問成員

         struct Stu{int id;char name[20];} stu;struct Stu *p = &stu;// 指針引用訪問成員p -> name; // 等價于 (*p).name;    
        
      • **第2種:**指針解引用間接訪問成員

         struct Stu{int id;char name[20];} stu;struct Stu *p = &stu;// 指針引用訪問成員(*p).name; // 等價于 p -> name;    
        
    • 結構體數組中元素的訪問

       // 結構體數組struct Stu{int id;         // 編號(成員)char name[20];  // 名字(成員)float scores[3];// 三門成績(成員)} stus[3] = {{1,"張三"{90,89,78},{2,"李四"{90,88,78},{3,"王五"{77,89,78},};// 取數據 --- 下標法printf("%s,%.2f\n", stus[1].name, stus[1].scores[1]);// 李四 88// 取數據 --- 指針法printf("%s,%.2f\n", stus->name, stus->scores[2]);// 張三 78printf("%s,%.2f\n", (stus+1) -> name, (stus+1) -> scores[1]);// 李四 88printf("%s,%.2f\n", (*(stus+1)).name, (*(stus+1)).scores[1]);// 李四 88
      

      小貼士:

      結構體是自定義數據類型,它是數據類型,用法類似于基本類型的int;

      結構體數組它是存放結構體對象的數組,類似于int數組存放int數據;

      基本類型數組怎么用,結構體數組就怎么用—>可以遍歷,可以作為形式參數,也可以做指針等;

  • 結構體類型的使用案例

    結構體可以作為函數的返回類型,形式參數等。

    舉例:

    
    #include <stdio.h>
    #include <string.h>/*** 定義一個Cat結構體*/
    struct Cat
    {char     *name;      // 姓名int        age;      // 年齡char color[20];      // 顏色
    };/*** 結構體類型作為形式參數*/
    void test1(struct Cat c)
    {printf("test1:\n%s,%d,%s\n", c.name, c.age, c.color);
    }/*** 結構體類型作為函數返回類型*/
    struct Cat test2(struct Cat c)
    {c.name = "金寶";c.age = 3;strcpy(c.color, "黃色");return c;
    }/*** 結構體指針作為參數和返回類型* 需求:根據Cat的name,在Cat數組中匹配Cat對象*/
    struct Cat *test3(struct Cat *cats, int len, char* name)
    {struct Cat *p = cats;for (; p < cats + len; p++){if (strcmp(name, p->name) == 0) return p; // p是指針}return NULL;
    }int main(int argc, char *argv[])
    {struct Cat cat = {"招財", 5, "白色"};test1(cat);struct Cat c = test2(cat);printf("test2:\n%s,%d,%s\n", c.name, c.age, c.color);struct Cat cats[] = {{"招財", 5, "白色"},{"金寶", 3, "金色"}};struct Cat *c2 = test3(cats, sizeof(cats)/sizeof(cats[0]),"招財");printf("test3:\n%s,%d,%s\n", c2->name, c2->age, c2->color);return 0;
    }
    
結構體類型求大小
字節對齊
  • 字節對齊的原因:

    1. 硬件要求 某些硬件平臺(如ARM、x86)要求特定類型的數據必須對齊到特定地址,否則會引發性能下降或硬件異常。
    2. 優化性能 對齊的數據訪問速度更快。例如,CPU訪問對齊的 int 數據只需一次內存操作,而未對齊的數據可能需要多次操作。
  • 字節對齊規則:

    1. 默認對齊規則
      • 結構體的每個成員按其類型大小和編譯器默認對齊數(通常是類型的自然對齊數)對齊。
      • 結構體的總大小必須是最大對齊數的整數倍。
    2. 對齊細節
      • 基本類型的對齊數char(1字節)、short(2字節)、int(4字節)、double(8字節)。
      • 結構體成員的對齊每個成員的起始地址必須是對齊數的整數倍
      • 結構體總大小的對齊結構體的總大小必須是其最大對齊數的整數倍
    3. #pragma pack(n) 的影響 使用 #pragma pack(n) 可以強制指定對齊數為 nn 為 1、2、4、8、16)。此時:
      • 每個成員的對齊數取 n 和其類型大小的較小值。
      • 結構體的總大小必須是 n 和最大對齊數中的較小值的整數倍。
  • 對齊示例

    • 默認對齊

       struct S1{char c;  // 1字節,偏移0int i;   // 4字節,(需對齊到4,填充3字節,偏移4-7)double d;// 8字節,(需對齊到8,偏移8~15)}
      
       struct S2{double d;char c;  int i;       }
      
       struct S3{char c;  double d;   int i;       }
      

      內存分析:

      image-20250801154415689

      總結,結構體中,成員的順序會影響到結構體最終的大小

      • 使用#pragma pack(1) 自定義對齊規則,(1)對齊的字節數

          #pragma pack(1)  // 對齊數之前的字節數能被1整數struct S1{char c; // 1字節 (偏移0) 1int i;// 4字節 (偏移1~4)4double d;// 8字節 (偏移5~12)8}; #pragma pack()// S1 的大小為 13字節
        
          #pragma pack(2) // 對齊數之前的字節數能被2整數struct S1{char c; // 1字節 (偏移0,填充1字節) 2int i;// 4字節 (偏移2~5)4double d;// 8字節 (偏移6~13)8}; #pragma pack()// S1 的大小為 14字節   char -1 + 1  int
        
          #pragma pack(4) // 對齊數之前的字節數能被4整數struct S1{char c; // 1字節 (偏移0,填充3字節) 4int i;// 4字節 (偏移4~7)4double d;// 8字節 (偏移8~15)8}; #pragma pack()// S1 的大小為 16字節
        
      • 在GNU標準中,可以在定義結構體時,指定對齊規則:

           __attribute__((packed));      -- 結構體所占內存大小是所有成員所占內存大小之和——attribute__((aligned(n)));  -- 設置結構體占n個字節,如果n比默認值小,n不起作用;n必須是2的次方
        
      • 案例:

        
        #include <stdio.h>int main(int argc, char *argv[])
        {struct Cat{char sex __attribute((aligned(2))); // 設置 char按照2字節對齊int id;   // 4char name[20];// 20}__attribute__((packed));  // 28  25printf("%ld\n",sizeof(struct Cat));// 28 25return 0;
        }
        
柔性數組

定義:柔性數組不占有結構體的大小。

特點:

  1. 柔性數組必須是結構體的最后一個成員
  2. 結構體大小在編譯時是未確定的
  3. 必須動態分配內存,指定數組的大小

語法:

 struct St{...char arr[];}

案例:


#include <stdio.h>
#include <stdlib.h>
#include <string.h>/* 定義包含柔性數組的結構體 */
struct FAStruct
{int     len;    // 用于記錄柔性數組的長度char data[];    // 柔性數組,注意這里沒有指定大小。柔性數組只能用于結構體,并且只能是最后一個成員。
};int main(int argc, char *argv[])
{const char* str1 = "Hello, Flexible Array!";const char* str2 = "hello world!";int str_len1 = strlen(str1) + 1; // +1 為了存儲\0int str_len2 = strlen(str2) + 1;// 使用calloc分配內存,為結構頭和柔性數組部分分配內存struct FAStruct *fas1 = (struct FAStruct*)calloc(1,sizeof(struct FAStruct) + str_len1); // 結構體空間大小 + 數組空間大小struct FAStruct *fas2 = (struct FAStruct*)calloc(1,sizeof(struct FAStruct) + str_len2); // 結構體空間大小 + 數組空間大小if (!fas1 || !fas2){perror("內存申請失敗!");return -1;}fas1->len = str_len1 - 1; // 不包含字符串結束符的長度strcpy(fas1->data, str1);fas2->len = str_len2 - 1; // 不包含字符串結束符的長度strcpy(fas2->data, str2);printf("字符串1:%s,%d\n", fas1->data,fas1->len);printf("字符串2:%s,%d\n", fas2->data,fas2->len);// 釋放內存free(fas1);free(fas2);return 0;
}
課堂練習

計算以下結構體的大小

#include <stdio.h>// 定義測試結構體
struct TEST1
{char a;int b; 
}; struct TEST1_1
{char a;int b;
}__attribute__((packed));// 取消字節對齊,取消之后,結構體數據類型大小就等于其所有成員的數據類型之和struct TEST1_2
{char a __attribute__((aligned(2)));int b;
};struct TEST2
{char a;short c; int b; 
};struct TEST3
{int num;char name[10];char sex;int age;double score;
};struct TEST3_1
{int num;char name[10];char sex;double score;int age;
};struct TEST4
{int num;short name[5];char sex;int age;int scores[2];
};
int main(int argc,char *argv[])
{// 創建結構體變量struct TEST1 test1;struct TEST2 test2;struct TEST3 test3;struct TEST3_1 test3_1;struct TEST4 test4;  struct TEST1_1 test1_1;struct TEST1_2 test1_2;// 計算大小printf("%lu\n",sizeof(test1));// 8printf("%lu\n",sizeof(test2));// 8printf("%lu\n",sizeof(test3));// 32printf("%lu\n",sizeof(test3_1));// 28printf("%lu\n",sizeof(test4));// 28printf("%lu\n",sizeof(test1_1));// 5printf("%lu\n",sizeof(test1_2));// 8return 0;
}

結構體的常見陷阱與最佳實踐

常見陷阱
  1. 成員訪問越界:訪問不存在的結構體成員。
 struct Point p;p.z = 10; // 錯誤,Point結構體沒有z成員
  1. 內存泄漏:忘記釋放動態分配的結構體內存。
 struct Point *p = malloc(sizeof(struct Point));p->x = 10;// 沒有free(p)導致內存泄漏
  1. 懸掛指針/空懸指針:使用已經釋放的內存或未初始化的指針。
 struct Point *p = malloc(sizeof(struct Point));free(p); // 釋放了p指向的內存p->x = 10; // 錯誤,p現在是懸掛指針
  1. 結構體大小計算錯誤:忘記考慮編譯器的內存對齊。
 struct {char a;int b;} s;// sizeof(s)可能是8而不是5
最佳實踐
  1. 使用typedef簡化語法:為結構體創建簡短的別名。
 typedef struct {int x;int y;} Point;
  1. 將相關數據組織在一起:使用結構體封裝相關數據。
 struct Student {char name[50];int age;float grade;};
  1. 避免過大的結構體:將大型數據結構分解為多個較小的結構體。
  2. 合理使用指針:對于大型結構體,使用指針而不是值傳遞。
  3. 注意內存管理:確保正確分配和釋放動態內存。

共用體/聯合體類型

  • 定義:使幾個不同變量占用同一段內存的結構。共用體按定義中需要存儲空間最大的成員來分配存儲單元,其他成員也是使用該空間,它們的首地址是相同。

  • 定義格式

      union 共用體名稱{數據類型 成員名;數據類型 成員名;...};
    
  • 共用體的定義和結構體類似。

    • 可以有名字,也可以匿名

    • 共用體在定義時也可以定義共用體變量

    • 共用體在定義時也可以初始化成員

    • 共用體也可以作為形參和返回值類型使用

    • 共用體也可以定義共用體變量

    • 也就是說,結構體的語法,共用體都支持

  • 注意:

    • 共用體弊大于利,盡量少用,一般很少用;

    • 共用體變量在某一時刻只能存儲一個數據,并且也只能取出一個數

    • 共用體所有成員共享同一內存空間,同一時間只能存儲一個值,可能導致數據覆蓋

      image-20250326172130719
    • 共用體和結構體都是自定義數據類型,用法類似于基本數據類型

      • 共用體可以是共用體的成員,也可以是結構體的成員
      • 結構體可以是結構體的成員,也可以是共用體的成員

案例:

#include <stdio.h>/*** 定義共用體*/ 
union S
{char a;float b;int c;
};// S的大小是4字節// 共用體作為共用體成員
union F
{char a;union S s; // 4字節
};// F的大小是4字節// 定義一個結構體
struct H
{int a;char b;
};// H的大小是8字節// 結構體作為結構體成員
struct I
{int a;int b;struct H h;
}; // I的大小是16字節// 共用體作為結構體成員
struct J
{int a; // 4char b; // 1 + 3union S s; // 4
}; // J的大小是12字節void test1()
{// 定義一個共用體(數據類型)union Obj{int num;char sex;double score;};// 定義匿名共用體union{int a;char c;} c;// 定義變量union Obj obj;// 存儲數據obj.num = 10; // 共用體空間數據:10obj.sex = 'A';// 共用體空間數據:'A' = 65 覆蓋數據// 運算obj.num += 5; // 共用體空間數據:70  覆蓋數據  'F'   sizeof(數據類型|變量名)printf("%lu,%lu,%d,%c,%.2lf\n",sizeof(obj),sizeof(union Obj), obj.num, obj.sex, obj.score);
}int main(int argc,char *argv[])
{test1();return 0;
}

案例:


#include <stdio.h>union Object
{char a;  // 8字節int b;   // 8字節double c;// 8字節
}; // 8字節,共用體所有成員共享最大成員的內存空間。union Object fun(union Object obj)
{return obj;
}int main(int argc, char *argv[])
{union Object obj;obj.a = 65;// charprintf("%c\n", fun(obj).a); // charobj.b = 10000000;printf("%d\n", fun(obj).b);// intobj.c = 12.25;printf("%f\n", fun(obj).c);// doublereturn 0;
}

要求:共用體在使用的時候,建議存取時使用的成員是一致的。也就是使用成員a存數據,必須使用成員b取數據。

結構體與共用體的主要區別

  1. 內存占用
    • 結構體的成員是連續存儲的,每個成員都有自己的內存空間。
    • 共用體的所有成員共享同一塊內存空間(共享所有成員中最大成員的空間)。
  2. 訪問方式
    • 結構體的成員可以同時訪問。
    • 共用體的成員不能同時訪問,因為它們共享同一塊內存空間。
  3. 使用場景
    • 結構體適用于需要同時存儲多個不同類型數據的情況。
    • 共用體適用于需要在同一時間存儲不同類型數據中的一種的情況,可以節省內存。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/91596.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/91596.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/91596.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

centos下安裝anaconda

下載 anaconda 安裝包 wget https://repo.anaconda.com/archive/Anaconda3-2022.05-Linux-x86_64.sh 2. 授權 chmod x Anaconda3-2022.05-Linux-x86_64.sh 3. 安裝 ./Anaconda3-2022.05-Linux-x86_64.sh 此時顯示Anaconda的信息&#xff0c;并且會出現More&#xff0c;繼續…

MySQL(172)如何進行MySQL的全局變量設置?

MySQL的全局變量是影響整個服務器設置和行為的參數。可以在服務器啟動時通過配置文件設置這些變量&#xff0c;也可以在服務器運行時通過SQL命令動態調整。以下是關于如何設置和管理MySQL全局變量的詳細說明和示例代碼。 一、通過配置文件設置全局變量 在MySQL服務器啟動時&…

【最后203篇系列】030 強化學習探索

前言 我發現在csdn寫完一篇文章越來越難了&#xff0c;有n篇寫了一半沒往下寫。原來我覺得補完203篇&#xff0c;湊到一千篇是個很簡單的事&#xff0c;沒想到還挺難的。 我回想了一下&#xff0c;過去一年大模型領域繼續發生這很劇烈的變化&#xff0c;這是一種新的模式 &…

fastGEO v1.7.0 大更新,支持PCA、差異分析、火山圖、熱圖、差異箱線圖、去批次等分析

前言 之前一篇文章【fastGEO V1.6.1 這個版本強的可怕&#xff0c;GEO數據自動下載、探針注釋、Shiny App】介紹了fastGEO用于GEO數據下載和探針注釋的核心功能。 雖然是付費50獲取安裝包&#xff08;剛開始是20&#xff09;&#xff0c;但也深受歡迎&#xff0c;說明這個R包…

LLM 典型模型技術特性及項目落地全流程實踐

在大語言模型(LLM)技術快速迭代的當下,開發者面臨的核心挑戰已從 “是否使用” 轉變為 “如何正確選型并高效落地”。本文將系統剖析當前主流 LLM 的技術特性,結合實際項目架構,提供從模型選型、接口集成到性能優化的全流程技術方案,并附關鍵代碼實現,為工業級 LLM 應用…

機器學習消融實驗:方法論演進、跨領域應用與前沿趨勢

一、定義與起源 消融實驗&#xff08;Ablation Study&#xff09;是一種系統性移除或修改模型關鍵組件以評估其對整體性能貢獻的實驗方法論。其術語源于神經科學和實驗心理學&#xff08;20世紀60-70年代&#xff09;&#xff0c;指通過切除動物腦區研究行為變化的實驗范式。2…

北京-4年功能測試2年空窗-報培訓班學測開-今天來聊聊我的痛苦

最近狀態很不對勁&#xff0c;因為我很少花時間好好思考&#xff0c;只是處于執行狀態&#xff0c;甚至也不太寫筆記了&#xff0c;我原以為這樣會更高效&#xff0c;現在想想&#xff0c;開始不愿花時間深思才是斷弦的開始吧而且從結課后我有了隱瞞&#xff0c;我不想過多透露…

深度解析 | AI 幻覺的形成和應對路徑

寫這一篇的緣由一是因為我也在摸索如何降低 AI 幻覺提升 AI 工具使用效率&#xff0c;二是因為前兩周在MIT學習時老師講的一節課&#xff0c;剛好也解釋了這個問題&#xff0c;所以一并做個總結&#xff0c;分享給大家。 近幾年&#xff0c;大型語言模型&#xff08;LLM&#…

Java把word轉HTML格式

Java把word轉HTML格式&#xff0c;兩種方式方式一&#xff1a;maven引入依賴,pom.xml<dependency><groupId>e-iceblue</groupId><artifactId>spire.office.free</artifactId><version>5.3.1</version> </dependency>然后代碼讀…

#C語言——學習攻略:探索字符函數和字符串函數(一)--字符分類函數,字符轉換函數,strlen,strcpy,strcat函數的使用和模擬實現

&#x1f31f;菜鳥主頁&#xff1a;晨非辰的主頁 &#x1f440;學習專欄&#xff1a;《C語言學習》 &#x1f4aa;學習階段&#xff1a;C語言方向初學者 ?名言欣賞&#xff1a;"編程的本質是理解問題&#xff0c;然后把它分解成可執行的步驟。" 目錄 1. 字符分類函…

(吃飯)質數時間

題目描述如果把一年之中的某個時間寫作 a 月 b 日 c 時 d 分 e 秒的形式&#xff0c;當這五個數都為質數時&#xff0c;我們把這樣的時間叫做質數時間&#xff0c;現已知起始時刻是 2022 年的 a 月 b 日 c 時 d 分 e 秒&#xff0c;終止時刻是 2022 年的 u 月 v 日 w 時 x 分 y…

【RK3568 RTC 驅動開發詳解】

RK3568 RTC 驅動開發詳解一、Linux RTC 子系統架構?二、設備樹配置?三、驅動四、時間相關命令實時時鐘&#xff08;RTC&#xff09;是嵌入式系統中不可或缺的硬件模塊&#xff0c;負責在系統斷電后繼續計時&#xff0c;為設備提供穩定的時間基準。本文將以瑞芯微 RK3568 平臺…

文本編碼檢測庫`chardet` 和 `uchardet`對比使用示例及注意事項

在處理未知編碼的二進制數據時&#xff0c;chardet 和 uchardet 是兩個非常實用的字符編碼自動檢測庫&#xff0c;尤其適用于從衛星通信、文件、網絡流等來源獲取的未標明編碼的文本數據。一、chardet&#xff08;Python版&#xff09; ? 簡介 chardet 是一個用 Python 編寫的…

[Windows]Postman-app官方歷史版本下載方法

Postman-app官方歷史版本下載方法最新版&歷史版本官網地址最新版本下載歷史版本下載禁止自動更新方法Postman最新版安裝后必須要登錄才能使用某些特定功能&#xff0c;多有不便&#xff0c;因此花了點時間整理了一下歷史版本如何下載的方法&#xff0c;鏈接均為官網鏈接&am…

【Spring Boot 快速入門】三、分層解耦

目錄分層解耦案例&#xff1a;將 emp.xml 中的數據解析并響應三層架構分層解耦IOC & DI 入門IOC 詳解DI 詳解分層解耦 案例&#xff1a;將 emp.xml 中的數據解析并響應 emp.xml 內容如下&#xff1a; <emps><emp><name>Tom</name><age>18…

井云科技2D交互數字人:讓智能服務觸手可及的實用方案

在如今的數字化時代&#xff0c;智能交互已成為各行業提升服務質量的重要方向。而井云 2D 交互數字人系統憑借其獨特的技術優勢&#xff0c;正逐漸成為眾多企業實現智能服務升級的優選。它無需復雜的操作和高昂的成本&#xff0c;就能讓數字人在各類線下場景中發揮重要作用&…

本地部署VMware ESXi,并實現無公網IP遠程訪問管理服務器

ESXi&#xff08;VMware ESXi&#xff09;是VMware公司推出的一款企業級虛擬化平臺&#xff0c;基于裸機&#xff08;bare-metal&#xff09;安裝的虛擬化操作系統。它可以在一臺物理服務器上運行多個虛擬機&#xff0c;廣泛應用于數據中心和云計算環境中。很多公司為了方便管理…

讓科技之光,溫暖銀齡歲月——智紳科技“智慧養老進社區”星城國際站溫情紀實

七月的風&#xff0c;帶著夏日的熱情&#xff0c;輕輕拂過邯鄲星城國際社區蔥郁的綠意。2025年7月30日&#xff0c;一個以“幸福晚景&#xff0c;樂享銀齡—智慧養老進社區”為主題的活動&#xff0c;如一股暖流&#xff0c;浸潤了社區的長者們。智紳科技懷揣著“科技賦能養老&…

Java單元測試和設計模式

單元測試 . 測試分類 什么是測試? 測試的目的是盡可能多的發現軟件中存在的BUG,而不是為了隱藏BUG。事實上測試有很多種類,比如:邊界測試,壓力測試,性能測試等 黑盒測試 黑盒測試也叫功能測試,主要關注軟件每個功能是否實現,并不關注軟件代碼是否有錯誤;測試人員…

UOS統信桌面系統解決編譯錯誤:C compiler cc is not found指南

一、系統環境 1.操作系統版本2.編譯環境 PC:~$ gcc --version gcc (Uos 8.3.0.13-deepin1) 8.3.0 Copyright (C) 2018 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY o…