结构体的理解(创建,初始化,内存对齐,位段,枚举,联合)

1:结构体类型创建
2:结构体初始化
3:结构体内存对齐
4:位段,位段计算机大小。
5:枚举+联合。

struct 结构体名{
结构体所包含的变量或数组
};

结构体的成员包括: 标量,数组,指针,其他结构体。

结构体是一种集合,它里面包含了多个变量或数组,它们的类型可以相同,也可以不同,每个这样的变量或数组都称为结构体的成员

struct stu{
char *name; //姓名
int num; //学号
int age; //年龄
char group; //所在学习小组
float score; //成绩
};

stu 为结构体名,它包含了 5 个成员,分别是 name、num、age、group、score。结构体成员的定义方式与变量和数组的定义方式相同,只是不能初始化

结构体变量名.成员名;

通过这种方式可以获取成员的值,也可以给成员赋值

#include<stdio.h>
int main()
  struct{
        char *name;  //姓名
        int num;  //学号
        int age;  //年龄
        char group;  //所在小组
        float score;  //成绩
    } stu1;
    //给结构体成员赋值
    stu1.name = "Tom";
    stu1.num = 12;
    stu1.age = 18;
    stu1.group = 'A';
    stu1.score = 136.5;
    //读取结构体成员的值
    printf("%s的学号是%d,年龄是%d,在%c组,今年的成绩是%.1f!\n", stu1.name, stu1.num, stu1.age, stu1.group, stu1.score);
    return 0;
}

Tom,学号12,18岁,A组136.5分
如果结构体访问指向变量的成员,有时候我们得到的不是一个结构体变量,而是一个只想结构体的指针。

struct S
{
   char name[29];
   int age;
}s;

void print(struct S* ps)
{
printf("name = %s,age=%d\n",(*ps).name,(*ps).age);
printf("name = %s,age=%d\n",ps->name,ps->age);
}

结构体的自引用

struct Node
{
 int data;
 struct Node* next;
};

用指针的方式进行自引用

struct B// //对结构体B进行不完整声明
struct A结构体A中包含指向结构体B的指针
{
 int _a;
 struct B* pb;
};
struct B
{
 int _b;
 struct A* pa;
};

有时候,必须声明一些相互之间存在依赖的结构。即:其中一个结构包含了另一个结构的一个成员或多个成员。
问题在于声明部分:如果每个结构都引用了其他结构的标签,哪个结构应该首先被声明呢?
该问题采用不完整声明来解决。它声明一个作为结构标签的标识符。
然后,把这个标签用在不需要知道这个结构的长度的声明中,声明指向这个结构的指针。接下来的声明把这个标签与成员列表联系在一起。

结构体内存对齐

结构体的内存对⻬是拿空间来换取时间的做法
结构体内存对齐规则:

1.第一个成员在与结构体变量偏移为0的地址处。

2.其他成员变量对对齐到某个数字的(对齐数)的整数倍的地址处。

//对齐数=编译器默认对齐数与该成员的较小值。

//vs默认对齐数为8,linux默认对齐数为4。

3.结构体总大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。

对齐原因:
1.平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据,某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常
2.性能原因::经过内存对齐之后,CPU的内存访问速度大大提升 ,内存不是一个个字节组成的,而是有2,4,6,,区分成的内存块称为内存读取粒度,内存对齐之后系统取数据时可以直接取出完整的数据,如果不对齐,可能需要在两块区读取数据,剔除并合并,额外操作降低cpu性能

结构体内存对齐理解及应用

1、数据成员对齐规则:结构的数据成员,第一个数据成员放在偏移地址0的地方,以后每个数据成员存储的起始位置要从该成员大小的整数倍开始(比如int在32位机为4字节,则要从4的整数倍地址开始存储)。

2、结构体作为成员:如果一个结构里有某些结构体成员,则结构体成员要从其内部最大元素大小的整数倍地址开始存储。(struct a里存有struct b,b里有char,int,double等元素,那b应该从8的整数倍开始存储。)

3、收尾工作:结构体的总大小,也就是sizeof的结果,必须是其内部最大成员的整数倍,不足的要补齐。

  例1:    struct {
                 short a1;
                 short a2;
                 short a3;
                 }A;

          struct{
                 long a1;
                 short a2;
                 }B;

          sizeof(A) = 6; 这个很好理解,三个short都为2。

          sizeof(B) = 8; 这个比是不是比预想的大2个字节?long为4,short为2,整个为8,因为原则3。

    例2:struct A{
                  int a;
                  char b;
                  short c;
                  };


         struct B{
                  char b;
                  int a;
                  short c;
                  };

       sizeof(A) = 8; int为4,char为1,short为2,这里用到了原则1和原则3。

       sizeof(B) = 12; 是否超出预想范围?char为1,int为4,short为2,怎么会是12?还是原则1和原则3。

   深究一下,为什么是这样,我们可以看看内存里的布局情况。

                 a         b         c
   A的内存布局:1111,     1*,       11

                 b          a        c
   B的内存布局:1***,     1111,   11**

   其中星号*表示填充的字节。A中,b后面为何要补充一个字节?因为c为short,其起始位置要为2的倍数,就是原则1。c的后面没有补充,因为b和c正好占用4个字节,整个A占用空间为4的倍数,也就是最大成员int类型的倍数,所以不用补充。

   B中,b是char为1,b后面补充了3个字节,因为a是int为4,根据原则1,起始位置要为4的倍数,所以b后面要补充3个字节。c后面补充两个字节,根据原则3,整个B占用空间要为4的倍数,c后面不补充,整个B的空间为10,不符,所以要补充2个字节。

   再看一个结构中含有结构成员的例子:

       例3:struct A{
                     int a;
                     double b;
                     float c;
                    };

                struct B{
                     char e[2];
                     int f;
                     double g;
                     short h;
                     struct A i;
                    };

       sizeof(A) = 24; 这个比较好理解,int为4,double为8,float为4,总长为8的倍数,补齐,所以整个A为24。

       sizeof(B) = 48; 看看B的内存布局。

                                 e         f             g                h                                    i 
    B的内存布局:11* *,   1111,   11111111, 11 * * * * * *,        1111* * * *, 11111111, 1111 * * * *

结构体传参

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;
}

讲解函数栈帧的时候,我们讲过函数传参的时候,参数是需要压栈的。
如果传递⼀个结构体对象的时候,结构体过⼤,参数压栈的的系统开销⽐较⼤,所以会导致
性能的下降。

###位段

声明
1.位段的成员必须是 int、unsigned int 或signed int 。
2.位段的成员名后边有1个冒号和1个数字

位段的内存分配

  1. 位段的成员可以是 int unsigned int signed int 或者是 char (属于整形家族)类型
  2. 位段的空间上是按照需要以4个字节( int )或者1个字节( char )的⽅式来开辟的。
  3. 位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使⽤位段。
  4. 当⼀个结构包含两个位段,第⼆个位段成员⽐较⼤,⽆法容纳于第⼀个位段剩余的位 时,是舍弃剩余的位还是利⽤,这是不确定的

总结:
跟结构相⽐,位段可以达到同样的效果,但是可以很好的节省空间,但是有跨平台的问题存
在。

###枚举
枚举顾名思义就是⼀⼀列举。
把可能的取值⼀⼀列举

enum Day//星期
位段的跨平台问题
枚举
枚举类型的定义
{
 Mon,
 Tues,
 Wed,
 Thur,
 Fri,
 Sat,
 Sun
};
enum Sex//性别
{
 MALE,
 FEMALE,
 SECRET
};
enum Color//颜⾊
{
 RED,
 GREEN,
 BLUE
};

以上定义的 enum Day , enum Sex , enum Color 都是枚举类型。
{}中的内容是枚举类型的可能取值,也叫 枚举常量 。这些可能取值都是有值的,默认从0开始,
⼀次递增1,当然在定义的时候也可以赋初值

需要注意的两点是:

  1. 枚举列表中的 Mon、Tues、Wed 这些标识符的作用范围是全局的(严格来说是 main() 函数内部),不能再定义与它们名字相同的变量。
  1. Mon、Tues、Wed 等都是常量,不能对它们赋值,只能将它们的值赋给其他的变量。

重点内容
枚举和宏其实非常类似:宏在预处理阶段将名字替换成对应的值,枚举在编译阶段将名字替换成对应的值。我们可以将枚举理解为编译阶段的宏。

枚举的优点:

  1. 增加代码的可读性和可维护性
  2. 很#define定义的标识符⽐较枚举有类型检查,更加严谨。
  3. 防⽌了命名污染(封装)
  4. 便于调试

###(共⽤体)
联合也是⼀种特殊的⾃定义类型
这种类型定义的变量也包含⼀系列的成员,特征是这些成员公⽤同⼀块空间(所以联合也叫共⽤
体)。

特点 联合的成员是共⽤同⼀块内存空间的,这样⼀个联合变量的⼤⼩,⾄少是最⼤成员的⼤⼩ (因为联合⾄少得有能⼒保存最⼤的那个成员)。

联合⼤⼩的计算
联合的⼤⼩⾄少是最⼤成员的⼤⼩。
当最⼤成员⼤⼩不是最⼤对⻬数的整数倍的时候,就要对⻬到最⼤对⻬数的整数倍

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值