结构体
1. 结构体类型的声明
结构体概念 :结构体是多个值的集合,这些值称为成员变量。结构体中的每个成员可以是不同类型的变量。
1.1 结构声明
结构体声明:
struct tag
{
member-list;
}variable-list;
举例:
struct Stu
{
char name[20];//名字
int age;//年龄
char sex[5];//性别
char id[20];//学号
}; //分号不能丢
1.2 结构体特殊声明
匿名结构体类型:
struct
{
int a;
char b;
float c;
}x;
匿名的结构体类型,如果没有对结构体类型重命名的话,基本上只能使⽤⼀次。
1.3 结构体自引用
在结构中包含⼀个类型为该结构本⾝的成员是否可以呢?
我们来看一个错误的例子:
struct Node
{
int data;
struct Node next;
};
认真观察发现,⼀个结构体中再包含⼀个同类型的结构体变量,这样结构体变量的⼤⼩就会⽆穷的⼤,是不合理的。
正确的自引用方式:
struct Node
{
int data;
struct Node* next;
};
再来看一个错误的例子,在结构体⾃引⽤使⽤的过程中,夹杂了typedef对匿名结构体类型重命名。这样可行吗?
typedef struct
{
int data;
Node* next;
}Node;
肯定是不行的,是因为Node是前面匿名结构体类型重命名产生的,但是在匿名结构体内部提前使用Node类型来创建变量这肯定是不可以的。
2. 结构体的创建和初始化
struct Stu //类型声明
{
char name[15];//名字
int age; //年龄
};
struct Stu s = {"zhangsan", 20};//初始化
struct tag
{
int x;
char y;
}p1;// 声明类型的同时定义变量p1
struct tag p2; // 定义结构体变量p2
struct Node
{
int data;
struct Point p;
struct Node* next;
}n1 = {10, {4,5}, NULL}; //结构体嵌套初始化
struct Node n2 = {20, {5, 6}, NULL};//结构体嵌套初始化
3.结构体访问操作符
结构成员访问操作符有两个⼀个是 . ,⼀个是 -> .
结构体变量.成员变量名
结构体指针—>成员变量名
举例:
#include <stdio.h>
#include <string.h>
struct Stu
{
char name[15];//名字
int age; //年龄
};
void print_stu(struct Stu s)
{
printf("%s %d\n", s.name, s.age);
}
void set_stu(struct Stu* ps)
{
strcpy(ps->name, "李四");
ps->age = 28;
}
int main()
{
struct Stu s = { "张三", 20 };
print_stu(s);
set_stu(&s);
print_stu(s);
return 0;
}
4.结构体内存对齐
4.1 对齐规则
- 结构体的第⼀个成员对⻬到相对结构体变量起始位置偏移量为0的地址处
- 其他成员变量要对⻬到某个数字(对⻬数)的整数倍的地址处。
对⻬数 = 编译器默认的⼀个对⻬数 与 该成员变量⼤⼩的较⼩值。
- VS中默认的值为8
- Linux中没有默认对⻬数,对⻬数就是成员⾃⾝的⼤⼩
- 结构体总⼤⼩为最⼤对⻬数(结构体中每个成员变量都有⼀个对⻬数,所有对⻬数中最⼤的)的
整数倍。 - 如果嵌套了结构体的情况,嵌套的结构体成员对⻬到⾃⼰的成员中最⼤对⻬数的整数倍处,结构
体的整体⼤⼩就是所有最⼤对⻬数(含嵌套结构体中成员的对⻬数)的整数倍。
4.1.1 练习
计算下例结构体大小:
练习1.
struct S1
{
char c1;
int i;
char c2;
};
int main()
{
printf("结构体大小为%d\n", sizeof(struct S1));
return 0;
}
解析:
首先我们看char 类型 c1 大小为1个字节 小于 VS默认对齐数 所以对齐数为 1。 int 类型 i 大小为4个字节 小于VS默认对齐数 ,所以对齐数为 4。char 类型 c2 大小为1个字节 小于 VS默认对齐数 ,所以对齐数为 1 。
根据对齐规则结构体的第⼀个成员对⻬到相对结构体变量起始位置偏移量为0的地址处 c1 对齐0地址处(红色),其他成员变量要对⻬到某个数字(对⻬数)的整数倍的地址处。 i对齐数4的整数倍所以对齐4~7地址处(蓝色),c2的对齐数位1 对齐9地址处(绿色)。
然后,结构体的大小为最大对齐数的整数倍 。4为最大对齐数 0~9 10并不是4的整数倍,因此要补齐 0 ~11 12为结构体大小。
练习2.
struct S2
{
char c1;
char c2;
int i;
};
int main()
{
printf("结构体大小为%d\n", sizeof(struct S2));
return 0;
}
还是一样结构体的第⼀个成员对⻬到相对结构体变量起始位置偏移量为0的地址处。所以c1 对齐0地址处(红色)。
其他成员变量要对⻬到某个数字(对⻬数)的整数倍的地址处,c2 对齐1地址处(橙色) ,i 的对齐数为4 因此对齐4 的整数倍 4~7地址处(绿色)。
最后结构体大小是最大对齐数的整数倍0~7 8刚好是4的整数倍,所以结构体大小为8。
练习3.
struct S3
{
double d;
char c;
int i;
};
int main()
{
printf("结构体大小为%d\n", sizeof(struct S3));
return 0;
}
还是一样结构体的第⼀个成员对⻬到相对结构体变量起始位置偏移量为0的地址处。d的对齐数为8因此对齐0~7地址处(红色)。其他成员变量要对⻬到某个数字(对⻬数)的整数倍的地址处,c 对齐8地址处(绿色)。i 对齐12 ~15地址处(蓝色)。最后结构体大小是最大对齐数的整数倍,0 ~15大小为16为最大对齐数8的整数倍。
练习4. 结构体嵌套问题
struct S4
{
char c1;
struct S3 s3;
double d;
};
int main()
{
printf("结构体大小为%d\n", sizeof(struct S4));
return 0;
}
首先还是一样结构体的第⼀个成员对⻬到相对结构体变量起始位置偏移量为0的地址处,c1对其0地址处(红色)。
根据对齐规则嵌套的结构体成员对⻬到⾃⼰的成员中最⼤对⻬数的整数倍处,结构体 s3 的大小为16 对齐到8~23地址处(蓝色),double类型 d对齐到24 ~ 31地址处(绿色 )。
最后结构体的整体⼤⼩就是所有最⼤对⻬数(含嵌套结构体中成员的对⻬数)的整数倍,s4中最大对齐数为8 ,0~31 大小32为8的整数倍。
4.2为什么存在内存对⻬?
⼤部分的参考资料都是这样说的:
- 平台原因(移植原因):
不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。 - 性能原因:
数据结构(尤其是栈)应该尽可能地在⾃然边界上对⻬。原因在于,为了访问未对⻬的内存,处理器需要作两次内存访问;⽽对⻬的内存访问仅需要⼀次访问。假设⼀个处理器总是从内存中取8个字节,则地址必须是8的倍数。如果我们能保证将所有的double类型的数据的地址都对⻬成8的倍数,那么就可以⽤⼀个内存操作来读或者写值了。否则,我们可能需要执⾏两次内存访问,因为对象可能被分放在两个8字节内存块中。
总体来说:结构体的内存对⻬是拿空间来换取时间的做法。