结构体的初始化跟数组一个套路:把值按成员顺序装进花括号——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, "张三"} 试试。你会体会到:类型差得远就报错,类型能互转就悄悄装错。
六、常见误区
- "初始化列表可以按名字指定"——标准用法是按顺序(C99/C++20 有"指定初始化器"写法,但老编译器不支持,别用)。顺序错了不会提示你,只会装错位置。
- "
s1.name = "张三"应该可以吧"——不行。name是数组,数组名是地址常量,不能当赋值号左边(7.5 的老规矩)。用strcpy。 - "
={}和什么都不写是一回事"——完全不同。Student s;→ 垃圾值;Student s = {};→ 全部为 0。 - "初始化列表漏几项会报错"——不会,后面自动补 0。反过来说:漏填不是"没写",而是"写了 0",用的时候不会有提示,得自己心里有数。
- "输入字符串成员也用
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);`。**都不能用 `=`。**- ☐ 8.1.4 结构体的嵌套(下一节)
相关笔记
- 结构体变量 —— 上一节:造变量与访问成员
- 结构体的嵌套 —— 下一节:成员自己又是结构体
- 一维数组的初始化 —— 规则完全同源
- 字符数组 —— strcpy 与 getline 的出处
- 🔷 C++基础 · 索引 —— 全课路线图