c and c++ data structure

详解C/C++结构体、联合体和枚举的区别与内存对齐

C语言中构造类型一共有4种,它们分别是数组、结构体(struct)、共用体(union)、枚举类型(enum)。

一、结构体类型

1、什么是结构体

在C语言中,结构体指的是一种数据结构,是C语言中聚合数据类型的一类。结构体可以被声明为变量、指针或数组等,用以实现较复杂的数据结构。结构体同时也是一些元素的集合,这些元素称为结构体的成员,且这些成员可以为不同的类型,成员一般用名字访问。也就是说,结构体是由多种原宿所构成的数据结构。

2、定义结构体类型


struct 结构体
{
    任意类型 任意变量;

    任意类型 任意变量;

    ……
};

注意:这不是定义变量,而是自定义一种类型而已。如:


struct student
{
char name[10];//学生姓名
int height;//学生身高
bool sex;//学生性别 假设0表示女,1表示男。
};

3、 定义结构体变量

类型定义好以后,则可以定义该类型的变量。

定义结构体变量:
    
    struct student a,b;// struct可以省略。
    可以在定义结构体变量的时候赋值。
    如student a={”liudehua”,172,1},b={“lixiaolong”,172,1};
    
    也可以在定义结构体变量以后赋值,但注意不能再用{}。如:
      
      student a,b;
      a={”liudehua”,172,1},b={“lixiaolong”,172,1};//这是错误的。
      
      而应该是:
      
      strcpy(a.name,”liudehua”);
      a.height=172;
      a.sex=1;
      
      可以在定义结构体类型的时候同时定义结构体变量并赋值。
      
      struct student
      {
        char name[10];//学生姓名
        int height;//学生身高
        bool sex;//学生性别 假设0表示女,1表示男。
      } a={”liudehua”,172,1},b={“aolong”,172,1};
      

      4.访问结构体

      访问结构体成员要用直接成员运算符“.”或间接成员运算符“->”。

      
      #include "iostream"
      struct student
      {
          char name[10];//学生姓名
          int height;//学生身高
          bool sex;//学生性别 假设0表示女,1表示男。
      }a={"liudehua",182,1},b={"aolong",188,1};
      
      int main()
      {
          struct student*  x;
          x=&a;
          std::cout << a.height << std::endl;
          std::cout << x->height << std::endl;
          return 0;
      }
      

      对于结构体变量,访问其中的成员采取“结构体变量.成员”的形式;而对于结构体指针,访问它所指向的结构体变量中的成员,则采取“结构体指针->成员”形式。

      二、联合体类型

      1、什么是联合体

      联合体也是一种自定义的复合类型,它可以包含多个不同类型的变量。这些变量在内存当中共用一段空间。这段空间的size就是各变量中size最大的那个变量。

      2、定义联合体类型

      
      
      union myunion
      {
        int num1;
        double num2;
        float num3;
      };
      

      定义了一个联合体类型myunion。
      myunion a,b;//定义了两个myunion型变量。
      也可以在定义联合体类型的时候定义联合体变量。如:

      
      union myunion
      {
        int num1;
        double num2;
        float num3;
      }a,b;
      

      a占用的空间有多大呢?
      Sizeof(a)结果即为8,即myunion占用8个字节,和double型变量相同。
      注意:任一时刻,只能访问结构体里面的一个变量。

      
      a.num1=2;
      a.num2=3.154;
      myunion *p;
      p=&a;
      p->num3=5.6;
      

      3、联合体的使用

      C++的联合体(Union)与结构体相似,但它们的区别在于联合体中只能同时存储一个成员的值。这些成员共享同一个物理存储空间,也就是说,一个联合体的大小,等于它最大的成员变量所占据的空间。下面是一个示例代码:

      
      #include "iostream"
      using namespace std;
      
      union Person {
         int age;
         float height;
         char name[50];
      };
      
      int main() {
         union Person p1;
         p1.age = 30;
         cout << "Age: " << p1.age << endl;
         
         p1.height = 1.85;
         cout << "Height: " << p1.height << endl;
      
         strcpy(p1.name, "John Doe");
         cout << "Name: " << p1.name << endl;
         
         return 0;
      }
      

      在这个例子中,我们定义了一个名为“Person”的联合体,包含三个不同数据类型的成员变量:整数、浮点数和字符数组。在main函数中,我们可以看到如何使用联合体。在第一次赋值时,我们将age设置为30并输出,然后我们将height设置为1.85并输出,此时前一个赋值的值被覆盖了。在最后一部分,我们使用strcpy函数将字符串赋给name成员变量并输出。

      需要注意的是,在实际应用联合体时,必须小心使用,因为一个成员变量的更改会影响所有其他成员变量。同时,由于联合体需要共享内存,因此必须确保联合体的大小能够容纳最大。

      三、枚举类型

      1、什么是枚举类型

      枚举类型是一种数据类型,它通常用来定义一个数字常量集合。在枚举类型中,每个常量都有一个唯一的名称和对应的数值。

      2、枚举类型的定义

      枚举类型也是一种自定义的复合类型。不过,枚举类型中的成员都是常量。如

      
      enum color
      {
        red,
        green,
        blue,
        white,
        black
      }
      

      枚举类型中的成员默认值为从0开始,依次序递增。此时red==1,green为2,blue为3,white为4,black为5

      也可以改变起默认值。如

      
      
      enum color
      {
        red=1,
        green=3,
        blue=5,
        white,
        black
      };
      

      没有初始化的枚举类型成员的值将在它前面的成员基础上递增。所以,white的值为6,而black的值为7。

      3、定义枚举变量

      
      color a1,a2;
      

      4、给枚举变量赋值:

      
      a1=red;
      a2=blue;
      cout << a1 << a2;//输出结果是15
      

      虽然枚举常量的值整数,但是不能直接将整数值赋给枚举变量。如

      
      a1=1;//这是不对的。因为类型不匹配。一个是整型,一个是枚举类型。
      a1=(color)1;//正确
      

      枚举变量的size是一个整数的大小。

      5、枚举的使用

      在C++中,枚举类型可以用来定义一组常量。枚举为程序员提供了一种方便的方式去定义一些有意义的名称,而不是硬编码数字,这样代码更易于理解和维护。下面是一个基本的枚举示例:

      
      #include "iostream"
      using namespace std;
      enum Weekday {
         Monday = 1,
         Tuesday,
         Wednesday,
         Thursday,
         Friday,
         Saturday,
         Sunday
      };
      
      
      int main() {
         Weekday today;
         today = Tuesday;
         
         if(today == Sunday){
            cout << "Today is holiday!" << endl;
         }else{
            cout << "Today is a working day." << endl;
         }
         return 0;
      }
      

      在以上代码中,我们定义了一个Weekday枚举类型,将每个工作日映射到一个整数值上。默认情况下,第一个成员的值被设置为0,接着逐一自增,但我们可以使用显式赋值来覆盖它们。

      在main函数中,我们声明了一个名为today的变量,并且将其设置为Tuesday,通过if-else语句检查是否为周日,然后输出结果。需要注意的是,枚举值可以与整数进行比较和赋值,因为每个成员都会被映射到一个整数值。

      四、内存对齐与内存大小

      1、内存对齐

      在C语言中,内存对齐可以优化程序的性能,而结构体是需要进行内存对齐处理的数据类型。结构体的大小通常不仅取决于其中成员所占用的空间大小,还取决于操作系统和编译器对于内存对齐方式的处理。

      具体地说,内存对齐方式是为了符合硬件平台访问内存数据的要求,并以此提高代码的执行效率。一般地,对于基本类型(如int、char、float等),系统通常会按照它们自身所占用的字节大小来进行内存分配,并确保各个变量在内存中的地址都是偶数或是四的倍数(这里假设系统采用的是32位架构)。

      但如果结构体中的成员变量总大小不是4的倍数,则在结构体中填充一些无用的字节使得结构体字节数是4的倍数。

      2、结构体大小

      举例说明,假设有以下的结构体:

      
      struct S1 
      {
          char c1;   // 1 byte
          int i;    // 4 bytes
          char c2;  // 1 bytes
      };
      int main()
      {
          std::cout << sizeof (S1) << std::endl; //12
      }
      
      内存对齐示意图

      它们总共占用的空间不是6字节(1+4+1),因为当前CPU硬件平台一次最小访问单位是4字节,所以编译器会自动进行内存补齐,使得每个成员的地址都是4的倍数,结构体的实际大小可能会是12字节(4字节对齐)或是8字节(1字节对齐)。所以,具体的内存大小可能会因编译器和硬件平台的不同而异,并且有些编译器可以通过一些指令来控制内存对齐方式以提高程序效率。

      对于这中浪费内存的情况,作为程序员,我们在定义数据结构的时候完全可以避免。如下面:

      
      struct S2 
      {
          int i;    // 4 bytes
          char c1;   // 1 byte
          char c2;  // 1 bytes
      };
      int main()
      {
          std::cout << sizeof (S2) << std::endl; //8
      }
      

      通过改变结构体里面变量的顺序,避免内存对齐带来的空间浪费。内存大小变为了8,内存节省了4个字节大小。

      3、联合体大小

      C语言联合体(union)的内存大小取决于其中最大成员的大小。因为联合体内所有成员共用同一块内存区域,因此联合体的大小必须足够容纳所有成员中最大的那个。

      举个例子,如果我们有一个联合体定义如下:

      
      union Example 
      {
          int x;
          char c;
          double d;
      };
      

      那么这个联合体的大小就是8个字节。因为这三个成员中最大的是double类型,占用8个字节,其他成员对齐到8个字节,所以整个联合体大小是8个字节。

      需要注意的是,联合体的成员必须是同一种类型或者大小相同,这是因为它们会共用同一个内存区域。如果不同类型的成员共存,可能会导致数据覆盖或者读取数据时出现未定义的行为。所以在使用联合体时要特别小心,确保成员的类型和大小相同。

      4、枚举类型大小

      在C语言中,枚举类型(Enum)是一种自定义类型,用于表示有限个数的常量。在内存中,枚举类型通常被存储为整数类型,其大小与int类型相同,即通常为4个字节或8个字节(取决于系统架构)。

      当定义枚举变量时,该变量的值由枚举列表中对应常量的整数值来决定。在下面这个例子中,我们创建了一个Color枚举类型,其中包含三个常量红色、绿色和蓝色,它们分别赋予值0、1和2。而变量c则被定义为Color类型并初始化为红色。

      
      #include "stdio.h"
      enum Color {
          RED,
          GREEN,
          BLUE
      };
      
      int main() {
          enum Color c = RED;
          printf("Sizeof enum: %d\n", sizeof(enum Color));  //输出为4或8字节
          return 0;
      }
      

      需要注意的是,枚举类型在内存大小上可能会因为编译器实现和程序运行的机器体系结构所影响。