【自定义类型-结构体】--结构体类型,结构体变量的创建和初始化,结构体内存对齐,结构体传参,结构体实现位段
目录
一.结构体类型
1.1结构体解析
1.1.1--结构体的声明
1.1.2--结构体变量的创建和初始化
1.2--结构体的特殊声明
1.3--结构体的自引用
二.结构体内存对齐
2.1--对齐规则
练习1:
练习2:
练习3:
练习4:
2.2--为什么存在内存对齐?
2.3--修改默认对齐数
三. 结构体传参
四.结构体实现位段
4.1--什么是位段
4.2--位段的内存分配
4.3--位段的跨平台问题
4.4--位段的应用
4.5--位段使用的注意事项
🔥个人主页:@草莓熊Lotso的个人主页
🎬作者简介:C++研发方向学习者
📖个人专栏:《C语言》
⭐️人生格言:生活是默默的坚持,毅力是永久的享受。
一.结构体类型
1.1结构体解析
--结构是一些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量。
1.1.1--结构体的声明
struct tag{member- list ;}variable- list ;
比如我们想描述一本书
struct book
{char title[20];//书名char author[20];//作者名int num_pages//页数
};//分号不能掉
1.1.2--结构体变量的创建和初始化
#include <stdio.h>
struct Stu
{char name[20];//名字int age;//年龄char sex[5];//性别char id[20];//学号
};
int main()
{//按照结构体成员的顺序初始化struct Stu s = { "张三", 20, "男", "20230818001" };printf("name: %s\n", s.name);printf("age : %d\n", s.age);printf("sex : %s\n", s.sex);printf("id : %s\n", s.id);//按照指定的顺序初始化struct Stu s2 = { .age = 18, .name = "lisi", .id = "20230818002", .sex ="女" };printf("name: %s\n", s2.name);printf("age : %d\n", s2.age);printf("sex : %s\n", s2.sex);printf("id : %s\n", s2.id);//结构体变量.成员名//结构体指针->成员名return 0;
}
1.2--结构体的特殊声明
--在声明结构的时候,可以不完全的声明。
例如:
//匿名结构体类型
struct
{int a;char b;float c;
}x;
struct
{int a;char b;float c;
}a[20], * p;
上面的两个结构在声明的时候省略掉了结构体标签(tag)。
那我们来思考一个问题
// 在上⾯代码的基础上,下⾯的代码合法吗?p = &x;
- 编译器会把上面的两个声明当成完全不同的两个类型,所以是非法的。
- 匿名的结构体类型,如果没有对结构体类型重命名的话,基本上只能使用一次。
1.3--结构体的自引用
--我们可以在结构中包含一个类型为该结构体本身的成员吗?
比如,定义一个链表的节点:
错误示范struct Node{int data;struct Node next ;};
struct Node{int data;struct Node * next ;};
在结构体自引用使用的过程中,夹杂了 typedef 对匿名结构体类型重命名,也容易引入问题,我们来看看下面的代码
错误示范typedef struct{int data;Node* next;}Node;
答案是不行的,因为Node是对前面的匿名结构体类型的重命名产生的,但是在匿名结构体内部提前使用Node类型来创建成员变量,这是不行的。
typedef struct Node{int data;struct Node * next ;}Node;
二.结构体内存对齐
我们掌握了结构体的基本使用之后,现在我们深入讨论一个问题:计算结构体的大小。
这也是⼀个特别热门的考点: 结构体内存对齐。
2.1--对齐规则
结构体的对齐规则:
1.结构体的第⼀个成员对齐到和结构体变量起始位置偏移量为0的地址处2.其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。对齐数 = 编译器默认的⼀个对齐数与该成员变量大小的较小值。- VS 中默认的值为 8- Linux中 gcc 没有默认对齐数,对齐数就是成员自身的大小3.结构体总大小为最大对齐数(结构体中每个成员变量都有⼀个对齐数,所有对齐数中最大的)的整数倍,即最后结果为最大对齐数的整数倍。4.如果嵌套了结构体的情况,嵌套的结构体成员变量大小是他的最大对齐数但对齐到那里还是根据第2条规则判断,结构体的整体大小就是所有最大对齐数(含嵌套结构体中成员的对齐数)的整数倍。
我们来通过4个练习直观的理解一下吧~
练习1:
#include<stdio.h>
#include<string.h>struct S1
{char c1;int i;char c2;
};int main()
{printf("%zu\n", sizeof(struct S1));//12
}
画图理解:
练习2:
#include<stdio.h>
#include<string.h>struct S2
{char c1;char c2;int i;
};int main()
{printf("%zu\n", sizeof(struct S2));//8
}
画图理解:
练习3:
#include<stdio.h>
#include<string.h>struct S3
{double d;char c;int i;
};int main()
{printf("%zu\n", sizeof(struct S3));//16
}
画图理解:
练习4:
#include<stdio.h>
#include<string.h>
//结构体嵌套问题
struct S3
{double d;char c;int i;
};
struct S4
{char c1;struct S3 s3;double d;
};int main()
{printf("%zu\n", sizeof(struct S4));//32
}
画图理解:
2.2--为什么存在内存对齐?
#include<stdio.h>
#include<string.h>struct S1
{char c1;int i;char c2;
};
struct S2
{char c1;char c2;int i;
};int main()
{printf("%zu\n", sizeof(struct S1));//12printf("%zu\n", sizeof(struct S2));//8
}
从输出结果可以看出S1 和 S2 类型的成员虽然⼀模⼀样,但是 S1 和 S2 所占空间的大小有了⼀些区别。
2.3--修改默认对齐数
--#pragma 这个预处理指令,可以改变编译器的默认对齐数,默认对齐数一般设置为2的次方数。
#include <stdio.h>
#pragma pack(1)//设置默认对⻬数为1
struct S
{char c1;int i;char c2;
};
#pragma pack()//取消设置的对⻬数,还原为默认,下次再到别的结构体中就是默认的了
int main()
{//输出的结果是什么?printf("%d\n", sizeof(struct S));//6return 0;
}
三. 结构体传参
#include<stdio.h>struct S
{int data[1000];int num;
};
struct S s = { {1,2,3,4}, 1000 };//结构体传参
void print1(struct S s)
{printf("%d\n", s.num);
}//结构体地址传参
void print2(struct S* ps)
{printf("%d\n", ps->num);
}int main()
{print1(s); //传结构体print2(&s); //传地址return 0;
}
- 函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销。
- 如果传递⼀个结构体对象的时候,结构体过大,参数压栈的的系统开销比较大,所以会导致性能的下降。
四.结构体实现位段
--在了解完结构体之后,我们再来学习一下结构体实现位段的能力。
4.1--什么是位段
- 位段的成员必须是 int、unsigned int 或signed int ,在C99中位段成员的类型也可以选择其他类型。
- 位段的成员名后边有⼀个冒号和一个数字。
比如:
struct A{int _a: 2 ;int _b: 5 ;int _c: 10 ;int _d: 30 ;};
A就是⼀个位段类型。 那位段A所占内存的大小是多少呢?我们学习完后面的知识就知道了
printf ("%d\n", sizeof(struct A));// 8
4.2--位段的内存分配
- 位段的成员可以是 int unsigned int signed int 或者是 char 等类型
- 位段的空间上是按照需要以4个字节( int )或者1个字节( char )的方式来开辟的。
- 位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段。
//⼀个例子
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;//空间是如何开辟的?
}
4.3--位段的跨平台问题
- int 位段被当成有符号数还是无符号数是不确定的。
- 位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32),写成27,在16位机器会出问题。
- 位段中的成员在内存中从左向右分配,还是从右向左分配,标准尚未定义。
- 当⼀个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的。
总结:
4.4--位段的应用

4.5--位段使用的注意事项
struct A{int _a : 2 ;int _b : 5 ;int _c : 10 ;int _d : 30 ;};int main (){struct A sa = { 0 };scanf ( "%d" , &sa._b); // 这是错误的// 正确的示范int b = 0 ;scanf ( "%d" , &b);sa._b = b;return 0 ;}
往期回顾:
【数据在内存中的存储】--整数在内存中的存储,大小端字节序和字节序判断,浮点数在内存中的存储
【C语言内存函数】--memcpy和memmove的使用和模拟实现,memset函数的使用,memcmp函数的使用
结语:本篇文章就到此结束了,继前面一篇文章后,在此篇文章中给大家分享了自定义类型中的结构体类型,结构体变量的创建和初始化,结构体内存对齐,结构体传参,结构体实现位段等知识点,后续会继续给分享其它内容,如果文章对你有帮助的话,欢迎评论,点赞,收藏加关注,感谢大家的支持。