scanf那些你不知道的用法

前言

我们在学习C语言的过程中学习了scanf的基本用法,知道了基本的格式化输入,但是在使用scanf的过程中有很多少用但是用对了地方却有奇效的使用 方法,现在我们就来解析这一系列的新用法,

  1. 指定读取长度

我们都知道printf中可以指定最小输出长度,例如%10d

  • 如果整数的宽度不足 10,那么在左边以空格补齐;

  • 如果整数的宽度超过了 10,那么以整数本身的宽度来输出,10 不再起作用。

在scanf中也有类似的用法,也可以在格式控制符中间加一个数字,用来表示读取数据的最大长度

%2d表示最多读取两位整数;

%10s表示读取的字符串的最大长度为 10,或者说,最多读取 10 个字符。

请看接下来的示例

#include <stdio.h>
int main() {
    int n;
    float f;
    char str[23];
    scanf("%2d", &n);
    scanf("%*[^\n]"); scanf("%*c");  //清空缓冲区
    scanf("%5f", &f);
    scanf("%*[^\n]"); scanf("%*c");  //清空缓冲区
    scanf("%19s", str);
    printf("n=%d, f=%g, str=%s\n", n, f, str);
    return 0;
}

输入输出展示

这段代码使用了多个scanf()函数读取数据,为了避免缓冲区中仍然存有数据,影响最后的结果,我们每次读取都使用scanf("%*[^\n]"); scanf("%*c");来清空缓冲区。

限制读取数据的长度在实际开发中非常有用,最典型的一个例子就是读取字符串:我们为字符串分配的内存是有限的,用户输入的字符串过长就存放不了了,就会冲刷掉其它的数据,从而导致程序出错甚至崩溃;如果被黑客发现了这个漏洞,就可以构造栈溢出攻击,改变程序的执行流程,甚至执行自己的恶意代码,这对服务器来说简直是灭顶之灾。

在用gets()函数读取字符串的时候,有一些编译器会提示不安全,建议替换为gets_s(),就是因为gets()不能控制读取到的字符串的长度,风险较高。

目前来看,虽然scanf()可以控制字符串的长度,但是字符串中不能包含空格,tab,这使得scanf还不能完全替代gets(),不过不用担心,在接下来的介绍中会补充scanf的高级用法 ,使得scanf()可以完全替代gets(),而且更加好用且智能。

  1. 匹配特定的字符

%s控制符会匹配除空白符以外的所有字符夫,它有两个缺点

  • %s 不能读取特定的字符,比如只想读取小写字母,或者十进制数字等,%s 就无能为力;

  • %s 读取到的字符串中不能包含空白符,有些情况会比较尴尬,例如,无法将多个单词存放到一个字符串中,因为单词之间就是以空格为分隔的,%s 遇到空格就读取结束了。

要想解决以上问题,可以使用scanf()的另外一种字符匹配方式就是%[],[]包围起来的是需要读取的字符集合。例如%[abcd]表示只读取字符abcd,遇到其他的字符就读取结束;这里并不强调字符顺序,只要字符在abcd范围内都可以匹配成功。

现在请看以下代码

#include <stdio.h>
int main(){
    char str[30];
    scanf("%[abcd]", str);
    printf("%s\n", str);
    return 0;
}

使用连接符

为了简化字符集合的写法,scanf() 支持使用连字符-来表示一个范围内的字符,例如 %[a-z]、%[0-9] 等。

连字符左边的字符对应一个 ASCII 码,连字符右边的字符也对应一个 ASCII 码,位于这两个 ASCII 码范围以内的字符就是要读取的字符。注意,连字符左边的 ASCII 码要小于右边的,如果反过来,那么它的行为是未定义的。

常用的连字符举例:

  • %[a-z]表示读取 abc...xyz 范围内的字符,也即小写字母;

  • %[A-Z]表示读取 ABC...XYZ 范围内的字符,也即大写字母;

  • %[0-9]表示读取 012...789 范围内的字符,也即十进制数字。

你也可以将它们合并起来,例如:

  • %[a-zA-Z]表示读取大写字母和小写字母,也即所有英文字母;

  • %[a-z-A-Z0-9]表示读取所有的英文字母和十进制数字;

  • %[0-9a-f]表示读取十六进制数字。

请看以下代码

#include <stdio.h>
int main(){
    char str[30];
    scanf("%[a-zA-Z]", str);  //只读取字母
    printf("%s\n", str);
    return 0;
}

不匹配某些字符

加入现在有一种需求,就是读取换行符以外的所有字符,或者读取0~9以外的所有字符,该怎是实现呢,总不能把剩下的字符列出来吧,一是麻烦,二来也不现实

所以scanf()允许我们在%[]中直接指定某些不能匹配的字符,具体方法就是在不匹配的字符前面加上^,例如:

  • %[^\n]表示匹配除换行符以外的所有字符,遇到换行符就停止读取;

  • %[^0-9]表示匹配除十进制数字以外的所有字符,遇到十进制数字就停止读取。

请看下面的例子:

#include <stdio.h>
int main(){
    char str1[30], str2[30];
    scanf("%[^0-9]", str1);
    scanf("%*[^\n]"); scanf("%*c");  //清空缓冲区
    scanf("%[^\n]", str2);
    printf("str1=%s \nstr2=%s\n", str1, str2);
    return 0;
}

注意看scanf("%[^\n]", str2);

这条语句的作用是,读取一行字符串,和gets的功能一模一样,同时,scanf还可以指定字符串的最大长度,指定字符串中不能包含哪些字符这是gets中不具备的功能。

例如接下来的代码

#include<stdio.h>
int main()
{
    char str[21];
    scanf("%[^0-9\n]", str);
    printf("%s", str);
    return 0;
}

总体来看scanf完全可以胜任gets的功能并且超过gets

  1. 丢弃读到的字符

在前面的代码中,每个格式控制符都要对应一个变量,把读取到的数据放入对应的变量中。其实scanf也允许把读取的数据直接丢弃,不往变量中存放,具体的方法就是在%后面加一个*,例如

  • %*d表示读取一个整数并丢弃;

  • %*[a-z]表示读取小写字母并丢弃;

  • %*[^\n]表示将换行符以外的字符全部丢弃。

请看接下来的代码展示

#include <stdio.h>
int main(){
    int n;
    char str[30];
    scanf("%*d %d", &n);
    scanf("%*[a-z]");
    scanf("%[^\n]", str);
    printf("n=%d, str=%s\n", n, str);
    return 0;
}

大家有没有意识到,将读取到的字符直接丢弃,这就是在清空输入缓冲区呀,虽然有点蹩脚,但是行之有效。

下面我们就来解释一下。

首先需要明白的是,等到需要清空缓冲区的时候,缓冲区中的最后一个字符一定是换行符\n,因为输入缓冲区是行缓冲模式,用户按下回车键会产生换行符,结束本次输入,然后输入函数开始读取。

scanf("%*[^\n]");将换行符前面的所有字符清空,scanf("%*c");将最后剩下的换行符清空。

有些网友将这两条语句合并起来,写作:

scanf("%*[^\n]%*c");

这是错误的。合并以后的语句不能清空单个换行符,因为该语句要求换行符前边至少要有一个其它的字符,单个换行符会导致匹配失败。

  1. 总结

canf() 控制字符串的完整写法为:

%{*} {width} type

其中,{ } 表示可有可无。各个部分的具体含义是:

  • type表示读取什么类型的数据,例如 %d、%s、%[a-z]、%[^\n] 等;type 必须有。

  • width表示最大读取宽度,可有可无。

  • *表示丢弃读取到的数据,可有可无。

  • 14
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 8
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值