学习资料
2025 冬季 InfiniTensor 训练营|C++ 实践课(二)
发表于:

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:专注运算逻辑,执行具体计算

参数传递:函数之间怎么"传话"?

maininput 处于不同的作用域,各自定义的变量互不可见。要共享数据,必须通过参数显式传递。

将参数以引用传递后,函数就能访问和修改外部变量了。调用流程:
input 获取输入 → 存入 main 的变量 → calculate 从变量中读取数据进行计算。

Git 版本管理:程序员的"时光机"

版本管理是什么?

你用过飞书文档、腾讯文档的历史记录功能吗?可以查看每个时间段的编辑内容,不满意就回退到之前的版本。

在 Git 出现之前,只能手动存副本:2022_11.docx2022_12.docx……
想回退?翻文件夹翻到崩溃。

Git 就是为了解决这个问题而生的——自动记录和管理每一个版本。

从零搭建:四步走

步骤 命令 作用
① 初始化仓库 git init 把当前目录纳入版本管理
② 添加到暂存区 git add main.cpp 文件变绿,等待提交
③ 提交 git commit -m '添加简单计算器功能' 记录一个版本快照
④ 查看历史 git log 查看所有提交记录(含哈希值、时间、说明)

⚠️ git addgit commit 为什么要分开?
✅选择性提交:一个文件改了多处,只把需要的部分加到暂存区
✅ 养成好习惯:每完成一个小功能就暂存,最后整合提交,方便追踪和回滚

🚫 编译产物别提交!
可执行文件(如 main)不要纳入版本控制,原因有二:
①占空间 —— GitHub 等平台对仓库大小有限制
②不可移植 —— Linux 编译的 main 在 Windows 上跑不了,只需将源代码纳入版本控制,实际使用时在现场编译即可。

代码风格:能跑不等于能看

代码可读性有多重要?

理论上,只要符合语法,你把代码全写一行、缩进随意、换行随心所欲,程序照样能跑。

但别人看你的代码时,内心是崩溃的。 😱

格式混乱 = 维护噩梦 = 协作灾难。所以,必须遵循统一的代码风格规范。

常见风格指南

指南 来源 特点
Google C++ Style Guide Google 文件命名、变量命名等细节规定详尽
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.datanums.size,一目了然。(访问成员用点运算符“.”)

变长输入:用户想输几个数就几个数

需求分析

用户输入 1+2+3 回车,程序自动解析出所有数字并求和。数字个数不固定,运行时才决定。

实现方案

读整行:使用 getline 函数,把用户输入的整行读进字符串;
字符串转流:使用 stringstream 把字符串变成类似 cin 的流对象;
循环提取:使用循环配合流提取依次读取每个数字,直到流为空。

注意事项:

  • 头文件建议显式引入所有需要的,别依赖隐式包含,跨平台兼容性更好;
  • getline 读完后,原来的 cin 已经被消耗了,必须用 stringstream 重新处理;
  • 循环提取时,以空格或回车作为分隔符。

课后 Q&A

Q1:能获取流在原始字符串中的位置吗?

流式输入不关心位置。如果需要追踪,建议直接处理字符串——用循环逐个字符遍历,遇到空格截断,再用 stoi(字符串转整数)或 stod(字符串转浮点数)转换。

Q2:转码学 C++ 有什么建议?

作为曾经的 EE 转 CS 选手,分享一下自己的经验:
📖 入门书推荐 《C++ Primer》,内容全面,适合新手。
🎯 学习方法:不要试图一次性学完所有知识点。先学基础 → 立刻动手实践 → 觉得够用了就找个感兴趣的方向开干。
编程能力主要靠练,看再多书不如写一行代码。 项目中学到的东西,印象最深,也最实用。