在C ++中'printf'与'cout'

printf()cout在C ++中有什么区别?

从C ++ FAQ :

[15.1]为什么我应该使用<iostream>而不是传统的<cstdio>

增加types安全性,减less错误,允许扩展性并提供可inheritance性。

printf()可以说没有被破坏, scanf()虽然易出错,但可能是可以存活的,但是对于C ++ I / O来说,两者都是有限的。 C ++ I / O(使用<<>> )是相对于C(使用printf()scanf() ):

  • 更安全的types:使用<iostream> ,I / O对象的types由编译器静态地知道。 相反, <cstdio>使用“%”字段dynamic地计算出types。
  • 不太容易出错:使用<iostream> ,没有多余的“%”标记必须与实际的I / O对象一致。 删除重复删除一类错误。
  • 可扩展性:C ++ <iostream>机制允许新的用户定义types在不破坏现有代码的情况下进行I / O。 想象一下,如果每个人都同时为printf()scanf()添加新的不兼容的“%”字段,那么混乱就会发生!
  • 可inheritance:C ++ <iostream>机制是由真正的类(如std::ostreamstd::istream )构build的。 不像<cstdio>FILE* ,这些是真实的类,因此是可inheritance的。 这意味着你可以有其他的用户定义的东西,像stream一样的行为,但它可以做任何你想要的奇怪而美妙的事情。 您将自动使用由您甚至不知道的用户编写的大量的I / O代码行,并且不需要知道您的“扩展stream”类。

另一方面, printf显着更快,这可能是非常特殊和有限的情况下优先使用它的理由。 总是首先介绍。 (例如,参见http://programming-designs.com/2009/02/c-speed-test-part-2-printf-vs-cout /)

我很惊讶,在这个问题上的每个人都声称, std::coutprintf ,即使问题只是要求差异。 现在,有一个区别 – std::cout是C ++,而printf是C(但是,可以在C ++中使用它,就像C中的其他任何东西一样)。 现在,我在这里诚实。 printfstd::cout都有它们的优点。

免责声明:我比C ++更有经验,所以如果我的答案有问题,请随时编辑或评论。

真正的差异

可扩展性

std::cout是可扩展的。 我知道人们会说printf也是可扩展的,但是在C标准中没有提到这样的扩展(所以你将不得不使用非标准特性 – 但是不存在通用的非标准特性),而且这样的扩展是一个字母(所以很容易与已经存在的格式冲突)。

printf不同, std::cout完全依赖于操作符重载,所以自定义格式没有问题 – 所有你要做的就是定义一个子程序,把std::ostream作为第一个参数,把你的types作为第二个参数。 因此,没有命名空间问题 – 只要你有一个类(不限于一个字符),你可以有工作std::ostream重载它。

但是,我怀疑很多人会想要扩展ostream (说实话,我很less看到这样的扩展,即使它们很容易做到)。 但是,如果你需要的话,这是在这里。

句法

因为可以很容易地注意到, printfstd::cout使用不同的语法。 printf使用标准函数语法,使用模式string和可变长度参数列表。 实际上, printf是C的一个原因 – printf格式太复杂,没有它们可用。 但是, std::cout使用不同的API – operator << API,它自己返回。

一般来说,这意味着C版本会更短,但在大多数情况下,这并不重要。 打印许多参数时,这种差异是显而易见的。 如果你必须写Error 2: File not found. ,假设错误号,它的描述是占位符,代码将如下所示。 两个例子都是一样的 (好吧, std::endl实际上是刷新缓冲区)。

 printf("Error %d: %s.\n", id, errors[id]); std::cout << "Error " << id << ": " << errors[id] << "." << std::endl; 

虽然这看起来不是太疯狂(只是两倍),但事实上格式化参数会变得更疯狂,而不是仅仅打印参数。 例如,像0x0424这样的0x0424是疯狂的。 这是由std::cout混合状态和实际值引起的。 我从来没有见过一种类似std::setfill的语言,当然是一种types(除了C ++)。 printf显然将参数和实际types分开。 我真的宁愿保持它的printf版本(即使它看起来有点神秘)相比,它的iostream版本(因为它包含太多的噪音)。

 printf("0x%04x\n", 0x424); std::cout << "0x" << std::hex << std::setfill('0') << std::setw(4) << 0x424 << std::endl; 

翻译

这是printf的真正优势所在。 printf格式string是一个string。 与operator <<滥用iostream operator <<相比,这使翻译变得非常容易。 假设gettext()函数转换,并且您想要显示Error 2: File not found. ,以前显示的格式string翻译的代码将如下所示:

 printf(gettext("Error %d: %s.\n"), id, errors[id]); 

现在,我们假设我们翻译成Fictionish,其中错误编号在描述之后。 翻译后的string看起来像%2$s oru %1$d.\n 。 现在,如何在C ++中做到这一点? 那么,我不知道。 我想你可以做假iostream ,它构造了printf ,你可以传递给gettext ,或者用于翻译的目的。 当然, $不是C标准,但是这是很常见的,在我看来是安全的。

不必记住/查找特定的整数types语法

C有很多整型,C ++也是如此。 std::cout为你处理所有types,而printf需要特定的语法,这取决于整数types(有非整数types,但是在printf中实际使用的唯一非整数types是const char * (C string,可以使用std::string to_c方法获得))。 例如,要打印size_t ,需要使用%zd ,而int64_t则需要使用%"PRIu64"d 。 表格可在http://en.cppreference.com/w/cpp/io/c/fprintf和http://en.cppreference.com/w/cpp/types/整理; 。

您不能打印NUL字节, \0

因为printf使用Cstring而不是C ++string,所以不能在没有特定技巧的情况下打印NUL字节。 在某些情况下,可以使用%c'\0'作为参数,虽然这显然是一种破解。

差别没有人关心

性能

更新:事实certificate, iostream是如此之慢,通常比你的硬盘慢(如果你redirect你的程序文件)。 如果需要输出大量数据,禁用与stdio同步可能会有所帮助。 如果性能是一个真正的问题(而不是写几行到标准输出),只需使用printf

每个人都认为他们关心绩效,但是没人会去衡量。 我的答案是无论如何,I / O都是瓶颈,不pipe你使用printf还是iostream 。 我认为printf 可以快速查看程序集(使用-O3编译器选项使用clang编译)。 假设我的错误示​​例, printf示例比cout示例的调用方式less得多。 这是int main printf int main

 main: @ @main @ BB#0: push {lr} ldr r0, .LCPI0_0 ldr r2, .LCPI0_1 mov r1, #2 bl printf mov r0, #0 pop {lr} mov pc, lr .align 2 @ BB#1: 

你可以很容易地注意到,两个string,和2 (数字)被推送为printf参数。 这是关于它; 没有别的。 为了比较,这是iostream编译汇编。 不,没有内联; 每一个单一的operator <<电话意味着另一个电话与另一组参数。

 main: @ @main @ BB#0: push {r4, r5, lr} ldr r4, .LCPI0_0 ldr r1, .LCPI0_1 mov r2, #6 mov r3, #0 mov r0, r4 bl _ZSt16__ostream_insertIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_PKS3_l mov r0, r4 mov r1, #2 bl _ZNSolsEi ldr r1, .LCPI0_2 mov r2, #2 mov r3, #0 mov r4, r0 bl _ZSt16__ostream_insertIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_PKS3_l ldr r1, .LCPI0_3 mov r0, r4 mov r2, #14 mov r3, #0 bl _ZSt16__ostream_insertIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_PKS3_l ldr r1, .LCPI0_4 mov r0, r4 mov r2, #1 mov r3, #0 bl _ZSt16__ostream_insertIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_PKS3_l ldr r0, [r4] sub r0, r0, #24 ldr r0, [r0] add r0, r0, r4 ldr r5, [r0, #240] cmp r5, #0 beq .LBB0_5 @ BB#1: @ %_ZSt13__check_facetISt5ctypeIcEERKT_PS3_.exit ldrb r0, [r5, #28] cmp r0, #0 beq .LBB0_3 @ BB#2: ldrb r0, [r5, #39] b .LBB0_4 .LBB0_3: mov r0, r5 bl _ZNKSt5ctypeIcE13_M_widen_initEv ldr r0, [r5] mov r1, #10 ldr r2, [r0, #24] mov r0, r5 mov lr, pc mov pc, r2 .LBB0_4: @ %_ZNKSt5ctypeIcE5widenEc.exit lsl r0, r0, #24 asr r1, r0, #24 mov r0, r4 bl _ZNSo3putEc bl _ZNSo5flushEv mov r0, #0 pop {r4, r5, lr} mov pc, lr .LBB0_5: bl _ZSt16__throw_bad_castv .align 2 @ BB#6: 

不过,说实话,这并不意味着什么,因为I / O是瓶颈。 我只是想表明iostream不是更快,因为它是“types安全的”。 大多数C实现使用计算goto来实现printf格式,因此即使没有编译器意识到printfprintf也是如此快(不是它们不是 – 某些编译器在某些情况下可以优化printf – 常量string以\n通常被优化puts )。

遗产

我不知道你为什么要inheritanceostream ,但我不在乎。 也可以使用FILE

 class MyFile : public FILE {} 

types安全

确实,变长参数列表没有安全性,但这并不重要,因为如果启用了警告,stream行的C编译器可以检测到printf格式string的问题。 实际上,Clang可以在不启用警告的情况下做到这一点。

 $ cat safety.c #include <stdio.h> int main(void) { printf("String: %s\n", 42); return 0; } $ clang safety.c safety.c:4:28: warning: format specifies type 'char *' but the argument has type 'int' [-Wformat] printf("String: %s\n", 42); ~~ ^~ %d 1 warning generated. $ gcc -Wall safety.c safety.c: In function 'main': safety.c:4:5: warning: format '%s' expects argument of type 'char *', but argument 2 has type 'int' [-Wformat=] printf("String: %s\n", 42); ^ 

我引用 :

在高层次上,主要的区别是types安全(cstdio没有它),性能(大多数iostreams实现比cstdio慢)和可扩展性(iostreams允许自定义输出目标和用户定义types的无缝输出)。

人们经常声称printf要快得多。 这在很大程度上是一个神话。 我刚刚testing了一下,结果如下:

 cout with only endl 1461.310252 ms cout with only '\n' 343.080217 ms printf with only '\n' 90.295948 ms cout with string constant and endl 1892.975381 ms cout with string constant and '\n' 416.123446 ms printf with string constant and '\n' 472.073070 ms cout with some stuff and endl 3496.489748 ms cout with some stuff and '\n' 2638.272046 ms printf with some stuff and '\n' 2520.318314 ms 

结论:如果你只需要换行符,使用printf ; 否则, cout几乎一样快,甚至更快。 更多细节可以在我的博客上find。

要清楚的是,我并不是说iostream总是比printf ; 我只是想说,你应该根据真实的数据做出明智的决定,而不是基于一些常见的误导性假设的猜测。

更新:这是我用于testing的完整代码。 用g++编译没有任何额外的选项(除了-lrt的时间)。

 #include <stdio.h> #include <iostream> #include <ctime> class TimedSection { char const *d_name; timespec d_start; public: TimedSection(char const *name) : d_name(name) { clock_gettime(CLOCK_REALTIME, &d_start); } ~TimedSection() { timespec end; clock_gettime(CLOCK_REALTIME, &end); double duration = 1e3 * (end.tv_sec - d_start.tv_sec) + 1e-6 * (end.tv_nsec - d_start.tv_nsec); std::cerr << d_name << '\t' << std::fixed << duration << " ms\n"; } }; int main() { const int iters = 10000000; char const *text = "01234567890123456789"; { TimedSection s("cout with only endl"); for (int i = 0; i < iters; ++i) std::cout << std::endl; } { TimedSection s("cout with only '\\n'"); for (int i = 0; i < iters; ++i) std::cout << '\n'; } { TimedSection s("printf with only '\\n'"); for (int i = 0; i < iters; ++i) printf("\n"); } { TimedSection s("cout with string constant and endl"); for (int i = 0; i < iters; ++i) std::cout << "01234567890123456789" << std::endl; } { TimedSection s("cout with string constant and '\\n'"); for (int i = 0; i < iters; ++i) std::cout << "01234567890123456789\n"; } { TimedSection s("printf with string constant and '\\n'"); for (int i = 0; i < iters; ++i) printf("01234567890123456789\n"); } { TimedSection s("cout with some stuff and endl"); for (int i = 0; i < iters; ++i) std::cout << text << "01234567890123456789" << i << std::endl; } { TimedSection s("cout with some stuff and '\\n'"); for (int i = 0; i < iters; ++i) std::cout << text << "01234567890123456789" << i << '\n'; } { TimedSection s("printf with some stuff and '\\n'"); for (int i = 0; i < iters; ++i) printf("%s01234567890123456789%i\n", text, i); } } 

一个是打印到标准输出的function。 另一个是提供了几个成员函数和operator<<重载到stdout的对象。 还有更多的差异,我可以列举,但我不知道你在做什么。

对我来说,真正的差异将使我去'cout'而不是'printf':

1) <<运算符可以为我的类重载。

2)输出stream可以很容易地更改为一个文件:(:复制粘贴:)

 #include <iostream> #include <fstream> using namespace std; int main () { cout << "This is sent to prompt" << endl; ofstream file; file.open ("test.txt"); streambuf* sbuf = cout.rdbuf(); cout.rdbuf(file.rdbuf()); cout << "This is sent to file" << endl; cout.rdbuf(sbuf); cout << "This is also sent to prompt" << endl; return 0; } 

3)我觉得更可读,特别是当我们有很多参数。

cout一个问题是格式化选项。 在printf格式化数据(精度,理由等)更容易。

在这里没有提到的两点我觉得有意义:

1)如果你还没有使用STL, cout带有很多的行李。 它为printf添加了两倍的代码。 这对string也是如此,这是我倾向于使用我自己的string库的主要原因。

2) cout使用重载的<<运算符,我发现不幸。 如果您也使用<<操作符来达到其预期目的(左移),则会增加混淆。 我个人不喜欢超载运营商的目的是切合他们的预期用途。

底线:如果我已经使用STL,我将使用cout (和string )。 否则,我倾向于避免它。

对于原语,完全可以使用哪一个并不重要。 我说什么它得到有用的是当你想输出复杂的对象。

例如,如果你有一个class级,

 #include <iostream> #include <cstdlib> using namespace std; class Something { public: Something(int x, int y, int z) : a(x), b(y), c(z) { } int a; int b; int c; friend ostream& operator<<(ostream&, const Something&); }; ostream& operator<<(ostream& o, const Something& s) { o << sa << ", " << sb << ", " << sc; return o; } int main(void) { Something s(3, 2, 1); // output with printf printf("%i, %i, %i\n", sa, sb, sc); // output with cout cout << s << endl; return 0; } 

现在上面可能看起来不是那么好,但是假设你必须在你的代码中的多个地方输出这个。 不仅如此,假设您添加一个字段“int d”。 随着cout,你只需要在一次地方改变它。 但是,对于printf,你可能需要在很多地方改变它,不仅如此,你还得提醒自己输出哪些东西。

有了这个说法,使用cout,您可以减less很多次维护代码的时间,而不仅仅是如果您在新的应用程序中重新使用“Something”对象,您不必担心输出。

我想说printf可扩展性不足并不完全正确:
在C中,这是事实。 但是在C中,没有真正的类。
在C ++中,可以重载转换运算符,因此,重载一个char*运算符并使用printf如下所示:

 Foo bar; ...; printf("%s",bar); 

如果Foo超载好操作符可能是可能的。 或者如果你做了一个好方法。 总之, printfcout一样可扩展。

技术参数,我可以看到的C + +stream(一般来说…不仅是cout)是:

  • types安全。 (顺便说一下,如果我想打印一个'\n'我用putchar('\n') …我不会用核弹来杀死昆虫。)

  • 更简单的学习。 (没有“复杂”的参math习,只需要使用<<>>运算符)

  • 本地工作与std::string (对于printfstd::string::c_str() ,但对于scanf ?)

对于printf我看到:

  • 更容易,或者至less更短(根据字符写)复杂的格式。 对我来说 ,更具可读性(我猜想,品味的问题)。

  • 更好地控制函数做了什么(返回写入的字符数和%n格式化程序:“没有打印出来,参数必须是一个指向有符号整数的指针,在这里存储了迄今为止写入的字符数。”(来自printf – C ++参考 )

  • 更好的debugging可能性 出于同样的原因,最后一个论点

我个人的偏好转到printf (和scanf )函数,主要是因为我喜欢短线,而且我不认为打印文本时的types问题真的很难避免。 我唯一对C风格函数感到愤慨的是不支持std::string 。 我们必须经过一个char*然后把它传给printf (如果我们想要读取的话,使用std::string::c_str() ,但是如何写?)

更多的区别:“printf”返回一个整数值(等于打印的字符数),“cout”不返回任何东西

和。

cout << "y = " << 7; 不是primefaces的。

printf("%s = %d", "y", 7); 是primefaces的。

cout执行types检查,printf不。

没有iostream相当于"% d"

TL; DR:在生成机器代码大小性能可读性编码时间之前,请始终做好自己的研究,然后再相信随机的在线评论,包括这一个。

我不是专家。 我碰巧听到两名同事在谈论我们应该如何避免在embedded式系统中使用C ++,因为性能问题。 好吧,有趣的是,我做了一个基于真实项目任务的基准。

在上述任务中,我们不得不向RAM写入一些configuration。 就像是:

咖啡=热
糖=无
牛奶=乳房
MAC = AA:BB:CC:DD:EE:FF

这里是我的基准testing程序(是的,我知道OP询问了printf(),而不是fprintf()。试图捕捉这个本质,顺便说一句,OP的链接指向fprintf()。

C程序:

 char coffee[10], sugar[10], milk[10]; unsigned char mac[6]; /* Initialize those things here. */ FILE * f = fopen("a.txt", "wt"); fprintf(f, "coffee=%s\nsugar=%s\nmilk=%s\nmac=%02X:%02X:%02X:%02X:%02X:%02X\n", coffee, sugar, milk, mac[0], mac[1],mac[2],mac[3],mac[4],mac[5]); fclose(f); 

C ++程序:

 //Everything else is identical except: std::ofstream f("a.txt", std::ios::out); f << "coffee=" << coffee << "\n"; f << "sugar=" << sugar << "\n"; f << "milk=" << milk << "\n"; f << "mac=" << (int)mac[0] << ":" << (int)mac[1] << ":" << (int)mac[2] << ":" << (int)mac[3] << ":" << (int)mac[4] << ":" << (int)mac[5] << endl; f.close(); 

我已经尽了最大的努力去磨光他们,然后我把他们都打了100,000次。 结果如下:

C程序:

 real 0m 8.01s user 0m 2.37s sys 0m 5.58s 

C ++程序:

 real 0m 6.07s user 0m 3.18s sys 0m 2.84s 

对象文件大小:

 C - 2,092 bytes C++ - 3,272 bytes 

结论:在我的具体平台上 ,使用一个非常特定的处理器 ,运行一个特定版本的Linux内核 ,运行一个编译了一个非常特定版本的GCC的程序 ,为了完成一个非常具体的任务 ,我会说C ++方法更适合,因为它运行速度更快,并提供更好的可读性。 另一方面,C提供的占用空间很小,在我看来,几乎没有任何意义,因为程序大小并不是我们所关心的。

记住,YMMV。

当然,你可以写一些更好的东西来保持维护:

 #include <iostream> #include <cstdlib> using namespace std; class Something { public: Something(int x, int y, int z) : a(x), b(y), c(z) { } int a; int b; int c; friend ostream& operator<<(ostream&, const Something&); void print() const { printf("%i, %i, %i\n", a, b, c); } }; ostream& operator<<(ostream& o, const Something& s) { o << sa << ", " << sb << ", " << sc; return o; } int main(void) { Something s(3, 2, 1); // Output with printf s.print(); // Simple as well, isn't it? // Output with cout cout << s << endl; return 0; } 

如果有人想做更多的testing(Visual Studio 2008,可执行文件的发布版本),那么cout和printf的一些扩展testing增加了一个“double”的testing:

 #include <stdio.h> #include <iostream> #include <ctime> class TimedSection { char const *d_name; //timespec d_start; clock_t d_start; public: TimedSection(char const *name) : d_name(name) { //clock_gettime(CLOCK_REALTIME, &d_start); d_start = clock(); } ~TimedSection() { clock_t end; //clock_gettime(CLOCK_REALTIME, &end); end = clock(); double duration = /*1e3 * (end.tv_sec - d_start.tv_sec) + 1e-6 * (end.tv_nsec - d_start.tv_nsec); */ (double) (end - d_start) / CLOCKS_PER_SEC; std::cerr << d_name << '\t' << std::fixed << duration * 1000.0 << " ms\n"; } }; int main() { const int iters = 1000000; char const *text = "01234567890123456789"; { TimedSection s("cout with only endl"); for (int i = 0; i < iters; ++i) std::cout << std::endl; } { TimedSection s("cout with only '\\n'"); for (int i = 0; i < iters; ++i) std::cout << '\n'; } { TimedSection s("printf with only '\\n'"); for (int i = 0; i < iters; ++i) printf("\n"); } { TimedSection s("cout with string constant and endl"); for (int i = 0; i < iters; ++i) std::cout << "01234567890123456789" << std::endl; } { TimedSection s("cout with string constant and '\\n'"); for (int i = 0; i < iters; ++i) std::cout << "01234567890123456789\n"; } { TimedSection s("printf with string constant and '\\n'"); for (int i = 0; i < iters; ++i) printf("01234567890123456789\n"); } { TimedSection s("cout with some stuff and endl"); for (int i = 0; i < iters; ++i) std::cout << text << "01234567890123456789" << i << std::endl; } { TimedSection s("cout with some stuff and '\\n'"); for (int i = 0; i < iters; ++i) std::cout << text << "01234567890123456789" << i << '\n'; } { TimedSection s("printf with some stuff and '\\n'"); for (int i = 0; i < iters; ++i) printf("%s01234567890123456789%i\n", text, i); } { TimedSection s("cout with formatted double (width & precision once)"); std::cout << std::fixed << std::scientific << std::right << std::showpoint; std::cout.width(8); for (int i = 0; i < iters; ++i) std::cout << text << 8.315 << i << '\n'; } { TimedSection s("cout with formatted double (width & precision on each call)"); std::cout << std::fixed << std::scientific << std::right << std::showpoint; for (int i = 0; i < iters; ++i) { std::cout.width(8); std::cout.precision(3); std::cout << text << 8.315 << i << '\n'; } } { TimedSection s("printf with formatted double"); for (int i = 0; i < iters; ++i) printf("%8.3f%i\n", 8.315, i); } } 

结果是:

 cout with only endl 6453.000000 ms cout with only '\n' 125.000000 ms printf with only '\n' 156.000000 ms cout with string constant and endl 6937.000000 ms cout with string constant and '\n' 1391.000000 ms printf with string constant and '\n' 3391.000000 ms cout with some stuff and endl 9672.000000 ms cout with some stuff and '\n' 7296.000000 ms printf with some stuff and '\n' 12235.000000 ms cout with formatted double (width & precision once) 7906.000000 ms cout with formatted double (width & precision on each call) 9141.000000 ms printf with formatted double 3312.000000 ms 

I'd like to point out that if you want to play with threads in C++, if you use cout you can get some interesting results.

考虑这个代码:

 #include <string> #include <iostream> #include <thread> using namespace std; void task(int taskNum, string msg) { for (int i = 0; i < 5; ++i) { cout << "#" << taskNum << ": " << msg << endl; } } int main() { thread t1(task, 1, "AAA"); thread t2(task, 2, "BBB"); t1.join(); t2.join(); return 0; } // g++ ./thread.cpp -o thread.out -ansi -pedantic -pthread -std=c++0x 

Now, the output comes all shuffled. It can yield different results too, try executing several times:

 ##12:: ABABAB ##12:: ABABAB ##12:: ABABAB ##12:: ABABAB ##12:: ABABAB 

You can use printf to get it right, or you can use mutex .

 #1: AAA #2: BBB #1: AAA #2: BBB #1: AAA #2: BBB #1: AAA #2: BBB #1: AAA #2: BBB 

玩的开心!

 cout<< "Hello"; printf("%s", "Hello"); 

Both are used to print values. They have completely different syntax. C++ has both, C only has printf.

I'm not a programmer, but I have been a human factors engineer. I feel a programming language should be easy to learn, understand and use, and this requires that it have a simple and consistent linguistic structure. Although all the languages is symbolic and thus, at its core, arbitrary, there are conventions and following them makes the language easier to learn and use.

There are a vast number of functions in C++ and other languages written as function(parameter), a syntax that was originally used for functional relationships in mathematics in the pre-computer era. printf() follows this syntax and if the writers of C++ wanted to create any logically different method for reading and writing files they could have simply created a different function using a similar syntax.

In Python we of course can print using the also fairly standard object.method syntax, ie variablename.print, since variables are objects, but in C++ they are not.

I'm not fond of the cout syntax because the << operator does not follow any rules. It is a method or function, ie it takes a parameter and does something to it. However it is written as though it were a mathematical comparison operator. This is a poor approach from a human factors standpoint.

printf() is a function whereas cout is a variable.