第一章 顺序结构程序设计(3) 文章目录1.4 常量1.5 注释1.6 顺序结构应用本节习题1.7 文件操作1.4 常量在前面的例题中我们使用了 13、0.5、1.0 等数据直接参与算术运算像这种用真实数据直接表示的量称为常量。13 是整型常量0.5 和 1.0 是浮点型常量。由于整型和浮点型有多种不同范围的类型因此 C 规定了常量的默认类型整型常量默认是int类型浮点型常量默认是double类型。其它类型的常量可以通过添加后缀实现例如3LL和3ll表示long long型常量3.14F和3.14f表示float型常量。【小贴士】浮点型常量的表示方式是很灵活的除了上面的表示方式还可以省略整数部分或者小数部分例如0.5可以写作.51.0可以写作1.。还有一种表示方式称为科学计数法例如0.0314E2和314.15e-2分别表示0.0314 × 10 2 0.0314×10^20.0314×102和314.15 × 10 − 2 314.15×10^{-2}314.15×10−2。字符型常量比较特殊需要用单引号包裹起来例如A、h、7、、*、 空格。另外有一些特殊字符无法直接表示C 语言中规定了一些转义字符进行表达。所谓转义字符是以反斜杠\开头的字符表示将其之后字符的原本含义进行转换例如\n换行、\0空字符、\\反斜杠。由于计算机只能识别二进制信息因此字符数据必须转换为二进制信息转换过程称为编码。char类型采用的编码规则是 ASCII 码美国信息交换标准代码规定了常用的 128 个字符的编码包括大小写字母、数字、标点符号以及常用的控制字符。例如字符A的 ASCII 码为 65计算机会将 65 转换为二进制并存储起来其余大写字母的 ASCII 码按照字母表顺序依次增加a为 970为 48剩余小写字母与数字也是按顺序递增。上面所说的常量称为字面值常量简称字面量在实际使用时并不方便尤其是大量使用同一字面量时不论是代码的可读性还是可维护性都会大打折扣。为了解决这个问题我们引入符号常量。【例 1. D1039 [OpenJudge] 与圆相关的计算】给出圆的半径求圆的直径、周长和面积。输出时数与数之间以一个空格分开每个数保留小数点后 4 位约定π 3.14159 π3.14159π3.14159。这里会多次使用π ππ这个常量如果将其声明为符号常量后续修改起来会方便许多。虽然使用变量来存储也能达到相同的效果但是如果不小心在程序中修改了π ππ的值后续计算的结果都将出错。符号常量的意义在于当你尝试在程序中修改其值时编译器会拒绝编译并给出编译错误的提示信息即符号常量的值不允许在程序中修改。这也意味着符号常量在声明的同时必须给定初始值。下面两种方式都是符合 C 语法规则的。constdoublePI(3.14159);doubleconstPI3.14159;其中const表明这是一个符号常量的声明可以出现在类型标识符之前或之后。第一种设定初始值的方式专门用于变量或符号常量的初始化中第二种方式中称为赋值运算符表示将其右边表达式的值赋给左边的变量或符号常量可以用在任何允许赋值的地方。为了便于在程序中区分符号常量和变量通常会用大写字母表示符号常量。C 语言可以通过宏定义的方式来实现类似的效果代码格式如下会在编译之前的预处理过程中用 3.14159 替换掉所有的标识符PI。#definePI3.14159不论哪种风格的常量一般都会将其定义在main函数上面详见代码。#includeiostream#includecstdiousingnamespacestd;constdoublePI3.14159;intmain(){doubler;cinr;printf(%.4f %.4f %.4f\n,2*r,2*PI*r,PI*r*r);return0;}由于需要输出多个结果因此printf使用了多个格式控制符。前面说到格式控制符的作用之一是占位除了格式控制符会被替换掉其余信息会原样输出于是可以在相邻两个控制符之间添加 1 个空格来满足输出格式要求。最后的\n表示换行printf只能用\n来换行。除此之外格式控制符和变量必须在数量、顺序上保持一致。1.5 注释为了进一步增加代码的可读性和可维护性可以通过注释对代码做更详细的说明比如在源代码开头添加作者、程序功能等信息亦或是对程序中较为复杂的部分进行注解说明。而编译器会对这些注释“视而不见”将它们统一当作空格来处理。/* Author: Mr. Gao * Date: 2025-12-17 * Function: 根据圆的半径计算圆的直径、周长、面积 */#includeiostream#includecstdiousingnamespacestd;// 圆周率constdoublePI3.14159;intmain(){doubler;// 半径为浮点型数据cinr;printf(%.4f %.4f %.4f\n,2*r,2*PI*r,PI*r*r);return0;}上面的程序开头部分使用/*和*/包裹起来的部分称为多行注释这两个符号之间的所有内容全都会被编译器认为是注释中间 2 行开头的*是为了美观。注意多行注释不能嵌套书写。使用//开头的部分称为单行注释它会将其后直到行末的内容都设置成注释。1.6 顺序结构应用至此我们已经学会了算法竞赛中可能用到的绝大部分顺序结构知识和技巧接下来通过解决几个稍微复杂的问题让我们的编程能力和解决问题能力更进一步。【例 1. D1091 [21 年 9 月一级] 交换输出】输入两个整数a aa、b bb将它们交换输出0 a 10 8 0a10^80a1080 b 10 16 0b10^{16}0b1016。解决这个问题是比较容易的不过我们要学习的是如何在程序中交换两个变量的值而不是通过调整输出顺序来完成因为这样的问题可能只是之后更加复杂问题中的一个步骤。我们可以通过赋值运算来修改一个变量的值同时该变量中原来的值会丢失。因此如果我们写出如下代码是不能交换变量a和b的值的只会让它们的值相同。inta3,b11;ab;// 执行结束后a 的值为 11b 的值为 11ba;// 执行结束后a 的值为 11b 的值为 11不难想到用一个临时变量先将a的值存储起来然后将b的值赋给a最后将临时变量中的值赋给b。这样一来就避免了在把变量b的值赋给a之后a的值被覆盖的情况。于是解决这个问题的关键代码如下。longlongta;ab;bt;我们还可以利用加减法运算结合顺序结构的特性来解决这个问题。// 假设输入的 a 和 b 分别是 3 和 11aab;// 执行结束后a 的值为 14b 的值为 11ba-b;// 执行结束后a 的值为 14b 的值为 3aa-b;// 执行结束后a 的值为 11b 的值为 3值得一提的是上述代码中的表达式a a b也可以写作a b。事实上C 允许任意符合a a ()的表达式缩写为a ()其它算术运算也有类似的缩写规则我们将类似于、-这样的运算符称为复合赋值运算符。【例 2. P2799 [ABC222A] Four Digits】给出一个介于 0 和 9包括 0 和 9之间的整数n nn。请将其打印为四位数字字符串如果需要的话在其前面添加必要数量的前导零。该问题可以通过格式控制来解决。首先需要将输出宽度设置为 4由于输入数据最多 4 位于是将场宽设置为 4 即可。所谓场宽是指输出数据的最小宽度。如果数据宽度大于场宽则会按照实际宽度输出否则会将数据右对齐输出。C 中设置场宽的工具是setw。然后需要设置右对齐之后的填充字符默认在左侧填充空格可以通过工具setfill来修改填充字符。关键代码如下注意包含头文件iomanip。coutsetw(4)setfill(0)n;C 语言中的printf工具也可以应对用0填充的情况也只能应对用0填充的情况。格式控制符%d用于输出int型整数%4d表明场宽为 4%04d表明用0填充。printf(%04d,n);如果需要输出long long型数据格式控制符为%lld。如果要设置浮点数的场宽可以使用类似%10.2f的格式控制符其中10表示场宽为 10.2表示保留 2 位小数。【例 3. G1181 [GESP2503 一级] 图书馆里的老鼠】图书馆里有n nn本书不幸的是还混入了一只老鼠老鼠每x xx小时能啃光一本书假设老鼠在啃光一本书之前不会啃另一本。请问y yy小时后图书馆里还剩下多少本完整的书。保证y yy小时后至少会剩下一本完整的书。y yy小时后老鼠应该正在啃第⌈ y / x ⌉ ⌈y/x⌉⌈y/x⌉本书符号⌈ t ⌉ ⌈t⌉⌈t⌉的含义是向上取整表示不小于t tt的最小整数。当y yy和x xx均为正数时C 表达式y / x y / xy/x的值是⌊ y / x ⌋ ⌊ y/x ⌋⌊y/x⌋即向下取整不大于t tt的最大整数。想要求出⌈ y / x ⌉ ⌈y/x⌉⌈y/x⌉的值可以使用表达式( y x − 1 ) / x (yx-1)/x(yx−1)/x于是剩下的完整书籍数量就是n − ( y x − 1 ) / x n-(yx-1)/xn−(yx−1)/x。【例 4. P2800 [ABC235A] Rotate】让x y z xyzxyz表示一个 3 位整数其中从左到右的数字分别是x xx、y yy、z zz。给定一个 3 位整数a b c abcabc其中没有任何一位数字是 0求a b c b c a c a b abcbcacababcbcacab。我们可以利用 C 中的取余运算和整除特性将整数n nn中的任一数位分离出来遵循的规则是用n nn除以对应的位权再对 10 取余。an/100%10;// 百位bn/10%10;// 十位cn/1%10;// 个位除以 1 可以省略观察题目中的表达式每一个数码在个位、十位、百位各出现了一次于是该表达式等价于a a a b b b c c c aaabbbcccaaabbbccc提取公因式可得111 × ( a b c ) 111×(abc)111×(abc)。【例 5. G1061 [GESP2312 一级] 小杨的考试】今天是星期x xx小杨还有n nn天就要考试了你能推算出小杨考试那天是星期几吗本题中使用 7 表示星期日此题可以使用取余运算来解决。表达式( x n ) % 7 (x n) \% 7(xn)%7的取值范围是0 ∼ 6 0\sim 60∼6当值为 0 时应当输出 7。我们可以将表达式修正为( x n ) % 7 1 (x n) \% 7 1(xn)%71以确保计算结果在1 ∼ 7 1\sim 71∼7之间。然而这样的修正会使得计算结果多 1可以在取余之前先减 1 来进一步修正于是最终表达式为( x n − 1 ) % 7 1 (x n - 1) \% 7 1(xn−1)%71。【例 6. P2829 [ABC305A] Water Station】有一条全长为 100 km 的超级马拉松赛道。沿着赛道每 5 km 设置一个水站包括起点和终点总共 21 个水站。高桥位于赛道的n nnkm 处。找到离他最近的水站的位置。在问题的限制条件下可以证明最近的水站是唯一确定的。可以利用表达式n / 5.0计算出距离高桥最近的水站具体来说如果n / 5.0的值小于x.5那么距离高桥最近的水站编号就是x否则就是x1。于是可以进一步利用四舍五入与强制类型转换求出答案最终 C 表达式为int(n / 5.0 0.5) * 5。【例 7. P6035 时间的差】给定两个标准格式的时间h:m:s即 “时:分:秒”你的任务是计算这两个时间之间相差多少秒。本题保证第一个时间一定大于第二个时间。这道题的输入数据并不是用空格隔开的整数而是用冒号隔开。直接使用cin h m s;会导致程序一直卡在输入阶段这是因为cin输入整数时会在非数字字符或者超出存储范围时停止将目前已经读取到的数据存入变量。也就是说用整型变量h接受第一个整数时会在第一个冒号处停止下一个整型变量m会因为前面的冒号而无法读取因此必须要处理掉冒号。由于冒号是一个字符我们可以声明一个char型变量将冒号接收掉这样后面的m就可以正常读取了。inth,m,s;charc;cinhcmcs;在 C 语言中我们可以使用scanf这个工具更灵活的处理这样的输入。与printf类似在scanf中出现的非格式控制符需要原样输入即scanf(%d:%d:%d, h, m, s);。注意每个变量前面都有一个符号在这里叫做取地址符含义是获取变量的地址。【小贴士】什么是地址前面我们学到每个变量都是内存中的一段空间不同的变量会占用不同的字节空间。为了便于操作系统识别内存中的每个字节都有一个唯一固定不变的编号这就是地址。声明变量可以当作是将内存编号与变量名进行绑定便于我们在程序中使用这段空间。注意int型有连续 4 个字节就有 4 个地址取地址符取出的是第一个字节的地址称为首地址。上面scanf的写法不够灵活如果分隔的字符不是冒号那么输入同样会出问题。更灵活的写法还是将冒号输入到一个char型变量中scanf和printf处理char型字符的格式控制符是%c于是可以使用scanf(%d%c%d%c%d, h, c, m, c, s);。事实上变量c的唯一作用就是消除输入中的无用字符C 语言考虑到了这种情况于是提供了一种不需要变量也能消除无用字符的格式控制规则%*c我们是不需要定义变量来接收%*c读取到的信息的即更灵活的写法是scanf(%d%*c%d%*c%d, h, m , s);。【小贴士】scanf的大部分格式控制符与printf是一致的在一些细节用法上不同例如printf需要考虑场宽和小数位数而scanf需要考虑无效字符的消除。scanf在浮点数的输入上是严格区分的%f用于读取float型数据%lf用于读取double型数据而printf没有区分。此外%c是可以读取空白字符的因此在明确不需要读取空白字符的情况下可以在%c前面添加一个空格即scanf( %c, c);作用是过滤掉第一个可见字符前面的所有空白符这是一个好习惯。本节习题D1260 [23 年 9 月一级] 日期输出D1167 [22 年 6 月一级] 平方差计算D1142 [22 年 3 月一级] 足球联赛积分P2793 [ABC254A] Last Two DigitsP2801 [ABC258A] When?P2204 [ABC105A] AtCoder CrackersP6036 简单 a * b1.7 文件操作前面我们所有程序的输入输出都是在控制台进行的下面我们学习一下如何从文件中进行输入输出。尽管这对我们的算法思维并没有提升但是许多竞赛要求从文件中进行输入输出比如 CSP-J/S 第二轮、NOIP、USACO因此文件操作成了算法竞赛的必备技能。要知道一个文件的名字是包含后缀名的所谓后缀名是用于区分文件类型的标志信息。在大多数算法竞赛中输入文件的后缀名统一为.in输出文件为.out。在 C 中是通过文件流对象来操作文件的比如我们想从文件test.in中读取两个整数并将它们的和输出到test.out这个文件中就需要用到下面的代码。ifstreamfin(test.in);// 声明输入流对象 fin并绑定文件 test.inofstreamfout(test.out);// 声明输出流对象 fout并绑定文件 test.outinta,b;finab;// 使用输入流对象 fin 从文件 test.in 输入foutabendl;// 使用输出流对象 fout 将结果输出到文件 tets.outfin.close();// 关闭输入流对象fout.close();// 关闭输出流对象ifstream和ofstream分别是输入和输出文件流这两个工具声明在头文件fstream中。fin和fout相当于是变量名后面的双引号中应该写的是文件路径这里我们使用的是相对路径默认这两个文件和源代码文件在同一个目录之中。使用文件流的好处是cin和cout仍然会在控制台进行坏处是在算法竞赛中实用性不高因为调试很麻烦。下面介绍 C 语言中的文件重定向操作在算法竞赛中相当实用这个工具与printf和scanf一样声明在头文件cstdio中。freopen(test.in,r,stdin);// 将输入重定向到test.in方式为只读freopen(test.out,w,stdout);// 将输出重定向到test.out方式为覆写inta,b;cinab;// 输入方式仍然使用标准输入coutabendl;fclose(stdin);// 关闭重定向fclose(stdout);上述文件重定向的操作在调试时只需要将上面两行和下面两行注释掉即可非常方便。