C語言之結構體基礎

c語言基礎 發佈 2024-03-03T15:31:03.403606+00:00

在前面我們學習過基礎的數據類型int float char 等,都只能用來表示基礎的數據類型,那麼要怎麼來表示複雜的數據類型呢?

什麼是結構體

在C語言中,結構體是不同數據類型的元素的集合。該結構用於創建用戶定義的數據類型。該結構也被稱為「 C語言自定義類型」。換句話說,結構體是不同類型數據的集合。這種數據類型的名字是由用戶自主定義的。通常結構體用於將不同數據類型的元素組合成一個組。結構體中定義的元素稱為結構成員。在前面我們學習過基礎的數據類型int float char 等,都只能用來表示基礎的數據類型,那麼要怎麼來表示複雜的數據類型呢?比如下信息:

定義5個數組,然後通過數組下標一致性原則去描述上述表格數據是否可行? 當然沒得問題,如下代碼:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main() 
{
  int ids[10] = { 0 };
  char names[10][10] = { 0 };
  char sexs[10][3] = { 0 };
  int ages[10] = { 0 };
  int scores[10] = { 0 };

  ids[0] = 100;
  strcpy(names[0], "歐陽瘋");
  strcpy(sexs[0], "男");
  ages[0] = 18;
  scores[0] = 666;

  return 0;
}

看起來還不錯,實際上很繁瑣,在排序需要交換兩者數據的時候極其繁瑣,既然學生信息有很多,那麼能不能定義一個學生類型呢?如果能,直接通過學生訪問該學生的所有信息就很方便了!

C語言結構體創建

為了定義結構體,您必須使用 struct 語句。struct 語句定義了一個包含多個成員的新的數據類型,struct 語句的格式如下:

  • 結構體名自己起,struct 結構體名組成新的數據類型,C語言中struct不可缺少
  • 多個成員之間用分號分隔,C語言中不允許無數據成員的結構體定義
  • 末尾的分號不可缺少

那麼對於上面的學生的信息,就可以用如下結構體表示學生結構體類型:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct Student 
{
    int id;    //學號
    char name[10];  //姓名
    char sex;    //性別
    int age;    //年齡
    int score;    //總分
};
int main() 
{

  return 0;
}

結構體中所有數據成員組成一個整體,形成一個新的數據類型,而不同變量則是零散內存,毫無關聯,如下圖:

C語言結構體訪問

結構體中的數據必須要通過結構體變量訪問,訪問方式有以下兩種:

  • 普通結構體變量: 變量.成員
  • 結構體指針:指針->成員 或者 (*指針).成員

結構體變量的創建

結構體類型已經聲明,如何使用結構體類型定義結構體變量呢?有以下方法(typedef別名創建後續再講):

  • 先聲明結構體類型再定義結構體變量
  • 在聲明結構體類型的同時定義變量

如下測試代碼:

#include <stdio.h>
#include <stdlib.h>
struct MM
{
  char name[20];
  int age;
  double score;
}mm;       
//在聲明結構體類型的同時定義變量mm
int main() 
{
  //先聲明結構體類型再定義結構體變量
  //struct MM: 類型
  //girl: 變量名
  struct MM girl;
  return 0;
}

結構體變量的初始化

在定義結構體變量的同時通過{}的方式為每一個成員變量進行賦初值,賦初值主要有以下幾種方式:

  • 全部初始化
  • 部分初始化:未初始化部分自動初始化為0
  • 全部初始化為0
  • 初始化指定的成員(可以初始化任意成員,不必遵循定義順序)
  • 用另一個結構體變量初始化

如下測試代碼:

#include <stdio.h>
#include <stdlib.h>
struct MM
{
  char name[20];
  int age;
  double score;
};       
int main() 
{
  
  //全部初始化,順序必須一致
  struct MM girl = {"girl",18,99.1};  
  //部分初始化:未初始化部分自動初始化為0
  struct MM mm = { "gril" };
  //全部初始化為0
  struct MM zero = { 0 };
  //初始化指定的成員(可以初始化任意成員,不必遵循定義順序)
  struct MM beauty = { .age = 18,.name = "beauty" };
  //用另一個結構體變量初始化
  struct MM woman = girl;
  return 0;
}

結構體數組

一個結構體變量可以存放一個學生的一組信息,可是如果有 10 個學生呢?難道要定義 10 個結構體變量嗎?難道上面的程序要複製和粘貼 10 次嗎?很明顯不可能,這時就要使用數組。結構體中也有數組,稱為結構體數組。它與前面講的數值型數組幾乎是一模一樣的,只不過需要注意的是,結構體數組的每一個元素都是一個結構體類型的變量,都包含結構體中所有的成員項。

struct Student stus[10]; 這就定義了一個結構體數組,共有 10 個元素,每個元素都是一個結構體變量,都包含所有的結構體成員。

示例程序| 從鍵盤輸入 5 個學生的基本信息,如學號、姓名、年齡、性別,將年齡最大的學生的基本信息輸出到屏幕

#include <stdio.h>
struct Student
{
    int id;
    char name[10];
    int age;
    char sex;
};
int main()
{
    struct Student stus[10];
    printf("input stu>:\n");
    for (int i = 0; i < 5; i++)
    {    
        scanf("%d %s %d %c", &stus[i].id, stus[i].name, &stus[i].age, &stus[i].sex);
    }
    struct Student maxStu = stus[0];
    for (int i = 0; i < 5; i++)
    {
        if (maxStu.age < stus[i].age)
        {
            maxStu = stus[i];
        }
    }
    printf("-------------Max---------------\n");
    printf("%d %s %d %c\n", maxStu.id, maxStu.name, maxStu.age, maxStu.sex);
    return 0;
}

程序測試結果如下:

當然對於這種表格數據操作有很多,例如排序,查找,文件保存等。詳細參見結構體數組寫管理系統。

結構體指針

當一個指針變量指向結構體時,我們就稱它為結構體指針。C語言結構體指針的定義形式一般為:struct 結構體名 *變量名;

如下測試代碼:

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
struct MM
{
  char name[20];
  int age;
  double score;
};       
int main() 
{
  struct MM girl = {"girl",18,99.1};  
  struct MM* pMM = NULL;
  //結構體指針指向結構體變量
  pMM = &girl;
  //指針用->訪問
  printf("%s\t%d\t%.1lf\n", pMM->name, pMM->age, pMM->score);
  //*pMM等效girl
  printf("%s\t%d\t%.1lf\n", (*pMM).name, (*pMM).age, (*pMM).score);
  //結構體指針動態內存申請
  struct MM* pArray = (struct MM*)malloc(sizeof(struct MM)*3);
  assert(pArray);
  for (int i = 0; i < 3; i++) 
  {
    pArray[i] =(struct MM){ "張三",18,99.9 };
    printf("%s\t%d\t%.1lf\n", pArray[i].name, pArray[i].age, pArray[i].score);
  }
  return 0;
}

程序測試結果如下:

基本上普通指針能做的,結構體指針一樣的。只是在訪問數據的時候需要剝洋蔥(通過->訪問每個數據)。當然也可以當做函數參數和返回值。

C語言位段

C語言允許在一個結構體中以位為單位來指定其成員所占內存長度,這種以位為單位的成員稱為位段。利用位段能夠用較少的位數存儲數據。基本語法如下:

struct 結構體名
{
      類型 位段名1 : 位段大小;
      類型 位段名2 : 位段大小;
      類型 位段名3 : 位段大小;
      類型 位段名4 : 位段大小;
      ...
};

C語言標準規定,只有有限的幾種數據類型可以用於位段。

  • 所有整數類型
  • char類型
  • bool類型

如下測試代碼:

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
struct BitField
{
    unsigned char a : 1;
    unsigned char b : 4;
    unsigned char c : 3;
};
int main()
{
    //初始化
    struct BitField bit = { 1,2,3 };
    //輸出
    printf("first:%d %d %d\n", bit.a, bit.b, bit.c);
    //賦值
    //10    1位有效去掉最高位
    bit.a = 2;  
    //10100 4位有效去掉最高位
    bit.b = 20;  
    //1000  3位有效去掉最高位
    bit.c = 8;   
    //再次輸出
    printf("last:%d %d %d\n", bit.a, bit.b, bit.c);
}

程序測試結果如下:

位段注意項:

  • 位段的內存分配:位段占的二進位位數不能超過該基本類型所能表示的最大位數,即位段不能跨字節存儲,比如char是占1個字節,那麼最多只能是8位;
  • 位域的存儲:C語言標準並沒有規定位域的具體存儲方式,不同的編譯器有不同的實現,但它們都儘量壓縮存儲空間。
  • 禁止對位段取地址:地址是字節(Byte)的編號,而不是位(Bit)的編號。
  • 無名位段:位域成員可以沒有名稱,只給出數據類型和位寬,無名位域一般用來作填充或者調整成員位置。因為沒有名稱,無名位域不能使用。

C語言結構體嵌套

在一個結構體內包含另一個結構體作為其成員,當然一般嵌套可以理解為一類數據的封裝。訪問的話逐步剝洋蔥即可,定義方式有兩種寫法。

  • 以結構體變量當做數據成員方式嵌套
  • 直接把結構體定義在另一個結構體內

示例程序以結構體變量當做數據成員方式嵌套 | 給學生增加一個出生日期,包含年月日

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
struct Date
{
    short year;
    short month;
    short day;
};
struct Student
{
    int id;
    char name[10];
    struct Date birth;  //出生日期
};
int main()
{
    //數據完整的情況,{}可有可無
    struct Student baby = { 1001,"baby",{2008,3,17} };
    printf("%d\t%s\t%d-%d-%d\n", baby.id, baby.name, 
        baby.birth.year, baby.birth.month, baby.birth.day);
    return 0;
}

示例程序直接把結構體定義在另一個結構體內| 給學生增加一個出生日期,包含年月日

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
struct Student
{
    int id;
    char name[10];
    struct Date
    {
        short year;
        short month;
        short day;
    }birth;//出生日期
};
int main()
{
    //數據完整的情況,{}可有可無
    struct Student baby = { 1001,"baby",{2008,3,17} };
    printf("%d\t%s\t%d-%d-%d\n", baby.id, baby.name, 
        baby.birth.year, baby.birth.month, baby.birth.day);
    return 0;
}

C語言結構體內存對齊

什麼是內存對齊

從理論上講,對於任何變量的訪問都可以從任何地址開始訪問,但是事實上不是如此,實際上訪問特定類型的變量只能在特定的地址訪問,這就需要各個變量在空間上按一定的規則排列,而不是簡單地順序排列,這就是內存對齊。通俗點講就是廁所建坑位需要合理排布對齊,不然可能會存在只有半個坑的情況。

為什麼要內存對齊

  • 某些平台只能在特定的地址處訪問特定類型的數據;
  • 提高存取數據的速度。比如有的平台每次都是從偶地址處讀取數據,對於一個int型的變量,若從偶地址單元處存放,則只需一個讀取周期即可讀取該變量;但是若從奇地址單元處存放,則需要2個讀取周期讀取該變量。

當然我們會不會算內存對齊其實並不重要,因為對於一個結構體占用內存一個sizeof即可搞定,重要的是大家要知道如何設計代碼可以讓內存更小,畢竟在特殊開發場景,內存占用是非常值得關注的。例如,網絡傳輸,嵌入式等。

內存對齊規則

C語言標準並沒有規定內存對齊的細節,而是交給具體的編譯器去實現,但是對齊的基本原則是一樣的。

  • 結構體變量的首地址能夠被其最寬基本類型成員的大小所整除;
  • 結構體每個成員相對於結構體首地址的偏移量都是成員大小的整數倍,如有需要編譯器會在成員之間加上填充字節;
  • 結構體的總大小為結構體最寬基本類型成員大小的整數倍,如有需要編譯器會在最末一個成員之後加上填充字節。

如下測試代碼:

#include <stdio.h>
#include <stdlib.h>
//會不會算出結果不重要,重要是學會怎麼寫可以少內存即可
//按照從小到大即可 字符和整形寫一塊
struct Data1
{
  double score;    //8
  char name[3];       //補一位和int組成8位
  int age;
};
struct Data2
{
  char name[3];     //補 5位
  double score;     //8位
  int age;          //補4位
};

struct Data3
{
  char name[9];   //8+1  +3
  int num;        //4
  double score;   //8
  int age;        //4+3 5
  char tel[3];
};
struct Data4
{
  int age;
  char name[3];
  int num;
};
struct Data5
{
  char name[5];
  char num;
};
struct Data6
{
  char name[5];
  int* p;         //32位按照4個字節對齊,64位按照8位對齊
};
struct Data7
{
  struct Data4 data;   //12
  //char name[3];     //3+1
  double score;        //8
  int age;             //8
};
union Data8
{
  char name[20];
  double score;
};
int main()
{
  struct Data1* p = (struct Data1*)malloc(sizeof(struct Data1));
  printf("%zd\n", sizeof(struct Data1));   //16
  printf("%zd\n", sizeof(struct Data2));   //24
  //8 1w  8w 
  printf("%zd\n", sizeof(struct Data3));   //32
  printf("%zd\n", sizeof(struct Data4));   //12
  printf("%zd\n", sizeof(struct Data5));   //6
  printf("%zd\n", sizeof(struct Data6));   //16
  //C語言不允許空的結構體
  printf("%zd\n", sizeof(struct Data7));   //32
  printf("%zd\n", sizeof(union Data8));   //24
  return 0;
}

相關

如果閣下正好在學習C/C++,看文章比較無聊,不妨關注下關注下小編的視頻教程,通俗易懂,深入淺出,一個視頻只講一個知識點。視頻不深奧,不需要鑽研,在公交、在地鐵、在廁所都可以觀看,隨時隨地漲姿勢。

關鍵字: