一、结构体类型
在C语言中,结构体(struct)是一种复合数据类型,它允许将不同类型的数据项打包成一个单一的数据结构。结构体的成员可以是不同类型的数据,例如整数、浮点数、字符数组等。结构体类型在C语言中非常重要,因为它提供了一种组织相关数据的方式,使得代码更加清晰和易于管理。
1.1 结构体类型的定义和声明
结构体通过struct
关键字进行声明,其基本语法如下:
struct 结构体名 { 数据类型 成员1; 数据类型 成员2; // ... };
例如:定义一个学生的结构体
struct Student { char name[20]; int age; float score; };
1.2 结构体的应用
结构体可以通过typedef关键字进行创建,使代码简化
typedef struct Student { char name[20]; int age; float score; } Student;
这样就可以使用这样声明结构体变量。
Student student1 = {" Keith", 20, 90.5};
1.3结构体的内存对齐
- 第一个成员对齐:结构体的第一个成员通常放置在起始地址偏移量为0的位置。
- 成员间填充:为了保证每个成员的地址是其自身大小的整数倍,编译器可能会在成员之间插入填充字节。
- 结构体总大小对齐:结构体的总大小应为其最大成员大小的整数倍,必要时会在结构体末尾添加填充字节。(VS中默认对齐数是8)
- 可以通过指定对齐方式的方式来改变结构体的对齐方式。可以使用#pragma pack指令或__attribute__((aligned(n)))来指定对齐方式,其中n表示对齐值。
例如:
typedef struct { int a; // 占用4字节 char b; // 占用1字节 double c; // 占用8字节 } MyStruct;
MyStruct
的总大小将是12字节,因为a
和b
已经在4字节的边界上对齐,而c
需要在8字节的边界上对齐。编译器将在a
和b
之间插入3个填充字节,并在b
和c
之间插入4个填充字节。最终就是16字节。
1.4 结构体传参
struct S { int data[1000]; int num; }; struct S s = {{1,2,3,4}, 1000}; //结构体传参 void print1(struct S s) { printf("%d\n", s.num); } //结构体地址传参 void print2(struct S* ps) { printf("%d\n", ps->num); } int main() { print1(s); //传结构体 print2(&s); //传地址 return 0; }
- 传递整个结构体作为参数:这种方式会将整个结构体复制到函数中,然后在函数内部访问结构体成员。这种方式适用于结构体较小且不需要在函数中修改的情况。
- 传递指向结构体的指针作为参数:这种方式仅传递结构体的地址,函数可以直接修改原始结构体变量的值。这种方式适用于结构体较大或需要在函数中修改的情况。
- 传递结构体成员的指针作为参数:这种方式是通过指针访问结构体成员,适用于需要传递结构体中特定成员的情况。
需要考虑到内存开销和性能等因素,优先选择传址。
二、 位段
2.1 位段的概念和特点
- 位段(bit-field)是C语言中的一种特殊数据类型。
- 它允许将一个字节或更大的存储空间分割成几个不同的区域,并为每个区域指定特定的位数。每个区域都有一个名称,允许在程序中按名称进行操作。这样可以有效地节省存储空间,因为某些数据可能只需要很少的位数来存储。
- 位段的成员必须是
int
、unsigned int
、signed int
或char
(属于整型家族)类型。位段的成员名后跟一个冒号和一个数字,表示该成员所占的位数。
2.2 位段结构体的定义
首先,你需要定义一个结构体,其中包含了你想要声明的位段成员。
typedef struct { int a: 2; // 定义一个名为a的位段,占用2位 int b: 5; // 定义一个名为b的位段,占用5位 } MyStruct;
枚举名是新定义的枚举类型的名称,枚举值是一组常量,用逗号分隔。
3.2 枚举的应用
enum Month { January = 1, February, March, April, May, June, July, August, September, October, November, December };
- 默认初始化:枚举列表中未显式初始化的成员将被自动初始化为整数值0,即第一个成员的值为0,后续成员的值依次递增1。
- 显式初始化:可以通过在枚举列表中的成员名称后面使用赋值运算符来显式地初始化其值。
- 如果January没有赋值为1 ,那么其初始化的值为 0 ,后续的成员依次递增 1 ,February则为2.
3.3 枚举类型的优点
- 提高代码的可读性和可维护性:
- 类型安全:
- 便于调试:
- 节省内存:
- 便于扩展:
3.4 枚举和 #define 的区别
枚举类型提供了更为安全、灵活且易于管理的常量定义方式,而#define
宏定义则较为简单,适用于定义单个常量或在宏展开时需要进行复杂计算的情况。
如何选择二者
使用enum的情况:
- 提高可读性和可维护性: enum可以提供更具描述性的常数名,使得代码更易于理解和后续维护。
- 防止命名污染: enum可以将相关的常数封装起来,避免全局命名空间的污染。
- 方便调试: enum常数在预处理后不会被替换,因此在调试时可以看到原始的常数名,而不是被替换的值。
- 类型安全: enum常数具有明确的类型,这有助于编译器进行类型检查,从而提高代码的安全性。
使用#define的情况:
- 灵活性: #define可以在预处理阶段进行复杂的文本替换,这在某些情况下可能非常有用。例如,如果你需要在多个地方重复相同的计算,使用#define可以简化代码。
- 条件编译: #define可以用于条件编译,根据不同的条件包含或排除代码部分,这在编写可配置的应用程序时非常有用。
- 简单的常数定义: 如果常数仅用于简单的数值定义,且不需要额外的语义含义,使用#define可能更为简洁和高效
四、联合体(共用体)
在C语言中,联合体(union)是一种特殊的数据类型,它允许在同一内存地址存储不同类型的数据。与结构体(struct)不同,联合体的所有成员共享同一块内存空间,这意味着对其中一个成员的修改会影响到其他成员。
4.1 联合体的定义
union 联合体名 { 成员1; 成员2; ... };
联合体名是用户定义的联合体的名称,成员是联合体的成员变量,可以是不同的数据类型。
4.2 联合体的应用
#include <stdio.h> union Data { char c; int i; }; int main() { union Data d = {0}; d.c = 'A'; printf("Character is %c and its ASCII value is %d\n", d.c, d.i); return 0; }
创建了一个名为Data
的联合体,它有两个成员:c
和i
。然后我们在main
函数中创建了一个Data
类型的变量d
,并通过d.c
给它赋值为'A'
。由于c
和i
共享同一份内存空间,所以d.i
的值也会被设置为'A'
的ASCII值,即65
。
4.3 联合体的大小
- 联合体的大小至少要能容纳其最大的成员变量。
- 联合体的总大小必须是最大成员变量大小的整数倍,以满足内存对齐要求。
typedef union { char array[5]; int i; } MyUnion;
array
的大小是5字节,int
的大小是4字节。由于int
的大小不是5的倍数,所以联合体会被扩大到8字节,以便满足内存对齐要求。