C/C++误区四:char c = getchar();
许多初学者都习惯用 char 型变量接收 getchar、getc,fgetc 等函数的返回值,其实这么做是不对的,并且隐含着足以 致命的错误 。getchar 等函数的返回值类型都是 int 型,当这些函数 读取出错 或者 读完文件后 ,会返回 EOF 。EOF 是一个宏,标准规定它的值必须是一个 int 型的负数常量 。通常编译器都会把 EOF 定义为 -1 。问题就出在这里,使用 char 型变量接收 getchar 等函数的返回值会导致对 EOF 的辨认出错,或者错把好的数据误认为是 EOF,或者把 EOF 误认为是好的数据。例如:
int c; /* 正确。应该使用 int 型变量接收 fgetc 的返回值 */
while ( (c = fgetc(fp)) != EOF )
{
putchar(c);
}
如上例所示,我们很多时候都需要先用一个变量接收 fgetc 等函数的返回值,然后再用这个变量和 EOF 比较,判断是否已经读完文件。上面这个例子是正确的,把 c 定义为 int 型保证了它能正确接收 fgetc 返回的 EOF,从而保证了这个比较的正确性。但是,如果把 c 定义为 char 型,则会导致意想不到的后果。
首先,因为 fgetc 等函数的返回值是 int 型的,当赋值给 char 型变量时,会发生降级,从而导致数据截断。例如:
---------------------------------
| 十进制 | int | char |
|--------|--------------|-------|
| 10 | 00 00 00 0A | 0A |
| -1 | FF FF FF FF | FF |
| -2 | FF FF FF FE | FE |
---------------------------------
在此,我们假设 int 和 char 分别是 32 位和 8 位的。由上表可得,从 int 型到 char 型,损失了 3 个字节的数据。而当我们要拿 char 型和 int 型比较的时候,char 型会自动升级为 int 型。char 型升级为 int 型后的值会因为它到底是 signed char 还是 unsigned char 而有所不同。不幸的是,如果我们没有使用 signed 或者 unsigned 来修饰 char,那么我们无从知晓 char 到底是指 unsigned char 还是指 signed char,因为这是由编译器决定的。不过,无论 char 是 signed 的也好,unsigned 的也罢,都不能改变使用 char 型变量接收 fgetc 等函数的返回值是错误的这个事实。唯一能改变的是该错误导致的后果。前面我们说了,char 型和 int 型比较时,char 会自动升级为 int,下面我们来看看 signed char 和 unsigned char 在转换成 int 后,它们的值有什么不同:
---------------------------------------
| char | unsigned | signed |
|-------|---------------|-------------|
| 10 | 00 00 00 0A | 00 00 00 0A |
| FF | 00 00 00 FF | FF FF FF FF |
| FE | 00 00 00 FE | FF FF FF FE |
---------------------------------------
由上表可知,当 char 是 unsigned 的时候,其转换为 int 后的值是正数。也就是说,假如我们把 c 定义为 char 型变量,而编译器默认 char 为 unsigned char,那么以下表达式将永远成立。
(c = fgetc(fp)) != EOF /* c 的值永远为正数,而标准规定 EOF 为负数 */
也就是说以下循环是一个死循环。
while ( (c = fgetc(fp)) != EOF )
{
putchar(c);
}
读到这里,可能有些读者朋友会说:“那么我明确把 c 定义为 signed char 型的就没问题了吧!”很遗憾,就算把 c 定义为 signed char,仍然是错误的。假设 fgetc 等函数读到一个字节的值为 FF,那么返回值就是 00 00 00 FF。把这个值赋值给 c 后, c 的值变成 FF。然后 c 的值为了和 EOF 比较,会自动升级为 int 型的值,也就是 FF FF FF FF。从而导致以下表达式不成立。
(c = fgetc(fp)) != EOF /* 读到值为 FF 的字符,误认为 EOF */
也就是说以下循环在没有读完文件的情况下提前退出。
while ( (c = fgetc(fp)) != EOF )
{
putchar(c);
}
综上所述,使用 char 型变量接收 fgetc 等函数的返回值是错误的,我们必须使用 int 型变量接收这些函数的返回值,然后判断接收到的值是否 EOF。只有判断发现该返回值并非 EOF,我们才可以把该值赋值给 char 型变量。
同理,C++ 中,用 char 型变量接收 cin.get() 的返回值也是错误的。不过,把 char 型变量当作参数传递给 cin.get 则是正确的。例如:
char c = cin.get(); // 错误,理由同上
char c;
cin.get(c); // 正确
C/C++误区四:char c = getchar();的更多相关文章
- codevs1004四子连棋[BFS 哈希]
1004 四子连棋 时间限制: 1 s 空间限制: 128000 KB 题目等级 : 黄金 Gold 题目描述 Description 在一个4*4的棋盘上摆放了14颗棋子,其中有7颗 ...
- COJ966 WZJ的数据结构(负三十四)
WZJ的数据结构(负三十四) 难度级别:C: 运行时间限制:20000ms: 运行空间限制:262144KB: 代码长度限制:2000000B 试题描述 给一棵n个节点的树,请对于形如"u ...
- COJ986 WZJ的数据结构(负十四)
WZJ的数据结构(负十四) 难度级别:D: 运行时间限制:6000ms: 运行空间限制:262144KB: 代码长度限制:2000000B 试题描述 请你设计一个数据结构,完成以下功能: 给定一个大小 ...
- COJ 0260 HDNOIP201204四个国王
HDNOIP201204四个国王 难度级别:A: 运行时间限制:1000ms: 运行空间限制:51200KB: 代码长度限制:2000000B 试题描述 在N*M的棋盘上摆国际象棋中的“国王”.如果两 ...
- NOIP2015普及组第四题推销员
好久没有写博客了,今天再写一篇.还是先看题: 试题描述 阿明是一名推销员,他奉命到螺丝街推销他们公司的产品.螺丝街是一条死胡同,出口与入口是同一个,街道的一侧是围墙,另一侧是住户.螺丝街一共有 N 家 ...
- bzoj3796(后缀数组)(SA四连)
bzoj3796Mushroom追妹纸 题目描述 Mushroom最近看上了一个漂亮妹纸.他选择一种非常经典的手段来表达自己的心意——写情书.考虑到自己的表达能力,Mushroom决定不手写情书.他从 ...
- 1111: [POI2007]四进制的天平Wag
1111: [POI2007]四进制的天平Wag 链接 题意: 用一些四进制数,相减得到给定的数,四进制数的数量应该尽量少,满足最少的条件下,求方案数. 分析: 这道题拖了好久啊. 参考Claris的 ...
- bzoj 1111 [POI2007]四进制的天平Wag 数位Dp
1111: [POI2007]四进制的天平Wag Time Limit: 10 Sec Memory Limit: 162 MBSubmit: 302 Solved: 201[Submit][St ...
- CCF-NOIP-2018 提高组(复赛) 模拟试题(四)
T1 贪吃蛇 [问题描述] 贪吃蛇是一个好玩的游戏.在本题中,你需要对这个游戏进行模拟. 这个游戏在一个 \(n\) 行 \(m\) 列的二维棋盘上进行. 我们用 \((x, y)\) 来表示第 \( ...
随机推荐
- 利用内存结构及多线程优化多图片下载(IOS篇)
利用内存结构及多线程优化多图片下载(IOS篇) 前言 下载地址, 后续发布, 请继续关注本blog 在IOS中,我们常常遇到多图片下载的问题.最简单的解决方案是直接利用别人写好的框架.但是这如同练武, ...
- Openstack no valid hot
错误: 创建实例 "ce" 失败: 请稍后再试 [错误: No valid host was found. ].
- 7款纯CSS3实现的炫酷动画应用|慕课网只学有用的!
关于我们 | 时尚廊 ♦ 时尚廊,中国大陆地区首家以"Lounge"为概念的艺文空间 ♦ 7款纯CSS3实现的炫酷动画应用|慕课网只学有用的! 7款纯CSS3实现的炫酷动画应用
- VS 2012 显示Link的参数
VC 通过Link将cl编译出来的.obj文件链接到一起.不过默认设置还是看不到究竟是怎么做的.需要如下设置: 右键点击工程,选择Properties菜单,然后选择左边的Linker->Gene ...
- JS nodeType返回类型
JS nodeType返回类型 前几天朋友正好问道 这个 js的nodeType是个什么概念(做浏览器底层的)正好遇到这篇文章可以向大家解释下 将HTML DOM中几个容易常用的属性做下记录: nod ...
- Linux shell编程 4 ---- shell中的循环
1 for循环 1 for语句的结构 for variable in values; do statement done 2 for循环通常是用来处理一组值,这组值可以是任意的字符串的集合 3 for ...
- LDA-线性判别分析(三)
本来是要调研 Latent Dirichlet Allocation 的那个 LDA 的, 没想到查到很多关于 Linear Discriminant Analysis 这个 LDA 的资料.初步看了 ...
- [Hapi.js] Logging with good and good-console
hapi doesn't ship with logging support baked in. Luckily, hapi's rich plugin ecosystem includes ever ...
- Linq的查询操作符
Linq有表达式语法和调用方法的语法.两者是可以结合使用,通常情况下也都是结合使用.表达式语法看上去比较清晰而调用方法的语法实现的功能更多,在此文章中介绍的是表达式语法.方法语法可以看System.L ...
- a^b的前n位数
假设我们现在需要知道 ab 的后 n 位数或前 n 位数,简单直观的做法就是求出 ab 的值,然后在分别取前 n位或后 n位,不过在 a,b很大的情况下显然是无法存储的.所以,直接求是不可能的了. ...