函数scanf

本节介绍输入函数 scanf 的用法。scanf 和 printf 一样,非常重要,而且用得非常多,所以一定要掌握。

概述

scanf 的功能用一句话来概括就是“通过键盘给程序中的变量赋值”。该函数的原型为:

# include <stdio.h>
int scanf(const char *format, ...);

它有两种用法,或者说有两种格式。

1) scanf("输入控制符", 输入参数);

功能:将从键盘输入的字符转化为“输入控制符”所规定格式的数据,然后存入以输入参数的值为地址的变量中。

下面给大家举个例子:

  1. #include <stdio.h>
  2. int main(void)
  3. {
  4. int i;
  5. i = 10;
  6. printf("i = %d\n", i);
  7. return 0;
  8. }

我们前面都是像这样写的,即直接给变量 i 赋一个值。但是这样写功能比较弱,因为这个值就变成一个“死值”了,它只能是 10,不可能是其他值,除非在程序中修改。很多时候我们希望这个值不是由程序员在程序中指定的,而是在程序运行的过程中由用户从键盘输入的。用户输入多少,变量i就是多少,这样程序的功能就更加灵活了。

那么如何实现在程序运行的过程中由用户从键盘输出值呢?用 scanf 即可实现:

  1. # include <stdio.h>
  2. int main(void)
  3. {
  4. int i;
  5. scanf("%d", &i); //&i 表示变量 i 的地址,&是取地址符
  6. printf("i = %d\n", i);
  7. return 0;
  8. }

“输入控制符”和“输出控制符”是一模一样的。比如一个整型数据,通过 printf 输出时用%d输出,通过 scanf 输入时同样是用%d

要想将程序中的 scanf 行弄明白,首先要清楚的是:我们从键盘输入的全部都是字符。比如从键盘输入 123,它表示的并不是数字 123,而是字符 '1'、字符 '2' 和字符 '3'。这是为什么呢?

操作系统内核就是这样运作的。操作系统在接收键盘数据时都将它当成字符来接收的。这时就需要用“输入控制符”将它转化一下。%d的含义就是要将从键盘输入的这些合法的字符转化成一个十进制数字。经过 %d 转化完之后,字符 123 就是数字 123 了。

第二个要弄清楚的是:&是一个取地址运算符,&后面加变量名表示“该变量的地址”,所以&i就表示变量 i 的地址。&i又称为“取地址i”,就相当于将数据存入以变量 i 的地址为地址的变量中。

那么以变量 i 的地址为地址的变量是哪个变量呢?就是变量 i。所以程序中 scanf 的结果就把值 123 放到变量i中。

综上所述,scanf 语句的意思就是:从键盘上输入字符 123,然后%d将这三个字符转化成十进制数 123,最后通过“取地址 i”找到变量 i 的地址,再将数字 123 放到以变量 i 的地址为地址的变量中,即变量 i 中,所以最终的输出结果就是i=123

注意,为什么不直接说“放到变量i中”?而是说“放到以变量 i 的地址为地址的变量中”?因为这么说虽然很绕口,但是能加强对 &i 的理解,这么说更能表达 &i 的本质和内涵。很多人在学习 scanf 的时候,经常将“变量 i”和“变量 i 的地址”混淆,从而思维开始混乱,等深刻了解 &i 的含义之后就可以不那么说了。

以上是 scanf 的最简单用法,也是最常用、最基本、最重要的用法。这样通过 scanf 就可以在程序运行的过程中由用户来指定变量 i 的值,这与在程序中赋值相比较功能更强大。

2) scanf("输入控制符非输入控制符", 输入参数);

这种用法几乎是不用的,也建议你们永远都不要用。但是经常有人问,为什么 printf 中可以有“非输出控制符”,而 scanf 中就不可以有“非输入控制符”。事实上不是不可以有,而是没有必要!下面来看一个程序:

  1. # include <stdio.h>
  2. int main(void)
  3. {
  4. int i;
  5. scanf("i = %d", &i);
  6. printf("i = %d\n", i);
  7. return 0;
  8. }

在 printf 中,所有的“非输出控制符”都要原样输出。同样,在 scanf 中,所有的“非输入控制符”都要原样输入。所以在输入的时候i=必须要原样输入。比如要从键盘给变量 i 赋值 123,那么必须要输入i=123才正确,少一个都不行,否则就是错误。

所以 scanf 中%d后面也没有必要加\n,因为在 scanf 中\n不起换行的作用。它不但什么作用都没有,你还要原样将它输入一遍。

所以在 scanf 的使用中一定要记住:双引号内永远都不要加“非输入控制符”。除了“输入控制符”之外,什么都不要加,否则就是自找麻烦。而且对于用户而言,肯定是输入越简单越好。

一次给多个变量赋值:

  1. # include <stdio.h>
  2. int main(void)
  3. {
  4. int i, j;
  5. scanf("%d%d", &i, &j);
  6. printf("i = %d, j = %d\n", i, j);
  7. return 0;
  8. }

首先,scanf 中双引号内除了“输入控制符”之外不要加任何“非输入控制符”。通过键盘给多个变量赋值与给一个变量赋值其实是一样的。比如给两个变量赋值就写两个 %d,然后“输入参数”中对应写上两个“取地址变量”;给三个变量赋值就写三个 %d,然后“输入参数”中对应写上三个“取地址变量”……

但是需要注意的是,虽然 scanf 中没有加任何“非输入控制符”,但是从键盘输入数据时,给多个变量赋的值之间一定要用空格、回车或者 Tab 键隔开,用以区分是给不同变量赋的值。而且空格、回车或 Tab 键的数量不限,只要有就行。一般都使用一个空格。

此外强调一点:当用 scanf 从键盘给多个变量赋值时,scanf 中双引号内多个“输入控制符”之间千万不要加逗号,

有些人觉得在输入的时候可以用逗号分隔,所以就在“输入控制符”之间用逗号隔开。这样做从程序的角度确实是可以的,但是建议大家不要这样做。在实际编程中这种写法是绝对不允许的,原因有两个:

  • 首先逗号要原样输入的,有几个就要输入几个,少一个或多一个都不行;
  • 其次,也是最主要的原因就是输入法的问题,在 scanf 中是在英文输入法下写的逗号,那么输入的时候如果是中文输入法下的逗号那也是错的。所以用逗号很容易出错。


最后再次强调:scanf“输入参数”的取地址符&千万不要忘了。这是初学者经常犯的错误。而 printf 中的“输出参数”是不带取地址符的,不要混淆了。

使用scanf的注意事项

1) 参数的个数一定要对应


在前面介绍 printf 时说过,“输出控制符”和“输出参数”无论在“顺序上”还是在“个数上”一定要一一对应。这句话同样对 scanf 有效,即“输入控制符”和“输入参数”无论在“顺序上”还是在“个数上”一定要一一对应。比如:

  1. # include <stdio.h>
  2. int main(void)
  3. {
  4. char ch;
  5. int i;
  6. scanf("%c%d", &ch);
  7. printf("ch = %c, i = %d\n", ch, i);
  8. return 0;
  9. }

在 VC++ 6.0 中的输出结果是:
a 6
ch = a, i = -858993460

这种错误是初学者经常犯的,由于粗心大意,少写一个参数。更严重的是,这种错误在编译的时候不会报错。printf 也是一样,即使“输出参数”少写了也不会报错,但从程序的功能上讲这么写就是错的。所以在编程的时候一定要避免这种错误的发生。

程序中为什么 i=–858993460?这个在《为什么要初始化变量》中讲过,当变量没有初始化的时候就会输出这个值。

在后面会讲到 scanf 是缓冲输入的,也就是说从键盘输入的数据都会先存放在内存中的一个缓冲区。只有按回车键后 scanf 才会进入这个缓冲区和取数据,所取数据的个数取决于 scanf 中“输入参数”的个数。所以上述程序中 scanf 只有一个输入参数,因此按回车键后 scanf 只会取一个数据。所以变量 ch 有数据,而变量 i 没有数据,没有数据就是没有初始化,输出就是 –858993460。

2) 输入的数据类型一定要与所需要的数据类型一致

在 printf 中,“输出控制符”的类型可以与数据的类型不一致,如:

  1. # include <stdio.h>
  2. int main(void)
  3. {
  4. int i = 97;
  5. printf("i = %c\n", i);
  6. return 0;
  7. }

在 VC++ 6.0 中的输出结果是:
i = a

但是在 scanf 中,对于从键盘输入的数据的类型、scanf 中“输入控制符”的类型、变量所定义的类型,这三个类型一定要一致,否则就是错的。虽然编译的时候不会报错,但从程序功能的角度讲就是错的,则无法实现我们需要的功能。比如:

  1. # include <stdio.h>
  2. int main(void)
  3. {
  4. int i;
  5. scanf("%d", &i);
  6. printf("i = %d\n", i);
  7. return 0;
  8. }

在 VC++ 6.0 中的输出结果是:
a
i = -858993460

输出 –858993460 表示变量未初始化。为什么输入 a,变量 i 却显示未初始化呢?

在 scanf 中,从键盘输入的一切数据,不管是数字、字母,还是空格、回车、Tab 等字符,都会被当作数据存入缓冲区。存储的顺序是先输入的排前面,后输入的依次往后排。按回车键的时候 scanf 开始进入缓冲区取数据,从前往后依次取。

但 scanf 中 %d 只识别“十进制整数”。对 %d 而言,空格、回车、Tab 键都是区分数据与数据的分隔符。当 scanf 进入缓冲区中取数据的时候,如果 %d 遇到空格、回车、Tab 键,那么它并不取用,而是跳过继续往后取后面的数据,直到取到“十进制整数”为止。对于被跳过和取出的数据,系统会将它从缓冲区中释放掉。未被跳过或取出的数据,系统会将它一直放在缓冲区中,直到下一个 scanf 来获取。

但是如果 %d 遇到字母,那么它不会跳过也不会取用,而是直接从缓冲区跳出。所以上面这个程序,虽然 scanf 进入缓冲区了,但用户输入的是字母 a,所以它什么都没取到就出来了,而变量 i 没有值,即未初始化,所以输出就是 –858993460。

但如果将 %d 换成 %c,那么任何数据都会被当作一个字符,不管是数字还是空格、回车、Tab 键它都会取回。

不但如此,前面讲过,你从键盘输入 123,这个不是数字 123,而是字符 '1'、字符 '2' 和字符 '3',它们依次排列在缓冲区中。因为每个字符变量 char 只能放一个字符。所以输入“123”之后按回车,scanf 开始进入缓冲区,按照次序,先取字符 '1',如果还要取就再取字符 '2',以此类推。

如果都取完了还有 scanf 要取数据,那么用户就需要再输入。先写一个程序看一下:

  1. # include <stdio.h>
  2. int main(void)
  3. {
  4. char i, j, k;
  5. scanf("%c%c%c", &i, &j, &k);
  6. printf("i = %c, j = %c, k = %c\n", i, j, k);
  7. return 0;
  8. }

在 VC++ 6.0 中的输出结果是:
123
i = 1, j = 2, k = 3

从这个程序中我们看出,就单纯地输入 123,不加任何空格,按回车键之后就同我们所讲的一样,分别将字符 '1'、字符 '2' 和字符 '3' 赋给字符变量 i、j 和 k。

但是需要提醒大家注意的是,在之前程序中,因为 scanf 是 %d,所以 a 没有被取出来,还在缓冲区中。当遇到下一个 scanf 是 %c 时它就会被取出来。但是如果一直没有出现 %c,那么这时就会出现一个问题:scanf怎么取十进制整数?即使使用 %d,但是由于字符 a “挡”在最前面,scanf 进去先碰到的总是 a,也就无法取到它后面的整数,所以必须先将 a“弄走”。这就牵涉到“清空输入缓冲区”的概念,这个稍后再讲。

3) 在使用 scanf 之前使用 printf 提示输入

大家想一想,前面写的 scanf 程序有没有不足的地方?

程序写好之后,编译、链接、执行,然后弹出黑窗口,出现一个光标在那不停地闪。对于编写程序的人来说他知道要输入什么,但是对于用户而言,用户怎么知道是什么意思呢?所以之前的程序都缺少提示信息!因此在使用scanf之前,最好先用printf提示用户以什么样的方式输入,这样可以大大提高代码的质量。看看下面这个程序:

  1. # include <stdio.h>
  2. int main(void)
  3. {
  4. int i, j;
  5. printf("请输入两个值,中间以空格分隔:");
  6. scanf("%d%d", &i, &j);
  7. printf("i = %d, j = %d\n", i, j);
  8. return 0;
  9. }

这样在执行的时候,用户一看就知道是要输入两个值,然后中间用空格隔开。所以这样写就更人性化、智能化了。

小结

scanf 的使用看似细节繁杂,但使用起来非常简单。就目前而言,只要掌握以下五点:

  1. 在 scanf 的“输入参数”中,变量前面的取地址符&不要忘记。
  2. scanf 中双引号内,除了“输入控制符”外什么都不要写。
  3. “输出控制符”和“输出参数”无论在“顺序上”还是在“个数上”一定要一一对应。
  4. “输入控制符”的类型和变量所定义的类型一定要一致。对于从键盘输入的数据的类型,数据是用户输入的,程序员是无法决定的,所以在写程序时要考虑容错处理,这个稍后再讲。
  5. 使用 scanf 之前先用 printf 提示输入。


只要掌握了以上五点,scanf 的使用基本上就没什么问题了。至于其他注意点,到后面讲数组和指针的时候再介绍。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/374892.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C语言中定义变量位置

C标准的问题 C89规定&#xff0c;在任何执行语句之前&#xff0c;在块的开头声明所有局部变量。 即应该如下&#xff1a;定义变量只能在最开始&#xff0c;scanf等执行语句之前 int N 0;double sum 0;scanf("%d",&N);在C99以及C中则没有这个限制&#xff0c;即…

Java中的低GC:使用原语而不是包装器

总览 有两个很好的理由在可能的地方使用原语而不是包装器。 明晰。 通过使用原语&#xff0c;您可以清楚地知道null值是不合适的。 性能。 使用原语通常更快。 清晰度通常比性能更重要&#xff0c;并且是使用它们的最佳理由。 但是&#xff0c;本文讨论了使用包装程序对性能…

C# 连接Oracle数据库以及一些简单的操作

拖了很久今天终于在博客园写了自己第一篇随笔&#xff1a; 话不多说&#xff0c;我们直接进入正题&#xff1a; 1.连接数据库 using (OracleConnection conn new OracleConnection("data source192.168.97.60/orcl;User Idabc;Passwordabc;")) { …

markdownb编辑器

这是H1 这是H2 这是H3 这是一个标题。 这是第一行列表项。这是第二行列表项。给出一些例子代码&#xff1a; return shell_exec("echo $input | $markdown_script"); 转载于:https://www.cnblogs.com/xcl461330197/p/4605163.html

Java Secret:使用枚举构建状态机

总览 Java中的枚举比许多其他语言更强大&#xff0c;这可能导致令人惊讶的用途。 在本文中&#xff0c;我概述了Java 枚举的一些单独功能&#xff0c;并将它们组合在一起形成一个状态机。 单例和实用程序类的枚举 您可以非常简单地将枚举用作Singleton或Utility。 enum Si…

数组部分笔记

对于数组的初始化需要注意以下几点&#xff1a; 可以只给部分元素赋值。当{ }中值的个数少于元素个数时&#xff0c;只给前面部分元素赋值。例如&#xff1a; int a[10]{12, 19, 22 , 993, 344};表示只给 a[0]~a[4] 5个元素赋值&#xff0c;而后面 5 个元素自动初始化为 0。 …

指向函数的指针

指向函数的指针变量的一般形式为&#xff1a;数据类型 &#xff08;*指针变量名&#xff09;&#xff08;函数参数表列&#xff09;&#xff1b;如&#xff1a; int (*p)(int ,int );1、int (*p)(int ,int );表示定义一个指向函数的指针变量p&#xff0c;它不是固定只能指向…

核心Java面试答案不正确

总览 在Internet上&#xff0c;Java面试问题和答案从一个网站复制到另一个网站。 这可能意味着错误或过时的答案可能永远不会得到纠正。 这是一些不太正确或已经过时的问题和答案。 即是Java 5.0之前的版本。 每个提供的问题后都有两个部分。 斜体的第一部分指示答案不完整/错…

138.括号序列(区间型DP)

3657 括号序列 时间限制: 1 s空间限制: 256000 KB题目等级 : 黄金 Gold题解查看运行结果题目描述 Description我们用以下规则定义一个合法的括号序列&#xff1a; &#xff08;1&#xff09;空序列是合法的 &#xff08;2&#xff09;假如S是一个合法的序列&#xff0c;则 (S) …

C# 执行批处理文件(*.bat)的方法代码

代码如下:static void Main(string[] args){Process proc null;try{ string targetDir string.Format("D:\adapters\setup");//this is where mybatch.bat liesproc new Process();proc.StartInfo.WorkingDirectory targetDir;proc.StartInfo.Fil…

C语言空格怎么表示

1.直接敲空格就行&#xff0c;或者使用ASCII码值赋值为32。 空格没有转义字符。 printf("12%c45 58",32);输出 12 45 582.合法转义字符如下&#xff1a;\a 响铃(BEL) 、\b 退格(BS)、\f 换页(FF)、\n 换行(LF)、\r 回车(CR)、\t 水平制表(HT)、\v 垂直制表(VT) 0、…

Tomcat中的零停机部署(和回滚); 演练和清单

亲爱的大家&#xff0c; 如果您认为Tomcat不能再进步&#xff0c;那您就错了。 Tomcat 7引入了所谓的并行部署 。 这是由SpringSource / VMWare贡献的。 简而言之&#xff0c;并行部署是一种能够并行部署一个以上版本的Web应用程序的功能&#xff0c;使所有版本都可以在完全相…

javaweb 学习资源

http://jinnianshilongnian.iteye.com/category/231099转载于:https://www.cnblogs.com/sishahu/p/5368018.html

HDU 1863 畅通工程(最小生成树,prim)

题意&#xff1a; 给出图的边和点数&#xff0c;要求最小生成树的代价&#xff0c;注&#xff1a;有些点之间是不可达的&#xff0c;也就是可能有多个连通图。比如4个点&#xff0c;2条边:1-2&#xff0c;3-4。 思路&#xff1a; 如果不能连通所有的点&#xff0c;就输出‘?’…

2000年不算在21世纪

练习3-5 输出闰年 (15 分) 输出21世纪中截止某个年份以来的所有闰年年份。注意&#xff1a;闰年的判别条件是该年年份能被4整除但不能被100整除、或者能被400整除。 想当然地以为21世纪是2000~2099&#xff0c;当然没有通过 if(N > 2000&&N < 2099){for(int i …

使用迭代器时如何避免ConcurrentModificationException

Java Collection类是快速失败的&#xff0c;这意味着如果在使用迭代器遍历某个线程的同时更改了Collection&#xff0c;则iterator.next&#xff08;&#xff09;将抛出ConcurrentModificationException 。 在多线程以及单线程环境下都可能出现这种情况。 让我们通过以下示例探…

Sublime Text 3实用快捷键大全

下面是我通过网上教程和文本资料学习sublime Text3时收集的一些实用功能和常用快捷键&#xff0c;现在分享出来&#xff0c;如果还有其它的好用的功能可以在下面留言&#xff0c;以便互相学习和交流&#xff0c;谢谢&#xff01;。 选择类 CtrlD 选中光标所占的文本&#xff0c…

Tomcat中配置JNDI数据源

准备工作&#xff1a; Tomcat版本&#xff1a;tomcat6.0以上 下例中均使用MySQL数据库 将对应数据源的jar包和MySQL的驱动包拷贝至tomcat的lib文件夹下 一、全局数据源 1步骤一&#xff1a;配置 在tomcat下的conf/server.xml的GlobalNamingResources节点标签中增加如下配置&…

练习3-8 查询水果价格 (15 分)

练习3-8 查询水果价格 (15 分) 给定四种水果&#xff0c;分别是苹果&#xff08;apple&#xff09;、梨&#xff08;pear&#xff09;、桔子&#xff08;orange&#xff09;、葡萄&#xff08;grape&#xff09;&#xff0c;单价分别对应为3.00元/公斤、2.50元/公斤、4.10元/公…

JavaFX 2.0 beta示例应用程序和思考

我有一段时间回过头来玩JavaFX&#xff0c;并且在使用该语言方面有好有坏的经验。 随着JavaFX 2.0 beta的发布&#xff0c;我想尝试一下。 在这里&#xff0c;我开发了一个简单的地址解析应用程序&#xff0c;该应用程序将使用Google地址编码API来获取地址并提供该位置的纬度-经…