1、结构体
1.1 什么是结构体
定义:结构是一些值的集合,这些值为成员变量,结果的每个成员可以是不同的类型。
在C语言中,数据类型是有限的,但人的需求是无限的,当我们需要存储更多不同的复合类型的数据时,我们就要用到结构体。比如说:我们要记录个人信息——姓名、性别、年龄和联系电话,此时我们就可以用到结构体。
1.2 结构体的声明
struct perinfo //个人信息
{
char name[20];
char sex[6];
char tel[12];
int age;
};//注意:分号不能丢
struct Book //嵌套定义
{
char name[20];
char id[20];
float price;
struct perinfo writer; //嵌套了结构体 struct perinfo
};
1.3 结构体变量的定义和初始化
struct perinfo //perinfo这个结构体是可以被除main函数以外的函数调用
{
char name[20];
char sex;
char tel[12];
int age;
}s1 = {"lisi",'m',"13455687546",18};//可以在声明结构体时定义结构体变量以及初始化结构体变量
int main()
{
struct Book //Book这个结构体只能被被main函数调用,只能在main函数中定义变量
{
char name[20];
char id[20];
float price;
};
struct perinfo s2 = { "asir",'f',"13455684546",28 };//也可以在函数中定义结构体变量以及初始化结构体变量
return 0;
}
1.4 结构体内存对齐
首先我们得知道什么是偏移量。
偏移量是以字节为单位,现在所处的地址距离结构体起始地址之间的字节个数。
计算结构体所占内存的大小:
- 第一个成员变量与结构体变量偏移量为0的地址处。
- 其它成员变量(除第一个成员变量以外的所有变量)要对齐到对齐数的整数倍的地址处(对齐数 = 编译器默认的一个对齐数 与 该成员大小 的 较小值,Vs中默认的值为8字节)
- 结构体的总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍
- 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
举例:
计算以下结构体所占内存的大小
struct s1
{
char c1;
char c2;
int b;
};
从偏移量为0到偏移量为7总共有八个字节,而结构体最大对齐数为4(一个是1一个是4),八为4的倍数,所有结构体占八个字节。
struct s1
{
char c1;
char c2;
int b;
};
struct s2 //嵌套结构体
{
char c1;
struct s1 s2;
int b;
};
最终结构体的大小为32个字节。
1.5 结构体传参
结构体传参有两种方式:
- 用一个结构体作为形参,将整个结构体传过去
- 用一个结构体指针作为形参,把结构体的地址传过去
举例:
#include<stdio.h>
struct S1
{
int a[10];
int c;
};
//把整个结构体传过去
void Print_struct1(struct S1 ss)
{
printf("%d\n", ss.c);
}
//把结构体的地址传过去
void Print_struct2(struct S1 *ss)
{
printf("%d\n", ss->c);
}
int main()
{
struct S1 s = { {0},1 };
Print_struct1(s);
Print_struct2(&s);
return 0;
}
显然,只把结构体的地址传过去更省力;传地址,则只需要再创建一个结构体指针,只需要四个字节(32位环境下);而把结构体传过去,需要创建一个结构体来接收,会浪费内存和时间。
2、位段
2.1 什么是位段
位段有以下要求:
- 位段的成员必须是整形家族的数据,如 int 、unsigned int 、signed int 、char型数据
- 位段的成员名后有一个冒号和一个数字
位段,顾名思义,是以位为单位的, 成员名后面的数字就是表明这个成员占了多少位。
位段写法跟结构体很像
举例:
struct s
{
int a : 4 ; //成员a原本应该占32位,现在占4位
char b : 5; //成员b原本应该占1位,现在占4位
char c : 6; //成员从c原本应该占1位,现在占4位
};
2.2位段的内存分配
如何计算位段所占字节数,下面我们以VS2017为例
#include<stdio.h>
struct s
{
char a : 2;
char b : 4;
char c : 5;
};
int main()
{
struct s s1;
s1.a = 5;
s1.b = 3;
s1.c = 4;
printf("%d",sizeof(s1));
}
这个程序输出的结果是2,说明这个位段占了两个字节,那么是怎么分配的呢?让我们来看看。
注意,这里涉及到一个大小端存储的问题,这个问题我在之前的博客中也有提到。在VS中,一个字节内部是大端存放,即低地址存到高地址处,高地址存到低地址处,而在字节与字节的数据存放中又是采用小端存放,即低地址存到低地址处,高地址存到高地址处。s1中,地址由低到高,对应的数据为 c d c 4,转成十六进制的形式,为0xc4cd。
以上的分配仅适用于VS,位段的存放方式有许多不确定性的。
2.3 位段的跨平台问题
位段的存放方式有许多的不确定性:
- int位段被当成有符号数还是无符号数不确定
- 位段中最大位(也就是int的位数)不确定,在16位机器中最大位16位,也就是两个字节,在32位和64位机器中最大位为32位,也就是4个字节)
- 位段中的成员在内存中是从左往右分配,还是从右往左分配的,也是不确定的
- 当一个结构包含两个位段,第二个位段成员比较大,无法容纳第一个位段剩余的位时,是舍弃剩余的位还是继续利用,这也是不确定的
因此,虽然位段能够节省空间,但是位段的跨平台型能力较差,具有很多的不确定性。
3、枚举
3.1 枚举的定义
enum color
{
red,
green,
purple
}col;
enum color 为枚举类型名,red,green,purple为枚举常量,用enum color定义了一个枚举变量,叫作col。
注意:
- 枚举常量也就是枚举元素的名字本身并没有特定的含义,例如不会因为写成sum患者Sunday就自动代表“星期天”,它只是一个负号,便于程序员理解
- 在没有对枚举常量初始化时,第一个枚举常量为0,且后一个的枚举常量比前一个枚举常量的值大1。在上面的声明中,编译系统按照定义给red,green,purple赋值为0,1,2。如果对其中的枚举常量初始化,它们的值就会有改变。
enum color { red=5, //red的值为5 green,//green的值为6 purple //purple的值为7 }; enum color { red, //red的值为0 green=5,//green的值为5 purple //purple的值为6 }; enum color { red, //red的值为0 green,//green的值为1 purple=5 //purple的值为5 };
-
枚举元素是常量,不能对它们赋初值。
enum color { red=5, //这是正确的 green, purple }; red=0;//这是错误的
4、联合(共用体)
4.1 联合体的定义
联合体也是一种特殊的自定义类型,这种类型定义的变量也包含一些列的成员,特征是这些成员公用同一块空间。
union Un
{
char c;
int a;
};
union Un un;
int main()
{
printf("%d", sizeof(un));
return 0;
}
4.2 联合大小的计算
联合有以下特点:
- 联合的大小至少是最大成员的大小
- 当最大成员的大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍处。
union Un1
{
char c[5];
int a;
};
union Un2
{
char f;
int i;
};
int main()
{
printf("%d", sizeof(union Un1));
printf("%d", sizeof(union Un2));
return 0;
}
在union Un1中,最大成员应该是c[5],大小为5个字节,所以联合的大小至少要是五个字节,c[5]这个数组的对齐数为1(1与8比较),a的对齐数是4,所以union Un1的最大对齐数是4,所以联合的大小要为4的倍数,所以最后联合的大小为8个字节。
在union Un2中,最大成员应该是i,大小为4个字节,所以联合的大小至少要是四个字节,联合的最大对齐数为4,所以最后联合的大小为4个字节。