今天来复习一下自定义类型中结构体类型的内容,并且深度分析
结构体
struct tag
{
member-list;
}variable-list;
struct Stu
{
//成员变量
char name[20];
int age;
float weight;
}s4,s5,s6;//全局变量
int main()
{
struct Stu s1;//局部变量
struct Stu s2;
struct Stu s3;
return 0;
}
结构体的特殊声明
//匿名结构体类型
struct
{
char c;
int a;
double b;
}s1,s2;
struct
{
char c
int a;
double b;
}*ps;
int main()
{
s2;//只能用结构体创建好的全局变量
//ps = $s1;//err
return 0;
}//编译器会把上面的声明当作完全不同的两个类型
结构体的自引用
struct Node
{
int data;
struct Node*next;
};
int main()
{
struct Node n1;
struct Node n2;
n1.next = &n2;
}//链表
//在一个结构体成员中存储的地址访问另一个结构体
结构体重命名
typedef struct
{
int data;
char c;
}S;//这里的S是结构体类型而不是全局变量
typedef struct
{
int data;
Node*next;
}Node;
//这样写存在问题,Node被调用,而定义结构体类型一开始未提及
//解决方案:
typedef struct Node
{
int data;
struct Node* next;
}Node;
结构体初始化
#include<stdio.h>
struct S
{
int a;
char c;
}s1;
struct S s3;
struct B
{
float f;
struct S s;
};
int main()
{
struct S s2 = {100,'q'};//默认的初始化方式
struct S s3 = {.c = 'r',.a = 2000};//按照想法初始化
struct B sb = {3.14f,{200,'w'}};//默认的初始化
printf("%f,%d,%c\n",sb.f,sb.a,sb.s.c);
return 0;
}
#include<stdio.h>
struct S
{
char name[100];
int*ptr;
};
int main()
{
struct S s = {"David",NULL};
return 0;
}
结构体传参
struct S
{
int arr[100];
int n;
};
void print1(struct S ss)
//struct S是类型
{
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", ss.arr[i]);
}
printf("\n%d\n", ss.n);
}
void print2(struct S* ps)
{
for (int i = 0; i < 10; i++)
{
printf("%d ", ps->arr[i]);
}
printf("\n%d\n ", ps->n);
}
int main()
{
struct S s = { {1,2,3,4,5},100 };
print1(s);
print2(&s);
return 0;
}
上面的 print1 和 print2 函数哪个好些?
答案是:首选 print2 函数。
原因:函数传参的时候,参数是需要压栈的。
如果传递一个结构体对象的时候,结构体过大,参数压栈的的系统开销比较大,所以会导致性能的下降。
结论:
结构体传参的时候,要传结构体的地址。
结构体内存对齐
struct s1
{
int a;
char c;
};
struct s3
{
char c1;
int a;
char c2;
};
struct s3
{
char c1;
int a;
char c2;
char c3;
}
int main()
{
printf("%d\n",sizeof(struct s1));
printf("%d\n",sizeof(struct s2));
printf("%d\n",sizeof(struct s3));
}
//8 8 16
1.结构体的第一个成员永远放在0偏移处
2.从第二个成员开始,以后每个成员都要对齐到某个对齐数的整数倍处,这个对齐数是成员自身大小和默认对齐数的较小值
备注:VS环境下默认对齐数是8
gcc环境下没有默认对齐数,没有默认对齐数时,对齐数就是成员自身大小
3.当成员全部存放进去后
结构体的总大小必须是,所有成员的对齐数中最大对齐数的整数倍,如果不够,则浪费空间对齐。
4.如果嵌套了结构体,嵌套的结构体成员要对齐到自己成员的最大对齐数的整数倍处,整个结构体的大小,必须是最大对齐数的整数倍,最大对齐数包含中嵌套的结构体成员的对齐数。
为什么存在内存对齐?
1. 平台原因(移植原因):
不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
2.性能原因:
数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问 。
总体来说:
结构体的内存对齐是拿空间来换取时间的做法
那在设计结构体的时候,我们既要满足对齐,又要节省空间,如何做到:
让占用空间小的成员尽量集中在一起
//例如:
struct S1
{
char c1;
int i;
char c2;
};
struct S2
{
char c1;
char c2;
int i;
};
//第一个空间占12
//第二个空间占8
S1和S2类型的成员一模一样,但是S1和S2所占空间的大小有了一些区别
#include<stido.h>
#pragma pack(8)//设置默认对齐数为8
struct S1
{
char c1;l
int i;
char c2;
};
#pragma pack()//取消设置的默认对齐数,还原为默认
#pragma pack(1)//设置默认对齐数为1
struct s2
{
char c1;
int i;
char c2;
};
int main()
{
//输出结果?
printf("%d\n",sizeof(struct S1));//6
printf("%d\n",sizeof(struct S2));
return 0;
}
在对齐方式不合适的时候,我们可以自己更改默认对齐数
offsetof:计算偏移量
结构体实现位段能力
位段的声明和结构是类似的,有两个不同:
1.位段的成员必须是int,unsigned int或signed int
2.位段的成员后边有一个冒号和一个数字
比如:
//位段:二进制位(比特位)-节省空间按
//例如:a只有00 01 11这四个数,用不到后面int原本分配剩下的30个比特位
struct A
{
int _a: 2;
int _b: 5;
int _c: 10;
int _d: 30;
};
int main()
{
struct A sa = {0};
printf("%d\n",sizeof(sa));//47 bit
//8个字节
}
位段的内存分配
位段的成员可以是 int unsigned int signed int 或者是 char (属于整形家族)类型
位段的空间上是按照需要以4个字节( int )或者1个字节( char )的方式来开辟的。
位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段。
struct A
{
int _a: 2;
int _b: 5;
int _c: 10;
int _d: 30;
};
//先开辟4个字节
//32 - 2 -> 30
//30 - 5 -> 25
//25 - 10-> 15
//再开辟4个字节
//先用15特位还是用下4个字节32比特位取决于编译器
VS环境下的内存分配情况的验证
c//一个例子
struct S
{
char a:3;
char b:4;
char c:5;
char d:4;
};
struct S s = {0};
s.a = 10;
s.b = 12;
s.c = 3;
s.d = 4;
//假设分配的内存中的比特位是由右向左使用
//分配的内存剩余的比特位不够使用时,浪费掉
//10 -> 1010, 三个比特位就是 010
//12 -> 1100, 四个比特位就是1100
//3 -> 11, 五个比特位就是00011
//4 -> 100, 四个比特位就是0100
//画到内存 0110 0010 0000 0011 0000 0100
// 6 2 0 3 0 4
位段的跨平台问题
int 位段被当成有符号数还是无符号数是不确定的。
位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32,写成27,在16位机器会出问题。
位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。
当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的
总结:
跟结构相比,位段可以达到同样的效果,但是可以很好的节省空间,但是有跨平台的问题存在。
位段的应用
以上便是自定义类型中的结构体的全部笔记内容