爱技术 & 爱分享
爱蛋蛋 & 爱生活

scanf, fscanf, sscanf

这篇文章跟大家聊一下 C 风格字符串的格式化输出方法

文件源

这三个函数都在 cstdio或者说是 stdio.h 中定义。

原型

int scanf( const char* format, ... );   //(1)
int fscanf( std::FILE* stream, const char* format, ... );   //(2)
int sscanf( const char* buffer, const char* format, ... );  //(3)

他们的相同点在 CppReference 中是这样描述的:

Reads data from the a variety of sources, interprets it according to format and stores the results into given locations.

解释:从各种源读取数据,按照 format 转译并存储结果于给定位置。简单说就是格式化输入。

上面介绍的各种源,分别是:

  1. 从 stdin 读数据
  2. 从文件流 stream 读数据
  3. 从空终止字符串 buffer 读数据

参数

参数 英文释义 中文释义
stream input file stream to read from 读取来源的文件流
buffer pointer to a null-terminated character string to read from 指向读取数据来源的空终止字符串
format pointer to a null-terminated character string specifying how to read the input 指向指定如何读取输入的空终止字符串的指针。

至于 format 参数的使用话,最详细的地方是这里 CppReference

这里简单讲一下:

format可为一个或多个{%[*] [width] [{h | l | L}]type | ' ' | '\t' | '\n' | 非%符号}格式转换符。集合中{a|b|c}表示格式符a、b、c任选其一。以中括号括起来的格式符可选。%与type为必选,所有格式符必须以%开头。

以下简要说明各格式符的含义:

1) 赋值抑制符’*’表明按照随后的转换符指示来读取输入,但将其丢弃不予赋值(“跳过”)。抑制符无需相应的指针可变参数,该转换也不计入函数返回的成功赋值次数。

%*[width] [{h | l | L}]type 表示满足该条件的字符被过滤掉,不会向目标参数中赋值。

2) width表示最大读取宽度。当读入字符数超过该值,或遇到不匹配的字符时,停止读取。多数转换丢弃起始的空白字符。这些被丢弃的字符及转换结果添加的空结束符(‘\0’)均不计入最大读取宽度。

3) {h | l | L}为类型修饰符。h指示输入的数字数值以short int或unsigned short int类型存储;hh指示输入以signed char或unsigned char类型存储。l(小写L)指示输入以long int、unsigned long int或double类型存储,若与%c或%s结合则指示输入以宽字符或宽字符串存储;ll等同L。L指示输入以long long类型存储。

4) type 为类型转换符,如%s、%d。

此外,还有两种特殊的格式符:

1) []:字符集合。[]表示指定的字符集合匹配非空的字符序列;^则表示过滤。该操作不会跳过空白字符(空格、制表或换行符),因此可用于目标字符串不以空白字符分隔时。[]内可有一到多个非^字符(含连字符’-‘),且无顺序要求。%[a-z]表示匹配a到z之间的任意字符,%[aB-]匹配a、B、-中的任一字符;%[^a]则匹配非a的任意字符,即获取第一个a之前的(不为a的)所有字符。^可作用于多个条件,如^a-z=表示^a-z且^=(既非小写字母亦非等号)。空字符集%[]和%[^]会导致不可预知的结果。

使用[]时接收输入的参数必须是有足够存储空间的char、signed char或unsigned char数组。[]也是转换符,故%[]后无s。

%[^]的含义和用法与正则表达式相同,故sscanf函数某种程度上提供了简单的正则表达式功能。

2) n:至此已读入值(未必赋值)的等价字符数,该数目必须以int类型存储。如”10,22″经过"%d%*[^0-9]%n"格式转换后,%n对应的参数值为3(虽然’,’未参与赋值)。

‘n’并非转换符,尽管它可用’*’抑制。C标准声称,执行%n指令并不增加函数返回的赋值次数;但其勘误表中的描述与之矛盾。建议不要假设%n对返回值的影响。

常见的 Format 用法:

原字符串 格式化字符串 结果字符串
helloworld %s helloworld
hello, world %s hello,
hello, world %4s hell
hello, world %*s%s world(若原字符串无空格则得到空串)
hello, world %[^ ] hello,
hello, world %[a-z] hello
12345helloWORLD %[1-9a-z]或%[a-z1-9] 12345hello
12345helloWORLD %[^A-Z] 12345hello
12345helloWORLD %[^a-f] 12345h
12345helloWORLD %*[1-9]%[a-z] hello
12345helloWORLD %[a-z] (空串,需先过滤前面不需要的字符)
12345helloWORLD= %[1-9]%[a-z]%[^a-z=] WORLD
12345/hello@world %*[^/]/%[^@] hello
IpAddr=10.46.44.40 %*[^=]=%s 10.46.44.40
Name = Yuan %[^=]=%[ \t]%s Yuan
email:wxy@zte.com.cn; %*[^:]:%[^;] wxy@zte.com.cn
email:wxy@zte.com.cn %*[^:]:%s wxy@zte.com.cn
wxy@zte.com.cn %[^@]%*c%s 串1:wxy;串2:zte.com.cn
IpAddr=10.46.44.40 %[^=]=%s 串1:IpAddr;串2:10.46.44.40
1hello234world5 1%[^2]234%[^5] 串1:hello;串2:world
Michael/nWang %[^/n]%c%c%s 串1:Michael;串2:Wang
Michael\nWang %[^\n]%*c%s 串1:Michael;串2:Wang
13:10:29-13:11:08 %[0-9,:] - %[0-9,:] 串1:13:10:29;串2:13:11:08
10.46.44.40 %d.%d.%d.%d 串1:10;串2:46;串3: 44;串4: 40

返回值

代表成功输入的参数数(在首个参数赋值前发生匹配失败的情况下可为零),或若在赋值首个接收的参数前输入失败则为 EOF 。

小技巧:

Because most conversion specifiers first consume all consecutive whitespace, code such as the follow(因为大多数转换指定符首先消耗所有连续空白符,如下代码)

std::scanf("%d", &a);
std::scanf("%d", &b);

will read two integers that are entered on different lines (second %d will consume the newline left over by the first) or on the same line, separated by spaces or tabs (second %d will consume the spaces or tabs)(将读取在不同行上输入的两个整数(第二个%d将消耗第一个剩余的换行符),或者在同一行上以空格或制表符分隔(第二个%d将消耗空格或制表符)。).

The conversion specifiers that do not consume leading whitespace, such as %c, can be made to do so by using a whitespace character in the format string:(不消耗前导空白符的转换指定符,如 %c ,可通过在格式化字符串中用空白符使得它这么做:)

std::scanf("%d", &a);
std::scanf(" %c", &c); // 忽略 %d 后的换行符,然后读一个 char

Demo:

#include <iostream>
#include <clocale>
#include <cstdio>

int main()
{
    int i, j;
    float x, y;
    char str1[10], str2[4];
    wchar_t warr[2];
    std::setlocale(LC_ALL, "en_US.utf8");

    char input[] = u8"25 54.32E-1 Thompson 56789 0123 56ß水";
    // parse as follows:
    // %d: an integer 
    // %f: a floating-point value
    // %9s: a string of at most 9 non-whitespace characters
    // %2d: two-digit integer (digits 5 and 6)
    // %f: a floating-point value (digits 7, 8, 9)
    // %*d an integer which isn't stored anywhere
    // ' ': all consecutive whitespace
    // %3[0-9]: a string of at most 3 digits (digits 5 and 6)
    // %2lc: two wide characters, using multibyte to wide conversion
    int ret = std::sscanf(input, "%d%f%9s%2d%f%*d %3[0-9]%2lc",
                     &i, &x, str1, &j, &y, str2, warr);

    std::cout << "Converted " << ret << " fields:\n"
              << "i = " << i << "\nx = " << x << '\n'
              << "str1 = " << str1 << "\nj = " << j << '\n'
              << "y = " << y << "\nstr2 = " << str2 << '\n'
              << std::hex << "warr[0] = U+" << warr[0]
              << " warr[1] = U+" << warr[1] << '\n';
}

运行结果:

Converted 7 fields:
i = 25
x = 5.432
str1 = Thompson
j = 56
y = 789
str2 = 56
warr[0] = U+df warr[1] = U+6c34

1、 常见用法。

char buf[512] = ;
sscanf("123456 ", "%s", buf);
printf("%s\n", buf);

结果为:123456

2、 取指定长度的字符串。如在下例中,取最大长度为4字节的字符串。

sscanf("123456 ", "%4s", buf);
printf("%s\n", buf);

结果为:1234

3、 取到指定字符为止的字符串。如在下例中,取遇到空格为止字符串。

sscanf("123456 abcdedf", "%[^ ]", buf);
printf("%s\n", buf);

结果为:123456

4、 取仅包含指定字符集的字符串。如在下例中,取仅包含1到9和小写字母的字符串。

sscanf("123456abcdedfBCDEF", "%[1-9a-z]", buf);
printf("%s\n", buf);

结果为:123456abcdedf

5、 取到指定字符集为止的字符串。如在下例中,取遇到大写字母为止的字符串。

sscanf("123456abcdedfBCDEF", "%[^A-Z]", buf);
printf("%s\n", buf);

结果为:123456abcdedf

6、给定一个字符串iios/12DDWDFF@122,获取 / 和 @ 之间的字符串,先将 “iios/”过滤掉,再将非’@’的一串内容送到buf中

sscanf("iios/12DDWDFF@122", "%*[^/]/%[^@]", buf);
printf("%s\n", buf);

结果为:12DDWDFF

7、给定一个字符串““hello, world”,仅保留world。(注意:“,”之后有一空格)

sscanf(“hello, world”, "%*s%s", buf);
printf("%s\n", buf);

结果为:world

%*s表示第一个匹配到的%s被过滤掉,即hello被过滤了,如果没有空格则结果为NULL。

sscanf的功能很类似于正则表达式, 但却没有正则表达式强大,所以如果对于比较复杂的字符串处理,建议使用正则表达式.

与之类似的函数有:

vscanf(C++11),vfscanf(C++11),vsscanf(C++11) (使用可变参数列表从 stdin 、文件流或缓冲区读取格式化输入 )

gets(C++14前)(从 stdin 读取字符串 ),fgets(从文件流获取字符串 ) ,fgetws(从文件流获取宽字符串 )

from_chars(C++17)(转换字符序列到整数或浮点值)

更多相关函数在这里 —–> C/C++输入输出与格式化函数汇总

赞(0) 传送门
本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。编程日志 » scanf, fscanf, sscanf
分享到: 更多 (0)

游戏 && 后端

传送门传送门