引言:
C语言当中除了,自己带的基本数据类型,还有一些自定义数据类型,用户自己可以控制数据类型大小,所包含的元素,使用起来更加方便,快捷。
一 数组arr:
对于数组而言,常常记住以下两点:
1.一定是相同类型元素的数据集合;
2.数组只是指数据元素,并不是指只是指是整数;
2.对于数组而言,在内存上是连续分布的;
数组的大小等于数据元素类型本本身大小乘以元素个数;
二结构体:
2.1结构体介绍:
C 数组允许定义可存储相同类型数据项的变量,结构是 C 编程中另一种用户自定义的可用的数据类型,它允许您存储不同类型的数据项。
结构体中的数据成员可以是基本数据类型(如 int、float、char 等),也可以是其他结构体类型、指针类型等。
2.1.2结构体组成:
结构体定义由关键字 struct 和结构体名组成,结构体名可以根据需要自行定义。
struct 语句定义了一个包含多个成员的新的数据类型,struct 语句的格式如下:
struct tag {
member-list
member-list
member-list//成员列表
...
} variable-list ;//结构体变量
//tag 结构体标签
tag: 是结构体标签。
member-list :是标准的变量定义,比如 int i; 或者 float f;,或者其他有效的变量定义。
variable-list :结构变量,定义在结构的末尾,最后一个分号之前,您可以指定一个或多个结构变量。下面是声明 Book 结构的方式:
struct Books
{
char title[50];
char author[50];
char subject[100];
int book_id;
} book;
在一般情况下,tag、member-list、variable-list 这 3 部分至少要出现 2 个。
2.2结构体内存对齐原则:
对于结构体而言,我们需要计算一个结构体内存的时候,需要注意,结构体内存对齐的原则
2.2.1对齐原则:
对于以上数据原则,要熟记。
可以结合以下实例逐条分析:
2.2.2示例计算:
示例一:
struct S1
{
char c1;
int i;
char c2;
};
printf("%d\n", sizeof(struct S1));
注意:首先,结构体也是连续存放的空间,但是存在一定的空间浪费,所以要注意结构体成员的列出,来节省内存空间。
存放c1:
首先了解一下:偏移量,就是指相对于第一个元素存放的起始地址而言的偏移多少的数值。
那么根据第一条原则,我们把第一个成员char 地址(偏移量)设置为0 ,
根据第二条,那么1和8的默认对齐数,选择较小的,那么对应,C1的对齐数就是1
那么我们开始存放的地址就必须是1的倍数,而0是1的倍数。所以存放在第一个位置0。
后面两条是针对于整体和嵌套,暂时不考虑;
存放int i :
首先,我们刚刚存放第一个元素位置为0,那么开始考虑第二条,存放的位置要是最小对齐数的倍数。那么比较int i和默认对齐数比较,取较小值为对齐数。
那么对于4个字节和8个默认对齐数比较。4小于8,所以对应,我们选择 int i的对齐数为4个字节
那么对于开始存放的第二个地址是从1开始:
而1不是最小对齐数的倍数,所以我们要从它的倍数,4的最小对应倍数是4那么从偏移量为4的地址开始存放。
那么对应的而言,就是说,会有1~3之间3个字节的空间浪费;
存放 char c2:
那么对于char c2 依旧是根据原则,一步一步计算。首先针对于我们的字节。char c2求它的对齐数
char 是1个字节和 8默认对齐数比较,选小的,那么对应就是 1;那么对于内存比较而言,我们存放的地址就要是1的倍数,那么开始存放的地址就是8,8是1的倍数,所以我们可以从8的位置开始存放:
整体内存对齐:
根据第三条原则,结构体的内存大小要是,整体对齐数的倍数。那么整体对齐数就是指结构体所有成员对齐数的较大值, char c1, int i,char c2分别对齐数是:1,4,1那么整体对齐数就是4,而目前我们存放使用了9个字节,那么9不是4的倍数,所以还要对齐整体偏移,最近4的倍数是12.
所以整体所占内存大小就是12。
实例二:
#include <stdio.h>
struct s2
{
char c1;
char c2;
int i;
};
int main()
{
struct s2 test;
printf("S2结构体大小为:%d\n", sizeof(test));
return 0;
}
首先 C1作为第一个成员,设置为地址为0的偏移处,c1为一个字节,默认对齐8个字节选择较小的,1作为c1的对齐数。c2起始地址为偏移量1处,c2为1个字节,默认对齐数为8个字节选择较小值为1作为c2的对齐数。1是偏移量1地址的倍数,所以可以存放1个字节。int i为4个字节,默认对齐数为8个字节,选择较小值那么对应为4。开始存放偏移量为3不是4的倍数。所以得从最小公倍数处,即偏移量为4处开始存放 int i。那么整体存放0~7对应整体字节为8个字节,那么开始计算整体对齐数,根据c1,c2,i的对齐数取最大值,即为4。8为4的倍数,所以满足内存对齐原则。
所以整体结构体内存大小为4个字节。
由此S1,S2大小对比可以发现,相同较小元素放一起可以有效节省结构体所占内存大小空间。
示例三(结构体嵌套):
struct S3
{
double d;
char c;
int i;
};
printf("%d\n", sizeof(struct S3));
结构体嵌套问题
struct S4
{
char c1;
struct S3 s3;
double d;
};
printf("%d\n", sizeof(struct S4));
计算S3结构体计算方法和上面基本相同,大概看一下我的计算过程:
可以计算出大小为16个字节。
计算S4结构体大小:成员c1第一个元素大小为1个字节默认对齐数为1个字节,放置在位置为0处。
计算c1的对齐数,1小于默认对齐数8,所以c1的对齐数为1。开始计算成员2,也就是S3那么S3对齐数,取决于该结构体内最大对齐数,也就是对应8作为该结构体的对齐数。那么对于8,所以要求起始地址就是要求是对齐数倍数处。而成员2的起始偏移量为1,那么对应应该从地址8处开始存放16个字节。也就是存放到8~23。对应成员3 double d,大小为8个字节,默认对齐数8,选择较小值8
那么d开始存放位置要是8的倍数。那么对应成员3开始地址刚好是24为8的倍数开始存放,成员d存放到24~31处。对于整体而言就是0~31整体大小为32。开始计算整体最大对齐数,1,8,8选择最大对齐数8作为对齐数。那么32刚好是8的倍数。所以对应整体大小就是32字节。
2.3结构体“.”和->的区别:
结构体中 “->” 与 “.” 的区别以及使用
两者在同一个代码块内使用的时候其实没有什么太大不同,无非就是声明结构体的时候一个是声明指针,一个是声明结构体。声明结构体的时候分配了内存空间,所以可以用".“直接访问,而声明指针之后并没有分配内存空间,所以用”->“来指向开辟的空间。也可以用”(*buffer).foo" ,等价于"buffer->foo"。
"->"是在声明结构体指针时,访问结构体成员变量时使用。
"."是声明结构体时,访问结构体成员变量时使用。
三 联合体(共用体):
3.1联合体(共用体)介绍:
3.2 联合体组成:
为了使用联合体,需要使用 uion关键词来定义联合体。那么对于整体架构其实和结构体差不多:
union [union tag]
{
member definition;
member definition;
...
member definition;
} [one or more union variables];
union tag 是可选的,也就是我们声明这个联合体类型叫做声明名字,每个 member definition 是标准的变量定义,比如 int i; 或者 float f; 或者其他有效的变量定义。在共用体定义的末尾,最后一个分号之前,您可以指定一个或多个共用体变量,这是可选的。下面定义一个名为 Data 的共用体类型,有三个成员 i、f 和 str:
3.3联合体(共用体)大小计算:
和结构体一样,我们对于共用体计算同样需要根据相应的对齐原则,去计算大小;
3.3.1共用体对齐原则:
3.4示例计算:
示例一:
union Un
{
char c;
int i;
};
计算:第一根据两条原则,计算即可;
char c 为1个字节 , int i为4个字节计算最大成员大小,最大成员为int 4个字节
那么联合体大小为4个字节,两者对齐数为1 ,4取最大对齐数4。4为4的倍数,所以
整体结构体大小为4个字节。
示例二:
union Un2
{
short c[7];
int i;
};
那么对于共用体2而言,它的整体大小,首先成员1为应该数组,那么对于其大小为2*7,大小为14个字节。成员2,int i大小为4个字节。那么取决于较大一位14字节
那么计算共用体对齐,计算,对齐数是取决于其成员的基本数据类型元素大小,而不是整体大小
short是2个字节。那么对于int 4个字节,其对齐数采取4。
那么对于整体共用体大小14不是4的对齐数,所以应该对齐到实际最小公倍数到16个字节。
(那么对于两条原则,如果共用体成员只有基本数据类型,不存在自定义数据类型,计算大小往往不需要考虑对齐数,因为对齐数就是基本数据类型元素的字节大小,即使考虑本是上内存大小不受影响)
3.5联合体(共用体)简单应用:
可以用来计算所使用内存存储空间到底是大端存储还是小端存储。
知识补充:大小端存储(Endianess)是指多字节数据在计算机内存中的字节排列顺序。具体来说,它决定了数据的高位字节(Most Significant Byte, MSB)和低位字节(Least Significant Byte, LSB)在内存地址中的存放顺序。根据字节的存储顺序,主要可以分为两种类型:
-
大端(Big Endian)存储:
- 在大端存储中,高位字节存储在内存的低地址端,低位字节存储在内存的高地址端。
- 举例来说,一个16位的整数
0x1234
在大端存储中,其内存表示会是12 34
(十六进制),即高位字节12
在前,低位字节34
在后。
-
小端(Little Endian)存储:
- 在小端存储中,低位字节存储在内存的低地址端,高位字节存储在内存的高地址端。
- 同样以16位整数
0x1234
为例,在小端存储中,其内存表示会是34 12
(十六进制),即低位字节34
在前,高位字节12
在后。
这两种存储方式对于单个字节的数据没有区别,但对于多字节的数据(如整型、浮点型等)就有明显的不同。在编写跨平台的软件时,需要特别注意数据的字节序,因为不同的硬件平台可能采用不同的字节序。例如,Intel和AMD的x86和x86_64架构通常使用小端存储,而一些网络协议和文件格式(如网络字节序)则采用大端存储。
在实际编程中,有时需要进行字节序的转换,以确保数据在不同平台之间的正确传输和解析。例如,可以使用函数如htonl
(host to network long)和ntohl
(network to host long)在网络编程中进行32位整数的字节序转换。对于更通用的字节序处理,可以使用联合(union)或者位操作等技巧来进行编程。
int check_sys()
{
union
{
int i;
char c;
}un;
un.i = 1;
return un.c;//返回1是⼩端,返回0是⼤端
}解读程序
解读:
这里的关键在于理解整数1在内存中的表示。在计算机中,整数1通常表示为0000 0001
(对于32位整数)。但是,这个表示如何映射到char
类型的un.c
取决于系统的字节序。
- 在大端系统上,高位字节(即
0000
)会存储在内存的低地址端,而un.c
(一个char
类型)只会读取这个地址的值,所以它会返回0。 - 在小端系统上,低位字节(即
0001
)会存储在内存的低地址端,所以un.c
会返回1。
四 枚举 :
4.1枚举介绍:
enum Day//星期
{
Mon,
Tues,
Wed,
Thur,
Fri,
Sat,
Sun
};
enum Sex//性别
{
MALE,
FEMALE,
SECRET
};
enum Color//颜⾊
{
RED,
GREEN,
BLUE
};
简单来说就是一一列举,实际应用非常方便。
4.2枚举赋值计算:
对于枚举的值,实际上枚举的成员都是有值的,对于枚举的值往往声明定义的时候就已经定了。
不能在改变。所以对于我们而言。往往就是一句话枚举常量。
默认枚举成员数值是从0开始依次递增,其次每次递增量为1
示例一:
enum Color//颜⾊
{
RED,
GREEN,
BLUE
};
默认就是从0开始,对于递增量为1。
那么就是 0,1,2;
示例二:
enum Color//颜⾊
{
RED=1,
GREEN,
BLUE
};
RED为1,开始递增,增量为1,那么对应1,2,3
示例三:
enum Color//颜⾊
{
RED,
GREEN=4,
BLUE
};
RED没有赋值从开始,默认从0,开始递增,GREEN赋值4,开始递增,增量为1 ,那么BLUE开始从4开始递增,增量为1,那么对应5.
所以结果1,4,5。