Scanf() - %a格式/转换说明符是什么?

3
在C语言中,可以使用%a作为格式说明符,用于在scanf()格式字符串中格式化浮点数值。例如:
float v;
scanf("%a",&v);

在C标准(我特别关注的是ISO/IEC 9899/2011(C11))中,关于这个特定说明符只有很少的解释,对与其关联的浮点数转换说明符%f%e%g没有任何区别的说明:

引用自ISO/IEC 9899/2011,§7.21.6.2:

a、e、f、g - 匹配一个可选的带符号浮点数,无穷大或NaN,其格式与strtod函数的主体序列所期望的格式相同。相应的参数应该是一个指向浮点型的指针。

这个说明符是什么意思?它的预期使用方式是什么?与其他浮点数转换说明符相比有何特殊之处?


这是一个 C99 引入的说明符,可能允许使用十六进制格式的浮点数输入:cppreference - Adrian Mole
@AdrianMole:"%a" 允许十六进制格式的浮点数输入:" 这个描述有些不准确。 C99 允许使用 "十六进制格式的浮点数输入",但它适用于 "%a、%A、%e、%E、%f、%F、%g、%G、%la、%lA、%le、%lE、... %LG"。 - chux - Reinstate Monica
@chux 这就是为什么我加了“(可能?)” - 链接的cppreference页面说C99允许十六进制输入浮点数,并且将“%a”格式标记为C99特定 - 所以我不确定。 - Adrian Mole
1个回答

3
%a%e%f%g 这几个格式说明符在 scanf 函数中的作用与标准中引用的一样。Linux 的 scanf 手册更加明确地解释了这一点:

f 匹配一个可选带符号的浮点数;下一个指针必须是指向 float 类型的指针。

e 等价于 f。

g 等价于 f。

E 等价于 f。

a (C99) 等价于 f。

大概是因为它们也是 printf 格式说明符,可以接受一个 float 参数,但与 scanf 不同的是,它们产生的输出不同。
为了说明这一点,考虑以下代码:
#include <stdio.h>

int main()
{
    char *str[] = { "234.56", "2.3456e2", "2.3456E2", "0x1.d51eb8p+7" };
    unsigned i;

    for (i=0; i<sizeof(str)/sizeof(*str); i++) {
        float f;

        printf("scanning %s\n", str[i]);
        sscanf(str[i], "%f", &f);
        printf("scanned with f: (f)%f, (e)%e, (g)%g, (a)%a\n", f, f, f, f);
        sscanf(str[i], "%g", &f);
        printf("scanned with g: (f)%f, (e)%e, (g)%g, (a)%a\n", f, f, f, f);
        sscanf(str[i], "%e", &f);
        printf("scanned with e: (f)%f, (e)%e, (g)%g, (a)%a\n", f, f, f, f);
        sscanf(str[i], "%a", &f);
        printf("scanned with a: (f)%f, (e)%e, (g)%g, (a)%a\n", f, f, f, f);
    }
    return 0;
}

输出:

scanning 234.56
scanned with f: (f)234.559998, (e)2.345600e+02, (g)234.56, (a)0x1.d51eb8p+7
scanned with g: (f)234.559998, (e)2.345600e+02, (g)234.56, (a)0x1.d51eb8p+7
scanned with e: (f)234.559998, (e)2.345600e+02, (g)234.56, (a)0x1.d51eb8p+7
scanned with a: (f)234.559998, (e)2.345600e+02, (g)234.56, (a)0x1.d51eb8p+7
scanning 2.3456e2
scanned with f: (f)234.559998, (e)2.345600e+02, (g)234.56, (a)0x1.d51eb8p+7
scanned with g: (f)234.559998, (e)2.345600e+02, (g)234.56, (a)0x1.d51eb8p+7
scanned with e: (f)234.559998, (e)2.345600e+02, (g)234.56, (a)0x1.d51eb8p+7
scanned with a: (f)234.559998, (e)2.345600e+02, (g)234.56, (a)0x1.d51eb8p+7
scanning 2.3456E2
scanned with f: (f)234.559998, (e)2.345600e+02, (g)234.56, (a)0x1.d51eb8p+7
scanned with g: (f)234.559998, (e)2.345600e+02, (g)234.56, (a)0x1.d51eb8p+7
scanned with e: (f)234.559998, (e)2.345600e+02, (g)234.56, (a)0x1.d51eb8p+7
scanned with a: (f)234.559998, (e)2.345600e+02, (g)234.56, (a)0x1.d51eb8p+7
scanning 0x1.d51eb8p+7
scanned with f: (f)234.559998, (e)2.345600e+02, (g)234.56, (a)0x1.d51eb8p+7
scanned with g: (f)234.559998, (e)2.345600e+02, (g)234.56, (a)0x1.d51eb8p+7
scanned with e: (f)234.559998, (e)2.345600e+02, (g)234.56, (a)0x1.d51eb8p+7
scanned with a: (f)234.559998, (e)2.345600e+02, (g)234.56, (a)0x1.d51eb8p+7

但这不是错的吗?%e%f 有不同的行为,其他的像 %e 也不完全等同于 %g。它们中没有一个直接相当于 %f。这个声明还意味着 %e 等同于 %E,这也不是完全正确的事实。这个引用的共识是:所有都是等同的,这相当荒谬。我无法相信这是写在 Linux 的 man 页面上的。 - RobertS supports Monica Cellio
1
@RobertS-ReinstateMonica - 不是这样的:所有scanf()系列的浮点扫描程序都接受任何有效的浮点表示,包括printf()系列产生的%a%A十六进制表示。指定%lf不会阻止您输入6.0221409e+23并获得阿伏伽德罗常数的近似值。 - Jonathan Leffler
@JonathanLeffler 是的,我知道他们可能会接受所有给定的浮点值表示,但它们的行为,如何精确地转换给定的值,略有不同。在这种情况下,我会跟随您的意见,并说他们都会表现得一样,我的问题也可以这样表达:如果它们在行为上都是等效的,为什么标准提供了这么多的说明符号?并且通过等效,我指的是它们的行为完全相同。 - RobertS supports Monica Cellio
1
@RobertS-ReinstateMonica — scanf()函数接受多个浮点数说明符,以实现与printf()格式的对称性。很长一段时间(直到C99再次出现),scanf()使用%lf来读取double,但是printf()仅使用%f(因为传递给printf()的值始终按照默认参数提升规则从float转换为double)。在C99中,他们添加了%lf作为%f的同义词,以再次改善这两个函数族之间的对称性。 - Jonathan Leffler
@RobertS-ReinstateMonica — 我不确定是否允许您使用 printf("%lf %d %d %lf", dbl1, int1, int2, dbl2); 来编写可以被 scanf("%lf %d %d %lf", &dbl1, &int1, &int2, &dbl2); 读取的数据,但是如果您使用未经过资格认证的转换规范(并且在 printf() 格式字符串的末尾不包括换行符),那么您可能会得到可工作的代码。我小心地避免了 %s;它可以愉快地打印带有空格的数据,但是 scanf() 在第一个空格处停止。一旦您为打印格式添加了几乎任何限定符,它就无法用于输入。 - Jonathan Leffler
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接