目录
结构体声明
结构体是一些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量。
struct tag
{
member-list;
}variable-list;
结构体是个自定义类型,以int a来对比,其实上面代码中的variable-list就相当于a,其他部分是一个结构体类型,可以类比int。
匿名结构体类型
结构在声明的时候省略掉了结构体标签(tag)
匿名结构体类型,一般都要加上variable-list,比如下面代码中的“x”。
匿名结构体变量只能在这里用一次
如果有两个匿名结构体变量,哪怕他们成员一样,但在编译器看来也是不同的两种类型。
struct
{
int a;
char b;
float c;
}x;
结构的自引用
在结构中包含一个类型为该结构本身的成员,在数据结构中使用比较多(线性数据结构链表)
struct Node
{
int data;
struct Node next;
//这种写法是有问题的,因为这个next结构体里又有一个结构体,会无限循环下去
};
正确自引用的写法如下:
struct Node
{
int data;//数据域
struct Node* next;//指针域,指向下一个这样的节点的地址
};
为了写起来不那么繁琐,通常使用typedef,对这个类型重命名,这样下次就可以不写struct
typedef struct Node
{
int data;
struct Node* next;
}Node;//对这个类型重命名为Node
struct Node n1;
Node n2;
//这时候n1和n2就是一样的
对结构体指针重命名
typedef struct Node
{
int data;
struct Node* next;
}* pNode;
//struct Node* 现在就可以写成 pNode
结构体初始化
struct Point
{
int x;
int y;
}p1; //声明类型的同时定义变量p1
struct Point p2; //定义结构体变量p2
struct Point p3 = {x, y};//初始化:定义变量的同时赋初值。
结构体嵌套初始化
struct Node
{
int data;
struct Point p;
struct Node* next;
}n1 = {10, {4,5}, NULL}; //结构体嵌套初始化
struct Node n2 = {20, {5, 6}, NULL}; //结构体嵌套初始化
结构体成员的访问
以下面这个结构体为例:
struct Stu
{
char name[20];
int age;
};
struct Stu s;
结构体变量访问成员
结构变量的成员是通过点操作符(.)访问的。点操作符接受两个操作数。
strcpy(s.name, "zhangsan");//使用.访问name成员
s.age = 20;//使用.访问age成员
结构体指针访问指向变量的成员
void print(struct Stu* ps)
{
printf("name = %s age = %d\n", (*ps).name, (*ps).age);//方法1
printf("name = %s age = %d\n", ps->name, ps->age);//方法2
}
方法·1:对结构体地址解引用找到这个结构体,再通过"."操作符访问成员
方法·2:使用"->",通过结构体地址指向成员名,访问成员。
结构体传参
结构体传参可以传结构体的地址,也可以直接传结构体;但考虑到直接传结构体会在栈区开辟一块和该结构体同样大小的空间,造成了栈空间的浪费,故一般使用结构体地址传参。
int main()
{
struct Stu s = {"zhangsan", 20};
Function(&s);//结构体地址传参
return 0;
}
结构体内存对齐
为什么存在内存对齐?
1. 平台原因(移植原因):
不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。2. 性能原因:
数据结构(尤其是栈)应该尽可能地在自然边界上对齐。
原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。
结构体的对齐规则:
1. 第一个成员在与结构体变量偏移量为0的地址处。
2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。
VS中默认的值为8;Linux环境下没有默认对齐数,自身的大小就是对齐数。VS中的默认对齐数可以通过#pragma pack();来进行修改。
3. 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
来几个例子(计算结构体的大小):
struct S1
{
char S1_1;//1字节,<8所以对齐数为1,在0偏移量处占1字节(0)
int S1_2; //4字节,<8所以对齐数为4,(1、2、3)偏移处的空间没有使用,在4偏移量处占4字节(4、5、6、7)
char S1_3;//1字节,<8所以对齐数为1,在8偏移处占1字节(8)
//然后根据第3条规则,结构体总大小为最大对齐数(4)的整数倍,所以(9、10、11)偏移处的空间没有使用
//所以S1占12字节
};
struct S2
{
char S2_1;//1字节,<8所以对齐数为1,在0偏移量处占1字节(0)
char S2_2;//1字节,<8所以对齐数为1,在1偏移量处占1字节(1)
int S2_3; //4字节,<8所以对齐数为4,(2、3)偏移处的空间没有使用,在4偏移量处占4字节(4、5、6、7)
//所以S2占8字节
};
struct S3
{
char S3_1; //1字节,<8所以对齐数为1,在0偏移量处占1字节(0)
struct S2 S3_2;//8字节,最大对齐数(4),所以对齐到4的整数倍处(4),在4偏移量处占8字节(4、5、6、7、8、9、10、11)
double S3_3; //8字节,=8所以对齐数为8,所以对齐到8的整数倍处(16),在16偏移量处占8字节(16、...、23)
//所以S3占24字节
};
总体来说:
结构体的内存对齐是拿空间来换取时间的做法。所以设计结构体的时候,我们既要满足对齐,又要节省空间时要做到:让占用空间小的成员尽量集中在一起,比如例子中的S2和S1的成员一样,但S2比S1少占用4个字节。
补充一个宏:
#include <stddef.h>
size_t offsetof(structName, memberName);
//计算结构体成员相对于起始位置的偏移量,size_t为无符号整形