🔷 第八章 结构体与共用体
📄 一句话概括

结构体的初始化跟数组一个套路:把值按成员顺序装进花括号——Student s1 = {"张三", 18, 92.5};。顺序必须和成员列表一致,填不满的后面自动补 0,= {} 一键清零。真正要小心的是字符数组成员:它只能用 strcpy 或 cin.getline 填,不能直接 =。本节末尾用实例01(为结构体成员赋值)把三种赋值途径全部走一遍。

📝 本节和前面几节的关系

8.1.1 画图纸、8.1.2 造变量,这一节解决"造出来时怎么直接填好值"。这里的规则你可以直接和 7.1.3(数组的初始化)做对照——几乎一模一样,因为结构体本质就是"把几个变量捆在一起",初始化规则自然照搬。唯一的差异点在字符数组:char name[20] 作为结构体成员时,依然受 7.3 那条"数组不能用 = 赋值"的铁律管着。

一、整体初始化:按顺序装进花括号

Student s1 = {"张三", 18, 92.5};
//              ↑      ↑    ↑
//            name   age  score   —— 必须和成员列表顺序一一对应

顺序错了会怎样?编译器按类型对不上就报错;万一是类型刚好能转(比如 int 和 double),它会"默默"装错位置——这种错最坑,不报错但数据全乱。

Student s2 = {18, "张三"};      // ❌ 编译错:int 格放不下字符串
Student s3 = {92.5, 18, "张三"};// ⚠️ 可能报错,也可能"歪打歪着"编译过 → 数据全错位

记忆法:结构体初始化和数组一样,靠位置对应、不看名字。所以要养成习惯:写初始化列表时对照成员列表逐个核对。

二、填不满:后面自动补 0

Student s1 = {"张三"};          // 只填 name
// 结果: name="张三", age=0, score=0.0

只填前面几项,后面自动补 0(和 7.1.3 数组"填一部分其余补 0"完全一致)。

Student s2 = {};                // 全部清 0:name 全 '\0'、age=0、score=0.0

= {} 是一键清零的标准写法。注意它和"未初始化"是两回事:8.1.2 里 Student s3; 是垃圾值,Student s3 = {}; 才是干净的 0。

三、先声明、后逐项赋值

初始化列表只在声明的那一句能用。声明完了再想填,就得逐个成员来:

Student s1;                         // 先造出来(此刻是垃圾值)
strcpy(s1.name, "张三");            // 字符数组成员:strcpy
s1.age   = 18;                      // 普通成员:直接 =
s1.score = 92.5;

这是最常用的一种,尤其从键盘输入的时候:

Student s1;
cout << "请输入姓名:";
cin.getline(s1.name, 20);           // 字符串成员:用 getline,遇空格不停
cout << "请输入年龄:";
cin  >> s1.age;                     // 普通成员:用 >>
cout << "请输入成绩:";
cin  >> s1.score;

为什么字符串成员不用 cin >> s1.name?因为 >> 遇到空格、Tab 就停(2.5 的解析规则)。姓名里一旦有空格(比如 Li Xiao),后面半截就丢了。要读整行就用 cin.getline(数组, 长度)(7.3 学过)。

四、一份"填值方式对照表"(背下来)

成员类型 声明时初始化 声明后赋值 键盘输入
int / double / char 直接写在 {} 里 s.age = 18; cin >> s.age;
char 数组 直接写在 {} 里({"张三"}) strcpy(s.name, "张三"); cin.getline(s.name, 20);
嵌套的结构体 再套一层 {}(见 8.1.4) 逐个进到最里层赋值 逐层进到最里层输入

一句话总结差异:只要是"数组"成员,就不能用 =,一律用 strcpy 或 getline。

五、动手 Demo(实例01 为结构体成员赋值)

教材实例01的核心就是"给结构体成员赋值"。这里把它做成三条路径对照,一次看清:

#include <iostream>
#include <cstring>
using namespace std;

struct Student {
    char   name[20];
    int    age;
    double score;
};

void printStudent(const char *tag, const Student &s) {   // 7.8.2 的 const 引用
    cout << tag << " -> " << s.name << " " << s.age << " " << s.score << endl;
}

int main() {
    // 路径① :声明时整体初始化(位置对应)
    Student a = {"张三", 18, 92.5};
    printStudent("①整体初始化 ", a);

    // 路径② :先声明,再逐项赋值
    Student b;
    strcpy(b.name, "李四");      // 字符数组 → strcpy
    b.age   = 19;                // 普通成员 → =
    b.score = 85.0;
    printStudent("②逐项赋值   ", b);

    // 路径③ :部分初始化,其余自动补 0
    Student c = {"王五"};
    printStudent("③部分初始化 ", c);

    // 路径④ :一键清零
    Student d = {};
    printStudent("④清零       ", d);

    // 路径⑤ :整体赋值(同类型可以直接搬)
    Student e;
    e = a;                       // 8.1.2 的特权
    e.score = 100;               // 改 e 不影响 a
    printStudent("⑤整体赋值后 e", e);
    printStudent("  原来的 a    ", a);
    return 0;
}

看到什么算成功:

①整体初始化  -> 张三 18 92.5
②逐项赋值    -> 李四 19 85
③部分初始化  -> 王五 0 0
④清零        ->  0 0
⑤整体赋值后 e -> 张三 18 100
  原来的 a     -> 张三 18 92.5

两个观察点: - ③ 只填了 name,age 和 score 自动是 0; - ⑤ 把 e 的成绩改成 100,a 还是 92.5——再次证明结构体赋值是整块复制(8.1.2)。

30 秒实验:把 Student a = {"张三", 18, 92.5}; 里的 "张三" 和 18 换个位置写成 {18, "张三", 92.5};,编译——看它报什么错。再把 double score 改成 int score(让类型能互相转),重新写成 {18, "张三"} 试试。你会体会到:类型差得远就报错,类型能互转就悄悄装错。

六、常见误区

  1. "初始化列表可以按名字指定"——标准用法是按顺序(C99/C++20 有"指定初始化器"写法,但老编译器不支持,别用)。顺序错了不会提示你,只会装错位置。
  2. "s1.name = "张三" 应该可以吧"——不行。name 是数组,数组名是地址常量,不能当赋值号左边(7.5 的老规矩)。用 strcpy。
  3. "={} 和什么都不写是一回事"——完全不同。Student s; → 垃圾值;Student s = {}; → 全部为 0。
  4. "初始化列表漏几项会报错"——不会,后面自动补 0。反过来说:漏填不是"没写",而是"写了 0",用的时候不会有提示,得自己心里有数。
  5. "输入字符串成员也用 cin >> 就行"——空格一出现就断。姓名、地址、书名一律用 cin.getline。
自测三问(点开对答案) 1. 成员顺序是 `name, age, score` 时,`Student s = {18, "张三", 92.5};` 有什么问题?——位置对不上。`18` 想塞进 `char[20]`,编译会报错;如果类型恰好能互转,就会**悄悄装错**。 2. `Student s = {"张三"};` 之后 `s.age` 是多少?——**0**。填不满的成员自动补 0。 3. 给 `char name[20]` 这个成员赋值,有哪三种正确写法?——① 初始化列表 `{"张三", ...}`;② `strcpy(s.name, "张三");`;③ `cin.getline(s.name, 20);`。**都不能用 `=`。**

相关笔记