江哥带你玩转C语言 | 14 - 结构体-枚举-共用体

Posted 极客江南

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了江哥带你玩转C语言 | 14 - 结构体-枚举-共用体相关的知识,希望对你有一定的参考价值。

什么是结构体

  • 结构体和数组一样属于构造类型
  • 数组是用于保存一组相同类型数据的, 而结构体是用于保存一组不同类型数组的
    • 例如,在学生登记表中,姓名应为字符型;学号可为整型或字符型;年龄应为整型;性别应为字符型;成绩可为整型或实型。
    • 显然这组数据不能用数组来存放, 为了解决这个问题,C语言中给出了另一种构造数据类型——“结构(structure)”或叫“结构体”。

定义结构体类型

  • 在使用结构体之前必须先定义结构体类型, 因为C语言不知道你的结构体中需要存储哪些类型数据, 我们必须通过定义结构体类型来告诉C语言, 我们的结构体中需要存储哪些类型的数据
  • 格式:
struct 结构体名{
     类型名1 成员名1;
     类型名2 成员名2;
     ……
     类型名n 成员名n;
 };
  • 示例:
struct Student {
    char *name; // 姓名
    int age; // 年龄
    float height; // 身高
};

定义结构体变量

  • 定好好结构体类型之后, 我们就可以利用我们定义的结构体类型来定义结构体变量

  • 格式: struct 结构体名 结构体变量名;

  • 先定义结构体类型,再定义变量

struct Student {
     char *name;
     int age;
 };

 struct Student stu;
  • 定义结构体类型的同时定义变量
struct Student {
    char *name;
    int age;
} stu;
  • 匿名结构体定义结构体变量
struct {
    char *name;
    int age;
} stu;
  • 第三种方法与第二种方法的区别在于,第三种方法中省去了结构体类型名称,而直接给出结构变量,这种结构体最大的问题是结构体类型不能复用

结构体成员访问

  • 一般对结构体变量的操作是以成员为单位进行的,引用的一般形式为:结构体变量名.成员名
struct Student {
     char *name;
     int age;
 };
 struct Student stu;
 // 访问stu的age成员
 stu.age = 27;
 printf("age = %d", stu.age);

结构体变量的初始化

  • 定义的同时按顺序初始化
struct Student {
     char *name;
     int age;
 };
struct Student stu = {“lnj", 27};
  • 定义的同时不按顺序初始化
struct Student {
     char *name;
     int age;
 };
struct Student stu = {.age = 35, .name = “lnj"};
  • 先定义后逐个初始化
struct Student {
     char *name;
     int age;
 };
 struct Student stu;
stu.name = "lnj";
stu.age = 35;
  • 先定义后一次性初始化
struct Student {
     char *name;
     int age;
 };
struct Student stu;
stu2 = (struct Student){"lnj", 35};

结构体类型作用域

  • 结构类型定义在函数内部的作用域与局部变量的作用域是相同的
    • 从定义的那一行开始, 直到遇到return或者大括号结束为止
  • 结构类型定义在函数外部的作用域与全局变量的作用域是相同的
    • 从定义的那一行开始,直到本文件结束为止
//定义一个全局结构体,作用域到文件末尾
struct Person{
    int age;
    char *name;
};

int main(int argc, const char * argv[])
{
    //定义局部结构体名为Person,会屏蔽全局结构体
    //局部结构体作用域,从定义开始到“}”块结束
    struct Person{
        int age;
    };
    // 使用局部结构体类型
    struct Person pp;
    pp.age = 50;
    pp.name = "zbz";

    test();
    return 0;
}

void test() {

    //使用全局的结构体定义结构体变量p
    struct Person p = {10,"sb"};
    printf("%d,%s\\n",p.age,p.name);
}

结构体数组

  • 结构体数组和普通数组并无太大差异, 只不过是数组中的元素都是结构体而已
  • 格式: struct 结构体类型名称 数组名称[元素个数]
struct Student {
    char *name;
    int age;
};
struct Student stu[2]; 
  • 结构体数组初始化和普通数组也一样, 分为先定义后初始化和定义同时初始化
    • 定义同时初始化
struct Student {
    char *name;
    int age;
};
struct Student stu[2] = {{"lnj", 35},{"zs", 18}}; 
    • 先定义后初始化
struct Student {
    char *name;
    int age;
};
struct Student stu[2]; 
stu[0] = {"lnj", 35};
stu[1] = {"zs", 18};

结构体指针

  • 一个指针变量当用来指向一个结构体变量时,称之为结构体指针变量
  • 格式: struct 结构名 *结构指针变量名
  • 示例:
      // 定义一个结构体类型
      struct Student {
          char *name;
          int age;
      };

     // 定义一个结构体变量
     struct Student stu = {“lnj", 18};

     // 定义一个指向结构体的指针变量
     struct Student *p;

    // 指向结构体变量stu
    p = &stu;

     /*
      这时候可以用3种方式访问结构体的成员
      */
     // 方式1:结构体变量名.成员名
     printf("name=%s, age = %d \\n", stu.name, stu.age);

     // 方式2:(*指针变量名).成员名
     printf("name=%s, age = %d \\n", (*p).name, (*p).age);

     // 方式3:指针变量名->成员名
     printf("name=%s, age = %d \\n", p->name, p->age);

     return 0;
 }
  • 通过结构体指针访问结构体成员, 可以通过以下两种方式
    • (*结构指针变量).成员名
    • 结构指针变量->成员名(用熟)
  • (pstu)两侧的括号不可少,因为成员符“.”的优先级高于“”。
  • 如去掉括号写作pstu.num则等效于(pstu.num),这样,意义就完全不对了。

结构体内存分析

  • 给结构体变量开辟存储空间和给普通开辟存储空间一样, 会从内存地址大的位置开始开辟
  • 给结构体成员开辟存储空间和给数组元素开辟存储空间一样, 会从所占用内存地址小的位置开始开辟
  • 结构体变量占用的内存空间永远是所有成员中占用内存最大成员的倍数(对齐问题)

+多实际的计算机系统对基本类型数据在内存中存放的位置有限制,它们会要求这些数据的起始地址的值是 某个数k的倍数,这就是所谓的内存对齐,而这个k则被称为该数据类型的对齐模数(alignment modulus)。

  • 这种强制的要求一来简化了处理器与内存之间传输系统的设计,二来可以提升读取数据的速度。比如这么一种处理器,它每次读写内存的时候都从某个8倍数的地址开始,一次读出或写入8个字节的数据,假如软件能 保证double类型的数据都从8倍数地址开始,那么读或写一个double类型数据就只需要一次内存操作。否则,我们就可能需要两次内存操作才能完成这个动作,因为数据或许恰好横跨在两个符合对齐要求的8字节 内存块上

结构体变量占用存储空间大小

    struct Person{
        int age; // 4
        char ch; // 1
        double score; // 8
    };
    struct Person p;
    printf("sizeof = %i\\n", sizeof(p)); // 16
  • 占用内存最大属性是score, 占8个字节, 所以第一次会分配8个字节
  • 将第一次分配的8个字节分配给age4个,分配给ch1个, 还剩下3个字节
  • 当需要分配给score时, 发现只剩下3个字节, 所以会再次开辟8个字节存储空间
  • 一共开辟了两次8个字节空间, 所以最终p占用16个字节
    struct Person{
        int age; // 4
        double score; // 8
        char ch; // 1
    };
    struct Person p;
    printf("sizeof = %i\\n", sizeof(p)); // 24
  • 占用内存最大属性是score, 占8个字节, 所以第一次会分配8个字节
  • 将第一次分配的8个字节分配给age4个,还剩下4个字节
  • 当需要分配给score时, 发现只剩下4个字节, 所以会再次开辟8个字节存储空间
  • 将新分配的8个字节分配给score, 还剩下0个字节
  • 当需要分配给ch时, 发现上一次分配的已经没有了, 所以会再次开辟8个字节存储空间
  • 一共开辟了3次8个字节空间, 所以最终p占用24个字节

结构体嵌套定义

  • 成员也可以又是一个结构,即构成了嵌套的结构
struct Date{
     int month;
     int day;
     int year;
}
struct  stu{
     int num;
    char *name;
    char sex;
    struct Date birthday;
    Float score;
}
  • 在stu中嵌套存储Date结构体内容
  • 注意:
  • 结构体不可以嵌套自己变量,可以嵌套指向自己这种类型的指针
struct Student {
    int age;
    struct Student stu;
};
  • 对嵌套结构体成员的访问
    • 如果某个成员也是结构体变量,可以连续使用成员运算符"."访问最低一级成员
struct Date {
       int year;
       int month;
       int day;
  };

  struct Student {
      char *name;
      struct Date birthday;
 };

 struct Student stu;
 stu.birthday.year = 1986;
 stu.birthday.month = 9;
 stu.birthday.day = 10;

结构体和函数

  • 结构体虽然是构造类型, 但是结构体之间赋值是值拷贝, 而不是地址传递
    struct Person{
        char *name;
        int age;
    };
    struct Person p1 = {"lnj", 35};
    struct Person p2;
    p2 = p1;
    p2.name = "zs"; // 修改p2不会影响p1
    printf("p1.name = %s\\n", p1.name); // lnj
    printf("p2.name = %s\\n", p2.name); //  zs
  • 所以结构体变量作为函数形参时也是值传递, 在函数内修改形参, 不会影响外界实参
#include <stdio.h>

struct Person{
    char *name;
    int age;
};

void test(struct Person per);

int main()
{
    struct Person p1 = {"lnj", 35};
    printf("p1.name = %s\\n", p1.name); // lnj
    test(p1);
    printf("p1.name = %s\\n", p1.name); // lnj
    return 0;
}
void test(struct Person per){
    per.name = "zs";
}

共用体

  • 和结构体不同的是, 结构体的每个成员都是占用一块独立的存储空间, 而共用体所有的成员都占用同一块存储空间
  • 和结构体一样, 共用体在使用之前必须先定义共用体类型, 再定义共用体变量
  • 定义共用体类型格式:
union 共用体名{
    数据类型 属性名称;
    数据类型 属性名称;
    ...   ....
};
  • 定义共用体类型变量格式:
union 共用体名 共用体变量名称;
  • 特点: 由于所有属性共享同一块内存空间, 所以只要其中一个属性发生了改变, 其它的属性都会受到影响
  • 示例:
    union Test{
        int age;
        char ch;
    };
    union Test t;
    printf("sizeof(p) = %i\\n", sizeof(t));

    t.age = 33;
    printf("t.age = %i\\n", t.age); // 33
    t.ch = 'a';
    printf("t.ch = %c\\n", t.ch); // a
    printf("t.age = %i\\n", t.age); // 97
  • 共用体的应用场景
    • (1)通信中的数据包会用到共用体,因为不知道对方会发送什么样的数据包过来,用共用体的话就简单了,定义几种格式的包,收到包之后就可以根据包的格式取出数据。
    • (2)节约内存。如果有2个很长的数据结构,但不会同时使用,比如一个表示老师,一个表示学生,要统计老师和学生的情况,用结构体就比较浪费内存,这时就可以考虑用共用体来设计。
      +(3)某些应用需要大量的临时变量,这些变量类型不同,而且会随时更换。而你的堆栈空间有限,不能同时分配那么多临时变量。这时可以使用共用体让这些变量共享同一个内存空间,这些临时变量不用长期保存,用完即丢,和寄存器差不多,不用维护。

枚举

  • 什么是枚举类型?

    • 在实际问题中,有些变量的取值被限定在一个有限的范围内。例如,一个星期内只有七天,一年只有十二个月,一个班每周有六门课程等等。如果把这些量说明为整型,字符型或其它类型 显然是不妥当的。
    • C语言提供了一种称为“枚举”的类型。在“枚举”类型的定义中列举出所有可能的取值, 被说明为该“枚举”类型的变量取值不能超过定义的范围。
    • 该说明的是,枚举类型是一种基本数据类型,而不是一种构造类型,因为它不能再分解为任何基本类型。
  • 枚举类型的定义

    • 格式:
enum 枚举名 {
    枚举元素1,
    枚举元素2,
    ……
};
    • 示例:
// 表示一年四季
enum Season {
    Spring,
    Summer,
    Autumn,
    Winter
};
  • 枚举变量
    • 先定义枚举类型,再定义枚举变量
enum Season {
    Spring,
    Summer,
    Autumn,
    Winter
};
enum Season s;
    • 定义枚举类型的同时定义枚举变量
enum Season {
    Spring,
    Summer,
    Autumn,
    Winter
} s;
    • 省略枚举名称,直接定义枚举变量
enum {
    Spring,
    Summer,
    Autumn,
    Winter
} s;
  • 枚举类型变量的赋值和使用
enum Season {
    Spring,
    Summer,
    Autumn,
    Winter
} s;
s = Spring; // 等价于 s = 0;
s = 3; // 等价于 s = winter;
printf("%d", s);
  • 枚举使用的注意
    • C语言编译器会将枚举元素(spring、summer等)作为整型常量处理,称为枚举常量。
    • 枚举元素的值取决于定义时各枚举元素排列的先后顺序。默认情况下,第一个枚举元素的值为0,第二个为1,依次顺序加1。
    • 也可以在定义枚举类型时改变枚举元素的值
enum Season {
    Spring,
    Summer,
    Autumn,
    Winter
};
// 也就是说spring的值为0,summer的值为1,autumn的值为2,winter的值为3
enum Season {
    Spring = 9,
    Summer,
    Autumn,
    Winter
};
// 也就是说spring的值为9,summer的值为10,autumn的值为11,winter的值为12

如果觉得文章对你有帮助,点赞、收藏、关注、评论,一键四连支持,你的支持就是江哥持续更新的动力。

配套视频地址

以上是关于江哥带你玩转C语言 | 14 - 结构体-枚举-共用体的主要内容,如果未能解决你的问题,请参考以下文章

江哥带你玩转C语言 | 08 - C语言函数

江哥带你玩转C语言 01 - C语言开发环境配置

江哥带你玩转C语言 | 17-文件操作

江哥带你玩转C语言 | 17-文件操作

江哥带你玩转C语言 | 11- C语言排序算法

江哥带你玩转C语言 | 10- C语言数组