C语言几种特殊的自定义类型
结构体
结构体的创建
可以在创建结构体的时候添加结构体的标签tag,用来为该结构体取唯一的名字,以便于和其它结构体区分开。
struct tag{
member_list;
} variable_list;
也可以在创建结构体的时候不加标签,声明为一个匿名结构体。如:
struct
{
char name[20];//名字
int age;//年龄
char sex[5];//性别
char id[20];//学号
}; //分号不能丢
在创建匿名结构体的时候要注意:如果是两个内容完全一样的结构体声明,编译器还是会把它们当做是两个不同类型的结构体。
结构体的menber_list中可以是标量、数组、指针、甚至是其它结构体,但不能是该结构体本身。
结构体的大小
一般而言,结构体的大小比结构体中所有成员的大小加起来还要大。至于为什么会这样? 大多少呢?这就涉及到一个知识点:结构体的内存对齐。
内存对齐
结构体的内存对齐是用空间换取时间的做法:为了访问未对齐的内存,处理器需要做两次内存访问;二对齐的内存只需一次内存访问。另一方面是考虑了某些平台不支持访问任意地址上任意数据的原因。
以下是结构体的对齐规则:
1. 第一个成员在与结构体变量偏移量为0的地址处。
2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。对齐数 = 编译器默认的一个对齐数与该成员大小的较小值。 VS中默认的值为8 Linux中的默认值为4
3. 结构体总大小为最大对齐数(每个成员变量除了第一个成员都有一个对齐数)的整数倍。
4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数(嵌套结构体内部的最大对齐数)的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
以下是几个例子:
例1:
struct S1
{
char C1; //1
int i; //4
char C2; //1
}; //结构体总大小为12
图解:
例二:
struct S2
{
char c1; //1
char c2; //1
int i; //4
}; //结构体总大小为8
图解:
例三:
struct S3
{
double d; //8
char c; //1
int i; //4
}; //结构体总大小为16
struct S4
{
char C1; //1
struct S3 s3; //大小为16,对齐为8
double d; //8
}; //结构体总大小为32
图解:
结构体的初始化
对结构体初始化,不能在结构体内部进行。比如:
struct
{
int x = 10; //错误的方式
int y = 0; //错误的方式
};
应该在定义了一个结构体变量后初始化,或者在定义结构体变量时初始化。就像对数组初始化一样。
结构体的调用
结构体是一个聚合数据类型,调用它时实际上调用的是结构体内部的成员。“.”表示对结构体内部成员的调用;“(a[i]).”表示对结构体数组a中第i个结构体内部成员的调用。若p是一个指向结构体的指针,那么对结构体成员的调用方式为“(p).”可简化为“p->”。
位段
位段的创建
- 位段的成员必须是 int、 unsigned int 或signed int。
- 位段的成员名后边有一个冒号和一个数字,这里的数字表示该变量所用的二进制位数。
比如:
struct A
{
int _a:2; //变量_a占2个比特位
int _b:5; //变量_b占5个比特位
int _c:10; //变量_c占10个比特位
int _d:30; //变量_d占10个比特位
};
位段的大小
- 位段的成员可以是 int unsigned int signed int 或者是 char (属于整形家族)类型
- 位段的空间上是按照需要以4个字节(int )或者1个字节(char )的方式来开辟的。
- 位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段。
struct A
{
int _a:2;
int _b:5;
int _c:10;
int _d:30;
}; //位段A的大小为8个字节
位段不存在内存对齐
图示:
位段的初始化
位段的初始化和调用方式与结构体一样
位段的调用
位段的初始化和调用方式与结构体一样
位段的跨平台问题
跟结构相比,位段可以达到同样的效果,但是可以很好的节省空间,但是有跨平台的问题存在。
1. int位段被当成有符号数还是无符号数是不确定的。
2. 位段中最大位的数目不能确定。(16位机器最大16, 32位机器最大32,写成27,在16位机器会出问题。)
3. 位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。
4. 当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的
联合体
类型定义的变量也包含一系列的成员,特征是这些成员公用同一块空间(所以联合也叫共用体)
联合体的创建
声明联合体与声明结构体类似:
union Un
{
char c;
int i;
};
联合体的大小
联合的大小至少是最大成员的⼤⼩。
当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。
例子:
union Un1
{
char c[5];
int i;
}; //联合体Un1的大小为8
union Un2
{
short c[7];
int i;
}; //联合体Un2的大小为16
联合体的初始化
联合体的初始化和调用方式与结构体一样
联合体的调用
联合体的初始化和调用方式与结构体一样
可根据联合体的特点,进行系统大小端检验,可见大小端检测
联合体的妙用:转化IP地址
union ip_addr
{
unsigned long addr;
struct
{
unsigned char c1;
unsigned char c2;
unsigned char c3;
unsigned char c4;
}ip;
};
枚举类型
枚举类型使用关键字:enum
例如:
enum Day//星期
{
Mon,
Tues,
Wed,
Thur,
Fri,
Sat,
Sun
};
enum Sex//性别
{
MALE,
FEMALE,
SECRET
};
enum Color//颜⾊
{
RED,
GREEN,
BLUE
};
枚举类型的成员叫做枚举常量,每一个枚举变量的取值范围只能是对应枚举类型中的所有枚举常量。(也可以是数字,但取值为数字的话就失去了枚举的意义)。
比如:
enum Color
{
RED,
GREEN,
BLUE
};
int main()
{
enum Color cloth;
cloth = RED;
return 0;
}
这里的cloth的取值可以是RED、GREEN、BULE,但不能是red、green、blue、yellow等不是枚举常量的值。可以将cloth赋值为1、2、3、345等数字,但这样就没有了枚举存在的意义。
枚举变量
枚举变量是变量,是声明一个枚举类型后定义的变量。
比如:
enum Color
{
RED,
GREEN,
BLUE
};
int main()
{
enum Color cloth; //这里的cloth就是一个枚举变量
cloth = RED;
return 0;
}
枚举常量
枚举常量是常量。是声明枚举类型时枚举类型内部的成员。枚举常量可以看做是整数,默认从0开始
enum Color
{
RED,
GREEN,
BLUE
};
int main()
{
enum Color cloth;
cloth = RED;
printf("%d\n", cloth); //结果为0
printf("%d\n", GREEN); //结果为1
printf("%d\n", BLUE); //结果为2
return 0;
}
可以在声明枚举类型时改变枚举常量默认的整数。
enum Color
{
RED,
GREEN = 5,
BLUE
};
int main()
{
enum Color cloth;
cloth = RED;
printf("%d\n", cloth); //结果为0
printf("%d\n", GREEN); //结果为5
printf("%d\n", BLUE); //结果为6
return 0;
}