C_结构体和枚举

C_结构体和枚举

结构体是一系列值(成员)的集合,值(成员)可以有不同的数据类型。枚举本质上是一种整数类型,只不过给整数值起了名称。

结构体(Structures)

结构体的定义

结构体是C语言中一种自定义的复合数据类型,允许存储不同类型的数据项。结构体由一组相关的数据项组成,每个数据项可以是不同的数据类型。基本语法:

1
2
3
4
5
struct 结构体名 {
数据类型1 成员1;
数据类型2 成员2;
// 其它成员
} 可选的结构体变量列表; //可以直接在可选的结构体变量列表处声明结构体变量名,如:}p1,p2;

例如:

1
2
3
4
5
6
7
struct Person {
char name[50];
int age;
float height;
};

struct Person p1;

结构体变量的初始化

结构体变量的初始化可以使用两种方法:逐个成员初始化和整体初始化,未被初始化的成员会被赋值为 0

逐个成员初始化:

1
2
3
4
struct Person person1;
strcpy(person1.name, "John");
person1.age = 25;
person1.height = 175.5;

整体初始化:

1
struct Person person2 = {"Alice", 30, 160.2};

结构体变量的基本操作

对结构体变量进行基本操作包括访问成员、赋值、比较等。

访问成员:结构体可以通过成员名称获取成员。

1
2
3
printf("Name: %s\n", person1.name);
printf("Age: %d\n", person1.age);
printf("Height: %.1f\n", person1.height);

结构体的成员代表着内存中的一块存储空间。它可以出现在赋值语句的左边,并且可以当作自增、自减运算符的操作数。

赋值:它的作用是把 person1 内存空间的数据 copy 到 person2 内存空间中。

1
2
struct Person person2;
person2 = person1;

能通过 = 运算符直接复制另一个结构体的数据,但当传递或返回一个结构体时,都会导致结构体的复制。避免开销时,会传递或返回一个指向结构体的指针。

基于结构体变量进行访问,语法为:变量名.成员名

1
2
3
4
5
6
void print_Person_info(struct person_p* p) {
   printf("%c %d %f\n",
        (*p).name,
        (*p).age,
        (*p).height);
}

注意:(*p)中的小括号是不能省的,因为解引用运算符 * 的优先级是低于取成员运算符 . 的。结构体指针通过 * 进行了解引用(比如代码中的 *p)后将不再是指针,在访问结构体成员的时候需要通过.进行操作

为方便使用指向结构体的指针,C语言提供了右箭头运算符 -> ,来基于结构体指针进行访问。

语法为:指针名->成员名。改写成:

1
2
3
4
5
6
void print_Person_info(struct person_p* p) {
   printf("%c %d %f\n",
         p->name,
         p->age,
         p->height);
}

结构体成员的比较:

1
2
3
if (person1.age == person2.age) {
printf("They are of the same age.\n");
}

结构体类型的别名

可以使用typedef关键字为结构体类型定义别名,避免每次声明结构体类型变量时都要加上 struct 关键字。

1
2
3
4
5
6
7
typedef struct Person {	//此行的Person可以不写,写了后可以使用struct Person p1;
char name[50];
int age;
float height;
} Person;

Person person1;

内存对齐

内存对齐早起是因为大多数计算机系统要求数据类型按照一定的边界对齐存储。现代的目的是为了内存获取速度更快。

内存对齐规则

  1. 按照结构体中最大的成员的大小来对齐,以保证结构体中每个成员都能够按照其自身大小对齐。

  2. 结构体A嵌套了结构体B,从结构体B最大元素的整数倍地址开始存。

  3. 函数、 static 变量属于类成员,不属于特定对象

  4. 带虚函数的类,有虚函数表指针,且属于对象首部,必须要和最大元素的大小对齐。

  5. 可以使用#pragma pack(n)来指定结构体的对齐方式,其中n表示对齐的字节数。例如,#pragma pack(1)表示按照1字节对齐。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    #include <stdio.h>
    #pragma pack(1) // 设置字节对齐为1字节
    struct Example {
    char c;
    int i;
    double d;
    };
    #pragma pack() // 恢复默认对齐
    int main() {
    printf("Size of struct Example: %zu\n", sizeof(struct Example));
    return 0;
    }
  6. C++11及之后的标准引入了alignasalignof关键字,可以显式指定对齐:

    1
    2
    3
    struct MyAlignedStruct {
    alignas(16) float data[4]; // 16字节对齐
    };

基础内容拓展

  • 嵌套结构体: 在结构体中嵌套其它结构体,以构建更复杂的数据结构。
  • 结构体数组: 创建存储多个结构体变量的数组。
  • 指向结构体的指针: 使用指针来访问和操作结构体变量。
  • 结构体与函数: 传递结构体作为函数参数,以及函数返回结构体。

枚举(Enumerations)

在很多程序中,变量取离散值,可以添加宏定义。

1
2
3
4
5
6
7
#define SUIT int
#define DIAMONDS 0
#define HEARTS 1
#define SPADES 2
#define CLUBS 3

SUIT s = HEARTS;

存在的问题:

  1. 没有显示地表明 DIAMONDS、HEARTS、SPADES、CLUBS隶属于同一种类型;
  2. 离散值比较多,为每个值添加一个宏定义将会很繁琐;
  3. 预处理器会删除定义的名字:DIAMONDS、HEARTS、SPADES、CLUBS,所以在调试的时候是没办法使用这些名字的。

枚举是一种自定义的数据类型,用于为一组相关的常量赋予有意义的名字,用来表示离散值的。基本语法:

1
2
3
4
5
enum 枚举名 {	//c++11可以使用 enum class 枚举名。
标识符1,
标识符2,
// 其它标识符
} 可选的枚举变量列表;//可以直接在可选的枚举变量列表处声明枚举变量名,如:}e1,e2;

C++11中 enum class定义的枚举类型不会隐式转换为整型,并且需要用::符号使用。它提供了更强的类型安全性和更好的作用域控制,避免了命名冲突和不必要的隐式转换,还可以显式指定枚举的底层类型。

1
2
3
4
5
enum class 枚举名 : unsigned int {	//: unsigned int可以不写,不显示的指定枚举的底层类型
标识符1,
标识符2,
// 其它标识符
} 可选的枚举变量列表;//可以直接在可选的枚举变量列表处声明枚举变量名,如:}e1,e2;

如:

1
2
3
4
5
6
7
8
9
// 定义枚举类型
enum Suit{
   // 罗列枚举值
   DIAMONDS,
   HEARTS,
   SPADES,
   CLUBS
};
enum Suit s = HEARTS; // 定义枚举类型的变量

枚举变量的基本操作包括赋值、比较、以及使用 switch 语句进行条件判断。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 赋值操作
enum Suit s = HEARTS;

// 比较操作
if (s == HEARTS) {
printf("HEARTS\n");
} else {
printf("NOT HEARTS\n");
}

// switch语句
switch (s) {
case DIAMONDS:
printf("DIAMONDS\n");
break;
case HEARTS:
printf("HEARTS\n");
break;
case SPADES:
printf("SPADES\n");
break;
default:
printf("CLUBS\n");
break;
}

给枚举类型起别名

可以用 typedef 给枚举类型起别名,避免每次声明枚举类型变量时都要加上 enum 关键字。

1
2
3
4
5
6
7
typedef enum {//对比结构体,此处的枚举名没写,不可以用enum Suit s = HEARTS;
   DIAMONDS,
   HEARTS,
   SPADES,
   CLUBS
} Suit;
Suit s = HEARTS;

基础内容拓展:

  1. 枚举类型的内存表示:枚举类型在内存中通常以整数值存储,编译器会为枚举类型的每个常量分配一个整数值。
  2. 枚举类型的限制:在C语言中,枚举类型的取值范围受限于其底层类型,通常为 int 类型。
  3. 枚举类型的应用场景:枚举类型常用于表示一组有限的、相关的常量,例如状态码、选项等。