认识 C++:为什么值得学?
C++ 是什么?
C++ 是一门 1979 年诞生于贝尔实验室的编程语言,作者是 Bjarne Stroustrup(本贾尼·斯特劳斯特卢普)。它最初叫「C with Classes」(带类的 C),1983 年正式改名为 C++ —— ++ 正是 C 语言里的「自增」运算符,寓意「C 的增强版」。
一句话概括它的性格:跑得快、管得细、本事大。它既像 C 语言一样贴近硬件、性能极高,又提供了「面向对象」等现代化的武器,四十多年长盛不衰,至今仍是全球最流行的编程语言之一。
C++ 都用在哪里?
你每天用到的软件,很多核心部分都是 C++ 写的:
程序是怎么跑起来的?
电脑的 CPU 只认识 0 和 1,并不认识 C++。所以你写好的代码(叫源代码)需要一位「翻译官」——编译器——把它翻译成电脑能直接执行的文件,然后运行才能看到结果:
源代码:你用文字写的程序文件(如 hello.cpp);编译:把源代码翻译成机器能执行的文件;可执行文件:翻译后的成品(Windows 上是 .exe);控制台:那个黑乎乎的窗口,程序在里面和你「对话」。
C++ 难吗?和其他语言比一比
| 语言 | 上手难度 | 运行速度 | 典型用途 |
|---|---|---|---|
| Python | ⭐ 最容易 | 较慢 | 数据分析、人工智能、脚本 |
| Java | ⭐⭐ 较容易 | 较快 | 企业后端、安卓 App |
| C++ | ⭐⭐⭐ 有一定深度 | 极快 | 游戏、系统软件、高性能程序 |
C++ 的概念确实比 Python 多一些,但这恰恰能让你真正理解「程序是怎么在电脑里跑的」,学完再学其他语言会非常轻松。本教程以 C++11 为基础,示例全部按 现代 C++(C++17 / C++20 可用)的写法来写,跟着一章一章敲代码,零基础完全可以学会。
搭建开发环境:准备好你的「厨房」
三条路,选一条就行
写 C++ 需要两样东西:编译器(翻译官)和编辑器(写代码的地方)。把它们打包在一起的软件叫 IDE(集成开发环境)。下面三条路任选其一即可:
| 方式 | 适合谁 | 特点 |
|---|---|---|
| 小熊猫 Dev-C++ | 完全新手(推荐) | 一个安装包全搞定,国内学校机房常用,界面简单 |
| VS Code + MinGW | 想用现代编辑器长期学习 | 界面漂亮、插件多,但安装配置步骤多一点 |
| 在线编译器 | 装不了软件 / 先试试水 | 打开网页就能写,零安装 |
方式一:小熊猫 Dev-C++(新手推荐)
- 下载安装:搜索「小熊猫 Dev-C++」(英文名 Red Panda Dev-C++)下载安装,一路「下一步」装完。别装名字里只有「Dev-C++」的经典版——它 2015 年后就停止更新了,对新标准支持差、还容易误报错误。
- 新建文件:打开软件,按 Ctrl + N 新建一个源代码。
- 保存:按 Ctrl + S 保存为
hello.cpp。建议先在桌面建一个cpp-learn文件夹统一存放,文件名不要用中文。 - 运行:写下第 3 章的第一个程序,按 F11(编译并运行)。弹出黑色窗口显示结果,就说明环境搭好了!
方式二:VS Code + MinGW
- 安装编译器:从 winlibs.com 下载 MinGW-w64 解压版(或用 MSYS2 安装),记住解压路径,例如
C:\mingw64。 - 配置环境变量:把
C:\mingw64\bin加入系统 PATH(设置 → 搜索「环境变量」 → 编辑 Path → 新建)。 - 验证:Win + R 输入
cmd打开一个新的命令行窗口,输入g++ --version,能看到版本号就成功了。 - 安装 VS Code 与官方「C/C++」扩展,新建
.cpp文件后点右上角 ▶ 按钮运行。
苹果电脑自带 clang 编译器:打开「终端」,输入 xcode-select --install 安装命令行工具,之后 g++ hello.cpp -o hello 的命令同样可用(此时 g++ 实际指向 clang)。
方式三:在线编译器(零安装)
打开「菜鸟教程在线工具」或 OnlineGDB 等网站,语言选择 C++,就能直接写代码点「运行」。适合快速试代码,但正式学习建议还是装一个本地环境。
认识命令行编译(理解原理)
无论用哪个 IDE,背后都是下面这条命令在工作。IDE 只不过是帮你自动执行了它而已:
g++ --version (检查编译器是否装好)
g++ hello.cpp -o hello (把 hello.cpp 编译成可执行文件)
hello (运行程序,Windows 下)
./hello (运行程序,macOS / Linux 下)| 命令片段 | 含义 |
|---|---|
g++ | 编译器程序的名字(GNU C++ 编译器) |
hello.cpp | 要被编译的源文件 |
-o hello | 「-o」是输出选项,指定生成的可执行文件叫 hello |
① 装完编译器后必须重新打开命令行窗口,新配置才会生效;② PATH 配错是最常见的问题,请逐字符对照检查路径;③ 源文件的后缀必须是 .cpp,别不小心保存成了 .txt。
选好你的工具并完成安装,新建一个 hello.cpp 文件(内容可以先随便打一行字)。能在屏幕上看到编译窗口弹出,本章就过关了。
第一个程序:Hello, World!
输入这段代码并运行
按照编程界 50 年的传统,你的第一个程序要在屏幕上打出「Hello, World!」。把下面的代码敲进编辑器(建议手敲,不要复制——肌肉记忆很重要),然后按 F11 运行:
#include <iostream> // 1. 把「输入输出工具箱」搬进来
using namespace std; // 2. 使用标准命名空间
int main() { // 3. main 函数:程序从这里开始执行
cout << "Hello, World!" << endl; // 4. 输出一行文字
return 0; // 5. 程序正常结束
}Hello, World!逐行解剖:每一行在干什么
| 行 | 代码 | 作用 |
|---|---|---|
| 1 | #include <iostream> | 把标准库里的「输入输出」功能包含进来,没有它就不能用 cout |
| 2 | using namespace std; | 标准库的东西都放在叫 std 的「抽屉」里,这句声明后就能直接写 cout,不必写 std::cout |
| 3 | int main() { | 主函数,整个程序的入口。每个 C++ 程序有且只有一个 main,系统从它开始执行 |
| 4 | cout << "..." << endl; | 把文字「流」到屏幕上;endl 表示换行。每条语句以 分号 结尾 |
| 5 | return 0; | main 函数结束,返回 0 告诉系统「程序正常退出」 |
一个 C++ 程序的标准骨架
两个立刻要养成的好习惯
① 写注释。注释是写给人看的说明,编译器会忽略它。重要的想法随手注释,一周后你会感谢自己:
// 这是单行注释:只能写一行
/*
这是多行注释:
可以写很多行说明
*/② 注意符号。C++ 的所有符号(分号、引号、括号)都必须是英文半角。初学者 80% 的报错都来自中文标点(如「;」「"」)。
① 语句结尾忘写分号;② 用了中文标点;③ main 拼成了 Main——C++ 严格区分大小写。
3-1:让程序输出三行:你的名字、你的爱好、一句想对自己说的话。
3-2(挑战):只用 cout,打印出下面这个图案。
*
**
***👀 查看参考答案(3-2)
#include <iostream>
using namespace std;
int main() {
cout << "*" << endl;
cout << "**" << endl;
cout << "***" << endl;
return 0;
}变量与数据类型:给数据安个家
变量是什么?
程序要处理数据,数据需要放在内存里。变量就是内存里的一格储物空间:它有名字(方便你找到它)、有类型(决定格子多大、能放什么)、有值(里面放的东西,可以随时更换)。
五种最常用的类型
#include <iostream>
#include <string> // 使用 string 要额外包含它
using namespace std;
int main() {
int age = 18; // 整数
double height = 1.75; // 小数(浮点数)
char grade = 'A'; // 单个字符,用单引号
bool isBoy = true; // 布尔值:true / false
string name = "小明"; // 字符串(一串字符),用双引号
cout << "姓名:" << name << endl;
cout << "年龄:" << age << endl;
cout << "身高:" << height << endl;
cout << "等级:" << grade << endl;
cout << "是男生吗:" << isBoy << endl;
return 0;
}姓名:小明
年龄:18
身高:1.75
等级:A
是男生吗:1C++ 里 true 本质上就是 1、false 是 0。想让它显示 true/false,在输出前加一句 cout << boolalpha; 即可。
| 类型 | 存什么 | 大小 | 例子 |
|---|---|---|---|
int | 整数 | 4 字节(约 ±21 亿) | int age = 18; |
double | 小数 | 8 字节(15~16 位有效数字) | double pi = 3.14159; |
char | 单个字符 | 1 字节 | char c = 'A'; |
bool | 真 / 假 | 1 字节 | bool ok = true; |
string | 一串文字 | 不固定 | string s = "hello"; |
想亲眼看看每种类型占多大?sizeof 可以测量(单位是字节):
cout << "int 占 " << sizeof(int) << " 字节" << endl; // 4
cout << "double 占 " << sizeof(double) << " 字节" << endl; // 8
cout << "char 占 " << sizeof(char) << " 字节" << endl; // 1变量的命名规则
- 只能由字母、数字、下划线组成,且不能以数字开头(
2name❌,name2✔)。 - 区分大小写:
Age和age是两个不同的变量。 - 不能用关键字(如
int、return)作名字。 - 起名要有意义:
score好,a、x1差。多个单词推荐「驼峰式」:maxScore、userName。
常量:不许改动的值
有些值从头到尾不该变,比如圆周率。加上 const,谁改它就报错:
const double PI = 3.14159; // 常量:初始化后不能再改
PI = 3.14; // ❌ 编译报错!不许修改常量一定要避开的坑:整数除法
两个整数相除,结果还是整数——小数部分直接被砍掉(不是四舍五入):
int a = 5, b = 2;
cout << a / b << endl; // 2 ← 整数除法,0.5 被丢掉了!
cout << a % b << endl; // 1 ← % 取余数(只对整数有效)
cout << 5.0 / 2 << endl; // 2.5 ← 只要有一个是小数,就按小数除
double x = 3.9;
int y = (int)x; // 强制类型转换:得到 3(直接砍掉小数)
cout << y << endl;让参与除法的数至少有一个是小数:5.0 / 2,或者先转换 (double)a / b。这个坑以后写「求平均分」时几乎人人会踩一次。
4-1:定义变量保存一本书的书名、价格、页数,用 cout 整齐地输出它们。
4-2:小明买了 3 支笔共花 10 元,用程序算出平均每支多少钱(提示:注意整数除法!答案是 3.33333)。
👀 查看参考答案(4-2)
int total = 10, count = 3;
double avg = (double)total / count; // 先转成 double 再除
cout << "平均每支 " << avg << " 元" << endl; // 3.33333输入与输出:程序和用户对话
cout 输出、cin 读入用户输入,学会保留小数位数的格式化输出。数据流动的方向
cin(读作 see-in)把用户敲的字读进变量;cout(see-out)把内容输出到屏幕。箭头方向就是数据流动的方向,非常好记:
cin:把输入装进变量
#include <iostream>
using namespace std;
int main() {
int age;
cout << "请输入你的年龄:";
cin >> age; // 读入一个整数,存进 age
cout << "明年你就 " << age + 1 << " 岁啦" << endl;
double x, y;
cout << "请输入两个数(空格分隔):";
cin >> x >> y; // 一次读两个,用空格或回车分隔
cout << x << " + " << y << " = " << x + y << endl;
return 0;
}请输入你的年龄:17
明年你就 18 岁啦
请输入两个数(空格分隔):3.5 2
3.5 + 2 = 5.5读一句话:getline
cin >> 遇到空格就停止,读不了「New York」这样带空格的句子。想读一整行,用 getline:
string city;
cout << "你想去哪个城市?";
getline(cin, city); // 读入一整行,包括空格
cout << city << " 是个好地方!" << endl;先用 cin >> 再用 getline 时,cin 会把残留的「回车」留给 getline,导致它读到空内容。新手阶段可以先避免混用;解决办法是在 getline 前加一句 cin.ignore(); 把残留的回车吃掉。
格式化输出:控制小数位数
算出 12.56636 这种一长串小数很难看。用 <iomanip> 里的 fixed 和 setprecision 控制保留几位:
#include <iostream>
#include <iomanip> // setprecision 住在这里
using namespace std;
int main() {
double r = 2.0;
double area = 3.14159 * r * r;
cout << area << endl; // 12.5664(默认保留到有效位)
cout << fixed << setprecision(2) << area << endl; // 12.57(固定 2 位小数)
return 0;
}5-1:让用户输入体重(公斤)和身高(米),输出 BMI 指数(BMI = 体重 ÷ 身高²),保留 1 位小数。
👀 查看参考答案(5-1)
#include <iostream>
#include <iomanip>
using namespace std;
int main() {
double w, h;
cout << "请输入体重(公斤)和身高(米):";
cin >> w >> h;
cout << "你的 BMI 是 " << fixed << setprecision(1) << w / (h * h) << endl;
return 0;
}运算符:数据的加工术
算术运算符
| 运算符 | 含义 | 例子 | 结果 |
|---|---|---|---|
+ - * | 加、减、乘 | 7 * 3 | 21 |
/ | 除(整数除法会丢小数!见第 4 章) | 7 / 2 | 3 |
% | 取余数(只对整数有效) | 7 % 2 | 1 |
++ | 自增 1 | i++ | i 变成 i+1 |
-- | 自减 1 | i-- | i 变成 i-1 |
判断奇偶:n % 2 == 0 是偶数;判断倍数:n % 3 == 0 能被 3 整除;取个位数:n % 10。
赋值运算符的简便写法
int score = 60;
score += 10; // 等价于 score = score + 10; → 70
score -= 5; // 等价于 score = score - 5; → 65
score *= 2; // 等价于 score = score * 2; → 130
score /= 13; // 等价于 score = score / 13; → 10关系运算符:比较大小
比较的结果是 bool 值(真 true / 假 false),它是第 7 章 if 判断的原料:
int a = 5, b = 3;
cout << (a > b) << endl; // 1(真)
cout << (a == b) << endl; // 0(假) == 是「等于吗?」
cout << (a != b) << endl; // 1(真) != 是「不等于吗?」逻辑运算符:把多个条件串起来
| 运算符 | 含义 | 真 | 记忆 |
|---|---|---|---|
&& | 与(并且) | 两边都真才真 | 「既要…又要…」 |
|| | 或(或者) | 一边真就真 | 「有一样就行」 |
! | 非(取反) | 真假互换 | 「唱反调」 |
int age = 20;
bool hasTicket = true;
// 年满 18 并且有票,才能进场
cout << (age >= 18 && hasTicket) << endl; // 1
// 周六或周日放假(假设 day = 6 表示周六)
int day = 6;
cout << (day == 6 || day == 7) << endl; // 1
// 逻辑与还有「短路」特性:左边能定结论,右边就不算了
int x = 0;
bool r = (x != 0) && (10 / x > 1); // 左边为假 → 右边 10/x 根本不执行,避免了除零!优先级:谁先算?
完整优先级表有几十条,新手只需记住这条简化口诀:
括号 () → 自增自减 → 乘除取余 * / % → 加减 + - → 比较 > < == != → 逻辑 && → 逻辑 || → 赋值 =
拿不准就加括号——加括号永远不会错,还能让代码更好读。
年度第一大坑:= 和 ==
int a = 10;
if (a = 5) // ❌ 错!这是「把 5 赋值给 a」,赋值结果是 5(真),条件永远成立
if (a == 5) // ✅ 对!这是「a 等于 5 吗?」的比较记住:一个等号是「赋值」,两个等号是「判断相等」。
6-1:写一个表达式,判断年份 year 是否为闰年(能被 4 整除但不能被 100 整除,或者能被 400 整除),输出 1 或 0。
👀 查看参考答案(6-1)
int year = 2024;
bool isLeap = (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
cout << year << (isLeap ? " 是闰年" : " 不是闰年") << endl; // 2024 是闰年分支结构:让程序学会做选择
if / else:如果…否则…
程序真正的「智能」从这里开始:根据条件不同,走不同的路。先看流程图,再对代码:
#include <iostream>
using namespace std;
int main() {
int score;
cout << "请输入成绩:";
cin >> score;
if (score >= 90) {
cout << "优秀" << endl;
} else if (score >= 80) {
cout << "良好" << endl;
} else if (score >= 60) {
cout << "及格" << endl;
} else {
cout << "不及格" << endl;
}
return 0;
}else if可以写多个,从上往下依次检查,命中一个就不再看后面的。else兜底,前面全不成立时执行。- 条件成立要执行多条语句时,必须用大括号
{ }包起来;只有一条语句时大括号可省略,但新手建议永远写上,避免出错。
switch:按具体的值分派任务
当判断的是某个具体的值(等级、菜单选项、星期几)而不是范围时,switch 更清爽:
char level;
cin >> level;
switch (level) {
case 'A':
cout << "优秀" << endl;
break; // break 表示「办完就走」,千万别忘!
case 'B':
cout << "良好" << endl;
break;
case 'C':
cout << "加油" << endl;
break;
default: // 相当于 else,前面都不匹配时执行
cout << "无效等级" << endl;
}case 'A':
cout << "优秀" << endl; // 没有 break!
case 'B':
cout << "良好" << endl; // 输入 A 会一路「掉」到这里继续执行没有 break,程序会从匹配的 case 一路执行到下一个 break 才停,这叫「击穿」。
三目运算符:一行版 if-else
int a = 7, b = 4;
int maxVal = (a > b) ? a : b; // 读作:a 大吗?大就取 a,否则取 b
cout << maxVal << endl; // 77-1:输入一个整数,输出它是奇数还是偶数。
7-2:输入三个整数,输出其中最大的那个(试试只用 if 完成一次,再用三目运算符完成一次)。
👀 查看参考答案(7-2)
int a, b, c;
cin >> a >> b >> c;
int maxVal = a;
if (b > maxVal) maxVal = b;
if (c > maxVal) maxVal = c;
cout << "最大值是 " << maxVal << endl;循环结构:让电脑替你重复劳动
为什么需要循环?
打印 1 到 5 难道要写 5 行 cout?10000 次呢?循环就是让电脑重复做一件事,你只要告诉它:从哪开始、重复到什么时候、每次做什么。
for:知道要重复几次时用
#include <iostream>
using namespace std;
int main() {
// ↓ 初始化 ↓ 条件 ↓ 更新
for (int i = 1; i <= 5; i++) {
cout << "第 " << i << " 次循环" << endl;
}
return 0;
}第 1 次循环
第 2 次循环
第 3 次循环
第 4 次循环
第 5 次循环while:只知道「什么时候停」时用
经典任务:求 1+2+…+100。用一个变量当「累加器」:
int i = 1, sum = 0;
while (i <= 100) {
sum += i; // 累加进 sum
i++; // ⚠️ 千万别忘了更新 i,否则 i 永远是 1,死循环!
}
cout << "1+2+...+100 = " << sum << endl; // 5050语法不同,能力相同。经验法则:次数固定用 for,条件触发用 while(比如「一直读输入直到遇到 0」)。
do-while:至少执行一次
先干活、后判断——最适合「菜单:先显示菜单,再问用户要不要继续」:
int choice;
do {
cout << "===== 菜单 =====" << endl;
cout << "1. 存款 2. 取款 0. 退出" << endl;
cout << "请选择:";
cin >> choice;
} while (choice != 0); // 只要用户没选 0,就一直显示菜单break 与 continue:循环的两个遥控器
// break:立刻跳出整个循环
for (int i = 1; i <= 10; i++) {
if (i == 5) break; // 到 5 就收工
cout << i << " "; // 输出:1 2 3 4
}
cout << endl;
// continue:跳过本轮,直接进入下一轮
for (int i = 1; i <= 10; i++) {
if (i % 2 == 0) continue; // 偶数跳过
cout << i << " "; // 输出:1 3 5 7 9
}如果循环条件永远成立(如 while(true) 忘了 break),程序会卡住。在控制台按 Ctrl + C 强制结束,然后检查循环变量有没有被更新。
嵌套循环:九九乘法表
循环里套循环:外层管「第几行」,内层管「这一行打印几列」:
#include <iostream>
using namespace std;
int main() {
for (int i = 1; i <= 9; i++) { // 外层:第 i 行
for (int j = 1; j <= i; j++) { // 内层:第 i 行有 i 列
cout << j << "×" << i << "=" << i * j << "\t";
}
cout << endl; // 打完一行换行
}
return 0;
}1×1=1
1×2=2 2×2=4
1×3=3 2×3=6 3×3=9
1×4=4 2×4=8 3×4=12 4×4=16
1×5=5 2×5=10 3×5=15 4×5=20 5×5=25
...(后面还有 4 行)\t 是制表符,让每列对齐——它属于「转义字符」,常用的还有 \n 换行、\\ 反斜杠、\" 双引号。
8-1:输入 n,计算 n!(阶乘)= 1×2×3×…×n。
8-2:打印一个 5 行的直角三角形(每行星号数 = 行号)。
8-3(挑战):找出 100~999 中所有的「水仙花数」(各位数字立方和等于它本身,如 153 = 1³+5³+3³)。
👀 查看参考答案(8-1 和 8-2)
// 8-1 阶乘
int n, fact = 1;
cin >> n;
for (int i = 1; i <= n; i++) fact *= i;
cout << n << "! = " << fact << endl;
// 8-2 直角三角形
for (int i = 1; i <= 5; i++) {
for (int j = 1; j <= i; j++) cout << "*";
cout << endl;
}函数:把代码打包复用
为什么需要函数?
同一段「求和」代码如果要在 10 个地方用,复制粘贴 10 遍太蠢了。函数就像一台洗衣机:把脏衣服(参数)放进去,按开始(调用),拿回干净衣服(返回值)。写一次,到处用。
定义与调用
#include <iostream>
using namespace std;
// 返回类型 函数名(参数列表)
int add(int a, int b) {
return a + b; // 把计算结果还给调用者
}
int main() {
int s = add(3, 5); // 调用函数,s 得到 8
cout << "3 + 5 = " << s << endl;
cout << add(10, 20) << endl; // 返回值也可以直接输出
return 0;
}- 形参(
a、b)是函数的「收件地址」,实参(3、5)是真正寄出的值。 - 不需要返回值时,返回类型写
void,函数里可以不写 return(或只写return;)。 - 如果函数定义在 main 之后,需要在 main 前先写一行声明:
int add(int a, int b);——新手最简单的做法是把所有函数写在 main 上面。
值传递 vs 引用传递(重要!)
默认情况下,函数拿到的是实参的复印件(值传递),改复印件不影响原件。参数前加 & 就变成拿原件(引用传递):
#include <iostream>
using namespace std;
void swapWrong(int a, int b) { // 值传递:只交换了复印件
int t = a; a = b; b = t;
}
void swapRight(int &a, int &b) { // 引用传递:直接操作原件
int t = a; a = b; b = t;
}
int main() {
int x = 1, y = 2;
swapWrong(x, y);
cout << x << " " << y << endl; // 1 2 (没换成功)
swapRight(x, y);
cout << x << " " << y << endl; // 2 1 (换成功了!)
return 0;
}默认参数与函数重载
// 默认参数:不传就用默认值
void greet(string name, string msg = "你好") {
cout << msg << "," << name << endl;
}
// greet("小明"); → 你好,小明
// greet("小明", "早上好"); → 早上好,小明
// 重载:同名函数,参数不同(个数或类型),各干各的
int add(int a, int b) { return a + b; }
double add(double a, double b) { return a + b; }
int add(int a, int b, int c) { return a + b + c; }9-1:写函数 bool isPrime(int n) 判断 n 是否为质数,然后在 main 里输出 2~50 的所有质数。
9-2:写函数 void printRect(int w, int h),打印 w×h 的星号矩形。
👀 查看参考答案(9-1)
bool isPrime(int n) {
if (n < 2) return false;
for (int i = 2; i * i <= n; i++) // 只需试到 √n
if (n % i == 0) return false;
return true;
}
int main() {
for (int i = 2; i <= 50; i++)
if (isPrime(i)) cout << i << " ";
return 0;
}数组:一排相同类型的储物格
数组长什么样?
要存全班 50 个人的成绩,总不能定义 50 个变量吧?数组是「同类型变量的连续排列」,一个名字管一排:
int scores[5] = {90, 85, 77, 92, 68}; // 声明并初始化
cout << scores[0] << endl; // 90 ← 第 1 个元素,下标是 0!
cout << scores[4] << endl; // 68 ← 最后一个元素
scores[2] = 80; // 修改第 3 个元素
int part[5]; // 只声明不初始化(里面是随机值,用前必须赋值)
int zeros[5] = {}; // 全部初始化为 0遍历:for 循环是数组的最佳拍档
#include <iostream>
using namespace std;
int main() {
int scores[5] = {90, 85, 77, 92, 68};
int n = 5;
int sum = 0;
int maxScore = scores[0]; // 先假设第 0 个最大
for (int i = 0; i < n; i++) {
cout << "第 " << i + 1 << " 个成绩:" << scores[i] << endl;
sum += scores[i];
if (scores[i] > maxScore) maxScore = scores[i];
}
cout << "总分:" << sum << endl;
cout << "平均分:" << (double)sum / n << endl; // 转成 double 再除!
cout << "最高分:" << maxScore << endl;
return 0;
}C++11 还有更简洁的「范围 for」——不用管下标,直接挨个拿元素:
for (int s : scores) { // 依次取出每个元素,存到 s
cout << s << " ";
}二维数组:给数据加个表格结构
grid[行][列],像 Excel 表格一样存数据:
int grid[2][3] = { // 2 行 3 列
{1, 2, 3},
{4, 5, 6}
};
for (int i = 0; i < 2; i++) { // 外层:每一行
for (int j = 0; j < 3; j++) { // 内层:这一行的每一列
cout << grid[i][j] << " ";
}
cout << endl;
}1 2 3
4 5 6长度为 5 的数组,合法下标是 0~4。scores[5] 不存在,但编译器往往不报错——程序会去读写别人的内存,结果不可预测。写循环时反复确认:i < n 而不是 i <= n。
📦 进阶了解:数组作为函数参数
数组传给函数时,传的是首元素的地址(不是整组拷贝),所以函数里能直接修改原数组;但也因为丢了长度信息,习惯上要额外传一个长度参数:
int sumArray(int arr[], int n) { // arr[] 实际是指向首元素的地址
int s = 0;
for (int i = 0; i < n; i++) s += arr[i];
return s;
}
// 调用:sumArray(scores, 5)10-1:让用户输入 10 个整数存进数组,输出它们的平均值、最大值及其下标。
10-2(挑战):把数组「倒过来」——第一个元素和最后一个交换、第二个和倒数第二个交换……原地完成。
👀 查看参考答案(10-2)
int a[6] = {1, 2, 3, 4, 5, 6};
int n = 6;
for (int i = 0; i < n / 2; i++) { // 只需走一半
int t = a[i];
a[i] = a[n - 1 - i]; // 与「镜像位置」交换
a[n - 1 - i] = t;
}
for (int x : a) cout << x << " "; // 6 5 4 3 2 1字符串 string:文字处理利器
string 的常用操作一表通
| 操作 | 写法 | 说明 |
|---|---|---|
| 求长度 | s.length() 或 s.size() | 字符个数 |
| 下标访问 | s[0] | 第 1 个字符(从 0 开始,和数组一样) |
| 拼接 | s1 + s2、s1 += s2 | 直接用加号 |
| 比较 | s1 == s2、s1 < s2 | 按内容比较 / 字典序比较 |
| 截取 | s.substr(起始下标, 长度) | 取子串 |
| 查找 | s.find("xx") | 返回第一次出现的下标;找不到返回 string::npos |
| 判空 | s.empty() | 是否为空字符串 |
#include <iostream>
#include <string>
using namespace std;
int main() {
string s = "Hello, C++";
cout << s.length() << endl; // 10(长度)
cout << s[0] << endl; // H(第一个字符)
cout << s.substr(7, 3) << endl; // C++(从下标 7 取 3 个字符)
cout << s.find("C++") << endl; // 7(C++ 从下标 7 开始)
s[0] = 'h'; // 修改单个字符
string t = s + " !"; // 拼接
cout << t << endl; // hello, C++ !
return 0;
}遍历每个字符
#include <cctype> // isupper / tolower 等字符工具在这里
#include <iostream>
#include <string>
using namespace std;
int main() {
string str = "Hello World";
int upper = 0;
for (char c : str) { // 逐个取出字符
if (isupper(c)) upper++; // 是大写字母就计数
}
cout << "大写字母有 " << upper << " 个" << endl; // 2
// 全部转成大写
for (char &c : str) { // 注意加 & 才能修改原字符
c = toupper(c);
}
cout << str << endl; // HELLO WORLD
return 0;
}'A' 是 char(单个字符,单引号),"A" 是 string(字符串,双引号)——它们是两种不同的类型。
11-1:输入一个单词,判断它是不是「回文」(正着读反着读一样,如 level)。
11-2:统计一句话里某个字母出现了几次。
👀 查看参考答案(11-1)
bool isPalin(string s) {
int i = 0, j = s.length() - 1; // 一个从头走,一个从尾走
while (i < j) {
if (s[i] != s[j]) return false;
i++; j--;
}
return true;
}
// isPalin("level") → true;isPalin("hello") → false指针与引用:直捣内存深处
先建立直觉:内存像一家酒店
变量住进内存这家酒店:值是房间里的客人,地址是房间号。&a 查房间号,指针 p 是一张「写着房间号的纸条」,拿着纸条(*p)就能找到那个房间、进出里面的人。
指针三板斧:&、*、nullptr
#include <iostream>
using namespace std;
int main() {
int a = 10;
int* p = &a; // p 存了 a 的地址(& 读作「取地址」)
cout << &a << endl; // 0x6ffe14 之类的地址(每次运行可能不同)
cout << p << endl; // 同上:p 里存的就是这个地址
cout << *p << endl; // 10(* 读作「取内容」,顺着地址找到 a)
*p = 20; // 通过指针修改 a
cout << a << endl; // 20
int* q = nullptr; // 空指针:暂时不指向任何地方
if (q != nullptr) { // 先判空再用,安全第一!
cout << *q << endl;
}
return 0;
}刚声明的指针里是随机垃圾值,直接 *p 去用它,程序轻则输出乱码、重则崩溃。规矩是:指针要么指向有效变量,要么置为 nullptr,解引用前先判空。
引用:变量的「别名」
引用比指针简单:int& r = a; 之后 r 就是 a 的另一个名字,操作 r 就是操作 a:
int a = 10;
int& r = a; // r 是 a 的别名,从此不分你我
r = 30; // 改 r 就是改 a
cout << a << endl; // 30
cout << &r << " " << &a << endl; // 地址完全相同指针 vs 引用,用哪个?
| 对比 | 指针 int* p | 引用 int& r |
|---|---|---|
| 本质 | 存地址的变量 | 别名 |
| 可以为空 | 可以(nullptr) | 不行,必须绑定一个变量 |
| 能换目标吗 | 能,p = &b; | 不能,绑定终身不变 |
| 使用方式 | 要写 *p 解引用 | 和普通变量一样直接用 |
| 典型场景 | 遍历、可选目标、底层数据结构 | 函数传参(第 9 章的 swap) |
指针是 C++ 区别于其他语言的看家本领,也是后面「链表」「动态内存」的地基。本教程只在传参和遍历时轻度使用它,先建立概念即可,不必恐惧。
📦 进阶了解:指针和数组的关系
数组名在多数场合会「退化」为首元素地址,所以 a[i] 本质上等价于 *(a + i)——C++ 用「地址 + 偏移」实现下标访问,这也解释了为什么数组下标从 0 开始(偏移量为 0 就是第一个)。
12-1:定义两个变量,用指针(不是引用)交换它们的值,并输出交换前后各自的地址验证。
👀 查看参考答案(12-1)
int a = 1, b = 2;
int* pa = &a, * pb = &b;
int t = *pa; // 借助 *pa / *pb 读写
*pa = *pb;
*pb = t;
cout << a << " " << b << endl; // 2 1结构体 struct:把相关数据打包
为什么需要结构体?
描述一个学生,需要姓名、年龄、成绩……散装变量管起来太乱。结构体把一组相关的数据装进一个「档案袋」,从此一个变量代表一个完整的学生:
#include <iostream>
#include <string>
using namespace std;
struct Student { // 定义一种新类型:学生
string name; // 姓名
int age; // 年龄
double score; // 成绩
}; // ← 这个分号不能忘!
int main() {
Student s1 = {"小明", 18, 92.5}; // 创建并初始化
Student s2;
s2.name = "小红"; // 用 . 访问每个成员
s2.age = 17;
s2.score = 95.0;
cout << s1.name << " " << s1.score << endl;
cout << s2.name << " " << s2.score << endl;
return 0;
}小明 92.5
小红 95结构体数组:一个班的学生
Student cls[3] = {
{"小明", 18, 92.5},
{"小红", 17, 95.0},
{"小刚", 18, 88.0}
};
for (int i = 0; i < 3; i++) {
cout << cls[i].name << ":" << cls[i].score << " 分" << endl;
}结构体套结构体
成员本身也可以是结构体,层层打包:
struct Date { int year, month, day; };
struct Person {
string name;
Date birthday; // 生日又是一个结构体
};
Person p = {"小明", {2010, 5, 20}};
cout << p.birthday.year << " 年" << endl; // 2010 年结构体是「纯数据打包」,下一章的 class 会在此基础上加入「函数」和「访问控制」。可以说:class = struct + 行为 + 权限管理。把这一章学扎实,面向对象就成功了一半。
13-1:定义 struct Book(书名、作者、价格),创建 3 本书存进数组,找出最贵的那本并输出。
👀 查看参考答案(13-1)
struct Book { string title; string author; double price; };
Book books[3] = {
{"西游记", "吴承恩", 45.0},
{"三体", "刘慈欣", 23.0},
{"红楼梦", "曹雪芹", 59.8}
};
int best = 0;
for (int i = 1; i < 3; i++)
if (books[i].price > books[best].price) best = i;
cout << "最贵:" << books[best].title << " " << books[best].price << " 元" << endl;类与对象:面向对象编程入门
从 struct 到 class:数据 + 行为
第 13 章的 Student 只有数据。现实里的学生还会「自我介绍」「修改成绩」——把这些行为也装进类型里,就是类(class)。类是图纸,对象(object)是按图纸造出的一个个具体实例:
封装:把「不许碰」的数据保护起来
private 的成员外面摸不到,只能通过 public 的函数访问——这样数据就只能被「受控地」修改,比如成绩不允许设成 120 分:
#include <iostream>
#include <string>
using namespace std;
class Student {
private: // 私有区:外面不能直接碰
string name;
double score;
public: // 公开区:外界通过这些函数使用类
// 构造函数:与类同名、无返回类型,创建对象时自动调用
Student(string n, double s) {
name = n;
score = s;
}
void show() { // 成员函数:行为
cout << name << " 的成绩是 " << score << endl;
}
void setScore(double s) { // 受控的修改入口,可以加校验
if (s >= 0 && s <= 100)
score = s;
else
cout << "成绩必须在 0~100 之间!" << endl;
}
};
int main() {
Student stu("小明", 92.5); // 创建对象(自动调用构造函数)
stu.show(); // 小明 的成绩是 92.5
stu.setScore(120); // 成绩必须在 0~100 之间!
stu.setScore(88);
stu.show(); // 小明 的成绩是 88
return 0;
}Student(string n, double s) : name(n), score(s) {}冒号后面这叫「初始化列表」,效果和函数体里赋值一样,实际工程更常用。
继承:站在父类的肩膀上
猫和狗都有「吃」「睡」等共同点,把这些放进父类 Animal,子类只补充自己的特色——代码不用重复写:
#include <iostream>
using namespace std;
class Animal {
public:
void eat() { cout << "吃东西" << endl; }
virtual void speak() { cout << "..." << endl; }
// virtual:允许子类重写这个函数
};
class Dog : public Animal { // 读作:Dog 是一种 Animal
public:
void speak() override { // 重写父类的 speak
cout << "汪汪!" << endl;
}
};
class Cat : public Animal {
public:
void speak() override {
cout << "喵~" << endl;
}
};
int main() {
Dog d; Cat c;
d.eat(); d.speak(); // 汪汪!(eat 是继承来的,speak 是重写的)
c.eat(); c.speak(); // 喵~
Animal* a = &d; // 用父类指针指向子类对象
a->speak(); // 汪汪! ← 多态:同一句话,不同对象有不同反应
a = &c;
a->speak(); // 喵~
return 0;
}多态(polymorphism)正是上面的名场面:同样是 a->speak(),指向狗就叫「汪汪」,指向猫就叫「喵」。调用哪个版本由对象的实际类型在运行时决定。父类指针访问成员用 ->(而不是 .)。
面向对象三大特性小结
📦 进阶了解:new / delete 动态创建对象
Student* p = new Student("小明", 90); // 在堆上创建对象,返回指针
p->show(); // 指针访问成员用 ->
delete p; // 用完必须释放,否则内存泄漏现代 C++ 更推荐用智能指针(unique_ptr / shared_ptr)自动管理,等你进阶后再学。
14-1:设计一个 BankAccount 类:私有成员是余额 balance;公有函数有 deposit(存款)、withdraw(取款,余额不足要拒绝)、show()。
14-2(挑战):以 Shape 为父类(有 area()),派生 Rect(长宽)和 Circle(半径),各自重写 area()。
STL 标准库:官方造好的轮子
STL 是什么?
STL(Standard Template Library,标准模板库)是 C++ 自带的「工具仓库」:常用数据结构 + 常用算法,全都写好、测试好、优化好了。以后遇到「要排序」「要去重」「要查频率」,先想想 STL 里有没有现成的。
| 工具 | 一句话理解 | 典型用途 |
|---|---|---|
vector | 会自动变长的数组 | 存数量不确定的一堆数据 |
map | 键值对字典 | 名字→电话、单词→出现次数 |
set | 自动去重的集合 | 判断「出现过没有」 |
sort | 万能排序函数 | 从小到大 / 自定义规则 |
vector:最常用的动态数组
#include <iostream>
#include <vector>
#include <algorithm> // sort 住在这里
using namespace std;
int main() {
vector<int> v; // 不用预先指定长度!
v.push_back(3); // 尾部添加元素
v.push_back(1);
v.push_back(2);
cout << v.size() << endl; // 3(当前元素个数)
cout << v[0] << endl; // 3(下标访问和数组一样)
sort(v.begin(), v.end()); // 排序:默认从小到大
for (int x : v) cout << x << " "; // 1 2 3
cout << endl;
sort(v.begin(), v.end(), greater<int>()); // 传 greater 就是从大到小
for (int x : v) cout << x << " "; // 3 2 1
return 0;
}push_back(x) 尾部添加;pop_back() 删除末尾;size() 个数;empty() 是否为空;clear() 清空。初始化也可以偷懒:vector<int> v = {3, 1, 2};
map:字典,一键查值
#include <map>
#include <iostream>
#include <string>
using namespace std;
int main() {
map<string, int> age; // 键类型 → 值类型
age["小明"] = 18;
age["小红"] = 17;
cout << age["小明"] << endl; // 18
if (age.count("小刚")) { // 判断键是否存在(0 或 1)
cout << "有小刚" << endl;
} else {
cout << "查无此人" << endl;
}
for (auto& p : age) { // 遍历:按键名自动排序
cout << p.first << " " << p.second << endl;
// p.first 是键,p.second 是值;auto 让编译器自动推断类型
}
return 0;
}set:自动去重的集合
#include <set>
set<int> s;
s.insert(3);
s.insert(1);
s.insert(3); // 插入重复的会被自动忽略
cout << s.size() << endl; // 2(自动去重)
if (s.count(1)) cout << "1 在集合里" << endl;
for (int x : s) cout << x << " "; // 1 3(自动从小到大)自定义排序规则(为实战项目做铺垫)
sort 的第三个参数可以传一个「比较规则」——写法是 lambda(匿名小函数):
vector<int> v = {3, 1, 2};
sort(v.begin(), v.end(), [](int a, int b) {
return a > b; // 告诉 sort:a 排在 b 前面的条件(降序)
});15-1:读入 5 个单词,用 map 统计每个单词出现的次数。
👀 查看参考答案(15-1)
map<string, int> cnt;
string w;
for (int i = 0; i < 5; i++) {
cin >> w;
cnt[w]++; // 键不存在时自动创建并初始化为 0,再 +1
}
for (auto& p : cnt)
cout << p.first << " 出现 " << p.second << " 次" << endl;文件读写:让数据留下来
为什么要操作文件?
到目前为止,你程序里的变量在程序一关闭就烟消云散。想让数据留下来(比如成绩单、日记),就要写进文件。好消息是:文件操作和 cin/cout 用法几乎一样——写文件用 <<,读文件用 >>。
写文件:ofstream
#include <fstream> // 文件操作都在这里
#include <iostream>
using namespace std;
int main() {
ofstream out("scores.txt"); // 打开文件用于写(没有会自动创建)
if (!out) { // 打不开(如路径不对)要检查
cout << "文件打开失败" << endl;
return 1;
}
out << "小明 92.5" << endl; // 用法和 cout 一模一样!
out << "小红 95" << endl;
out.close(); // 用完关闭
cout << "已写入 scores.txt" << endl;
return 0;
}运行后到程序的同一个文件夹里找 scores.txt,用记事本打开就能看到两行成绩。
读文件:ifstream
ifstream in("scores.txt");
if (!in) {
cout << "文件不存在" << endl;
return 1;
}
string name;
double score;
while (in >> name >> score) { // 一条条读,读到文件尾自动停
cout << name << " 考了 " << score << " 分" << endl;
}
in.close();小明 考了 92.5 分
小红 考了 95 分想按行读(保留原始格式),用 getline:
ifstream in("scores.txt");
string line;
while (getline(in, line)) { // 一次读一整行
cout << line << endl;
}追加模式:不覆盖旧内容
ofstream out("scores.txt", ios::app); // app = append 追加
out << "小刚 88" << endl; // 加在文件末尾,原来的不丢| 打开模式 | 含义 |
|---|---|
ios::out | 写(默认,会清空原内容) |
ios::in | 读 |
ios::app | 追加,保留原内容 |
ios::binary | 按二进制方式读写 |
16-1:写一个「电子日记」程序:启动后让用户输入一句话,把「日期 + 这句话」追加到 diary.txt 里,可以反复运行积累内容。
实战项目一:猜数字游戏 🎲
第一步:想清楚游戏规则
- 程序在 1~100 之间随机「想」一个数,玩家看不到。
- 玩家反复输入猜测,程序提示「大了」或「小了」。
- 猜中后公布答案和总次数。
翻译成程序结构:准备随机数 → do-while 循环(读猜测 → 比较提示)→ 直到猜中退出。这就是「先干活再判断」的典型场景,正好用上第 8 章的 do-while。
第二步:随机数怎么来?
#include <cstdlib> // rand / srand
#include <ctime> // time
srand(time(0)); // 播种:以当前时间为种子,每次运行结果不同
int answer = rand() % 100 + 1; // rand()%100 得到 0~99,+1 变成 1~100① 为什么播种?不播种的话,rand() 每次运行程序都给出同一串数。② 公式 rand() % N + 1 可以生成 1~N 的随机整数,记下来很有用。
rand() 是 C 语言留下的老接口,随机质量一般,而且对大范围取模会有轻微偏差。C++11 起更推荐 <random> 里的 std::mt19937 + uniform_int_distribution。入门阶段用 rand() 完全没问题,做正式项目时再换。
第三步:完整代码
#include <iostream>
#include <cstdlib> // rand、srand
#include <ctime> // time
using namespace std;
int main() {
srand(time(0)); // 用当前时间做随机种子
int answer = rand() % 100 + 1; // 1~100 的随机数
int guess, count = 0;
cout << "我已经想好了一个 1~100 之间的数,来猜猜看!" << endl;
do {
cout << "请输入你的猜测:";
cin >> guess;
count++;
if (guess > answer) {
cout << "大了,往小猜一点 ⬇" << endl;
} else if (guess < answer) {
cout << "小了,往大猜一点 ⬆" << endl;
} else {
cout << "🎉 恭喜猜对!答案就是 " << answer
<< ",你一共猜了 " << count << " 次" << endl;
}
} while (guess != answer); // 没猜中就继续
return 0;
}运行效果
我已经想好了一个 1~100 之间的数,来猜猜看!
请输入你的猜测:50
大了,往小猜一点 ⬇
请输入你的猜测:25
小了,往大猜一点 ⬆
请输入你的猜测:37
🎉 恭喜猜对!答案就是 37,你一共猜了 3 次项目复盘
| 需求 | 用到的知识 | 出自 |
|---|---|---|
| 随机答案 | rand / srand / 取余公式 | 本章 |
| 反复猜 | do-while 循环 | 第 8 章 |
| 大小提示 | if-else 分支 | 第 7 章 |
| 计数 | 累加器变量 count | 第 8 章 |
① 限制最多猜 7 次,超过就失败并公布答案;② 每轮结束后问「再来一局吗?(y/n)」;③ 把它改造成「人脑猜、电脑来提示范围」的逆向版。
实战项目二:学生成绩管理系统 📚
第一步:需求与设计
先想清楚这个软件要做什么、每个功能用到什么知识——写程序前先设计,是工程师的核心习惯:
| 功能 | 用到知识 | 出自 |
|---|---|---|
| 存学生(姓名+成绩) | struct 结构体 | 第 13 章 |
| 人数自动增长 | vector 动态数组 | 第 15 章 |
| 菜单循环 | while + switch | 第 7、8 章 |
| 各功能拆成函数 | 函数 | 第 9 章 |
| 按成绩排序 | sort + lambda | 第 15 章 |
| 表格对齐 / 平均分 | iomanip 格式化 | 第 5 章 |
程序骨架一句话:一个 while(true) 主循环显示菜单,switch 把每个选项分派给一个专门的函数——分而治之,每个函数只干一件事。
第二步:完整代码
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
#include <iomanip>
using namespace std;
// ---------- 数据设计 ----------
struct Student {
string name; // 姓名
double score; // 成绩
};
vector<Student> students; // 全班数据(动态增长)
// ---------- 功能函数 ----------
void addStudent() {
Student s;
cout << "请输入姓名:";
cin >> s.name;
cout << "请输入成绩:";
cin >> s.score;
students.push_back(s);
cout << "✔ 添加成功!当前共 " << students.size() << " 名学生" << endl;
}
void showAll() {
if (students.empty()) {
cout << "(还没有任何学生记录)" << endl;
return;
}
cout << "--------------------" << endl;
double sum = 0;
for (auto& s : students) { // 逐行打印
cout << " " << s.name << " " << s.score << endl;
sum += s.score;
}
cout << "--------------------" << endl;
cout << " 共 " << students.size() << " 人,平均分 "
<< fixed << setprecision(1) << sum / students.size() << endl;
}
void findMax() {
if (students.empty()) {
cout << "(还没有任何学生记录)" << endl;
return;
}
auto best = &students[0]; // 用指针指向当前最高分
for (auto& s : students)
if (s.score > best->score) best = &s;
cout << "🏆 最高分:" << best->name << " " << best->score << " 分" << endl;
}
void sortByScore() {
if (students.empty()) return;
sort(students.begin(), students.end(), [](const Student& a, const Student& b) {
return a.score > b.score; // 成绩从高到低
});
cout << "已按成绩从高到低排序:" << endl;
showAll();
}
// ---------- 主程序 ----------
int main() {
cout << "====== 学生成绩管理系统 ======" << endl;
while (true) {
cout << "\n1.添加学生 2.显示全部 3.查找最高分 4.按成绩排序 0.退出"
<< endl;
cout << "请选择:";
int choice;
cin >> choice;
if (choice == 0) break;
switch (choice) {
case 1: addStudent(); break;
case 2: showAll(); break;
case 3: findMax(); break;
case 4: sortByScore(); break;
default: cout << "无效选项,请重新输入" << endl;
}
}
cout << "已退出,再见!" << endl;
return 0;
}① auto:让编译器根据右边自动推断类型,auto& s : students 就是「挨个引用每个学生」,比写全名省事;② lambda [](...){...} 是匿名小函数,这里只用来告诉 sort 排序规则,第 15 章讲过。
运行效果
====== 学生成绩管理系统 ======
1.添加学生 2.显示全部 3.查找最高分 4.按成绩排序 0.退出
请选择:1
请输入姓名:小明
请输入成绩:92.5
✔ 添加成功!当前共 1 名学生
1.添加学生 2.显示全部 3.查找最高分 4.按成绩排序 0.退出
请选择:1
请输入姓名:小红
请输入成绩:95
✔ 添加成功!当前共 2 名学生
1.添加学生 2.显示全部 3.查找最高分 4.按成绩排序 0.退出
请选择:3
🏆 最高分:小红 95 分
1.添加学生 2.显示全部 3.查找最高分 4.按成绩排序 0.退出
请选择:0
已退出,再见!项目复盘:这段代码教会了你什么
- 数据与功能分离:数据放在 struct + vector 里,每个功能是一个独立函数——这就是「模块化」,大程序无非是更多这样的积木。
- 防御式检查:每个函数开头都判断了「空列表」的情况,好的程序要替用户的「意外操作」着想。
- 菜单 = 无限循环 + 分派:几乎所有命令行软件都是这个骨架,你已经掌握了它的套路。
① 增加「按姓名查找」功能;② 增加「删除指定学生」功能(提示:遍历找到下标,再用 erase);③ 结合第 16 章,退出时把学生数据保存到 students.txt、启动时自动读入——这就成了一个能记住数据的完整软件!
常见错误对照表与调试心法
报错速查表:对号入座
| 报错 / 现象 | 原因 | 解决 |
|---|---|---|
expected ';' ... | 上一行忘写分号 | 看报错行和它的上一行 |
'cout' was not declared | 没写 using namespace std; 或没 include 头文件 | 补上第 1、2 行 |
'xxx' was not declared | 变量名拼错 / 用了没声明的变量 | C++ 区分大小写,逐字符核对拼写 |
| 满屏红色报错却都是英文标点错 | 输入法打出了中文的「;」「(」「"」 | 写代码时切英文输入法 |
| 程序一闪而过 | 跑完窗口立即关闭 | return 前加 cin.get();,或在终端里手动运行 |
| 结果不对但没报错 | = 写成 == 反了 / 整数除法丢小数 / 变量没初始化 | 逐行检查逻辑,打印中间变量 |
| 数组读出乱码 | 越界(下标 ≤ 长度-1 才合法) | 检查循环条件是 < 不是 <= |
| 程序卡死 | 死循环(忘了更新循环变量) | Ctrl+C 结束,检查 i++ 那一行 |
| cin 读到的数是 0 | 输入了字母,读取失败 | 按提示输入正确类型 |
| 中文输出乱码(Windows) | 源文件编码和控制台编码不一致 | Dev-C++ / VS Code 中统一编码设置,或在控制台执行 chcp 65001 切 UTF-8 |
undefined reference to 'main' | 工程里没有 main 函数或有多个 main | 一个程序有且只有一个 main |
| 大括号对不上 | { } 没有成对出现 | 利用编辑器的括号高亮逐层检查 |
调试五板斧(顺序使用,百试不爽)
- 只看第一条报错:编译器一次只可靠地报告一个问题,后面的报错常常是它引起的连锁反应。修复第一条,再重新编译。
- 读懂 / 搜索报错:报错行号会告诉你位置;看不懂就把报错原文复制去搜索引擎,99% 的新手报错都已有现成答案。
- print 大法:在可疑位置插入
cout << "到这里了, x=" << x << endl;,观察变量到底变成多少——最朴素也最强大。 - 注释二分法:注释掉一半代码再跑,问题还在吗?在 → 问题在被注释的那半;不在 → 在剩下的半。几次就能锁定出错的行。
- 小黄鸭调试法 🦆:把代码逐行大声讲给桌上的小黄鸭(或任何人)听——解释的过程常常让你自己发现 bug。
报错不是失败,而是编译器在免费帮你查错。专业程序员每天也在和报错打交道——会读报错,是新手蜕变的标志性技能。
学习路线图与推荐资源
你已经走到哪了?
变量与类型 · 输入输出 · 分支循环 · 函数 · 数组与字符串 · 指针与引用 · 结构体 · 类与对象(封装/继承/多态) · vector/map/set/sort · 文件读写 · 两个完整程序。对照自查,薄弱的章节回去再敲一遍代码。
下一站学什么?
- 数据结构与算法(最重要):链表、栈、队列、二叉树、排序查找——配着刷题平台练,编程能力会有质的飞跃。
- C++ 进阶特性:模板、STL 源码原理、智能指针、lambda 深入、多线程。
- 选一个方向做项目:游戏开发(Unreal / 自研引擎)、后端开发(网络编程、Linux)、嵌入式、量化金融、桌面应用(Qt)。
推荐资源清单
| 资源 | 类型 | 说明 |
|---|---|---|
| 《C++ Primer Plus》 | 📖 书 | 入门经典,厚但极细致 |
| 《C++ Primer》(第 5 版) | 📖 书 | 进阶必读,覆盖 C++11 |
| cppreference.com | 🌐 网站 | 最权威的标准库手册(工具书,遇到就查) |
| learncpp.com | 🌐 网站 | 英文但极适合自学的免费系统教程 |
| 菜鸟教程(runoob) | 🌐 网站 | 中文快速查阅语法 |
| 洛谷(luogu.com.cn) | ⌨️ 刷题 | 中文题库,从入门题开始闯关 |
| 力扣(LeetCode) | ⌨️ 刷题 | 算法练习与面试标配 |
| B 站系统课 | 🎬 视频 | 搜「C++ 入门」跟一套完整课程复习巩固 |
给未来程序员的 5 条建议
- 动手 > 观看:看懂 ≠ 会写。每个示例都亲手敲一遍、改一遍、弄坏再修好。
- 每天 30 分钟 > 周末狂练 5 小时:编程是肌肉记忆,贵在持续。
- 给自己找「想要的东西」做:想做个小游戏?查资料做出来,项目驱动学习最快。
- 把报错当朋友:参考第 19 章的五板斧,慢慢你会越看越快。
- 学会提问和搜索:把问题用准确的词描述出来,答案往往就在路上。
🎉 恭喜你学完了全教程!
你从「不知道什么是编程」,到独立写出了猜数字游戏和成绩管理系统——这已经超越了很多只停留在「看视频」阶段的学习者。
下一步很简单:把第 18 章的系统改造成你自己的版本,加上查找、删除、存文件功能。遇到问题就回到对应章节翻一翻。