重要的事情说三遍
本关任务:
程序接受一个输入字符,然后将该字符输出三遍,再输出一个!。
相关知识
本关的“小目标”是帮助大家掌握 C 和 C++ 程序中字符的基本输入输出开发技能。
标准输入输出函数库 stdio.h
C 和 C++ 提供了一个标准输入输出函数库stdio.h
。在程序中包含该函数库后,就可以使用其中的标准函数进行输入输出操作了。在程序中表示如下:
// 包含标准输入输出函数库
#include <stdio.h>
字符的输入输出函数
在 C 语言中,输入输出函数有着相当重要的地位,它是我们的程序与用户交互的唯一途径。
标准输入输出函数库提供的函数总体上可分为:
- 格式化输入输出函数
- 单个字符输入输出函数
- 字符串输入输出函数
下面我们要学习的是单个字符的输入输出函数。
输入函数
字符的输入函数为 getchar
,其函数原型为:
int getchar(void);
函数 getchar
没有参数(后面的括号中为空或者 void),它从标准输入设备(键盘)读入一个字符,并将该字符以整数( int ) 形式返回。
返回的字符要用于输出,所以不能丢弃。我们需要申明一个变量来存储该字符,申明的变量在内存中会有对应的存储空间。使用变量存储输入的字符的语句为:
// 声明变量 x 存储输入的字符
char x = getchar();
输出函数
字符的输出函数为 putchar
,其函数原型为:
int putchar(int c);
函数 putchar
用于向标准输出设备(显示器屏幕)输出一个字符,它的参数 c 是要输出的字符变量或常量。
字符常量是用单引号括起来的一个字符,如’A’。例如:
char x='A';
putchar(x); // 输出变量 x 中存储的字符
putchar('A'); // 输出字符常量'A'
编程要求
补充代码,程序接收一个输入字符,然后将该字符输出三遍并以!结束。
测试说明
测试输入:A
预期输出:AAA!
测试输入:1
预期输出:111!
// 包含标准输入输出函数库
#include <stdio.h>
// 定义main函数
int main()
{
// 请在下面编写将字符输出三遍的程序代码
/********** Begin *********/
char x = getchar();
for(int i=0;i<3;i++)
{
putchar(x);
}
putchar('!');
/********** End **********/
return 0;
}
整数四则运算表达式的输出格式控制
任务描述
本关任务:用户输入两个四位数以内的整数,请你对两个整数进行四则运算(为保证除法能正确计算,输入的第二个整数不能为0),要求输出的四则运算表达式是完整对齐的。
例如,给定输入1256和20,其符合上述对齐要求的四则运算的输出格式应该如下所示(为了让空格显示的更直观,这里暂用下划线_表示空格):
1256 + 20__ = ___1276
1256 - 20 = ___1236
1256 * 20 = __25120
1256 / 20 = _______62
相关知识
程序的输出格式对用户体验影响很大,用户往往因为输出格式不美观便抱怨软件设计的不好。
那么,如何对程序的输出格式进行控制呢?接下来我们介绍一下格式化输入输出,格式化输入输出可以用函数库stdio.h
中的 scanf
和 printf
两个函数来实现。
scanf 函数
scanf
函数按照一定的格式从键盘输入数据。函数形式为:
scanf (<格式控制串> , <参数列表>) ;
- <格式控制串> 是一个字符串,说明了输入数据要遵循的格式;
- <参数列表> 是存放输入数据地址的列表,有多个输入数据时,参数之间用,分隔。如果要把输入的数据写入一个变量,参数列表中需要使用变量的地址。获取变量地址的方法为:
&变量名
,其中&
是地址运算符,其运算结果是后面的变量的地址。
例如从命令行读入一个整数、一个浮点数、一个整数,分别赋值给 num1、num2 和 num3。如下代码:
scanf(″%d%f%d″, &num1, &num2, &num3); //其中 %d 和 %f 均为转换说明符,分别表示读取的数据为整型和浮点型。
如果命令行输入为:12 34.5 678
,则该函数执行的效果是:系统读取整数12并赋值给 num1,读取浮点数34.5并赋值给 num2,读取整数678并赋值给 num3。
scanf
函数常用的转换说明符如下表(省略%):
类型字符 | 含义 |
---|---|
d | 十进制整型量 |
o | 八进制整型量 |
x | 十六进制整型量 |
u | 无符号十进制整型 |
i | 整型 |
f | 实型的小数形式 |
e | 实型的指数形式 |
g | f 和 e 的较短形式 |
c | 字符 |
s | 字符串 |
l 或 h | 放在任何整数转换说明符之前,用于输入 short 或 long 类型数据 |
l 或 L | 放在任何浮点转换说明符之前,用于输入 double 或 long double类型数据 |
printf 的输出格式控制
printf
函数按照指定的格式向屏幕输出数据。函数形式为:
printf (<格式控制串>, <参数列表>) ;
- <格式控制串>
一般是一个字符串,描述输出数据的格式。格式控制串可以包含3种类型的字符:格式指示符、转义字符、普通字符(除格式指示符和转义字符外的其他字符)。其中:- 格式指示符的作用是将参数列表中相应的输出数据转换为指定的格式输出
- 转义字符按照其含义输出相应的特殊符号
- 普通字符则按照原样输出
- <参数列表> 存放输出数据列表,有多个输出数据时,中间用
,
分隔。格式指示符的数量、顺序与输出数据相对应。
printf 函数执行时,根据格式控制串中的格式指示符、转义字符和普通字符等,从左到右依次进行处理:遇到普通字符,则按照原样输出;遇到转义字符,则按照其含义输出相应的符号;遇到格式指示符,则将对应的输出数据按照格式指示符指定的方式输出。
格式指示符的一般形式为:
% <标志> <域宽> <精度> <转换说明符>
其中,<标志>、<域宽>
和<精度>
都是可选项,可以不出现。
标志
printf 函数常用的标志如下表:
标志 | 含义 |
---|---|
- | 输出在域宽内左对齐 |
+ | 在正数值之前显示一个加号,在负数值之前显示一个减号 |
空格 | 在正数值之前显示一个空格 |
# | 与八进制转换说明符 0 一起使用时,在输出值之前加 0,与十六进制转换说明符 x 或 X 一起使用时,在输出值之前加 0x 或 0X |
0 | 用 0 填充域宽 |
注意:多个标志可以联合使用。
域宽
域宽一般为一个整数,指明了数据输出的宽度。
-
如果数据实际长度小于域宽,则数据输出右对齐,即数据仍然按照域宽规定输出,在数据左边用空格补齐;
-
如果数据实际长度大于域宽,那么系统将自动突破域宽的限制,按照数据的实际长度进行输出。
注意,负号要占据一个字符位置。如果没有指明域宽,系统则按照数据的实际长度输出。
精度
精度一般也是一个整数,对于不同类型的数据,精度的含义也不一样。
- 对于整数,精度表示至少要输出的数字个数。
如果数据实际长度小于精度,则左边用0补齐,使得数据长度等于精度;如果数据实际长度大于精度,则自动突破精度限制,按照数据的实际长度输出。省缺情况下整数的精度为1。
- 对于浮点数,有两种情况。
①如果转换说明符为 e、E 和 f,精度表示小数点后的有效位数。如果数据小数部分的实际长度小于精度,则在右边补齐0,使得小数部分长度等于精度;如果数据小数部分的实际长度大于精度,则按照精度对数据进行舍入输出;
②如果浮点数的转换说明符为 g 和 G,精度表示打印数据的最大的长度。
- 对于字符串数据,精度表示字符串输出的最大长度。
如果输出字符串的实际长度小于精度,则按照字符串的实际长度输出;如果字符串的实际长度大于精度,则按照精度截取输出字符串开头的 n 个字符(假设精度为 n )输出。
转换说明符
转换说明符指明了输出数据的类型信息。printf 函数常用的转换说明符如下表:
类型字符 | 含义 |
---|---|
d | 十进制整型量 |
o | 八进制整型量 |
x | 十六进制整型量 |
u | 无符号十进制整型 |
i | 整型 |
f | 实型的小数形式 |
e | 实型的指数形式 |
g | f 和 e 的较短形式 |
c | 字符 |
s | 字符串 |
% | 输出%本身 |
l 或 h | 放在任何整数转换说明符之前,用于输出 short 或 long 类型数据 |
L | 放在任何浮点转换说明符之前,用于输出 long double类型数据 |
例如以域宽为10、右对齐的方式输出15,然后再输出换行符(\n):
printf("%10d\n",15);
说出结果为:________15
编程要求
补充代码,对输入的两个非负整数进行四则运算,使得输出的四则运算表达式是完整对齐的。具体输出格式要求如下:
- 两个整数各占5个字符的位置,如果整数不足5位,则第一个整数在前面用空格补齐,第二个整数在后面用空格补齐,运算符(+、 -、 *、 /、
=都是运算符)占1个字符,且运算符前后各留一个空格位置; - 输出结果占10个字符位置,不足10位时前面用空格补齐。
测试说明
测试输入:9876 123
预期输出:
9876 + 123_ = _____9999
9876 - 123 = _____9753
9876 * 123 = __1214748
9876 / 123 = ________80
测试输入:7777 12
预期输出:
7777 + 12__ = ____7789
7777 - 12 = ____7765
7777 * 12 = ___93324
7777 / 12 = _______648
//包含标准输入输出函数库
#include <stdio.h>
int main()
{
//声明两个整型变量,用于存储输入的两个整数
int x,y;
//请在Begin-End之间添加你的代码,按要求格式输出四则运算式子
/********** Begin *********/
scanf("%d%d",&x,&y);
printf("%5d",x);
printf(" + ");
printf("%-5d",y);
printf(" = ");
printf("%10d\n",x+y);
printf("%5d",x);
printf(" - ");
printf("%-5d",y);
printf(" = ");
printf("%10d\n",x-y);
printf("%5d",x);
printf(" * ");
printf("%-5d",y);
printf(" = ");
printf("%10d\n",x*y);
printf("%5d",x);
printf(" / ");
printf("%-5d",y);
printf(" = ");
printf("%10d\n",x/y);
/********** End **********/
return 0;
}
你好,生日
任务描述
本关任务:当输入一个生日时,如“1992 3 18”,程序输出相应的问好信息,如“Hello! 3 18 1992”。
提示:使用流对象进行输入输出是 C++ 对 C 语言在 I/O 上的扩展,如果只想学习 C 语言,这一关可以跳过。
相关知识
C++ 预定义了一些 I/O 流对象来实现输入输出,这些 I/O 流对象关联相应设备(键盘、显示器等)并实现程序和设备之间的数据传输。
C++ 预定义的 I/O 流对象有4个:cout 、cin 、cerr 和 clog,其中:
-
cout 代表标准输出流,关联显示器;
-
cin 代表标准输入流,关联键盘;
-
cerr 和 clog 代表标准错误流,也关联显示器。
本关我们主要学习标准输入输出流。
标准输入输出流
cin 和流提取运算符>>
一起实现数据输入,cout、cerr 和 clog
和流插入运算符<<
一起实现数据输出。
例如:
int a,b;
cin >> a >> b; // 输入两个整数分别给 a 和 b
cout << a + b << " " << a - b; // 输出 a+b 和 a-b 的值,中间用空格隔开
由于流对象的输入输出功能在 iostream 类库中实现,流对象则在名字空间 std 中定义,所以要使用上述输入输出方法需要包含 iostream 类库和加载名字空间 std 。相应的语句为:
// 包含I/O流库iostream
#include <iostream>
// 加载名字空间std
using namespace std;
不同精度的PI
任务描述
本关任务:输出不同精度的PI(程序给出的PI值为 3.14159265358979323846)。
相关知识
流输入输出也可以进行格式控制, C++ 中是通过流操纵算子来实现的。流操纵算子是在头文件 iomanip
中定义的,因此要使用这些流操纵算子,必须包含该头文件。
// 包含流操作算子库
#include <iomanip>
C++ 的 iomanip 库提供了多种流操纵算子,来实现不同的格式控制功能,包括设置域宽、设置精度、设置和清除格式化标志、设置域填充字符、在输出流中插入空字符、跳过输入流中的空白字符等,下表为一些常用的流操作算子:
流操纵算子 | 功能描述 |
---|---|
setbase(b) | 以进制基数 b 为输出整数值 |
setprecision(n) | 将浮点精度设置为 n |
setiosflags(long) | 设置特定的格式标志位 |
setw(n) | 按照 n 个字符来读或者写 |
setfill(ch) | 用 ch 填充空白字符 |
flush | 刷新 ostream 缓冲区 |
ends | 输出空字符 |
endl | 输出换行符并刷新 ostream 缓冲区 |
ws | 跳过空白字符(用于输入) |
下面本关主要介绍setbase(b)、setprecision(n)、setiosflags(long)和setw(n)算子,剩下的同学们可以自己尝试。
控制进制基数
对于标准输出流 cout 可以使用 setbase 来设置输出整数的进制基数(只支持8、10、16进制),如:
// 以八进制形式输出整数 n
cout << setbase(8) << n << endl;
也可以直接使用流操纵算子 oct(八进制)、hex(十六进制)和 dec(十进制)直接控制输出整数的进制,如:
// 以十六进制输出整数 n
cout << hex << n << endl;
设置浮点数输出精度
流操纵算子 setprecision 或函数 precision 都可以设置浮点数输出的精度,其参数为输出浮点数的有效数字个数(包括整数部分和小数部分,如12.34的有效数字个数为4)。
例如按5位有效位输出12.3 * 3.578的值:
cout << setprecision(5) << 12.3 * 3.578 << endl;
或者:
cout.precision(5);
cout << 12.3 * 3.578 << endl;
以上输出结果均为:44.009
设置辅助格式
流操纵算子 setiosflags
可以辅助设置流输入输出格式,其参数是该流的格式标志值,setiosflags
提供了不同的参数来支持不同的输入输出格式需求。
setiosflags
的格式标志值如下表格:
标志值 | 含义 |
---|---|
ios::skipws | 在输入中跳过空白 |
ios::left | 左对齐,用填充字符填充右边。 |
ios::right | 右对齐,用填充字符填充左边(缺省对齐方式)。 |
ios::dec | 以基 10(十进制)格式化数值(缺省进制) |
ios::oct | 以基 8(八进制)格式化数值 |
ios::hex | 以基 16(十六进制)格式化数值 |
ios::showbase | 以 C++ 编译器能读的格式显示数值常量 |
ios::showpoint | 按精度把后面的空白补 0 输出 |
ios::uppercase | 对于十六进制数值显示大写字母 A 到 F,对于科学格式显示大写字母 E。 |
ios::showpos | 对于正数显示正号(+) |
ios::scientific | 以科学格式显示浮点数值 |
ios::fixed | 以定点格式显示浮点数值 |
例如:
double x = 1.23;
cout << setprecision(5) << x << endl;
cout << setiosflags(ios::showpoint) << setprecision(5) << x << endl;
输出结果为:
1.23
1.2300
域宽
对于域宽,函数 width 和流操纵算子 setw 都可以实现对当前域宽(即输入输出的字符数)的设置。
-
如果输出的数据所需的宽度比设置的域宽小,空位用填充字符(默认为空格)填充;
-
如果被显示的数据所需的宽度比设置的域宽大,系统会自动突破宽度限制,输出所有位。
例如:
cin >> n;
cout << setw(6) << n << endl; // 以域宽输出 n,如果 n 不足位,前面补空格
编程要求
- 输入的数为一个小于 15 的非负整数 n ;
- 输出 5 个不同精度的PI,即小数点后面分别保留 n,n+1 ,n+2 ,n+3 ,n+4 位的PI,每个PI分别独占一行。
测试说明
平台会对你编写的代码进行测试,比对你输出的数值与实际正确数值,只有所有数据全部计算正确才能通过测试:
测试输入:4
预期输出:
3.1416
3.14159
3.141593
3.1415927
3.14159265
测试输入:10
预期输出:
3.1415926536
3.14159265359
3.141592653590
3.1415926535898
3.14159265358979
#include <iostream>
// 包含流操作算子库
#include <iomanip>
using namespace std;
// 定义常量PI,后面可以直接用PI代替后面的数值
#define PI 3.14159265358979323846
int main()
{
int n;
// 请在Begin-End之间添加你的代码,输入n,按不同的精度输出 PI。
/********** Begin *********/
cin >> n;
for(int i=0;i<5;i++)
{
cout << setiosflags(ios::fixed) << setprecision(n+i) << PI << endl;
}
/********** End **********/
return 0;
}
整型数据的运算:剩下的苹果哪去了
任务描述
本关任务:程序输入苹果数 m 和人数 n,要求输出每个人平均可以分到多少个苹果,并将计算结果存入到变量 x 中。
相关知识
C 和 C++ 的程序中,每个数据对象都必须有确定的数据类型。这是为什么呢?我们知道,计算机内存中的数据都是0、1串,例如下面这16位0、1组成的串。
01000001 01000010
那么这个0、1串到底代表什么意思呢?
同样的这一段0、1串,如果是8位整数,则是两个整数65和66;如果是字符,则是‘A’和‘B’;如果是16位整数,则是16706。
同一串数据,赋予不同的类型就有不同的解释,就代表了不同的含义,所以数据对象都必须有确定的数据类型。C 和 C++ 有四种基本数据类型:整型、浮点型、字符型和布尔型。
本关我们首先来介绍整形和浮点型。
整型
C 和 C++ 的整型数据分为有符号和无符号两类,每类又包含几种类型。不同的 C 和 C++ 编译器对同一种数据类型的表示方式可能会有不同。
典型的 C 和 C++ 整型类型如下表:
类别 | 类型名 | 简写 | 名称 | 存储长度 | 表示范围 |
---|---|---|---|---|---|
有符号 | signed short int | Short | 有符号短整型 | 2 字节 | -32768~32767 |
有符号 | signed int | Int | 有符号整型 | 4 字节 | -2147483648~2147483647 |
有符号 | signed long int | Long | 有符号长整型 | 4 字节 | -2147483648~2147483647 |
无符号 | unsigned short int | unsigned short | 无符号短整型 | 2 字节 | 0~65535 |
无符号 | unsigned int | unsigned | 无符号整型 | 4 字节 | 0~4294967295 |
无符号 | unsigned long int | unsigned long | 无符号长整型 | 4 字节 | 0~4294967295 |
整型数据可以参与算术运算、关系运算、逻辑运算、位运算等。整数的加法、减法、乘法和数学中的算术运算一致,而除法略有不同。
例如:
int a = 23, b = 8, c; // 声明整型变量 a、b、c,并对 a、b 进行初始化
c = a / b; // 计算 a 除以 b 并将结果存入 c 中
printf("%d\n",c); // 输出 c 的值
输出结果为:2
说明:23/8的结果为2,而不是2.875。这是因为两个整数相除,采用的是整数除法,结果也是整数。而且不是四舍五入的整数,而是直接截掉小数部分的整数。
浮点型
具有浮点型的数叫浮点数,也常常被称为双精度数或实数。如果要得到除法结果的小数部分,需要使用浮点数除法。只要除数和被除数其中有一个是浮点数,系统就会使用浮点数除法。
例如:
float num = 123 / 23.0;
printf("%f\n",num);
输出结果为:5.347826
说明:算式中23.0为浮点数,所以123/23.0
采用浮点数除法,结果也是浮点数(存入到浮点变量 num 中)。使用 printf 输出时,采用的转换说明符也是相应的%f。
二进制数据的位运算:字符是怎么存储的
任务描述
字符变量用来存储字符,一个字符占1个字节(8位),字符存储的其实是 ASCII 码表中所对应的整数,这些整数以 0-1 串来表示。那么每个字符对应的 0-1 串到底是什么呢?
本关任务
输入一个字符,输出该字符在内存中存储时对应的 0-1 串。
例如:‘A’的 ASCII 码是65,对应的8位二进制 0-1 串是 01000001。
相关知识
本关将介绍如何通过位运算来获得一个字符对应的字节值(即8个位对应的 0-1 值)。首先介绍一下位运算的基本知识。
位运算
位运算允许在二进制位级别上对数据进行操作。C 和 C++ 支持 6 种不同的位运算符。
运算符 | 名称 | 示例 | 说明 |
---|---|---|---|
& | 按位与 | a & b | a 和 b 的每一位作与运算 |
I | 按位或 | aIb | a 和 b 的每一位作或运算 |
^ | 按位异或 | a ^ b | a 和 b 的每一位作异或运算 |
~ | 按位取反 | ~ a | 将 a 的每一位取反 |
<< | 向左移位 | a << b | 将 a 的每一位向左移 b 位 |
>> | 向右移位 | a >> b | 将 a 的每一位向右移 b 位 |
按位与
两个操作数的按位与(&)是将两个操作数二进制表示的对应位进行与运算,即如果两个操作数的对应位都为1,则结果的对应位也为1,否则为0。
例如,3 & 14
结果为2,计算方法为:
3 的二进制表示: 00000011 ;
14 的二进制表示: 00001110 ;
3 & 14 的二进制表示: 00000010 。
按位或
两个操作数的按位或(∣)
是将两个操作数二进制表示的对应位进行或运算,即如果两个操作数的对应位都为0,则结果的对应位也为0,否则为1。
例如,3 | 14 结果为15,计算方法是:
3 的二进制表示: 00000011 ;
14 的二进制表示: 00001110 ;
3 | 14 的二进制表示: 00001111 。
按位异或
两个操作数的按位异或(^)是将两个操作数二进制表示的对应位进行异或运算,即如果两个操作数的对应位不相同,则结果的对应位为1,相同则为0。
例如,3 ^ 14 结果为13,计算方法是:
3 的二进制表示: 00000011 ;
14 的二进制表示: 00001110 ;
3 ^ 14 的二进制表示: 00001101 。
按位取反
按位取反(~)是将操作数二进制表示的对应位进行取反运算,即如果操作数的对应位为0,则结果的对应位为1,操作数为0,则结果对应位为0。
例如,~14 结果为241,计算方法是:
14 的二进制表示: 00001110 ;
~ 14 的二进制表示: 11110001 。
向左移位
向左移位(<<)是将左操作数的二进制表示向左移位,移动的位数就是右操作数(右操作数必须是非负值)的值,右端移出的空位填充0,移位后的左操作数的值即为运算的结果。
例如,3 << 2 结果为12,计算方法是:
3 的二进制表示: 00000011 ;
3 << 2 的二进制表示: 00001100 ;
向右移位
向右移位(>>)
是将左操作数的二进制表示向右移位,移动的位数就是右操作数的值,移位后的左操作数的值即为运算的结果。
左端移出的空位填充方式取决于左操作数的类型和具体的值:如果左操作数是无符号类型,或者是有符号类型但其值非负(最高位为0),那么高位填充0;如果左操作数是有符号类型,并且为负数(最高位为1),高位填充的值取决于所用的计算机系统,有的 C 和 C++ 系统实现填充0,有的填充1。
字符的二进制位的计算
有了上述位运算,我们就可以使用按位与运算判断一个字符的二进制表示(共8位)的某一位是1还是0。
例如下面的语句输出了字符变量 c 的二进制表示的第7位的值(c 的二进制表示有8位,从左到右分别为第 1 ~ 8 位):
cout << (int)(bool)(c & 0x02);
该语句输出表达式(int)(bool)(c & 0x02)
的值。该表达式有三个运算符:强制类型转换( int )、强制类型转换( bool )和按位与运算符 &。
因为括号的原因,表达式先计算c & 0x02
。0x02 是十六进制的02,其二进制表示为 00000010 。将 c 和 0x02 进行按位与,除了第7位外,不需要管 c 的其它位是什么,因为 0x02 除了第7位,其它位都是0。那么就有,如果 c 的第7位如果是1,则按位与的结果就是 00000010,否则结果为 00000000。
然后再进行两个类型转换运算,优先级相同,右结合,所以先计算(bool)(c & 0x02),即将计算结果转换为 bool 类型。如果c & 0x02的值为 00000000,则结果为 false,否则结果为 true(即只要有一位不为0,则整个十六进制数转换后即为 true)。最后再把计算结果转换为 int 类型,即如果前面的计算结果是 true,转换结果就是1,否则是0。
所以整个表达式的运算效果是:如果 c 的第7位为0,就输出0,第7位数为1则输出1。以此类推,我们就可以计算出 c 对应的二进制8位 0-1 串了。
编程要求
补充代码,实现输入一个字符(该字符由平台提供,你需要获取后使用),程序输出字符对应二进制 0-1 串。
测试说明
平台会对你编写的代码进行测试,比对你输出的数值与实际正确数值,只有所有数据全部计算正确才能通过测试:
测试输入:4
预期输出:00110100
测试输入:A
预期输出:01000001
说明:第一个测试用例的输入是字符 4,其 ASCII 码值为 52,所以二进制表示为 00110100。
// 包含两种I/O库,可以使用任一种输入输出方式
#include <stdio.h>
#include <iostream>
using namespace std;
int main()
{
char c; // c-存储输入的字符
cin >> c; // 输入字符
// 请在Begin-End之间添加代码,输出 c 的 8 位二进制表示
/********** Begin *********/
for(int i=7;i>=0;i--)
{
cout << (int)(bool)(c & 1<<i);
}
/********** End **********/
return 0;
}
分支结构:是闰年吗
任务描述
本关任务: 输入一个年份,判断该年份是否是闰年,是闰年则输出 Yes,否则输出 No。
相关知识
编程时有时候需要根据某个条件是否满足来选择执行或不执行某些语句。
C 和 C++ 的 if 语句可以实现这种根据条件判断来选择执行的功能。if 语句有两种形式,分别实现一路分支和两路分支。
if 语句
实现一路分支的基本方法是采用 if 语句:
if (<条件表达式>) <语句>
其语义为:如果<条件表达式>的值为非0 ( true ),即“真”,则执行<语句>,然后按顺序执行 if 语句的后继语句。如果<条件表达式>的值为0( false ),即“假”,则忽略<语句>,按顺序执行 if 语句的后继语句。
例如:
// 当分数 score 小于 60 时,执行输出语句,否则跳过该语句(不执行)
if (score < 60)
cout << "Sorry, you've failed!\n";
if…else 语句
实现两路分支的常用方法是采用 if…else 语句:
if (<条件表达式>) <语句1>
else <语句2>
其中,else 和<语句2>称为 else 分支或 else 子句。
其执行语义为:如果<条件表达式>
的值为非0,即“真”( true ),则执行<语句1>
,执行完<语句1>
后继续执行整个 if 语句的后继语句(不执行<语句2>
);如果<条件表达式>
的值为0,即“假”( false ),则跳过<语句 1>
,执行<语句2>
,执行完<语句2>
后继续执行整个 if 语句的后继语句。
即 if 语句是根据<条件表达式>
的结果,选择<语句1>
和<语句2>
中的一个执行,执行完后,整个 if 语句也就执行完了。
例如:
// 当分数 score 大于等于 60 时输出"Passed",否则输出"Failed"
if (grade >= 60)
cout << ″Passed\n″;
else
cout << ″Failed\n″;
if 语句的分支有且只能有一条语句,如果某个分支不需要做任何事时,可以使用空语句,空语句就是一个独立的分号。如果需要做很多事,一条语句完成不了,可以使用复合语句。用花括号括起来的一条或者多条语句叫复合语句,复合语句可以当做一条语句来看。
例1:
// 当速度 speed 小于等于 120 时什么也不做,否则输出"危险驾驶"
if (speed <= 120)
;// 空语句
else
cout<<"危险驾驶"<<endl;
例2:
// 如果 a 大于 b 则交换 a、b 的值
if (a > b)
{
// 复合语句
int c = a;
a = b;
b = c;
}
C 和 C++ 中任何有值的表达式都可以作为条件表达式(计算结果非0则为真,0则为假)。如果需要综合判断多个条件,就需要使用逻辑运算符了。
例如如果要描述条件“分数 x 在0到100之间”,不能使用数学中的表达形式0 <= x <= 100,而应当使用逻辑运算符x >= 0 && x <= 100。
// 逻辑与 && 表示两个条件x >= 0 和 x <= 100 都为真,结果才为真
if (x >= 0 && x <= 100)
……
直接写成0 <= x <= 100
编译器也不会报错,但语义完全不一样:
// 表示先计算 0 <= x ,结果可能是 true 或者 false,然后用该结果和 100 比较,所以该条件永远为真
if (0 <= x <= 100)
……
编程要求
补充代码,判断输入的年份(输入数据由平台提供,需要你获取后再使用)是否位闰年,是则输出 Yes,否则输出 No。
提示:闰年的判别条件是该年年份能被 4 整除但不能被 100 整除、或者能被 400 整除。闰年的 2 月有 29 天,其它月份的天数和非闰年是一样的。
测试说明
平台会对你编写的代码进行测试,比对你输出的数值与实际正确数值,只有所有数据全部计算正确才能通过测试:
测试输入:2017
预期输出:No
测试输入:2000
预期输出:Yes
// 包含两种I/O库,可以使用任一种输入输出方式
#include <stdio.h>
#include <iostream>
using namespace std;
int main()
{
int year;
// 请在此添加代码,判断输入的年份是否位闰年,是则输出"Yes",否则输出"No"
/********** Begin *********/
cin >> year;
if(year >=0)
{
int a = year % 100;
int b = year % 400;
int c = year % 4;
if(a == 0 && b == 0)
cout << "Yes" <<endl;
else if(a!=0&&c==0)
cout << "Yes" << endl;
else
cout << "No" << endl;
}
else
{
year = -year;
int a = year % 100;
int b = year % 400;
int c = year % 4;
if(a == 0 && b == 0)
cout << "Yes" <<endl;
else if(a!=0&&c==0)
cout << "Yes" << endl;
else
cout << "No" << endl;
}
/********** End **********/
return 0;
}
分支结构:一年中的第几天
任务描述
本关任务:输入一个日期,如“ 2017 6 15 ”,计算这一天是这一年的第几天(1月1日是第一天)并输出。假设输入的都是合法的日期,但别忘了考虑闰年的情况。
相关知识
if 语句回根据对某个条件的判断结果,将程序的流程分为两支。而本关将要介绍的是会将程序分为多个分支的语句:
-
if-else 嵌套
-
switch 语句
下面我们就一起来学习这两种语句的使用。
if-else 嵌套
多路分支的第一种实现方法就是使用 if-else 嵌套,即在 if 分支和(或者) else 分支中再嵌套 if-else 语句。
例如:
cin >> angle; // 输入角度
if (angle % 90 == 0)
{
// if-else嵌套
if (angle % 180 == 0)
cout << "线段在X轴上" << endl;
else
cout << "线段在Y轴上" << endl;
}
else
cout << "线段在象限" << angle / 90 + 1 << endl;
该程序计算输入的角度 angle 落在哪个象限或者哪个轴上。当输入的角度是90的倍数时(angle % 90 == 0为真)
,有两种情况,或者在X轴上,或者在Y轴上,所以需要其它条件(angle % 180 == 0)
再次判断进行区分处理。
上面程序通过 if-else 嵌套实现了三路分支。
switch 语句
另一种实现多路分支的方法是使用 switch 语句, 格式如下:
switch(<条件表达式>)
{
case <常量表达式1>:
<语句序列1>;
case <常量表达式2>:
<语句序列2>;
……
case <常量表达式n>:
<语句序列n>;
default:
<语句序列n+1>;
}
其中,switch 、case 和 default
都是 C++ 的关键字。<条件表达式>是值为整型的表达式;每个 case 对应一个分支处理;default 分支为默认处理分支;<常量表达式1>……<常量表达式n>都是值为整型常量的表达式;<语句序列1>……<语句序列n+1>都是一组语句,可以为空。
执行流程
switch 语句执行流程如下:
switch 语句执行时,首先计算<条件表达式>得到一个整型的值,将该值与<常量表达式1>……<常量表达式n>的值逐个进行比较,如果与其中一个相等,则执行该常量表达式下的语句序列。
需要注意的是执行完该常量表达式对应的语句序列后,还将继续执行后续分支的处理语句序列,直到 switch 语句结束或者遇到跳转指令(break);如果测试表达式的值不与任何一个常量表达式的值相等,则执行 default 分支后面的语句。
流程图如下:
switch 语句与 break
当一个 case 分支条件得到满足时,执行完该分支的语句序列后,还将继续执行后续分支的处理语句序列。如果希望执行完一个分支后就结束整个 switch 语句,可以在每个分支语句后面都加上一条 break 语句。
break 语句是一种转移语句,只能出现在 switch 结构和循环结构中。break 语句可以跳出直接包含该 break 语句的 switch 结构或循环结构(只能跳出一层),程序控制离开该 switch 结构或循环结构,执行其后继语句。
带 break 语句的 switch 语句模式如下:
switch(<条件表达式>)
{
case <常量表达式1>:
<语句序列1>;
break;
case <常量表达式2>:
<语句序列2>;
break;
……
case <常量表达式n>:
<语句序列n>;
break;
default:
<语句序列n+1>;
}
其执行过程为:首先计算<条件表达式>,将得到的整型值与<常量表达式1>……<常量表达式n>的值逐个进行比较,一旦检测到与其中一个相等,则执行该常量表达式下的语句序列,执行完后,紧接着就执行 break 语句,离开该 switch 结构,不再执行后续分支的处理语句序列,转向执行整个 switch 语句的后继语句。
例如下面的程序将百分制转换成十分制输出。如输入85,计算 scorePhrase 为8,则执行 switch 语句后进入case 8:分支,输出‘B’,然后执行 break ;语句跳出整个 switch 语句:
cin >> score; // 输入百分制分数
scorePhrase = score / 10; // 计算分数段,转换成十分制
// 判断并输出等级
switch (scorePhrase)
{
case 10:
case 9:
cout << 'A' << endl;
break;
case 8:
cout << 'B' << endl;
break;
case 7:
cout << 'C' << endl;
break;
case 6:
cout << 'D' << endl;
break;
case 0: case 1: case 2: case 3: case 4: case 5:
cout << 'E' << endl;
break;
default:
cout << "The score is illegal!" << endl;
}
编程要求
在右侧编辑器中的Begin-End之间补充代码,通过输入的日期(数据由平台提供,以“年 月 日”的形式表示,需要你获取后使用)来判断该天是当年的第几天的要求。具体要求如下:
-
对于输入一个日期(年月日之间以一个空格间隔),形如2017 6 15,计算这一天是这一年的第几天并输出;
-
输出形式为“年-月-日是第X天”(其中 X 是你的计算结果)。
提示:本题计算思路比较清晰,例如 6 月 15 日就需要把前 5 个月的天数全部加上再加上 15 , 5 月 3日则只需要加满前四个月的天数再加上 3 ,所以本题的计算根据月份不同而方法不同。
测试说明
平台会对你编写的代码进行测试,比对你输出的数值与实际正确数值,只有所有数据全部计算正确才能通过测试:
平台会对你选择的答案进行判断,全对则通过测试:
测试输入:2017 6 15
预期输出:2017-6-15是第166天
测试输入:2000 10 1
预期输出:2000-10-1是第275天
// 包含两种I/O库,可以使用任一种输入输出方式
#include <stdio.h>
#include <iostream>
using namespace std;
int main()
{
// y-年,m-月,d-日,n-第几天
int y, m, d, n;
// 请在此添加代码,计算并输出指定日期是第几天
/********** Begin *********/
cin >> y >> m >> d;
switch(m)
{
case 12:
n = 30+n;
case 11:
n = 31+n;
case 10:
n = 30+n;
case 9:
n = 31+n;
case 8:
n = 31+n;
case 7:
n = 30+n;
case 6:
n = 31+n;
case 5:
n = 30+n;
case 4:
n = 31+n;
case 3:
{int a = y % 100;
int b = y % 400;
int c = y % 4;
if(a == 0 && b == 0)
n = 29+n;
else if(a!=0&&c==0)
n = 29+n;
else
n = 28+n;}
case 2:
n = 31+n;
case 1:
n = n+d;
}
/********** End **********/
printf("%d-%d-%d是第%d天\n",y,m,d,n);
return 0;
}
分支结构:重排最大数
任务描述
本关任务:输入一个1000(不包含1000)以内的正整数,首先将其补齐为三个数字(如果是两位数或者一位数,则前面补0),然后将这三个数字按不同顺序排列成一组数据,输出排列中最大的那个数。
例如:
如果输入249,可以重排出429,249,924,942等等,其中最大的应该是942,因此应输出942;
如果输入的是14,则在前面补0得到014,据此可以重排出041,140,410等等,其中最大的应该是410,因此应输出410。
提示
要得到一个三位数的某个数字可以使用求余运算和除法运算。例如下面的程序可以获取一个三位数的十位数并输出:
int num = 258;
int a = num / 10 % 10;
cout << a;
第二条语句先计算num /10得到25,然后用25对10求余得到5,程序最后输出5。
测试说明
平台会对你编写的代码进行测试,比对你输出的数值与实际正确数值,只有所有数据全部计算正确才能通过测试:
测试输入:5
预期输出:500
测试输入:185
预期输出:851
// 包含两种I/O库,可以使用任一种输入输出方式
#include <stdio.h>
#include <iostream>
using namespace std;
int main()
{
// n-输入的数,m-重排后的数
int n, m;
// 请在此添加代码,输入一个小于1000的正整数,重排出最大的数存入m中
/********** Begin *********/
cin >> n;
if(n>99)
{
int a =n%10;
int b = n/10%10;
int c = n/100%10;
int m1,m2,m3;
if (a>b)
{
m1 =a;
if(b>c)
{
m2=b;
m3=c;
}
else
{
if(a>c)
{
m2=c;
m3=b;
}
else
{
m1=c;
m2=a;
m3=b;
}
}
}
else
{
m1 = b;
if(a>c)
{
m2=a;
m3=c;
}
else
{
if(b>c)
{
m2=c;
m3=a;
}
else
{
m1=c;
m2=b;
m3=a;
}
}
}
m = m1*100+m2*10+m3;
}
else if (n>9)
{
int a =n%10;
int b = n/10%10;
int c = 0;
int m1,m2,m3;
if (a>b)
{
m1 =a;
if(b>c)
{
m2=b;
m3=c;
}
else
{
if(a>c)
{
m2=c;
m3=b;
}
else
{
m1=c;
m2=a;
m3=b;
}
}
}
else
{
m1 = b;
if(a>c)
{
m2=a;
m3=c;
}
else
{
if(b>c)
{
m2=c;
m3=a;
}
else
{
m1=c;
m2=b;
m3=a;
}
}
}
m = m1*100+m2*10+m3;
}
else
{
int a =n%10;
int b = 0;
int c = 0;
int m1,m2,m3;
if (a>b)
{
m1 =a;
if(b>c)
{
m2=b;
m3=c;
}
else
{
if(a>c)
{
m2=c;
m3=b;
}
else
{
m1=c;
m2=a;
m3=b;
}
}
}
else
{
m1 = b;
if(a>c)
{
m2=a;
m3=c;
}
else
{
if(b>c)
{
m2=c;
m3=a;
}
else
{
m1=c;
m2=b;
m3=a;
}
}
}
m = m1*100+m2*10+m3;
}
/********** End **********/
// 输出重排后的数
cout << m << endl;
return 0;
}
循环结构:黑洞陷阱
任务描述
**本关任务:**程序输入的一个小于1000且三个数字不全相等的整数,请你输出进入黑洞的重排求差过程。本问题又称“Kaprekar问题”。
495是一个很神奇的数,被称为黑洞数或者陷阱数。
给定任何一个小于1000的正整数,经前位补0后可以得到一个三位数(两位数前面补1个0,一位数前面补2个0)。如果这个三位数的三个数字不全相等,那么经有限次“重排求差”操作(组成该数的数字重排后的最大数减去重排后的最小数),总会得到495。
例如,对整数80,前位补0后得到080,重排后可以得到800,008。此时可以得到的最大数为800,最小数为008(即8)。那么只需要4次重排求差即可得到495,过程如下:
1:800-8=792 //第一次
2:972-279=693 //第二次,将第一次的结果进行重排求差
3:963-369=594 //第三次,将第二次的结果进行重排求差
4:954-459=495 //第四次以此类推
相关知识
要实现上述功能,需要反复做一些相同或相似的工作,也就是反复执行一些代码。这需要用到循环语句。
C 和 C++ 有3种基本的循环控制结构:while 语句、do-while 语句和 for 语句。
while 循环
while 循环语句表现为:
while (<条件表达式>)
<语句>
其中 while 是 C 和 C++ 的关键字, <条件表达式>是循环控制条件,<条件表达式>后面的语句是循环体。
- while 语句执行过程
首先计算<条件表达式>的值,如果<条件表达式>的值为0(即 false ),则跳过循环体<语句>,执行整个 while 语句的后继语句;
如果<条件表达式>的值为非0(即 true ),则执行指定的<语句>,执行完该语句后,再计算<条件表达式>的值,如果<条件表达式>的值仍然为非0,则继续执行指定的<语句>,再进行测试 ……,直到<条件表达式>的值为0,再跳过循环体<语句>,结束整个 while 语句的执行,接着执行整个 while 语句的后继语句。
执行流程图如下:
例如下面的程序可以计算飞机上下落的铁球落地的时间(精确到秒)。假设飞机高度为2500米,下落的高度d(米)和时间t(秒)之间的关系是d=(1/2)gt 2,其中g=9.82m/s2:
// 初始状态时间为秒,下落高度为米
int sec = 0;
float dis = 0;
// 如果下落高度小于米,则继续循环
while (dis < 2500)
{
// 每次循环计算每秒后的下落高度
sec += 1;
dis = 0.5 * 9.82 * sec * sec;
}
// 输出落地需要的秒数
cout << sec << "秒" << endl;
do-while 语句
第二种循环语句是 do-while 语句,一般格式为:
do <语句>
while (<条件表达式>);
其中,do 和 while 都是 C 和 C++ 的关键字,do 和 while 之间的语句是循环体,<条件表达式>是循环控制条件,整个 do-while 语句的最后是作为语句结束标志的分号。
do-while 语句构成的循环与 while 语句构成的循环有所不同: 它 先执行循环中的<语句> ,然后计算<条件表达式>的值,判断条件的真假,如果为 true,则继续循环;如果为 false,则终止循环,继续执行整个 do-while 语句的后继语句。
因此,do-while 语句是一种出口控制的循环结构,其循环体至少要被执行一次,而 while 语句是入口控制的循环结构,其循环体未必会被执行。
do-while 语句的执行流程图如下:
同样的,当循环体包含多条语句时,可以用花括号把它们括起来,形成一个复合语句。
for 语句
第三种循环语句为 for 语句,一般形式为:
for (<初始化语句> ;[<条件表达式>]; [<增量表达式>] )
<语句>
for 是 C 和 C++ 的关键字,表示 for 循环语句的开始。<语句>是 for 语句的循环体。
<初始化语句>可以是任何合法的语句,<条件表达式>和<增量表达式>则可以由任何合法的表达式充当,具体说明如下:
1.初始化语句
<初始化语句>
通常是一个赋值语句,用来给循环控制变量赋初值。<初始化语句>可以是表达式语句或声明语句,以“ ; ”结束。
2.条件表达式
<条件表达式>
是一个能够转换成逻辑值的表达式,它决定什么时候退出循环,该表达式可以为空(为空时逻辑值恒为 true )。<条件表达式>和<增量表达式>之间用“ ; ”分开。
3.增量表达式
<增量表达式>
定义了循环控制变量每循环一次后按什么方式变化,该表达式也可以为空,这时不产生任何计算效果。
for 语句的执行过程
首先计算<初始化语句>
,然后计算<条件表达式>
的值。
如果该值为 false,则结束循环,跳过循环体的<语句>,转到整个for语句的后继语句继续执行;
如果该值为 true,则执行循环体的<语句>,执行完循环体后,紧接着执行<增量表达式>,再计算<条件表达式>的值,如果该值为 true,则执行循环体的<语句>,再执行<增量表达式>,再计算<条件表达式>进行测试,…… ,直到<条件表达式>的值为 false,则结束循环,跳过循环体的<语句>,继续执行整个 for 语句的后继语句。
for 语句的执行流程图如下:
例如下面的程序可以计算1到100之间所有整数的和并输出:
int sum = 0; // 声明求和变量并初始化
// 求和
for (int i = 1; i <= 100; i++)
sum += i;
cout << "The sum of 1 to 100 is: " << sum << endl;
编程要求
在右侧编辑器中的Begin-End之间补充代码,获取输入的一个小于1000且三个数字不全相等的整数(数据由平台提供,你需获取后使用),并输出进入黑洞的重排求差过程。
测试说明
平台会对你编写的代码进行测试,比对你输出的数值与实际正确数值,只有所有数据全部计算正确才能通过测试:
测试输入:123
预期输出:
1:321-123=198
2:981-189=792
3:972-279=693
4:963-369=594
5:954-459=495
测试输入:18
预期输出:
1:810-18=792
2:972-279=693
3:963-369=594
4:954-459=495
// 包含两种I/O库,可以使用任一种输入输出方式
#include <stdio.h>
#include <iostream>
using namespace std;
int maxx(int n){
int m;
if(n>99)
{
int a =n%10;
int b = n/10%10;
int c = n/100%10;
int m1,m2,m3;
if (a>b)
{
m1 =a;
if(b>c)
{
m2=b;
m3=c;
}
else
{
if(a>c)
{
m2=c;
m3=b;
}
else
{
m1=c;
m2=a;
m3=b;
}
}
}
else
{
m1 = b;
if(a>c)
{
m2=a;
m3=c;
}
else
{
if(b>c)
{
m2=c;
m3=a;
}
else
{
m1=c;
m2=b;
m3=a;
}
}
}
m = m1*100+m2*10+m3;
}
else if (n>9)
{
int a =n%10;
int b = n/10%10;
int c = 0;
int m1,m2,m3;
if (a>b)
{
m1 =a;
if(b>c)
{
m2=b;
m3=c;
}
else
{
if(a>c)
{
m2=c;
m3=b;
}
else
{
m1=c;
m2=a;
m3=b;
}
}
}
else
{
m1 = b;
if(a>c)
{
m2=a;
m3=c;
}
else
{
if(b>c)
{
m2=c;
m3=a;
}
else
{
m1=c;
m2=b;
m3=a;
}
}
}
m = m1*100+m2*10+m3;
}
else
{
int a =n%10;
int b = 0;
int c = 0;
int m1,m2,m3;
if (a>b)
{
m1 =a;
if(b>c)
{
m2=b;
m3=c;
}
else
{
if(a>c)
{
m2=c;
m3=b;
}
else
{
m1=c;
m2=a;
m3=b;
}
}
}
else
{
m1 = b;
if(a>c)
{
m2=a;
m3=c;
}
else
{
if(b>c)
{
m2=c;
m3=a;
}
else
{
m1=c;
m2=b;
m3=a;
}
}
}
m = m1*100+m2*10+m3;
}
return m;
}
int minn(int n){
int m;
if(n>99)
{
int a =n%10;
int b = n/10%10;
int c = n/100%10;
int m1,m2,m3;
if (a>b)
{
m1 =a;
if(b>c)
{
m2=b;
m3=c;
}
else
{
if(a>c)
{
m2=c;
m3=b;
}
else
{
m1=c;
m2=a;
m3=b;
}
}
}
else
{
m1 = b;
if(a>c)
{
m2=a;
m3=c;
}
else
{
if(b>c)
{
m2=c;
m3=a;
}
else
{
m1=c;
m2=b;
m3=a;
}
}
}
m = m3*100+m2*10+m1;
}
else if (n>9)
{
int a =n%10;
int b = n/10%10;
int c = 0;
int m1,m2,m3;
if (a>b)
{
m1 =a;
if(b>c)
{
m2=b;
m3=c;
}
else
{
if(a>c)
{
m2=c;
m3=b;
}
else
{
m1=c;
m2=a;
m3=b;
}
}
}
else
{
m1 = b;
if(a>c)
{
m2=a;
m3=c;
}
else
{
if(b>c)
{
m2=c;
m3=a;
}
else
{
m1=c;
m2=b;
m3=a;
}
}
}
m = m3*100+m2*10+m1;
}
else
{
int a =n%10;
int b = 0;
int c = 0;
int m1,m2,m3;
if (a>b)
{
m1 =a;
if(b>c)
{
m2=b;
m3=c;
}
else
{
if(a>c)
{
m2=c;
m3=b;
}
else
{
m1=c;
m2=a;
m3=b;
}
}
}
else
{
m1 = b;
if(a>c)
{
m2=a;
m3=c;
}
else
{
if(b>c)
{
m2=c;
m3=a;
}
else
{
m1=c;
m2=b;
m3=a;
}
}
}
m = m3*100+m2*10+m1;
}
return m;
}
int main()
{
int n;
// 请在此添加代码,输出整数进入黑洞过程
/********** Begin *********/
cin >> n;
int r = maxx(n)-minn(n);
cout <<"1:"<< maxx(n) <<"-"<<minn(n)<<"="<<maxx(n)-minn(n)<<endl;
int i=2;
while(r!=495)
{
cout <<i<<":"<< maxx(r) <<"-"<<minn(r)<<"="<<maxx(r)-minn(r)<<endl;
r =maxx(r)-minn(r);
i=i+1;
}
/********** End **********/
return 0;
}
登月纸桥
任务描述
本关任务:编写一个函数,计算需要把纸折叠多少次(假设纸张足够大,可以无限次折叠),其厚度才能搭建一座登月纸桥,考虑到将来需要到更远的星球,所以函数需要根据具体距离计算纸张折叠的次数并返回。
相关知识
函数是一个命名的程序代码块,是程序完成其操作的一种功能单位。在程序设计中,有许多算法是通用的,例如求一个数的平方根,求一个三角函数等,经常会将这些算法定义为一个函数,这样在程序中需要这些算法的地方就可以直接使用(通过函数调用)它们了。
虽然 C 和 C++ 的库函数已经提供了丰富的功能,但很多时候程序员还是需要根据具体问题的需求定义自己的函数。函数需要先定义再使用(函数调用)。
函数的定义
函数定义的一般格式为:
<返回值类型> <函数名>(<参数列表>)
<函数体>
- <返回值类型>、<函数名>及<参数列表>构成了函数头。
- <返回值类型>说明函数返回值的数据类型,也称为函数的返回类型。
它可以是任一基本数据类型或用户自定义的数据类型;如果无返回值,则用关键字 void
说明。默认的返回类型是 int,即若未指定返回类型,则返回类型是 int 。
- <函数名>是程序员为该函数指定的名字,函数名需要遵守标识符命名规定。
- <参数列表>指明函数的参数的个数、名称和类型,函数定义中的参数称为形式参数,简称形参。
当有多个形式参数时,用逗号分隔。如果函数没有形参,参数列表为空,函数名后面的圆括号不能少,这时也可在括号中加上关键字 void,表示这是一个无参函数。
- <函数体>描述函数的功能,即函数所完成的具体操作,它由一系列说明语句和执行语句组成。
函数体实际上是一个复合语句,花括号不能少,它指明函数体的开始和结束。函数执行时,如同执行一个复合语句一样,顺序执行函数体,直到遇到 return 语句或者遇到表示函数体结束的那个右花括号为止,函数执行完毕后,返回调用程序继续执行。
例如,下面的程序定义了一个名为 max 的函数,该函数有3个形式参数 a、 b 和 c,类型均为 int,返回值也是 int 类型。函数体由一个变量说明语句、一个赋值语句和一个返回语句构成。函数的功能是求 a 、b 和 c 中最大的值:
int max(int a, int b, int c)
{
int m;
m = (a > b) ? a : b;
return (m > c) ? m : c;
}
注意:
-
所有函数的定义是并列的、平行的,在一个函数定义内部不允许定义另外一个函数。但可以对别的函数进行调用或作引用说明;
-
函数定义中声明的所有变量都是局部变量,只在声明语句所处的程序块中有效。大多数函数都有一组参数,函数定义时指明了每个形式参数的名字、类型,函数调用时提供的实在参数要与形式参数一一对应(函数调用时需要进行参数传递,将实参的值一个个传给对应的形参)。函数的形式参数也可视为局部变量,在函数体范围内有效;
-
函数可以有返回值,也可以没有返回值;
-
函数没有返回值时,返回类型必须用关键字 void 说明。这类函数的函数体内没有 return 语句或 return语句中无表达式。当函数执行到 return 语句时,程序返回到调用该函数的地方;如果函数体没有 return语句,当执行完函数体最后的语句后,程序再返回到调用该函数的地方;
-
函数有返回值时,必须指明返回值类型(不能是 void ),此时函数体必须包含带表达式的return语句。表达式的值将返回给调用程序,该值的数据类型必须与返回值类型一致;
-
函数体可有多个 return 语句。调用函数执行时,只要遇到一个 return 语句,就马上忽略剩余代码,立刻返回到调用程序;
-
函数需要先定义后使用,但函数的相互调用关系很难保证所有函数都是先定义后使用的。这时就需要先声明函数原型了。
声明函数
为了让 main 程序(或者其他准备调用函数的程序)知道函数的相关信息,需要在调用程序的前面,以“函数原型”的方式对被调用的函数进行声明。
在 C 和 C++ 中,函数原型的一般形式为:
<返回值类型> <函数名>(<参数列表>);
函数原型形式上只是比函数头多了一个分号,函数原型参数列表中形参的名称可以省略。
函数原型告诉编译器后面将会定义这么一个函数,其函数名、返回值类型、参数列表分别是什么,便于编译器检查函数调用的正确性。
例如,前面函数 max 的函数原型可以为:
int max(int , int , int);
建议:如果一个文件中定义多个函数时,把主函数之外的其它函数的函数原型都放在文件中所有函数定义的前面,这样所有函数调用就都符合“先声明,后使用”的原则了。
编程要求
在右侧编辑器中的Begin-End之间补充代码,编写一个函数,给定一个距离和纸张的厚度(数据由平台提供,你需要获取后使用),计算需要把这张纸折叠多少次,其厚度才能搭建一座登月纸桥。考虑到将来需要到更远的星球,函数需要根据具体距离计算纸张折叠的次数并返回。
已知:月球离地球最近距离(近地点)为363300千米,最远距离(远地点)为405500千米,一张纸的厚度一般为0.088到0.114毫米。
测试说明
平台会对你编写的代码进行测试,比对你输出的数值与实际正确数值,只有所有数据全部计算正确才能通过测试:
测试输入:363300 0.088
预期输出:需要折叠42次
测试输入:405500 0.088
预期输出:需要折叠43次
#include <iostream>
using namespace std;
// foldTimes-计算建纸桥的折叠次数
// 参数:dis-星际距离(千米),thick-纸的厚度(毫米)
// 返回值:建桥需要折叠的次数
int foldTimes(double dis, double thick);
int main()
{
double dis, thick;
cin >> dis >> thick;
cout << "需要折叠" << foldTimes(dis,thick) << "次" << endl;
return 0;
}
int foldTimes(double dis, double thick)
{
// 请在这里补充代码,实现函数foldTimes
/********** Begin *********/
double h = thick;
int i;
for(i=0;h<(dis*1000000);i++)
{
h = h*2;
}
return i;
/********** End **********/
}
几点几分了?
任务描述
本关任务:编写一个函数 whatTime,该函数通过秒表显示的秒数,计算当前的时间(几点几分几秒),计算得到的时间通过参数返回。
相关知识
本关内容涉及传引用,是 C++ 对 C 语言的扩充部分, C 语言不包括这部分内容。
由于 C++ 的函数只能返回一个值,而本题要求返回当前时间(几点几分几秒),需要返回三个值,所以没法通过 return 语句一次返回(如果学了结构也可以把三个数据一起打包返回),但 C++ 函数参数提供了另一种返回值的方式:引用参数。
函数调用时,需要进行参数传递,即把实参的值逐个传给对应的形参。对 C++ 而言参数的传递方式有两种:传值和传引用。
参数传值
传值是指值的复制,即把实参的值传递给形参,实参和形参是不同的变量,有各自独立的存储空间,因此函数被调用执行时,只能访问形式参数对应的内存单元,不能访问或修改实在参数的值。
下面的 swap 函数试图交换两参数的值。
#include <iostream>
using namespace std;
// 交换 x 和 y 的值
void swap(int x,int y)
{
int temp = x;
x = y;
y = temp;
}
int main()
{
int a = 10, b = 20;
// 调用函数,试图交换
swap(a, b);
cout << "a:" << a << " b:" << b << endl;
return 0;
}
程序的运行结果显示 a 和 b 的值并没被交换。
这是因为 main 函数在执行swap(a,b)
时,是把实在参数 a 和 b 的值传给了 swap
中形式参数 x 和 y,swap
函数中交换的也只是形参 x 和 y 这两个局部变量的值,跟 a 、b 两个变量无关,当 swap
函数执行完后,就返回到 main
函数的调用语句处,继续向下执行,输出 a 、b 的值,因此依然是原来的10和20。
参数传引用
传引用是指调用函数时,把对实参变量的引用传给形式参数,即将实参变量的地址存放到对应的形参的形式单元中。
当程序转入到被调用函数后,在执行函数体过程中,对形式参数的任何引用或赋值都被处理成对相应形式单元的间接访问,即根据形式单元中存放的实参变量的地址找到实参单元,对形式参数的引用或赋值被处理成对该实参单元的访问。
也就是说,对形参的访问实际上就是对实参的访问,可以通过修改实参的方式把数据传出被调用函数。
提示:传引用要求实在参数必须是变量。
修改上面的 swap 函数,实现真正的交换。
#include <iostream>
using namespace std;
// 交换x和y的值
void swap(int &x, int &y)
{
int temp = x;
x = y;
y = temp;
}
int main()
{
int a = 10, b = 20;
// 调用函数,试图交换
swap(a, b);
cout << "a:" << a << " b:" << b << endl;
return 0;
}
输出结果为:a:20 b:10
程序中函数 swap
的参数定义为int &x,int &y
,其中 x 、y 前面的 & 为引用符号,表示 x 、y 是引用变量,调用该函数时,对应参数的传递采用传引用方式。这时函数 swap 中修改 x 和 y 的值,实际上就是在修改实参 a 和 b 的值。
编程要求
在右侧编辑器中的Begin-End之间补充代码,编写函数 whatTime,该函数通过秒表显示的秒数(该数据由平台提供,你需要获取后使用),计算当前的时间(几点几分几秒),计算得到的时间通过参数返回。
测试说明
平台会对你编写的代码进行测试,比对你输出的数值与实际正确数值,只有所有数据全部计算正确才能通过测试:
测试输入:23456
预期输出:6:30:56
测试输入:34567
预期输出:9:36:7
#include <iostream>
using namespace std;
void whatTime(int secs, int &h, int &m, int &s)
{
// 请在这里补充代码,设计并实现函数whatTime,使main函数中的函数调用正确
/********** Begin *********/
if(secs>=3600)
{
h = secs / 3600;
int a = secs%3600;
m = a /60;
s = a%60;
}
else if(secs>=60)
{
h = 0;
m = secs / 60;
s = secs % 60;
}
else
{
h = 0;
m = 0;
s = secs;
}
/********** End **********/
}
int main()
{
int secs; // secs秒表上的秒数
int h, m, s; // 当前时间:h-小时,m-分,s-秒
cin >> secs; // 输入秒表上的秒数
whatTime(secs,h,m,s); // 计算当前时间
cout << h << ":" << m << ":" << s << endl; // 输出当前时间
return 0;
}
这天星期几?
任务描述
本关任务:编写函数 whatDay,计算某年某月的1号是星期几并返回。
相关知识
要知道某一天是星期几,可以用已知的某一天进行推导。
例如已知公元1年1月1日是星期一,公元2年1月1日则是在星期一基础上加上一整年的天数(要考虑闰年,闰年一年366天,非闰年365天),如果不是1月,例如7月,则要加上1到6月的所有天数,这里也要考虑闰年,因为闰年的二月是29天,非闰年是28天。
这样就可以算出从公元1年1月1日到该年月过了多少天,而过了7天星期不变,所以可以用这种方法推导出公元元年之后的任何一天是星期几。
下面的程序可以计算星期一过了 n 天后是星期几:
w = 1; // 从星期一开始
w = w + n; // n天后
w = w % 7; // 得到0-6,其中0为星期天
// 调整星期天
if(w == 0) w = 7;
编程要求
在右侧编辑器中的Begin-End之间补充代码,计算输入数据年月份的1号是星期几并返回(周一到周日分别返回整数1到7)。
其中该函数的参数为输入的两个整数数据:年和月。
提示:由于判断是否闰年有多个地方需要用到,故已把该功能单独实现为一个函数。
测试说明
平台会对你编写的代码进行测试,比对你输出的数值与实际正确数值,只有所有数据全部计算正确才能通过测试:
测试输入:2016 1
预期输出:2016年1月1日是星期5
测试输入:2017 7
预期输出:2017年7月1日是星期6
#include <iostream>
using namespace std;
// 函数leapYear
int leapYear(int y)
{
if(y % 4 == 0 && y % 100 != 0 || y % 400 == 0)
return 1;
return 0;
}
// 函数whatDay:计算某年某月某日是星期几
// 参数:year-年,month-月
// 返回值:--7分别表示星期一到星期日
int for_mounth(int a,int b)
{
int days = 0;
switch (a)
{
case 12:
days = days+30;
case 11:
days = days+31;
case 10:
days = days+30;
case 9:
days = days+31;
case 8:
days = days+31;
case 7:
days = days+30;
case 6:
days = days+31;
case 5:
days = days+30;
case 4:
days = days+31;
case 3:
{
if(leapYear(b))
days = days +29;
else
days = days +28;
}
case 2:
days = days+31;
case 1:
days = days+0;
}
return days;
}
int whatDay(int year, int month)
{
// 请在这里补充代码,实现函数whatDay
/********** Begin *********/
int num_4 = (year / 4)*(365*3+366);
int num_1 = year % 4;
int num,weekNum;
switch (num_1){
case 1:
num = num_4 + for_mounth(month,year);
break;
case 2:
num = num_4 + 365 + for_mounth(month,year);
break;
case 3:
num = num_4 + 365*2 + for_mounth(month,year);
break;
case 0:
num = num_4 -366 + for_mounth(month,year);}
weekNum = num % 7;
return weekNum;
/********** End **********/
}
int main()
{
int y, m, xq; // 年、月、星期几
cin >> y >> m; // 输入年月
xq = whatDay(y,m); // 计算星期几
cout << y << "年" << m << "月1日是星期"; // 输出星期
if(xq == 7)
cout << "日" << endl;
else
cout << xq << endl;
return 0;
}
打印日历
任务描述
本关任务:根据输入的年份和月份来输出该年月的日历。
相关知识
日历的格式如下图所示:
上图中每个汉字(一、二…日)占四个字节,右对齐,由于汉字显示本身就占2个字节,所以只需要在汉字前面多输出两个空格就好了。每个日期数字占4个字节,也是右对齐,这样能使输出的日历上下对齐。
剩下的事情就是循环输出这个月的所有日期了,这个月有多少天可以专门用一个函数实现,注意闰年二月(判闰年的函数这里又可以用一次了)是29天。
注意:在 1 号前应该留多少空位(如果 1 号是星期 n ,则留 n-1 个空位,每个空位是一个日期的宽度),注意什么时候换行(日期加 1号前空位数量是 7 的倍数则换行)。
编程要求
在右侧编辑器中的Begin-End之间补充代码,根据输入的年月(函数printMonth 的两个参数)打印该年该月的日历。日历输出格式要求如下:
每个汉字(一、二、…、日)占四个字节,右对齐,由于汉字显示本身就占2个字节,所以只需要在汉字前面多输出两个空格就好了;
每个日期数字占4个字节,也是右对齐,这样能使输出的日历上下对齐。
// 包含两种I/O库,可以使用任一种输入输出方式
#include <stdio.h>
#include <iostream>
using namespace std;
// 函数printMonth:按要求的格式打印某年某月的日历
// 参数:year-年,month-月
// 返回值:无
void printMonth(int year, int month);
// leapYear:判断闰年
// 参数:y-年
// 返回值:1-是闰年,0-不是闰年
int leapYear(int y)
{
if(y % 4 == 0 && y % 100 != 0 || y % 400 == 0)
return 1;
return 0;
}
// 函数whatDay:计算某年某月的1号是星期几
// 参数:year-年,month-月
// 返回值:1到7--星期1到星期日
int whatDay(int year, int month)
{
// 1年月日是星期一
int w = 1;
int i;
// 1到year-1都是全年
for(i = 1; i < year; i++)
{
if(leapYear(i))
w += 366;
else
w += 365;
}
switch(month)
{
case 12: // 加月的
w += 30;
case 11: // 加月的
w += 31;
case 10: // 加月的
w += 30;
case 9: // 加月的
w += 31;
case 8: // 加月的
w += 31;
case 7: // 加月的
w += 30;
case 6: // 加月的
w += 31;
case 5: // 加月的
w += 30;
case 4: // 加月的
w += 31;
case 3: // 加月的
if(leapYear(year))
w += 29;
else
w += 28;
case 2: // 加月的天
w += 31;
case 1: // 1月不加了
;
}
// 得到-6,其中为星期天
w = w % 7;
// 调整星期天
if(w == 0)
w = 7;
return w;
}
// 请在下面补充代码,实现函数printMonth
/*************** Begin **************/
void printMonth(int year,int month){
int week_n = whatDay(year,month);
int day_month;
if(month == 12 || month == 10 || month == 8 || month == 7 || month == 5 || month == 3 || month == 1)
day_month =31;
if(month == 11 || month == 9 || month == 6 || month == 4)
day_month = 30;
if(month == 2){
if(leapYear(year)){
day_month =29;
}
else day_month = 28;
}
printf(" 一 二 三 四 五 六 日\n");
int day = 1;
int order = week_n;
for(int j=1;j<week_n;j++){
printf("%4s"," ");
}
for(int i = 0;i<day_month;i++){
printf("%4d",day);
day++;
order++;
if(order==8){
printf("\n");
order=1;
}
}
printf("\n");
}
/*************** End **************/
int main()
{
// 年、月
int y, m;
// 输入年月
cin >> y >> m;
// 输出该年月的日历
printMonth(y,m);
return 0;
}
拆开了输出整数
任务描述
本关任务:输入一个正整数,然后把该整数的每一位数字从高位到低位顺序输出,每个数字占一行。
例如:输入:123,程序输出:
1
2
3
相关知识
对于将一个整数各位数字拆开的问题,由于该数字的位数未知,采用取余的方式也只能获得最低位,最高位很难获取(如果使用即将学习的数组,也可以实现,但不建议这样做)。而这里要求先输出最高位,所以不能用循环实现。但递归函数可以很好的解决这个问题。
递归函数
直接调用自己或通过其它函数间接调用自己的函数称为递归函数。
递归函数适合于求解递归问题,所谓递归问题:是指一类比较复杂的问题,其问题的解决又依赖于类似问题的解决,只不过后者的复杂程度或规模较原来的问题更小,而且一旦将问题的复杂程度和规模化简到足够小时,问题的解法其实非常简单。
例如,计算某个自然数 n 的阶乘的公式写为:
n!=n×(n−1)×(n−2)×…×2×1
这种阶乘的计算用如下循环结构来实现:
int factorial = 1;
for (int counter=1; counter <= n; counter ++)
{
factorial=factorial * counter;
}
也可以从另外一个角度来看待阶乘的计算,n 的阶乘可以通过递归定义为:
n!=n×(n -1)! (n>1)
n!=1 (n=1)
例如为了计算5!,要先计算出4!,要计算4!,又要先计算出3!,要计算3!,则要先计算2!,而2!又需要先计算1!。
根据定义,1!为1,有了1!就可以计算2!了,有了2!的结果就可以计算出3!的值,有了3!的值就可以算出4!的值,最后可以得到5!的结果。
从上述递归计算过程可以看到,一个复杂的问题,被一个规模更小、更简单的类似的问题替代了,经过逐步分解,最后得到了一个规模非常小、非常简单的、更容易解决的类似的问题,将该问题解决后,再逐层解决上一级问题,最后解决了较复杂的原始的问题。
递归性质
对于递归函数而言其调用过程与一般函数的调用过程完全一样,同时函数的递归调用具有下面两个性质:
- 函数调用时,调用程序在函数调用处暂时挂起,程序控制离开调用程序转入被调用函数执行,只有当被调用函数执行完后,才返回到调用程序的调用处继续向下执行,所以调用函数一定要在被调用函数执行完成后才能继续执行并结束。由于递归调用中调用函数和被调用函数可能是同一个函数,同一个函数的两次调用可以理解为函数的两个不同的副本;
- **一个函数被调用,系统会为该函数的这次执行分配存储空间,包括为该函数的形式参数和局部变量分配单元。**因此,递归函数执行时,在某一时刻,计算机内可能有该递归函数的多个活动的同时存在,每个活动(即函数的每次调用执行)都有自己对应的存储空间,也就是说,函数的形式参数和局部变量,在函数的每次调用执行中都有不同的存储空间。
使用递归函数求阶乘的程序为:
#include <iostream>
using namespace std;
// 递归函数 fac: 求 num 的阶乘
// 返回值:num 的阶乘
int fac(int num)
{
if (num <= 1) // 递归终止,直接给出结果
return 1;
else // 递归调用
return (num * fac(num - 1));
}
int main()
{
int i;
cin >> i;
cout << i << "! = " << fac(i) << endl;
return 0;
}
编程要求
在右侧编辑器中的Begin-End之间补充代码,输入一个正整数(数据由平台提供,已在主函数中获取),将该整数的每一位数字从高位到低位顺序输出,且每个数字占一行。
提示
可以设计一个递归函数 fun,其参数为 n,功能为顺序输出 n 的各位数字。
函数实现时可以将 n 拆分为两个部分:n%10和n/10,即最低位和剩下的部分;程序语义应该为先顺序输出n/10的各位,然后输出n%10。顺序输出n/10的各位可以通过调用自身来实现(函数 fun 的功能就是这个),输出n%10则直接输出就好了。
#include <iostream>
using namespace std;
// 递归函数splitNum:顺序输出n的各位数字,每个数字占一行
// 返回值:无
void splitNum(unsigned int n)
{
// 请在这里补充代码,实现递归函数splitNum
/********** Begin *********/
if(n<10)
cout << n<<endl;
else
{
splitNum(n/10);
cout << n%10 << endl;
}
/********** End **********/
}
int main()
{
unsigned n;
cin >> n; // 输入正整数n
splitNum(n); // 调用splitNum函数,顺序输出n的各位数字
return 0;
}