← 返回作品集
🎉 专为零基础设计 · 无需任何编程经验

从零开始学 C++
直到写出你的第一个完整项目

这是一篇写给完全新手的 C++ 教程:每一章都配图解、可运行的代码和动手练习。跟着一章一章敲下来,你就能真正走进编程的世界。

📖 20 章 · 循序渐进 🖼️ 10+ 幅图解 💻 40+ 段可运行代码 ✏️ 每章配套练习 🎯 2 个实战项目
hello.cpp
#include <iostream>
using namespace std;

int main() {
    cout << "Hello, C++!" << endl;
    return 0;
}
01
CHAPTER 01 · 入门准备

认识 C++:为什么值得学?

📌 本章目标:搞清楚 C++ 是什么、能做什么,并理解「源代码 → 编译 → 运行」的基本流程。

C++ 是什么?

C++ 是一门 1979 年诞生于贝尔实验室的编程语言,作者是 Bjarne Stroustrup(本贾尼·斯特劳斯特卢普)。它最初叫「C with Classes」(带类的 C),1983 年正式改名为 C++ —— ++ 正是 C 语言里的「自增」运算符,寓意「C 的增强版」。

一句话概括它的性格:跑得快、管得细、本事大。它既像 C 语言一样贴近硬件、性能极高,又提供了「面向对象」等现代化的武器,四十多年长盛不衰,至今仍是全球最流行的编程语言之一。

C++ 都用在哪里?

你每天用到的软件,很多核心部分都是 C++ 写的:

🎮游戏引擎虚幻引擎、Unity 的底层都是 C++,王者荣耀、原神都离不开它
🖥️操作系统Windows、macOS 的大量核心组件由 C++ 编写
🗄️数据库MySQL、MongoDB、Redis 的核心是 C/C++
🤖AI 框架PyTorch、TensorFlow 的底层计算引擎是 C++
💰金融系统高频交易系统用它追求微秒级的速度
🚗嵌入式汽车、家电、机器人等智能设备的程序

程序是怎么跑起来的?

电脑的 CPU 只认识 0 和 1,并不认识 C++。所以你写好的代码(叫源代码)需要一位「翻译官」——编译器——把它翻译成电脑能直接执行的文件,然后运行才能看到结果:

hello.cpp 源代码(你写的) 编译器 g++ 负责翻译 hello.exe 可执行文件 屏幕输出 看到结果 人看得懂 翻译这一步 电脑能执行 程序运行
图 1-1 从源代码到运行结果:编译器是人和机器之间的「翻译官」
📝 小词典

源代码:你用文字写的程序文件(如 hello.cpp);编译:把源代码翻译成机器能执行的文件;可执行文件:翻译后的成品(Windows 上是 .exe);控制台:那个黑乎乎的窗口,程序在里面和你「对话」。

C++ 难吗?和其他语言比一比

语言上手难度运行速度典型用途
Python⭐ 最容易较慢数据分析、人工智能、脚本
Java⭐⭐ 较容易较快企业后端、安卓 App
C++⭐⭐⭐ 有一定深度极快游戏、系统软件、高性能程序
💡 新手建议

C++ 的概念确实比 Python 多一些,但这恰恰能让你真正理解「程序是怎么在电脑里跑的」,学完再学其他语言会非常轻松。本教程以 C++11 为基础,示例全部按 现代 C++(C++17 / C++20 可用)的写法来写,跟着一章一章敲代码,零基础完全可以学会。

02
CHAPTER 02 · 入门准备

搭建开发环境:准备好你的「厨房」

📌 本章目标:装好一个能写 C++ 的工具,并知道「编译」和「运行」命令是怎么回事。

三条路,选一条就行

写 C++ 需要两样东西:编译器(翻译官)和编辑器(写代码的地方)。把它们打包在一起的软件叫 IDE(集成开发环境)。下面三条路任选其一即可:

方式适合谁特点
小熊猫 Dev-C++完全新手(推荐)一个安装包全搞定,国内学校机房常用,界面简单
VS Code + MinGW想用现代编辑器长期学习界面漂亮、插件多,但安装配置步骤多一点
在线编译器装不了软件 / 先试试水打开网页就能写,零安装

方式一:小熊猫 Dev-C++(新手推荐)

  1. 下载安装:搜索「小熊猫 Dev-C++」(英文名 Red Panda Dev-C++)下载安装,一路「下一步」装完。别装名字里只有「Dev-C++」的经典版——它 2015 年后就停止更新了,对新标准支持差、还容易误报错误。
  2. 新建文件:打开软件,按 Ctrl + N 新建一个源代码。
  3. 保存:按 Ctrl + S 保存为 hello.cpp。建议先在桌面建一个 cpp-learn 文件夹统一存放,文件名不要用中文。
  4. 运行:写下第 3 章的第一个程序,按 F11(编译并运行)。弹出黑色窗口显示结果,就说明环境搭好了!

方式二:VS Code + MinGW

  1. 安装编译器:从 winlibs.com 下载 MinGW-w64 解压版(或用 MSYS2 安装),记住解压路径,例如 C:\mingw64。
  2. 配置环境变量:把 C:\mingw64\bin 加入系统 PATH(设置 → 搜索「环境变量」 → 编辑 Path → 新建)。
  3. 验证:Win + R 输入 cmd 打开一个新的命令行窗口,输入 g++ --version,能看到版本号就成功了。
  4. 安装 VS Code 与官方「C/C++」扩展,新建 .cpp 文件后点右上角 ▶ 按钮运行。
💡 macOS 用户看这里

苹果电脑自带 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 文件(内容可以先随便打一行字)。能在屏幕上看到编译窗口弹出,本章就过关了。

03
CHAPTER 03 · 入门准备

第一个程序:Hello, World!

📌 本章目标:写出第一个程序,并逐行理解它的结构——这是后面所有章节的地基。

输入这段代码并运行

按照编程界 50 年的传统,你的第一个程序要在屏幕上打出「Hello, World!」。把下面的代码敲进编辑器(建议手敲,不要复制——肌肉记忆很重要),然后按 F11 运行:

📄 hello.cpp
#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
2using namespace std;标准库的东西都放在叫 std 的「抽屉」里,这句声明后就能直接写 cout,不必写 std::cout
3int main() {主函数,整个程序的入口。每个 C++ 程序有且只有一个 main,系统从它开始执行
4cout << "..." << endl;把文字「流」到屏幕上;endl 表示换行。每条语句以 分号 结尾
5return 0;main 函数结束,返回 0 告诉系统「程序正常退出」

一个 C++ 程序的标准骨架

① 头文件区 #include <iostream> ② main() 主函数 —— 程序入口 有且只有一个 ③ 函数体 { 一条条语句 } 每条语句以分号 ; 结尾 ④ return 0; 向系统报告:程序正常结束
图 3-1 C++ 程序的标准结构:从头文件开始,main 是入口,语句按顺序执行

两个立刻要养成的好习惯

① 写注释。注释是写给人看的说明,编译器会忽略它。重要的想法随手注释,一周后你会感谢自己:

两种注释写法
// 这是单行注释:只能写一行

/*
   这是多行注释:
   可以写很多行说明
*/

② 注意符号。C++ 的所有符号(分号、引号、括号)都必须是英文半角。初学者 80% 的报错都来自中文标点(如「;」「"」)。

⚠️ 新手最容易犯的三个错

① 语句结尾忘写分号;② 用了中文标点;③ main 拼成了 Main——C++ 严格区分大小写。

✏️ 动手练习

3-1:让程序输出三行:你的名字、你的爱好、一句想对自己说的话。

3-2(挑战):只用 cout,打印出下面这个图案。

▶ 目标输出
*
**
***
👀 查看参考答案(3-2)
star.cpp
#include <iostream>
using namespace std;

int main() {
    cout << "*" << endl;
    cout << "**" << endl;
    cout << "***" << endl;
    return 0;
}
04
CHAPTER 04 · 基础语法

变量与数据类型:给数据安个家

📌 本章目标:理解变量是「内存里的储物格」,掌握 5 种常用类型,学会给变量起好名字,避开整数除法的坑。

变量是什么?

程序要处理数据,数据需要放在内存里。变量就是内存里的一格储物空间:它有名字(方便你找到它)、有类型(决定格子多大、能放什么)、有值(里面放的东西,可以随时更换)。

int age 18 地址 0x1000 double height 1.75 地址 0x1010 char grade 'A' 地址 0x1020 bool isBoy true 地址 0x1030
图 4-1 每个变量都是内存里的一格:有类型、有名字、有值,还有个地址

五种最常用的类型

📄 types.cpp
#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
是男生吗:1
💡 为什么 bool 输出的是 1?

C++ 里 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 可以测量(单位是字节):

sizeof.cpp
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.cpp
const double PI = 3.14159;   // 常量:初始化后不能再改
PI = 3.14;                   // ❌ 编译报错!不许修改常量

一定要避开的坑:整数除法

两个整数相除,结果还是整数——小数部分直接被砍掉(不是四舍五入):

divide.cpp
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;
💡 想要 2.5 怎么办?

让参与除法的数至少有一个是小数:5.0 / 2,或者先转换 (double)a / b。这个坑以后写「求平均分」时几乎人人会踩一次。

✏️ 动手练习

4-1:定义变量保存一本书的书名、价格、页数,用 cout 整齐地输出它们。

4-2:小明买了 3 支笔共花 10 元,用程序算出平均每支多少钱(提示:注意整数除法!答案是 3.33333)。

👀 查看参考答案(4-2)
price.cpp
int total = 10, count = 3;
double avg = (double)total / count;   // 先转成 double 再除
cout << "平均每支 " << avg << " 元" << endl;   // 3.33333
05
CHAPTER 05 · 基础语法

输入与输出:程序和用户对话

📌 本章目标:熟练使用 cout 输出、cin 读入用户输入,学会保留小数位数的格式化输出。

数据流动的方向

cin(读作 see-in)把用户敲的字读进变量;cout(see-out)把内容输出到屏幕。箭头方向就是数据流动的方向,非常好记:

⌨️ 键盘 用户输入 cin >> 变量(内存) 数据在这里 cout << 🖥️ 屏幕 显示结果
图 5-1 cin 和 cout:箭头永远指向数据流去的方向

cin:把输入装进变量

📄 input.cpp
#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:

getline.cpp
string city;
cout << "你想去哪个城市?";
getline(cin, city);              // 读入一整行,包括空格
cout << city << " 是个好地方!" << endl;
⚠️ cin 和 getline 混用的小坑

先用 cin >> 再用 getline 时,cin 会把残留的「回车」留给 getline,导致它读到空内容。新手阶段可以先避免混用;解决办法是在 getline 前加一句 cin.ignore(); 把残留的回车吃掉。

格式化输出:控制小数位数

算出 12.56636 这种一长串小数很难看。用 <iomanip> 里的 fixed 和 setprecision 控制保留几位:

format.cpp
#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)
bmi.cpp
#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;
}
06
CHAPTER 06 · 基础语法

运算符:数据的加工术

📌 本章目标:掌握算术、关系、逻辑三类运算符,记住「= 与 == 不一样」和运算符优先级口诀。

算术运算符

运算符含义例子结果
+ - *加、减、乘7 * 321
/除(整数除法会丢小数!见第 4 章)7 / 23
%取余数(只对整数有效)7 % 21
++自增 1i++i 变成 i+1
--自减 1i--i 变成 i-1
💡 % 很有用

判断奇偶:n % 2 == 0 是偶数;判断倍数:n % 3 == 0 能被 3 整除;取个位数:n % 10。

赋值运算符的简便写法

assign.cpp
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 判断的原料:

compare.cpp
int a = 5, b = 3;
cout << (a > b) << endl;    // 1(真)
cout << (a == b) << endl;   // 0(假)  == 是「等于吗?」
cout << (a != b) << endl;   // 1(真)  != 是「不等于吗?」

逻辑运算符:把多个条件串起来

运算符含义真记忆
&&与(并且)两边都真才真「既要…又要…」
||或(或者)一边真就真「有一样就行」
!非(取反)真假互换「唱反调」
logic.cpp
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 根本不执行,避免了除零!

优先级:谁先算?

完整优先级表有几十条,新手只需记住这条简化口诀:

📌 优先级口诀(从高到低)

括号 () → 自增自减 → 乘除取余 * / % → 加减 + - → 比较 > < == != → 逻辑 && → 逻辑 || → 赋值 =

拿不准就加括号——加括号永远不会错,还能让代码更好读。

年度第一大坑:= 和 ==

⚠️ 全世界新手都踩过的坑
错误示范 vs 正确示范
int a = 10;
if (a = 5)   // ❌ 错!这是「把 5 赋值给 a」,赋值结果是 5(真),条件永远成立
if (a == 5)  // ✅ 对!这是「a 等于 5 吗?」的比较

记住:一个等号是「赋值」,两个等号是「判断相等」。

✏️ 动手练习

6-1:写一个表达式,判断年份 year 是否为闰年(能被 4 整除但不能被 100 整除,或者能被 400 整除),输出 1 或 0。

👀 查看参考答案(6-1)
leap.cpp
int year = 2024;
bool isLeap = (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
cout << year << (isLeap ? " 是闰年" : " 不是闰年") << endl;   // 2024 是闰年
07
CHAPTER 07 · 基础语法

分支结构:让程序学会做选择

📌 本章目标:掌握 if / else if / else 和 switch 两种分支写法,能画出程序判断的流程图。

if / else:如果…否则…

程序真正的「智能」从这里开始:根据条件不同,走不同的路。先看流程图,再对代码:

开始 输入成绩 score score >= 60 ? 是 输出「及格」 否 输出「不及格」 结束
图 7-1 if-else 的执行流程:菱形是判断,两条路最终汇合到结束
📄 grade.cpp
#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 更清爽:

switch.cpp
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;
}
⚠️ 忘写 break 会怎样?
错误示范
case 'A':
    cout << "优秀" << endl;   // 没有 break!
case 'B':
    cout << "良好" << endl;   // 输入 A 会一路「掉」到这里继续执行

没有 break,程序会从匹配的 case 一路执行到下一个 break 才停,这叫「击穿」。

三目运算符:一行版 if-else

ternary.cpp
int a = 7, b = 4;
int maxVal = (a > b) ? a : b;   // 读作:a 大吗?大就取 a,否则取 b
cout << maxVal << endl;         // 7
✏️ 动手练习

7-1:输入一个整数,输出它是奇数还是偶数。

7-2:输入三个整数,输出其中最大的那个(试试只用 if 完成一次,再用三目运算符完成一次)。

👀 查看参考答案(7-2)
max3.cpp
int a, b, c;
cin >> a >> b >> c;
int maxVal = a;
if (b > maxVal) maxVal = b;
if (c > maxVal) maxVal = c;
cout << "最大值是 " << maxVal << endl;
08
CHAPTER 08 · 基础语法

循环结构:让电脑替你重复劳动

📌 本章目标:掌握 for、while、do-while 三种循环,理解 break / continue,会用嵌套循环打印九九乘法表。

为什么需要循环?

打印 1 到 5 难道要写 5 行 cout?10000 次呢?循环就是让电脑重复做一件事,你只要告诉它:从哪开始、重复到什么时候、每次做什么。

i = 1(初始化,只做一次) i <= 5 ? 是 执行循环体(打印 i) i++(更新) 继续循环 否 结束
图 8-1 for 循环的执行流程:初始化只做一次,条件成立就一直循环,否则结束

for:知道要重复几次时用

for.cpp
#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。用一个变量当「累加器」:

while.cpp
int i = 1, sum = 0;
while (i <= 100) {
    sum += i;   // 累加进 sum
    i++;        // ⚠️ 千万别忘了更新 i,否则 i 永远是 1,死循环!
}
cout << "1+2+...+100 = " << sum << endl;   // 5050
💡 for 和 while 可以互相替换

语法不同,能力相同。经验法则:次数固定用 for,条件触发用 while(比如「一直读输入直到遇到 0」)。

do-while:至少执行一次

先干活、后判断——最适合「菜单:先显示菜单,再问用户要不要继续」:

menu.cpp
int choice;
do {
    cout << "===== 菜单 =====" << endl;
    cout << "1. 存款  2. 取款  0. 退出" << endl;
    cout << "请选择:";
    cin >> choice;
} while (choice != 0);   // 只要用户没选 0,就一直显示菜单

break 与 continue:循环的两个遥控器

bc.cpp
// 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 强制结束,然后检查循环变量有没有被更新。

嵌套循环:九九乘法表

循环里套循环:外层管「第几行」,内层管「这一行打印几列」:

📄 table99.cpp
#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)
practice8.cpp
// 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;
}
09
CHAPTER 09 · 核心进阶

函数:把代码打包复用

📌 本章目标:会定义、调用函数,理解参数传递和返回值,了解默认参数与函数重载。

为什么需要函数?

同一段「求和」代码如果要在 10 个地方用,复制粘贴 10 遍太蠢了。函数就像一台洗衣机:把脏衣服(参数)放进去,按开始(调用),拿回干净衣服(返回值)。写一次,到处用。

main() 主函数 int s = add(3, 5); cout << s; return 0; add() 函数 int add(int a, int b) { return a + b; } ① 调用:把 3 和 5 传过去 ② 返回:带回结果 8
图 9-1 函数调用:main 把参数传给函数,函数把结果还给 main

定义与调用

📄 add.cpp
#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 引用传递(重要!)

默认情况下,函数拿到的是实参的复印件(值传递),改复印件不影响原件。参数前加 & 就变成拿原件(引用传递):

swap.cpp
#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;
}

默认参数与函数重载

features.cpp
// 默认参数:不传就用默认值
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)
prime.cpp
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;
}
10
CHAPTER 10 · 核心进阶

数组:一排相同类型的储物格

📌 本章目标:掌握一维、二维数组的声明与遍历,实现求和、找最大值等经典操作,牢记「下标从 0 开始」。

数组长什么样?

要存全班 50 个人的成绩,总不能定义 50 个变量吧?数组是「同类型变量的连续排列」,一个名字管一排:

int scores[5] —— 5 个连续的储物格,共用一个名字 90 85 77 92 68 scores[0] scores[1] scores[2] scores[3] scores[4] 0x1000 0x1004 0x1008 0x100C 0x1010 ↑ 下标从 0 开始,最后一个元素的下标是 长度-1
图 10-1 数组在内存中连续存放,用「数组名[下标]」访问每个元素
basic.cpp
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 循环是数组的最佳拍档

📄 traverse.cpp
#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」——不用管下标,直接挨个拿元素:

range-for.cpp
for (int s : scores) {   // 依次取出每个元素,存到 s
    cout << s << " ";
}

二维数组:给数据加个表格结构

grid[行][列],像 Excel 表格一样存数据:

grid2d.cpp
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。

📦 进阶了解:数组作为函数参数

数组传给函数时,传的是首元素的地址(不是整组拷贝),所以函数里能直接修改原数组;但也因为丢了长度信息,习惯上要额外传一个长度参数:

func-arr.cpp
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)
reverse.cpp
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
11
CHAPTER 11 · 核心进阶

字符串 string:文字处理利器

📌 本章目标:熟练使用 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()是否为空字符串
📄 string.cpp
#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;
}

遍历每个字符

loop.cpp
#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)
palin.cpp
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
12
CHAPTER 12 · 核心进阶

指针与引用:直捣内存深处

📌 本章目标:理解「地址」概念,会使用指针(取地址 &、解引用 *)和引用,分清两者的适用场景。

先建立直觉:内存像一家酒店

变量住进内存这家酒店:值是房间里的客人,地址是房间号。&a 查房间号,指针 p 是一张「写着房间号的纸条」,拿着纸条(*p)就能找到那个房间、进出里面的人。

指针变量 p 0x1000 p 里存的是一个地址 *p 顺着地址找过去 变量 a 10 a 的地址是 0x1000 指针 = 存「地址」的变量;通过 *p 可以读写 a 的值
图 12-1 p 存着 a 的地址,*p 就是「a 的值」的另一种访问方式

指针三板斧:&、*、nullptr

📄 pointer.cpp
#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:

ref.cpp
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)
ptr-swap.cpp
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
13
CHAPTER 13 · 核心进阶

结构体 struct:把相关数据打包

📌 本章目标:会用 struct 定义自己的数据类型,会创建、访问、批量管理结构体变量——为实战项目做准备。

为什么需要结构体?

描述一个学生,需要姓名、年龄、成绩……散装变量管起来太乱。结构体把一组相关的数据装进一个「档案袋」,从此一个变量代表一个完整的学生:

📄 student.cpp
#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

结构体数组:一个班的学生

class.cpp
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;
}

结构体套结构体

成员本身也可以是结构体,层层打包:

nested.cpp
struct Date { int year, month, day; };

struct Person {
    string name;
    Date birthday;          // 生日又是一个结构体
};

Person p = {"小明", {2010, 5, 20}};
cout << p.birthday.year << " 年" << endl;   // 2010 年
💡 struct 和 class 的关系预告

结构体是「纯数据打包」,下一章的 class 会在此基础上加入「函数」和「访问控制」。可以说:class = struct + 行为 + 权限管理。把这一章学扎实,面向对象就成功了一半。

✏️ 动手练习

13-1:定义 struct Book(书名、作者、价格),创建 3 本书存进数组,找出最贵的那本并输出。

👀 查看参考答案(13-1)
book.cpp
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;
14
CHAPTER 14 · 面向对象与标准库

类与对象:面向对象编程入门

📌 本章目标:理解类与对象、封装、构造函数,会写继承与多态的基本代码,理解「三大特性」。

从 struct 到 class:数据 + 行为

第 13 章的 Student 只有数据。现实里的学生还会「自我介绍」「修改成绩」——把这些行为也装进类型里,就是类(class)。类是图纸,对象(object)是按图纸造出的一个个具体实例:

class Student(类 = 图纸) 数据成员: - name 姓名 - score 成绩 成员函数: - show() 自我介绍 实例化 小明 age 18 score 92.5 小红 age 17 score 95 小刚 age 18 score 88 同一个类,可以造出无数个数据不同的对象
图 14-1 类是模具,对象是产品:图纸只有一份,房子可以盖无数套

封装:把「不许碰」的数据保护起来

private 的成员外面摸不到,只能通过 public 的函数访问——这样数据就只能被「受控地」修改,比如成绩不允许设成 120 分:

📄 student-class.cpp
#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,子类只补充自己的特色——代码不用重复写:

Animal 动物类(父类) eat() / speak() Dog speak(): 汪汪! Cat speak(): 喵~ Bird speak(): 叽叽
图 14-2 继承:子类自动拥有父类的能力,并可以重写自己的版本
inherit.cpp
#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(),指向狗就叫「汪汪」,指向猫就叫「喵」。调用哪个版本由对象的实际类型在运行时决定。父类指针访问成员用 ->(而不是 .)。

面向对象三大特性小结

🛡️封装private 藏数据,public 留接口,数据只能被受控地修改
🧬继承子类复用父类的代码,建立「is-a」的层次关系
🎭多态同一个接口,不同对象有不同实现,程序更灵活
📦 进阶了解:new / delete 动态创建对象
heap.cpp
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()。

15
CHAPTER 15 · 面向对象与标准库

STL 标准库:官方造好的轮子

📌 本章目标:会用 vector 动态数组、map 字典、set 集合和 sort 排序——写程序从此事半功倍。

STL 是什么?

STL(Standard Template Library,标准模板库)是 C++ 自带的「工具仓库」:常用数据结构 + 常用算法,全都写好、测试好、优化好了。以后遇到「要排序」「要去重」「要查频率」,先想想 STL 里有没有现成的。

工具一句话理解典型用途
vector会自动变长的数组存数量不确定的一堆数据
map键值对字典名字→电话、单词→出现次数
set自动去重的集合判断「出现过没有」
sort万能排序函数从小到大 / 自定义规则

vector:最常用的动态数组

📄 vector.cpp
#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:字典,一键查值

map.cpp
#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:自动去重的集合

set.cpp
#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(匿名小函数):

sortby.cpp
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)
wordcount.cpp
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;
16
CHAPTER 16 · 面向对象与标准库

文件读写:让数据留下来

📌 本章目标:会用 ofstream 写文件、ifstream 读文件,理解「程序退出数据就没了,所以要用文件持久化」。

为什么要操作文件?

到目前为止,你程序里的变量在程序一关闭就烟消云散。想让数据留下来(比如成绩单、日记),就要写进文件。好消息是:文件操作和 cin/cout 用法几乎一样——写文件用 <<,读文件用 >>。

写文件:ofstream

📄 write.cpp
#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

read.cpp
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:

readline.cpp
ifstream in("scores.txt");
string line;
while (getline(in, line)) {    // 一次读一整行
    cout << line << endl;
}

追加模式:不覆盖旧内容

append.cpp
ofstream out("scores.txt", ios::app);   // app = append 追加
out << "小刚 88" << endl;                // 加在文件末尾,原来的不丢
打开模式含义
ios::out写(默认,会清空原内容)
ios::in读
ios::app追加,保留原内容
ios::binary按二进制方式读写
✏️ 动手练习

16-1:写一个「电子日记」程序:启动后让用户输入一句话,把「日期 + 这句话」追加到 diary.txt 里,可以反复运行积累内容。

17
CHAPTER 17 · 实战项目

实战项目一:猜数字游戏 🎲

📌 项目目标:综合运用 do-while、if-else、随机数,写出一个完整可玩的小游戏。预计 30 分钟。

第一步:想清楚游戏规则

  • 程序在 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() 完全没问题,做正式项目时再换。

第三步:完整代码

📄 guess.cpp
#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)」;③ 把它改造成「人脑猜、电脑来提示范围」的逆向版。

18
CHAPTER 18 · 实战项目

实战项目二:学生成绩管理系统 📚

📌 项目目标:综合全书知识(结构体、vector、函数、循环、switch、排序、格式化输出),写出一个真正「像软件」的菜单式管理系统。

第一步:需求与设计

先想清楚这个软件要做什么、每个功能用到什么知识——写程序前先设计,是工程师的核心习惯:

功能用到知识出自
存学生(姓名+成绩)struct 结构体第 13 章
人数自动增长vector 动态数组第 15 章
菜单循环while + switch第 7、8 章
各功能拆成函数函数第 9 章
按成绩排序sort + lambda第 15 章
表格对齐 / 平均分iomanip 格式化第 5 章

程序骨架一句话:一个 while(true) 主循环显示菜单,switch 把每个选项分派给一个专门的函数——分而治之,每个函数只干一件事。

第二步:完整代码

📄 manager.cpp(约 90 行,建议分块敲)
#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、启动时自动读入——这就成了一个能记住数据的完整软件!

19
CHAPTER 19 · 走得更远

常见错误对照表与调试心法

📌 本章目标:看懂最常见的报错信息,掌握 5 个通用调试方法——以后遇到报错不再慌。

报错速查表:对号入座

报错 / 现象原因解决
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
大括号对不上{ } 没有成对出现利用编辑器的括号高亮逐层检查

调试五板斧(顺序使用,百试不爽)

  1. 只看第一条报错:编译器一次只可靠地报告一个问题,后面的报错常常是它引起的连锁反应。修复第一条,再重新编译。
  2. 读懂 / 搜索报错:报错行号会告诉你位置;看不懂就把报错原文复制去搜索引擎,99% 的新手报错都已有现成答案。
  3. print 大法:在可疑位置插入 cout << "到这里了, x=" << x << endl;,观察变量到底变成多少——最朴素也最强大。
  4. 注释二分法:注释掉一半代码再跑,问题还在吗?在 → 问题在被注释的那半;不在 → 在剩下的半。几次就能锁定出错的行。
  5. 小黄鸭调试法 🦆:把代码逐行大声讲给桌上的小黄鸭(或任何人)听——解释的过程常常让你自己发现 bug。
💡 心态建议

报错不是失败,而是编译器在免费帮你查错。专业程序员每天也在和报错打交道——会读报错,是新手蜕变的标志性技能。

20
CHAPTER 20 · 走得更远

学习路线图与推荐资源

📌 本章目标:明确下一步往哪走,收藏一份可持续使用的资源清单。

你已经走到哪了?

① 基础语法 ✔ ② C++ 进阶 模板/智能指针 ③ 数据结构算法 刷题强化 ④ 做项目 游戏/工具软件 ⑤ 选方向深耕 游戏/AI/后端 🚀
图 20-1 学习路线:本教程带你完成了第 ① 阶段,下一站是进阶语法与数据结构
✅ 学完本教程你应该已经会

变量与类型 · 输入输出 · 分支循环 · 函数 · 数组与字符串 · 指针与引用 · 结构体 · 类与对象(封装/继承/多态) · vector/map/set/sort · 文件读写 · 两个完整程序。对照自查,薄弱的章节回去再敲一遍代码。

下一站学什么?

  1. 数据结构与算法(最重要):链表、栈、队列、二叉树、排序查找——配着刷题平台练,编程能力会有质的飞跃。
  2. C++ 进阶特性:模板、STL 源码原理、智能指针、lambda 深入、多线程。
  3. 选一个方向做项目:游戏开发(Unreal / 自研引擎)、后端开发(网络编程、Linux)、嵌入式、量化金融、桌面应用(Qt)。

推荐资源清单

资源类型说明
《C++ Primer Plus》📖 书入门经典,厚但极细致
《C++ Primer》(第 5 版)📖 书进阶必读,覆盖 C++11
cppreference.com🌐 网站最权威的标准库手册(工具书,遇到就查)
learncpp.com🌐 网站英文但极适合自学的免费系统教程
菜鸟教程(runoob)🌐 网站中文快速查阅语法
洛谷(luogu.com.cn)⌨️ 刷题中文题库,从入门题开始闯关
力扣(LeetCode)⌨️ 刷题算法练习与面试标配
B 站系统课🎬 视频搜「C++ 入门」跟一套完整课程复习巩固

给未来程序员的 5 条建议

  1. 动手 > 观看:看懂 ≠ 会写。每个示例都亲手敲一遍、改一遍、弄坏再修好。
  2. 每天 30 分钟 > 周末狂练 5 小时:编程是肌肉记忆,贵在持续。
  3. 给自己找「想要的东西」做:想做个小游戏?查资料做出来,项目驱动学习最快。
  4. 把报错当朋友:参考第 19 章的五板斧,慢慢你会越看越快。
  5. 学会提问和搜索:把问题用准确的词描述出来,答案往往就在路上。

🎉 恭喜你学完了全教程!

你从「不知道什么是编程」,到独立写出了猜数字游戏和成绩管理系统——这已经超越了很多只停留在「看视频」阶段的学习者。

下一步很简单:把第 18 章的系统改造成你自己的版本,加上查找、删除、存文件功能。遇到问题就回到对应章节翻一翻。

↺ 回到第一章复习