C语言中的 printf( ) 与 scanf( )

时隔多日,小编我又回来咯······小编相信之前的博客能够给大家带来不少的收获。在我们之前的文章中,许多代码块的例子都用到了printf( ) 与 scanf( )这两个函数,大家都知道他们需要声明头文件之后才能使用,那这两个函数是什么呢?这两个函数的用途是什么呢??这两个函数又有什么其他的用法呢???今天小编就围绕这个这两个函数为大家展开讲讲。

1. printf( )

1.1 printf( )的基本用法

printf( ) 这个函数没有太多抽象的地方,其作用就是将参数文本输入到屏幕上。但是仔细想想,感觉哪里好像不对劲,英文中的打印应该是 print ,但是这个函数为什么要在 print 后面多加一个 f 呢??这里小编给读者们解释一下,这里的 f 代表的是英文中的format,就是格式、格式化的意思,这也说明了 printf( ) 还有许多其他的 "玩法"( f 其实代表的是可以定制输出文本格式),小编给大家一一介绍。

cpp 复制代码
#include <stdio.h>
int main() 
{
     printf("Hello World");
     return 0;
}

假如我们执行这段代码,这段代码的结果就是将 "Hello World" 这行文字打印在屏幕上。在我们没有对 printf( ) 做出任何定制化输出的时候,这个函数是不会在这串文字打印完的时候添加换行符的,运行结束后,光标就会停留在输出结束的地方,不会自动换行。如果我们想让光标移动到下一行的开头,可以在输出文本的结尾加上一个换行符 '\n' 。想要在文本内部实现换行,我们也可以通过插入换行符来实现:

cpp 复制代码
#include <stdio.h>
int main(void) 
{
     printf("Hello\nWorld\n");
 
     printf("Hello\n");
     printf("World\n");
     return 0;
}

上述代码执行结果

在第一个 printf( ) 中,要注意两个 '\n' 的位置,出现 '\n' 则换行继续输入。重要的小细节小编会为大家反复强调:printf( ) 是在标准库的头文件 stdio.h 定义的。使用这个函数之前,必须要在原码文件的头部引入这个头文件!!!

1.2 占位符

printf( ) 可以在输出的文本中指定占位符,这里也许读者们会有疑惑:什么是占位符呢?所谓的占位符,就是这个地方可以用其他的值代入。接下来看个例子:

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
	printf("There are %d apples\n", 10);
	return 0;

}

上述代码执行结果

从这段代码中我们可以看到,%d 的位置被我们给定的数值 10 给替换了。There are %d apples\n 是输出的文本,%d 就是占位符,表示 %d 的位置要用其他值来替换,占位符的第一个字符一律为百分号 % ,第二个字符表示占位符的类型,%d 表示这里代入的值必须是⼀个整数。在这个例子中,printf( ) 的第二个参数就是替换占位符的值。

输入的文本中也可以使用多个占位符:

cpp 复制代码
#include <stdio.h>
int main()
{
     printf("%s says it is %d o'clock\n", "Lee", 5);
     return 0;
}

上述代码运行结果

输出文本 %s says it is %d o'clock 有两个占位符,第一个是字符串占位符 %s ,第二个是整数占位符 %d ,分别对应 printf( ) 的第二个参数Lee 和第三个参数 5 。执行后的输出就是 Lee says it is 5 o'clock 。还需要提醒读者们的是如果参数少于对应的占位符,**printf( )**可能会输入内存中的任意值。

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( ) 可以定制占位符的输出格式,首先允许限定占位符的最小宽度:

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
	printf("%7d\n", 1234);
	return 0;
}

上述代码运行结果

%5d 表示这个占位符的宽度至少为 5 位。如果不满 5 位,对应的值的前面会添加空格。输出的值默认是右对齐,即输出内容前面会有空格;如果希望改成左对齐,在输出内容后面添加空格,可以在占位符的 % 的后面插入一个 - 号:

右对齐用例

由于是右对齐,1234 后面不足 7 位的部分会用空格补齐,虽然看不出来,但是我们要知道 1234 后面还有三个空格。

对于小数这个限定符会限制所有数字的最小显示宽度:

通过代码的执行结果我们可以看到,%12f 表示输出的浮点数最少要占据 12 位。由于小数的默认显示精度是小数点后 6 位,所以 123.45 输出结果的头部会添加 2个空格。

默认情况下, printf( ) 不对正数显示 + 号,只对负数显示 - 号。如果想让正数也输出 + 号,可 以在占位符的 % 后,面加⼀个 + :

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
	printf("%+d\n", 12); // 输出 +12
	printf("%+d\n", -12); // 输出 -12
	return 0;
}

上述代码运行结果

%+d 可以确保输出的数值,总是带有正负号。

我们还可以通过占位符限定输出的小数位,举例来说,我们如果希望输出的浮点数只保留小数点后两位,那我们就可以将占位符写成**%.2f** :

cpp 复制代码
#include <stdio.h>
int main()
{
     printf("Number is %.2f\n", 0.5);
     return 0;
}

上述代码运行结果

更一般的有:想要保留 A 位的小数位,占位符就写成 %.Af ,如果是双精度浮点型的话,就需要写成 %.Alf。而且这种写法可以结合限定宽度的占位符使用:

结合限定宽度用例

%7.2f 表示输出字符串最小宽度为 7 ,小数位数为 2 。所以,输出字符串的头部有 3 个空格。

最小宽度和小数位这两个限定值都可以用 * 代替,可以通过 **printf( )**导入:

限定值可用 * 代替用例

延续了使用 %7.2f 的用例,通过观察结果可以发现,直接用数字限定宽度和小数位与用 * 代替再通过 printf( ) 导入的执行结果是一样的(将 7 和 2 用 * 代替,再利用 printf( ) 导入)。

%s 占位符用来输出字符串,默认是全部输出。如果只想输出开头的部分,可以用 %.ms 定输出的长度,其中 m 代表⼀个数字,表示所要输出的长度:

我们可以发现,占位符 %.5s 表示只输出字符串 "hello 1234" 的前5个字符,即"hello"。

2. scanf( )

2.1 关于 scanf( ) 的使用注意事项

当我们创建了变量之后,我们需要给变量输入一个值就可以使用 scanf() 函数,有一个重要的事项,使用 VS2022 这款编译器的读者要注意的是:

我们可以检查一下,这段代码按照常理来说是没有任何语法错误的,但是这段代码却无法运行,编译器的错误列标给出了错因:" 'scanf': This function or variable may be unsafe.Consider using scanf_s instead.To disable deprecation, use _CRT_SECURE_NO_WARNINGS.See online help for details. " 这句话的中文意思是:" scanf':此函数或变量可能不安全。考虑使用scanf_s。要禁用弃用,请使用_CRT_SECURE_NO_WARNINGS。详细信息请参见联机帮助。"

也就是说,编译器限制了我们对 scanf( ) 这个函数的使用,让我们使用 scanf_s ( )。但是小编建议大家不要使用 scanf_s ( ) ,因为这个函数只有在 VS2022 下才能使用,如果使用的不是这个编译器则无法使用,为了解决这个问题,我们可以宏定义 _CRT_SECURE_NO_WARNINGS。


上述用例的执行过程

加上了这个宏定义后,我们就可以正常运行代码了,小编建议使用 VS2022 的小伙伴在创建一个项目的时候先写上这个宏定义,这也是一个良好的习惯。

2.2 scanf( ) 的基本用法

scanf( ) 函数用于读取用户的键盘输入。程序运行到这个语句时,会停下来,等待用户从键盘输入。用户输入数据、按下回车键后,scanf( ) 就会处理用户的输入,将其存入变量。它的原型定义在头文件 stdio.h ,并且 scanf( ) 的语法跟 printf( ) 类似。

cpp 复制代码
scanf("%d", &i);

scanf( ) 的第⼀个参数是一个格式字符串,里面会放置占位符(与 printf( ) 的占位符基本⼀致),告诉编译器如何解读用户的输入,需要提取的数据是什么类型。这是因为C语言的数据都是有类型的,scanf( ) 必须提前知道用户输入的数据类型,才能处理数据。
scanf( ) 的其余参数就是存放用户输入的变量,格式字符串里面有多少个占位符,就有多少个变量。上面示例中, scanf( ) 的第⼀个参数 %d ,表示用户输入的应该是一个整数。 %d 就是一个位符, % 是占位符的标志, d 表示整数。第二个参数 &i 表示,将用户从键盘输入的整数存入变量 i 。

注意:变量前面必须加上 & 运算符(指针变量除外),因为 scanf( ) 传递的不是值,而是地址,即:将变量 i 的地址指向用户输入的值。
如果这里的变量是指针变量(比如字符串变量),那就不用加 & 运算符。

scanf( ) 也可以一次性读取多个变量:

cpp 复制代码
scanf("%d%d%f%f", &i, &j, &x, &y);

且在处理数值占位符时,会自动过滤空白字符,包括空格、制表符、换行符等。所以,我们在输入的数据之间有一个或者是多个空格不会影响 **scanf( )**解读数据;使用回车换几行,将数据分行输入也不会影响这个函数解读数据。

scanf() 处理用户输入的原理是,用户的输⼊先放入缓存,等到按下回车键后,按照占位符对缓存进行解读。解读用户输入时,会从上一次解读遗留的第一个字符开始,直到读完缓存,或者遇到第⼀个不符合条件的字符为止:

上⾯⽰例中,scanf( ) 读取用户输入时, %d 占位符会忽略数据首部的空格,从 1 处开始获取数据,读取到 123 停下来,因为后面的 . 不属于整数的有效字符。这就是说,占位符 %d 会读到 123。

第二次调用 scanf( ) 时,就会从上一次停止解读的地方,继续往下读取。这一次读取的首字符 是 . ,由于对应的占位符是 %f ,会读取到 .12345 ,这是采用科学计数法的浮点数格式。后面的 * 不属于浮点数的有效字符,所以会停在 *****这里。

2.3 scanf( ) 的返回值

我们在使用这个函数的时候可以发现,在错误列标这里有两个警告:"scanf 的返回值被忽略" ,也就是说,scanf( ) 这个函数是有返回值的。

这里小编就来给大家讲解一下,scanf( ) 的返回值是一个整数,表示的是成功读取的变量个数。对于没有读取到任何一个项,或者是匹配失败,scanf( ) 的返回值都是 0 ;如果在成功读取任何数据之前,发生了读取错误或者遇到读取到文件结尾,则返回常量EOF (-1) ------EOF 的全称是end of file,它也是文件结束的标志。我们可以用一组代码来测试一下:

cpp 复制代码
#include <stdio.h>
int main()
{
     int a = 0;
     int b = 0;
     float f = 0.0f;
     int r = scanf("%d %d %f", &a, &b, &f);
     printf("a=%d b=%d f=%f\n", a, b, f);
     printf("r = %d\n", r);
     return 0;
}

根据结果可以发现:我们输入的三个数据都被成功读取到了,我们将 scanf( ) 的数值赋给了一个变量 r ,再将这个 r 打印,r 的返回值是 3 ,也就说明了 3 个数据被成功读取到了。但是,如果我们使用两下快捷键 " 啃臭+z " 将提前结束输入,那结果是什么呢??

可以发现,如果我们提前结束了输入,scanf( ) 只成功读取了两个变量,所以返回值就是2 ,如果一个数字都不输入,直接按 3 次 " 啃臭+z " , 输出的r 是 -1 ,也就是EOF:

2.4 占位符

scanf() 常用的占位符如下,与 printf() 的占位符基本一致:
%c :字符。
%d :整数。
%f : float 类型浮点数。
%lf : double 类型浮点数。
%Lf : long double 类型浮点数。
%s :字符串。
% :在方括号中指定一组匹配的字符(比如 %a-h ),遇到不在集合之中的字符,匹配将会
停止。

以上所有占位符之中,除了 %c 以外,都会自动忽略起首的空白字符。 %c 不忽略空白字符,总是返回当前第⼀个字符,无论该字符是否为空格。
如果要强制跳过字符前的空白字符,可以写成 scanf(" %c", &ch) ,即 %c前加上⼀个空格,表示跳过零个或多个空白字符。
注意:特别说⼀下占位符 %s ,它其实不能简单地等同于字符串。它的规则是,从当前第⼀个非空白字符开始读起,直到遇到空白字符(即空格、换行符、制表符等)为止。因为 %s 不会包含空白字符,所以无法用来读取多个单词,除非多个 %s 一起使用。这也意味着,scanf( ) 不适合读取可能包含空格的字符串,比如书名或歌曲名。另外, scanf( ) 遇到 %s 占位符,会在字符串变量末尾存储⼀个空字符**\0** 。
scanf( ) 将字符串读入字符数组时,不会检测字符串是否超过了数组长度。所以,储存字符串时,很可能会超过数组的边界,导致预想不到的结果。为了防止这种情况,使用 %s 占位符时,应指定读入字符串的最大长度,即写成 %ms ,其中的 m 是⼀个整数,意为读取字符串的最大长度,而最大长度后的字符将被丢弃:

限定字符串输入长度的用法

2.5 赋值忽略符

有的时候,用户的输入可能不符合我们预期的格式:

cpp 复制代码
#include <stdio.h>
int main()
{
     int year = 0;
     int month = 0;
     int day = 0;
     scanf("%d-%d-%d", &year, &month, &day);
     printf("%d %d %d\n", year, month, day);
     return 0;
}

代码如上,如果用户输入 2020-01-01 ,就会正确解读出年、月、日。问题是用户可能输入其他格式比如 2020/01/01 ,这种情况下, scanf( ) 解析数据就会失败。

为了防止这种情况的发生,我们需要引入一个复制忽略符 * 。只要把 *加在任何占位符的百分号后面,该占位符就不会返回值,解析后将被丢弃。

cpp 复制代码
scanf("%d%*c%d%*c%d", &year, &month, &day);

这里小编来给大家解释一下: %*c 就是在占位符的百分号后面加上了赋值忽略符 * ,表示这个占位符没有对应的变量,解读后不必返回。

3. 结语

到这里小编就将 printf( ) 与 scanf( ) 这两个我们最常见的函数解析完了,希望大家能够从中有所收获,内容有点多,还需要各位读者慢慢去领悟和理解," 有道无术,术可求;有术无道,止于术 。"我们学习不能只能停留在表面,还需要去深究其中的门道,搞清楚原理,这块知识点自然就烙印在脑海中了。

相关推荐
Navicat中国4 小时前
Navicat 18 内置 AI 助手让数据库开发从繁琐操作迈向智能协作
大数据·人工智能·数据库开发
4SAPI5 小时前
多模型请求路由怎么设计?成本、速度和能力如何平衡
人工智能·python·gateway·php
魔乐社区5 小时前
翼芯・智算测试与适配优化专区,让国产算力“选得准、跑得通、用得好“
人工智能·开源·大模型
Pniubi6 小时前
HashMap的扩容和树化边界
java·开发语言
XCDoris7 小时前
郴州logo设计机构
大数据·人工智能·python
Goboy7 小时前
一个人带三个Agent,我用全新TRAE从0到1搭了个资讯周刊站
人工智能·trae
俊哥V7 小时前
每日 AI 研究简报 · 2026-10-19
人工智能·ai
AI_AGENT_DEV_AI7 小时前
AI英语教培智能体的开发
人工智能
物联网软硬件开发-轨物科技7 小时前
【技术白皮书】光伏清检一体化机器人系统解决方案技术白皮书
大数据·运维·网络·人工智能·机器人
gCode Teacher 格码致知7 小时前
Pandas教学-17:Series与Dataframe的关系
开发语言·python·pandas