这个问题可能看起来很基础,但对于某些工程(非计算机科学)背景的人来说,不确定 C++ 代码中的 '#
' 片段是什么意思。
快速搜索让我找到了简明易懂的cplusplus预处理指令教程页面。
但为什么要费心去理解预处理指令的概念呢?难道不能编写等效的代码来分配常量值,定义子程序/函数/宏并处理错误吗?
我想最终想知道的是,在什么情况下使用此类预处理指令是好的做法,而在什么情况下不是。
这个问题可能看起来很基础,但对于某些工程(非计算机科学)背景的人来说,不确定 C++ 代码中的 '#
' 片段是什么意思。
快速搜索让我找到了简明易懂的cplusplus预处理指令教程页面。
但为什么要费心去理解预处理指令的概念呢?难道不能编写等效的代码来分配常量值,定义子程序/函数/宏并处理错误吗?
我想最终想知道的是,在什么情况下使用此类预处理指令是好的做法,而在什么情况下不是。
当您需要执行超出实际应用程序范围的操作时,可以使用预处理器指令。例如,您会看到预处理器根据可执行文件构建的架构来包含或不包含代码。例如:
#ifdef _WIN32 // _WIN32 is defined by Windows 32 compilers
#include <windows.h>
#else
#include <unistd.h>
#endif
预处理指令也用于保护包含文件,以防类/函数等被重复定义。
#ifndef MY_CLASS_6C1147A1_BE88_46d8_A713_64973C9A2DB7_H_
#define MY_CLASS_6C1147A1_BE88_46d8_A713_64973C9A2DB7_H_
class MyClass {
....
};
#endif
另一个用途是在代码和库中嵌入版本控制。
在Makefile中,你会看到类似这样的内容:
-D MY_APP_VERSION=4.5.1
在代码中你有
cout << "My application name version: " << MY_APP_VERSION << endl;
__MY_HEADER_H__
不太好,因为以 __[A-Z]
开头的命名在技术上是保留的。大多数编译器定义了 许多 配置设置,所以很可能最终会遇到一个与宏相关的奇怪错误。 - Tom/^_[A-Z]/
也是保留的,/__/
也是一样。所以你也不能使用 _MY_CLASS_H
。在你问之前,/^_[a-z]/
在全局命名空间中被保留了,因此也不适合用作宏。这就是为什么#pragma once
如此有吸引力的原因 - 不要担心编译速度,它可以节省你选择包含保护名称的时间。 - Steve Jessop#pragma once
比传统的 include-guards 更快。我刚刚通过在 gcc
上运行 strace
进行了测试,正确保护的头文件永远不会被打开超过一次。我怀疑 cl.exe
没有这种优化,因为微软对于非可移植选项和供应商锁定有着既得利益。 - Tom答案1:有条件地编写代码,取决于它在哪种类型的计算机上运行。
答案2:在编译时启用和禁用语言扩展和兼容性功能。
预处理器来自C语言,因为有许多无法表达的内容。良好的C++代码找到使用它的较少原因,但遗憾的是它并非完全无用。
预处理是在代码编译之前发生的。在以下情况下使用它是合适的:
#ifdef WIN32
#include <something.h>
#elseif LINUX
#include <somethingelse.h>
#endif
显然,编译时需要包含所需的头文件,而不是运行时。我们不能在变量中实现这一点。
另一方面,在C++中,将像以下示例中的常量表达式替换为变量是良好的实践并且极大地被鼓励。
#define PI 3.141592654
with
const double PI=3.141592654;
原因是您可以获得正确的类型转换和数据类型处理。
此外,
#define MAX(x,y) (x) > (y) ? (x) : (y)
这并不好,因为你可以写任何东西
int i = 5
int max = MAX(i++, 6);
int max = (i++) > (6) ? (i++) : (6);
很明显这样不能得到预期的结果。
相反,MAX 应该是一个函数而不是宏。如果它是一个函数,它也可以在参数中提供类型。
我见过预处理器用于各种有趣的事情,比如语言关键字声明。在这种情况下,它可以帮助提高可读性。
简而言之,将预处理器用于必须在编译时发生的事情,例如条件包含指令。避免使用它来定义常量。避免使用宏,在可能的情况下使用函数。
const int foo = 4;
这样的代码,而不是#define FOO 4
,后者是C的等价代码。不幸的是,太多人将他们在C中使用预处理器的习惯带到了C++中。#include
包含头文件是必要的。它还对条件编译很有用,包括头文件包含保护,因此可以将一个头文件#include
多次(例如在不同的头文件中),并且只处理一次。 assert
语句实际上是一个预处理器宏,还有一些类似的用法。#include
和相关的#ifdef...#define
之所以需要,是因为C和C++都没有创建一个适当的接口工具,就像在Pascal、Modula-2、Ada以及几乎所有现代语言中一样。这只是一种预处理器技巧,每个人都已经习惯了它。 - T.E.D.不,实际上并非所有情况下都可以不使用预处理器。我最喜欢的宏之一是
#define IFDEBUG if(DEBUG==1)
//usage:
IFDEBUG{
printf("Dump of stuff:.,,,");
}else{
//..do release stuff
}
如果没有宏,最终可执行文件中可能会浪费(很多)空间。
而且你必须意识到,C/C++ 没有任何类型的包管理系统,如 require
等。因此,如果没有预处理器,就无法防止代码重复。(头文件无法被包含)
if(0){..}
。 - Earlz