我不知道如何准确地表达我想问的问题,但它看起来像这样:
如何在C中打印变量的内容:
char msg = 'k'; printf("%c", msg);如何在C++中打印变量的内容:
char msg = 'k'; cout << msg;
现在的问题是:
在C++中,您不需要为输出指定修饰符,那么为什么C中没有类似物
cout(我知道该语言比C++古老得多,但仍然如此)?他们经常写信给我说
printf需要转换类型:char msg = 'k'; printf("%f", (float)msg);
做什么的?在C++的类比中,您不必这样做!为什么我不能这样写:printf("%f", msg);?是什么原因?printf它不会自己将数据引导到我们指定的修饰符吗?
在 C 语言中,无论变量的类型和值如何,
printf(format, msg)在源代码中它都会调用相同的函数msg。C 是一种静态类型语言,这意味着在printf 执行过程中,函数不知道msg它是什么char,而且 printf 甚至不知道它传递了多少个参数。因此,您必须format在该行中手动设置所需的表示形式:从它所在的内存中加载什么类型的值以及如何格式化该值,以便将结果字节写入stdout 。msg在C++中,调用不同的函数(运算符重载)可以
cout << msg针对不同的类型msg进行编译。您可以轻松定义自己的 operator ,例如,输出向量以进行调试:<<<<例子:
简化一下,您可以想象如果这是, , 如果这是, , 如果这是等,编译器会
cout << msg生成一个调用。在这里,每个函数都知道它接受什么类型,如果不使用 iomanip,则每种类型使用一个默认字节表示)。例如,默认情况下显示它的十进制数字(而不是十六进制或其他)。print_char(msg)msgcharprint_float(msg)msgfloatprint_vector(msg)msgvectorintC11引入了
_Generic,它允许根据要调用的参数 ( ) 的类型来调用不同的函数controlling-expression,因此您可以定义print_arg(arg),这将适用于不同的类型arg(单个参数)。当调用参数中用省略号 ( ) 声明的 printf 之类的可变参数函数时,可以采用编译器未知的
...预期类型的不同数量的参数),默认转换发生:隐式转换为(整数提升)或如果在给定的平台(异国情调)上无法表示价值。类似地,float 在调用时会转换为 double -因此in 不需要它,而只是简单地使用它。formatcharintunsigned intcharintprintf()%lfdoubleprintf()%f对于这种情况,一个简单的模型可以与 printf 一起使用:编译器将参数(int、double 等)放入内存区域,然后 printf 根据行中的说明从那里读取它们
format- printf 充当迷你 - computer: format 指定程序,printf读取va_arg存储其参数的内存 ( )格式化它们并将结果字节写入stdout.(double)msg对象在内存中可以与(int)msg对象不同。因此,printf 可以在内存中看到不同的位模式,因此,结果printf("%f", (int)msg)可以printf("%f", (double)msg)不同(相同的指令:%f应用于内存中的不同内容)。我的机器的示例(内存中的类型可能取决于平台(操作系统 + 处理器)和编译器选项)。我将以十六进制转储的形式显示内存中的字节(例如:6B 16 == 107 10)。
对于
printf("%c", msg):'k'在 C 中有类型int-6B 00 00 00char msg—6Bint-6B 00 00 00%c获取此参数6B 00 00 00并将其转换为unsigned char(6B) 并将相应的字节 (6B) 打印到标准输出。有关格式,请参阅printf 文档c。对于
printf("%f", (float)msg):msg(6B) 转换为(float)msg—00 00 D6 42double-00 00 00 00 00 C0 5A 40%f将内存解释00 00 00 00 00 C0 5A 40为浮点数并以固定格式(6小数位)输出:(107.000000点的字符可能因语言环境而异)。ascii编码对应的字节,写入stdout:31 30 37 2e 30 30 30 30 30 30对于
printf("%f", msg):msg(char - ) 作为 -6B传递给 printfint6B 00 00 00%f将内存解释6B 00 00 00 XX XX XX XX为浮点数5.3e-322(ifXX == 00) 并输出0.000000由于格式不正确,行为是未定义的(未定义行为 - UB),它
printf("%f", msg)可以做任何事情,甚至可以发射火箭。在我的机器上,结果printf("%f", msg)取决于前面的代码,例如:印刷:
但:
印刷:
确保对于编译时已知的格式(例如
"%f\n"),您的编译器会针对无效类型生成警告 - 应避免使用 UB。在这两种情况下char 都作为( )
msg传递并且具有相同的格式,结果应该是相同的 (6B 16 == 107 10 )。int6B 00 00 00您可以使用 C 代码在您的机器上查看变量的内容:
例子:
记忆中的双重可能是什么样的
适用于以下答案的示例
107.0:IEEE 754 双精度数表示为d = ±sign (1 + mantissa / 2 52 ) 2nd order − 1023
符号、尾数和指数以二进制表示形式打包为:
一起:
d = +(1 + 3025855999639552 / 2 52 ) * 2 (1029 - 1023)
= 64 + 3025855999639552 / 70368744177664
= (4503599627370496 + 3025855999639552) / 70368744177664
= 7529455627010048 / 70368744177664
= 107.0
这演示了为什么
107.0它可以在内存中表示为00 00 00 00 00 C0 5A 40.精简 printf 函数的示例
switch用于识别%d格式字符串中的转换说明符 ( )va_arg()加载所需类型的参数itoa()和ftoa()格式分别为 int 和 doublewritec()写一个字节到stdout.这个定义
print()对于代码来说已经足够了:例子:
要编译,定义辅助函数就足够了(
print()必须在定义之前插入):提供了函数定义以便示例可以运行,但实际上它们只是存根(不用于重用),仅用于演示最简单的
print(format, ...)实现之一。这是glibc的完整实现示例
vfprintf()。C++ 语言中的功能与库级别的函数重载
cout机制密切相关,即 事实上,基于库(和用户)可用的函数重载机制的存在,以及伴随的运算符重载机制。重载机制根据对您指定的参数类型的分析来选择输出函数的正确版本。C在用户或库级别没有函数重载和运算符重载的机制。因此,不存在这种表面上“类型无关”的 I/O 操作。
C 标准的 C11 版本引入了一种通用表达式机制,可用于模拟自定义/库函数重载。例如,这种机制在标准头文件中使用(可以
<tgmath.h>使用)以实现“重载”数学宏函数。但是标准库中没有实现“类型无关”的 I/O 函数。如果你愿意,你可以使用这个新机制并尝试自己实现它们。
printf就是所谓的可变参数函数。这个函数的所有参数,除了第一个,都是可变参数。...它们在函数参数列表的声明中匹配此类参数通过特殊的参数传递机制传递。它的特点是函数本身
printf对实际传递的参数类型一无所知,因此,没有任何东西可以独立地得出正确的类型。从纯实用的角度来看,您可以认为所有可变参数都写入连续的二进制流。在内部,该函数
printf将使用va_list/va_start/va_arg. 到底向这个流中写入了什么,函数printf本身无法知道。因此,它会根据您自己从外部传输给它的格式将此二进制流解析为多个部分。如果您以这种格式对她“撒谎”,那么她自己会在不怀疑任何事情的情况下错误地解析这个二进制流。因此,您放入此二进制流中的所有数据都必须与您在格式字符串中指定的格式说明符完全匹配。实现不需要以这种方式实现传递可变参数,但该模型非常准确地说明了传递可变参数的细节。
要工作
cout,运算符重载是必要<<的,并且对于每种类型它都有自己的类型,因此不需要修饰符。在 C 中,运算符不会重载。在我看来,“结果”这个词不太准确,我会说“解释”:
printf它将显示变量的值,就好像它是格式字符串中指定的类型一样。变量不会改变它的值。它本身并没有printf能力在运行时确定参数与格式字符串的对应关系(而且 C 编译器不会教程序员如何生活以及在哪里布置耙子)。因此,如果您不小心在格式字符串中犯了错误并混淆了参数的类型或数量,则可能会出现各种意外情况。