news 2026/10/20 14:15:57

Linux下C++入门:注释与输出的正确打开方式

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Linux下C++入门:注释与输出的正确打开方式

我最初接触C++是在Windows上,跟着教程敲了一段输出语句,程序跑起来就觉得自己入门了。直到后来切到Linux环境,接手一个带源码的老项目,导师让我先看懂日志输出模块——那才意识到自己对"注释"和"输出"这两个基础点的理解有多浅。注释写不好,项目根本不敢接手;输出用不好,调试就像闭着眼找针。这篇文章把我整理的"基于Linux的C++学习——注释与输出"完整讲一遍,涵盖环境准备、语法细节、编译命令、高频踩坑和一个可以直接抄走的综合示例。无论你是刚装好Linux的纯新手,还是学过一点C++但没在Linux下完整跑过程序的半新手,按这个思路走一遍,后面学指针、类、内存管理都会顺畅不少。

1. 第一课不是语法:先把Linux环境与工具链磨顺

很多人入门C++的第一课直接开讲数据类型、变量、if else,结果学到指针就崩了。我的经验恰恰相反:第一课应该先花半小时把"写代码→编译→运行"这条链路跑通。语法不会可以查,工具链不顺,每学一个新知识点都要跟环境搏斗,热情很快会被磨光。

1.1 为什么我坚持在终端里完成C++入门

在Linux下学C++,最大优势就是工具链透明。Windows上点一下IDE的绿色按钮,虽然也能跑,但编译、链接、预处理这些步骤全被藏起来了。Linux终端里用g++敲命令,每个环节都看得见,出错时能直接看到是哪一步挂了,这对建立编译模型特别有帮助。

我见过不少同学在Windows下用IDE写C++写得挺好,一到Linux就蒙了,连main函数入口和编译命令都分不清。原因很简单:IDE把底层逻辑包裹得太严实。所以在入门阶段,我坚持建议所有人在终端里完成编译运行,哪怕你觉得命令行恐怖。实际上C++入门期的编译命令就那几条,十分钟就能掌握,收益却是一辈子的。

我自己就是这么过来的。头两周几乎所有练习都是在终端里完成的,敲错命令、看报错、查手册、改代码,循环往复。这个过程看似原始,但恰恰把C++程序从源文件到可执行文件的每个环节都刻在了脑子里。后面学多文件编译、静态库、动态库、Makefile时,几乎没有额外学习成本。

1.2 安装g++与验证编译环境的三个命令

大多数主流Linux发行版默认不带g++,或者只带了gcc。安装很简单,以Debian系为例:

sudo apt update sudo apt install build-essential

build-essential会一起装上gcc、g++、make等工具,省得以后再单独装。如果你用的是其他发行版,包管理器不同但名字类似,Red Hat系用dnf、Arch系用pacman。

装完以后,用三个命令验证环境是否可用:

g++ --version which g++ # 或者 which g++-14 / g++-13 这类版本化命令 echo 'int main(){return 0;}' | g++ -x c++ - -o /tmp/t && /tmp/t && echo "chain ok"

第三条命令把一行极简代码通过管道喂给g++,编译成临时可执行文件并运行,能输出"chain ok"就说明整条编译运行链路没有问题。我习惯在任何一台新机器上先跑这条验证链,比只查版本号可靠得多。版本号正常但编译链损坏的情况虽然少见,但也遇到过,所以后来就一直用这个组合验证。

1.3 编辑器选择:从vim到VS Code的务实建议

编辑器这块,我劝大家不要纠结太久。入门阶段只需要一个能写代码、能保存、语法高亮不是必须的编辑器。vim是Linux自带的基础款,vim hello.cpp就能开始写,够用但学习曲线略陡。如果你之前没用过vim,至少要会三件事:按i进入插入模式、按Esc退回普通模式、输入:wq保存退出。这三条记住,vim就能拿下了。

不想碰vim就装VS Code,在终端里用code hello.cpp打开文件,装一个C/C++扩展,语法高亮、自动补全、跳转定义都够用。但我的建议是:即使用了VS Code,编译运行依然去终端执行g++命令,不要依赖IDE的"一键运行"按钮。

原因有两层。第一,调试阶段你会频繁用到gdb、valgrind这些终端工具,提前熟悉终端操作属于未雨绸缪。第二,编辑器是编辑器,编译器是编译器,把这两个概念分开理解,写Makefile、写构建脚本时才能心里有数。工具是给人用的,别让工具牵着走。

2. 注释的真正价值:三种写法与工程习惯

搞清楚了环境,终于可以进入正题。C++的注释语法非常简单,简单到很多人不屑于学。但工作几年后我越来越确信:注释水平基本等于代码维护水平。你去看一个老项目的源码,先不看代码逻辑,只看注释密度和注释风格,很快就能判断这个项目维护得好不好。

2.1 单行注释、多行注释与它们的一个限制

C++里注释有两种基础写法:

// 这是单行注释,从两个斜杠开始,到行尾结束 int a = 10; // 也可以用在一行代码的末尾 /* 这是多行注释, 以斜杠星号开始, 以星号斜杠结束。 */ int b = 20;

单行注释适合标注一行代码的含义,多行注释适合在函数或逻辑块之前写一段说明。如果只是临时注释掉某一行代码,单行注释最顺手;如果要注释掉一整个函数实现,多行注释更合适。

但多行注释有一个非常容易踩的坑:C++的块注释不支持嵌套。也就是说,/* ... /* ... */ ... */这种写法,第一个*/出现时注释就结束了,后面的部分会变成正常代码,大概率导致编译错误。我刚学的时候干过一件事:用块注释把一大段代码包起来,里面又有块注释,结果编译器报了一堆莫名其妙的错误,我还以为是语法问题,排查了半天才发现是注释嵌套了。

所以,当你需要临时屏蔽一段代码,而这段代码里恰好含有块注释时,最稳妥的做法是逐行加//,别用块注释整体包。很多编辑器和IDE都提供"注释/取消注释选中区域"的快捷键,本质就是批量加单行注释,这也是工程里的常见做法。

2.2 好的注释写"为什么",不写"是什么"

新手写注释最常见的毛病,是把代码翻译成人话:

int count = 0; // 定义count变量,初始值为0

这种注释毫无营养。代码自己已经说明了"是什么",读者需要的是代码之外的信息。我之前在某公司维护过一个订单处理模块,遇到过一个很典型的例子:

// 这里不能直接用 >= 判断,因为历史数据里存在精度误差, // 用 >= 会导致部分老订单被重复处理,必须加上 0.001 的容差。 if (amount >= threshold + 0.001) { process(order); }

这段注释写的是"为什么",解释了常量的来历和可能引发的线上问题。如果这段注释缺失,后来的人优化代码时很可能把+ 0.001当冗余删掉,导致线上故障。这就是注释在工程中的真实价值:它承载的是代码没法自我表达的历史背景和业务约束。

我给自己定了一条原则:写代码时但凡产生"为什么要这样做"的念头,立刻把它写进注释。这个念头过了三天自己都未必记得,更不用说别人了。这也是为什么很多项目规范里要求"注释解释意图而非实现"——这不是教条,是用大量故障换来的教训。

2.3 文档注释:让注释变成可检索的说明书

除了上面两种基础注释,C++还有一种常用于接口标注的文档注释写法。这种注释通常以///开头,配合Doxygen等工具,可以从注释直接生成HTML格式的API文档:

/// 计算两个整数的最大公约数。 /// /// \param a 第一个整数 /// \param b 第二个整数 /// \return a 和 b 的最大公约数 int gcd(int a, int b) { while (b) { int t = a % b; a = b; b = t; } return a; }

入门阶段可能觉得写这种注释很麻烦,但我建议尽早养成习惯。原因很简单:注释的@param和@return部分会逼你把接口的输入输出边界想清楚。你写参数说明的过程,就是在做一次接口设计自检——如果连参数都解释不清,多半是函数设计有问题。

另外,文档注释还有一个隐藏好处:它能被IDE和代码静态检查工具识别,写错的参数名、缺失的返回值说明会被高亮提示。等于免费获得了一层接口规范检查。我见过很多老工程师写函数前先写文档注释,写完注释接口设计基本就定了,代码只是把已经设计好的逻辑落地而已。

3. 输出不是print那么简单:cout、printf与cerr的分工

注释解决的是"代码能不能被看懂",输出解决的是"程序运行状态能不能被看见"。C++里输出手段非常多,初学者最容易混淆的就是cout和printf,再加上一个cerr,很多人压根不清楚它们之间的本质区别。

3.1 cout的运算符链:类型安全带来的幸福感

cout是C++标准库提供的标准输出流对象,配合左移运算符<<使用:

#include <iostream> int main() { int age = 25; double score = 89.5; std::cout << "age = " << age << ", score = " << score << std::endl; return 0; }

你注意看这条输出语句的结构:<<把要输出的内容像流水线一样依次串起来,字符串、整数、浮点数可以直接混在一起输出,不需要像printf那样靠占位符%d、%f去指定类型。这意味着cout是类型安全的——如果你不小心把一个结构体对象传给cout,编译器会直接报错;而printf传错类型时,很多情况下只是输出乱码或脏数据,运行时才炸。

这个差异在实际调试中价值极大。早期我用printf排错,经常把%d和%ld搞混,在64位系统上输出负数或随机数,排查半天才意识到是格式占位符不对。换成cout之后这类问题几乎绝迹。对刚入门的新手,我强烈建议优先掌握cout,别急着学printf。

3.2 endl与\n:一个看似等效却影响性能的细节

大多数教材里,输出换行用std::endl。但很少有人告诉你,endl和输出"\n"并不完全等价。endl干了两件事:输出一个换行符,同时刷新输出缓冲区。而"\n"只输出换行符,不刷新缓冲区。

缓冲区是什么?简单理解,cout并不是每输出一个字符就立刻写入终端,而是先积攒在内存缓冲区里,攒够一批再一次性交给操作系统写出去。这种机制大大减少了I/O次数,性能自然更高。endl的刷新操作会强制把缓冲区内所有内容立即写出去,这和频繁的小额写入没什么区别。

举个我在实际开发中遇到的例子。某次写一个批量处理程序,循环里用std::endl输出进度信息,总共循环10万次,跑完花了二十多秒。我把endl全部改成'\n',跑同样的数据,耗时降到三秒左右。原因就是每次endl都触发一次缓冲区刷新,底层等于做了10万次系统调用。

所以结论很简单:显示进度、打印日志时用'\n';需要立即把输出写到终端或文件时,用std::endl或std::flush。例如程序马上要崩溃,或者输出之后紧接着要等待用户输入,这时候就该刷新缓冲区。

3.3 cerr与clog:把调试信息和正常输出分开

cout之外,C++还提供了标准错误流cerr和clog。它们的用处经常被忽略,但实际上非常重要。

cerr用于输出错误信息。它和cout最大的区别在于:cerr默认是不缓冲的,写进去的内容立刻输出到标准错误输出(stderr)。即使程序紧接着就崩溃,错误信息也已经显示出来了。而cout是带缓冲的,如果程序在缓冲区刷新前崩溃,你写的那些"救命"日志可能压根没来得及显示。

clog则是缓冲版的错误流,行为和cout类似,常用于记录日志文件。

这三者在Linux系统里对应不同的文件描述符:cout对应stdout,cerr和clog对应stderr。这意味着你在终端里可以用重定向把它们分开:

./demo 1> output.log 2> error.log

这条命令把所有正常输出写到output.log,错误输出写到error.log。排查问题时分工明确,一眼就能定位。我在实战里强烈建议:程序遵循"正常结果走cout,错误提示走cerr"的约定,即便现在写的只是一个几十行的练习程序,也尽早这么做。等哪天需要处理真正的日志系统时,你已经天然掌握了分级输出的习惯。

4. 从源文件到屏幕:g++编译运行全链路

环境有了,语法会了,接下来最关键的就是把源文件变成可执行文件的过程。很多新手直接照抄网上的命令,跑通了就万事大吉,一旦报错就彻底懵掉。我建议至少搞清楚编译命令里的每个参数是干什么的,以及编译报错时该怎么读。

4.1 一个命令从零到可执行文件所经历的过程

先看最基础的编译命令:

g++ hello.cpp -o hello

这个命令做了四件事:

  • 预处理:处理#include、#define等指令,把头文件内容展开进源文件;
  • 编译:把预处理后的代码翻译成汇编代码;
  • 汇编:把汇编代码转成机器指令,生成目标文件(.o文件);
  • 链接:把目标文件与C++标准库、其他依赖的目标文件合并,生成最终的可执行文件hello。

想逐阶段观察,可以用g++ -E看预处理结果,用g++ -S看汇编输出,用g++ -c只编译不链接生成.o文件。我入门时每个阶段都跑了一遍,亲眼看到标准库头文件被展开成一个几万行的庞然巨物,才真正理解"#include不是导入,而是文本展开"这句话的含义。

运行生成的程序也很简单:

./hello

前面的./不能省。因为Linux默认不会在当前目录寻找可执行文件,你直接输hello,shell会去PATH变量指定的目录里找,找不到就报"command not found"。这个细节让无数新手困惑过,包括当年的我。后来养成习惯,自己编译出来的程序一律用./可执行文件名运行,就再没出过这种问题。

4.2 警告信息的价值:开启-Wall与-Wextra之后的发现

只敲g++ hello.cpp -o hello虽然能编译,但我强烈建议加上两个编译选项:

g++ -Wall -Wextra hello.cpp -o hello

-Wall不是"all warnings"的简单含义,它实际是开启一组常见的警告检查;-Wextra再开启额外一组更细致的检查。它们不会改变程序的运行结果,却能帮你发现很多潜在问题。

我印象很深的一个案例:某次写代码,把一个int和一个unsigned int做比较,写的时候觉得毫无问题。加上-Wall编译后,编译器提示"有符号与无符号比较可能导致意外结果"。当时没当回事,后来程序在数据量变大时出现了诡异的循环不退出问题,排查很久才意识到正是这个类型不匹配在特定边界条件下翻车。

所以我的建议是:从你写的第二个C++程序开始,就把-Wall -Wextra焊死在编译命令里,甚至可以用环境变量或alias把默认编译命令替换成带警告选项的版本。越早习惯看警告,越少在运行期吃苦。

4.3 用Makefile管理多文件:从小工程开始的习惯

单个cpp文件用g++直接编译没问题,但项目一旦拆成多个文件,每次都手动敲一长串命令就很痛苦,还容易漏文件。这时候就该上Makefile了。

一个最简Makefile长这样:

hello: hello.o g++ hello.o -o hello hello.o: hello.cpp g++ -Wall -Wextra -c hello.cpp -o hello.o clean: rm -f hello hello.o

在终端里敲make,make工具会根据文件时间戳自动判断哪些文件需要重新编译,只编译有变动的部分。make clean清理中间产物。这个习惯我在入门第三周就养成了,后来写任何超过两个文件的小练习,都会顺手建一个Makefile,省了不少重复劳动。

Makefile的语法初看有些别扭,尤其是目标: 依赖和下面命令前必须有一个Tab缩进这两点。但台上一分钟台下十年功,这玩意儿学会一次,后面写多文件程序、调用第三方库时全都是这个套路。我建议新手别等课程教,自己写第一个多文件程序时就主动去碰Makefile。

5. 输出环节最常翻车的五个场景:现象、根因与修复

前面讲的是"正确用法",这一节专门讲"错误现场"。以下五个问题,涵盖了我自己以及身边同学、同事在Linux下用C++输出时踩过的高频坑。每个我都给出现象、根因和修复方案,你可以直接照做。

5.1 中文乱码:终端、源文件与编码三方对齐问题

现象:程序里输出"你好",终端显示成乱码或问号。

根因:这通常是三处编码不一致造成的。源文件保存时的编码、源文件里的字符串字节流、终端显示使用的字符集,三者必须对齐。Linux下大多数环境默认UTF-8,但有些老系统或某些编辑器默认保存为GBK或其他编码,一旦源文件按GBK保存,而终端按UTF-8解析,中文必然乱码。

排查步骤:

file hello.cpp # 查看源文件编码 locale # 查看终端语言环境,注意LANG的取值

修复:统一使用UTF-8编码保存源文件,确保locale输出包含UTF-8。在vim里可以用:set encoding=utf-8和:set fileencoding=utf-8强制保存为UTF-8。VS Code则在右下角选择"UTF-8"作为文件编码即可。

额外提醒:这句代码std::cout << "中文";在C++里其实是把一串多字节字符原样交给输出流,程序本身不解析编码,所以只要源文件和终端对齐,中文输出就没问题。某些老教材建议用setlocale或wchar_t处理中文,在现代Linux UTF-8环境下通常多此一举,反而引入复杂度。

5.2 cout与printf混用:缓冲区不一致造成的顺序错乱

现象:先写cout输出一行,再写printf输出一行,结果终端上printf的内容反而出现在前面。

根因:C++的iostream和C语言的stdio各自维护一套缓冲区,两者互不感知。当cout有缓冲未刷新、printf先写入并刷新时,输出顺序就可能颠倒。

修复方案有两条路:

  • 统一使用cout,不要混用printf;
  • 如果必须混用,在切换输出方式前调用std::ios::sync_with_stdio(false)或主动std::cout.flush()。

我在实际代码里基本只选第一条路:统一cout。只有在处理老代码遗留的printf调用时,才会在切换点加上std::cout.flush()。混用缓冲区导致的输出乱序问题,表面看着是"顺序问题",本质是两套I/O机制之间的协调问题,讲清楚后其实不难理解。

5.3 程序崩溃前没输出:缓冲未刷新的陷阱

现象:程序运行到一半崩溃,但屏幕上什么都没有,事后排查却发现代码明明执行过输出语句。

根因:回到之前讲的缓冲区机制。cout先把内容存进缓冲区,如果使用的'\n'而非冲刷新操作,那么用户看不到任何输出。当程序随后因为段错误等原因崩溃,缓冲区里的内容来不及写入终端就丢了。

修复:在容易崩溃的代码段之前,主动刷新缓冲区:

std::cout << "before dangerous operation" << std::endl;

或者临时改用std::cerr输出,因为cerr天然不缓冲。这个技巧在调试崩溃问题时几乎是救命稻草:崩溃前的最后一行输出,往往直接指向出错代码的位置。我至今保留一个习惯,当程序莫名崩溃时,第一件事就是在可疑位置插入cerr输出,逼出最后一个正常执行的语句,问题通常立刻缩小到崩溃点附近。

5.4 浮点数输出总是不对:默认精度与格式控制

现象:double d = 3.14159;输出时只显示3.14159没问题,但double x = 1.0 / 3.0;输出变成0.333333,位数不够;或者想输出两位小数,结果输出了1.2而不是1.20。

根因:cout对浮点数默认输出6位有效数字,而且不会补零。这不是bug,是默认格式使然。

修复:使用<iomanip>头文件提供的格式化工具:

#include <iostream> #include <iomanip> int main() { double x = 1.0 / 3.0; std::cout << std::fixed << std::setprecision(2) << x << std::endl; // 输出 0.33 std::cout << std::setprecision(10) << x << std::endl; // 恢复科学计数法风格,但保留10位有效数字 return 0; }

std::fixed表示使用定点小数形式,std::setprecision(n)设置小数点后位数。这两个控制项写入输出流后持续生效,直到再次修改。我比较建议在每次输出浮点数前明确设置格式,不要依赖默认值,因为这个默认值很容易在不同编译器和标准版本间产生细微差异,造成输出格式不一致。

5.5 大量输出时性能骤降:endl为什么是罪魁祸首

现象:循环10万次输出日志,程序跑得非常慢;把endl改成'\n'后,速度明显提升。

根因:前面第3节已经讲透,endl每次都会刷新缓冲区,大量刷新导致频繁系统调用,性能断崖式下降。

修复:把循环内的endl全部替换成'\n'。如果确实需要间歇性刷新(比如希望用户在长时间运行中看到进度),可以在循环内每100次或每1000次触发一次std::flush:

for (int i = 0; i < 100000; i++) { std::cout << "progress: " << i << '\n'; if (i % 1000 == 0) { std::cout << std::flush; // 每1000次强制刷新一次 } }

这个模式我在写数据处理工具时经常用。既保证用户能看到进度,又不会因为每次刷新拖垮性能。新手很容易陷入"输出慢=死循环"的误判,我见过的真实的排查案例就是:程序其实在正常跑,只是endl拖着性能,看起来像卡住了。所以一旦遇到输出密集型的程序变慢,先扫一遍有没有滥用endl,往往比深挖算法更快定位问题。

6. 一个把注释和输出串起来的综合示例:简易运行日志模块

前面讲了不少理论和坑,现在拿出一个能直接抄走的示例。这个程序模拟一个简易日志输出模块,它把本文讲到的注释、cout、cerr、格式化输出、缓冲区控制全部串在一起,非常适合作为入门第一个"正经"练习。

6.1 示例代码:不到80行的完整实现

// simple_logger.cpp // 简易运行日志模块 // 功能:按日志级别输出信息,区分普通日志与错误日志。 #include <iostream> #include <iomanip> #include <string> // 日志级别枚举 enum LogLevel { DEBUG = 0, INFO = 1, WARN = 2, ERROR = 3 }; // 当前全局日志级别,低于该级别的日志不输出 // 之所以做成全局变量,是为了让所有调用点共享同一配置, // 后续可以替换成更完善的配置管理机制。 LogLevel g_currentLevel = INFO; /// 输出普通日志到 stdout。 /// 调试和提示信息都可以走这里,便于通过重定向与错误日志分离。 void logMessage(LogLevel level, const std::string& msg) { if (level < g_currentLevel) { return; // 级别不足,直接丢弃,避免无意义的输出开销 } // 根据级别给日志加前缀,方便在大量输出中快速筛选 std::string prefix; switch (level) { case DEBUG: prefix = "[DEBUG]"; break; case INFO: prefix = "[INFO] "; break; case WARN: prefix = "[WARN] "; break; case ERROR: prefix = "[ERROR]"; break; } if (level >= ERROR) { // 错误日志走 stderr,配合 2> 重定向单独收集 std::cerr << prefix << " " << msg << '\n'; } else { std::cout << prefix << " " << msg << '\n'; } } int main() { logMessage(INFO, "program start"); double x = 0.1; double y = 0.2; double sum = x + y; // 浮点数直接输出容易带出长尾巴,这里统一保留两位小数 std::cout << std::fixed << std::setprecision(2); logMessage(INFO, "x = " + std::to_string(x)); logMessage(INFO, "y = " + std::to_string(y)); // 故意制造一个"错误"场景,演示错误日志走向 if (sum < 0.3) { logMessage(ERROR, "sum is unexpectedly small"); } else { logMessage(INFO, "sum = " + std::to_string(sum)); } // 程序结束前刷新一下输出,确保所有缓冲内容都落到终端 std::cout << std::flush; logMessage(INFO, "program end"); return 0; }

6.2 设计思路逐段拆解

这段代码虽然短,每个关键位置都有对应的设计考量。

第一,日志级别枚举。让日志分成DEBUG、INFO、WARN、ERROR四级,后续过滤起来非常方便。入门阶段可以不管它,但一旦程序复杂起来,没有分级,几百行输出混在一起根本没法看。

第二,全局日志级别g_currentLevel。很多人会质疑全局变量不好,这里我特意保留它,是因为入门阶段引入依赖注入、单例等概念为时过早。全局配置虽然丑,但在小工具代码里完全够用,等工程变大了再重构成配置类也不迟。注释里我把这个意图写清楚了,这就是第2节讲过的"写为什么"。

第三,错误日志走cerr。ERROR级别的内容输出到标准错误流,其他走标准输出。当你用./simple_logger 1> info.log 2> error.log运行程序时,普通日志和错误日志自动分流,这是实际运维中日志系统最基本的分工逻辑。

第四,格式化输出。我在输出浮点数前先设置了std::fixed和std::setprecision(2),这样sum的最终输出不会带出一长串浮点噪声。这个设置在整个main函数里持续生效,后续所有浮点输出都保持两位小数,行为统一。

第五,缓冲区控制。普通日志用'\n'换行,不频繁刷新;程序结束前主动std::flush一次,确保缓冲区内容完整输出。

6.3 运行验证与扩展方向

保存文件后,编译运行:

g++ -Wall -Wextra simple_logger.cpp -o simple_logger ./simple_logger

正常情况下输出大致是:

[INFO] program start [INFO] x = 0.10 [INFO] y = 0.20 [ERROR] sum is unexpectedly small [INFO] program end

从这里可以看到:0.1 + 0.2在浮点数表示下并不是精确等于0.3,所以走了ERROR分支。这个结果本身就是一个很好的知识延伸点——浮点数精度问题,也是C++新手必须迟早面对的。

你可以顺手跑一下带重定向的版本:

./simple_logger 1> info.log 2> error.log cat info.log cat error.log

会发现普通日志和错误日志被分开写到两个文件。至此,注释、输出、缓冲、重定向、格式化这一整套知识都串在了一起。

如果想让这个小模块更完整,可以考虑以下扩展方向:

  • 给日志加上时间戳,需要引入<chrono>;
  • 把日志级别配置改成启动参数,./simple_logger --level=DEBUG这样;
  • 增加写入文件的功能,用std::ofstream创建一个日志文件对象;
  • 将日志级别输出到文件的同时,ERROR级别仍然回显到终端。

每个方向都可以作为下一个小练习,实际操作一遍,收获比看十篇教程都大。我个人体会是:入门阶段的综合练习,不在于题目多难,而在于它是否把多个基础知识点串在了一起。这种"串起来"的感觉,比单独学一百个语法点更能建立对程序整体运行逻辑的直观理解。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/20 14:15:47

鸢尾花分类入门:用深度学习实战掌握表格数据训练全流程

1. 为什么拿鸢尾花做深度学习项目&#xff1a;小数据的价值恰恰在于看清深度学习我之前带过不少刚入门的朋友&#xff0c;发现一个普遍现象&#xff1a;大家学完张量、卷积、反向传播这些概念之后&#xff0c;最焦虑的问题不是"理论没懂"&#xff0c;而是"下一步…

作者头像 李华
网站建设 2026/10/20 14:14:32

基于LSTM预测控制的地源热泵系统能耗优化实践

做建筑能源管理的朋友应该都有同感——真正难的不是把设备装好&#xff0c;而是怎么让它常年保持高效运行。地源热泵&#xff08;GSHP&#xff09;系统就是个典型例子&#xff0c;光看设计工况&#xff0c;COP随便四五&#xff0c;但实际项目跑起来&#xff0c;很多系统一年的平…

作者头像 李华
网站建设 2026/10/20 14:13:01

Agent记忆系统设计:工作/情景/语义三层架构实战

1. 项目概述&#xff1a;为什么“Agent记忆”不是锦上添花&#xff0c;而是系统能否落地的生死线你有没有遇到过这样的情况&#xff1a;花两周时间调通了一个智能体流程&#xff0c;能准确解析用户意图、调用API、生成结构化回复&#xff0c;结果上线三天就被业务方叫停——因为…

作者头像 李华
网站建设 2026/10/20 14:12:56

中国象棋C语言源码解析:从棋盘表示到AI搜索的完整实现

简介&#xff1a;这是一份面向C语言初学者与棋类游戏开发爱好者的中国象棋完整源代码&#xff0c;基于MantisChess工程实现&#xff0c;帮助读者通过实战项目理解数据结构、算法与游戏逻辑的综合运用。压缩包共85个文件&#xff0c;约143KB&#xff0c;包含10个cpp源文件与14个…

作者头像 李华
网站建设 2026/10/20 14:12:31

私有化部署跨网络穿透与反向隧道:基于 SSH/FRP 的合规远程运维通道搭建

在大型政企、国有银行、高端制造等行业的企业级私有化交付中&#xff0c;交付与运维团队面临的最普遍网络拓扑现状就是&#xff1a;“客户机房处于深层多重 NAT 与严格的物理防火墙隔离之下&#xff0c;完全没有公网 IP&#xff0c;且外部防火墙绝对禁止任何从公网主动发起的入…

作者头像 李华
网站建设 2026/10/20 14:12:30

如何识别插件生态真假?看那60%的僵尸率就够了

大概是从去年开始&#xff0c;我养成了一个不太讨喜的习惯。每次看到哪家平台放出一张漂亮的插件生态图&#xff0c;我不是先点赞&#xff0c;而是先打开数据接口&#xff0c;把插件市场的元数据拉下来算一遍。起因是一次很现实的项目选型。当时某家低代码平台在汇报里放了一张…

作者头像 李华