结构体
含义:聚合数据类型,能够同时存储超过一个的单独数据。结构体是一些值的集合,这些值称为成员,成员可以是不同的数据类型。
- 定义结构体
//头部定义
struct TypeName
{
类型 成员;
...;
};
//尾部定义,也叫匿名结构体
struct
{
类型 成员;
....;
} Name = {val1,val2,val3};
//错误示例
//在C++中正确,C中错误
//在定义结构体时,未创建对象,没有给结构体分配内存,因此没有空间赋值。
struct TypeName
{
int val1 = 200;
char val2 = 'A';
};
//错误示例
//错误原因同上
struct TypeName
{
static int val1;
register int val2;
auto int val3;
};
- 声明结构体变量
struct TypeName val1;
- 初始化
// 成员类型顺序必须正确
struct TypeName 变量名 = {value1,value2,...};
//顺序可改变
struct TypeName 变量名 = {
.member1 = value1;
.member2 = value2;
...
};
//相同数据类型可直接赋值
struct TypeName val1 = val2;
//结构体指针创建动态对象
struct TypeName *p = (struct TypeName*)malloc(sizeof(struct TypeName));
- 结构体类型重命名
声明结构体变量时,结构体类型前都要加 struct ,较为繁琐,可以给结构体取别名。这样就省去了加struct
//方法一
typedef struct TypeName
{
...
} TypeName;
//方法二
typedef struct
{
...
} TypeName;
注意点,方法一中,如下两种声明变量是正确的。
//正确
struct TypeName val1;
//正确
TypeName val2;
方法二,第一中声明变量是错误的。方法二是匿名结构体,根本就没有TypeName这种结构体类型,因此不能用一种方法声明。
//错误
struct TypeName val1;
//正确
TypeName val2;
如果是链表类型,一个节点指向下一个节点,C语言中,下面的示例是错误的。类型名直到声明末尾才定义,在结构体内部,它尚未完成定义。因为在取别名之前,得先有一个名,在取名时,不能用你的别名。
typedef struct TypeName_Node
{
int val1;
char val2;
TypeName* next;
}Typename;
解决方案:
typedef struct TypeName_Node
{
int val1;
char val2;
struct TypeName_Node* next;
} TypeName;
- 结构体成员的排列
结构体成员排列的顺序会影响结构体字节数。系统为了快速访问结构的成员,会对结构体的成员内存排列进行对齐和补齐。
32位CPU读取数据时,一次读取4字节,也就是32位,如果一个int
类型的变量A,存储以0x01开头的地址,那么它所占的内存地址为0x01 - 0x04,也就是说,一部分存储在0x00 - 0x03区间,一部分存储在0x04 - 0x07区间。CPU读取A时,先读取0x00 - 0x03部分, 再读取0x04 - 0x07部分,这样分两次读取。如果直接存储在能整除4的地址,那么只需要读一次。
-
对齐:
假定第一个成员使用0地址,所有成员使用的内存地址必须被它所占的字节数整除(大于4则以4为基准),如果不能则补充空字节。
-
补齐:
结构体的总字节数,必须是它最大成员字节数的整数倍,如果不是,补充空字节
注意:Linux系统在计算对齐,补齐时,成员的字节超过4则按4计算
struct X
{
char a;
int b;
char c;
}
main()
{
// 12
printf("%d\n",sizeof(struct X));
}
- 第一个成员为
char
类型,占一个字节,地址为0x00- 第二个成员为
int
类型,由于0x01地址不能整除sizeof(int)
4字节,为了对齐,往后补三个字节,地址为0x04- 第三个成员为
char
类型,任何数都能整除1,故地址为0x08- 到此,结构体大小为9字节,最大类型为
int
类型,4字节,不是它的倍数,往后补3个字节,共12字节
struct X
{
int a;
char b;
char c;
}
main()
{
// 8
printf("%d\n",sizeof(struct X));
}
struct X
{
char a[21];
int b;
double c;
};
main()
{
// 36
printf("%d\n",sizeof(struct X));
}
- 第一个成员大小为21个字节,假设地址初始为0,成员1所占地址为0-20
- 第二个成员为
int
类型,21不能整除4,往后补3个到24,地址24整除4,故成员2所占地址为24-27- 第三个成员为
double
类型,8字节大于4字节,以4字节为基准,地址28能整除4,故成员3所占的地址为28-35
struct X
{
char a[21];
int b;
double c;
short d;
};
main()
{
// 40
printf("%d\n",sizeof(struct X));
}
- 第一个成员大小为21个字节,假设地址初始为0,成员1所占地址为0-20
- 第二个成员为
int
类型,21不能整除4,往后补3个到24,地址24整除4,故成员2所占地址为24-27- 第三个成员为
double
类型,8字节大于4字节,以4字节为基准,地址28能整除4,故成员3所占的地址为28-35- 第四个成员类型为
short
类型,2字节,地址36能整除2,成员4所占的地址为36-37- 至此,结构体所占字节数为38个,最大类型大小为
8
字节超过4按4来算,38不能整除4,往后补2个字节,共40字节。
联合
含义:是一种由程序设计的一种数据类型,使用语法和结构一样,只是成员的排列方式不同,所有成员共用一块内存,一个成员的值发生改变,其他成员的值,也会跟着变化。联合使一块内存对应多个标识符,达到节约内存的目的。
union TypeName
{
int val1;
char val2;
...
};
联合的成员是天然对齐的,但是有内存补齐。
使用联合判断系统是大端系统还是小端系统。
union X
{
char ch;
int num;
}
int main()
{
union X x;
x.num = 0x0a0b0c0d;
if (x.ch == 0x0a)
{
printf("小端")
}
else
{
printf("大端")
}
}
大端系统:低位数据存储在高位地址
小端系统:低位数据存储在低位地址
一般个人计算机使用的是小端系统,服务器、网络设备使用的是大端,大端字节序也叫网络字节序。
枚举
含义:枚举是一种特殊的整形,它是把一个整形数据可能出现的值全部罗列出来,除此之外不应该再使用其他的值,保证安全性。如果第一个枚举未赋值,那么默认为0,如果后面还有成员,那么往后递增1。
//枚举类型定义
enum Direction{Up,Down,Left,Right};
//匿名枚举,只使用枚举值
enum{Up,Down,Left,Right};
枚举值是常量,可以直接当常量使用,可使用在case
语句的后面或者判断语句中,不用再写字面值常量,从而提高程序的可读性。
main(){
//也可以定义在main函数外
enum {Up,Down,Left,Right};
switch(getchchar()){
case Up: break;
case Down: break;
case Left: break;
case Right: break;
}
}