第一个 C++ 程序
环境搭好了,编译器也装好了,接下来该干正事了——写我们的第一行 C++ 代码。
每一门语言的第一课总是Hello, World。不管我是学C#, Rust, C++, C, Java, Kotlin。。。说真的所有教程都是起手Hello World打印。这一点我觉得应该是传奇KR C那本书带来的,也就是《C语言程序设计》。写书人我记得就是搞出来C的佬。没啥说的,致敬!
但我可以保证,如果我们把这个小程序拆清楚,后面很多概念都会变得顺理成章。所以别急着跳过,我们一行一行地把它吃透。
从零开始——hello.cpp 的骨架
打开你喜欢的编辑器,新建一个文件叫 hello.cpp,把下面这段代码原封不动地敲进去。注意是敲进去,不是复制粘贴(我们常常调侃,程序员只有三个按键,Ctrl, C, V,我澄清一下,真学习的时候别这样做。这种事情留到任何你不感兴趣但是不得不做的工作上,比如说写我不感兴趣的业务代码)——肌肉记忆这个东西,在学编程的时候真的很重要。
#include <iostream>
int main()
{
std::cout << "Hello, C++!" << std::endl;
return 0;
}仓库中,笔者是用了一个CMake组织了项目的编译,但是实际上,你要是
g++ hello_world.cpp -o hello_world
./hello_world我也没意见,但是我更加推介你用cmake:
cd /path/to/project
cmake -B build -S .
cmake --build build -j${nproc}
./build/hello_world运行结果:
Hello, C++!六行代码,看起来简单得离谱。但这里面其实藏着好几个关键概念,我们现在来逐行拆解。
第一行:#include <iostream>
这行告诉编译器:我们需要用到"输入输出流"这个功能模块。你可以把它理解成在工具箱里拿出一个叫做 iostream 的工具包——里面有 std::cout(用来输出)和 std::cin(用来输入),这些是我们和程序打交道的最基本手段。C++ 标准库里有大量的这样的工具包,比如 <vector>、<string>、<cmath>,需要什么就包含什么。
这里的尖括号 < > 表示这是一个系统头文件,编译器会去系统的标准库路径里找它。如果是自己写的头文件,就用双引号 "my_header.h",编译器会先在当前目录搜索。
第二行:int main()
这是整个程序的入口。操作系统启动我们的程序时,就是从这里开始执行的。int 表示这个函数会返回一个整数给操作系统——返回 0 意味着"一切正常",返回非零值意味着"出了问题"。这个返回值在 Linux 脚本里可以通过 $? 拿到,CI/CD 流水线也经常依赖它来判断程序是否执行成功。
第三到第五行:函数体
std::cout << "Hello, C++!" << std::endl;
return 0;std::cout 是"字符输出"的意思(c = character,out = output),你可以把它理解成屏幕。<< 运算符在这里被重新定义了,它的作用是把右边的内容"推送"到左边的输出流里。所以 std::cout << "Hello, C++!" 就是把这段文字推送到屏幕上。
std::endl 是"end line"的缩写,它做了两件事:输出一个换行符,然后刷新缓冲区——也就是说它确保你的文字立刻出现在屏幕上,而不是被暂存在某个角落里。
最后 return 0 告诉操作系统:我正常结束了,没什么好担心的。
⚠️ 踩坑预警:有些教程或者老代码里你会看到
void main()这种写法。这是错的。C++ 标准明确规定main的返回类型必须是int,虽然某些老旧编译器可能不会报错,但这不代表它是对的。养成习惯,永远写int main()。
你可能注意到 std::cout 和 std::endl 前面都有一个 std:: 前缀。std 是"标准"(standard)的缩写,它是一个命名空间——可以理解为一个工具包的品牌标签。C++ 标准库里的所有东西都放在 std 这个命名空间里,用来避免名字冲突。比如你自己写了一个叫 cout 的函数,它不会和标准库的 std::cout 打架,因为它们在不同的命名空间里。有些教程会在开头加一行 using namespace std; 然后直接写 cout,这样确实省打字,但在大型项目里容易引发命名冲突,所以我们从一开始就习惯带上 std:: 前缀。
编译和运行
代码写好了,接下来让它跑起来。打开终端,进入 hello.cpp 所在的目录,执行:
g++ -o hello hello.cpp这条命令做了两件事:用 g++ 编译器把 hello.cpp 编译成可执行文件,-o hello 指定输出文件名叫 hello(如果不指定,默认会叫 a.out,这个名字没什么意义)。编译成功后,当前目录下会出现一个 hello 文件,直接运行它:
./hello输出:
Hello, C++!很好,你的第一个 C++ 程序已经成功运行了。
如果你之前看过了环境搭建那一章,可能还记得 CMake 的用法。对于这种单文件的小程序,直接用 g++ 命令是最快的。但随着项目变大、文件变多,每次手动敲编译命令会让人崩溃——那时候 CMake 的价值就体现出来了。我们这里先用 g++,等后面的章节再正式引入 CMake。
幕后发生了什么——编译流水线
欸,这我有的说了,笔者真见过计算机民科跟我刚——你说什么编译链接执行步骤呢,现在我们都是点一下运行按钮就能运行了。
我每一次看到这个就想笑。每次讲到这个我包是拿出来鞭尸这个人的。典型的计算机学了一点就出来卖弄。来,我跟你们说说这背后到底多复杂:
每次敲 g++ -o hello hello.cpp 的时候,背后其实跑了一个完整的流水线。我们不需要深入每个阶段的细节,但至少要知道这个流程的存在,因为以后碰到编译报错的时候,知道错误出在哪个阶段,能帮你快速定位问题。
整个流程可以简化为四个步骤。第一步是预处理,编译器处理所有以 # 开头的指令——把 #include <iostream> 替换成 iostream 头文件的实际内容,展开宏定义,处理条件编译。第二步是编译,把预处理后的 C++ 代码翻译成汇编语言——这时候编译器会做语法检查、类型检查,你写的语法错误在这一步就会被抓住。第三步是汇编,把汇编代码翻译成机器码,生成目标文件(.o 文件)。第四步是链接,把目标文件和需要用到的库文件(比如 C++ 标准库)组合在一起,生成最终的可执行文件。
hello.cpp → [预处理] → [编译] → [汇编] → [链接] → hello你可能会问:为什么要知道这些?因为以后你一定会遇到各种编译报错——有些是预处理阶段的问题(头文件找不到),有些是编译阶段的问题(语法错误、类型不匹配),有些是链接阶段的问题(重复定义、找不到符号)。知道错误出在哪个阶段,排查起来就有方向了。
⚠️ 踩坑预警:编译器报错时,一定要看第一条错误信息。很多新手习惯从最后一条看起,但实际上 C++ 编译器有一个"级联报错"的特性——一个错误可能导致后面几十个"假阳性"的错误。修复了第一条,后面那些可能就自动消失了。所以养成习惯:看第一条,修第一条,重新编译,再看。
那些年我们踩过的坑——常见编译错误
光会写正确的代码是不够的,我们还必须学会读错误信息。下面我们故意制造几个经典错误,看看编译器会怎么说。
忘记加分号
把 hello.cpp 里的分号去掉:
#include <iostream>
int main()
{
std::cout << "Hello, C++!" << std::endl // 这里少了分号
return 0;
}编译一下:
g++ -o hello hello.cpphello.cpp: In function 'int main()':
hello.cpp:5:5: error: expected ';' before 'return'
5 | return 0;
| ^~~~~~~
| ;
hello.cpp:4:42: note: ...after this token
4 | std::cout << "Hello, C++!" << std::endl
| ^
| ;编译器告诉你:在第 5 行的 return 之前,它期望看到一个分号。虽然错误标记在第 5 行,但实际问题出在第 4 行末尾——这种"错误位置和报错位置差一行"的情况在 C++ 里非常常见,记住这个规律就好。
忘记包含头文件
把 #include <iostream> 那行删掉,再编译:
hello.cpp: In function 'int main()':
hello.cpp:3:5: error: 'cout' is not a member of 'std'
3 | std::cout << "Hello, C++!" << std::endl;
| ^~~
hello.cpp:3:5: note: suggested alternative: 'count'
hello.cpp:3:5: error: 'endl' is not a member of 'std'
3 | std::cout << "Hello, C++!" << std::endl;
| ^~~~编译器说"cout 不是 std 的成员"——因为它根本不知道 std::cout 是什么,没人告诉过它。解决方案就是加回 #include <iostream>。有意思的是 GCC 还会"好心"地建议你是不是想写 count,这个有时候挺搞笑的。
拼写错误
把 std::cout 写成 std::couth:
hello.cpp:3:10: error: 'couth' is not a member of 'std'
3 | std::couth << "Hello, C++!" << std::endl;
| ^~~~~错误信息很直接——couth 不是 std 的成员。仔细检查拼写就行。这类错误在初学阶段特别常见,cout 和 cin 经常被敲成 couth 和 cim 之类的,多写几遍就熟悉了。
⚠️ 踩坑预警:如果你用的是 GCC,建议编译时加上
-Wall -Wextra选项,即g++ -Wall -Wextra -o hello hello.cpp。这两个选项会开启大量的警告信息——虽然警告不阻止编译,但它们往往指向潜在的问题。把警告当错误来对待,是成为合格 C++ 程序员的第一步。
再进一步——和程序对话
光能输出还不够,我们让程序能接收输入。新建一个文件叫 calc.cpp,实现一个简单的加法计算器。
我们先写骨架,再逐步填充。首先,我们需要从用户那里读取两个数字,所以要用到 std::cin(c = character,in = input),它是 std::cout 的好搭档。
#include <iostream>
int main()
{
int a = 0;
int b = 0;
std::cout << "请输入第一个数字: ";
std::cin >> a;
std::cout << "请输入第二个数字: ";
std::cin >> b;
int sum = a + b;
std::cout << a << " + " << b << " = " << sum << std::endl;
return 0;
}编译运行:
g++ -o calc calc.cpp
./calc❯ ./build/calc
请输入第一个数字: 1
请输入第二个数字: 2
1 + 2 = 3这里有几个值得注意的地方。int a = 0; 声明了一个整数类型的变量,并且初始化为 0。std::cin >> a; 中的 >> 运算符和 << 的方向相反——它从输入流中"抽取"数据放到变量 a 里。你可以把 << 理解为"推出去"(输出),>> 理解为"拉进来"(输入),箭头的方向就是数据的流向。
std::cout << a << " + " << b << " = " << sum << std::endl; 这一行连续用了多个 << 运算符,它们从左到右依次执行:先输出 a 的值,再输出字符串 " + ",再输出 b 的值,依此类推。这种"链式"写法在 C++ 里非常常见,习惯就好。
变量声明那里,我们用了 int a = 0; 而不是 int a;,这是故意的。C++ 不会自动给局部变量初始化——如果不赋初值,a 的值就是内存里残留的垃圾数据。虽然后面 std::cin 会立刻覆盖它,但养成"声明即初始化"的习惯非常重要,这能帮你避开一大类难以调试的问题。
动手试试
到这里,我们已经能把代码写出来、编译、运行、读错误信息了。接下来是检验你学习成果的时候了——光看不练等于没学。以下是三个练习,难度递增,建议每个都动手写一遍。
练习一:输出你的名字
修改 hello.cpp,让程序输出你的名字而不是 "Hello, C++!"。比如输出 "大家好啊!我是说的道理!"。
练习二:读取年龄并问候
写一个新程序 age.cpp,用 std::cin 读取用户的年龄,然后输出一段包含年龄的问候语。预期交互效果如下:
请输入你的年龄: 24
你好!你今年 24 岁了,是个学生。练习三:摄氏转华氏
写一个 convert.cpp,读取一个摄氏温度,转换为华氏温度后输出。转换公式是 F = C * 9 / 5 + 32。预期交互效果:
请输入摄氏温度: 25
25°C = 77°F这三个练习覆盖了本章的所有核心知识点:变量声明、输入输出、基本运算。如果三个都能独立完成,说明你已经完全掌握了本章内容。
小结
这一章我们从零开始,写了一个完整的 C++ 程序,并且把它拆得七零八碎地看了一遍。我们来回顾一下关键点:#include 用来引入标准库的功能模块,int main() 是程序入口,std::cout 和 std::cin 分别负责输出和输入,<< 和 >> 是对应的数据流向运算符,编译需要经过预处理、编译、汇编、链接四个阶段。
更重要的是,我们学会了如何阅读编译器的错误信息——这可能是这章最实用的技能。在接下来的学习里,你会无数次面对编译器的报错,别害怕,看第一条、修第一条、重新编译。
下一章我们开始学习 C++ 的类型系统——变量到底是怎么存储数据的,整数和浮点数有什么区别,为什么 C++ 对类型这么执着。这些知识是我们后续写任何有意义程序的基础。