在使用printf时,%f和%lf可以互换使用吗?

3
在w3resource.com的一篇文章中,我看到了以下代码:
double x, y;
pr1 = sqrt(pr1);
x = (-b + pr1)/(2*a);
y = (-b - pr1)/(2*a);
printf("Root1 = %.5lf\n", x);
printf("Root1 = %.5lf\n", y);

文章中提到要使用三个浮点数。但在他们的解决方案(这段代码片段)中,他们使用了`%lf`格式说明符。 为什么他们使用`%lf`而不是`%f`? 我能否也使用`%f`来表示浮点数?
如果我使用`float`关键字而不是`double`,并且使用`%f`,会有什么问题吗? 对我来说输出结果是一样的。

4
这要看情况。如果上下文是printf(),那么%lf%f可以互换使用。如果上下文是scanf(),那么%f用于读取到float变量中,而%lf用于读取到double变量中(%Lf用于读取到long double变量中),这个区别非常重要。 - Jonathan Leffler
4
这取决于情况。如果上下文是printf(),那么%lf%f可以互换使用。如果上下文是scanf(),那么%f用于读取到float变量中,而%lf用于读取到double变量中(%Lf用于读取到long double变量中),这个区别非常重要。 - Jonathan Leffler
3
printf()中,%lf%f是可以互换使用的,因为任何类型为float的可变参数都会根据适用于所有函数参数的默认参数提升规则转换为double。相同的规则对于类型为float *double *的参数没有影响(它们不会被转换为不同的类型),所以在scanf()中需要区分%f%lf - Ian Abbott
3
printf()中,%lf%f是可以互换使用的,因为任何类型为float的可变参数都会根据适用于所有函数参数的默认参数提升规则转换为double。相同的规则对于类型为float *double *的参数没有影响(它们不会被转换为不同的类型),所以在scanf()中需要区分%f%lf - Ian Abbott
3
printf()中,%lf%f是可以互换使用的,因为任何类型为float的可变参数都会根据适用于所有函数参数的默认参数提升规则转换为double类型,而这些参数的类型没有被在作用域内的函数原型指定。相同的规则对于类型为float *double *的参数没有影响(它们不会被转换为不同的类型),所以在scanf()中需要区分%f%lf - undefined
显示剩余7条评论
2个回答

6

%fprintfscanf系列函数中有不同的含义:

  • printf中,%f%lf是等价的,两者都匹配double。这是因为在可变参数函数中,当调用函数时,所有的float参数都会被提升为double,所以在语言层面上传递floatdouble没有区别。
  • scanf中,%f匹配一个float*%lf匹配一个double*,这个区别非常重要。 使用错误类型的指针将导致未定义行为。

您还可以使用cdecl+更好地理解printfscanf的调用。 输出如下:

printf("Root1 = %.5lf\n", x)
Write "Root1 = "
Write a decimal double with lower-case infinity/NaN symbols
    precision: 5
Write "\n"

ARGUMENTS & EXPECTED TYPES
--------------------------
x (double)
当有疑问时,在floatdouble中使用%f%lf,即使在printf中也是如此,两者之间没有区别。 否则,会在scanfprintf的格式字符串之间引入无意义的不一致。
关于浮点数的注意事项
你可能会对float浮点数的含义感到困惑。 在C语言中,floatdoublelong double等都是浮点数。 当文章提到浮点数时,它们可能也指的是double_Decimal32_Float128等。

为什么scanf不像printf那样将float*提升为double*
这是不可能的,因为我们存储float的对象不能用来存储double

  • 在语言层面上,这将是一个严格别名违规
  • 在实现层面上,我们会尝试将8个字节的double存储在由4个字节组成的float中,这是不可能的。

2
从C标准(7.21.6.1 fprintf函数)中可以看出: l(小写的L)指定后面的d、i、o、u、x或X转换说明符适用于long int或unsigned long int参数;指定后面的n转换说明符适用于指向long int参数的指针;指定后面的c转换说明符适用于wint_t参数;指定后面的s转换说明符适用于指向wchar_t参数的指针;或对后面的a、A、e、E、f、F、g或G转换说明符没有影响。
另外,在函数调用中(6.5.2.2 Function calls):
如果表示被调用函数的表达式具有包含原型的类型,则参数会隐式转换,就像通过赋值一样,转换为相应参数的类型,将每个参数的类型视为其声明类型的未限定版本。在函数原型声明符中的省略号表示在最后一个声明的参数之后停止参数类型转换。默认参数提升将在尾部参数上执行。
6 如果表示调用函数的表达式没有包含原型,那么对每个参数执行整数提升,并将类型为float的参数提升为double。这些被称为默认参数提升。
使用长度修饰符l与转换说明符f一起使用只会让代码的读者感到困惑。它的使用会引发问题。代码的读者会认为,要么代码的作者不知道长度修饰符没有效果,要么在没有长度修饰符的情况下调用printf(或fprintf)确实会产生未定义的行为,因此使用长度修饰符是必要的。
关于在调用scanf时使用长度修饰符,函数处理的是float *double *类型的指针。也就是说,它们不会应用默认参数提升。因此,为了区分指针是指向float类型的对象还是指向double类型的对象,您必须在double *类型的指针上使用长度修饰符l与转换说明符f,而在float *类型的指针上则不需要长度修饰符。
热衷于在调用printf(或fprintf)时使用长度修饰符l来处理double类型对象的人只会使代码难以维护,因为例如,如果输出变量的类型从double更改为float,他们将不得不找到并更改所有调用printf的地方,其中使用了该变量。

1
显然,这是一个基于个人观点的话题,但就我个人而言,我发现在任何地方都一致使用%lf来表示double类型更好,即使在printf中你可以更简洁。从技术上讲,你不需要在很多地方显式地进行类型转换,但这样做可以清楚地传达你的意图。同样地,在printf中你也不需要使用%lf,但它可以表明你打算打印一个double类型,而不是一个float类型。 - Jan Schultke
1
显然,这是一个非常基于个人观点的话题,但就我个人而言,无论在何处,我发现一直使用%lf来表示double更好,即使在printf中可以更简洁。从技术上讲,你不需要在很多地方显式转换,但这样做可以清楚地传达你的意图。同样,在printf中你也不需要使用%lf,但它传达了你打算打印一个double,而不是一个float - Jan Schultke
1
显然,这是一个非常主观的话题,但就个人而言,我发现在所有地方一致地使用%lf来表示double类型更好,即使在printf中你可以更简洁。从技术上讲,你在很多地方不需要显式地进行类型转换,但这样做可以清楚地传达你的意图。同样地,在printf中你不需要使用%lf,但它可以传达你打算打印一个double而不是一个float - undefined
@JanSchultke 即使是为初学者编写代码(非专业代码),在这种情况下使用%lf也是一个不好的主意,因为初学者会认为根据C标准,必须使用长度修饰符来处理double类型的对象,以避免未定义的行为。:) 尽管在初学者的书籍中,我还没有遇到过使用%lf而不是只用%f的建议。:) - Vlad from Moscow
这不是关于“初学者”的问题,而是关于共同合作开发软件项目并做出明智的风格决策。当没有严格必要时,使用%lf不会造成任何损害,但如果你在项目中使用了double,但为了方便起见选择编写%f,将格式字符串从printf复制粘贴到scanf中却可能造成实际的伤害。在现实世界中,没有人关心你是一个掌握了所有技巧以节省仅有的一个字符的l33t开发者。人们关心的是一致、易于遵循并能完成工作的规则。 - Jan Schultke
显示剩余11条评论

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