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

定义结构体的格式是 struct 结构体名 { 成员列表 };——注意末尾那个分号,它是本章第一大坑。定义只是画图纸,不占内存;图纸画好可以反复用(造很多个变量)。成员列表跟普通变量声明长得一样,唯一区别是它们被包在花括号里、有了统一的归属。

📝 本节和前面几节的关系

8.1 说了"结构体是什么、为什么需要"。这一节落地语法:模板到底怎么写。用到的零件你全都学过——类型来自第二章,成员名要守 2.3.1 的标识符规则,花括号是 3.x/4.x 就见过的"圈定范围"符号(作用域边界,6.4),分号是 1.3.2 的三条铁律之一。下一节 8.1.2 拿这张图纸去造变量。

一、标准写法:先画模板,再单独造变量

struct Student              // ① struct + 结构体名
{
    char   name[20];        // ② 成员列表:每一行都是一个普通的变量声明
    int    age;             //    末尾分号照常写
    double score;
};                          // ③ 整个定义结束,还要再加一个分号!

拆成三部分看:

部分 内容 讲究
关键字 + 名字 struct Student Student 是新造出来的类型名,从此可以像 int 一样用
成员列表 { ... } 里面每一行都是普通变量声明;成员名不能重复,可以和外面的变量重名(那样会被"遮住",见 6.4 就近遮蔽)
结尾分号 }; 必须有。漏了就报错,而且报错行往往不在这一行

为什么末尾要分号?把 struct Student { ... } 理解成一句完整的声明:它要告诉编译器"有这么一种类型"。C++ 里凡是"声明"都以分号收尾——就像 int a; 那样。记忆法:花括号圈完内容,分号宣布结束。

二、三种写法:什么时候造变量

写法 1:先定义类型,之后随时造变量(最常用)

struct Student {
    char   name[20];
    int    age;
    double score;
};                          // ← 类型定义完毕

int main() {
    Student s1;             // 用的时候再一个个造
    Student s2;
    return 0;
}

推荐这种。类型和变量分开,模板只写一次、变量用到再造,最清楚。

写法 2:定义类型的同时顺便造变量

struct Student {
    char   name[20];
    int    age;
    double score;
} s1, s2;                   // ← 分号前多了变量名

s1, s2 是紧跟类型定义的变量。语法合法,但不推荐:将来想再加变量,就得回去改定义那一行,容易乱。

写法 3:匿名结构体(连名字都不起)

struct {
    char   name[20];
    int    age;
    double score;
} s1;                       // 一次性用品:只有 s1 能用,再想造 s2 就没名字可用了

特殊场合才用(比如某个嵌套里只需要一个成员)。新手别用——你没法再声明第二个同类型的变量。

三、"定义类型"和"定义变量"是两件事

这是本节最需要分清的一点:

动作 代码 干什么 占内存吗
定义类型 struct Student { ... }; 告诉编译器"有这么一种模板" 不占
定义变量 Student s1; 照着模板真正分配一块空间 占(28 字节)

打个比方:struct Student 是表格模板(印在纸上),Student s1 是照着模板打印出来的一张具体表格(填了张三的数据)。你可以印一万张,模板本身还是那张纸。

struct Student {
    char   name[20];
    int    age;
    double score;
};                          // 到这一行为止,内存里一个字节都没分配

Student s1;                 // 现在才有了第一块 28 字节的空间
Student s2;                 // 又一块
Student arr[30];            // 一下子 30 块(8.4)

四、取名与排版约定

教材和多数工程都遵循这几条,照着写就对了:

  1. 结构体名首字母大写(Student、Point、Date)——用来和变量名(s1、p)区分,一眼看出"这是模板"。
  2. 成员名用有意义的小写(name / age / score,而不是 a / b / c)——结构体的意义就在于让人看懂"一条记录由什么组成"。
  3. 成员按类型从小到大排——这不只是为了好看,能省内存(8.1.5 的"对齐"会讲清楚)。
  4. 结尾分号单独顶格写,别藏在成员后面。

关于 typedef:你可能会在教材或老代码里看到 typedef struct Student { ... } Student;,那是给结构体起别名,8.2 专门讲。

五、动手 Demo:把图纸画出来,看它占不占内存

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

// ① 先定义类型(此刻不占内存)
struct Point {
    int x;
    int y;
};

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

int main() {
    // ② 看"类型"本身的大小
    cout << "sizeof(Point)   = " << sizeof(Point)   << endl;
    cout << "sizeof(Student) = " << sizeof(Student) << endl;

    // ③ 造变量,此刻才占用内存
    Point  p1;
    p1.x = 3;  p1.y = 4;
    cout << "p1 = (" << p1.x << ", " << p1.y << ")" << endl;

    Student s1;
    strcpy(s1.name, "张三");
    s1.age = 18;  s1.score = 92.5;
    cout << s1.name << " " << s1.age << " " << s1.score << endl;
    return 0;
}

看到什么算成功:依次输出 sizeof(Point) = 8、sizeof(Student) = 32、p1 = (3, 4)、张三 18 92.5。

读结果:Point 两个 int 恰好 8 字节;Student 是 32(20+4+8,三种成员正好排得下)。但并不总是这么"正好"——有些组合会凭空多出几个字节的空白,那就是"对齐",下一节 8.1.5 揭晓。

30 秒实验:故意把定义末尾的分号删掉:

struct Point {
    int x;
    int y;
}                           // ← 少了一个分号

编译,记下报错的行号和信息。你会发现报错常常指向下一行的 struct Student——这正是 1.3.2 说过的"漏分号,报错常报在下一行"的典型病例。

六、常见误区

  1. "末尾分号可写可不写"——必须有。少了它,编译器会把下一个 struct 当成"接着声明变量",报出一堆莫名其妙的错。
  2. "定义完就能用了"——定义完只得到了类型名。要用 Student s1; 造出变量才能存数据(8.1.2)。sizeof(Student) 能问大小,但那不是"有了一块你的内存"。
  3. "成员列表里可以给默认值"——老标准里不行(int age = 18; 在 C++11 之前不允许)。新版 C++ 允许了,但教材环境(Dev-C++ 的老 MinGW)多半不支持,老老实实在初始化里填(8.1.3)。
  4. "结构体名和变量名可以一样"——语法上勉强能过,但极易看晕。按约定:类型首字母大写、变量小写。
  5. "匿名结构体更省事"——它没法复用:只能在那一次声明里定义变量,之后再也找不到这个类型名。除非你确定只用一次,否则别用。
自测三问(点开对答案) 1. 写出"定义一种叫 `Book` 的结构体,含书名、作者、价格"的代码(注意分号)。——`struct Book { char title[50]; char author[30]; double price; };` 2. `struct Student { ... };` 这一句执行完,内存里多了什么?——什么都没多。它只登记了**类型**;分配内存发生在 `Student s1;` 这类语句。 3. 为什么推荐"先定义类型、用到时再造变量"?——类型只写一次、集中好维护;变量按需创建,用途清楚。在定义处顺手造变量会让那一行变长难改。

相关笔记