目录
一.结构体类型的声明
1.结构体的概念
结构体是一些值的集合,这些值成为结构体成员变量。这些变量的类型不必相同。
2.声明
(1)语法形式:
struct tag
{
member_list;
}variabal-list;
name是结构体标签;member_list是结构体成员;varialbal-list是结构体name创建的变量。
(2)结构体创建变量
struct Student
{
char name[20];//姓名
char sex[5];//性别:男、女、保密
int age;//年龄
int score;//成绩
}p1;//p1是创建的结构体变量
结构体成员的类型可以是常量、变量、指针、数组、以及其他结构体。
(3)结构体变量的初始化
- 按照顺序初始化
#include <stdio.h>
struct Student
{
char name[20];//姓名
char sex[5];//性别:男、女、保密
int age;//年龄
int score;//成绩
}p1;//p1是创建的结构体变量
int main()
{
//按照顺序初始化
struct Student p2 = { "张三","baomi",25,90 };
return 0;
}
- 按照指定顺序初始化
#include<stdio.h>
struct Student
{
char name[20];//姓名
char sex[5];//性别:男、女、保密
int age;//年龄
int score;//成绩
}p1;//p1是创建的结构体变量
int main()
{
//按照指定顺序初始化
struct Student p3 = { .age = 18, .sex = "man", .score = 97, .name = "wangwu" };
return 0;
}
当结构体按照指定顺序初始化的时候,我们必须指明结构体成员并用“=”来进行初始化。结构体成员名前面要加上"."。就像上面代码所示“ .age = 18 ”、“ .sex = "man" ”。
以上是结构体比较常见的初始化方法,结构体还有特殊的初始化方法:结构体嵌套初始化
#include<stdio.h>
struct Student
{
char name[20];//姓名
char sex[5];//性别:男、女、保密
int age;//年龄
int score;//成绩
}p1;//p1是创建的结构体变量
struct school
{
int szie;
char name[20];
struct Student p;
char location[20];
};
//结构体的嵌套初始化
struct school s = { 200, "xiwangxiaoxue", {"lisi", "baomi", 6, 84 }, "beijing" };
int main()
{
//结构体的嵌套初始化
struct school s = { 200, "xiwangxiaoxue", {"lisi", "baomi", 6, 84 }, "beijing" };
return 0;
}
结构体的嵌套初始化本质就是,一个结构的初始化包含在另一个结构体的初始化中。
3.结构体的特殊声明
在有些时候,声明结构是可以不完全声明的。我们将这种结构体称为匿名结构体
struct
{
int a;
char b;
float c;
}x;
struct
{
int a;
char b;
float c;
}a[20],*p;
对于上述两种结构,我们在声明的时候省略掉了结构体的标签,此时以上两结构体是匿名结构体。
(1)尽管上面两个结构体的成员相同,但是编译器会将他们当做两个完全不同的类型。因此,对于上面代码,我们不能写:p = &x。p和x是不同的类型.
(2)匿名结构体如果没有对结构体进行重命名的话,结构体只能用一次,不能够多次使用.
4.结构体的自引用
(1)概念
结构体自引用,就是在结构体内部,包含指向自身类型结构体点的指针。
(2)自引用方法:
- 没有typedef时
struct Node
{
int data;
struct Node next;
};
这种生命是错误的,因为这种声明是一个无限循环,成员next是一个结构体,next的内部还是一个结构体,就这样依次往下循环迭代.使得无法确定结构体的长度,而且内存空间会被无限制的往下调用.因此,这种方式是非法的。
正确方法:使用指针
struct Node
{
int data;
struct Node* next;
};
由于指针的长度是确定的(32位机器上是4;64位机器上是8),因此,编译器能够正确的确定结构体的长度.
- 有typedef时
在夹杂typedef对匿名结构体类型命名时容易出现问题.
typedef struct
{
int data;
Node* next;
}Node;
这种方法是错误的,Node我们是在结构体后面声明的,在匿名结构体内部提前使用Node来创建成员变量这是不行的,因为Node的作用于是从结构体的末尾开始的。
正确方法:不要使用匿名结构体了
typedef struct base
{
int data;
struct Node* next;
}Node;
二.结构体内存对齐
1.对其规则
- 结构体的第一个成员对齐到和结构体变量起始位置偏移量为0的地址处。
- 其他成员变量对齐到对齐数整数倍的地址处。
对齐数 = 编译器默认对齐数和该成员变量大小的的较小值.(VS中的对齐数是8;Linux的gcc中没有对齐数,对齐数就是成员自身的大小)
- 结构体的总大小为最大对齐数的整数倍.
- 如果嵌套了结构体的情况,嵌套结构体成员对齐到自己的成员中最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体中成员的对齐数)的整数倍。
2.练习
例1:
struct S1
{
char c1;
int i;
char c2;
};
printf("%d\n", sizeof(struct S1));
输出结果为:
struct S2
{
char c1;
char c2;
int i;
};
printf("%d\n", sizeof(struct S2));
输出结果为:
3.为什么存在内存对齐
(1)平台原因(移植原因)
不是所有的平台都能访问任意地址上的任意数据的,某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出异常.
(2)性能原因
数据结构(尤其是栈)应该尽可能的在自然边界上对齐.原因在于,为了访问未对齐的数,处理器要做两次内存访问;而对齐的只要做一次访问.结构体内存对齐是拿空间换取时间的做法.
我们在创建结构体类型时,为了既节省空间又节省时间,可以将占用空间小的成员尽量聚到一起。
修改默认对齐数:
当我们认为默认对齐数不合适的时候,我们可以用#pragma这个预处理指令来修改编译器的默认对齐数。
#include <stdio.h>
#pragma pack(1)//设置默认对⻬数为1
struct S
{
char c1;
int i;
char c2;
};
#pragma pack()//取消设置的对⻬数,还原为默认
int main()
{
printf("%zd\n", sizeof(struct S));
return 0;
}
输出结果为:
上述代码将默认对齐数改为了1,所以int i的最小对齐数是1,对齐到偏移量是1的位置,占4个字节,char c2的对齐数是1,对齐到偏移量为为5的位置。在该结构体中所有的对齐数都是1,所以结构体的大小是6.