Overview
C语言中复合类型 (composite type) 是指用户自定义类型,通常由多种元素组成的类型,其元素被紧密存储在内存中。C语言常见的复合类型有:
- 数组
- 字符串
- 结构体
- 联合类型
结构体 [1]
结构体 (structure) 是指用户定义的数据类型,允许将不同类型的多个元素组合在一起,来创建出更复杂的数据类型,类似于数组,但又区别于数组,数组只能保存同类型的元素,而结构体可以保存不同类型的元素。
定义
声明结构体的语法如下
|
|
这里需要注意的一些地方:
- struct是关键字,structureName定义的新数据类型,variable{}是作为使用 structureName 声明的新变量名
- 每个成员方法结尾都是 “;" 而不是逗号 ”,"
- 结构体不能递归
- 变量可以有多个
例如声明一个学生的结构体,而student是作为一个新的数据类型存在
|
|
Notes:在定义(创建)结构体变量前,结构体成员不会占用内存
声明
使用结构体声明变量
也可以一次性定义结构体和声明变量
|
|
赋值
在声明结构体后,student结构体只是自定义数据结构,要使用还需要进行初始化,或者赋值
|
|
或者
|
|
或者
|
|
或者使用不同顺序进行初始化
|
|
也可以仅初始化部分成员,未初始化的成员应该按顺序在后位
|
|
访问
访问结构体可以使用符号 ”.“ 来访问,成员名称==.==成员属性
#include<string.h>
struct Student
{
char name[25];
int age;
char branch[10];
char gender;
};
int main()
{
struct Student s1;
s1.age = 18;
strcpy(s1.name, "Viraaj");
printf("Name of Student 1: %s\n", s1.name);
printf("Age of Student 1: %d\n", s1.age);
return 0;
}
也可以使用scanf() 赋值
结构体运算
结构体不能够执行算术运算符 +, -, x, ÷ ,关系运算符 < > <= >=, 等式运算符,但是可以在两个相同结构体变量的场景下进行赋值运算。
|
|
因为C语言没有提供比较运算,所以没法进行结构体比较,需要自行比较结构体成员来比较结构体是否一样
|
|
输出结果
|
|
结构体数组
结构体数组是指数组元素是结构体,例如下面声明一个类型为student的数组
|
|
初始化和访问可以通过循环进行
|
|
将代码整合为一起
|
|
结构体嵌套结构体
嵌套结构体表示,结构体的成员是另外一个结构体
|
|
其定义语法为:struct <other struct> <member_name>;
这里 birthday
是名为data类型结构体
Notes:结构体内部不能嵌套自己
访问嵌套结构体和正常结构体访问一样使用符号,成员名称==.==成员属性==.==成员属性
|
|
结构体内存分配
结构体声明后是不占用内存,只有被初始化后才占用内存,结构体内每个成员会被分配到连续的内存内,sizeof()的大小是每个元素所占用的大小。
示例代码为一个student的结构体,有四个成员,name为20 bytes的字符串,roll是4字节的int类型,gender是1字节的char,marks为5个元素的数组,那么这个结构体的总大小应该为 $20+4+1+5\times4$
|
|
将上述代码整合为
|
|
可以看到两个结果并不相等,可以看出实际被多分配了3个字节,需要知道为什么被多分配需要先打印他们的地址
|
|
输出结果为
|
|
可以看到char类型占用一个字节,而接下来的成员 marks 却是从4225436开始的,而不是4225433。这就需要引入下面的概念数据对齐 (Data alignment)
数据对齐
数据对齐是指处理器在数据对齐时访问效率最高,这将代表了数据存储在内存中的大小的倍数。而现代计算机字长通常为4 字节(32 位操作胸痛)或 8 字节(64 位操作系统)的字长。
对于一个int类型的变量,占用的资产时4字节,此时符合处理器读取机制,因为符合计算机字长长度。而作为char类型,占用一个字节。如果不做数据对齐操作,就会出现如下图出现的问题,数据在存储时读取的字长永远是多一个步骤的。
下图是一个错位的数据,粉红代表char类型,蓝色代表short类型,绿色代表int类型,如果不进行对齐,再继续存储int时,在读取数据时一个字长位移都将不足以读取一个int类型,这就需要进行两次数据访问才能读取一个int类型,也就是花费了两倍的时间
出于上述原因才有了数据对齐的概念,下图所示的对齐模式被称为自然对齐 (naturally aligned)
内容填充
在对齐时所插入的额外字节数的部分被称为填充 (padding),在上图中,黑色部分为填充的部分,而在上述代码示例中所填充的部分为3字节,而4225433位的内存地址存储int类型(marks[0]的地址)不是4的倍数。
下表说明了需要对其的数据类型规则
数据类型 | 占字节数大小 | 地址倍数 |
---|---|---|
char | 1 | 1的倍数 |
short | 2 | 2的倍数 |
int, float | 4 | 4的倍数 |
double, long, *(pointer) | 8 | 8的倍数 |
long double | 16 | 16的倍数 |
另外一个示例,应该是多少?
|
|
由于 i1
为int类型4字节,d1
为 double类型8字节,c1
为char类型1字节 ,那么 $4+8+1=13$ 被填充后应该是16字节,那么看下输出结果
|
|
实际上在C语言中结构体的数据类型对齐不是这么计算的,实际上结构体数据对齐条件是根据结构体内最大的元素进行调整 [2],例如这里最大元素为8,那么对齐标准就是补足8字节 i1
需要补4,c1
需要补7
通过调整结构体顺序可以减少填充的大小,例如下列代码,其实际大小为1 byte + 8 bytes + 1 bytes = 10 bytes,而实际大小为24bytes,因为double将影响填充的大小
|
|
而通过按照类型的由小到大的顺序进行定义成员,可以减少填充的次数与大小,这样1+1+(6)+8=16
|
|
为此得出的结论为,对结构体成员重新排序可以提高内存效率
数据打包
数据打包 (Packing) 是指强制编译器不进行数据填充,与数据填充是相反的作用
在windows上使用宏定义 #pragma pack(1)
来指定对齐方式,也可以使用 __attribute__((packed))
指定一个结构体补进行填充。
|
|
输出结果为
|
|
还可以指定特定的大小进行填充,例如
|
|
输出结果为
|
|
指针结构体
这里包含指针作为结构体成员和指针指向结构体
|
|
总结:
.
运算符优先于*
运算符,需要加括号改变优先级如果成员属性是指针类型,访问其内容应先解引用成员
*(stu1.roll)
如果指针是结构体需要解引用结构体
(*stu2).name
指针类型访问成员的特殊方法为
->
结构体数组
结构体也可以作为数组的形式,每个数组元素为一个结构体。作为数组结构体时,指针类型需要解引用或者使用 ->
来访问。
|
|
结构体函数
在C语言中,函数不能作为结构体成员,但是函数指针可以,使用 .
可以调用指针函数成员
|
|
结构体作为函数参数
当函数参数过多时,传递大量参数效率很低,可以将结构体作为参数传递给函数
|
|
如果结构体比较复杂,传递副本参数效率不高,也可以传递指针结构体作为函数参数
|
|
结构体作为函数返回值
结构体可以作为函数的返回值
|
|
当然如果结构体交复杂,也可以用结构体指针作为返回值
|
|
typedef
typedef 是C语言中的关键字,功能是为现有数据类型分配别名,例如为long类型声明一个别名
|
|
也可以在结构体中使用
|
|
typedef主要功能:
别名,简化结构体类型struct关键字
区分数据类型
- c
1
char * p1,p2; // 声明两个变量 p1为char指针类型,p2为char类型
- c
1 2
typedef char* charPtr; charPtr p1,p2; // 声明两个变量为char*类型
提高代码的可移植性
- c
1 2 3 4
typedef long long int64; typedef long long int32; // 在大量别名情况下无需每个替换 int64 a=10; int64 b=20;
union
union是类似于结构体的一种用户自定义类型,与结构体最大的区别是,结构体是存储一系列元素的联合体,而union是多个成员,仅有一个元素能被存储。
定义
定义union语法:
- union <attr-spec-seq(optional)> <name(optional)> { struct-declaration-list } <union var,…>
- union <attr-spec-seq(optional)> name
|
|
定义union不会被分配内存,如果要分配内存则需要创建变量使用它
访问
|
|
总结
- union是用户自定义的数据类型
- union中成员都是相同的内存地址
- union保存的内容仅为最近一次赋值的元素的值(哪个元素被赋值,哪个元素被激活)
- union大小为其占用空间最大的那个成员
enum
枚举(enumeration)是C语言中特殊的数据类型,通常是包含具有共同性数据的集合,例如性别,男,女
|
|
声明
常用的有两种方式来使用枚举类型
|
|
与
|
|
赋值
|
|
总结:
- 枚举是整数常量类型
- 枚举包含的元素即为对应变量可以拥有的值
- 因为是整数常量,可以转换为char,bool等
- 枚举的元素结尾是逗号,最后一个元素没有符号;结构体的元素结尾为分号
Reference
[1] structured data types in c explained
[2] Alignment in C