【C4】结构体 struct



2021年12月04日    Author:Guofei

文章归类: C    文章编号: 10004

版权声明:本文作者是郭飞。转载随意,但需要标明原文链接,并通知本人
原文链接:https://www.guofei.site/2021/12/04/c4.html


结构体

基本用法

struct student {
    char name[100];
    int age;
};


struct student st; // 定义一个变量 st,其类型是 student
st.age = 20;
strcpy(st.name, "张三"); // 不能用 st.name = "张三"

// 如何使用:
printf("name= %s, age = %d\n", st.name, st.age);


// 定义2:定义+初始化
struct student st1 = {"李四", 25};

// 定义3:可以省略1个
struct student st2 = {"王二"};

// 定义4:所有字段都初始化为0
struct student st3 = {0};

// 定义5:指定字段,这样顺序可以打乱
struct student st4 = {.age=26, .name="麻子"};

用等号赋值就是内存拷贝

struct student st1;
st1 = st;

结构体的内存布局

1

// 占用2个字节,没问题
struct A {
    char a1;
    char a2;
};

// 占用8个字节,也没问题
struct A {
    int a1;
    int a2;
};


// 占用8个Byte
struct A {
    char a1;
    int a2;
};
// 这是因为结构体的内存总是对齐的
// 按照占用最大内存的元素为基本单位,做对齐

内存中是这样的

1Byte1Byte1Byte1Byte
char a1;
int a2;

占用 8 Byte

2

// 这个也占8个 Byte
struct A {
    char a1;
    char a2;
    char a3;
    char a4;
    int i;
};
// 因为空闲的内存给补上了

内存中是这样的

char a4;
1Byte1Byte1Byte1Byte
char a1;char a2;char a3;
int a2;

占用 8 Byte

3

struct A {
    char a1;
    short a2;
    int a3;
};

内存中是这样的

1Byte1Byte1Byte1Byte
char a1;short a2;
int a2;

4

struct A {
    char a1;
    short a2;
    char a3;
    int i;
};

内存中是这样的

1Byte1Byte1Byte1Byte
char a1;short a2;
char a1;
int a2;

占用 12 个字节

所以,你可以通过调整顺序,减少内存消耗。但计算机 不会自动帮你优化

5:出现数组的情况

struct A {
    char a1[5];
    int i;
};

占用 12 个字节

1Byte1Byte1Byte1Byte
char a[0]; char a[1];char a[2];char a[3];
char a[4];
int a2;

按照每个元素对齐,而不是按照数组为单位

操作内存

struct A {
    char a1[4];
    int i;
};

struct A a;
char *s = (char *) &a;
s[0] = 'a';
s[1] = 'b';
s[5] = 'c'; //其实指向 i 的第1个Byte了

printf("%s\n", a.a1);
printf("%d\n", a.i);
return 0;

所以

  1. 可以像操作数组一样,用指针操作结构体
  2. 可以用指针,把浪费的“空隙”用起来

指定bit

// 总共只占用1个Byte
struct A {
    char a1: 2; // 占用2个bit
    char a2: 2; // 占用2个bit
    char a3: 4; //占用4个bit
};

可以用这个技巧把内存应用到极致,尤其是在简陋的设备上

但是不要用不同的类型做定义

// 这是错的
struct A {
    char a1:1;
    int a2:1;
};
// 实测在不同的机器上占用内存不一样。在内存中的堆叠方式也不一样

结构体数组

struct student {
    char name[20];
    unsigned char age;
    int sex;
};


// 初始化方法1
struct student st[3];

strcpy(st[0].name, "张三");
st[0].age = 15;
st[0].sex=1;

// 初始化方法2
struct student st2[3] = { {"张三", 16, 1},
                         {"李四", 18, 0} };
// 初始化方法3
struct student st3[] = { {"张三", 16, 1},
                        {"李四", 18, 0} };

基于结构体数组的特点,元素交换可以直接用内存交换来做


struct student {
    char name[20];
    unsigned char age;
    int sex;
};

int swap_std(struct student *a, struct student *b) {
    static struct student tmp;
    memcpy(&tmp, a, sizeof(tmp));
    memcpy(a, b, sizeof(tmp));
    memcpy(b, &tmp, sizeof(tmp));
}

int main() {

    struct student st[] = { {"张三",  16, 1},
                           {"李四",  18, 0},
                           {"王二",  14, 0},
                           {"白展堂", 20, 0},
                           {"吕秀才", 19, 0}
    };


    swap_std(&st[0],&st[1]);


    //显示
    for (int i = 0; i < sizeof(st) / sizeof(st[0]); i++) {
        printf("%s,%d,%d\n", st[i].name, st[i].age, st[i].sex);
    }


//其实结构体也可以直接赋值
    int swap_std(struct student *a, struct student *b) {
        static struct student tmp;
        tmp = *a;
        *a = *b;
        *b = tmp;
        return 0;
    }

}

结构体嵌套

如何使用?

struct A {
    char a1;
};

struct B{
    struct A a;
    int b1;
};

struct B b;
b.a.a1;

内存

内存 case 1

struct A {
    char a1;
};

// B 占 2 个 Byte
struct B{
    struct A a;
    char b1;
};

内存 case 2

struct A {
    char a1;
};

// B 占 8 个 Byte
struct B{
    struct A a;
    int b1;
};

似乎在内存中是紧凑的堆叠

struct A {
    int a1;
    char a2;

};

// 占用 16 个字节
struct B {
    struct A a;
    char b1;
    int b2;
};
1Byte1Byte1Byte1Byte
int A.a1
char A.a2
char b1
int b2

b1 不会用来补其它结构体的内存空隙

自引用

// 这个是错的,因为会引起无限递归
// struct A {
//        char a1;
//        struct A a;
//    };

// 这个是可以的:
struct A {
    char a1;
    struct A *a;
};

结构体与指针

指向结构体的指针

struct student st;
struct student *p = &st;

strcpy(p->name, "hello");
p->age = 20;

p->age 等价于 (*p).age

指向结构体数组的指针

struct student {
    char name[100];
    int age;
};

struct student st[3] = {
        {"张三", 25},
        {"李四", 26},
        {"王二", 29}
};
struct student *p = st;

//    用指针赋值
p->age = 20;
p++;
p->age = 15;

//    用指针取值
p--;
for (int i = 0; i < 3; i++) {
    printf("%s,%d\n", p[i].name, p[i].age);
}

结构体的成员是指针的情况

struct student {
    char *name;
};

struct student st = {0};
st.name = calloc(20, sizeof(char)); // 因为是指针,因此需要分配内存
strcpy(st.name, "hello");
printf("%s", st.name);
free(st.name);

下面这个很好理解,浅拷贝

struct student {
    char *name;
};

struct student st = {0};
st.name = calloc(20, sizeof(char)); // 因为是指针,因此需要分配内存
struct student st1 = st;
strcpy(st.name, "hello");
printf("%s", st1.name);
free(st.name);

额外

  1. p->a 的优先级高于 *p,因此 *p->a 代表取成员 a 这个指针指向的内容
  2. 因为结构体的成员也可以是指针,指向另一个结构体,因此可以写出类似 p1->a->a1p1->a->arr2[1] 这样的代码
  3. 结构体作为函数入参,虽然正确但应该避免,因为每次调用函数都会把整个结构体复制一遍,效率很低。最好传入指针

堆中的结构体

struct student p1={0}; // 在栈里面
struct student *p = malloc(sizeof(struct student)); // p->name 在堆里面
free(p);

错误的结构体

struct B
{
	char *p = malloc(100); //不能这样写,这里只是定义,不能执行,编译器不认识。
	int a;
};

联合体 union

union A {
    int a1;
    short a2;
    char a3;
};

union A a;
//     union A a = {1}; // 也可以这样,给第一个赋值
printf("%ld\n", sizeof(union A));
printf("%p== %p== %p\n", &a.a1, &a.a2, &a.a3);

联合体在形式上与结构体类似,但区别是联合体成员的内存是共用的,每个成员在内存中的开头都一样。

使用场景举例

// 需要一种变量,它可能是 int、float、string 类型
struct VARIABLE {
    enum {
        INT, FLOAT, STRING
    } type;

    union {
        int i;
        float f;
        char *s;
    } value;
};

struct VARIABLE v1;
v1.type = INT;
v1.value.f = 1;

枚举类型 enum

enum spectrum {
    red, yellow, green, blue, white, black
}; // 其实是定义了很多int型的常量(0, 1, 2, ...),提高代码可读性

int main() {
    enum spectrum flower_color = red;
}

还可以自定义值

enum spectrum {
    red = 100,
    yellow, //这里后面累加1
    green, blue, white, black
};

typedef

用途

  • 增加代码的可读性
  • 减少代码量
  • 提高可维护性
  • 提高跨平台能力
typedef struct student stu; // 就是定义 stu = struct student
typedef unsigned char BYTE; // 就是定义了 BYTE = unsigned char
int main() {
    stu st; // 等价于  struct student st
    BYTE a; // 等价于 unsigned char a
    sizeof(BYTE);
}

可维护性:例如,你代码里面有很多 short a1; 你可以这样:

typedef short SHORT;
SHORT a1;
SHORT a2;

如果很多版本后,你想把那些变量改成 long long 类型,只需要改1行 def long long SHORT

跨平台:

#ifdef UNICODE
typedef wchar_t TCHAR // 不同平台用的 char 类型不一样
#else
typedef char TCHAR
#endif


// 下面是主体代码:
#define UNICODE // 靠这个来定义不同平台上的变量定义
TCHAR a;

您的支持将鼓励我继续创作!