8.结构体/链表

4 阅读6分钟

结构体、联合体、枚举、链表

结构体

用计算机去解决现实世界的一些问题的时候,需要把现实世界的物体,抽象成一个计算机语言中的模型 -> 建模

学生信息:
    姓名:张三       ---->字符数组
    学号:20260309   ---->整形
    性别:b          ---->字符
    年龄:23         ---->整形
    身高:185        ---->整形

自定义类型:是一种组合的数据类型,是一组不同的数据类型的集合。

  • 定义
    • 语法

      struct 结构体名
      {
          成员类型1 成员变量名1;
          成员类型2 成员变量名2;
          ...
          成员类型n 成员变量名n;
      };
      
      • 示例:
      struct student
      {
          char name[50];
          int id;
          char sex;
          int age;
          int height;
      };
      

      注意:这一句执行完,并不会分配空间。只是单纯的定义了一个新的数据类型(结构体类型的声明),没有进行空间分配

  • 使用:struct 结构体名字 结构体变量名{=初始化值};
    struct student
    {
        char name[50];
        int id;
        char sex;
        int age;
        int height;
    };
    
    struct student zhangsan = {"张三", 20260309, 'b', 23, 180};
    
    • 取结构体的值:结构体变量名+域操作符+成员名
      struct student
      {
          char name[50];
          int id;
          char sex;
          int age;
          int height;
      };
      struct student zhangsan = {"张三", 20260309, 'b', 23, 180};
      printf("name:%s\n", zhangsan.name);
      
      成员变量的使用和普通变量是一模一样的
    • 完整示例:
      #include <stdio.h>
      
      struct student 
      {
          char name[40];
          int age;
      };
      
      int main(){
          struct student zhangsan;
          printf("请输入名字和年龄:");
          scanf("%s %d", zhangsan.name, &zhangsan.age);
          printf("zhangsan.name:%s, zhangsan.age:%d\n", zhangsan.name, zhangsan.age);
      
          struct student lisi = {"李四", 22};
          printf("lisi.name:%s, lisi.age:%d\n", lisi.name, lisi.age);
      
          return 0;
      }
      
      结果:
      C:\ccode\class6> .\struct.exe
      请输入名字和年龄:张三 18
      zhangsan.name:张三, zhangsan.age:18
      lisi.name:李四, lisi.age:22
      
  • 结构体指针变量的引用
    • 语法:结构体指针变量名+指向操作符+成员名:
      zhangsanp->name;
      #include <stdio.h>
      
      struct student {
          char name[20];
          int age;
      };
      
      int main(){
          struct student zhangsan;
          struct student *zhangsanp;
      
          zhangsanp = &zhangsan;
          printf("请输入name和age:");
          scanf("%s %d", zhangsanp->name, &(zhangsanp->age));
          printf("zhangsan.name:%s, zhangsan.age:%d\n", zhangsan.name, zhangsan.age);
          printf("(*zhangsanp).name: %s\n", (*zhangsanp).name);
          printf("(*zhangsanp).age: %d\n", (*zhangsanp).age);
          return 0;
      }
      
      结果:
      PS C:\Users\86176\Desktop\嵌入式\c语言\ccode\class6> .\struct2.exe
      请输入name和age:zhangsan 222
      zhangsan.name:zhangsan, zhangsan.age:222
      (*zhangsanp).name: zhangsan
      (*zhangsanp).age: 222
      

结构体成员的内存分布

  • 结构体类型所占的空间大小是各个成员变量的所占空间之和(可能会被pack填充)
  • 结构体内各个成员变量按他们定义时出现的次序,依次保存。

sizeof(结构体)===> ??? 并非简单的成员空间大小相加,而是根据对齐规则有略微的超出

结构体的字节对齐规则
  • 字节对齐:

    • CPU为了访问效率,一般要求数据按字节对齐来存放。32位CPU数据总线长度为32位,按4字节对齐效率更高
    • 64位CPU,按8字节对齐的效率更高

    如果结构体数据的成员所占内存不是对齐pack值的整倍数,就会按他的整数倍字节来算,32位CPU中:char 类型为1字节,实际应该是4字节

    #include <stdio.h>
    
    struct student {
        char name[36];
        char sex;
        int age;
    };
    
    int main(){
        struct student zhangsan;
        printf("%d\n", sizeof(zhangsan));
        return 0;
    }
    结果:
    44
    

    示例中,char[36], char, int 总和应该41才对,实际是44

    • 手动指定字节对齐pack: #pragma pack(2)
      #include <stdio.h>
      
      #pragma pack(2)
      
      struct student {
          char name[36];
          char sex;
          int age;
      };
      
      int main(){
          struct student zhangsan;
          printf("%d\n", sizeof(zhangsan));
          return 0;
      }
      
      结果:
      42
      

共用体

共用体也叫联合体,各个成员共用一段存储空间。

语法:
union 共用体名
{
    成员类型1 成员变量名1;
    成员类型2 成员变量名2;
    ...
    成员类型n 成员变量名n;
};
共用体与结构体的区别:
  • 结构体所占空间是各个成员之和,共用体所占空间是各个成员中最大的那个
    注意:共用体使用一个成员的时候,不能使用另一个成员(多线程并发可能会使用多个成员,一定要注意)

共用体最早的作用就是解决嵌入式中内存不够的情况,现在空间已经够大了,用的会很少

大端模式和小端模式

早期在各个处理器厂商,在处理把CPU的寄存器中的数据存放到内存中时,处理方式并没有统一

  • 大端模式:内存的低地址,存放寄存器中的高字节数据
  • 小端模式:内存的地地址,存放寄存器中的低字节数据
    #include <stdio.h>
    
    union demo
    {
        int a;
        char b;
    };
    
    int main(){
        union demo d;
        d.a = 0xAE86;
        printf("%x\n", d.b);
    
        return 0;
    }
    
    结果:
    ffffff86
    

    示例中取出的是86说明是小端模式,因为数据都是存放在低地址的,只是数据顺序可能会反,通过结果可以看出,数据并没有存反

枚举

把该类型变量的所有值都列举出来 有的时候,我们需要列举一些整数值,而且这些整数值在一定的范围内:

语法:
enum 枚举类型名
{
    成员标识符1{=整数值},
    成员标识符2{=整数值},
    ...
    成员标识符n{=整数值},
}

成员标识符
  • 用一些合法的C标识符来命名一些数值,默认从零开始
#include <stdio.h>

enum weekday
{
    sun,  // 0
    mon,  // 1
    tue,  // 2
    wed,  // 3
    thu,  // 4
    fri,  // 5
    sat   // 6
};

int main(){
    enum weekday w = sun;
    printf("%d %d %d\n", w, fri, sizeof(w));
    return 0;
}

结果:
0 5 4
// 可以指定值
enum weekday
{
    sun = 10,  // 10
    mon,  // 11
    tue,  // 12
};

注意:枚举完全可以当做一个常量列表来使用

typedef

用来将一个已存在的类型声明为一个新的数据类型

语法
typedef 旧类型名(已经存在的) 新类型名;

// 比如:
typedef int ACK;  // 将int类型重新声明成ACK类型
ACK a;  // 定义一个ACK类型的变量 a
与宏 #define 的区别
typedef int ACK_T;
#define ACK_D int

ACK_T a1;  // 通过ACK_T定义变量: ACK_T a1 !=> int a1;  不等价
ACK_D a2;  // 通过ACK_D定义变量: ACK_D a2 ==> int a2;  等价

练习

使用 struct 指针做一个链表
#include <stdio.h>
#include <stdlib.h>

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


int main(){

    struct Node *head = NULL;

    // 构建链表
    struct Node *temp = NULL;
    for (int i = 0; i<10; i++){
        // 这里要使用malloc手动申请内存,否则在循环结束,会将内存释放,导致存储失败
        struct Node *n = (struct Node*)malloc(sizeof(struct Node));  
        n->val = rand();
        n->next = NULL;

        if (head==NULL){
            n->val = 1;
            head = n;
            temp = n;
        } else {
            if (temp==NULL){
                temp = head;
            }
            temp->next = n;
            temp = temp->next;
        }
        printf("n->val:%d\n", n->val);
    }

    // 打印链表
    printf("================================ 打印链表 =====================================\n");
    temp = head;
    while (temp!=NULL){
        printf("%d\n", temp->val);
        temp = temp->next;
    }

    // 释放链表内存
    printf("================================ 释放内存 =====================================\n");
    temp = head;
    while(temp !=NULL){
        struct Node *next = temp->next;
        printf("释放 val:%d\n", temp->val);
        free(temp);
        temp = next;
    }

    return 0;
}

结果:
PS C:\Users\86176\Desktop\嵌入式\c语言\ccode\class6> .\node2.exe
n->val:1
n->val:18467
n->val:6334
n->val:26500
n->val:19169
n->val:15724
n->val:11478
n->val:29358
n->val:26962
n->val:24464
================================ 打印链表 =====================================
1
18467
6334
26500
19169
15724
11478
29358
26962
24464
================================ 释放内存 =====================================
释放 val:1
释放 val:18467
释放 val:6334
释放 val:26500
释放 val:19169
释放 val:15724
释放 val:11478
释放 val:29358
释放 val:26962
释放 val:24464
PS C:\Users\86176\Desktop\嵌入式\c语言\ccode\class6>