scanf函数,想说输入不容易!----小话c语言(3)

来源:互联网 发布:奥尼尔04总决赛数据 编辑:程序博客网 时间:2024/05/20 05:05

Q: 前面是关于输出的,给个关于输入的代码吧。

A: 如下:

#include <stdio.h>int main(){    int i;    scanf("%d", &i);     printf("%d", i);     return 0;}


Q:%d格式和printf中的都表示整形是吧,为什么后面用了&i,用i的地址呢?

不能直接用下面的代码吗?

scanf("%d", i);

A: 这是因为,scanf是一个函数调用,如果仅仅将i以值的形式传进去,那么scanf函数最终不会也没有能力修改对于它来说是外部的i.

总之,原因在于c语言函数调用参数的值传递原理。

对于char *字符串,也同样是这样的原理。

char buf[32];scanf("%s", buf);

你会发现这里的buf也是地址。


Q:说说这个代码的运行效果吧。

A: 暂且把开头那个代码保存为scanf.c.然后简单编译一下:

[Mac-10.7.1 Lion  Intel-based]

gcc  scanf.c

(因为笔者习惯在苹果系统下面编程,可以看到上面的提示Mac-10.7.1表示在mac系统10.7.1下面;

当然,因为某些场合可能需要切换到windows或者ubuntu,都会具体显示各个平台,以保证读者测试代码的正确性。)

gcc版本是:


Q: 为什么gcc scanf.c执行后什么都没输出?

A: gcc编译源代码,如果什么都没输出,那么说明已经正确编译成功了。

可以看到,使用ls  -l  | grep a.out 命令可以看到同目录下面已经有了a.out这个文件,这也是用上面的编译命令默认编译得到的可执行文件名。


Q: 看看运行效果吧。

A:  在命令行下输入./a.out ,结果如下:

可以看到,输入完./a.out后回车,命令行进入了等待状态,输入100后回车,输出了100

Q: 怎么输出的100和它后面的命令行提示符连到一起了?

A: 因为这个程序的输出的最后没有换行导致的。

修改代码:

#include <stdio.h>int main(){    int i;    scanf("%d", &i);    printf("%d\n", i);    return 0;}

重新编译,再运行:

可以看到,这次运行的输出和后面的命令行提示符已经分开了。如果查看此解释器bash的源代码,也可以看到,每次执行完应用程序,

解释器就会接着将自己的提示符再次输出,所以,如果让程序的执行结果不和它混在一起,一般都需要在代码加换行。

如果需要查看bash源代码,下面是苹果的官方开源代码网址:

http://opensource.apple.com/


Q: scanf函数有返回值吗?

A: 是的。如下是scanf函数的原型:

[Mac-10.7.1 Lion Intel-based]

int scanf(const char * __restrict, ...) __DARWIN_LDBL_COMPAT(scanf) __scanflike(1, 2);

Q: 原型中__restrict是什么意思?

A: 它是C99标准引入的,它只可以用于限定指针,并表明该指针是访问一个数据对象的唯一且初始的方式,这样编译器就可以做一些特定的优化。

举个例子吧:

int f(int * x, int * y){    *x = 0;    *y = 1;    return *x;}

上面这段代码,看起来似乎只返回了和x习惯的数据,但是不能肯定x和y究竟有没有关系,因为可能y和x是一样的;

所以,编译器不敢直接将代码编译优化成:

int f(int * x, int * y){    *x = 0;    *y = 1;    return 0;}

但是,如果加上__restrict关键字:
int f_restrict(int *__restrict x, int *__restrict y){    *x = 0;    *y = 1;    return *x;}

上面的代码表示:对x对应内容的修改只可能通过x,绝对不可能和y相关,所以编译器就可以做上面的优化了。

scanf函数原型,还有后面的__DARWIN_LDBL_COMPAT(scanf) __scanflike(1, 2)的意思,可以自己去查看,这里不介绍了。


Q: scanf函数返回值是什么意思?

A: 可以使用man scanf命令得到scanf函数的说明。


可以看到,它返回的是被成功赋值变量的个数。

也就是说,上面的代码scanf("%d", &i); 如果返回为1,则表示成功输入了变量i.

修改代码来验证:

#include <stdio.h>int main(){    int i;    int ret;    ret = scanf("%d", &i);    if(ret == 1)        printf("%d\n", i);    else        printf("input i error\n");    return 0;}

编译运行:

输入100:

重新运行,输入字符d

可以发现这里就进入了错误显示。

scanf函数的返回值能够很好地帮助我们分析需要输入的变量到底有没有被正确输入。


Q: 如果调用scanf函数进行输入,但是输入了不正确格式的数据,再次循环输入,怎么不能输入了,且一直打印错误输入?

代码是:

#include <stdio.h>int main(){    int i;    int ret;    while(1)    {        ret = scanf("%d", &i);        if(ret == 1)            printf("%d\n", i);        else            printf("input i error\n");    }    return 0;}

运行时输入一个字符d : 

会一直打印input i error不能停止,这是为什么?

A: 这个原因就在于输入缓冲区了。还记得前面说过的缓冲区的概念吗?提到了输出缓冲区,scanf同样也有一个输入缓冲区。

调用scanf, scanf就会从它的输入缓冲区中来获取对应的数据,如果获取到就会传入再继续执行,否则就会等待输入。

当输入了字符d并回车后,scanf会将d和整形匹配,结果发现是不匹配的,所以会打印input i error信息,接着再次进入循环,

但是之前的输入缓冲区中依然保留着字符d呢,所以又会继续打印错误信息,依次循环永不停止。


Q: 那应该怎么办呢?

A: 很简单,如果发现输入数据错误,那么清空输入缓冲区即可。使用rewind(stdin);即可,

修改后的代码为:

#include <stdio.h>int main(){    int i;    int ret;    while(1)    {        ret = scanf("%d", &i);        if(ret == 1)            printf("%d\n", i);        else        {            rewind(stdin);            printf("input i error\n");        }    }    return 0;}
运行:


Q: 可以使用fflush(stdin); 来清空输入缓冲区吗?

A: c语言标准并没有对此规定可行,在笔者的mac系统下此函数就无效;所以,最好不要用这个。

不过,因为不同系统都可能存在差异,就算上面用的rewind(stdin); 也许在某些系统上也无效;所以,有个

更好的建议,那就是使用如下代码:

            int ch;            while((ch = getchar()) != EOF && ch != '\n')                ;

Q: 我写了如下代码,scanf用于输入两个整形数,为什么总是有问题?

#include <stdio.h>int main(){    int i, j;    int ret;    ret = scanf("%d,%d", &i, &j);    if(ret == 2)        printf("%d %d\n", i, j);    else        printf("input i or j error\n");    return 0;}

运行:


A: 你需要注意scanf的输入格式,"%d,%d",两个整形中间是有个逗号的,但是看看你的输入,中间是个空格,并没有

逗号,所以会导致错误。所谓,格式化输入,你得符合它的格式才行。

和这个问题类似的还有:

scanf("%d\n", &i);           格式串中有\n,所以需要输入两次回车才能继续;

scanf("%2d%2d", &i, &j);     %2d指整形占用两位,所以当输入1234时, 12被赋值给i, 34被赋值给j;

float f;  scanf("%4.1f", f);       scanf的格式串中没有类似小数位数的格式,所以这样的输入经常会导致输入错误;

等等。


Q: 我想输入一个double型浮点数,怎么会不管怎么输入都得不到正确的结果?

#include <stdio.h>int main(){    double d;    scanf("%f", &d);    printf("%f\n", d);    return 0;}

运行:

A: 这个问题在于, scanf格式中的%f表示输入的是一个float类型,在笔者的平台上是4字节的,而实际上却被保存

在double类型变量中,所以它被保存到变量d的低4个字节中(intel小端),但是整个double变量的值却和低4个字节的

值根本不能直接运算,可以参考IEEE 754关于浮点数的标准。

其实,在编译过程中应该也会有警告:

解决这个问题不复杂,只需要将格式%f改为%lf即可。


Q: 写了一个代码,scanf用于输入字符,但是第二个需要输入的字符却使用不正确,为什么呢?

#include <stdio.h>int main(){    char ch1, ch2;    scanf("%c%c", &ch1, &ch2);    printf("|%c|%c|\n", ch1, ch2);    return 0;}

运行:

输入a然后回车准备输入第二个字符,但是没有进入等待输入状态,却直接输出了2个字符,且输出的不正确。

A: 其实这里,主要是因为在输入a后的回车也被当做了一个字符用于存储在b当中导致的。记得,scanf在输入整形、浮点型等

数据的时候可以忽略回车、空格、制表符等字符,但是输入格式为%c的时候就不这样了,此时它会很认真。

所以,将输入的代码改为scanf("%c\n%c", &ch1, &ch2); 即可满足你的要求。


Q: scanf函数和printf函数一样都有输入或输出的变量个数和格式中的变量个数不一致的情况吧,那么运行结果会如何呢?

A: 是的,当出现这样的不一致的时候,可能导致运行结果匪夷所思。当然,可以根据分析scanf的源代码得到不同情况下的运行结果。

比如,scanf("%d", &i, &j);   这种情况下, j不会被输入,因为格式中仅有1个,只会向i中输入,&j仅仅被当做一个参数传进来,却没有使用。

再比如, scanf("%d%d", &i);  这种情况下,多余需要被输入的整数可能导致某个内存被意外改写甚至崩溃;

当然,不管怎么样,上面这样的写法终究可能导致一些问题,不过有一点是肯定的,一个优秀的编译器会给出相应的警告信息,这样的代码

不要写在项目中,把它当做研究c语言内部机制的一种方式即可。


Q: 这个scanf函数还真有意思,到底还有多少有意思的?

A: 其实,对于输入概念来说,到底用什么格式来输入是五花八门的,在特定需求下可能产生一种别于寻常的输入方式,可能由此产生了一种

新的输入格式%XXXX.

举些例子:

类似%*d的方式表示此输入的整数被忽略;

scanf("%d%*d%d", &i, &j);            输入:  100 2 200 然后回车,100被保存在i中,2被忽略, 200被保存在j中;

 

char s[32];   scanf("%[abcd]", s);    %[abcd]表示如果输入的字符是a,b,c,d中的任何一个,那么接收,否则不接收;

如果输入  abr3dc  然后回车,那么字符串ab将被保存在s中。 这里可以看到正则表达式的影子。

查看c标准,可以发现%[abcd]也可以用%[a-d]来表示。


 使用%i可以输入整数,但是可以输入八进制或者十六进制的形式,这是它和%d作为输入整数格式的不同之处:

scanf("%i", &i);   输入012然后回车,10会被保存在i中; 如果输入0x12, 那么18会被保存在i中;


Q: 看来scanf还是很精彩的,不过要是想全面学习它的格式规则,还真是不容易。

A: 是的,c语言很底层,越是底层的语言,越是深入细节就越需要精深的知识去解释,犯错误不是c语言的错,

而是程序员的错。



xichen

2012-5-10 12:13:34