程序设计基础第四周答疑要点整理

输入不定个数字符时,不要通过换行符''判断结尾

有的同学在做E4-B《从十进制数到2421码》时,使用while((c = getchar()) != '\n')语句来实现不定个数字符输入,但提交上OJ会发现结果为TLE(超时)。这是因为OJ是通过读取测试点输入文件的方式实现输入的,题目中并没有规定以换行符'\n'作为输入的结尾,那么输入文件的最后就不一定会有'\n',当代码始终读不到'\n'时,就会一直循环执行循环体代码块,导致超时。

正确的做法是使用while ((c = getchar()) != EOF)while (scanf("%c", &c) !=EOF)

不定组输入时怎么在本地调试

当题目中要求不定组数据输入时,常用读入方式为while (scanf()!=EOF){}(其中scanf函数的括号里需要根据读入内容填入合适的格式字符串和变量序列),此时本地调试中如果要输出最终的结果,需要我们手动输入文件结束符表示输入结束,输入方式为在最后一行数据后输入回车,在下一行行首先按住ctrl键,再按下Z键,此时程序运行控制台中显示输入了^Z,然后再次输入回车,将上述内容送入自己编写的程序,这样就可以起到控制台输入文件结束符的作用。以C4-E《真实命中率》的样例输入为例:

输入样例数据操作展示:

image-20240103103822987

注意:此时输入控制台的最后一行为一个控制字符ctrl Z,是通过先按住ctrl键然后按下Z键的方式输入的,而不是^Z两个字符

结束输入后,在ctrl Z后按下回车,输出结果:

image-20240103103833175

百分数字面值不存在,要写成小数

不存在形如x%的百分数字面值,应当使用百分数对应的浮点数字面值。

也就是说,没有这样的写法:

1
double x=10%;

如需要用10%时应当使用0.1,即:

1
double x=0.1;

不要以变量作为数组的大小

固定大小的数组在声明时,可以通过赋值的方式直接初始化,例如:

1
2
int a1[100]={};	//a1是大小为100的int数组,通过赋值将数组中每个位置初始化为0
int a2[100]={1,2,3}; //a2是大小为100的int数组,通过赋值将数组中前三个位置分别初始化为1,2,3,后面自动填充0

有些同学使用变量作为数组长度,并且试图像上面一样初始化:

1
2
3
int n;
scanf("%d",&n);
int a[n]={};

但是他们发现本地上可以运行,但是提交到OJ上显示编译失败,这是为什么呢?

事实上,编译失败的提示信息是:

1
[Error] variable-sized object may not be initialized
image-20240103103846754

本地可以编译,但是提交上去却不通过,可能的原因是本地代码文件在新建后保存时默认为cpp类型,代码语法为C++,也就是说本地运行的代码实际上是C++代码。判断方式是查看当前打开代码文件的后缀名,若为.c则表示这个文件是C语言代码文件,若为.cpp则表示这个文件是C++语言代码文件:

image-20240103103859246

为了避免出现代码本地编译运行正常而OJ上CE的问题,这里要求大家在新建代码文件并保存时必须将后缀名改为.c,这样就可以在本地编译运行代码时发现上述问题。如下图所示,在“保存类型”一栏中选择C source files(*.c)选项后保存:

image-20240103103907975

另外,强烈不建议在声明数组时以变量作为数组的大小。即:数组的长度必须要在程序开始运行之前确定。

建议根据数据范围上限,直接声明一个一定满足题目所有要求的固定大小数组,然后根据其他输入数据的范围使用这个数组的一部分或全部位置。

switch-case语句的一些问题

switch-case结构中,当被switch语句判断的变量满足某个case分支的条件时就会使代码运行进入该分支。但if不同的是:在该case分支中的语句执行完后,如果没有break语句来跳出switch-case结构,那么会继续执行后面case分支中的语句。举例如下:

breakswitch-case结构及其执行结果:

image-20240103103919115

不带breakswitch-case结构及其执行结果:

image-20240103103929640

因此,我们建议:switch中的 casedefault必须以 break终止,如果存在极特殊的情况需要共用 case必须加以明确注释。

另外还发现有人在case里面用continue的,没有这样的用法,也不允许出现。

整除运算和模运算的一点性质

C99标准规定,整型除法的结果向0取整,也就是说,当对应实数除法的结果为正时,整除结果为实数除法结果向下取整,当对应实数除法的结果为负时,整除结果为实数除法结果向上取整。例如:

1
2
3
4
printf("%d", 5 / 3);	//输出结果为1
printf("%d", -5 / 3); //输出结果为-1,而不是-2
printf("%d", 5 / -3); //输出结果为-1
printf("%d", -5 / -3); //输出结果为1

模运算的两个运算数只能是整型变量,当整型变量a,b都为正数时,模运算a % b结果范围为0 ~ b-1

执行模运算时需要考虑待取模的数的符号。模运算a % b的结果并不总是非负的,当取模结果不为0时,结果的符号取决于a 的符号结果的绝对值为:a的绝对值 对 b的绝对值 取模的值。例如:

1
2
3
4
printf("%d", 5 % 3);    //输出结果为2
printf("%d", -5 % 3); //输出结果为-2
printf("%d", 5 % -3); //输出结果为2
printf("%d", -5 % -3); //输出结果为-2

unsigned int类型变量使用的一个隐蔽问题

unsigned int类型变量表示无符号整型,有的同学会认为当确保不会存入负数的时候就可以使用该类型,事实上这是不合适的。因为当unsigned int类型变量被用作和int类型变量比较时,会隐式地将int变量中存的数转换为unsigned int类型,当参与比较的int类型变量原本保存的值为负数时,这步转换会导致运行结果与预期完全不符,且很难发现问题所在。当对unsinged int类型变量的值进行修改时,如果该变量的新值为负数,同样会产生问题。例如:

1
2
3
4
unsigned int i;
for (i = 5; i >= 0; i--){
...
}

上面的代码会运行6次循环就结束吗?错了!事实上上面的代码是一个无限循环。因为unsigned int变量能表示的值中不存在负数,当i == 0时执行i--时,i变量空间中存储的二进制比特位会变成-1的补码形式,而由于变量iunsigned int类型,此时变量i的值事实上是unsigned int类型变量所能表示的最大值。所以i >= 0条件将永远满足,循环也将永远执行下去。

下面的代码输出了unsigned int类型的0减去1后得到的值的十进制与十六进制形式,也证实了上述情况:

image-20240103103947968

综上所述:使用unsigned int类型变量时,需要慎重考虑变量可能的范围。


程序设计基础第四周答疑要点整理
https://suzumiyaakizuki.github.io/2023/10/18/C4-E4答疑要点整理/
作者
SuzumiyaAkizuki
发布于
2023年10月18日
许可协议