2025 冬季 InfiniTensor 训练营|C++ 实践课(二)
上节课我们用C++写了一个简易计算器,这节课直接升级——函数拆分、Git版本管理、数组、结构体……干货拉满,建议收藏反复看。
上节课回顾:我们做到了哪一步?
上节课,我们用 C++ 完成了一个支持加减法的简易计算器,包含 main 函数和 input 函数,能连续运行三次后自动退出。
到目前为止,我们已经解锁了这些技能👇
| 技能 | 说明 |
|---|---|
| 命令行基础 | 历史记录查看、cd切换目录等 |
| C++ 基本输入输出 | cin读输入、cout出结果 |
| & 代码编译运行 | g++ 编译源代码为可执行程序 |
| 条件判断 | if / else if / else 分支逻辑 |
| 循环结构 | while 循环让程序重复执行 |
💡 关于
while循环补充一点:当条件满足时进入循环体,执行完回到开头重新判断,直到条件不满足才退出。调试时强制退出按 Ctrl+C。
⚠️ 上节课演示时计数器count设为 3,只是为了展示循环判断效果。实际项目中通常会使用持续运行的循环,直到用户主动退出程序。
函数模块化:
别把所有代码放在 main 里
为什么要拆函数?
上节课的 input 函数又管输入又管计算,功能杂糅。从实际开发角度看,这远远不够。
定义函数有两个核心目的:
- 模块化 —— 程序越来越大时,如果所有代码都堆在
main里,维护起来就是噩梦。把不同功能拆到独立函数,结构清晰得多。 - 为递归铺路 —— 递归(函数调用自身)是编程中非常重要的概念,模块化思维是理解它的基础。
怎么拆?
input:专注用户交互,读取运算符和操作数;calculate:专注运算逻辑,执行具体计算
参数传递:函数之间怎么"传话"?
main 和 input 处于不同的作用域,各自定义的变量互不可见。要共享数据,必须通过参数显式传递。
将参数以引用传递后,函数就能访问和修改外部变量了。调用流程:input 获取输入 → 存入 main 的变量 → calculate 从变量中读取数据进行计算。
Git 版本管理:程序员的"时光机"
版本管理是什么?
你用过飞书文档、腾讯文档的历史记录功能吗?可以查看每个时间段的编辑内容,不满意就回退到之前的版本。
在 Git 出现之前,只能手动存副本:2022_11.docx → 2022_12.docx……
想回退?翻文件夹翻到崩溃。
Git 就是为了解决这个问题而生的——自动记录和管理每一个版本。
从零搭建:四步走
| 步骤 | 命令 | 作用 |
|---|---|---|
| ① 初始化仓库 | git init |
把当前目录纳入版本管理 |
| ② 添加到暂存区 | git add main.cpp |
文件变绿,等待提交 |
| ③ 提交 | git commit -m '添加简单计算器功能' |
记录一个版本快照 |
| ④ 查看历史 | git log |
查看所有提交记录(含哈希值、时间、说明) |
⚠️
git add和git commit为什么要分开?
✅选择性提交:一个文件改了多处,只把需要的部分加到暂存区
✅ 养成好习惯:每完成一个小功能就暂存,最后整合提交,方便追踪和回滚
🚫 编译产物别提交!
可执行文件(如 main)不要纳入版本控制,原因有二:
①占空间 —— GitHub 等平台对仓库大小有限制
②不可移植 —— Linux 编译的 main 在 Windows 上跑不了,只需将源代码纳入版本控制,实际使用时在现场编译即可。
代码风格:能跑不等于能看
代码可读性有多重要?
理论上,只要符合语法,你把代码全写一行、缩进随意、换行随心所欲,程序照样能跑。
但别人看你的代码时,内心是崩溃的。 😱
格式混乱 = 维护噩梦 = 协作灾难。所以,必须遵循统一的代码风格规范。
常见风格指南
| 指南 | 来源 | 特点 |
|---|---|---|
| Google C++ Style Guide | 文件命名、变量命名等细节规定详尽 | |
| LLVM Coding Standards | LLVM 项目 | 包含头文件引用等原则 |
| Linux Kernel Coding Style | Linux 内核 | C 语言编码规范 |
细节各异,但核心都是两个字:一致。
基本格式规范
- 命名统一:如驼峰式 nMakeAlphaCheck 或 下划线分隔式 with_underscore,选一种全项目贯彻;
- 行长度 ≤ 80 字符:太长得左右滚动,眼睛受罪;
- 缩进统一:推荐 4 个空格(Tab 也行,关键是一致);
- 花括号位置统一:同行或另起一行,选定就别变。
自动化格式化:clang-format
手动调格式太累?用工具!有了自动格式化工具,写代码时可以先专注逻辑,最后一键整理格式,效率拉满:
- clang-format:LLVM 出品,在项目根目录放
.clang-format配置文件,一键格式化; - VS Code:快捷键
Shift+Alt+F,或者命令面板搜Format Document
const 修饰符:
告诉编译器"这个别动"
const 是什么?
const = constant(常量/不变的)。加在参数前,就是告诉编译器:这个参数我只读不改,你帮我盯着,谁改就报错。
什么时候加?什么时候不加?
input不加:要修改参数(写入用户输入)。calculate加:只读取参数做计算,不应修改。
除非必须修改,否则尽量用
const。
代码意图更清晰,bug 更容易被发现。
数组:装下不止两个数
目前的计算器只能处理两个数。要是想算 1+2+3+4+5 呢?总不能无限加参数 x, y, z, a, b, c... 吧?
数组(Array)来了!
double numbers[2]; // 存2个double类型的数据
关键知识点
| 要点 | 说明 |
|---|---|
| 索引从 0 开始 | numbers[0]是第一个,numbers[1] 是第二个(不是 1!) |
| 数组名 = 指针 | 数组名本质上指向内存首地址,numbers[i] = 首地址 + i × 元素大小 |
| 传参时大小可省略 | double numbers[] 或 double* numbers 都行 |
💡 编程习惯:最小化替代原则。 修改代码时,先用最小改动让程序能跑,再逐步扩展。别一次性改太多,否则出了 bug 都不知道是哪里的问题。
循环遍历数组:while vs for
while 循环遍历
用 while 循环配合索引变量,可以逐个访问数组中的所有元素。定义一个整数变量 i 作为索引,初始值为 0,每次循环后递增,直到 i 达到数组长度为止。
⚠️ 漏了
i++= 无限循环 = 程序假死,跟while(true)一个效果。所以循环体里一定要有索引递增的语句,否则 i 值永远不变,循环条件永远为真。
for 循环:更优雅的选择
for循环的优势:
- “初始化、条件判断、递增”三合一,结构紧凑;
- 循环变量
i只在循环内部可见,循环结束后自动销毁,避免误用; - 让变量的作用域更小,代码的模块化程度更高,也更不容易出 bug。
函数返回值:让函数"交出答案"
返回值的概念
之前的函数都用void,表示不返回任何东西。但函数完全可以把计算结果返回给调用者。
main 函数末尾的 return 0 也是返回值——0 表示正常结束,非 0 表示异常终止。
求和函数
定义一个 sum 函数,返回类型为 double,接受数组和长度作为参数。函数内部遍历数组累加所有元素,最终返回求和结果。
这样,calculate 函数就不用自己遍历累加了,直接调用 sum 拿结果,代码更简洁。
结构体:把相关数据打包在一起
当需要传递的参数越来越多——数组首地址、数组长度、操作符类型……一个个传太麻烦了。结构体(Struct) 就是把多个相关数据组合成一个新类型。
用结构体的好处:
- 逻辑清晰:相关数据放一起,一看就懂;
- 调用简洁:传一个结构体,而不是一堆零散参数;
- 可读性强:
nums.data、nums.size,一目了然。(访问成员用点运算符“.”)
变长输入:用户想输几个数就几个数
需求分析
用户输入 1+2+3 回车,程序自动解析出所有数字并求和。数字个数不固定,运行时才决定。
实现方案
①读整行:使用 getline 函数,把用户输入的整行读进字符串;
②字符串转流:使用 stringstream 把字符串变成类似 cin 的流对象;
③循环提取:使用循环配合流提取依次读取每个数字,直到流为空。
注意事项:
- 头文件建议显式引入所有需要的,别依赖隐式包含,跨平台兼容性更好;
getline读完后,原来的cin已经被消耗了,必须用stringstream重新处理;- 循环提取时,以空格或回车作为分隔符。
课后 Q&A
Q1:能获取流在原始字符串中的位置吗?
流式输入不关心位置。如果需要追踪,建议直接处理字符串——用循环逐个字符遍历,遇到空格截断,再用
stoi(字符串转整数)或stod(字符串转浮点数)转换。
Q2:转码学 C++ 有什么建议?
作为曾经的 EE 转 CS 选手,分享一下自己的经验:
📖 入门书推荐 《C++ Primer》,内容全面,适合新手。
🎯 学习方法:不要试图一次性学完所有知识点。先学基础 → 立刻动手实践 → 觉得够用了就找个感兴趣的方向开干。
编程能力主要靠练,看再多书不如写一行代码。 项目中学到的东西,印象最深,也最实用。
