目录
引言
C 语言提供了多种基本数据类型,如整型、浮点型、字符型等,以及一些构造类型,如数组、指针、函数等。但是,有时候这些类型还不足以表达一些复杂的数据结构或者逻辑关系,因此,C 语言也允许我们根据实际需要自定义一些数据类型,以便更好地解决问题。
本文将介绍三种自定义类型,分别是枚举类型、结构体类型、联合(共用体)类型。
枚举类型
枚举类型的定义和使用
枚举类型是一种用来表示一组有限的离散值的类型,它可以用来定义一些常量集合,如星期、月份等。枚举就是把可能的取值一一列举。
枚举类型的定义格式如下:
enum 枚举类型名 {枚举元素表};
其中,enum
是关键字,枚举类型名
是用户自定义的标识符,枚举元素表
是由逗号分隔的多个枚举元素组成,每个枚举元素可以看作是一个整型常量,它们的值默认从 0 开始依次递增,也可以在定义时指定。
例如,定义星期时:
enum Weekday
{
MON, //0
TUE, //1
WED, //2
THU, //3
FRI, //4
SAT, //5
SUN //6
}; // 定义一个星期的枚举类型
此时的星期所对应的数字并不对,一个星期应该是从1开始到7结束,而此时是从0开始6结束的,我们只需要将Mon(星期一)赋值为1即可,后面的星期仍会依次增加1。
修改后的代码如下:
enum Weekday
{
MON = 1, //1
TUE, //2
WED, //3
THU, //4
FRI, //5
SAT, //6
SUN //7
}; // 定义一个星期的枚举类型
请注意,枚举类型并不是只能赋值连续的数值,枚举类型会在赋值处重新开始累加。另外,枚举变量只能用枚举常量来赋值(用int类型赋值枚举变量的行为,在C中不会报错但在C++中会报错)。
例如:
enum Color
{
RED = 1, //1
YELLOW, //2
BLUE = 4, //4
GREEN = 10,//10
};
enum Color clr = BLUE;//4
clr = 5;//error
枚举类型的优缺点
枚举类型的优点:
- 增加了代码的可读性和可维护性,使得程序更容易理解和修改。
- 有类型检查的功能,防止了不合法的赋值或者比较操作,使得程序更加严谨和安全。
- 便于调试,预处理阶段不会删除枚举类型的定义,可以在调试器中查看枚举变量的值和名称。
- 使用方便,一次可以定义多个常量,不需要使用
#define
指令。 - 遵循作用域规则,枚举类型的定义和使用受到作用域的限制,可以避免命名冲突的问题。
枚举类型的缺点:
- 内存消耗:每一个枚举值都是一个对象,它占用的内存比整数或字符串多。如果应用中使用了很多枚举类型,可能会影响性能和内存效率。
不可继承:枚举类型不能被继承或扩展,它只能包含固定的常量。这限制了枚举类型的灵活性和可重用性,如果需要添加或修改枚举值,就必须修改源代码并重新编译。
结构体类型
结构体的定义和使用
结构体类型是一种用来表示一组相关的数据的类型,它可以将不同类型的数据成员组织到一个整体中,适合对复杂的数据进行处理。
结构体类型的定义格式如下:
struct 结构体类型名 {数据类型1 成员1; 数据类型2 成员2; ... 数据类型n 成员n;};
其中,struct
是关键字,结构体类型名
是用户自定义的标识符,数据类型i
和 成员i
分别表示结构体中的第 i 个成员的数据类型和名称,它们可以是任意合法的数据类型,包括基本类型、数组、指针、函数、枚举类型、结构体类型等。
例如:
struct Stu // 定义一个学生的结构体类型
{
char name[20]; // 姓名
int age; // 年龄
char sex[5]; // 性别
double score; // 成绩
};
结构体类型名可以省略,省略后创建的就是匿名结构体。
例如:
struct // 定义一个匿名结构体
{
int i;
float f;
char str[20];
};
定义了结构体类型后,就可以用它来定义结构体变量。
如:
struct Stu stu1, stu2; // 定义两个学生的结构体变量
结构体变量可以在定义时或者之后赋值,赋值时需要使用大括号将各个成员的值按照顺序包含起来。
如:
struct Stu stu = {"张三", 18, "男", 95.5}; // 定义并初始化一个学生的结构体变量
结构体变量可以通过'.'操作符来进行引用、赋值、输出等操作。
如:
printf("姓名:%s\n", stu.name); // 引用结构体变量的成员
printf("年龄:%d\n", stu.age);
printf("性别:%s\n", stu.sex);
printf("成绩:%.2f.\n", stu.score);
将结构体变量的地址赋值给一个指针p后也可以通过'->'来进行上述操作。
如:
struct Stu *p = &stu;//定义结构体指针p,并将结构体变量stu的地址赋值给p
printf("姓名:%s\n", p->name); // 引用结构体变量的成员
printf("年龄:%d\n", p->age);
printf("性别:%s\n", p->sex);
printf("成绩:%.2f.\n", p->score);
结构体变量也可以在定义的时候就赋值,还可以使用'.'操作符来改变赋值的顺序。
例如:
struct SN
{
char c;
int i;
}sn1 = { 'A', 1 }, sn2 = { .i = 2 , .c = 'B' };
结构体的自引用
C语言中结构体的自引用的方法是在结构体内部,包含指向自身类型结构体的指针。这样可以实现链表等数据结构。
错误的自引用方式:
struct Node {
int data;
struct Node next;
};
正确的自引用方式:
struct Node {
int data; //数据域
struct Node *next; //指针域,指向下一个结点
};
结构体内存对齐
C语言中结构体的对齐是指结构体中的成员变量在内存中的存放方式,它受到两个因素的影响:自身对齐值和指定对齐值
- 自身对齐值:是指每个成员变量的数据类型所占的字节数,例如char为1,short为2,int为4,double为8等。
- 指定对齐值:是指通过
#pragma pack(n)
宏定义来设定的一个整数,它表示结构体中的成员变量的对齐方式,n只能是2的幂次方,例如1,2,4,8,16等。
结构体的对齐遵循以下三个原则:
- 第一个成员变量的偏移量为0,即从结构体的起始地址开始存放。
- 每个成员变量的偏移量必须是它的自身对齐值和指定对齐值中的较小者的整数倍,如果不是,则在前一个成员变量后面补充一些空字节,直到满足条件为止。
- 结构体的总大小必须是它的最大成员变量的自身对齐值和指定对齐值中的较小者的整数倍,如果不是,则在最后一个成员变量后面补充一些空字节,直到满足条件为止。
下面举一个例子来说明结构体的对齐过程,假设在32位系统上,指定对齐值为4,有如下结构体定义:
struct test {
char a; // 1 byte
int b; // 4 bytes
short c; // 2 bytes
};
根据对齐原则,我们可以得到以下结果:
- a的偏移量为0,占用1个字节,从0到0。
- b的偏移量必须是4的整数倍,因此在a后面补充3个空字节,从1到3,然后b占用4个字节,从4到7。
- c的偏移量必须是2的整数倍,因此不需要补充空字节,c占用2个字节,从8到9。
- 结构体的总大小必须是4的整数倍,因此在c后面补充2个空字节,从10到11,然后结构体占用12个字节,从0到11。
同样,根据对齐原则,我们稍微调整一下结构体成员的顺序便可减少结构体所占内存的大小:
struct test {
char a; // 1 byte
short c; // 2 bytes
int b; // 4 bytes
};
分析可知,修改后的代码使结构体所占内存大小减少4个字节。因此我们在创建结构体类型时应该注意结构体成员的排列顺序。
位段
C语言中的位段(bit field)是一种特殊的结构体成员,它可以指定占用的二进制位数,从而节省内存空间。
位段的声明和使用方法如下:
//声明一个位段结构体
struct A {
int a: 3; //a占用3位
int b: 4; //b占用4位
int c: 5; //c占用5位
};
//使用一个位段变量
struct A bf;
bf.a = 5; //赋值
bf.b = 10;
bf.c = 15;
printf("%d, %d, %d\n", bf.a, bf.b, bf.c); //输出
位段的存储方式和对齐规则取决于编译器的实现,不同的编译器可能有不同的结果。
一般来说,位段的存储遵循以下原则:
- 位段成员必须是整型或枚举类型,通常是无符号类型。
- 位段成员的位宽不能超过其数据类型的长度,例如int类型的位段不能超过32位。
- 相邻的位段成员如果类型相同,且位宽之和小于类型的长度,那么它们会紧邻存储,直到不能容纳为止;如果类型不同,或者位宽之和大于类型的长度,那么后面的成员会从新的存储单元开始,其偏移量为类型长度的整数倍。
- 位段结构体的总大小必须是其最大成员类型的长度的整数倍,如果不是,会在最后补充空字节。
- 位段成员可以没有名称,只给出数据类型和位宽,这样的位段成员不能使用,一般用来作为填充或调整位置。
注意:跟结构相比,位段可以达到同样的效果,并且可以很好的节省空间,但是有跨平台的问题存在。
联合类型
联合类型是一种特殊的数据类型,它可以在同一段内存空间中存储不同的数据类型,但是每次只能使用其中一个成员的值。联合类型的优点是节省内存空间,缺点是不能同时操作多个成员。联合类型的定义和使用方法与结构体类似,只是关键字从 struct 改为 union。
例如:
union Data
{
int i;
double d;
char str[20];
};
union Data data; //定义一个联合变量
data.i = 10; //给整型成员赋值
data.f = 3.14; //给浮点型成员赋值,会覆盖整型成员的值
data.str = "Hello"; //给字符串成员赋值,会覆盖浮点型成员的值
结构体向double对齐,20个char一共是20字节,对齐后是24字节。n是单独的4字节,d是单独的8字节,由于是union,所以n,d与s共用空间,只取最长的元素,故占用24字节。
结语
以上,就是C语言中有关枚举类型,结构体类型和联合(共用体)类型的介绍,希望对大家有所帮助!
如有错误,欢迎在评论区指出!