结构体

typedef的使用

typedef struct S
{
    char c[20];
    struct S* next;
}S;
#include <stdio.h>
int main()
{
    S s1;
    return 0;
}

typedef是一个很实用的标识符,可以将类型重命名,比如编译器就将,unsigned int 重命名为size_t,在这里也是同样的将 struct S重命名为S,使用的时候更加方便。我本人是比较喜欢在结构体中使用 typedef 的。

结构体的自引用

struct S
{
    char c[20];
    struct S* next;
};
#include <stdio.h>
int main()
{
    return 0;
}

结构体里面是不能直接出现或者引用自身的,如果出现就会进入无限的自引用,但是可以出现指向自身的指针,直到指针溢出指针就无效了,也不会再引用。

结构体内存对齐

#pragma pack(4)
//设置默认对齐数为4
//MSVC的默认对齐数为8
typedef struct S1
{
    char a;
    int c;
    char b;
}S1;
//typedef struct S2
//{
//  char a;
//  char b;
//  int c;
//}S2;
//typedef struct S3
//{
//  int c;
//  char a;
//  char b;
//}S3;
#pragma pack()
//取消设置的默认对齐数
#include <stdio.h>
#include <stddef.h>
int main()
{
    S1 n1 = { 0 };
    printf("%d\n", sizeof(n1));
    //S2 n2 = { 0 };
    //printf("%d\n", sizeof(n2));
    //S3 n3 = { 0 };
    //printf("%d\n", sizeof(n3));

    printf("%d\n", offsetof(S1, a));
    printf("%d\n", offsetof(S1, b));
    printf("%d\n", offsetof(S1, c));
    return 0;

}

内存对齐的现象在各个编译器中都会存在,但是没有统一的标准,内存对齐,指的是在结构体内部,对内部的元素进行内存分配时存在的一种规律,以内存对齐数为标准,MSVC中默认的内存对齐数为8,成员类型的对齐数为本身字节数,比如int的对齐数为4,double的对齐数为8,对齐数是默认对齐数与成员对齐数的较小值。
以第一个元素存储开始的内存位置为0,下一个成员开始的位置的需要是相对于第一个成员内存开始的位置的对齐数的整数倍,比如,显存进去一个char 占一个字节,那么以这个字节为开始,后面要继续存进去一个int (四个字节)的话,就需要在下一个是4的整数倍的位置开始,也就是说不能从该char之后的第1,2,3个位置存,得从第4个开始存,所以能够明显的知道,如果先定义char 再定义int 那么这两个成员在结构体中占据了8个字节,如果先定义int,在定义char,那么这两个成员只会占5个字节
#pragma pack()是可以改默认对齐数使,将要改的数字放在括号里,用完之后要在后面再加一句#pragma pack(),使得默认对齐数恢复默认。

位段

//位段 - 位指的是二进制位
#include <stdio.h>
struct A
{
    int _a : 2;//只要占2个比特位
    int _b : 5;//只要占5个比特位
    int _c : 10;//只要占10个比特位
    int _d : 30;//只要占30个比特位
};
//注重移植的程序不应该使用位段
//int 位段被当成有符号数还是无符号数是不确定的
//位段中的最大位数是不能确定的
//位段中的从左向右分配还是从右向左分配尚未定义
//当一个结构体包含两个位段,第二个位段成员较大时,无法容纳第一个位段剩余位时,是舍弃剩余位还是利用,这是不确定的
//位段的数字不能大于32
int main()
{
    struct A s;
    printf("%d\n", sizeof(s));//8个字节
    return 0;
}
typedef struct S
{
    char a : 3;
    char b : 4;
    char c : 5;
    char d : 4;
}S;

int main()
{
    S s = { 0 };

    s.a = 10;
    s.b = 20;
    s.c = 3;
    s.d = 4;

    return 0;
}

位段也是一种自定义类型,不确定性较多,最重要的是不能移植,但是优点是能够优化内存管理。

枚举

enum Color
{
    RED,
    GREEN,
    BULE
};

#include <stdio.h>
int main()
{
    enum Color c = RED;

    return 0;
}

若不赋初值,则自动进行了赋初值,对应的是RED = 0, GREEN = 1, BULE = 2也可以进行手动赋初值
使用方法结构体类似,但是定义出来的变量的值只能取枚举内部的值
优点
1.增加了代码的可读性
2.和define定义的标识符比枚举有类型检查,更加严谨

  1. 防止了命名污染(封装)
  2. 便于调试
  3. 使用方便,一次可以定义多个常量
    合 - 联合体 - 共用体

    联合(联合体 /共用体)

联合体的认识

#include <stdio.h>

typedef union Un
{
    char c;//1个字节
    int i;//4个字节
}Un;
int main()
{
    Un u;
    printf("%d\n", sizeof(u));
    printf("%p\n", &u);
    printf("%p\n", &(u.c));
    printf("%p\n", &(u.i));
    //i和c不能同时使用
    return 0;
}

联合体也是一种自定义类型,意思是,内部的成员使用了同一块空间,联合体所占的空间大小是由内部较大的成员决定的,当最大成员不是最大对齐数的整数倍时,就要对齐到最大对齐数的整数倍,但是两个成员不能同时使用,因为,同一块空间不能存得下两个元素。

简单的使用

#include <stdio.h>

int check_sys()
{
    union U
    {
        char c;
        int i;
    }u;
    u.i = 1;
    return u.c;

    //int a = 1;
    ////返回1 表示小端
    //return *(char*)&a;
}
int main()
{
    int ret = check_sys();

    if (1 == ret)
    {
        printf("小端字节序\n\a");
    }
    else
    {
        printf("大端字节序\n");
    }

    //高高低低 小端字节序
    //高低低高 大端字节序
    return 0;
}

对于大端字节序和小端字节序,我有点记不住,就发明了个简短的总结,高高低低,意思是数字的高位存储在内存的高位,数字的低位存储在内存的低位,叫做小端字节序,高低低高,就是数字的高位存储在内存的低位,数字的低位存储在内存的高位,叫做大端字节序。

当初是写了一个a=1来进行读取第一个字节的数字判断大小端,现在用联合体,因为公用同一块空间,所以在空间内部存进去数字之后,不同的类型读取的位数不同,但是读取的顺序都是由低地址到高地址,所以通过char访问由int存进去的数字,就能够判断大小端了。