Skip to content

第一个 C++ 程序

环境搭好了,编译器也装好了,接下来该干正事了——写我们的第一行 C++ 代码。

每一门语言的第一课总是Hello, World。不管我是学C#, Rust, C++, C, Java, Kotlin。。。说真的所有教程都是起手Hello World打印。这一点我觉得应该是传奇KR C那本书带来的,也就是《C语言程序设计》。写书人我记得就是搞出来C的佬。没啥说的,致敬!

但我可以保证,如果我们把这个小程序拆清楚,后面很多概念都会变得顺理成章。所以别急着跳过,我们一行一行地把它吃透。

从零开始——hello.cpp 的骨架

打开你喜欢的编辑器,新建一个文件叫 hello.cpp,把下面这段代码原封不动地敲进去。注意是敲进去,不是复制粘贴(我们常常调侃,程序员只有三个按键,Ctrl, C, V,我澄清一下,真学习的时候别这样做。这种事情留到任何你不感兴趣但是不得不做的工作上,比如说写我不感兴趣的业务代码)——肌肉记忆这个东西,在学编程的时候真的很重要。

cpp
#include <iostream>

int main()
{
    std::cout << "Hello, C++!" << std::endl;
    return 0;
}

仓库中,笔者是用了一个CMake组织了项目的编译,但是实际上,你要是

bash
g++ hello_world.cpp -o hello_world
./hello_world

我也没意见,但是我更加推介你用cmake:

bash
cd /path/to/project
cmake -B build -S .
cmake --build build -j${nproc}
./build/hello_world

运行结果:

text
Hello, C++!

六行代码,看起来简单得离谱。但这里面其实藏着好几个关键概念,我们现在来逐行拆解。

第一行:#include <iostream>

这行告诉编译器:我们需要用到"输入输出流"这个功能模块。你可以把它理解成在工具箱里拿出一个叫做 iostream 的工具包——里面有 std::cout(用来输出)和 std::cin(用来输入),这些是我们和程序打交道的最基本手段。C++ 标准库里有大量的这样的工具包,比如 <vector><string><cmath>,需要什么就包含什么。

这里的尖括号 < > 表示这是一个系统头文件,编译器会去系统的标准库路径里找它。如果是自己写的头文件,就用双引号 "my_header.h",编译器会先在当前目录搜索。

第二行:int main()

这是整个程序的入口。操作系统启动我们的程序时,就是从这里开始执行的。int 表示这个函数会返回一个整数给操作系统——返回 0 意味着"一切正常",返回非零值意味着"出了问题"。这个返回值在 Linux 脚本里可以通过 $? 拿到,CI/CD 流水线也经常依赖它来判断程序是否执行成功。

第三到第五行:函数体

cpp
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::coutstd::endl 前面都有一个 std:: 前缀。std 是"标准"(standard)的缩写,它是一个命名空间——可以理解为一个工具包的品牌标签。C++ 标准库里的所有东西都放在 std 这个命名空间里,用来避免名字冲突。比如你自己写了一个叫 cout 的函数,它不会和标准库的 std::cout 打架,因为它们在不同的命名空间里。有些教程会在开头加一行 using namespace std; 然后直接写 cout,这样确实省打字,但在大型项目里容易引发命名冲突,所以我们从一开始就习惯带上 std:: 前缀。

编译和运行

代码写好了,接下来让它跑起来。打开终端,进入 hello.cpp 所在的目录,执行:

bash
g++ -o hello hello.cpp

这条命令做了两件事:用 g++ 编译器把 hello.cpp 编译成可执行文件,-o hello 指定输出文件名叫 hello(如果不指定,默认会叫 a.out,这个名字没什么意义)。编译成功后,当前目录下会出现一个 hello 文件,直接运行它:

bash
./hello

输出:

text
Hello, C++!

很好,你的第一个 C++ 程序已经成功运行了。

如果你之前看过了环境搭建那一章,可能还记得 CMake 的用法。对于这种单文件的小程序,直接用 g++ 命令是最快的。但随着项目变大、文件变多,每次手动敲编译命令会让人崩溃——那时候 CMake 的价值就体现出来了。我们这里先用 g++,等后面的章节再正式引入 CMake。

幕后发生了什么——编译流水线

欸,这我有的说了,笔者真见过计算机民科跟我刚——你说什么编译链接执行步骤呢,现在我们都是点一下运行按钮就能运行了。

我每一次看到这个就想笑。每次讲到这个我包是拿出来鞭尸这个人的。典型的计算机学了一点就出来卖弄。来,我跟你们说说这背后到底多复杂:

每次敲 g++ -o hello hello.cpp 的时候,背后其实跑了一个完整的流水线。我们不需要深入每个阶段的细节,但至少要知道这个流程的存在,因为以后碰到编译报错的时候,知道错误出在哪个阶段,能帮你快速定位问题。

整个流程可以简化为四个步骤。第一步是预处理,编译器处理所有以 # 开头的指令——把 #include <iostream> 替换成 iostream 头文件的实际内容,展开宏定义,处理条件编译。第二步是编译,把预处理后的 C++ 代码翻译成汇编语言——这时候编译器会做语法检查、类型检查,你写的语法错误在这一步就会被抓住。第三步是汇编,把汇编代码翻译成机器码,生成目标文件(.o 文件)。第四步是链接,把目标文件和需要用到的库文件(比如 C++ 标准库)组合在一起,生成最终的可执行文件。

text
hello.cpp → [预处理] → [编译] → [汇编] → [链接] → hello

你可能会问:为什么要知道这些?因为以后你一定会遇到各种编译报错——有些是预处理阶段的问题(头文件找不到),有些是编译阶段的问题(语法错误、类型不匹配),有些是链接阶段的问题(重复定义、找不到符号)。知道错误出在哪个阶段,排查起来就有方向了。

⚠️ 踩坑预警:编译器报错时,一定要看第一条错误信息。很多新手习惯从最后一条看起,但实际上 C++ 编译器有一个"级联报错"的特性——一个错误可能导致后面几十个"假阳性"的错误。修复了第一条,后面那些可能就自动消失了。所以养成习惯:看第一条,修第一条,重新编译,再看。

那些年我们踩过的坑——常见编译错误

光会写正确的代码是不够的,我们还必须学会读错误信息。下面我们故意制造几个经典错误,看看编译器会怎么说。

忘记加分号

hello.cpp 里的分号去掉:

cpp
#include <iostream>

int main()
{
    std::cout << "Hello, C++!" << std::endl  // 这里少了分号
    return 0;
}

编译一下:

bash
g++ -o hello hello.cpp
text
hello.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> 那行删掉,再编译:

text
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

text
hello.cpp:3:10: error: 'couth' is not a member of 'std'
    3 |     std::couth << "Hello, C++!" << std::endl;
      |          ^~~~~

错误信息很直接——couth 不是 std 的成员。仔细检查拼写就行。这类错误在初学阶段特别常见,coutcin 经常被敲成 couthcim 之类的,多写几遍就熟悉了。

⚠️ 踩坑预警:如果你用的是 GCC,建议编译时加上 -Wall -Wextra 选项,即 g++ -Wall -Wextra -o hello hello.cpp。这两个选项会开启大量的警告信息——虽然警告不阻止编译,但它们往往指向潜在的问题。把警告当错误来对待,是成为合格 C++ 程序员的第一步。

再进一步——和程序对话

光能输出还不够,我们让程序能接收输入。新建一个文件叫 calc.cpp,实现一个简单的加法计算器。

我们先写骨架,再逐步填充。首先,我们需要从用户那里读取两个数字,所以要用到 std::cin(c = character,in = input),它是 std::cout 的好搭档。

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

编译运行:

bash
g++ -o calc calc.cpp
./calc
text
❯ ./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 读取用户的年龄,然后输出一段包含年龄的问候语。预期交互效果如下:

text
请输入你的年龄: 24
你好!你今年 24 岁了,是个学生。

练习三:摄氏转华氏

写一个 convert.cpp,读取一个摄氏温度,转换为华氏温度后输出。转换公式是 F = C * 9 / 5 + 32。预期交互效果:

text
请输入摄氏温度: 25
25°C = 77°F

这三个练习覆盖了本章的所有核心知识点:变量声明、输入输出、基本运算。如果三个都能独立完成,说明你已经完全掌握了本章内容。

小结

这一章我们从零开始,写了一个完整的 C++ 程序,并且把它拆得七零八碎地看了一遍。我们来回顾一下关键点:#include 用来引入标准库的功能模块,int main() 是程序入口,std::coutstd::cin 分别负责输出和输入,<<>> 是对应的数据流向运算符,编译需要经过预处理、编译、汇编、链接四个阶段。

更重要的是,我们学会了如何阅读编译器的错误信息——这可能是这章最实用的技能。在接下来的学习里,你会无数次面对编译器的报错,别害怕,看第一条、修第一条、重新编译。

下一章我们开始学习 C++ 的类型系统——变量到底是怎么存储数据的,整数和浮点数有什么区别,为什么 C++ 对类型这么执着。这些知识是我们后续写任何有意义程序的基础。

基于 VitePress 构建