在C++中使用C语言特性是否是不好的实践?

18

例如使用printf代替coutscanf代替cin,使用#define宏等等?

10个回答

18

我不会说这是坏的,因为这将取决于个人选择。我的策略是,在C++中存在类型安全的替代方案时,请使用它们,因为这将减少代码中的错误。


有没有在线文档可以查看一个函数是否类型安全? - Gustavo Puma
9
如果一个函数接受 void* 参数或返回 void* 结果,那么它可能不是类型安全的。带有可变参数列表的函数(如 printf)也通常不是类型安全的。每当你需要使用 sizeof 时,该代码很可能就不是类型安全的。 - Kristopher Johnson
1
@vash47:我不知道有任何出版物,但您可以使用以下规则作为起点。可变参数函数(如printf和scanf)不是类型安全的,因为编译器无法验证可变参数是否具有正确的类型(除了一些特殊情况)。宏也比内联函数不够类型安全,原因相同。 - Bart van Ingen Schenau
1
@vash47: 这是语言的含义。C++试图使绕过类型更加困难。Naveen提到了C++版本的C等价物,其中涉及模板和类似的内容。看一下你本地(且缓慢)的libstdc++。 - Matt Joiner
它不仅是类型安全,还具有良好的可扩展性。大多数编译器至少可以发出警告以诊断 printf 的参数是否为正确类型,但即使编译器确保您不会在这里犯错,也不能传递任何用户定义的类型,因此重载 operator<< 并使用 cout 是解决需要在 C 中更改语法的问题的解决方案:std::cout << "My object is:" << obj << std::ednl; 相比之下,printf("My object is: "); obj.print(); printf("\n"); - David Rodríguez - dribeas

15
这取决于哪些特性。在C++中使用define宏被强烈反对,这是有充分理由的。在C++中,您几乎总是可以用更可维护和安全的东西(模板、内联函数等)来替换define宏的使用。
另一方面,许多人认为流(stream)非常慢,我看过许多有效且高质量的C++代码都使用C的FILE*及其众多函数。
还有一件事:尽管有各种流格式化可能性,但对于像简单的调试打印之类的东西,我个人认为您无法击败printf及其格式化字符串的简洁性。

4
根据使用情况而定。Boost(被广泛认为是 C++ 最好的库集合)大量使用宏。 - Konrad Rudolph
1
@ Konrad,同意,尽管在任何尊重的C++指南(如Effective C ++)中,您都会发现整个章节专门介绍了为什么不建议在C++中使用宏。此外,按照什么标准来判断Boost“最佳”?你认为它的内部实现最易读吗? - Eli Bendersky
1
@Eli:我说的是接口/功能,而不是实现。但宏对此很重要:如果没有宏的使用,Boost就无法提供它提供的接口(在某些情况下),并且在调试和维护方面会更加困难(在其他情况下)。因此,即使Boost的实现通常很混乱(主要是为了迎合不兼容的编译器),宏也使代码更加简单易懂。话虽如此,我真的尽量避免使用宏(在“正常”代码中根本不使用它们)。 - Konrad Rudolph
1
我也喜欢printf,但是你不应该推荐它而忽略其缺点:缺乏类型安全性和很容易搞砸规范和参数之间的匹配。 - Mark Ransom
1
@Mark:虽然如此,它仍然比iostream好用;-) - Eli Bendersky
显示剩余3条评论

8
我认为唯一真正有害的混合是在 malloc/freenew/delete 之间。其他情况下,这只是一个风格问题...虽然 C 兼容 C++,但既然 C++ 拥有一切所需而不需要回退,为什么要混合两种语言呢?

7
printf 提供了一种精确格式化输出的机制,而我在 cout 中找不到类似的功能。 - user191776
3
@crypto - cout 提供格式化选项:http://www.arachnoid.com/cpptutor/student3.html - Justin Niessner
6
cout允许你精确地格式化输出。但是使用cout完成此任务的方式通常比printf更复杂(反之亦然)。 - Kristopher Johnson
@bk1e - 天啊...我是说免费的。早上太长了。已修复。 - Justin Niessner

8
你应该使用printf代替cout。后者虽然能够实现printf的大部分或全部格式控制,但是它以一种有状态的方式实现。也就是说,当前的格式模式被存储为(全局)对象的一部分。这意味着糟糕的代码可能会使cout处于一个状态,导致随后的输出格式错误,除非你每次使用它都重置所有格式设置。它还会对线程使用造成破坏。

阿门,我们都有保卫这个立场的战斗伤疤 ;) - Matt Joiner
2
+1,我完全同意。编程中的副作用是邪恶的,而这个库标准却强制实施了这种不良行为 :-( - Jens Gustedt
当然,std::cout 的整个设计是作为一个存储状态的对象。它的主要问题在于缺少复制构造函数;你应该能够轻松地复制、设置状态、执行输出,然后丢弃副本而不影响原始对象。 - MSalters
1
@MSalters:当你考虑到缓冲和与线程的关系时,这变得更加复杂。我相信可以通过共享隐藏的内部对象来实现缓冲和锁定,并在包装器对象中保留本地状态,但我认为这只是糟糕的设计。 - R.. GitHub STOP HELPING ICE
如今设计的std::cout已经很复杂了。如果格式化程序状态是一个单独的对象,那么您可以允许线程拥有独立的对象。如果有什么问题,这将简化多线程开发。 - MSalters

7

大多数情况下都有更好的解决方案,但并非所有情况。

例如,人们经常使用 memcpy。我几乎不会这样做(除了在真正的低级代码中)。我总是使用 std::copy,即使在指针上也是如此。

输入/输出例程也是如此。但确实有时,C风格的 printfcout(特别是在日志记录中)更容易使用。如果不能使用 Boost.Format,那么当然可以使用 C。

#define 是完全不同的东西。它并不是仅限于 C 的特性,在 C++ 中有许多合法的用途。(但有更多不合法的用途。)

当然,您永远不会使用它来定义常量(这就是 const 的作用),也不会用来声明内联函数(使用 inline 和模板!)。

另一方面,生成调试断言通常很有用,并且通常作为代码生成工具。例如,我正在对类模板进行单元测试,如果没有广泛使用宏,这将是一个真正的痛苦。在这里使用宏并不好,但可以节省成千上万行代码。


2

对于内存分配,我建议避免使用malloc/free而是坚持使用new/delete。


0

实际上,printf()cout要快得多,而且C++的iostream库非常庞大。这取决于用户的偏好或程序本身(是否需要等等)。此外,scanf()已经不适合使用了,我更喜欢使用fgets()


3
“printf比cout快”这种说法是错误的传说。这取决于标准库的实现以及流是否被缓存(但这对于C和C++流是相同的!)。printf通常cout快。甚至可能相反。此外,fgetsscanf是完全不同的函数(scanf提供格式化输入),而fgets可以被cin替代。 - Konrad Rudolph
1
很确定他的意思是使用fgets和sscanf。 - FastAl

0

能否使用取决于将要使用的编译器。由于您正在使用C ++进行编程,我认为为了最大化兼容性,最好使用C ++提供的内容,而不是C函数,除非您没有其他选择。


0

从稍微不同的角度来看,我会说在C语言中使用scanf是不好的,更不用说在C++中了。用户输入太过变化无常,无法可靠地通过scanf进行解析。


-1

我本来想在另一个回复下发表评论,但是由于无法... C的printf()比C++的iostream更好,因为它支持国际化。想要翻译一个字符串并将嵌入的数字放在不同的位置?使用ostream无法实现。printf()的格式规范是一个完整的小语言,可以在运行时解释。


1
在为商业产品进行翻译时,我发现两者都不太适合。字符串“我有%d个小部件”无法翻译。即使是在英语中,你也需要第二个字符串,“我有%d个小部件”或“我有1个小部件”(因为在英语中,这是唯一具有不同形式的值)。在其他语言中,根据%d的值,您最多可以有4种形式。更不用说当您将格式字符串交给翻译人员时会发生什么了。很有可能其中至少一个会破坏%d,例如将其更改为%d。 - MSalters

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