先编译,再运行
现在你已经装好了编译器,让 C++ 程序运行起来是一个两步的循环。脚本语言由解释器逐行读取你的文件,而 C++ 不同,它先把源代码编译成本机可执行文件,也就是一个由机器码组成的独立二进制文件,然后你直接运行这个二进制文件。运行时,在你的程序和 CPU 之间没有任何解释器。
正是这种分离让 C++ 运行得很快,也让语法错误在编译时就把你拦下,而不是在运行到一半时才出问题。编译器在生成任何东西之前,会先检查整个文件。
任何独立的示例都可以直接在本页运行,下面的编辑器会替你编译并运行。但了解在你自己的电脑上会发生什么也很有必要,因为真正的项目都在那里。
在你的电脑上编译和运行
假设你把下面的代码保存为 main.cpp:
#include <iostream>
using namespace std;
int main() {
cout << "Hello from the terminal" << endl;
return 0;
}
在保存该文件的文件夹中打开终端,调用编译器。最常用的编译器是 g++(GCC 的一部分);对于这里的所有内容,clang++ 的用法完全相同:
g++ main.cpp -o main
如果代码顺利通过编译,g++ 什么也不会输出,并在源文件旁边生成一个新文件:一个名为 main 的可执行文件(在 Windows 上是 main.exe)。这个二进制文件是机器码,不是可读的文本,并且绑定于你的操作系统和 CPU。现在运行它:
./main
Hello from the terminal
在 Windows 上,你会在同一个终端里用 main.exe(或者只写 main)来运行它。macOS 和 Linux 上的 ./ 告诉 shell:“程序就在当前这个文件夹里,而不是在 PATH 上的某个地方”。新手常常忘了这一步,然后纳闷 main 为什么提示“command not found”。
-o 的作用(以及 a.out)
-o 选项用来指定输出文件名。去掉它,g++ 照样会编译,但会把可执行文件写成一个默认的名字:macOS/Linux 上是 a.out,Windows 上是 a.exe。
g++ main.cpp # produces a.out, not main
./a.out
这一点总是让人犯迷糊:编译之后没有报错,运行 ./main,却得到“No such file or directory”,因为生成的程序其实叫 a.out。始终加上 -o,这样你就清楚自己运行的到底是什么。
源代码每改动一次,你只需要编译一次。之后可执行文件就是独立的了,你可以运行 ./main 一百次而无需重新编译。只有在修改了 .cpp 文件之后,才需要再次运行 g++。
选择 C++ 标准
C++ 有多个版本:C++11、C++14、C++17、C++20、C++23,每个版本都增加了新的语言特性。问题在于:编译器选择的默认标准可能比你预想的更旧,所以现代代码可能会莫名其妙地编译失败。用 -std 明确设置标准:
g++ -std=c++17 main.cpp -o main
g++ -std=c++20 main.cpp -o main
下面的代码用到了 C++17 的一个特性(结构化绑定)。它在编辑器中可以正常编译,但在你的电脑上需要 -std=c++17 或更新的标准:
如果你看到类似“'structured bindings' only available with '-std=c++17'”的错误,问题不在你的代码里,而是需要加上正确的 -std 选项。在整个课程中,默认使用 C++17 或更新的标准。
打开警告
一个 C++ 程序可能顺利通过编译,却仍然是错的。只要你要求,编译器就会在可疑的代码给你带来运行时问题之前把它指出来。加上 -Wall -Wextra:
g++ -std=c++17 -Wall -Wextra main.cpp -o main
看看下面这个程序。不加 -Wall 时它能编译通过,但它有一个真正的 bug:读取了一个从未赋值的变量,这属于未定义行为:
#include <iostream>
using namespace std;
int main() {
int count; // never initialized
cout << count << endl; // reads garbage - undefined behavior
return 0;
}
打开警告后,编译器会把它标出来:
warning: 'count' is used uninitialized [-Wuninitialized]
从第一天起就养成用 -Wall -Wextra 编译的习惯。警告就是编译器在免费帮你审查代码;忽略它们,隐蔽的 bug 就会存活下来。这里的修复很简单:int count = 0;。
读懂编译错误
当 g++ 拒绝你的代码时,它会指出文件、行号和出错的原因。学会读懂这些信息,问题就解决了一半。下面是最经典的一个:漏掉了分号。
#include <iostream>
using namespace std;
int main() {
cout << "Oops" // no semicolon
return 0;
}
main.cpp:5:5: error: expected ';' before 'return'
5 | return 0;
| ^~~~~~
有几点值得注意。错误报告的是第 5 行,但错误其实在第 4 行:编译器只有读到下一个记号时,才意识到少了一个分号。所以当错误指向的那一行看起来没问题时,就检查它的前一行。main.cpp:5:5 依次是文件、行号和列号。修复它指出的那一个问题,然后重新编译,不要靠猜。
编译错误意味着程序还没有运行。它们和运行时错误不同,运行时错误是程序已经启动然后崩溃了。在程序运行之前就在编译时捕获错误,是 C++ 最大的优势之一。
一个自检程序
在编辑器中运行它,或者把它保存为 main.cpp,然后在你自己的电脑上执行 g++ -std=c++17 -Wall main.cpp -o main,再执行 ./main。如果三行都出现了,说明你的工具链从头到尾都能正常工作:
这里出现了三样你很快就会正式学到的东西:一个 int 变量、一个 vector(C++ 的可变大小数组),以及用于输出的 cout。现在只要程序能编译并按顺序打印出三行就够了。
下一篇:C++ 语法
你已经编译并运行了几个程序,但我们一直略过了那些符号:#include 行、花括号、分号、int main(),以及每一行为什么长成这样。下一篇会逐一拆解 C++ 语法,让这些结构不再像是样板代码,而是变得合情合理。
常见问题
怎么编译和运行 C++ 程序?
把代码保存为 main.cpp,在该文件夹中打开终端,运行 g++ main.cpp -o main 生成可执行文件。然后在 macOS/Linux 上用 ./main 运行,在 Windows 上用 main.exe 运行。编译一次之后,生成的程序可以运行任意多次。
g++ 的 -o 选项有什么作用?
-o 用来指定输出文件的名字。g++ main.cpp -o hello 会生成一个名为 hello 的可执行文件。不写 -o 的话,g++ 默认输出 a.out(在 Windows 上是 a.exe),这就是新手常常运行了一个自己都不知道生成过的文件的原因。
怎么用 C++17 或 C++20 这样的特定标准编译 C++?
传入 -std 选项:g++ -std=c++17 main.cpp -o main 或 -std=c++20。不加的话,编译器会使用它自己的默认标准,可能比你预想的要旧,所以结构化绑定或 <ranges> 这样的较新特性可能会编译失败,直到你明确设置标准为止。