ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

c语言_深入理解结构体(超详细,看这一个就够了)

c语言_深入理解结构体(超详细,看这一个就够了) 目录1.什么是结构体2.结构体变量的创建和初始化2.1创建方式2.2.结构体初始化2.3不完全声明3.结构体内存对齐3.1对齐规则3.2为什么存在内存对齐3.3修改默认对齐数4.结构体传参4.1传参的两种方法4.2传址调用的好处5.结构体实现位段5.1什么是位段5.2位段的内存分配1.什么是结构体简单来说结构体是一种将多个不同类型的数据结合在一起形成一个新的复合数据类型的方法。写程序时需要结构体核心原因是把相互关联的一组数据打包成一个整体让代码更清晰直观更容易维护。例如一个学生有姓名学号年龄性别每个类型单独定义就会变得非常混乱这时候使用struct结构体将它们捆绑在一起就非常方便。struct Str { char name[20];//名字 char sex[5];//性别 int age;//年龄 char id[20]//学号 };2.结构体变量的创建和初始化2.1创建方式结构体的创建分为局部变量和全局变量可以在创建结构体时连带创建变量也单独创建。2.2.结构体初始化结构体变量初始化时可以选择直接初始化也能选择按照指定顺序初始化#includestdio.h struct Stu { char name[20];//名字 char sex[5];//性别 int age;//年龄 char id[20];//学号 }; int main() { //按照结构体成员顺序初始化 struct Stu s1 { zhangsan, 女, 20, 20260101 }; printf(name: %s\n, s1.name); printf(sex: %s\n, s1.sex); printf(age: %d\n, s1.age); printf(id: %s\n, s1.id); //按照指定顺序初始化 struct Stu s2 { .age 18, .name lisi, .id 20260102, .sex 女 }; printf(name:%s\n,s2.name); printf(sex:%s\n, s2.sex); printf(age:%d\n, s2.age); printf(id:%s\n, s2.id); return 0; }2.3不完全声明当结构体不需要再次使用时可以把结构体的名字省略也称为匿名结构体类型/匿名结构体类型 struct { int a; int b; }s1;如果没有对结构体类型进行重命名结构体只能使用一次。当然我们也可以用typedef来给匿名结构取一个名字只不过这就失去匿名的意义了。typedef struct { float aa; char arr[20]; }S; int main() { S s1 { 3.14, abcedf }; printf(%f\n, s1.aa); printf(%s\n, s1.arr); return 0; }3.结构体内存对齐3.1对齐规则#includestdio.h struct S { char a1;//1字节 int b1;//4 char c1;//1 }s1; struct S2 { char a2;//1 char c2;//1 int b2;//4 }s2; int main() { printf(%zu\n, sizeof(s1)); printf(%zu\n, sizeof(s2)); return 0; }对于这个代码以我们当前的认知来看输出结果应该是两个6但是真正的结果却是12和8。结构体在内存中存储按照根据对齐规则决定的1. 结构体的第1个成员对齐到和结构体变量起始位置偏移量为0的地址处。2. 从第2个成员变量开始都要对齐到某个对齐数的整数倍的地址处对⻬数编译器默认的⼀个对⻬数与该成员变量大小的较小值。VS中默认是8Linux中gcc没有默认对齐数对齐数就是成员自身的大小3. 结构体总大小为最大对齐数结构体中每个成员变量都有⼀个对齐数所有对齐数中最大的的 整数倍。4. 如果嵌套了结构体的情况嵌套的结构体成员对齐到自己的成员中最大对齐数的整数倍处结构 体的整体大小就是所有最大对齐数含嵌套结构体中成员的对⻬数的整数倍。在这个代码中第一个字节成员a起始位置为偏移量0大小为一个字节第二个整形变量b1大小为四个字节需要对齐到4的整数倍处也就是偏移量4的位置第三个字节成员变量是字符型对齐到偏移量为8的位置因此结构体占了9字节又因为结构体总大小为最大对齐数的整数倍编译器宁愿浪费一些空间也要保证对齐最终的结果就是12字节。3.2为什么存在内存对齐1.平台原因(移植原因) 不是所有的硬件平台都能访问任意地址上的任意数据的某些硬件平台只能在某些地址处取某些特定 类型的数据否则抛出硬件异常。2.性能原因 数据结构(尤其是栈)应该尽可能地在⾃然边界上对⻬。原因在于为了访问未对⻬的内存处理器需要 作两次内存访问⽽对⻬的内存访问仅需要⼀次访问。假设⼀个处理器总是从内存中取8个字节则地 址必须是8的倍数。如果我们能保证将所有的double类型的数据的地址都对⻬成8的倍数那么就可以 ⽤⼀个内存操作来读或者写值了。否则我们可能需要执⾏两次内存访问因为对象可能被分放在两个8字节内存块中。总体来说结构体的内存对⻬是拿空间来换取时间的做法。从这个例子可以看出系统想读取一个整形变量内存对齐时只需要读一次即可反之则需要读取两次甚至更多。在创建结构体时既要满足对齐又要节省空间时可以让占⽤空间⼩的成员尽量集中在⼀起这两个结构体中的成员是一样的但是二者的空间却不一样。3.3修改默认对齐数#pragma这个预处理指令可以改变编译器默认对齐数#pragma pack(1)//设置默认对⻬数为1 struct S { char c1; int i; char c2; }; #pragma pack() //取消设置的对⻬数还原为默认 int main() { printf(%d\n, sizeof(struct S)); return 0; }齐数设置成1时这个结构体大小就为6字节结构体在对⻬⽅式不合适的时候我们可以⾃⼰更改默认对⻬数。仅限于windows操作系统Linux没有默认对齐数4.结构体传参4.1传参的两种方法结构体传参一般分为两种方式传结构体变量和传结构体指针两种方式struct S { int data[1000]; int num; }; void print1(struct S t)//通过变量的方式 { int i 0; for (i 0; i 5; i) { printf(%d , t.data[i]); } printf(\n); printf(%d\n, t.num); } void print2(struct S* ps)//通过指针的方式 { int i 0; for (i 0; i 5; i) { printf(%d , ps-data[i]); } printf(\n); printf(%d\n, ps-num); } int main() { struct S s1 { { 1,2,3,4,5 }, 100 }; print1(s1); print2(s1); return 0; }虽然这两种方式均可完成传参不过第二种方式会更好一些。4.2传址调用的好处使用传值调用时函数要创建单独的空间来接收data和num空间上和时间上双重开销。但是使用传址调用的情况下函数接收一个地址大小是四个或八个字节通过地址直接调用值这种方式下需要的空间和时间明显要少得多。5.结构体实现位段5.1什么是位段位段的声明与结构体是类似的有两个不同1.位段的成员必须是int unsigned int 或 signed intc99中也可以选择其他整型家族类型例如char类型。2.位段的成员名后面有一个冒号和一个数字。struct A { int _a : 2;//:2表示占2个bit位 int _b : 5;//:5表示占5个bit位 int _c : 10; int _d : 20; };冒号后面的数字表示占多少个bit位如果直接使用结构体那么一个int类型的数据就占4个字节32个比特位很多时候我们不需要这么大的空间使用位段就能很好的节约系统空间。现在可以看到同样的成员位段比结构体占用的内存要小得多。5.2位段的内存分配1.位段的成员可以是 int unsigned int signed int 或者是 char 等类型2. 位段的空间上是按照需要以4个字节 int 或者1个字节 char 的⽅式来开辟的。3. 位段涉及很多不确定因素位段是不跨平台的注重可移植的程序应该避免使⽤位段。举个例子struct S { char a : 3; char b : 4; char c : 5; char d : 4; }; int main() { struct S s { 0 }; s.a 10; s.b 12; s.c 3; s.d 4; }char类型的成员变量系统先开辟一个字节8bit位a(3bit) 和 b(4bit) 放进去后系统开辟的8个bit只剩一个bit但是一个bit显然不够存放成员c(5bit)因此系统会再次开辟8个bit位来存放c前面剩的那个bit就浪费掉了以此类推当这个字节存放c后剩余的内存不够继续存放d(4bit)就再开辟一个字节剩余的bit就浪费掉。注意给位段分配空间时要考虑到现实使用比如这里的成员a给定的字节位是3但是变量a赋值十进制为10二进制为1010但是它只有3个bit位的空间编译器就会把前面截掉只保留3bit位。
返回列表