一、基础:程序结构与输出
这两章一共 9 道题,全部不需要读入任何数据。程序自己算,自己输出。
这是故意安排的。初学者同时卡两件事的时候,会分不清是谁的错——程序没输出,到底是 printf 的格式串写错了,还是 scanf 没读到?往往调半小时才发现是输入格式不对。这两章把输入拿掉,只留输出,错了就只有一种可能。
把这一页当成热身,但它不是走过场。 你得在这里把三件事变成肌肉记忆:代码骨架怎么敲、输出怎么控制、算术怎么写。第 3 章开始读输入,这三件事就不会再有人提醒你。
1. 你的第一个程序
你要解决的问题只有一个:让屏幕上出现你指定的字。
1.1 四行骨架
所有 C++ 程序都长这个样子。以后你写到几千行,外面这层壳也不变:
#include <bits/stdc++.h>
using namespace std;
int main() {
printf("Hello, World!\n");
return 0;
}
很多人第一天就把这四行背下来了,然后一直照着敲,从没想过每行在干什么。我们花三分钟拆开——因为你迟早会遇到它们出问题的时候,那时候不知道哪行在干什么,就只能干瞪眼。
#include <bits/stdc++.h>
#include 的意思是「把另一个文件的内容原样抄到这里」。bits/stdc++.h 是个特殊文件,它把所有标准库一次性全抄进来。
正经的 C++ 教程会教你 #include <iostream>、#include <cmath> 一个个写。竞赛里不这么干——记不住哪个函数在哪个头文件里,多抄一点进来,编译器多干活,你少查一次文档。
这行不是 C++ 标准
bits/stdc++.h 是 GCC 编译器自带的,不在 C++ 标准里,Windows 的 MSVC 上编译不过。
但你不会用 MSVC。OJ 用 GCC,Dev-C++ 用 GCC,你现在能碰到的所有环境都是 GCC。放心用。
using namespace std;
标准库的东西都放在一个叫 std 的命名空间里。不写这一行,你每次都得写 std::printf、std::scanf、std::string。
代价是:如果哪天你自己定义了一个叫 printf 的变量,会和标准库撞名。初学阶段不会撞,写就是了。
int main()
程序从 main 开始执行,花括号里的代码从上到下一条条跑。int 表示这个函数最后要交回一个整数——就是下面的 return 0;。
return 0;
告诉操作系统「程序正常结束」。
严格说现代 C++ 里 main 的 return 0 可以省略,编译器会自动补上。但别省:一是老编译器会警告,二是「函数有返回值就写出来」这个习惯,第 27 章讲函数时你会知道有多重要。
1.2 让它说话:printf
printf 读作 "print-f",意思是「按格式打印」。括号里用一对双引号包住要输出的内容:
printf("Hello, World!\n");
双引号里的这部分叫格式串。格式串里的字符会原样送到屏幕上,只有一种情况例外——反斜杠开头的东西。
\n 是最常用的一个,表示换行:
printf("第一行\n第二行\n");
屏幕上是两行。\n 写在引号里面,看起来是两个字符,编译器会把它们当成一个换行符。
常用的反斜杠写法就下面这几个:
| 写法 | 意思 | 什么时候用 |
|---|---|---|
\n |
换行 | 基本每一行输出都要 |
\t |
制表符,约 8 个空格宽 | 输出要按列对齐时(第 12 章九九乘法表) |
\\ |
一个反斜杠本身 | 很少用 |
\" |
一个双引号本身 | 要输出引号时 |
C++ 还有一种写法叫 cout
你会看到别人写 cout << "Hello" << endl;,效果和 printf("Hello\n"); 一样。
这本讲义统一用 printf,不用 cout。 为什么?下一章讲输入的时候会说明白——输入输出是配套的,混着用只会给自己找麻烦。现在你只要知道:看见别人的 cout 别慌,它是同一个意思。
第一个程序
1.3 输出必须一字不差
接下来四道题的输出要求各不相同——有的要多一行,有的要多个空格,有的要改标点。你必须逐字读题目的输出要求。
这不是出题人为难你。OJ 判断对错的方式是:把你的输出和标准答案逐字符比对。多一个空格、少一个换行、逗号用了全角,都算错。
举个最容易踩的例子:
printf("Hello,World\n"); // 错:逗号是全角
printf("Hello, World\n"); // 对
这两个逗号长得几乎一样,但全角的 , 占两个字节,半角的 , 占一个。编译器不认全角标点,而且报错信息往往指向别的地方,让你找半天。
交之前先把输入法切成英文
新手第一个编译错误里,一半是全角标点。写代码时输入法保持英文,只在写注释和字符串内容时才切中文。
肉眼判断方法:全角逗号 , 比半角逗号 , 明显宽一圈,而且后面会自带半个字的间距。
一模一样的输出
四道题,四个不同的输出要求。每做一题,先读题 → 再写 → 再交,不要凭感觉一口气写完。
如果某一题反复错又不知道错在哪:把格式串末尾的 \n 临时改成 |\n,跑一次。那个竖线会告诉你「你的输出到底到哪儿结束」——是多了一个空格,还是少了一个换行。
第 1 章验收
做完这 5 题,你应该能做到:
- 不看任何资料,默写出四行骨架
- 说清楚
#include、using namespace、main、return 0各自的作用 - 一眼看出自己的逗号是全角还是半角
做不到就回去再看一遍 1.1,别往下走。
2. 让程序自己算
第 1 章你只能输出写死的字。这一章开始,让程序算出结果再输出。
2.1 %d:在格式串里留个坑
第 1 章的格式串里全是固定的字。现在要在中间塞一个「算出来的数」,得先留个坑:
printf("%d\n", 3 + 5);
屏幕上是 8。
%d 是占位符,意思是「这里要放一个整数」。逗号后面那个 3 + 5 会被算出来,填进坑里。
这个顺序不能反,也不能漏:
printf("%d\n", 3 + 5); // 8 对
printf(3 + 5, "%d\n"); // 错 参数顺序反了
printf("3 + 5\n"); // 3 + 5 输出了这几个字符本身,没有算
%d 的 d 是 decimal(十进制)。以后还会遇到 %f(小数)、%c(字符)、%s(字符串)——它们都是坑,区别只是往坑里放什么类型的东西。类型不放对,输出就是一串乱码。
多个数就多个坑,按顺序填:
printf("%d %d\n", 3, 5); // 3 5
printf("%d+%d=%d\n", 3, 5, 8); // 3+5=8
坑的个数必须和后面的参数个数对上。 少一个,printf 会去读一个不存在的位置,输出随机值;多一个,多的被忽略。这两种情况编译器都不报错,只能你自己数。
printf 最大的特点:它不检查你
printf 是 C 语言留下来的老函数,编译器只知道「第一个参数是个字符串」,不知道后面该有几个参数、该是什么类型。
所以 printf("%d\n") 少写参数、printf("%f\n", 3) 类型不对,都能编译通过,然后在运行时给你一个莫名其妙的结果。
以后遇到「输出是乱码 / 是随机数 / 是 0」,第一个怀疑对象就是格式串和参数对不上。
2.2 五个算术运算符
| 运算符 | 含义 | 例子 | 结果 |
|---|---|---|---|
+ |
加 | 3 + 5 |
8 |
- |
减 | 3 - 5 |
-2 |
* |
乘 | 3 * 5 |
15 |
/ |
除 | 7 / 2 |
3,不是 3.5 |
% |
取余(余数) | 7 % 2 |
1 |
* 和 % 是初学者最容易写错的两个:数学里写 ×,代码里必须写 *;% 在数学里是百分号,在这里是「除完剩多少」。
2.3 整数除法会吃掉小数
7 / 2 的结果是 3,不是 3.5。这条规则会让很多人第一次栽跟头。
原因是:7 和 2 都是整数(int),C++ 规定整数除整数,结果还是整数,小数部分直接扔掉。不是四舍五入,是直接砍掉。
printf("%d\n", 7 / 2); // 3
printf("%d\n", 1 / 2); // 0 ← 不是 0.5,是 0
printf("%d\n", 7 % 2); // 1 ← 余数
想要小数结果,至少让一边变成小数。这时候不能再用 %d 了,得换成 %f:
printf("%f\n", 7.0 / 2); // 3.500000
printf("%.2f\n", 7.0 / 2); // 3.50
%f 是给小数留的坑。它默认输出 6 位小数,%.2f 表示保留 2 位——这个写法第 5 章专门讲,现在知道有这么回事就行。
7.0 是浮点数,浮点数除以整数,结果就是浮点数。现在先记住一句话:看到除法,先问自己「我要不要小数」。
还有一条更隐蔽的:% 只能用在整数上。7.5 % 2 编译不过。
2.4 优先级和括号
C++ 的运算顺序跟数学一样:先乘除取余,后加减。2 + 3 * 4 是 14。
拿不准就加括号。括号不要钱,猜错要命:
printf("%d\n", (2 + 3) * 4); // 20
printf("%d\n", 2 + 3 * 4); // 14
写表达式时,如果你自己都要想两秒钟才能确定顺序,那就加括号。三个月后回来看这段代码的人是你自己。
纯计算的输出
第 2 章验收
- 能说清
7 / 2和7.0 / 2为什么不一样 - 知道
%两侧必须是整数 - 遇到算不准的表达式,第一反应是加括号而不是凭记忆
这一页的 9 道题全部通过,就可以进第 3 章了。第 3 章开始读输入——那才是真正的分水岭。
延伸阅读
服务器上有一整套 OI-Wiki(621 页),是给竞赛选手写的参考手册。下面几页跟这一页相关,但不是每一页都该现在看——标了「现在跳过」的,等以后再说。
- 可以先看:C++ 语法基础 —— 同一个骨架的 printf 版本。比赛里两种写法都能见到,你现在用哪个都行
- 可以先看:Hello, World! —— 第一份代码逐行拆解,还讲了 Dev-C++ 和 MinGW 怎么装
- 可以先看:运算 —— 运算符优先级完整表。拿不准
a + b * c先算谁的时候回来查
看不懂是正常的,不是你的问题
OI-Wiki 默认读者已经会写程序、目标是打 NOI。你现在需要的那部分,讲义里已经写了; wiki 是用来「以后查得更深」的,不是用来补课的。