跳过正文
P9 结构体与共用体
  1. S/
  2. C/C++/
  3. C/
  4. C语言程序设计/

P9 结构体与共用体

Li
作者
Li
往前走,别回头!
目录
C 语言程序设计笔记 - 这篇文章属于一个选集。

知识点
#

10.1 结构体变量定义、引用与初始化
#

  • @ 结构体基础图
    • ![[白板/P29 结构体基础.canvas|P29 结构体基础]]

10.1.1 结构体简介
#

[!tip] 结构体

  • @ 结构体: 把多种不同类型的数据组合在一起构成的新数据类型
  • 目的: 表达更丰富、更全面的信息
  • 结构体中的每个数据称为一个==结构体成员==
  • ! 结构体是 C++ 中"类"概念的基础
struct student {       // 定义一个结构体类型
    int num;           // 学号
    char name[100];    // 姓名
    int sex;           // 性别 0女, 1男
    int age;           // 年龄
    char address[100]; // 地址
};                     // 这里的分号不要忘记!
  • 一般形式:
struct 结构体名 {
    成员列表
};

10.1.2 定义结构体类型变量的方法
#

方法一: 先定义类型,再定义变量
#

struct student s1, s2;  // 定义两个结构体变量

方法二: 定义类型的同时定义变量
#

struct student {
    int num;
    char name[100];
    int sex;
    int age;
    char address[100];
} s1, s2;  // 同时定义变量,多个变量用逗号分隔

方法三: 直接定义结构体类型变量(省略结构体名)
#

struct {
    int num;
    char name[100];
    int sex;
} s1, s2;  // 省略了结构体名

结构体嵌套
#

struct date {          // 定义一个结构体类型
    int month;
    int day;
    int year;
};

struct student {
    int num;
    char name[100];
    int sex;
    int age;
    char address[100];
    struct date birthday;  // 生日,是一个结构体成员
};
  • ! 结构体内成员名可以与程序中的变量名相同,互不影响

10.1.3 结构体类型变量的引用
#

[!tip] 结构体成员运算符

  • @ .: 结构体成员运算符,优先级非常高,与圆括号 () 平级
  • 引用方式: 结构体变量名.成员名

引用规则
#

  • ! 不能将结构体变量作为整体引用,只能对各个成员分别引用
  • ! 嵌套结构体要一级一级找到最低级成员
s1.num = 1001;                  // 直接赋值
s1.birthday.month = 12;         // 嵌套结构体要逐级引用
s1.birthday.day = 30;
s1.birthday.year = 2018;

// 成员变量可以像普通变量一样运算
s2.age = s1.age;                // 赋值
int agesum = s1.age + s2.age;   // 求和
s1.age++;                       // 自增(.优先级最高,s1.age是整体)

// 成员变量有地址
int *p = &s1.num;
printf("%d\n", *p);

10.1.4 结构体变量的初始化
#

struct student s5 = {100, "王五", 1, 16, "3栋5楼", 10, 14, 2018};

10.2 结构体数组与结构体指针
#

  • @ 结构体数组与指针图
    • ![[白板/P30 结构体数组与指针.canvas|P30 结构体数组与指针]]

10.2.1 结构体数组
#

[!tip] 结构体数组

  • 每个数组元素都是一个结构体变量
  • 可以存放多组数据

定义方式
#

// 方式1: 单独定义
struct student stuArr[3];  // 可用下标 0-2

// 方式2: 定义结构体时顺便定义
struct student {
    int num;
    // ...
} stuArr[3];

初始化
#

struct student stuArr[3] = {
    {1001, "张三", 1, 18, "1栋1单元", 12, 30, 2000},
    {1002, "李四", 1, 20, "2栋2单元", 11, 15, 1998},
    {1003, "王五", 1, 22, "3栋3单元", 10, 15, 1996}
};

// 不指定元素个数,系统自动推断
struct student stuArr[] = {
    {1001, "张三", 1, 18, "1栋1单元", 12, 30, 2000},
    {1002, "李四", 1, 20, "2栋2单元", 11, 15, 1998},
    {1003, "王五", 1, 22, "3栋3单元", 10, 15, 1996}
};

使用
#

stuArr[1].age++;                     // 年龄+1
printf("name = %s\n", stuArr[1].name);  // name = 李四
printf("age = %d\n", stuArr[1].age);    // age = 21

10.2.2 结构体指针
#

[!tip] 结构体指针

  • 指向结构体变量所占据的内存起始地址
  • 也可指向结构体数组中的元素
struct student stu;       // 结构体变量
struct student *ps;       // 结构体指针
ps = &stu;
strcpy(stu.name, "小虎");
stu.age = 16;

通过指针访问成员的两种方式
#

方式写法说明
方式1(*ps).num*ps 要加括号,因为 . 优先级高于 *
方式2ps->num-> 指向结构体成员运算符,优先级最高
(*ps).num = 1008;    // 第一种
ps->sex = 1;         // 第二种(推荐)
printf("name: %s\n", ps->name);    // name: 小虎
printf("age = %d\n", ps->age);     // age = 16
printf("num = %d\n", ps->num);     // num = 1008

结构体指针与数组
#

struct student stuArr[3] = { ... };
struct student *ps;
ps = stuArr;  // 数组名作为首地址

for(int i = 0; i < 3; i++) {
    printf("name = %s\n", ps->name);
    printf("age = %d\n", ps->age);
    printf("num = %d\n", ps->num);
    ps++;  // 跳过一个数组元素所占的字节数
}

int ilen = sizeof(struct student);
printf("ilen = %d\n", ilen);  // 如 224

指针自增运算
#

ps = stuArr;
printf("%d\n", (++ps)->num);  // 1002,先++后用

ps = stuArr;
printf("%d\n", (ps++)->num);  // 1001,先用后++
  • ! 结构体指针只能指向同类型的结构体变量或数组元素,不能指向成员
// 错误写法
ps = &stu.num;          // 不能指向结构体变量的成员
ps = &stuArr[0].num;    // 不能指向数组元素的成员

10.2.3 用指向结构体的指针作为函数参数
#

[!tip] 传参方式对比

方式效果效率
传结构体指针可修改原数据高(只传地址)
传结构体变量不能修改原数据低(复制全部成员)
// 方式1: 传指针(推荐)
void func1(struct student *pd) {
    pd->age = 118;  // 修改原数据
}

int main() {
    struct student *ps = stuArr;
    func1(ps);  // 传指针
    printf("stuArr[0].age = %d\n", stuArr[0].age);  // 118,已修改
    return 0;
}

// 方式2: 传整个结构体变量(不推荐)
void func1(struct student d) {
    d.age = 118;  // 只修改形参副本,不影响原数据
}

int main() {
    stuArr[0].age = 12;
    func1(stuArr[0]);  // 传整个变量
    printf("stuArr[0].age = %d\n", stuArr[0].age);  // 12,未修改
    return 0;
}
  • ! 传整个结构体变量开销大(所有成员数据全部复制),建议用指针

10.3 共用体、枚举类型与 typedef
#

  • @ 共用体枚举typedef图
    • ![[白板/P31 共用体枚举typedef.canvas|P31 共用体枚举typedef]]

10.3.1 共用体
#

[!tip] 共用体(联合)

  • @ 共用体: 几种不同类型的变量存放到同一段内存单元
  • 成员共用同一段内存,会互相覆盖
  • ! 共用体大小 = 占用内存最大的成员的大小
union myun {
    int carnum;      // 轿车编号,4字节
    char cartype;    // 轿车类型,1字节
    char cname[60];  // 轿车名,60字节
} a, b, c;

// 也可以分开定义
union myun a, b, c;

// 也可以省略共用体名
union {
    int carnum;
    char cartype;
    char cname[60];
} a, b, c;

共用体 vs 结构体
#

对比项结构体共用体
内存大小各成员大小之和(可能因对齐更多)最大成员的大小
成员关系各占不同内存,互不影响共用同一段内存,互相覆盖
同时有效所有成员同时有效每一瞬间只有一个成员有效

共用体要点
#

  • ! 每一瞬间只能有一个成员起作用,最后赋值的成员起作用
  • ! 共用体变量地址和其所有成员的地址都相同
  • ! 不能在定义时给所有成员初始化,只能初始化第一个成员
union myun a;
a.carnum = 128924898;
strcpy(a.cname, "小汽车");  // cname起作用,carnum的值已无意义

// 初始化第一个成员是允许的
union myun b = {12};  // 正确

// 不能初始化所有成员
// union myun a = {12, 'A', "小汽车"};  // 报错!

10.3.2 枚举类型
#

[!tip] 枚举类型

  • @ 枚举: 将值一一列举出来,用有意义的名字代替数字
  • 枚举常量默认从 0 开始递增
enum color {          // color 是枚举类型名
    Red,              // 0
    Green,            // 1
    Blue,             // 2
    Yellow            // 3
};

enum color mycolor1, mycolor2;  // 定义枚举变量
mycolor1 = Red;                 // 赋值

// 也可以省略枚举类型名
enum {Red, Green, Blue, Yellow} mycolor1, mycolor2;

枚举要点
#

  • % 枚举常量是常量,不是变量,不能对它们赋值
  • % 默认值从 0 开始递增
  • % 可以改变默认值
enum color {
    Red = 1,     // 1
    Green,       // 2(前一个+1)
    Blue = 5,    // 5
    Yellow       // 6(前一个+1)
};
printf("Red = %d\n", Red);      // 1
printf("Yellow = %d\n", Yellow); // 6

10.3.3 typedef 关键字
#

[!tip] typedef

  • @ typedef: 为已有的数据类型定义一个新的名字(别名)
  • ! typedef 不创建新类型,只是给已有类型起别名
// 给 int 起别名
typedef int INTEGER;
INTEGER a, b;  // 等价于 int a, b;

// 给结构体起别名(最常用)
typedef struct student {
    int num;
    char name[100];
    int age;
} STU;

STU s1, s2;  // 不用写 struct student,直接用 STU

// 给指针类型起别名
typedef int *PINT;
PINT p1, p2;  // 等价于 int *p1, *p2;

// 给数组类型起别名
typedef int ARR[10];
ARR a, b;  // 等价于 int a[10], b[10];

typedef#define 的区别
#

对比项typedef#define
处理时机编译时预处理时
类型检查
作用范围受作用域限制从定义到文件末尾
本质类型别名文本替换
// typedef 正确处理指针类型
typedef int *PINT;
PINT a, b;    // a 和 b 都是 int*

// #define 可能出错
#define PINT int*
PINT a, b;    // a 是 int*,b 是 int!(宏展开为 int* a, b;)
Reply by Email
C 语言程序设计笔记 - 这篇文章属于一个选集。

相关文章