快速入门
写出你的第一个 C++ 程序,理解从敲下源码到跑出结果之间发生了什么。
学习目标
- 能用自己的话解释 C++ 源码从敲下到跑出结果,中间经过了哪四道工位,以及每道工位做了什么
- 能解释一个最简 C++ 程序中
#include、main、cout、return各自在干什么 - 能回答:如果把
return 0;删掉,程序还能跑吗?删掉#include <iostream>呢?
为什么写下了代码,电脑就能懂?
你敲下一段 C++ 代码,点运行,屏幕上就出现了一行 "Hello, World!"。这正是官方首节 Writing a Simple C++ Program(编写简单 C++ 程序)的任务。这中间其实经过了一条工厂流水线——你的代码是「原料」,经过几道工位一道道加工,最后从流水线另一头出来一个能直接运行的「成品」。
这条流水线有四道工位:第一道把原料里杂七杂八的标注清理干净、展开折叠好的部分;第二道把人类能读的文字翻译成机器"方言";第三道把方言逐字译成机器能认的二进制编码;第四道把分散的零件拼成一个完整的、能直接跑的程序。
这一章就带你走一遍这条流水线的四道工位,同时写出你人生中第一个 C++ 程序。没有这条流水线会怎样?你写的代码永远只是一堆人类能读的文字,电脑一个字也不认识。
你的第一个程序,逐行拆开看
在走流水线之前,先把「原料」长什么样搞清楚。下面这个六行代码就是 C++ 里最经典的一小段:
每一行都在说什么?下面逐一看。
main 函数:程序从这进门
↡每个 C++ 程序都有的一个特殊函数——它标记了程序从哪里开始执行。操作系统运行你的程序时,第一件事就是找到 main。 是整个程序的入口。你写再多代码,操作系统启动你的程序时都只会钻进 main 这个大门。int 表示它最后要交回一个整数,「交给操作系统」——return 0 就是在说「一切正常,我跑完了」。如果把 return 0 删掉?现代编译器一般会自己帮你补一句,但规范上每一扇大门走了就要把"我没事"的纸条留下来。
它处在整个流程的起点:你的所有代码都经过它开始执行,它是流水线前那道「开工」的指令。
头文件:声明"我能用什么"
代码第一行 #include <iostream> 是什么?它是一句 ↡用 #include 引入的文件,里面提前写好了别人帮你实现的功能声明。你 include 了它,就能直接调用里面的东西,不用自己从头写。。iostream 这个头文件里写好了 cin(从键盘读输入)和 cout(往屏幕写输出)的声明。你不 include 它就直接写 cout,编译器会告诉你「我不认识 cout 是什么东西」。
它处在流水线的第一道工位(预处理):预处理器看到 #include,就把那个文件里的所有文字原样粘贴到你这行所在的地方。之后你再引用 cout,编译器才知道「哦,原来这个东西在 iostream 里定义过」——预处理这一步之后继续往下走。
↡程序设计里「一个操作」这样一个独立的单元。C++ 里多数语句以分号 ; 结尾,表示「这句话说完了」。 std::cout << "Hello, World!" << std::endl; 是本文真正干活的一句——把 "Hello, World!" 这段文字送进 cout 输出流,再补一个换行 std::endl,于是屏幕上就出字了。std:: 是 ↡给名字加一个归属前缀,避免不同库里的同名词打架。std 就是「标准库的命名空间」,里面的 cin、cout、endl 都在这个空间里。——可以理解成「住在 std 这个门牌号下的 cout」。在它前面加上 std::,就是在告诉编译器「我用的是 std 空间里的那个 cout,不是别的」。
它和 main 函数里余下的语句,在编译流程中对应第二道和第三道工位:编译器先把这行 C++ 语法解析成汇编指令,汇编器再把它变成二进制机器码。
什么是「流」——cin 和 cout 在干什么
cout 和 cin 都是一种叫 ↡数据像水一样从一个源头流向一个目的地的通道;cout 负责输出,cin 负责输入。 的东西。想象两条管道:cout 是通往屏幕的那根水管,你用 << 把文字往里一塞,它就顺着管道流到控制台窗口显示出来。反过来,cin 是从键盘那边过来的水管,你用 >> 往外抽数据,像用吸管从杯子里吸水。
↡cout 使用的插入运算符把右侧值写入左侧输出流;同一符号在其他类型上可以有不同含义。 << 把右边的数据推向左边那条流管道,>> 则相反。它们背后有缓冲区暂存,数据并不是一个字一个字立刻上屏——等缓冲区满了或遇到 endl,一整批一起推过去,减少来回奔波。下面这张图把两条管道的方向画出来:
流在整个流程里不归属某一道工位——它是运行时才工作的概念。编译流水线只是造出了程序的「外壳」;等到你双击运行,cin/cout 这套管道才真正开始流动。
从控制流走到书店程序
官方 Chapter 1 不止写一个 Hello World。Flow of Control 让程序能重复读取未知条数的交易,并按条件决定继续累计还是输出;Introducing Classes 用 Sales_item 展示“先使用类的接口,再理解实现”;最后的 The Bookstore Program 把这些能力接成完整数据处理流程。
↡决定语句执行顺序的语言结构,包括顺序执行、条件分支和循环;它把一次操作扩展成可处理任意输入的程序。首先体现在 while (std::cin >> item):输入成功时条件为真,循环处理当前交易;遇到 EOF 或格式错误时 stream 状态变为失败,循环自然停止。if (total.isbn() == item.isbn()) 再决定合并同一本书,还是先输出上一组并开始新组。
↡原书第一章用于演示用户自定义类型接口的交易记录类;它封装 ISBN、销量和收入,并提供输入输出与合并操作。让调用者只关心 isbn()、输入、输出和 +=,不必先知道数据成员如何存储。这个顺序体现第五版的 C++11 教学取向:从一开始使用标准库和抽象类型,而不是先陷入字符数组和手工内存。
Sales_item total;
if (std::cin >> total) {
Sales_item item;
while (std::cin >> item) {
if (total.isbn() == item.isbn()) total += item;
else { std::cout << total << '\n'; total = item; }
}
std::cout << total << '\n';
}这段程序还建立了后续章节的阅读方法:先写清输入 contract,使用类型公开接口完成业务,再用控制流组织生命周期,最后以编译、运行和样例输出验收。Chapter 1 的完成标准因此是“能处理一组交易”,不是只看到终端打印一句话。
注释:给自己和队友留话
代码里双斜线 // 开头的那一行、或者 /* 和 */ 裹起来的那一段,叫 ↡代码里写给程序员看的文字,编译器完全忽略。// 是单行注释(从它开始到本行末尾都被忽略),/* */ 是多行注释(裹起来的部分都被忽略)。。它们写给未来看这行代码的人——包括三个月后的你自己。预处理器在第一道工位就把注释剥光了(替换成空格),它们一个字都不会进最终的二进制程序,所以想写多少写多少。
// 只能管到行末;/* */ 能跨行包住一大段。比较:
// 这是一个单行注释——只到这一行末尾
std::cout << "hi"; // 也可以写在语句后面
/* 这是一个多行注释——
可以跨好几行,
裹住一大片 */注释不占用运行时任何资源,因为它们走完预处理就从代码里消失了——在第一道工位,/* */ 形式的注释有时甚至被预处理器直接替换成空格。
动手:跟着编译器走一遍流水线
现在你知道了每行代码在说什么,下面打开这条流水线,一步步看你的代码怎么变成能跑的程序。下面这个分步演示把一个 hello.cpp 从源码走完四道工位——每切一步,上面的流程图会高亮当前工位,下方说明这一步发生了什么。
猜一猜:哪一道工位最耗时?是把头文件展开、还是把 C++ 翻译成汇编?动手切一切,看看每一步的解释再说你的答案。
① 预处理:把 #include、#define 都展开
预处理器先打开你的 hello.cpp。它做三件事:① 见到 #include <iostream>,就把整个 iostream 文件内容原样粘贴进来(可能有几千行);② 见到 #define 宏就把所有用到的地方替换掉;③ 删掉所有注释。输出的是一个「展开过的纯净源代码」——后缀名 .i。你可以理解成:把原料里折叠的、注解的部分全摊平,得到一张干干净净的底稿。
代码速查:一行命令走完流水线
不用 IDE 点按钮,直接在终端里敲一条命令,就能从头到尾跑完上面那四道工位。以 Linux/macOS 上的 g++ 为例:
# 一条命令 = 预处理 + 编译 + 汇编 + 链接
g++ hello.cpp -o hello
# 运行你刚造出来的程序
./hello
# 输出:Hello, World!如果你好奇中间产物的样子,可以分步走:
# 只预处理,看展开的头文件有多长
g++ -E hello.cpp -o hello.i
# 只编译成汇编,看 C++ 语句被翻译成什么
g++ -S hello.cpp -o hello.s
# 只汇编成目标文件(二进制,人类不可读)
g++ -c hello.cpp -o hello.o在 Visual Studio 下,按 Ctrl+F5 运行;Xcode 里点 Product → Run。IDE 背后做的事完全一样。
容易踩的坑
小结
- C++ 源码经过四道工位变成可执行文件:预处理→编译→汇编→链接
int main()是程序入口,return 0告诉操作系统「一切正常」#include在预处理阶段把头文件内容粘贴进来,让你能使用cin/coutcout <<把数据推进输出流送到屏幕,cin >>从输入流取出键盘输入——背后有缓冲区暂存- 注释
//和/* */写给程序员看,编译器在处理时完全忽略它们
练习
问题 1(改代码型) 把上面 Hello World 程序里的 "Hello, World!" 改成 "你好,C++!",再加一行 std::cout << "这是我的第一个程序。" << std::endl;。在 return 0; 之前加一句 std::cin.get(); 让窗口停住。跑起来,解释你加的每一句各干了什么。
问题 2(问答型) 编译流水线的四道工位如果出一道错,分别会长什么样?各举一个例子。
问题 3(问答型) 把 #include <iostream> 删掉,程序还能编译吗?把这行注释掉试试(用 //),把你看到的错误信息抄下来,解释编译器为什么报这个错。
名词解释
本章出现的专业名词,用大白话再讲一遍。
- main 函数
C++ 程序的入口。操作系统运行你的程序第一件事就是钻进
main,从它开始一行一行往下跑。int表示它会交回一个整数给操作系统,return 0就是说「正常结束了」。详见本章「main 函数」一节。- 头文件
用
#include拉进来的文件,里面写好了别人为你准备好的东西的声明。比如<iostream>里声明了cin和cout——你不是自己写它们怎么工作,而是 include 后直接用。它在预处理阶段被「粘贴」进你的源码。详见本章「头文件」一节。- 语句
程序里一个独立操作单元。多数 C++ 语句以分号
;结束,表达「这句话说完了,可以执行下一句」。int x = 1;、cout << "hi";都是语句。- 命名空间
一群相关名字的「门牌号」。
std是 C++ 标准库住的那个门牌号——std::cout意思是「std 门牌号下的那个 cout」。加上门牌号,不同库里即使起了同样的名字也不会打架。详见本章「头文件」一节。- 流(stream)
把数据想象成水,流就是它的通道。
cout是通向屏幕的「输出流管道」,用<<把数据往里一塞就顺管流到控制台。cin是从键盘过来的「输入流管道」,用>>往外抽数据。管道两头有缓冲池暂存,满了或遇到endl才一口气推过去。详见本章「什么是流」一节。- 输出运算符
<<符号在cout << x里不是「向左移位」,而是被重新赋予了含义:把右边的东西推进左边那根流管道。同理cin >> x的>>是「从左边管道里拉东西出来给右边」。二者都是对同一个符号赋予了与人读的直觉一致的语义。详见本章「什么是流」一节。- 控制流
决定程序下一步执行哪条语句的结构。本章用输入流状态驱动
while,再用 ISBN 比较驱动if/else,从而处理任意数量的书店交易。- Sales_item 类
原书第一章用于表示一条书店交易的类,通过输入输出、
isbn()和+=接口演示如何先使用抽象类型,再学习类的内部实现。- 注释
代码里写给程序员看的文字,编译器完全忽略。
//是单行注释(本行到此完),/* */是多行注释(中间全忽略)。给三个月后的自己留句话,别全靠记忆力。详见本章「注释」一节。