为什么预处理器的使用在C / C ++ / ObjC以外的语言中不常见?

我已经是Java和VB.Net程序员已经有4年了,还有一个C#程序员已经有6个月了。 我还使用了一堆动态语言,如Perl,Python,PHP和JavaScript。

我从来不需要预处理器。

我的问题是:为什么你会在C,C ++和Objective-C中看到如此广泛使用预处理器,但很少(或从不)在Java,C#或Scala等语言中看到它?

我不知道Objective-C,所以我的答案将是在C和C ++中对比预处理器的使用。

由于几个原因,预处理器最初是C所必需的。 如果我没记错的话,原来C没有常数,所以需要#define来避免幻数。 在1999之前,C没有内联函数,因此#define再次用于创建宏或“伪函数”以节省函数调用的开销,同时保持代码结构化。 C也没有运行时或编译时多态,因此条件编译需要#ifdefs。 编译器通常不够聪明,无法优化无法访问的代码,因此,#ifdefs再次用于插入调试或诊断代码。

在C ++中使用预处理器是对C的回归,并且通常不赞成。 语法function,例如常量,内联函数和模板,可以在C中使用预处理器的大多数情况下使用。

在C ++中使用预处理器是可接受的甚至是必要的少数情况包括头文件的保护,以防止多次包含相同的头, #ifdef __cplusplus对C和C ++使用相同的头,__ FILE__和__LINE__用于记录,还有其他一些。

预处理器也经常用于特定于平台的定义,尽管Stephen Dewhurst的C ++ Gotchas建议为平台特定定义分别包含目录,并在每个平台的单独构建配置中使用它们。

您没有看到Java,C#或Scala中使用的预处理器的原因是这些语言显然没有。

C预处理器的一个常见用途是帮助提供特定于平台的代码。 由于C(我在这里包括C ++和Objective-C)是一种需要直接与操作系统接口的低级语言,因此在可移植代码中必须有针对不同操作系统编译的代码的不同部分。 您可以在成熟,高度可移植的代码库(如zlib)中找到此类事物的大量示例。

作为一个简单的例子,要关闭一个网络套接字,必须做这样的事情(在某种程度上,这肯定可以包含在一个函数中但它必须存在于某个地方):

 #ifdef WIN32 closesocket(s); #else close(s); #endif 

在VM上运行的较新语言不需要特定于平台的不同代码段,并且可以针对单个可移植标准库进行编写。

预处理器还提供了一种在C中定义常量的方法,这些方法由较新语言中的其他更好的语言function提供。

在C ++的设计和演变中,Bjarne Stroustrup表示他希望在C ++中删除对预处理器的依赖,但是没有成功。

每种语言都需要一种单独编译的机制。 理想情况下,语言将接口与实现区分开来,模块仅依赖于它导出的模块的接口。 (参见,例如,Ada,Clu,Modula等。)

C没有用于接口或实现的语言构造。 因为不同的.c文件共享单个接口视图是至关重要的,所以编程规则演变为在.h文件中放置声明(即接口)并使用文本包含( #include )共享这些声明/接口。 原则上, #ifdef #define#ifdef可以省略,但#include不能。

如今语言设计者认识到文本包含无法运行铁路,因此语言往往运行到单独编译的接口(Ada,Modula,OCaml),编译器生成的接口(Haskell),或运行到保证接口一致性的动态系统(Java,Smalltalk)。 使用这样的机制,不需要预处理器,并且没有必要使用预处理器(想想源代码分析调试 )。

因为这些语言的设计和目标并不相同。

C是作为一个强大的工具在预处理器中构建的,它用于实现非常基本的东西(例如包含保护),开发人员可以使用它来通过宏优化代码或者可选地包含/排除某些块代码以及其他东西。 C ++inheritance了C的大多数习语,宏不再用于速度了(因为内联被引入)但它仍然用于很多东西,请看post什么是预处理器宏有用?

因为Gosling和Heilsberg都了解滥用预处理所带来的危险和技术债务!

C和C ++中的预处理器有两个不同的function

  • 在构建过程中将文件拉到一起 – 像Java等人的语言。 有自己的机制,如导入这样做

  • 执行文本替换 – 这在C中仍然需要一定程度,但C ++可以更好地使用模板(大部分)

所以C和C ++都需要第一个这样的东西,但是C ++可以把它当作第二个,尽管它在C ++中也很有用 – 请参阅今天早些时候的这个问题 。

我不同意在现代语言中cpp是不必要的似乎正在达成的共识。 我有很多案例,我有相同程序的3个略有不同的版本,我希望能够为每个版本进行一系列更改。 使用CPP,我可以将它们全部放在#if #else块中,我可以在编译行定义#if。 在Java中,我需要创建某种静态全局并在编译时初始化它。 我从来没有让它正常工作。

现代语言将预处理器包含在语言本身中! 对于C ++,预处理器仅用于模块管理和条件包含,例如非常有用。

我认为这是一个单独的工具,因为编译器不是我们今天所知的单一工具。 我听说很老的C编译器用来生成令牌到文件,然后在不同的阶段进行剩余的编译。 我能想到的主要原因是,与我们今天的情况相比,记忆和其他资源非常稀少。

预处理在Java世界中非常普遍。 它用于弥补语言缺乏足够的内置抽象工具,否则会导致无限复制和粘贴的样板代码。

许多人没有意识到这一点的原因是,在Java世界中它被称为“代码生成”而不是“预处理”,因为“预处理器”听起来像讨厌的旧C,而“代码生成”听起来像一个专业工具,有效地成熟企业流程。 尽管如此,它仍然是预处理,即使您必须为不兼容的非标准专有工具支付大笔费用,而不是仅使用内置于该语言中的设施。

您可以确定现代语言是用C或C ++编写的,并且在该实现中本身就有宏。 您需要它们来处理操作系统差异。 动态/更高级语言包装,并隐藏底部某处的许多东西,你需要宏。

此外,宏有时用于速度。 在动态语言中,速度并不是那么重要。

你应该仔细看看Perl。 Perl支持源filter ,它基本上是用Perl编写的自定义Perl预处理器:)

Java旨在避免使C ++难以使用的几个function。

C#复制(或inheritance)Java的大部分设计决策。

更高级的编程语言避免了这种低级别的伪像。