这一章在干嘛?
盖房子先搭框架。这一章带你逐行解剖两三个真实可跑的 C++ 程序,搞清楚每个程序的必备骨架:必须有 main() 函数、用 cout 输出、用 cin 输入、用变量存数据、用函数封装模块。学完你就能独立写出”输入 → 处理 → 输出”的简单程序了。
2.1 进入 C++:第一个程序
程序清单 2.1 myfirst.cpp:
// myfirst.cpp -- displays a message
#include <iostream> // a PREPROCESSOR directive
int main() // function header
{
using namespace std; // make definitions visible
cout << "Come up and C++ me some time."; // message
cout << endl; // start a new line
cout << "You won't regret it!" << endl;
return 0; // terminate main()
}运行结果:
Come up and C++ me some time.
You won't regret it!注意 C++ 区分大小写:写成 Cout 或 COUT 编译器会直接拒绝,说你不认识 cout。程序包含这些元素:
- 以
//开头的注释(编译器无视它们); - 预处理器指令
#include; - 函数头
int main(); using namespace编译指令;- 花括号
{}括起来的函数体; - 用 cout 显示消息的语句;
- 结束 main() 的
return语句。
如果窗口一闪而过,在 return 前加 cin.get();(必要时加两条)。另外 C++ 也能用 C 的 printf()、scanf(),但本书用更聪明的 C++ 输入输出方案。
2.2 解剖 main 函数
去掉装饰后,程序的基本结构是:
int main()
{
statements
return 0;
}这几行合起来叫函数定义(function definition),分两部分:第一行是函数头(function header),花括号里的部分是函数体(function body)。函数头概括了函数与程序其他部分的接口,函数体则是”让计算机干什么”的指令。C++ 中每条完整指令叫一条语句(statement),必须以分号结尾。

▲ 图 2.1 main() 函数
分号是终止符,不是分隔符。Pascal 用分号”分隔”两条语句,某些位置可以省略;C++(像 C 一样)把分号当作语句的组成部分——所以永远不要省略分号。
函数头为什么长这样?int 是返回类型——main() 向调用者交回一个整数;空括号表示参数列表为空——main() 不从调用者那里接收任何信息。谁调用了 main()?是编译器加进来的启动代码,它负责在程序和操作系统之间居中协调,所以 main() 的返回值最终交给了操作系统。经典 C 风格的 main()(省略返回类型)已在 C++ 中被淘汰;int main(void) 与 int main() 等价(C++ 中空括号就等于 void);而 void main() 不是标准的一部分,某些系统能跑、另一些会失败——别用。
标准还给了 main() 一个专属优惠:如果执行到达 main() 末尾还没遇到 return,效果等同于 return 0;。这个隐式返回只对 main() 有效,其他函数没有。
为什么必须叫 main():C++ 程序执行总是从 main() 的开头开始,没有 main() 就不是完整程序。例外情况:Windows 的动态链接库(DLL)模块、机器人控制器芯片程序等非独立程序可以没有 main()。另外拼写和大小写都要对——Main()、MAIN()、mane() 都不算数。
2.3 注释、预处理器与头文件
注释:// 引出单行注释,可独占一行也可跟在代码后面。C 风格注释 /* ... */ 以 */ 结尾,可以跨行,但容易忘配对,建议坚持用 //。注释是写给人看的,程序越复杂越有价值。
预处理器与 iostream:#include <iostream> 让预处理器(编译前自动处理源文件的程序)把 iostream 文件的内容加进你的程序——iostream 里的 io 指 input/output,你的程序要用 cout 显示消息,就得有这个文件里的定义。使用 cin 和 cout 的程序都必须包含它。
头文件命名:像 iostream 这样的文件叫包含文件或头文件。命名惯例经过了几次演变:
| 头文件种类 | 惯例 | 示例 | 说明 |
|---|---|---|---|
| C++ 旧式 | 以 .h 结尾 | iostream.h | 可被 C++ 程序使用 |
| C 旧式 | 以 .h 结尾 | math.h | C、C++ 程序都能用 |
| C++ 新式 | 无扩展名 | iostream | 可被 C++ 程序使用,使用命名空间 std |
| 转换后的 C | 加 c 前缀、无扩展名 | cmath | 可被 C++ 程序使用,可能使用非 C 特性如 namespace std |
ANSI/ISO 委员会曾想统一给 C++ 头文件定一个 .hpp 之类的扩展名,结果谁也说服不了谁,最终”达成了一致:什么都不用”。
命名空间:不用 iostream.h 而用 iostream 时,就要配合 using namespace std;。为什么?设想两个厂商的库都有个 wanda() 函数,编译器怎么知道你要哪个?命名空间让厂商把产品装进”带标签的箱子”里:Microflop::wanda() 和 Piscine::wanda() 各归各。标准库的东西统统装在名为 std 的箱子里,所以 cout 的全名其实是 std::cout,endl 是 std::endl。
三种省事程度的写法:
using namespace std; // 图省事:std 里所有名字都可用
using std::cout; // 更克制:只开放 cout
using std::endl; // 只开放 endl
std::cout << "hi"; // 最克制:每次都写全名大型项目里 using namespace std; 被视为偷懒且有隐患,推荐后两种;简单程序用它无妨,本书开头也用它。
2.4 用 cout 输出
cout << "Come up and C++ me some time.";双引号括起的字符系列叫字符串。<< 指示信息流向——把字符串”插入”输出流。cout 是一个预定义对象:你不需要知道它的内部结构,只需要知道接口用法。这就是对象的好处——先会用,后懂原理。


▲ 图 2.2 用 cout 显示字符串
运算符重载初见:<< 本来是按位左移运算符,这里被赋予了”插入输出流”的新含义——同一个符号按上下文表达不同含义,就是运算符重载。C 自己也有:& 既是取地址又是按位与,* 既是乘法又是解引用。就像”sound card”和”sound financial basis”里的 sound,靠语境判断意思。
endl 操纵器:cout << endl; 让屏幕光标移到下一行行首。这类对 cout 有特殊含义的记号叫操纵器(manipulator)。关键点:cout 输出完不会自动换行!省略 endl 的后果:
Come up and C++ me some time. You won't regret it!两句话挤在了一行。想接缝处有空格,就把空格写进字符串里。
换行符 \n:更古老的方式,把 \n 嵌在字符串里更省事:
cout << "Pluto is a dwarf planet.\n"; // 显示文本并换行
cout << "Pluto is a dwarf planet." << endl; // 同样效果区别在于:endl 保证立即刷新输出(马上显示到屏幕),"\n" 没有这个保证——某些系统上提示符可能要等你输完才显示出来。显示字符串时用 \n 省键击,其他场合用 endl。
2.5 源代码格式与空白
C++ 用分号标示语句结束,回车、空格、制表符的地位是平等的——你可以把一条语句摊成几行,也可以把几条挤成一行。下面这段”奇丑但合法”的代码完全有效:
#include <iostream>
int
main
() { using
namespace
std; cout
<<
"Come up and C++ me some time."
; cout <<
endl; cout <<
"You won't regret it!" <<
endl; return 0; }但有红线:不能在元素中间插空白——名字中间不行,字符串中间的回车也不行(C++11 的原始字符串除外):
int ma in() // 非法:名字里有空格
re
turn 0; // 非法:单词里夹回车
cout << "Behold the Beans
of Beauty!"; // 非法:字符串里夹回车代码中不可分割的最小元素叫记号(token),记号之间一般要用空白(空格、制表符、回车)隔开;括号、逗号等单字符记号则不需要:

▲ 图 2.3 记号与空白
return0; // 非法,必须写 return 0;
return(0); // 合法,省略了空白
intmain(); // 非法,int 和 main 之间没空白
int main ( ) // 合法惯例风格:一行一条语句;函数的左右花括号各占一行;函数体内的语句相对花括号缩进;函数名括号周围不放空白。
2.6 变量与赋值
程序清单 2.2 carrots.cpp:
// carrots.cpp -- food processing program
// uses and displays a variable
#include <iostream>
int main()
{
using namespace std;
int carrots; // declare an integer variable
carrots = 25; // assign a value to the variable
cout << "I have ";
cout << carrots; // display the value of the variable
cout << " carrots.";
cout << endl;
carrots = carrots - 1; // modify the variable
cout << "Crunch, crunch. Now I have " << carrots << " carrots." << endl;
return 0;
}运行结果:
I have 25 carrots.
Crunch, crunch. Now I have 24 carrots.声明语句:int carrots; 一句话干了两件事——指明需要能装一个整数的内存,并给这块内存贴上标签 carrots。为什么必须先声明变量?声明让编译器帮你抓拼写错误。BASIC 这类语言用新名字就自动创建变量,短看友好,但把 CastleDark 误打成 CastleDank,程序不报错却悄悄用错了变量,这种 bug 极难排查。C++ 里没声明的名字直接编译失败,错误当场现形。另外 C++ 不要求声明全堆在函数开头,惯例是在离首次使用最近的地方声明。

▲ 图 2.4 变量声明
赋值语句:carrots = 25; 把 25 放进 carrots 代表的内存位置。= 是赋值运算符,可以连环用,从右向左依次生效:
yamaha = baldwin = steinway = 88; // 三个都变成 88carrots = carrots - 1; 也展示了变量可以改值:右边的算术表达式先算(25−1=24),再存回 carrots。
cout 的新把戏:cout << carrots; 打印的不是字母 c-a-r-r-o-t-s,而是变量里存的数 25。这里面有两步:把 carrots 替换成当前数值,再把数值翻译成输出字符。别忘了整数 25 和字符串 “25” 完全是两码事——后者存的是字符 ‘2’ 和 ‘5’ 的编码。对比 C 的 printf():
printf("Printing a string: %s\n", "25");
printf("Printing an integer: %d\n", 25);printf() 靠 %s、%d 这些代码指定类型,用错了它也不吭声,直接输出乱码。cout 则自动识别类型,聪明又防呆——这正是 OOP 特性带来的好处,<< 会根据后面数据的类型调整行为。
2.7 用 cin 输入
程序清单 2.3 getinfo.cpp:
// getinfo.cpp -- input and output
#include <iostream>
int main()
{
using namespace std;
int carrots;
cout << "How many carrots do you have?" << endl;
cin >> carrots; // C++ input
cout << "Here are two more.";
carrots = carrots + 2;
// the next line concatenates output
cout << "Now you have " << carrots << " carrots." << endl;
return 0;
}运行示例:
How many carrots do you have?
12
Here are two more. Now you have 14 carrots.cin >> carrots; 把键盘输入的字符流”提取”出来变成变量的值。cin 和 cout 是流的两端:输出是流出程序的字符流,用 << 插入;输入是流进程序的字符流,用 >> 提取——符号形状直观暗示了信息流向。cin 和 cout 一样聪明:你敲的是字符 1、2,cin 会自动把它们转成整数 12 存进 carrots。
cout 拼接:<< 可以串成一长条,一行太长还能分行写:
cout << "Now you have " << carrots << " carrots." << endl;注意”Here are two more.”和”Now you have 14 carrots.”出现在同一行——前一条 cout 输出的结尾就是后一条的起点,中间隔着别的语句也一样。
顺便认识类:cin 是 istream 类的对象,cout 是 ostream 类的对象,两个类的定义都在 iostream 文件里。类之于对象,就像类型之于变量:类是一份数据形式及其可用操作的说明书,对象是按说明书造出来的实体。用”名演员”这个类别打比方:类是”名演员”这个概念(含”读台词""表达悲伤""领奖”等操作),Kermit the Frog 则是具体的一个对象。对对象执行允许的操作 = 给对象发消息,比如用重定义的 << 向 cout 发”把这个显示出来”的消息:

▲ 图 2.5 向对象发送消息
2.8 函数初步
C++ 函数分两种:有返回值的和没有的。
库函数 sqrt():要算 6.25 的平方根并存进 x:
x = sqrt(6.25); // 返回值 2.5,赋给 xsqrt(6.25) 叫函数调用;括号里的 6.25 是传递给函数的参数(argument);函数算完送回来的 2.5 是返回值。

▲ 图 2.6 调用函数

▲ 图 2.7 函数调用语法
函数原型(prototype):编译器用函数前必须先知道它的参数和返回值是什么类型。原型就是干这个的,就像变量声明之于变量:
double sqrt(double); // 函数原型:收一个 double,返回一个 double提供原型有两种办法:自己手敲,或者包含 cmath 头文件——后者更好,因为头文件比你更不容易抄错。库中每个函数的原型都在某个头文件里,查文档确认该包含哪个。别混淆原型和定义:原型只描述接口(进什么、出什么),定义才包含干活的真实代码。
程序清单 2.4 sqrt.cpp:
// sqrt.cpp -- using the sqrt() function
#include <iostream>
#include <cmath> // or math.h
int main()
{
using namespace std;
double area;
cout << "Enter the floor area, in square feet, of your home: ";
cin >> area;
double side;
side = sqrt(area);
cout << "That's the equivalent of a square " << side
<< " feet to the side." << endl;
cout << "How fascinating!" << endl;
return 0;
}运行示例:
Enter the floor area, in square feet, of your home: 1536
That's the equivalent of a square 39.1918 feet to the side.
How fascinating!顺带三个知识点:声明格式统一为 类型名 变量名;;double 能存带小数的值,范围远大于 int;初始化(声明同时赋值)可以合并一步:double side = sqrt(area);。如果链接时报 _sqrt 是”未定义的外部符号”,说明编译器没有自动搜索数学库,Unix 下在命令行末尾加 -lm 选项即可。
函数的变体:pow() 要两个参数(double pow(double, double);,调用如 pow(5.0, 8.0) 求 5 的 8 次方);rand() 不要参数(int rand(void););有的函数没有返回值,返回类型写 void(如 void bucks(double);),这种函数只能当独立语句调用,不能塞进表达式里。
自定义函数:程序清单 2.5 ourfunc.cpp:
// ourfunc.cpp -- defining your own function
#include <iostream>
void simon(int); // function prototype for simon()
int main()
{
using namespace std;
simon(3); // call the simon() function
cout << "Pick an integer: ";
int count;
cin >> count;
simon(count); // call it again
cout << "Done!" << endl;
return 0;
}
void simon(int n) // define the simon() function
{
using namespace std;
cout << "Simon says touch your toes " << n << " times." << endl;
} // void functions don't need return statements运行示例:
Simon says touch your toes 3 times.
Pick an integer: 512
Simon says touch your toes 512 times.
Done!函数定义的通用形式是 类型 函数名(参数列表) { 语句 }。C++ 不允许函数定义嵌套——每个定义独立并列,所有函数一律平等:

▲ 图 2.8 函数定义在文件中依次排列
simon() 的头 void simon(int n) 表示:不返回值、收一个 int 参数。调用时 simon(3) 把 3 赋给函数体内的变量 n。因为是 void 函数,不能写 simple = simon(3);。而 main() 的头 int main() 表示要返回一个 int——返回给谁?给操作系统。很多系统会拿这个退出状态做文章:约定 0 表示程序成功,非零表示出了问题,shell 脚本就能据此决定下一步动作。
带返回值的自定义函数:程序清单 2.6 convert.cpp(1 英石 = 14 磅):
// convert.cpp -- converts stone to pounds
#include <iostream>
int stonetolb(int); // function prototype
int main()
{
using namespace std;
int stone;
cout << "Enter the weight in stone: ";
cin >> stone;
int pounds = stonetolb(stone);
cout << stone << " stone = ";
cout << pounds << " pounds." << endl;
return 0;
}
int stonetolb(int sts)
{
return 14 * sts;
}运行示例:
Enter the weight in stone: 15
15 stone = 210 pounds.return 14 * sts; 说明 return 后面可以跟表达式,不必非得先存进变量。嫌难读也可以走长路线:
int stonetolb(int sts)
{
int pounds = 14 * sts;
return pounds;
}两版结果一样,后者把计算和返回分开,更易读易改。有返回值的函数可以出现在任何同类型常量能出现的地方:
int aunt = stonetolb(20);
int aunts = aunt + stonetolb(10);
cout << "Ferdie weighs " << stonetolb(16) << " pounds." << endl;函数原型描绘的是”黑盒”视角:参数列表是流入,函数类型是流出:

▲ 图 2.9 函数原型与作为黑盒的函数
stonetolb() 虽短,却五脏俱全:有头有体、收参数、返值、要原型——把它当作函数设计的标准模板。
关键词:int、void、return、double 是 C++ 的关键词(keyword),语言专用,不能拿来当变量或函数名,但可以作名字的一部分(如 painter 里藏着 int)。注意 main 不是关键词——它是必需函数的名字。把 cout 同时用作对象名和变量名会迷惑编译器,同一个函数里别这么干。
2.9 using 指令的位置与命名习惯
多函数程序里,using namespace std; 放哪里?清单 2.5 在两个函数里各放了一条——每个用到 cout 的函数都需要访问 std。也可以放在所有函数定义的外面、上面,让整个文件的所有函数共享。主流理念是按需开放:只有 main() 用 cout 就只放 main() 里。汇总一下你的选择:
- 文件级
using namespace std;——std 的所有名字对文件内每个函数开放; - 函数级
using namespace std;——只对该函数开放; - 函数级
using std::cout;——只开放指定名字; - 全不写,用时带前缀:
std::cout << "..." << std::endl;。
命名习惯:函数、类、变量怎么起名,纯看团队口味——myFunction()、my_function()、myfunct() 都合法。一致、精确的个人命名风格是良好软件工程的标志,值得从现在就养成。
常见坑:分号与大小写
C++ 的分号是语句的一部分,漏写必报错;而 cout 写成 Cout、COUT 也必报错——C++ 严格区分大小写和拼写。新手期一半以上的编译错误来自这两处,看到 unknown identifier(未知标识符)先检查拼写和大小写。
常见坑:void main() 和忘写 endl
void main()在某些系统上能编译通过,但它不在 C++ 标准里,换台机器就挂——请写int main()。另一个坑:cout 输出不自动换行,忘加 endl(或\n)时输出会全部挤在同一行,且两条输出之间哪怕隔着其他语句也照样接着上一条尾巴写。
通关标准:
你能默写一个完整的最小程序(#include、int main()、return 0)并解释每一行的作用;能说出
cout <<与cin >>各自的信息流向;能自定义一个带参数、带返回值的函数并在 main() 里调用它(原型 + 定义一个不少)。
自测:
using namespace std;到底做了什么?有哪三种替代方案?它让 std 命名空间里的所有名字(cout、endl 等)不必加 std:: 前缀即可使用。替代方案:①只开放个别名字的 using 声明(
using std::cout;);②每次都写全名std::cout;③旧程序直接用 iostream.h 头文件(旧式写法,不推荐新代码使用)。
自测:函数原型和函数定义有什么区别?为什么建议用 cmath 头文件而不是手写 sqrt 的原型?
原型只描述接口——收什么类型的参数、返回什么类型,以分号结尾;定义包含函数实际干活的代码。头文件里的原型由编译器厂商提供,比你自己手抄更不容易出错,库中每个函数的原型都能在对应头文件里找到。
自测:为什么说 cout 比 printf() 聪明?
cout 的
<<运算符能自动识别后续数据的类型并完成数值到字符的转换——打印整数变量就显示数值;printf() 必须靠 %s、%d 等代码手工指定类型,指定错了它照常输出乱码。cout 还可扩展:重定义<<就能让它显示你自定义的新类型。
自测:
int main()的返回值交给了谁?0 和非 0 分别表示什么?交给操作系统(实际是编译器加入的启动代码负责中转)。约定俗成:0 表示程序成功结束,非 0 值表示出了问题——shell 脚本和批处理文件可以读取这个退出值来决定后续动作。
自测:下面两段代码合法吗?
int ma in();return(0);第一段非法——名字 ma in 中间插了空白,记号被拆散了;第二段合法——return 和 (0) 是两个独立记号,它们之间可以省略空白(括号属于单字符记号,不需要空白分隔)。