一、内置类型与自定义类型
在C语言中,有内置类型(也称为基本数据类型)和自定义类型(结构体)两种类型。
1.1 内置类型(基本数据类型)
- 整型(Integer types):用于表示整数值,包括:
- int:通常表示整数,取决于编译器和系统架构,一般为4字节。
- short int:短整数,通常为2字节。
- long int:长整数,通常为4字节或8字节。
- long long int:长长整数,通常为8字节。
- 字符型(Character type):
- char:用于表示单个字符或小整数值,通常为1字节。
- 浮点型(Floating-point types):用于表示实数,包括:
- float:单精度浮点数,通常为4字节。
- double:双精度浮点数,通常为8字节。
- long double:扩展精度浮点数,大小不定,通常大于8字节。
- 空类型(Void type):
- void:表示无类型,常用于函数返回类型或指针类型。
这些内置类型是C语言提供的基本数据类型,用于表示基本数据,如整数、浮点数、字符等。
1.2 自定义类型
在C语言中,除了内置的基本数据类型外,还可以通过结构体(Structures)和枚举类型(Enums)来定义自定义类型。
1.结构体(Structures)
结构体是一种用户自定义的数据类型,用于组合不同类型的数据成员。它允许将多个不同类型的变量组合在一起,形成一个新的数据类型,以便更方便地操作相关数据。
2.枚举类型(Enums)
枚举类型是一种用户自定义的数据类型,用于定义一组相关的命名常量。它允许将一组有限的取值集合在一起,形成一个新的数据类型,以便更清晰地表示程序中的意图。
二、结构体
2.1 结构体的声明
在C语言中,定义结构体使用 struct 关键字,结构体的形式如下:
struct 结构体名 { 数据类型 成员名1; 数据类型 成员名2; // 更多成员... };
【示例】:描述⼀个学⽣
struct Stu { char name[20]; // 姓名 int age; // 年龄 char set[5]; // 性别 int id; // 学号 }; // 分号不能丢
2.2 结构体变量的创建和初始化
初始化结构体变量:有几种方法可以初始化结构体变量:
1.按照结构体成员的顺序初始化:
#include<stdio.h> int main() { struct Stu s = { "张三", 19, "男", "202201170248" }; printf("%s\n", s.name); printf("%d\n", s.age); printf("%s\n", s.set); printf("%s\n", s.id); return 0; }
2. 按照指定的顺序初始化
前面在说操作符时我们讲过,可以通过点操作符来访问结构体成员,这里同样可以通过点操作符来给结构体成员进行初始化。
int main() { struct Stu s = { .age = 19, .id = "202201170248", .name = "张三", .set = "男" }; printf("%s\n", s.name); printf("%d\n", s.age); printf("%s\n", s.set); printf("%s\n", s.id); return 0; }
2.3 结构体的特殊声明
在声明结构的时候,可以不完全的声明。
匿名结构体类型
struct { int a; char b; float c; }x; struct { int a; char b; float c; }a[20], *p;
注意:
匿名的结构体类型,如果没有对结构体类型重命名的话,基本上只能使用一次。
p = &x; 这种写法是不合法的,编译器会把上面的两个声明当成完全不同的两个类型,所以是非法的。
2.4 结构体的自引用
结构体中的成员不仅可以是内置的数据类型,还可以是这个结构体本身,也就是结构体中包含指向相同类型结构体的指针或引用的情况。这种自引用的数据结构通常称为递归数据结构。
比如说定义一个链表的结点:
struct Node { int data; struct Node next; };
注意:这种自引用是错误的,因为⼀个结构体中再包含⼀个同类型的结构体变量,这样结构体变量的大小就会无穷的大,是不合理的。即无法确定 sizeof(struct Node) 的大小。
正确的自引用方式:
struct Node { int data; struct Node* next; };
在结构体自引用使用的过程中,夹杂了 typedef 对匿名结构体类型重命名,也容易出现引入问题。
typedef struct { int data; struct Node* next; }Node;
这种也是错误的,因为Node是对前面的匿名结构体类型的重命名产生的,但是在匿名结构体内部提前使用Node类型来创建成员变量,这是不行的。
解决方案如下:定义结构体不要使用匿名结构体了
typedef struct Node { int data; struct Node* next; }Node;
三、结构体内存对齐
【示例】:计算结构体的大小。
struct S { char c1; // 占1个字节 int i; // 占4个字节 char c2; // 占1个字节 }; int main() { struct S s = { 0 }; printf("%zd\n", sizeof(s)); return 0; }
在代码中我们看到结构体中有两个char和一个int,那他的大小就是6个字节,但结果真的是这样吗?
运行之后发现是12个字节,这是为什么呢?
这说明结构体中的成员不是随便放的,这里面是有一定规则的,这就是结构体的内存对齐。
3.1 对齐规则
首先得掌握结构体的对齐规则:
- 结构体的第⼀个成员对齐到和结构体变量起始位置偏移量为0的地址处。
- 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
对齐数 = 编译器默认的⼀个对齐数与该成员变量大小的较小值。
- VS 中默认的值为 8
- Linux中 gcc 没有默认对齐数,对齐数就是成员自身的大小
3.结构体总大小为最大对齐数(结构体中每个成员变量都有⼀个对齐数,所有对齐数中最大的)的整数倍。
4.如果嵌套了结构体的情况,嵌套的结构体成员对齐到自己的成员中最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体中成员的对齐数)的整数倍。
解析:对照规则1,第一个成员对齐到和结构体变量起始位置偏移量为0,也就是图中为0的位置(char占1个字节),其余的成员变量对齐到对齐数整数倍的位置(int占4个字节,VS的默认值为8,4小于8,即这里的对齐数为4),也就是4的整数倍(图中序号4)开始存,第三个成员变量也一样(char占1个字节小于8,即对齐数是1)。最后结构体总大小是最大对期数(第一个和第三个对齐数都是1,第二个对齐数是4)的整数倍,也就是4的倍数,由于已经占了9个字节,所以下一个4的倍数就是12,这里总共浪费了6个字节的空间大小。
【C语言基础】:自定义类型(一)--> 结构体-2