目录
结构体、联合体和枚举
1.结构体类型
-如何声明一个结构体类型?
-什么是匿名结构体类型?
-结构体是否可以嵌套?
2.结构体成员访问
-如何访问结构体变量的成员?
3.结构体内存对齐
-为什么有内存对齐?
-结构体内存对齐的规则是什么?
4.结构体的柔性数组成员
-什么是柔性数组?
-柔性数组的特点是什么?
5.结构体实现位段
-什么是位段?
-位段的好处是什么?
-位段的优缺点是什么?
6.联合体/共用体
-什么是联合体?
-联合体的关键字是什么?
-联合体大小如何计算?
-联合体大小是最大成员的大小吗?
7.枚举
-什么是枚举?
-枚举的关键字是什么?
8.枚举常量和取值
-什么是枚举常量
-枚举常量的默认值是怎么样的?
板块
- 结构体类型
- 结构体成员访问
- 结构体内存对齐
- 结构体的柔性数组成员
- 结构体实现位段
- 联合体/共用体
- 枚举
- 枚举常量和取值
提问
- 如何声明一个结构体类型?
- 什么是匿名结构体类型?
- 结构体是否可以嵌套?
- 如何访问结构体变量的成员?
- 为什么有内存对齐?
- 结构体内存对齐的规则是什么?
- 什么是柔性数组?
- 柔性数组的特点是什么?
- 什么是位段?
- 位段的好处是什么?
- 位段的优缺点是什么?
- 什么是联合体?
- 联合体的关键字是什么?
- 联合体大小如何计算?
- 联合体大小是最大成员的大小吗?
- 什么是枚举?
- 枚举的关键字是什么?
- 什么是枚举常量
- 枚举常量的默认值是怎么样的?
- 结构体的声明:使用struct关键字声明一个结构体类型。结构体类型允许你自定义一种数据类型,其中可以包括多个不同类型的成员。
- 一般语法形式如下:
struct 结构体类型名 { 成员类型1 成员名1; 成员类型2 成员名2; // ... };
struct Student { char name[50]; int age; int studentId; };
struct Student 表示结构类型,name,age,studentId是成员名,分别是一个字符数组和两个整数。
- 匿名结构体类型:在定义结构体变量时,省略结构体名称而直接定义其成员。
- 示例:
#include
int main() { struct { int x; int y; } point; point.x = 10; point.y = 20; printf("Point coordinates: (%d, %d)\n", point.x, point.y); return 0; } 在这段代码中:我们定义了一个匿名的结构体类型,它包含两个成员变量 x 和 y。然后我们声明了一个结构体变量 point,可以使用点操作符来访问结构体的成员:
可以,相当于在结构体中定义另一个结构体作为成员
- 示例:
#include
// 定义一个嵌套结构体 struct Date { int day; int month; int year; }; struct Person { char name[20]; int age; struct Date birthday; }; int main() { // 创建一个 Person 结构体变量 struct Person person; // 访问结构体成员并赋值 strcpy(person.name, "John"); person.age = 25; person.birthday.day = 10; person.birthday.month = 7; person.birthday.year = 1998; // 打印结构体成员 printf("Name: %s\n", person.name); printf("Age: %d\n", person.age); printf("Birthday: %d/%d/%d\n", person.birthday.day, person.birthday.month, person.birthday.year); return 0; } 我们定义了两个结构体类型:
Date
和Person
。Person
结构体中包含了一个Date
结构体作为其成员。然后,我们在main
函数中创建了一个Person
结构体变量person
,并对其成员进行赋值和访问。这是结构体的初始化也可以使用{}为其赋值:person = {“John”,25,{10,7,1998}};
--结构体自引用:
-结构体自引用:
struct Node { int data; struct Node* next; }; typedef struct { int data;
这里将结构体的指针作为其成员即可。
-结构体与typedef:
typedef struct Node { int data; struct Node* next; }Node;
注意:这里不能使用匿名结构体类型,否则tyoedef struct{}Node;前后顺序会有歧义,在使用指针的时候报错。
- 使用 点操作符: . (上面有示例,不在赘述)
- 使用箭头操作符:->
对于指向结构体的指针,可以使用箭头操作符(
->
)示例:
struct Person { char name[20]; int age; float height; }; int main() { struct Person person; struct Person *personPtr; personPtr = &person; // 访问结构体成员并赋值 strcpy(personPtr->name, "John"); personPtr->age = 25; personPtr->height = 1.75; // 访问结构体成员并打印 printf("Name: %s\n", personPtr->name); printf("Age: %d\n", personPtr->age); printf("Height: %.2f\n", personPtr->height); return 0; }
- .平台原因(移植原因): 不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
- 性能原因: 数据结构(尤其是栈)应该尽可能地在自然边界上对齐。 原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。
- 第一个成员在与结构体变量偏移量为0的地址处。
- 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。 对齐数 = 编译器默认的一个对齐数与该成员大小的较小值。
VS中默认的值为(8)- 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
- 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
柔性数组:是C语言中的一种特性,它允许在结构体的末尾定义一个具有可变长度的数组。柔性数组在定义结构体时,作为最后一个成员出现,并且没有指定数组的大小。
使用柔性数组可以在结构体内部直接存储变长的数据,而不需要通过指针来引用额外分配的内存。这样可以方便地将相关的数据组织在一起,提高代码的可读性和效率。
示例:
#include
#include // 定义结构体 struct flex_array_struct { int length; int data[]; // 柔性数组成员 }; int main() { int array_size = 5; // 动态分配内存给结构体 struct flex_array_struct* flex_array = malloc(sizeof(struct flex_array_struct) + array_size * sizeof(int)); if (flex_array == NULL) { printf("内存分配失败\n"); return 1; } flex_array->length = array_size; // 对柔性数组进行赋值 for (int i = 0; i < array_size; i++) { flex_array->data[i] = i * 2; } // 打印数组元素 printf("数组元素:"); for (int i = 0; i < flex_array->length; i++) { printf("%d ", flex_array->data[i]); } printf("\n"); // 释放内存 free(flex_array); return 0; } 我们定义了一个名为
flex_array_struct
的结构体,它包含一个整数length
和一个柔性数组data[]
。在main()
函数中,我们首先动态分配了结构体的内存,并计算了柔性数组的大小,然后将其赋值给length
成员。接下来,我们使用循环将一些数据赋值给柔性数组data[]
。最后,我们打印出数组的元素,并释放了之前动态分配的内存。
动态大小:柔性数组允许在运行时动态分配数组的大小,而不需要在编译时指定固定的数组大小。这使得我们可以根据实际需求来动态调整数组的长度。
结构体扩展:通过柔性数组,可以扩展结构体的大小,使其能够容纳不同大小的数据。这对于处理变长数据非常有用,例如字符串、动态列表等。
紧凑的内存布局:柔性数组位于结构体的末尾,因此不会增加结构体的额外内存开销。它可以与其他结构体成员一起存储在连续的内存块中,提供紧凑的内存布局。
访问元素:可以使用索引访问柔性数组的元素,就像访问常规数组一样。例如,
struct_name->array_name[index]
可以用于访问柔性数组中的特定元素。内存管理:由于柔性数组是通过动态内存分配(如
malloc()
)进行管理的,因此需要小心处理内存分配和释放,以避免内存泄漏或访问无效的内存。
位段:是一种用于定义结构体成员的数据类型,它允许你按位对结构体的成员进行定义,指定它们所占用的位数。
语法:
struct bitfield_struct { type member_name : width; };
示例:
#include
struct bitfield_struct { unsigned int flag1 : 1; // 1位宽度 unsigned int flag2 : 2; // 2位宽度 unsigned int flag3 : 5; // 5位宽度 }; int main() { struct bitfield_struct bf; bf.flag1 = 1; bf.flag2 = 2; bf.flag3 = 7; printf("flag1: %u\n", bf.flag1); printf("flag2: %u\n", bf.flag2); printf("flag3: %u\n", bf.flag3); return 0; } 我们定义了一个名为
bitfield_struct
的结构体,其中包含了三个位段成员flag1
、flag2
和flag3
,它们分别占用了1位、2位和5位。在main()
函数中,我们创建了一个bitfield_struct
类型的变量bf
,并为每个位段成员赋值。然后,我们打印出每个位段成员的值注:
1. 位段的宽度不能超过成员的数据类型的位数。
2. 位段的成员可以是 int unsigned int signed int 或者是 char (属于整形家族)类型
3. 位段的空间上是按照需要以4个字节( int )或者1个字节( char )的方式来开辟的。
4. 位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段
位段的主要目的是有效地使用内存空间,特别是当你需要在结构体中存储大量的布尔值或者小范围的整数值时。通过使用位段,你可以显式地指定结构体成员所需的位数,从而节省内存。
位段的行为在不同的编译器和平台上可能有所不同:
1. int 位段被当成有符号数还是无符号数是不确定的。
2. 位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32,写成27,在16位机器会出问题。
3. 位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。
4. 当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的
联合体(Union):是一种特殊的数据类型,它允许在同一块内存空间中存储不同类型的数据。联合体中的各个成员共享同一块内存,但每次只能存储其中一个成员的值。
union
示例:
union union_name { member_type1 member_name1; member_type2 member_name2; // 可以有更多的成员 };
#include
union Data { int i; float f; char str[20]; }; int main() { union Data data; data.i = 10; printf("data.i: %d\n", data.i); data.f = 3.14; printf("data.f: %f\n", data.f); strcpy(data.str, "Hello"); printf("data.str: %s\n", data.str); return 0; } 联合体Data定义了3个成员:i(整形),f(浮点型),str(字符数组),在main函数中我们分别访问并存储这些成员的值。
注:每次存储新值会覆盖掉之前的值,因为它们共享同一块内存空间。
使用联合体去判断电脑是大端存储还是小端存储:
//使用联合体 int judge_sys() { union judge { int value; char arr[4]; }; union judge data; data.value = 1; return data.arr[0]; } int main() { int ret = judge_sys(); if (ret == 1) printf("小端\n"); else printf("大端\n"); return 0; }
我们定义了一个联合体judge,它的成员:value(整型),arr(字符数组),并将1赋值给了value,通过返回arr[0]去判断它的大小端存储顺序。
联合体:0x00 00 00 01(1的表示)
将value赋值为1内存:0x 01 00 00 00(小端) 0x 00 00 00 01(大端)
char arr[4]的内存表示与其相同(它们共享同一块空间)
通过检查arr[0]是1还是0去判断字节存储顺序。
- 联合的大小至少是最大成员的大小。
- 当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。
不一定,当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。
示例:
union Un1 { char c[5]; int i; }; union Un2 { short c[7]; int i; }; //下面输出的结果是什么? printf("%d\n", sizeof(union Un1)); printf("%d\n", sizeof(union Un2));
这段代码的输出结果为:8,16
un1:最大对齐数:4,char c[5]占了5个字节(char类型1个字节,5个元素) int i :4
最大成员大小:5 不是最大对齐数的整数倍,uni1的大小为8
un1:最大对齐数:4
最大成员大小:14 short c[7](2*7个字节大小) uni2的大小为16
枚举:是一种用户定义的数据类型,用于定义一组命名的常量。枚举常量表示一组相关的离散取值,例如表示星期几、月份、状态等。
关键字:enum
语法示例:
enum enum_name { value1, value2, // 可以有更多的值 };
#include
enum Weekday { Monday, //0 Tuesday, //1 Wednesday, //2 Thursday, //3 Friday, //4 Saturday, //5 Sunday //6 }; int main() { enum Weekday today = Wednesday; if (today == Wednesday) { printf("Today is Wednesday.\n"); } else { printf("Today is not Wednesday.\n"); } return 0; }
枚举常量:是在枚举类型中定义的命名常量。它们表示枚举类型的可能取值,可以在程序中用作具体的标识符。
在上面的例子中Monday, Tuesday, Wednesday, Thursday等都是枚举常量,分别对应了0,1,2,3,4等。
枚举常量的默认值是从0开始自动递增的整数值。
在上面的例子中,你也可以将Monday赋值为1,其后面就会从1开始递增/