C语言 - 结构体和共用体

结构体简介:

  • 采用关键字 struct 修饰,可以将不同类型的值组合在一起。(很像C++里面的类)

结构体格式:

1
2
3
4
5
6
struct 结构体名{ 
数据类型1 成员名1; //分号结尾
数据类型2 成员名2;
……
数据类型n 成员名n;
}; //注意最后有一个分号

例子:

1
2
3
4
5
6
struct Student{       // 定义结构体:Student学生
int id; //学号
char name[20]; //姓名
char gender; //性别
char address[50]; //家庭住址
};

声明结构体变量并调用成员:

  1. 声明结构体变量格式1:常用

    1
    struct 结构体类型名称 结构体变量名;
    1. 注意:声明自定义类型的变量时,类型名前面,不要忘记加上 struct 关键字

    2. 举例:struct Student stu1;

    3. 调用结构体变量的成员:

      1
      结构体变量名.成员名 = 常量或变量值;
    4. 举例:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      #include <stdio.h>
      #include <string.h>
      int main() {

      struct Student stu1; //声明结构体变量

      //调用结构体成员
      stu1.id = 1001;
      //stu1.name = "Tom"; //报错,不能直接通过赋值运算符来给字符数组赋值
      strcpy(stu1.name, "Tony");
      stu1.gender = 'M';
      strcpy(stu1.address, "北京市海淀区五道口");

      printf("id = %d,name = %s,gender = %c,address = %s\n",
      stu1.id, stu1.name, stu1.gender, stu1.address);

      return 0;
      }

      说明:

      1)先声明了一个 struct Student类型的变量 stu1,这时编译器就会为 stu1 分配内存,接着就可以为 stu1 的不同属性赋值。可以看到,struct 结构的属性通过点( . )来表示,比如 id 属性要写成 stu1.id。

      2)字符数组是一种特殊的数组,直接改掉字符数组名的地址会报错,因此不能直接通过赋值运算符来对它进行赋值。你可以使用字符串库函数 strcpy() 来进行字符串的复制操作。

  2. 声明结构体变量格式2:

    1. 除了逐一对属性赋值,也可以使用大括号,一次性对 struct 结构的所有属性赋值。此时,初始化的属性个数最好与结构体中成员个数相同,且成员的先后顺序一一对应。格式:

      1
      struct 结构体名 结构体变量={初始化数据};
    2. 举例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    //构造结构体
    struct Car {
    char* name;
    double price;
    int speed;
    };
    int main()
    {
    //声明结构体变量并初始化
    struct Car aud1 = {"audi A6L", 460000.99, 175};
    struct Car aud1 = {"audi A6L"};//没有赋值的会自动初始化
    }

    **注意:**如果大括号里面的值的数量少于属性的数量,那么缺失的属性自动初始化为 0 。

  3. 声明结构体变量格式3:

    1. 方式2中大括号里面的值的顺序,必须与 struct 类型声明时属性的顺序一致。此时,可以为每个值指定属性名。格式:

      1
      struct 结构体名 结构体变量={.成员1=xxx,.成员2=yyy,...};
    2. 举例:

    1
    struct Car audi = {.speed=175, .name="audi A6L"};
    1. 同样,初始化的属性少于声明时的属性,剩下的那些属性都会初始化为 0 。

    2. 声明变量以后,可以修改某个属性的值。

    1
    2
    struct Car audi = {.speed=175, .name="audi A6L"};
    audi.speed = 185; //将 speed 属性的值改成 185
  4. **声明结构体变量格式4:**声明类型的同时定义变量

    1. struct 的数据类型声明语句与变量的声明语句,可以合并为一个语句。格式:

      1
      2
      3
      struct 结构体名 {
      成员列表
      } 结构体变量名;
    2. 举例:同时声明了数据类型 Circle 和该类型的变量 c1

      1
      2
      3
      4
      struct Circle {
      int id;
      double radius;
      } c1;//c1就是这个结构体的变量名,用法也是c1.id等
  5. **声明结构体变量格式5:**不指定类型名而直接定义结构体类型变量

    1. 如果结构体名(比如Student、Circle、Employee等)只用在声明时这一个地方,后面不再用到,那就可以将类型名省略。 该结构体称为匿名结构体。格式:

      1
      2
      3
      struct {
      成员列表;
      } 变量名列表;
    2. 举例:

      1
      2
      3
      4
      5
      6
      struct {
      char name[20];
      int age;
      char gender;
      char phone[11];
      } emp1, emp2;
    3. struct 声明了一个匿名数据类型,然后又声明了这个类型的两个变量emp1、emp2 。与其他变量声明语句一样,可以在声明变量的同时,对变量赋值。

      1
      2
      3
      4
      5
      6
      7
      struct {
      char name[20];
      int age;
      char gender;
      char phone[11];
      } emp1 = {"Lucy", 23, 'F', "13012341234"},
      emp2 = {"Tony", 25, 'M', "13367896789"};

      上例在声明变量 emp1 和 emp2 的同时,为它们赋值。

  6. **声明结构体变量格式6:**使用 typedef 命令(常用)

    1. 使用 typedef 可以为 struct 结构指定一个别名

    2. 举例:

      1
      2
      3
      4
      5
      6
      7
      8
      //声明结构体
      typedef struct cell_phone {
      int phone_no; //电话号码
      double minutes_of_charge; //每分钟费用
      } Phone;

      //声明结构体变量
      Phone p = {13012341234, 5};
    3. 上例中, Phone 就是 struct cell_phone 的别名。声明结构体变量时,可以省略struct关键字。

    4. 这种情况下,C 语言允许省略 struct 命令后面的类型名。进一步改为:

      1
      2
      3
      4
      5
      6
      7
      8
      //声明匿名结构体
      typedef struct {
      int phone_no;
      double minutes_of_charge;
      } Phone;

      //声明结构体变量
      Phone p = {13012341234, 5};
  7. 说明:

    1. 在创建一个结构体变量后,需要给成员赋值。在没有给成员赋值的情况下调用,打印的值是垃圾数据,可能导致程序异常终止。
    2. 不同结构体变量的成员是独立的,互不影响,一个结构体变量的成员更改,不影响另外一个。

区分三个概念:结构体、结构体变量、结构体变量的成员:

  • 结构体是自定义的 数据类型,表示的是一种数据类型。

  • 结构体变量代表一个具体变量。类比:

    1
    2
    3
    int num1 ; // int 是数据类型, 而num1是一个具体的int变量

    struct Car car1; // Car 是结构体数据类型,而car1是一个Car变量
  • Car 就像一个“汽车图纸”,生成出来的具体的一辆辆汽车,就类似于一个个的结构体变量。这些结构体变量都含有相同的成员,将结构体变量的成员比作“零件”,同一张图纸生产出来的零件的作用都是一样的。

结构体嵌套:

  1. 结构体的成员也是变量,那么成员可以是基本数据类型,也可以是数组指针结构体等类型 。如果结构体的成员是另一个结构体,这就构成了结构体嵌套

  2. 举例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    struct Date {    //声明一个结构体类型 struct Date 
    int year; //年
    int month; //月
    int day; //日
    };
    struct Employee { //声明一个结构体类型 struct Employee
    int id;
    char name[20];
    int age;
    //结构体里面包含另一个结构体变量
    struct Date birthday; //成员birthday属于struct Date类型
    };
    int main(){
    struct Employee emp1;

    emp1.id = 1001;
    strcpy(emp1.name,"Tony");
    emp1.age = 24;

    emp1.birthday.year = 2001;
    emp1.birthday.month = 3;
    emp1.birthday.day = 12;

    return 0;
    }

    说明:如果成员本身又属一个结构体类型,则要用若干个点( . ),一级一级地找到最低的一级的成员。比如,emp1.birthday.year

结构体占用空间:

  1. 结构体占用的存储空间,不是各个属性存储空间的总和。为了计算效率,C 语言的内存占用空间一般来说,都必须是 int 类型存储空间的整数倍。如果 int 类型的存储是4字节,那么 struct 类型的存储空间就总是4的倍数。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    struct A{
    char a;
    int b;
    } s;

    int main() {
    printf("%d\n", sizeof(s)); // 8
    return 0;
    }
  2. 变量 s 的存储空间不是5个字节,而是占据8个字节。a 属性与 b 属性之间有3个字节的“空洞”。如果再加一个char类型的变量,则变成12字节

结构体变量的赋值操作:

  1. 同类型的结构体变量可以使用赋值运算符( = ),赋值给另一个变量,比如

    1
    2
    //假设student1和student2已定义为同类型的结构体变量
    student1 = student2;
    • 这时会生成一个全新的副本。系统会分配一块新的内存空间,大小与原来的变量相同,把每个属性都复制过去,即原样生成了一份数据。

    • 也就是说,结构体变量的传递机制是值传递,而非地址传递。这一点跟数组的赋值不同,使用赋值运算符复制数组,不会复制数据,只是传递地址。

    • 举例1:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      struct Car {
      double price;
      char name[30];
      } a = {.name = "Audi A6L", .price = 390000.99};

      int main() {
      struct Car b = a;

      printf("%p\n", &a); //结构体a变量的地址 00007ff75a019020
      printf("%p\n", &b); //结构体b变量的地址 000000a6201ffcd0

      printf("%p\n", a.name); //结构体a变量的成员name的地址 00007ff719199028
      printf("%p\n", b.name); //结构体b变量的成员name的地址 000000c2565ffd88

      a.name[0] = 'B';
      printf("%s\n", a.name); // Budi A6L
      printf("%s\n", b.name); // Audi A6L

      return 0;
      }
      • 上例中,变量 b 是变量 a 的副本,两个变量的值是各自独立的,修改掉 b.name 不影响 a.name 。
    • 举例2:将结构体内的字符数组改为字符指针

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      struct Car {
      char *name;
      double price;
      } a = {"Audi A6L", 390000.99};

      int main() {
      struct Car b = a;

      printf("%p\n", &a); //结构体a变量的地址 00007ff75a019020
      printf("%p\n", &b); //结构体b变量的地址 000000a6201ffcd0

      printf("%p\n", a.name); //结构体a变量的成员name的地址 00007ff7d778a000
      printf("%p\n", b.name); //结构体b变量的成员name的地址 00007ff7d778a000

      return 0;
      }
      • name 属性变成了一个字符指针,这时 a 赋值给 b ,此时的b变量仍然是新开辟的内存空间。但是,a 和 b的 name 成员保存的指针相同,也就是说两个属性共享同一个”Audi A6L”。
      • 在C语言中,相同的字符串常量通常只会保存一份,即这些字符串常量共享相同的内存。当你声明多个指针变量并让它们指向相同的字符串常量时,它们实际上都指向相同的内存地址。字符串常量的共享,有助于减小程序的内存占用。
    • 注意:C 语言没有提供比较两个自定义数据结构是否相等的方法,无法用比较运算符(比如 == 和 != )比较两个数据结构是否相等或不等。

结构体数组:

  1. 本质:结构体数组是一个数组,只不过它的每个元素都是一个结构体类型的变量。
  • 声明格式:结构体类型 数组名[数组长度];

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    struct Person{ 
    char name[20];
    int age;
    };

    struct Person pers[3]; //pers是结构体数组名

    //也可以直接声明,和上述结构体的声明六种方式是相同的
    struct Person{
    char name[20];
    int age;
    }pers[3];
  • 初始化:

    1
    2
    3
    struct Student stus[3] = { {1001,"Tom",'M', 14},
    {1002, "Jerry", 'M', 13},
    {1003, "Lily",'F',12}};
  • 调用:结构体数组名[下标].成员名

    1
    stus[1].age = 23;

结构体指针:

  1. 结构体指针:指向结构体变量的指针 (将结构体变量的起始地址存放在指针变量中)

  2. 格式:struct 结构体名 *结构体指针变量名;

    举例:

    1
    2
    3
    4
    5
    6
    7
    struct Book {
    char title[50];
    char author[10];
    double price;
    };

    struct Book *b1;

    **说明:**变量 b1 是一个指针,指向的数据是 struct Book 类型的实例。

  3. 结构体传参:

    如果将 struct 变量传入函数,函数内部得到的是一个原始值的副本。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    struct Person {
    char *name;
    int age;
    char *address;
    };

    void addAge(struct Person per) {
    per.age = per.age + 1;
    }

    int main() {
    struct Person p1 = {"Tom", 20, "北京市海淀区"};
    addAge(p1);
    printf("age = %d\n", p1.age); // 输出 20
    return 0;
    }
    1. 函数 addAge() 要求传入一个 struct 变量 per,但实际上传递的是 struct 变量p1的副本,改变副本影响不到函数外部的原始数据。

    2. 通常情况下,开发者希望传入函数的是同一份数据,函数内部修改数据以后,会反映在函数外部。而且,传入的是同一份数据,也有利于提高程序性能。这时就需要将 struct 变量的指针传入函数,通过指针来修改 struct 属性。如下

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      struct Person {
      char *name;
      int age;
      char *address;
      };

      void addAge(struct Person *per) { //说明1
      (*per).age = (*per).age + 1; //说明2
      }

      int main() {
      struct Person p1 = {"Tom", 20, "北京市海淀区"};
      addAge(&p1); //说明3
      printf("age = %d\n", p1.age); // 说明4:输出 21
      return 0;
      }

      **说明1:**per 是 struct 结构的指针,调用函数时传入的是指针。

      **说明2:**函数内部必须使用 (*per).age 的写法,从指针拿到 struct 结构本身。因为运算符优先级问题,不能写成*per.age,会将per.age看成是一个指针,然后取其值。

      **说明3:**结构体类型跟数组不一样,类型标识符本身并不是指针,所以传入时,指针必须写成 &p1。

      **说明4:**addAge() 内部对 struct 结构的操作,就会反映到函数外部。

  4. -> 操作符:(指针变量引用属性)

    1. 如果一个结构体指针变量p指向一个结构体变量stu,以下3种用法等价:

      1
      2
      3
      ① stu.成员名    stu.num  变量调用属性
      ② (*p).成员名 (*p).num
      ③ p->成员名 p->num 指针调用属性
    2. 例子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    struct Student {
    char name[20];
    int age;
    char gender;
    };

    int main() {
    //打印结构体信息
    struct Student s = {"张三", 20, 'M'};

    //方式1:.为结构成员访问操作符
    printf("name = %s,age = %d,gender = %c\n", s.name, s.age, s.gender);

    struct Student *ps = &s;
    //方式2:.为结构成员访问操作符
    printf("name = %s,age = %d,gender = %c\n", (*ps).name, (*ps).age, (*ps).gender);

    //方式3:->操作符
    printf("name = %s,age = %d,gender = %c\n", ps->name, ps->age, ps->gender);

    return 0;
    }
    1
    2
    3
    4
    void addAge(struct Person * per) {
    //使用结构体指针访问指向对象的成员
    per->age = per->age + 1;
    }
  5. 指向结构体数组的指针:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    struct Person {
    int id;
    char name[20];
    };

    int main() {

    struct Person per;
    struct Person arr[5];

    struct Person *p,*q;

    p = &per; //指向单个结构体变量
    q = arr; //指向结构体数组

    return 0;
    }

共用体简介:

  1. 共用体(Union) 是 C 语言中的一种特殊数据类型,它允许在相同的内存位置存储不同的数据类型。共用体的所有成员共享同一块内存空间,因此同一时间只能存储其中一个成员的值。
  2. 共用体变量所占的内存长度等于最长的成员的长度;几个成员共用一个内存区。

共用体格式:

1
2
3
4
5
6
union 共用体类型名称{
数据类型 成员名1;
数据类型 成员名2;

数据类型 成员名n;
};

举例:

1
2
3
4
5
union Data {
short m;
float x;
char c;
};

上例中, union 命令定义了一个包含三个属性的数据类型 Data。虽然包含三个属性,但是同一时间只能取到一个属性。最后赋值的属性,就是可以取到值的那个属性。

声明共用体变量:

  • 和结构体非常相似

    1
    2
    3
    4
    5
    6
    7
    8
    union Data {
    short m;
    float x;
    char c;
    };

    //声明共用体变量
    union Data a, b;
    1
    2
    3
    4
    5
    union Data {
    short m;
    float x;
    char c;
    } a, b;
  • 以共用体变量a为例,它由3个成员组成,分别是m、x和c,编译时,系统会按照最长的成员为它分配内存,由于成员x的长度最长,它占4个字节,所以共用体变量a的内存空间也为4个字节。

    共用体1

调用共用体成员:

  • 和结构体相似

  • 方式1:

    1
    2
    union Data a;
    a.c = 4;
  • 方式2:声明共用体变量的同时,给任一成员赋值

    1
    union Data a = {.c = 4};
  • 方式3:声明共用体变量的同时,给首成员赋值

    1
    union Data a = {8};

    注意,方式3不指定成员名,所以只能为第一个成员进行赋值。

  • 错误的方式:

    1
    2
    3
    union Data a = {1,1.5,'a'};  //错误的
    因为共同体是共用一块内存空间,同一刻只能存储一个成员的值,
    所以上述代码,只有最后一个有效,也就是'a'

-> 操作符:

  1. Union 结构也支持指针运算符 -> 。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    union evaluation { //评价
    int score;
    float grade;
    char level;
    };

    int main() {
    union evaluation e;
    e.score = 85;

    union evaluation *p;
    p = &e;
    printf("%d\n", p->score); // 85 int类型
    e.grade=3.0f;
    printf("%d\n", p->grade);//3.0 float类型

    return 0;
    }

    上例中, p 是 e 的指针,那么 p->score等同于 e.score。

    **了解:**Union指针与它的属性有关,当前哪个属性能够取到值,它的指针就是对应的数据类型。

  2. 共用体类型可以出现在结构体类型定义中,也可以定义共用体数组。反之,结构体也可以出现在共用体类型定义中,数组也可以作为共用体的成员。

参考链接: