结构体是一些不同类型变量的集合
struct tag
struct tag
{
member - list;
}variable - list;
举个例子
struct Stu
struct Stu
{
char name[20];//姓名
int age;//年龄
char sex[5];//性别
char id[15];//学号
};//这个分号不能丢
在声明结构体时可以不声明结构体的标签(tag)
struct
struct
{
char a;
int b;
float c;
}x;
struct
{
int a;
char b;
float c;
} * p;
如果省略了标签,则必须在定义结构体时就声明变量。否则无法使用该结构体(因为该结构体没有名字所以你在其他地方无法创建该结构体变量)。
对于上面代码,进行下面操作可以吗?
p = &x;
答案是不行的
编译器会把这两结构体当成两种完全不同的类型
struct Node
struct Node
{
int dada[20];
struct Node next;
};
这段代码正确吗?
显然是错误的。
此时我们正在定义结构体(构造)。想要定义struct Node这个变量,至少要等到将结构体创建完成后,才能创建其变量。
定义结构体时要确定其需要多大空间,如果结构体不完整,就无法确定它需要多大内存空间。所以在定义结构体时不能定义自身的普通变量
那么如何做,才可以自引用呢?
struct Node
struct Node
{
int dada[20];
struct Node *next;
};
这样就是正确的
肯定有不少人有疑问,为啥变成指针变量就可以了呢?
你在定义自身的指针时分配的内存空间是确定的(32位指针变量大小是4字节,64位是8字节)存放的是struct Node的地址,在分配内存时不需要知道stuct Node 的结构,只需要知道struct Node 中有这个指针就可以了。
定义
struct Point
struct Point
{
int x;
int y;
}p1; //声明类型的同时定义变量p1 -----第一种
struct Point p2; //定义结构体变量p2 -----第二种
初始化
struct Stu //类型声明
struct Stu //类型声明
{
char name[15];//名字
int age; //年龄
}s1 = {"lisi",18}; -----第一种
struct Stu s2 = { "zhangsan", 20 };//初始化 ----第二种
struct Node
{
int data;
struct Point p;
struct Node* next;
}n1 = { 10, {4,5}, NULL }; //结构体嵌套初始化 ----第一种
struct Node n2 = {20, {5, 6}, NULL};//结构体嵌套初始化 ----第二种
我们已经会定义和初始化结构体了。
那么一个结构体的大小该怎么算呢?
#include
#include
struct S1
{
char a;
int c;
char b;
};
int main()
{
printf("%d", sizeof(struct S1));
return 0;
}
看看上面的代码,大家觉得会显示什么?
有人会说是6 ----这个答案是错误的,可以再往下看看,你就懂了
也会有人说12 ----这个答案是正确的,但你不一定是正确的理解
因为有一次我想知道结构体内存如何算,便去百度了一下,看了一些人是这样说的
先判断结构体中有多少成员,再找出成员中所占空间最大的是多大。
结构体内存大小 = 成员数量*成员中最大的字节数 错误
就比如上面的例子:
3个成员,最大字节是int型(4个字节) 结构体大小 = 3*4 = 12
我当时什么都不知道,觉得挺对,因为当时确实算正确了。
但是我想说:这种方法所错误的,你算对了只能说是巧合
在看一个代码
#include
#include
struct S1
{
char a;
char b;
int c;
};
int main()
{
printf("%d", sizeof(struct S1));
return 0;
}
其实和上面代码差不多,只是换了一下结构体中的顺序,猜猜看输出的还是12吗
肯定不是,不然我还举这个例子干什么
这个输出的是8,好奇就往下看,看完就懂了。
首先要掌握内存对其的规则
第一个成员在与结构体变量偏移量为0的地址处。
其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。
VS中默认的值为8
结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整
体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍
文字看完便看图理解吧,文字总没有图片直观吧
看完这个,再来看看另外一个代码的内存分布吧。
图就是这样希望大家自己去好好理解理解
还没有讲规则4,应为上面这些例子没有设计嵌套结构体,下面看看下一例子
以上就是结构体的内存对其全部内容,从此你在也不用担心算错了。
不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则会出现硬件异常。
数据结构(尤其是栈)应该尽可能地在自然边界上对齐。
原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问
总的来说:
就是以空间换取时间
那在设计结构体的时候,我们既要满足对齐,又要节省空间,如何做到:
让占用空间小的成员尽量集中在一起。
struct S1
struct S1
{
char c1;
int i;
char c2;
}; //12
struct S2
{
char c1;
char c2;
int i;
}; //8
S1和S2类型的成员一模一样,但一个占8个字节,一个占12字节
如何修改默认对齐数呢?
要通过#pragma这个预处理指令,可以改变我们的默认对齐数。
但修改的默认对齐数必须是2 ^ n( n>=0)
结论:
结构在对齐方式不合适的时候,我么可以自己更改默认对齐数。
我们知道的传参方式有两种:1.值传递 2.地址传递 在结构体中也一样有两种。
话不多说看代码
#include
#include
struct S
{
int arr[100];
int a;
}s ={{0},100};
void test1(struct S s) //值传递
{
printf("%d\n", s.a);
}
void test(struct S* s) // 地址传递
{
printf("%d\n", s->a);
}
int main()
{
test1(s);
test2(&s);
return 0;
}
两种方法,都可以达到目的。哪一种更好呢?
第一种,如果用值传递的话,形参是实参的一份临时拷贝,形参会占用很大的空间,在时间和空间上都有很大的 开销
第二种,如果用值传递,不需用进行开辟空间进行临时拷贝,只需要存储地址,不会占用大部分空间,只占用一个指针变量的空间,时间上也会快很多,大大降低了时间和空间的开销
综上所述:结构体传参时,用地址传递更好。
位段的声明和结构是类似的,有两个不同:
1.位段的成员必须是 int、unsigned int 或signed int 。
2.位段的成员名后边有一个冒号和一个数字。
数据类型 成员名 :整数(不能大于int的字节数)
看例子
struct A {
struct A
{
int _a : 2;
int _b : 5;
int _c : 10;
int _d : 30;
};
冒号后面的数字代表,占几个比特位
那么位段的大小是多少呢?
位段的成员可以是 int ,unsigned int ,signed int 或者是 char (属于整形家族)类型
位段的空间上是按照需要以4个字节( int )或者1个字节( char )的方式来开辟的。
位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段。
从上面这幅图,我们可以看出他是如何存储的。
跟结构相比,位段可以达到同样的效果,但是可以很好的节省空间,但是有跨平台的问题存在。
int 位段被当成有符号数还是无符号数是不确定的。
位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32,写成27,在16位机 器会出问题。
位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。
当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是 舍弃剩余的位还是利用,这是不确定的
那位段有这么多问题为什么要使用位段呢?
位段可以大大更好的利用内存,不造成浪费。
上图是一个数据包的个个部分,这里应用位段,可以大量的节省内存。
比如:四个版号位,完全可以用位段,4个比特位,可以大大减少内存。