1.结构体是一些值的集合,这些值被称为成员变量,成员可以是不同类型的变量
struct tag//struct为结构体关键字,tag为结构体标签
{
member list;
}varible list;//逗号不可省略
2.结构体声明
举个例子,在描述复杂对象的时候,我们往往不止对对象的一个方面进行描述。一本书有它的名字、作者、价格、编号等等;一名学生的信息包含着姓名、性别、学号等众多要素,仅用一个库内部的变量类型不足以将各个特征记录,而这种通过自定义的方式声明的结构体类型,可以很好的描述对象的各个要素。如下是关于学生信息的结构体声明:
struct stu
{
char name[20];
char sex[5];
int age;
char id[20];
};
3.匿名结构体
结构体可以不完全声明,省略掉结构体标签时,为特殊的结构体声明,称为匿名结构体,但要注意的是,匿名结构体只可使用一次。
struct
{
char name[20];
char sex[5];
int age;
char id[20];
}x;
struct
{
char name[20];
char sex[5];
int age;
char id[20];
}*p,stu1,stu2;
上述两个都是匿名结构体,是在声明时省略了结构体标签,除变量列表不同以外,其余部分都相同的匿名结构体,但是编译器会把上述两个声明当成完全不同的两个类型。
p=&x
因为类型不同,所以这行代码是非法的。
4.结构体的自引用
就是在结构体内部,包含指向自身类型结构体的指针。
在结构体内部包含一个类型为该结构体本身的一个成员是不可以的。
struct stu
{
char name[20];
char sex[5];
int age;
char id[20];
struct stu next;
};
该代码中我们无法通过sizeof( struct stu );计算出该结构体在内存中的大小,就无法在定义该结构体时在内存中开辟出合适的空间,所以在结构体内部包含一个类型为该结构体本身的一个成员是不可行的。
但是结构体内部可以包含指向自身类型的结构体指针,结构体指针在不同平台上占内存大小也只有4字节与8之分,可以计算出结构体占内存的大小。以下自引用是可行的:
struct stu
{
char name[20];
char sex[5];
int age;
char id[20];
struct stu* next;
};
同时,在自引用过程中结构体类型的名字过长,可以用typedef来重命名,命名方式如下:
typedef struct stu
{
char name[20];
char sex[5];
int age;
char id[20];
struct stu next;
}stu;
这样一来,就可以用stu来作为该结构体的类型直接使用,但需要注意的是,stu不可放在声明的结构体成员变量中:
typedef struct stu
{
char name[20];
char sex[5];
int age;
char id[20];
stu* next;//这种重命名是不可行的
}stu;
5.结构体的定义和初始化
struct stu
{
char name[20];
char sex[5];
int age;
char id[20];
}s1={"lisi", "nan", 20, "234"},s2; //在声明类型的同时定义变量s1,s2;
struct stu p; //定义结构体变量,p,s1,s2都是结构体变量,是全局变量
//结构体变量初始化
struct stu p={"zhangsan", "nan", 15, "233"};//定义变量的同时进行赋值
//结构体嵌套初始化
struct node
{
int data;
struct stu p;
struct node* next;
}n1{3, {"wangwu", "nv", 18, "222"}, NULL};
int main()
{
struct node n2={6, {"keli", "nv", 10, "123"}, NULL};//局部变量,结构体嵌套初始化
return 0;
}
赋值,当不按照顺序赋值时需要用结构体成员访问操作符" . "+成员变量名来赋值。
struct node
{
int data;
struct stu p;
struct node* next;
};
struct node ss1;
printf("%d %s %S %d %s %p",ss1.date,ss1.p.name,ss1.p.sex,ss1.p.age,ss1.p.id,ss1.next);
struct S
{
char c;
int a;
float f;
};
struct S s1={.f=3.14f, .c='b', .a=233};
printf("%c %d %f\n",s1.c, s1.a, s1.f);
6.结构体内存对齐
计算结构体内存大小首先要掌握结构体内存对齐规则。
结构体内存对齐的规则如下:
①结构体的第一个成员直接对齐到相对于结构体变量起始位置为0的偏移处;
②从第二个成员开始,要对齐到某个对齐数的整数倍的偏移处;
③结构体的总大小,必须是结构体最大对齐数的倍数;
每个结构体成员都有一个对齐数,其中最大的对齐数就是最大对齐数;
④如果嵌套了结构体的情况:嵌套的结构体对齐到自己的最大对齐数的整数倍处,
结构体的整体大小就是所有最大对齐数的整数倍(含嵌套结构体的最大对齐数)。
对齐数:结构体成员自身大小和默认对齐数的较小值
在vs环境下为8字节;linux环境默认不设对齐数,对齐数是结构体成员自身大小
举个例子:
struct S1
{
char c1;
int i;
char c2;
};
struct S2
{
char c1;
char c2;
int i;
};
猜测一下,这两个结构体大大小是怎样的?
这里我们offsetof这个宏来计算结构体成员相对于起始位置的偏移量:
offsetof(type, num);
很显然,明明内部结构体成员变量都是一样的,两个结构体的所占空间的大小并不相同,这里就是根据结构体内存对齐的规则在内存中开辟空间给结构体的。
我们不妨画个图来理解一下:
那么为什么会存在内存对齐呢?
①平台原因:某些硬件平台只能在某些地址处取得某些特定类型的数据,否则抛出硬件异常。
②性能原因:为了访问未对齐的内存,处理器需要做两次内存访问,而对齐的内存访问只需一次访问。
结构体的内存对齐是拿空间换取时间的做法。在设计结构体的时候,我们既要满足对齐,又要节省空间,可以让占空间小的成员尽量集中在一起,上述s2就比s1占空间更少。
7.修改默认对齐数
结构在对齐方式上不合适的时候,可以自己更改默认对齐数。
//设置默认对齐数
#pragma pack(1) //设置默认对齐数为1
struct S1
{
char c1;
int i;
char c2;
};
//恢复默认对齐数
#pragma pack() //取消设置的默认对齐数,还原为默认
8.结构体传参
结构体传参的时候,我们最好传结构体指针。一方面,形参是实参的一份临时拷贝,如果要对结构体进行修改,改变形参并不会影响实参;另一方面,传参如果传的是结构体指针,内存开辟的空间只有4字节或8字节,如果传的是结构体变量,内存会开辟与实参一样大小的空间给形参,占用内存空间大,影响性能。