printf 与 scanf 函数的使用
下方为本篇内容的思维导图,以便大家思维框架的构建
文章目录
1. printf
1.1 基本用法
首先 printf ( ) 的作用就是将参数文本输出到屏幕。printf 函数中,print 就是打印的意思,f 就是format 格式化的意思,连起来printf 函数的意思也就是格式化打印、输出,即可以定制输出文本的格式。
#include <stdio.h> //stdio.h 使用printf 与scanf 函数 所必须要包含的头文件
int main(void) //此处的void 就表示main 函数的参数为空的意思 直接写成int main()也是可以的 具体后面函数部分的文章会讲解
{
printf("Hello World"); //打印字符串Hello World
return 0;
}
printf 函数并不会在行尾自动添加换行符,运行结束后,光标仍停留在输出结束的地方,不会自动换行。如果我们想要光标移动的下一行的开头,可以在输出文本的结尾,添加一个换行符\n。
#include <stdio.h>
int main(void)
{
printf("Hello World\n"); //打印字符串Hello World后换到下一行
return 0;
}
如果换行符\n 出现在文本中呢?
#include <stdio.h>
int main(void)
{
printf("Hello\nWorld\n"); //打印字符串Hello后换行 再打印World 然后再换行
//等价于下方的代码
printf("Hello\n");
printf("World");
printf("\n"); //像这样直接使用printf 打印换行符\n 也是可以的
//与此处printf("\n"); 等价的代码为putchar('\n'); 就是要注意 在使用putchar 函数时要包含头文件string.h
return 0;
}
当我们使用printf 函数时,需要注意printf 函数是在标准库的头文件stdio.h 中被定义的。因此,当我们使用pirntf 函数时,我们必须要包含头文件stdio.h 。
【扩展】在ANSI C等,C语言的标准制定中,为了使得程序员更方便的编码,统一规定了标准库函数,而这些标准库函数都是放在其对应的头文件下的,而每个不同的头文件下,会集中放置同类或类似的库函数,比如:stdio.h 头文件,其中std 就是stdandard 标准的意思 而i 就是input 输入的意思 而o 就是output 输出的意思 ,因此stdio.h 也就是标准输入输出的头文件,那么自然标准输入输出函数printf 与scanf 就被包含在内了。(注意:C语言标准只是规定了标准库函数的制定,但具体库函数的实现,是由各厂商来实现的,因此不同的IDE,不同的编译器,实际起来的方式可能有所差异)
- ANSI C 也叫C90,是第一个正式的C语言标准,发布于1989年。
- IDE 就是集成开发环境的意思
1.2 占位符
printf 函数 可以在输出文本中指定占位符。
所谓 “ 占位符 ” ,就是这个符号是帮其它值占了这个位置,也就是这个位置可以由其它值代入。
//打印I have 3 apple.
#include <stdio.h>
int main(void)
{
printf("I have %d apple.\n",3); //此处占位符%d 所占的位置 就会被3 替代 然后打印I have 3 apple.
return 0;
}
上面示例中,双引号内的 I have %d apple.\n 就是输出文本,而里面的%d 就是占位符,表示这个位置要用其它值来替换。占位符的第一个字符一律为百分号%,第二个字符表示占位符的类型,%d 表示这里代入得值必须是一个整数。
printf 函数的第二个参数,就是替换占位符的值,也就是上面示例里的3 ,因此也就是3 去替换了输出文本里的%d ,为此执行后的输出结果就是 I have 3 apple.
常用的占位符除了%d 外,还有%s 表示代入的是字符串。
#include <stdio.h>
int main(void)
{
printf("Merry Christmas %s !\n","Lihua"); //打印结果为Merry Christmas Lihua !
return 0;
}
上面示例中,%s 表示代入的值是一个字符串,因此printf 函数的第二个参数,就必须是字符串,也就是Lihua 。
注意以下几点:
- printf 函数以及其它函数,如果有多个参数,那么每个参数之间都是用逗号,来隔开的。
- printf 函数 以及待会下面要提到的scanf 函数都是不定参数的函数,也就是参数个数不确定的函数,这类函数的实现是比较特殊的,当然未来我会在文件函数中讲解的fprintf、fscanf、sprintf、sscanf等都是这类的函数(这里只是提一下 大家要是不了解也没有关系哈)。
- 像上面示例中第一个双引号,表示文本输出的内容,而第二个双引号,就是当我们要写一个字符串时所必须带的,表示Lihua 为常量字符串。
当然,我们刚刚知道了printf 函数是不定参数的函数,为此输出文本里面也就可以使用多个占位符。
#include <stdio.h>
int main(void)
{
printf("%s will get a nice job in his %d.\n","Reader",23); //打印结果为 Reader will get a nice job in his 23.
return 0;
}
上面示例中,输出文本/ %s will get a nice job in his %d.\n 中有两个占位符,分别是第一个占位符%s ,与第二个占位符%d ,它们分别对应printf 函数的第二个参数"Reader" ,和第三个参数23。注意以下几点:
- 第几个占位符 是从输出文本的左边往右边数的,写参数时要一一对应上。
- 即如果有n 个占位符 那么就应该由n+1 个参数,如果参数个数少于对应的占位符,那么printf 可能会输出内存中的任意值。(在printf 中第一个参数为输出文本嘛 别忘记咯~)
- 占位符是什么类型的 那么其对应的参数就应该是什么类型的,否则会出现错误。比如:在上面的示例中 %s 应该对应一个字符串 那么它的参数就应该是一个字符串 而不能是一个字符。
1.3 占位符的列举
printf 函数的占位符有许多种类,与 C 语⾔的数据类型相对应。下⾯按照字⺟顺序,列出常⽤的占位符,⽅便查找,具体含义在后⾯章节介绍。
• %a :⼗六进制浮点数,字⺟输出为⼩写。
• %A :⼗六进制浮点数,字⺟输出为⼤写。
• %c :字符。
• %d :⼗进制整数。// int
• %e :使⽤科学计数法的浮点数,指数部分的 e 为⼩写。
• %E :使⽤科学计数法的浮点数,指数部分的 E 为⼤写。
• %i :整数,基本等同于 %d 。
• %f :⼩数(包含 float 类型和 double 类型)。//float -%f double - %lf
• %g :6个有效数字的浮点数。整数部分⼀旦超过6位,就会⾃动转为科学计数法,指数部分的 e
为⼩写。
• %G :等同于 %g ,唯⼀的区别是指数部分的 E 为⼤写。
• %hd :⼗进制 short int 类型。
• %ho :⼋进制 short int 类型。
• %hx :⼗六进制 short int 类型。
• %hu :unsigned short int 类型。
• %ld :⼗进制 long int 类型。
• %lo :⼋进制 long int 类型。
• %lx :⼗六进制 long int 类型。
• %lu :unsigned long int 类型。
• %lld :⼗进制 long long int 类型。
• %llo :⼋进制 long long int 类型。
• %llx :⼗六进制 long long int 类型。
• %llu :unsigned long long int 类型。
• %Le :科学计数法表⽰的 long double 类型浮点数。
• %Lf :long double 类型浮点数。
• %n :已输出的字符串数量。该占位符本⾝不输出,只将值存储在指定变量之中。
• %o :⼋进制整数。
• %p :指针(⽤来打印地址)。
• %s :字符串。
• %u :⽆符号整数(unsigned int)。
• %x :⼗六进制整数。
• %zd : size_t 类型。
• %% :输出⼀个百分号。
1.4 输出格式
printf 函数可以定制占位符的输出格式。
1.41 限定宽度
printf 函数允许限定占位符的最小宽度。
#include <stdio.h>
int main(void)
{
printf("%5d\n",123); //打印结果为:" 123" 在123 的前面会有两个空格(此处的双引号只是为了更好的展示出来)
//为了使这个空格更明显 我们可以这样打印
printf("*******%5d*******\n", 123); //可以把我这个代码复制下了 运行试试哦~
printf("%05d\n", 123); //此处在% 的后面加个0 就可以起到用0 替换空格的作用 也就是当位数不够时 会添加0 而再是空格
return 0;
}
上面示例中,%5d 表示这个占位符的宽度至少为5 位。如果不满5 位,对应的值前面会自动添加空格。而如果我们不希望自动添加空格,那么在% 的后面加上0 就可以做到自动添加0 了,且输出的值默认是右对齐的,即输出内容的前面会有空格;如果希望改成左对齐,在输出内容后面添加空格,可以在占位符的百分号% 的后面加一个负号 - 。
#include <stdio.h>
int main(void)
{
printf("%-5d\n",123); //打印结果为:"123 " 在123 的后面会有两个空格(此处的双引号只是为了更好的展示出来)
//同样为了使这个空格更明显 我们可以这样打印
printf("*******%-5d*******\n", 123); //注意加负号- 的位置哦~
return 0;
}
上面示例中,123的后面添加了两个空格。
【小试牛刀】那么对于
printf("****%7d\n",123456789);
这行代码的打印结果是什么呢?
(可以打在评论区 或者私信发给我 让我看看哦~)
那么对于小数(浮点数)呢?这个限定会限制所有数字的最小显示宽度
#include <stdio.h>
int main(void)
{
printf("%f\n",123.56); //打印结果为:" 123,560000" (此处的双引号只是为了更好的展示出来)
return 0;
}
上面示例中,%f 占位符表示printf 的打印格式为浮点型float ,而%12f 就表示该占位符最小的显示宽度为12 位数字,而由于浮点型的默认显示精度为小数点后6 位,所以实际打印的结果位123.450000 的前面会添加两个空格。注意以下几点:
- 浮点型的最小宽度限制 也是默认左对齐的
- 与前面相同 当我们希望其改为右对齐的时候,加个负号- 即可 (此处就不再演示了)
【理解的小提升】我们可以将一个占位符整体看待来得出最后具体的打印结果。如:
printf("*****%5d*******\n",123);
在此行代码中,%5d 就是一个占位符,而要替代进来的值,就是参数123 ,但123 仅为3 位数字,且此处没有加负号- ,因此为默认的左对齐 ,因此这整个占位符%5d 的替换结果就是" 123" (ps:此处双引号只是为了更好的演示空格的存在),然后将替换结果" 123" 放到printf 的输出文本中也就是"***** 123*******\n"
1.42 总是显示正负号
默认情况下,printf 函数不对正数显示+ 号,只对负数显示- 号,如果想要整数也显示+ 号,可以在占位符的% 后面加一个+ 。
#include <stdio.h>
int main(void)
{
printf("%+d\n",12); //打印结果为:+12
printf("%+d\n",-12); //打印结果为:-12
return 0;
}
上面的示例中,%+d 可以保证输出的数值总是带有正负号的。
1.43 限定小数位数
输出小数时,有时希望限定小数的位数。比如说:当我们希望小数点后面只保留两位数字,那么我们可以将占位符写成%.2f 的形式,即在% 后面加点. 再加希望保留的位数。
#include <stdio.h>
int main(void)
{
printf("%.3f\n",12.34); //打印结果为:12.340
printf("%.3f\n",12.3456); //打印结果为:12.345 或12.346 都是有可能的 此处的四舍五入规则在不同的编译器中可能有所区别
return 0;
}
看完了上面的示例,那在浮点数中,可不可以将其与限制最小宽度的方式相结合来使用呢?
#include <stdio.h>
int main(void)
{
printf("%8.3f\n",32.1); //打印结果为:" 32.100" 前面有两个空格 (此处双引号只是为了更好的演示)
return 0;
}
最小宽度和小数位数这两个限定值,都可以用* 代替,通过printf 函数的参数传入。
#include <stdio.h>
int main(void)
{
printf("%*.*f\n",8,3,32.1); //打印结果为:" 32.100" 前面有两个空格 (此处双引号只是为了更好的演示)
//此处将两个限制值 都通过* 传入
//等价于
printf("%8.3f\n",32.1);
return 0;
}
注意:此处的* 号并不是占位符,只是表示* 的位置可以用后面的数字替换,而%%*.*f 才是占位符。
1.44 输出部分字符串
%s 占位符是用来输出字符串的,默认是全部输出。如果只想输出开头的部分,可以用%.[m]s 指定输出的长度,其中[m] 代表一个数字,表示所要输出的长度。
#include <stdio.h>
int main(void)
{
printf("%.5s\n","hello world");//输出结果为:hello 因为. 后面数字为5 因此只打印前5个字符
return 0;
}
2. scanf
当我们有了变量,需要给变量输入值就可以使用scanf 函数,如果需要将变量的值输出在屏幕上的时候可以使用printf 函数
#include <stdio.h>
int main(void)
{
int score = 0;
printf("请输入成绩:");
scanf("%d",&score); //输入96
printf("成绩是:%d\n",score);
return 0;
}
运行截图:
画图演示:
(此图为鹏哥上课所画)
【扩展】像我们电脑、平板电脑等机器中,stdin stdout 标准输入 输出流默认都是开着的,与此同时默认打开的流,还有stderr 标准错误流,其实stdin 标准输入流,也就是我们从键盘上输入数据到我们的电脑中,而stdout 标准输入流,则是电脑将一些内容显示到我们的屏幕上,与stdout 标准输出流输出正常的信息不同,stderr 标准错误流,则是一般输出错误信息。(流 这方面的知识,我会在文件函数部分讲解,大家先不用太急哈)
2.1 基本用法
scanf 函数用于读取用户的键盘输入。
int n = 0;
scanf("%d",&n); //表示将键盘上输入的值 存放到变量n 中
注意:此处的& 为取地址符,表示取到变量n 的地址,在学习初期,容易将此处的& 取地址符漏写。
那么当我们已经知道了scanf 函数,要把我们输入的数据存到哪里、存到哪个地址中之后,scanf 函数是不是也要知道,这个数据的类型,才能真正的把数据存进去对不对,因为在上一章的内容中,我们知道了不同的数据类型,所对应的存储空间大小也是不同的,为此,此处scanf 函数的第一个参数中的占位符,就起到了让scanf 函数知道这个输入的数据是什么类型的作用。
【理解的小提升】 既然scanf 函数需要知道的是存储的地址,那么对于像一些本来就表示一个地址的变量是不是就不用再去取地址,不用使用& 符号了呢?比如:像后期我们会学习的数组名,本身就代表了首元素的地址,那么就不再需要再去取地址了。
而我们刚刚学完了printf 函数,既然printf 函数可以多个参数去替换其对应的占位符,那么对于与printf 都来自stdio.h 头文件同源的scanf 函数,是不是也能做到呢?
#include <stdio.h>
int main(void)
{
int a = 0, b = 0;
double c = 0.0, d = 0.0; //创建double 浮点型的变量
scanf("%d%d%lf%lf",&a,&b,&c,&d); //将输入的值存储到a b c d 变量中
getchar(); //字符函数中的函数 用于得到一个字符 //此处的作用是读取scanf 后的那个换行符\n
return 0;
}
上面示例中,scanf 函数做到了,一次性从键盘读取多个数据并存储。以下有几点需要注意:
- 首先在scanf 函数的第一个参数中,不要出现\n 这样的换行符,如果写成
scanf("%d%d%lf%lf\n",&a,&b,&c,&d);
那么当我们正常输入完数据后,scanf 会期待一个额外的换行符 然后才会结束输入,也就是说scanf 会一直读取输入,直到它遇到它期待的换行符\n 为止,而我们一般输入的时候,都不会特意去输入一个\n 换行符,因此不应该在此处多加一个\n ,如下示例所示:(此处的4. 就相当于4.0 也就是省略了此处小数点后面都为0 一样表示浮点数)* 字符串 string.h 头文件,中包含了getchar ,这个函数,该函数就是用来得到一个字符的,放在getchar 函数在程序中使用,可以起到卡一下程序的作用,从而更好调试,因为它跟scanf 一样都期待我们的输入,而两者的区别是,getchar 只能得到一个字符,即一种类型,且只能得到一个,而scanf 则没有数据数量的限制,且什么数据类型都可以输入。 - scanf 函数得到输入后,与光标仍停在最终位置的printf 不同,scanf 是自带一个换行符的,也就是scanf 之后,光标会自动换到下一行,而此时如果我们紧跟着scanf 的后面,使用getchar 函数,那么getchar 得到的那个字符,就是scanf 后面自带的那个换行符\n 。这是一个易错点,大家特别注意!特别注意!特别注意! 如下所示:(打印结果可见,getchar 并没有要我们去从键盘给它输入一个值,因为它已经得到了scanf 后面自带的换行符\n )
- scanf 在我们输入数据的时候,是先将我们输入的数据放到缓存中,然后等我们按下回车键之后再按照占位符对缓存进行解读。
2.2 scanf 的返回值
scanf 函数的返回值,是一个整数,表示成功读取到了变量数量。
而如果没有读取到任何项,或者匹配失败,则返回0。
而如果在成功读取到任何数据之前,发生了读取错误,或者遇到文件结尾,则返回常量EOF,EOF 也就是-1。
EOF - end of file 文件读取结束
#include <stdio.h>
int main(void)
{
int a = 0, b = 0;
double c = 0.0;
int r = scanf("%d %d %lf", &a, &b, &c); //使用变量r 来接收scanf 的返回值
printf("a = %d b = %d c = %lf\n",a, b, c);
printf("r = %d\n",r);
return 0;
}
-
都读取成功的运行截图:
都读取成功scanf 的返回值就是3 。 -
部分读取的运行截图:
可见,此处只有第一个数据是匹配错误的,而后面两个数据都是与占位符正确匹配的,但是这里由于第一个匹配错误,尽管还是读取3.2 读取到了3 放到变量a 中,但此处的不匹配也导致了后面数据无法读取,因此返回值为1 。 -
没有读取到任何项,或全都匹配失败的情况的运行截图:
-
全都读取失败的运行截图:
此处是按ctrl + z 使其读取结束,在vs2022 中,此情况下,需要三行都有^Z 才能做到结束读取,可见最后scanf 的返回值为-1 ,也就是EOF。
2.3 占位符
scanf 函数的占位符,与printf 函数的占位符是基本一致的。
占位符决定了scanf 函数如何去理解这个输入的数据。
【小思考】嘻嘻 考考你 Q:比如说,我用%s 作为输出格式,那么是谁将其理解成字符串的类型,是编译器,还是scanf 函数?
A:编译器只负责将高级语言的代码,翻译成汇编代码,其并不会去理解代码内容,而将%s理解成字符串类型,是在运行时,由scanf 函数去理解的。注意区分编译 与运行 这两个过程哦~
常见的占位符有:
• %c :字符。
• %d :整数。
• %f : float 类型浮点数。
• %lf : double 类型浮点数。
• %Lf : long double 类型浮点数。
• %s :字符串。
• %[] :在⽅括号中指定⼀组匹配的字符(⽐如 %[0-9] ),遇到不在集合之中的字符,匹配将会
停⽌。
-
上⾯所有占位符之中,除了 %c 以外,都会⾃动忽略起⾸的空⽩字符。 %c 不忽略空⽩字符,总是返回当前第⼀个字符,⽆论该字符是否为空格。 如下所示当输入一个空格的情况:ASCII 码值为32 的就是空格,可见%c 为输入格式的时候,并不会跳过空格。
-
如果要%c 强制跳过字符前的空⽩字符,可以写成 scanf(" %c", &ch) ,即 %c 前加上⼀个空格,表示跳过零个或多个空⽩字符。跳过一个空格:
跳过多个空格:跳过0 个空格:
-
下⾯要特别说⼀下占位符 %s ,它其实不能简单地等同于字符串。它的规则是,从当前第⼀个⾮空⽩字符开始读起,直到遇到空⽩字符(即空格、换⾏符、制表符等)为⽌。因为 %s 不会包含空⽩字符,所以⽆法⽤来读取多个单词,除⾮多个 %s ⼀起使⽤。这也意味着,scanf() 不适合读取可能包含空格的字符串,⽐如书名或歌曲名。另外, scanf() 遇到 %s 占位符,会在字符串变量末尾存储⼀个空字符 \0 。如下所示:像此处就是只读取到了第一个单词keep 而无法读取到空格隔开的第二个单词do。
-
scanf 函数将字符串读⼊字符数组时,不会检测字符串是否超过了数组⻓度。所以,储存字符串时,很可能会超过数组的边界,导致预想不到的结果。如下所示:
-
为了防⽌这种情况,使⽤ %s 占位符时,应该指定读⼊字符串的最⻓⻓度,即写成 %[m]s ,其中的 [m] 是⼀个整数,表⽰读取字符串的最⼤⻓度,后⾯的字符将被丢弃。错误示范如下所示:此处依旧是越界了,因为字符串的最后一个元素一定为\0 ,因此,此处scanf 的输入格式应该为%9s 而不是%10s 因为这个数组空间一共就10 个元素,第10 个元素存放的是\0 。正确使用如下所示:
2.4 赋值忽略符
有时,用户的输入可能不符合预定的格式。‘
#include <stdio.h>
int main(void)
{
int year = 0;
int month = 0;
int day = 0;
scanf("%d-%d-%d",&year, &month, &day);
printf("year = %d month = %d day = %d \n", year, month, day);
return 0;
}
- 格式匹配,成功读取的情况:
- 输入格式与scanf 所期望的输入格式不符时,读取失败的情况:
为了避免这种情况, scanf 函数提供了⼀个赋值忽略符 * 。
只要把 * 加在任何占位符的百分号后⾯,该占位符就不会返回值,解析后将被丢弃。从而做到忽略僵化的读取格式,成功读取到数据。如下所示:
上面示例中, %*c 就是在占位符的百分号后⾯,加⼊了赋值忽略符 * ,表⽰这个占位符没有对应的变量,解读后不必返回。
【章末小检测】Q:
scanf("%d",&a);scnaf("%f",&b);
且当a b 变量都正确创建的情况下,那么当我们输入" -13.34e12# 0 " 的a 和 b 两次scanf 的读取结果是什么呢?(此处双引号只是为了方便表示输入的数据都有什么)
A:a = -13 b = 0.34e12 (也就是0.34 乘上10 的12次方)
完
(文章内容有所参考,我目前已跟鹏哥学完C语言部分全部内容,因此,我写文章会将C语言前后有所联系的知识稍微串一下,望理解!)
需要本篇文章截图的代码可点击此处找到对应的内容即可
制作不易,您的点赞就是我更新最大的动力!