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

结构体变量的声明和普通变量一模一样:Student s1;。真正的新东西是访问成员的那个点号 .——s1.name、s1.age。记住一句口诀:"变量点成员",点号左边是哪个变量,右边是哪一格。另外结构体变量有一条数组羡慕不来的特权:同类型的两个结构体变量可以整体赋值 s2 = s1;。

📝 本节和前面几节的关系

8.1.1 画好了图纸(类型),这一节照着图纸造房子(变量)并开门进去(访问成员)。点号 . 是新符号,但它背后的思路你熟悉:就像 7.2.2 用 a[i] 说"整排里的第 i 格",这里用 s.age 说"这一包里的 age 那一格"。下一节 8.1.3 讲"造出来的时候怎么直接填好值"。

一、声明:跟普通变量一个模子

Student s1;             // 一个学生
Student s2;             // 又一个
Student s3, s4;         // 一行造两个,也合法

看到 Student s1; 要能自动翻译成:"按 Student 的图纸,分配一块 32 字节的空间,整块叫 s1"。

结构体变量可以声明在三个常规位置:

位置 代码 特点
函数里(局部) int main() { Student s1; } 随函数生灭(6.7.1 的 auto);未初始化=成员全是垃圾值
函数外(全局) 在所有函数之前 Student g; 活到程序结束;成员自动为 0;全局变量代价见 6.4
结构体嵌套时 作为另一个结构体的成员 8.1.4 的内容

重要区别:全局的结构体变量成员自动清零,局部的不会(和 2.3.2 的"未初始化=垃圾值"完全一致,只是从一格变成了一包)。 等一会儿:教材常见写法是把变量声明在 main 里,那就一定是垃圾值,必须先赋值再用。

二、访问成员:点号 .

Student s1;

s1.age   = 18;              // 写:给 age 那一格填 18
s1.score = 92.5;            // 写:给 score 那一格填 92.5
strcpy(s1.name, "张三");    // 写:字符数组成员要用 strcpy(7.3)

cout << s1.age << endl;     // 读:把 age 那一格的内容拿出来

格式固定为:

结构体变量名 . 成员名

. 是"成员访问运算符",读作"的"。s1.age 读作"s1 的 age",正好就是它的语义。

一句口诀记牢:"变量点成员"(s1.age)。 反过来写就错:Student.age、age.s1 全都不对。

不同成员要有不同的填值方式

这是新手最容易混的地方,按成员类型分三档:

成员类型 填值写法 为什么
普通类型(int/double) s1.age = 18; 直接 =
字符数组(char name[20]) strcpy(s1.name, "张三"); 数组不能整体赋值(7.3 的铁律),要用 strcpy
输入 cin >> s1.age; 和普通变量一样;但字符数组成员要用 cin.getline(遇空格不停)
// ❌ 三个典型错误
s1.name = "张三";             // 错:数组名是地址常量,不能当左值(7.5)
s1 = {"张三", 18, 92.5};      // 错:这句是"整体赋值",右边必须已经是同类型变量(见下节)
s1.age = "18";                // 错:类型不匹配,int 格放不下字符串

三、结构体变量的"特权":整体赋值

普通数组有一条遗憾:不能整体赋值。

int a[5] = {1,2,3}, b[5];
b = a;                  // ❌ 编译错误:数组名不是可修改的左值(7.5)

结构体可以:

Student s1, s2;
strcpy(s1.name, "张三");  s1.age = 18;  s1.score = 92.5;

s2 = s1;                // ✅ 合法!把 s1 的 32 字节整块复制到 s2
cout << s2.name << endl;   // 张三

它的含义是"逐字节整块复制"(和 s2.name、s2.age、s2.score 一项项赋值等效),比手写三行省事,也更不容易漏项。

三条注意: 1. 两边必须是同一个结构体类型。Student s; Point p; p = s; 报错(编译器按类型对不上)。 2. 这是"复制",不是"绑定"。s2 = s1; 之后改 s2,s1 不受影响——它们各自占用独立的 32 字节。 3. 这条特权只对结构体(和后面的类)有,数组、char[] 都没有。

四、动手 Demo:造两个学生,试试整体赋值

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

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

int main() {
    Student s1, s2;

    // ① 逐项填 s1
    strcpy(s1.name, "张三");
    s1.age   = 18;
    s1.score = 92.5;

    // ② 整体复制给 s2
    s2 = s1;
    cout << "复制后 s2:" << s2.name << " " << s2.age << " " << s2.score << endl;

    // ③ 验证"复制"不是"绑定":改 s2,看 s1 是否受影响
    s2.age = 99;
    strcpy(s2.name, "李四");
    cout << "改 s2 后  s1:" << s1.name << " " << s1.age << endl;
    cout << "改 s2 后  s2:" << s2.name << " " << s2.age << endl;

    // ④ 看看未初始化的成员
    Student s3;
    cout << "未初始化的 s3.age = " << s3.age << "(垃圾值)" << endl;
    return 0;
}

看到什么算成功: - 复制后 s2:张三 18 92.5 - 改完 s2 后,s1 仍然是 张三 18(证明是复制,各管各的) - 最后一行 s3.age 是个随机数(每次运行可能不同)

30 秒实验:把 Student s3; 挪到 main 函数外面(变成全局变量),重新运行——s3.age 变成 0。全局自动清零、局部是垃圾值,和第二章变量规则完全一致。

五、常见误区

  1. "结构体变量必须初始化才能声明"——不用。但不初始化就不能读(局部变量里全是垃圾值)。想干净:要么声明时初始化(8.1.3),要么先全部赋值再用。
  2. "s1 = "张三" 能直接给整个结构体赋值"——不能。右边必须是同类型的结构体变量。字符串只是其中一个成员的值,得进到 s1.name 那一格。
  3. "访问成员用 ->"——-> 是指针专用的(8.3.2),普通变量一律用 .。口诀:变量点、指针箭头。
  4. "s2 = s1; 之后 s2 和 s1 是同一块内存"——不是。它是复制(值语义)。想让两个名字指向同一块,那是指针或引用干的事(7.8)。
自测三问(点开对答案) 1. 访问结构体变量成员的写法?举三种不同成员类型的填值例子。——`变量名.成员名`。普通类型 `s.age = 18;`;字符数组成员 `strcpy(s.name, "张三");`;输入 `cin >> s.age;`。 2. `s2 = s1;` 是复制还是绑定?之后改 `s2` 会影响 `s1` 吗?——复制(整块 32 字节搬过去),**不影响**,两者各自独立。 3. 为什么 `s1.name = "张三";` 是错的,而 `s2 = s1;` 是对的?——`s1.name` 是**数组名**(地址常量,不能当左值);而 `s1`/`s2` 是**结构体变量**,结构体整体赋值是语言允许的。

相关笔记