我正在尝试自己学习C,并且我对getchar
和putchar
感到困惑:
#include <stdio.h>
int main(void)
{
char c;
printf("Enter characters : ");
while((c = getchar()) != EOF){
putchar(c);
}
return 0;
}
#include <stdio.h>
int main(void)
{
int c;
printf("Enter characters : ");
while((c = getchar()) != EOF){
putchar(c);
}
return 0;
}
C库函数int putchar(int c)
将参数char指定的字符(unsigned char)写入stdout。
C库函数int getchar(void)
从stdin获取一个字符(unsigned char)。这相当于以stdin作为参数的getc。
这是否意味着putchar()
接受int
和char
或其中任何一个,getchar()
我们应该使用int
或char
?
答案 0 :(得分:43)
TL; DR:
char c; c = getchar();
错误,破坏和错误。int c; c = getchar();
正确。这同样适用于getc
和fgetc
,如果不是这样的话,因为人们经常会读到文件的结尾。
始终将getchar
(fgetc
,getc
...)(和putchar
)的返回值最初存储到int
类型的变量中。
putchar
的参数可以是int
,char
,signed char
或unsigned char
中的任何一个;它的类型并不重要,并且所有这些都是相同的,即使一个可能导致正整数和其他负整数被传递给上面的字符,包括\200
(128)。
必须使用int
来存储getchar
和putchar
的返回值的原因是达到文件结束条件(或发生I / O错误),它们都返回宏EOF
的值,这是一个负整数常量(usually -1
)。
对于getchar
,如果返回值不是EOF
,则读取 unsigned char
零扩展为int
。也就是说,假设8位字符,返回的值可以是0
... 255
或宏EOF
的值;再次假设8位字符,没有办法将这257个不同的值压缩为256,这样就可以唯一地识别每个值。
现在,如果您将其存储到char
中,效果将取决于the character type is signed or unsigned by default!这从编译器到编译器,架构到架构各不相同。如果char
已签名且假定EOF
定义为-1
,那么输入中的 EOF
和字符'\377'
将相等到EOF
;他们会被签名延伸到(int)-1
。
另一方面,如果char
是无符号的(因为它默认存在于ARM处理器上,包括Raspberry PI systems;并且AIX too似乎是真的),那么无值可以存储在c
中,可以比较等于-1
;包括EOF
;而不是在EOF
上突破,您的代码将输出一个\377
字符。
这里的危险是,使用签名的char
,代码似乎正常工作即使它仍然可怕地被破坏 - 其中一个合法输入值被解释为EOF
。此外,C89,C99,C11不要求EOF
的值;它只说EOF
是一个负整数常数;因此,而不是-1
,它可以在特定实现上说-224
,这会导致空格像EOF
一样。
gcc
有一个开关-funsigned-char
,可用于在默认签名的平台上使char
无符号:
% cat test.c
#include <stdio.h>
int main(void)
{
char c;
printf("Enter characters : ");
while((c= getchar()) != EOF){
putchar(c);
}
return 0;
}
现在我们使用签名char
:
% gcc test.c && ./a.out
Enter characters : sfdasadfdsaf
sfdasadfdsaf
^D
%
似乎工作正常。但是使用未签名的char
:
% gcc test.c -funsigned-char && ./a.out
Enter characters : Hello world
Hello world
���������������������������^C
%
也就是说,我尝试多次按Ctrl-D
,但为每个�
打印EOF
而不是打破循环。
现在,对于已签名的char
案例,它无法区分Linux上的char
255和EOF
,打破二进制数据等等:
% gcc test.c && echo -e 'Hello world\0377And some more' | ./a.out
Enter characters : Hello world
%
只有\0377
转义的第一部分被写入stdout。
请注意,字符常量与包含无符号字符值的int
之间的比较可能无法按预期工作(例如,ISO 8859-1中的字符常量'ä'
将表示带符号的值{{1}因此,假设您编写的代码将在ISO 8859-1代码页中读取输入,直到-28
,您就可以了
'ä'
由于整数提升,所有int c;
while((c = getchar()) != EOF){
if (c == (unsigned char)'ä') {
/* ... */
}
}
值都适合char
,并会在函数调用时自动提升,因此您可以提供int
,int
中的任何一个, char
或signed char
到unsigned char
作为参数(不存储其返回值),它会按预期工作。
整数传递的实际值可能是正数甚至是负数;例如,在putchar
签名的8位字符系统中,字符常量\377
将否定;但是char
(或实际上putchar
)会将值转换为unsigned char。 C11 7.21.7.3p2:
2 fputc函数将
fputc
指定的字符(转换为unsigned char)写入stream [...]指向的输出流
(强调我的)
即。 c
将保证转换给定的fputc
,就像c
答案 1 :(得分:3)
始终使用int
来保存getchar()
中的字符EOF
常量为int
类型。如果您使用char
,则与EOF
的比较不正确。
您可以安全地将char
传递给putchar()
,因为它会自动提升为int
。
注意强>:
技术上使用char
在大多数情况下都可以使用,但是你不能拥有0xFF字符,因为类型转换会将它们解释为EOF
。要涵盖所有案例始终,请使用int
。正如@Ilja所说 - int
需要表示所有256个可能的字符值和 EOF
,这是总共257个可能的值,不能存储在{ {1}}输入。