C语言scanf匹配用法

不同系统的换行符制定

三大系统的换行符制定不同,如下表所示:

操作系统换行符标识说明
Windows\r\nCRLF回车换行
Linux\nLF换行
Mac OS X\rCR回车

在Windows系统,以文本方式读取文件时,CR被抛弃,只读取LF,而以二进制方式读取文件则不对数据做任何处理。

不同系统之间传递文本文件

不同系统之间,由于换行符不同,传递文本文件存在以下问题:

  • 在Linux编辑的文本文件,移至Windows之后,多行文本可能显示为单行文本。
    对于不同换行符,新版本Windows 10自带的记事本、Nodepad++、Visual Studio等软件可以区分,未出现多行文本显示为单行文本的情况。
  • 在Windows编辑的文本文件,旧版本Linux也许会显示^M符号。
  • 在Windows编写的shell脚本,移至Linux之后,无法直接执行,需对换行符进行转换。
  • 一些编译器无法识别其他系统的换行符,有时出现莫名其妙的问题,若寻找原因未果,可以猜想换行符的问题。

函数scanf参数列表及返回值

第一参数

函数scanf第一参数为格式字符串,由单个或者多个格式说明符组成。格式说明符的形式如下所示:
%[*][width][length]specifier
最后的说明符(specifier)定义提取的字符类型,相关解释以及对应参数类型如下表所示:

说明符描述摘录
[character]Scanset方括号之间,指定任意数量字符,用以反复匹配指定字符,直到遇到未指定字符为止。
[^character]Negated scanset方括号之间,尖角符号之后,指定任意数量字符,用以反复匹配非指定字符,直到遇到指定字符为止。

格式说明符还包括可选的星号(*)、宽度和长度,遵循以下说明:

子说明符描述
*星号位于%符号之后,表示从流读取数据但将其忽略。
width指定读取的最大字符数。
length可选择hh,h,l,ll,j,z,t,L之中一个,限定相应参数所指向的预期存储类型。

除n以外的任何说明符应至少匹配一个字符。 否则,匹配失败,扫描到此结束。

可变长参数

根据格式字符串,函数可能需要一系列附加参数,每个参数都是已分配的存储空间地址,传递形式为可变长参数。
附加参数的数量至少与格式字符串指定的存储值数量一样,而忽略其他参数。

返回值

匹配成功时返回读取并存储的项目数。
当匹配失败,读取错误或者越过文件结尾,返回项目数少于预期数量,甚至为零。
若在成功读取任何数据之前,发生三者任何一种情况,则返回EOF(-1)。

输入缓冲区清除残留字符

错误用法

对于清空输入缓冲区,以下列举三种错误用法。

第一种:

fflush(stdin);

函数fflush用于将输出流缓冲区未写的数据写入关联的输出设备。至于输入流,此行为未定义。
对于一些底层库实现,作用于输入流会清空输入缓冲区,但并非可移植的预期行为。

第二种:

rewind(stdin);

函数rewind用于将与流关联的位置指示器设置为文件开头。若执行成功,清除与流关联的文件结尾和错误内部指示符,并且流上先前调用ungetc的所有影响都被丢弃。
但根据底层库实现,对输入流的操作不一定成功,或许本无清空缓冲区功能。

在Visual Studio编译,对标准输入流stdin执行rewind函数确实有清空输入缓冲区的效果。
在Linux用gcc编译,对标准输入流stdin执行rewind函数无清空输入缓冲区的作用。

第三种:

fseek(stdin, 0, SEEK_END);

函数fseek用于将与流关联的位置指示器设置为新位置。执行成功返回零,清除流的文件末尾内部指示符,并且流上先前调用ungetc的所有影响都被丢弃。失败则返回非零值。
对于以二进制模式打开的流,通过由指定参考位置加偏移量来定义新位置。
对于以文本模式打开的流,偏移量应为零或先前调用ftell的返回值,并且参考位置必须为SEEK_SET。
库的实现允许无意义地支持SEEK_END,如果使用其他参数值调用函数,则支持取决于特定的系统和库实现。因此,依赖于真正标准之外的代码不具有可移植性。

正确用法

输入缓冲区清除残留字符的正确用法:

#include <stdio.h>

static int menu()
{
	printf("请输入编号:");
	int number;
	while (scanf("%d%*[^\r\n]%*c", &number) != 1)
	{
		scanf("%*[^\r\n]%*c");
		printf("请输入正确的编号:");
	}
	return number;
}

int main()
{
	printf("选择编号:%d\n", menu());
	getchar();
	return 0;
}

代码解析

  • 第7行:格式说明符%*[^\r\n]用于反复读取并忽略既非\r也非\n的字符,格式说明符%*c用于读取并忽略剩余的\r或者\n,两者联合就能够清除缓冲区一行字符。
    当scanf函数返回值等于一时,格式说明符%d匹配成功, 其后的格式说明符%*[^\r\n]和%*c能够清除缓冲区残留字符,避免影响之后的输入操作。
    若scanf函数返回值不等于一,格式说明符%d匹配失败,其后的格式说明符未起作用,即输入缓冲区有残留字符。

注意\r对应在Mac OS X终端按Enter键而存放于缓冲区的字符,\n则对应在Windows或者Linux的控制台窗口按Enter键之后存放于缓冲区的字符。

  • 第9行:当while循环条件的scanf函数匹配失败,此时输入缓冲区非空,联合运用%*[^\r\n]和%*c,以清除缓冲区残留字符。
  • 第18行:若输入缓冲区为空,等待Enter键,从而暂停程序,方便查看执行结果。

指定字符串的输入长度

#include <stdio.h>

int main()
{
	char str[9];
	/*
	 * %8s:输入8个非空白字符
	 * %*[^\r\n]%*c:清除缓冲区剩余字符
	 */
	scanf("%8s%*[^\r\n]%*c", str);
	puts(str);
	getchar();	// 等待Enter键,暂停程序,方便查看执行结果
	return 0;
}

输入支持含空格的字符串

#include <stdio.h>

int main()
{
	char str[9];
	/*
	 * %8[^\r\n]:输入8个非换行符的字符
	 * %*[^\r\n]%*c:清除缓冲区剩余字符
	 */
	scanf("%8[^\r\n]%*[^\r\n]%*c", str);
	puts(str);
	getchar();	// 等待Enter键,暂停程序,方便查看执行结果
	return 0;
}

灵活指定输入长度和格式

#include <stddef.h>
#include <stdio.h>

static char format[BUFSIZ];

const char* getStrFormat(size_t size, const char *specifier)
{
	snprintf(format, BUFSIZ, "%%%u%s%%*[^\r\n]%%*c", size, specifier);
	return format;
}

int main()
{
	char string[BUFSIZ];
	scanf(getStrFormat(sizeof string - 1, "[^\r\n]"), string);
	puts(string);
	getchar();
	return 0;
}

参考资料

[1] scanf: http://www.cplusplus.com/reference/cstdio/scanf/
[2] fflush: http://www.cplusplus.com/reference/cstdio/fflush/
[3] rewind: http://www.cplusplus.com/reference/cstdio/rewind/
[4] fseek: http://www.cplusplus.com/reference/cstdio/fseek/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值