结构是一些值的集合,这些值称为成员变量,结构的每个成员与数组不同,结构体的每个成员可以是不同的类型变量【但是成员变量类型不可以是自己的结构体类型】;
- struct Student {
- char name[20];
- char tele[12];
- char sex[10];
- short age;
- }
- struct Student {
- char name[20];
- char tele[12];
- char sex[10];
- short age;
- }s2,s3,s4;
-
- struct Student s5;
- struct {
- int x;
- int y;
- int z;
- }a;
1. 向上面代码描述的是匿名结构体类型,那么这种类型由于没有结构体名字,所以只能在声明结构体类型的后面直接定义结构体变量,在其他地方无法定义结构体变量;
2. 当然这种声明的方式不建议大家去使用,这里只做了解即可;
3. 结构体类型的指针只能存放自己结构体类型的对象地址;
- struct Node {
- int data;
- struct Node* next;
- };
1. 其实就是数据结构中的链式存储方式;
2. 结构体中一部分存储数据,一部分存储一个指向下一个结构体元素的指针;
- typedef struct Student {
- char name[20];
- char tele[12];
- char sex[10];
- short age;
- }Student;
-
- int main() {
- struct Student s1 = {"小澜","1355841253","男",18};
- printf("%s%s%s%d",s1.name,s1.tele,s1.sex,s1.age);
- return 0;
- }
- typedef struct Student {
- char name[20];
- char tele[12];
- char sex[10];
- short age;
- }Student;
-
- typedef struct People {
- Student s;
- int a;
- int b;
- int c;
- }People;
-
- int main() {
- People p = { {"小澜","1355841253","男",18},1,2,3 };
- printf("%s%s%s%d",p.s.name,p.s.tele,p.s.sex,p.s.age);
- return 0;
- }
- struct S1 {
- char c1;
- int a;
- char c2;
- };
- struct S2 {
- char c1;
- char c2;
- int a;
- };
- int main() {
- struct S1 s1 = { 0 };
- printf("%d\n",sizeof(s1));
- struct S2 s2 = { 0 };
- printf("%d\n", sizeof(s2));
- return 0;
- }
计算一下这两个结构占用内存空间的大小 ->
1. 第一个成员在与结构体变量偏移量为 0 的地址处;
2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处;【对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值;】
VS中默认的值为 8 ;【那么有些编译器没有默认对齐数(例如 gcc 编译器),这时候变量的大小就是每次的对齐数】
3. 结构体总大小为最大齐数(每个成员变量都有一个对齐数)的整数倍;
4. 如果嵌套了结构体的情况,嵌套的 结构体a 对齐到自己结构体中变量的最大对齐数的整数倍处【比如说:该 结构体a 中定义了 int a; 且这个 a 的对齐数是该 结构体a 中最大的,那么该 结构体a 就按照 4 的倍数来算】,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍;

1. s1 算出来的大小是 12;
2. 那么结构体类型变量 s2 的大小计算方法也同理能算出来是 8;
3. 可以看到 s1 和 s2 的成员变量都是一样的,只是定义的顺序不一样,最终导致结构体占用的内存空间大小不一样;
4. 可以用 #pragma pack( int ) 去修改 默认的对齐数;如果int参数什么都不写就代表取消设置的默认对齐数;
offsetof 其实是宏,这里先做了解即可,在之后宏的学习章节中再给大家详细介绍;
size_t offsetof ( structName , memberName );
1. 返回该变量在该结构体中的偏移量;【使用的时候需要引入头文件 #include
2. 第一个参数为 -> 结构体类型,第二个参数为 -> 该结构体的变量;
3. 实例代码如下所示:
- struct S {
- char c;
- int i;
- double d;
- };
- int main() {
- printf("%d\n", offsetof(struct S ,c));
- printf("%d\n", offsetof(struct S, i));
- printf("%d\n", offsetof(struct S, d));
- return 0;
- }
输出的结果为:0 , 4 , 8
大部分的参考资料都是这么说的:
1. 平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常;
2. 性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐;原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问;
总的来说:
结构体的内存对齐是拿 空间 来换取 时间 的做法
- struct S {
- int a;
- char b;
- short c;
- };
- void init(struct S tmp) {
- tmp.a = 1;
- tmp.b = 'a';
- tmp.c = 2;
- }
- int main() {
- struct S s = { 0 };
- init(s);
- printf("a=%d,b=%c,c=%d",s.a,s.b,s.c);
- return 0;
- }
如上代码,将对象 s 传递给 init()函数 并且修改结构体对象 s 的值,但是由于这是值传递,所以 tmp 是一个临时拷贝的变量,所以修改的也只是 tmp 临时拷贝变量中的值,并没有真正的修改到 s 的值;
- struct S {
- int a;
- char b;
- short c;
- };
- void init(struct S* p) {
- (*p).a = 1;
- (*p).b = 'a';
- (*p).c = 2;
- }
- int main() {
- struct S s = { 0 };
- init(&s);
- printf("a=%d,b=%c,c=%d",s.a,s.b,s.c);
- return 0;
- }
1. 如上代码,传址方式将 s 的地址传递到 init() 函数中通过 s 的地址去修改 s 的值,是可以修改成功的;
2. 如果我们要传递 结构体对象 给函数,尽量使用传址的方式,因为这样更加节省空间节省资源;
3. 如果用传值的方式,当我们的变量很大的时候,会导致需要拷贝一个很大的临时变量,很占用栈的内存空间;
1. 函数传参的时候,参数是需要压栈,会有时间和空间上的开销;
2. 如果传递一个结构体对象的时候,结构体过大,参数压栈的的系统开销比较大,所以会导致性能的下降;
3. 所以当结构体传参的时候要传结构体的地址;