目录
一、是什么
二、三种结构体声明
①普通的声明
②自引用
③特殊的声明——匿名的结构体
三、三种初始化方法
四、三种解引用方法
五、结构体内存对齐(重点)
六、内存对齐的意义
七、修改默认对齐值
八、获取偏移值
九、结构体传参
一、是什么
结构体是一种自定义类型,用来存放多种类型的数据,使我们得以轻松描述诸如人、书本这些较为复杂的对象。
二、三种结构体声明
①普通的声明
struct book
{
int date;
float price;
char name[10];
char*ps;
struct content c;
}book1;
typedef struct book
{
int date;
float price;
char name[10];
char*ps;
struct content c;
}book;
1.struct+后面的tab(标签)组成我们创建的变量名。里面可以放入任何类型的变量,甚至是指针和另一个结构体。图一中book1是我们在定义时顺手定义的一个类型为struct book的变量
2.图二我们将struct book这个类型名重命名为book,这里并没有定义一个变量,注意区分。
②自引用
struct book
{
int date;
struct book b;
}book1;
但注意结构体不能“调用”自己 ,如上面的例子。我们可以类比递归函数的形式,图中无疑是一个“死循环”,根本无法计算他的大小,当然编译器的语法也不通过。要想实现结构体的自引用,我们可以传入一个指针,实现链表一个指向下一个的效果。如下图:
struct Node
{
int data;
struct Node*pn;
};
③特殊的声明——匿名的结构体
有时候在使用结构体的时候我们直接省去了tag,如下图,在一定程度上简化了我们的代码
struct content
{
char name[10];
char id[20];
int age;
struct
{
int arr[10];
};
}s1,s2,s3;
虽然匿名结构体没有名字,但编译器也是坚决把他们看成两个不同的类型,如下图。虽然*px指向的类型看似和x一样,但编译器坚决认为两者是不同的,所以px=&x是错误的书写。
struct
{
int price;
}x;
struct
{
int price;
}y,*px;
三、三种初始化方法
//声明结构体
struct content
{
int page;
char* character;
};
typedef struct book
{
float price;
char name[20];
struct content c;
}b;
typedef struct book1
{
float price;
char* name;
struct content c;
}b1;
int main()
{
//方法一:定义时赋值
b book1 = { 10.0, "c++", {100,"bit"} };
//方法二:先定义后赋值
b1 book2 = {0};
book2.price = 10.0;
book2.name = "c++";
book2.c.page = 100;
book2.c.character = "bit";
//定义时乱序赋值
b1 book3 = {
.name = "c++",
.c.page = 100,
.c.character = "bit",
.price = 10.0
};
}
值得注意的是我们不能用数组来接收例如“c++”,因为常量字符串实际传入的是首元素的地址,所以我们用指针接收。
四、三种解引用方法
struct book
{
int price;
char name[10];
};
int main()
{
struct book b = { 10, "hello" };
struct book*ps = &b;
//方法一:->
printf("%d\n",ps->price);
//方法二:.
printf("%d\n",b.price);
//方法三:本质等同方法二
printf("%d\n",(*ps).price);
return 0;
}
五、结构体内存对齐(重点)
1.第一个成员在结构体变量偏移量为0的地址处。
2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。
VS中默认的值为8
Linux中的默认值为4gcc下没有默认值
3. 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的 整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
以下面的例子分析规则一二三。(结果分别为8,12)
struct s1
{
char a;
char b;
int c;
};
struct s2
{
char a;
int b;
char c;
};
int main()
{
struct s1 s1 = { 0 };
struct s2 s2 = { 0 };
printf("%d\n",sizeof(s1));
printf("%d",sizeof(s2));
}
先来分析s2吧
①a作为第一个成员,创建在结构体偏移量为0的地址,即结构体的起始地址。占用内存一字节。
②b作为第二个成员要考虑偏移量的问题。b的类型int 的大小4<8,所以对齐数为4。对齐在最后未被利用的空间里找一块地址,其偏移量为对齐数的整数倍。
③c作为第三个成员。c的类型为1,偏移量8当然是1的倍数啦,所以c随即在b的后方开辟空间。但由于规则三,现在的总大小为9,并不是最大对齐数(每个成员的对齐数之间)4的倍数,所以又在后面浪费了3个字节使得总字节为12。
同样的道理分析s1,我们发现是s1的大小是8字节。同样的内容却占用了不同大的空间?我们由此得出以下结论:
为了减小整体所占空间,应该使占用空间小的成员集中在一起
再分析一个内嵌结构体的例子吧!
struct s1
{
char a;
char b;
int c;
};
struct s2
{
char a;
char b;
struct s1 c;
};
int main()
{
struct s1 s1 = { 0 };
struct s2 s2 = { 0 };
printf("%d\n",sizeof(s1));
printf("%d",sizeof(s2));
}
相同的分析不再赘述,分析一下内嵌结构体。内嵌结构体的最大对齐数是4,整个结构体的最大对齐数也是4,由此我们画出以下的内存示意图。
内嵌结构体第一个成员进行内存对齐(对齐数为内嵌结构体内对齐数的最大值),之后的照123规则对齐。
六、内存对齐的意义
由于官方没有给出明确的规定,我们来学习以下两个比较有说服力的理由
1. 平台原因(移植原因): 不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
2. 性能原因: 数据结构(尤其是栈)应该尽可能地在自然边界上对齐。 原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。
想必原因一通俗易懂,我们来解释一下原因二。
我们知道我们的电脑为32/64根地址线,换算成字节就是4/8字节,以4个字节进行说明。内存每四个字节四个字节进行访问时,若结构体的内存对齐为四个字节,则内存可以顺畅的一次性访问,若没有对齐,为了完整访问,需要两次,这就降低了效率
总的来说,就是用空间来换取效率
七、修改默认对齐值
我们可以采用预处理指令pragma来修改,如以下的案例:打印结果先后为5 8。
#pragma pack(1)//将默认对齐数改为1;
struct book1
{
char c;
int a;
};
#pragma pack()//将默认对齐数改为默认值(8)
struct book2
{
char c;
int a;
};
int main()
{
struct book1 b1 = { 0 };
struct book2 b2 = { 0 };
printf("%d\n",sizeof(b1));
printf("%d\n", sizeof(b2));
}
八、获取偏移值
我们可以采用宏“offsetof”实现,头文件为<stddef.h>。
#pragma pack(1)//将默认对齐数改为1;
struct book1
{
char c;
int a;
};
#pragma pack()//将默认对齐数改为默认值(8)
int main()
{
struct book1 b1 = { 0 };
printf("%d\n",offsetof(struct book1,c));
printf("%d\n",offsetof(struct book1,a));
}
我们也可以用offsetof来验证修改默认对齐值的情况。
九、结构体传参
结构体传参有传址操作和传参操作之分,但当我们在选择时,如果需要修改结构体,必须选择传址操作;若不用修改,也应该尽可能选择传址操作。
原因一:传址才可以改变结构体的内容。
原因二:传参需要压栈,会带来内存和效率上的损耗,若结构体过大,会导致程序性能下降
若用传址的方法,但不想结构体被改变,可以采用const修饰。
以下用一个简单的函数分别演示传址和传参操作。
struct book
{
int page;
float price;
};
void print1(struct book b1)
{
printf("%d\n",b1.page);
}
void print2(const struct book*ps)
{
printf("%d\n", ps->page);
}
int main()
{
struct book b1 = {100,15.0};
print1(b1);
struct book*ps = &b1;
print2(ps);
return 0;
}
很厉害嘛,坚持看到这里,希望对你有所帮助!