结构体

结构体定义方法

定义方法一:

struct MyStruct   //定义结构体,定义的结构体在内存中是不占空间的
{
    int a;
    char b;
    short c;
};               //没有创建对象

int main(void)
{
    //使用结构体,创建一个结构体对象,创建对象时才会占用内存空间
    struct MyStruct a = {10,'b',4};  //可以在创建时就初始化,也可以在之后赋值
    struct MyStruct* sp = &a;  //结构体指针
    a.a = 10;
    (*sp).b = 'b';
    sp->c = 4;
}

定义方法二:

struct MyStruct
{
    int a;
    char b;
    short c;
}s1,s2,s3; 
//s1,s2,s3 是创建的结构体对象,在定义结构体的同时创建对象
//s1,s2,s3 是全局变量      

int main(void)
{
    s1.a = 10;  //这里可以直接使用结构体对象
}

定义方法三:

typedef struct MyStruct   //相当于对这个结构体重命名为 Stu
{
    int a;
    char b;
    short c;
}Stu;   

int main(void)
{
    Stu s1;   //使用重命名,进行创建对象。
}

结构体作为参数时

  • 函数传参时,参数是需要压栈的
  • 如果传递一个结构体对象时,直接传递结构体实参,在函数那边会压栈一个同样大小的内存空间(创建结构体形参),会导致系统开销增大,从而导致性能降低。
  • 所以传递结构体参数时,尽量传递结构体的指针(地址)

匿名结构体类型

  • 注意:如果两个匿名结构体的成员一样,但是在编译器看来也是不同的结构体类型。
struct     //没有声明结构体的名称
{
    int a;
    char b;
    short c;    
}x;        //定义了一个结构体的对象
//这种声明结构体方法,只能在声明时定义结构体对象,之后不能在定义对象(没有结构体名称)

struct     
{
    int a;
    char b;
    short c;    
}p;           //p和x是不同的结构体类型。

结构体的自引用

struct Node
{
    int date;
    struct Node* next;   //用指针指向下一个同类结构体的地址。
    //struct Node n;     //在结构体中创建自己类型的对象
    //这种用法是错误的,会出现套娃的情况,内存会无限大,系统报错
}

结构体内存对齐

  • 先看示例:
struct X  
{
    int a;
    char b;
    short c;    
}; 
struct P   
{
    char a;
    int b;
    char c
    short d;  
    struct X st;
}; 
struct P a;
printf("%d\n",sizeof(p));    //输出结果为  20

结构体对齐规则

  • 第一个成员,存放在结构体变量偏移量为0的地址处(即结构体变量的首地址处)
  • 其他成员要对齐到某个数(对齐数)的整数倍的地址处。

    • 对齐数 = 编译器默认的对齐数 和 该成员大小 的较小值
    • VS 中默认对齐数为 8。
    • 每个成员都有一个对齐数(可能会不相同)
  • 结构体的总大小为最大对齐数的整数倍。
  • 如果有嵌套结构体,嵌套结构体对齐到字节的最大对齐数的整数倍处,结构体的整体大小就是最大对齐数(包括嵌套结构体成员的对齐数)的整数倍。
    image.png

为什么存在内存对齐

  • 移植原因:不是所有硬件平台都能任意地址访问,某些硬件平台只能在某些地址处取某些特定类型的数据。
  • 性能原因:数据结构(尤其是栈)应该尽可能的在边界上对齐。为了访问未对齐的内存,处理器需要作两次内存访问,而对齐内存的访问只需要访问一次。
  • 总的来说就是:结构体的内存对齐是拿空间来换取时间。
  • 所以在设计结构体时,尽量将空间小的成员放在一起,节省空间。

修改默认对齐数#pragma pack

  • 将结构体放在#pragma pack之间,该结构体的默认对齐数就变为4。
#pragma pack(4)
struct X  
{
    int a;
    char b;
    short c;    
}; 
#pragma pack()

宏offsetof()求偏移量

offsetof(type, member-designator);
int i = offsetof(struct X,b);   //返回值为b的偏移量
  • 函数作用 :求结构体成员相当于首地址的偏移量
  • type :结构体类型
  • member-designator:结构体成员
  • 返回值为size_t ,表示结构体成员的偏移量。

嵌套结构体的初始化

typedef struct Contact
{
    char name[name_MAX];
    char danwei[danwei_MAX];
    char tele[tele_MAX];

}Contact;

typedef struct SUM
{
    Contact people[people_MAX];
    int size;
}SUM;

//嵌套结构体初始化
SUM Data = {
        .people = {{"111","444","777"},{"222","555","888"},{"333","666","999"}},
        .people = 0

    };
 //单独赋值时,必须使用strcpy进行字符串的赋值(int等数值类型可以直接使用=号)
 strcpy(Data.people[3].name,"abcd");
 //注意:Data.size或者(&Data)->size 都是值类型,int型(size是int型)
 //(((&Data)->people) )->name或者(Data.people)->name  都是指针类型,char*类型(name是char类型)

结构体与位段

  • 位段的定义方法和结构体类似。
  • 位段成员的后面有一个冒号和数字。(数字是这个变量的大小bit位)(不是字节哦)
  • 位段成员只能是 unsigned int 和 signed int 或者char类型。
  • 位段不存在内存对齐(节省空间)
  • 位段是不能跨平台的,所以可移植程序中不能使用位段。
//定义方法
struct MyStruct
{
    int _a : 2;            //_a这个变量只占2个bit位
    int _b : 5;            //_b这个变量只占5个bit位
    unsigned int _c : 10;  //_c这个变量只占10个bit位
    unsigned int _d : 30;
};
//总共37个bit位,占两个int型8字节

枚举

  • 把可能的值一一列举出来。
//枚举的定义方法
enum Day    //一周
{
    Mon,
    Tues,
    Wed,
    Thur,
    Fri,
    Sat,
    Sum
};
  • 如果定义枚举时,如果没有初始化,则默认从0开始依次增大1。
  • 枚举里面的都是常量,都是枚举类型,只能在定义时初始化,之后无法赋值。

联合体(共用体)

  • 共用体中的成员共用同一个内存空间(即所有成员的地址都一样)
  • 注意:共用体是成员的地址相同,并且那个空间存的数据也相同
  • 但是每个成员的值是不同的(因为变量类型不同,解析方法不同)
union Un   //定义共用体
{
    char c;
    int i;
    short j;
};
union Un u;
u.i = 123456;
printf("%d\n", u.c);   //64
printf("%d\n", u.i);  //123456
printf("%d\n", u.j);  //-7616
  • 注意:共用体定义时不能初始化,只能在创建共用体对象后才能赋值。

联合体的对齐

  • 共用体的总大小为最大对齐数的整数倍。
  • 共用体的总大小至少是成员变量中空间最大的那个。
union Un   //该共用体大小为 4 (int的对齐数为4)
{
    char c;
    int i;
    short j;
};

union Un   //大小位16,int为4字节,arr占14字节,所以总大小为16字节
{
    short arr[7];
    int i;
};

夜枫微凉
27 声望4 粉丝