C语言---自定义类型详解(结构体 + 枚举 + 共用体)
C语言自定义类型(结构体+枚举+联合)的超详细分享,附+贴心图解,清晰明了,快来看看你都学会了吗!
目录
4.3 联合大小的计算
1. 结构体
1.1 基本概念
在C语言中,结构体 是一种用户自定义的复合数据类型,允许将不同类型的数据组合在一起,称为 成员。
比如一个学生对象应该包含的属性有:学号(字符串),姓名(字符串),成绩(浮点数),年龄(整形),......
这样做主要有以下几个优点:
-
数据组织更方便: 当我们需要处理多种相关的变量时,结构体可以将它们组合在一起,形成一个逻辑上的整体。例如,处理一个“人”的信息时,使用单独的变量来存储姓名、年龄、身高等信息会显得杂乱且不易维护,而结构体将相关信息归为一体,代码会更加清晰且易于管理。
-
易于扩展: 结构体允许我们轻松扩展和修改数据类型。如果你需要为“人”增加新的属性,例如“性别”或“地址”,可以很方便地修改结构体的成员类型声明,而不需要在代码的其他部分做大量改动。
-
提高代码可读性: 通过结构体,可以更直观地表达数据之间的关系,使得代码更加符合逻辑;不同类型的数据被绑定在一起,避免了使用独立变量时可能出现的混乱。
1.2 声明,定义和初始化
语法结构:
struct tag(struct是结构体的关键字,tag是可自定义的标签名;struct tag就是一个自定义的类型,可以像内置类型一样定义变量)
{
member-list; //成员变量,根据具体的描述对象添加不同类型的变量
}variable-list; //逗号分隔,分号结束
variable-list是用此结构体类型 在声明的同时 创建的变量列表,属于全局变量;也可以为空,此时 仅为声明,没有定义开辟具体的对象内存。
初始化:struct tag类型的变量一律使用 ={......} 给每个成员初始化;否则为随机值。
例如描述一个学生:
struct Stu
{
char name[10];//名字
int age;//年龄
const char sex[5];//性别
char id[12];//学号
double weight;//体重
}stu1={"ZhangSan", 18, "男", "1", 76.8}, * p_stu = NULL;//全局变量:定义一个 struct Stu 类型的 变量stu1; 一个 struct Stu* 指针变量p_stu,存放 struct Stu 类型变量的地址
struct Stu stu2 = { "WangYan", 20, "女", "2", 48.5};//全局变量
int main()
{
struct Stu stu3 = {"LiSi", 19, "男", "3", 55};//struct Stu 类型的 局部变量
p_stu = &stu3;
return 0;
}
1.3 点操作符(.)和 箭头操作符(->)
用于访问结构体变量中的成员。
使用方式:结构体变量 . 成员
结构体变量指针 -> 成员
下面我们把 1.2 的示例代码变量打印出来:
//......
void Print(struct Stu* arr[])
{
int i;
for (i = 0; i < 3; ++i)
{
printf("stu%d:>\n学号:%s ; 姓名:%-10s ; 年龄:%d ; 性别:%s; 体重:%.2lf\n", \
i+1, arr[i]->id, \
(*(arr[i])).name, \
arr[i]->age, \
(*(arr[i])).sex, \
arr[i]->weight);
}
}
int main()
{
//......
struct Stu* arr[] = { &stu1, &stu2, p_stu };
//输出
Print(arr);
printf("修改stu1的姓名为:YuHe,学号为4;stu2的年龄+5;stu3的体重-4.5后:>\n");
strcpy(stu1.name, "YuHe");//或者使用memcpy
strcpy(stu1.id, "4");
stu2.age += 5;
stu3.weight -= 4.5;
//输出
Print(arr);
//......
return 0;
}
输出:
1.4 匿名结构体
省略结构体的标签名。
1:用于内嵌结构体,减少嵌套结构体的访问层级,增强代码简洁性和可读性
如下对比:
struct Body_form//身体形态
{
float _height;//身高
float _weight;//体重
//......
};
struct Physical_fitness//身体素质
{
float _flexibility;//柔韧度
//......
};
struct Physical_test_info//体测信息
{
struct Body_form _bf;//身体形态
struct Physical_fitness _pf;//身体素质
//......
};
struct Persion1
{
struct Physical_test_info _pti;
//......
};
struct Persion2
{
struct//身体形态
{
float _height;//身高
float _weight;//体重
//......
};
struct//身体素质
{
float _flexibility;//柔韧度
//......
};
//......
};
int main()
{
struct Persion1 p1 = { {{178.5, 67.0}, {0.6}} };
printf("%.2lf, .2lf, .1lf\n", p1._pti._bf._height, p1._pti._bf._weight, p1._pti._pf._flexibility);
struct Persion2 p2 = { 165.5, 78.5, 0.7};
printf("%.2lf, .2lf, .1lf\n", p2._height, p2._weight, p2._flexibility);
return 0;
}
显然,p2的可读性和可维护性更优!
2. 用于定义变量(只能在 variable-list 变量列表定义),简化了不需要在多个地方 频繁大量 使用结构体的场景,避免显式命名结构体类型
如下示例:
#include <stdio.h>
int main() {
// 定义一个匿名结构体并声明变量
struct {
int id;
char name[20];
float salary;
} employee;
// 初始化匿名结构体成员
employee.id = 1;
strcpy(employee.name, "Alice");
employee.salary = 5000.0;
// 访问匿名结构体成员
printf("ID: %d\n", employee.id);
printf("Name: %s\n", employee.name);
printf("Salary: %.2f\n", employee.salary);
return 0;
}
有以下几点需要注意:
1:成员命名冲突的风险
比如:
struct A
{
struct
{
int _t;
};
int _t;
};
int main()
{
struct A a = { 1, 2 };
return 0;
}
错误:
2:与标准C的兼容性
匿名结构体最早是在C11标准引入的,因此在编写C语言程序时,如果需要兼容C99或更早的标准,匿名结构体的用法可能不被支持
3:每个匿名结构体变量即使成员完全相同,也是不同的类型
比如:
struct
{
int _a;
}x;
struct
{
int _a;
}*p;
int main()
{
p = &x;
return 0;
}
如果使用g++编译,直接错误,编译不通过:
如果使用gcc编译,则报警告,编译通过:

如果使用VS,可能就直接顺利编译,因为:
-
编译器的宽松处理: 尽管这两种匿名结构体的类型严格来说是不同的,但很多编译器会忽略这种差异,允许指针之间的赋值。这是因为编译器可以推断出两个结构体的内存布局一致,不会引发运行时的错误。
-
不严格的类型检查: 在某些上下文中,C语言对指针类型的检查没有那么严格,尤其是当指向的对象类型相同时。尽管结构体本身是匿名的,但由于指针类型匹配编译器的预期行为,所以不会报错。
所以,从严格类型安全的角度来看,,应该使用显式的类型定义来确保代码的可移植性和安全性!
1.5 结构的自引用
注意:必须使用指针

举个例子:
struct Node
{
int data;
struct Node* next;
};
int main()
{
struct Node n1 = { 1, NULL };
struct Node n2 = { 2, NULL };
struct Node n3 = { 3, NULL };
n1.next = &n2;
n2.next = &n3;
//遍历
struct Node* p = &n1;
while (p)
{
printf("%d ", p->data);
p = p->next;
}
return 0;
}
示例输出:
1.6 typedef的使用
对复杂类型 重命名,简化书写。
比如:
typedef struct Persion
{
int _weight;
//......
}peo;//不再是变量创建列表,而是把类型 struct Persion 重命名为peo
struct Student
{
int _height;
//......
};
int main()
{
struct Persion P1;
peo P2;
//或者
typedef struct Student stu;
stu s1;
struct Student s1;
return 0;
}
需要注意的是:

1.7 结构体内存对齐
首先,介绍一个宏:offsetof(type,member); 头文件<stddef.h>
type : 数据的类型
member: 数据的成员变量
功能:返回成员偏移量
此具有函数形式的宏返回数据结构或联合类型类型中成员相对于起始位置的偏移值(以字节为单位)。
返回的值是类型为 size_t 的无符号整数值。
相对于起始位置的偏移值是什么?(如下图)

举个例子如下:

为什么会出现上述结果?
这是因为结构体的数据在存储时要满足内存对齐,那具体的规则是怎样呢,别急,我们接着往下看。

为什么存在内存对齐

1.8 修改默认对齐数
1.9 结构体传参

2. 位段
基本结构:
实例测试:
需要特别注意的地方:

3. 枚举
枚举顾名思义就是一 一列举。
把可能的取值一 一列举。
比如我们现实生活中:
一周的星期一到星期日是有限的7天,可以一 一列举。
性别有:男、女、保密,也可以一 一列举。
月份有12个月,也可以一 一列举
3.1 枚举类型的定义
书写示例:
enum Day//星期
{
Mon,
Tues,
Wed,
Thur,
Fri,
Sat,
Sun
};
enum Sex//性别
{
MALE,
FEMALE,
SECRET
};
enum Color//颜色
{
RED,
GREEN,
BLUE
};
以上定义的 enum Day , enum Sex , enum Color 都是枚举类型。
{}中的内容是枚举类型的可能取值,也叫 枚举常量 。
这些可能取值都是整形,默认从0开始,依次递增1,当然在声明枚举类型的时候也可以赋初值。
比如:
enum Color//颜色
{
RED=1,
GREEN=2,
BLUE=4
};
3.2 枚举的优点
我们可以使用 #define 定义常量,为什么非要使用枚举?
枚举的优点:
1. 增加代码的可读性和可维护性
2. 使用方便,一次可以定义多个常量
3. 便于调试

从上图不难看出,左边的写法更加直观一些。
#define也可以定义多个常量,但是一次只能定义一个,如果要和上图示左边达到同样的效果,那么就要写成:
#define Exit 0
#define Add 1
#define Sub 2
#define Mul 3
#define Div 4
虽然这样要繁琐一些
当然,合不合适,繁琐还是简易都不能一概而论,要根据具体场景的具体需要来确定,一般来说:枚举是用在有限个 已知的可能的取值可以一 一列举,且这些可能的取值一般都归为同一类描述同一个对象的时候,就像星期一到星期日描述的就是一周内有限的7个可能的取值,一月到十二月描述的就是一年内有限的12个可能的取值,等等,这些时候,用枚举就比较简便合适。
4. 和#define定义的标识符比较枚举有类型检查,更加严谨

而#define即使在C++下,也不会有任何的类型检查。
3.3 枚举的使用

4. 联合(共用体)
4.1 联合类型的定义
联合也是一种特殊的自定义类型
这种类型定义的变量也包含一系列的成员,特征是这些成员公用同一块空间(所以联合也叫共用体)。
比如:
//联合类型的声明
union Un
{
char c;
int i;
};
//联合变量的定义
union Un un;
int main()
{
//初始化
un.c = 'a';
un.i = 10;
return 0;
}
4.2 联合的特点
联合的成员是共用同一块内存空间的,这样一个联合变量的大小,至少是最大成员的大小(因为联 合至少得有能力保存最大的那个成员)。

举个例子:
判断当前机器的大小端存储类型:

4.3 联合大小的计算
好了,到这就结束了,如果我的分享对你有帮助的话,点赞关注加收藏或者把它分享给你的好友一起学习吧。
关注我,持续更新哟!
更多推荐




所有评论(0)