typedefs和#defines


32

我们每个人肯定都使用了typedefs和#defines。今天,在与他们一起工作时,我开始思考一件事。

请考虑以下两种情况,以使用int其他名称的数据类型:

typedef int MYINTEGER

#define MYINTEGER int

与上述情况类似,在很多情况下,我们可以使用#define很好地完成某件事,并且也可以使用typedef进行相同的操作,尽管我们进行相同操作的方式可能完全不同。#define还可以执行typedef不能执行的MACRO操作。

尽管使用它们的基本原因是不同的,但是它们的工作有何不同?当两者都可以使用时,什么时候应该优先使用另一种呢?另外,在某些情况下,是否可以保证一个比另一个更快?(例如,#define是预处理程序指令,因此所有操作都比编译或运行时更早完成)。


8
使用#define它们的设计目的。基于您在编写代码时不知道的属性的条件编译(例如OS / Compiler)。否则,请使用语言构造。
马丁·约克

Answers:


67

typedef通常首选A,除非出于特殊原因需要特殊的宏。

宏会进行文本替换,这可能会对代码的语义造成重大破坏。例如,给定:

#define MYINTEGER int

您可以合法地写:

short MYINTEGER x = 42;

因为short MYINTEGER扩展为short int

另一方面,使用typedef:

typedef int MYINTEGER:

名称MYINTEGER是该类型的 另一个名称int,而不是关键字“ int”的文本替换。

使用更复杂的类型,情况会变得更糟。例如,鉴于此:

typedef char *char_ptr;
char_ptr a, b;
#define CHAR_PTR char*
CHAR_PTR c, d;

abc都是指针,但是dchar,因为最后一行扩展为:

char* c, d;

相当于

char *c;
char d;

(指针类型的Typedef通常不是一个好主意,但这说明了这一点。)

另一个奇怪的情况:

#define DWORD long
DWORD double x;     /* Huh? */

1
再次责备指针!:)除了指针之外,还有其他不明智的例子吗?
c0da 2012年

2
并不是说我曾经用宏代替a typedef,而是构建可以对以下内容执行某些操作的宏的正确方法是使用arguments #define CHAR_PTR(x) char *x。如果使用不正确,这至少会导致编译器kvetch。
Blrfl 2012年

1
不要忘记:const CHAR_PTR mutable_pointer_to_const_char; const char_ptr const_pointer_to_mutable_char;
乔恩·普迪

3
定义还使错误消息难以理解
Thomas Bonini 2012年

滥用宏可能造成的痛苦的一个示例(尽管与宏与typedef并不直接相关):github.com/Keith-S-Thompson/42
Keith Thompson,

15

到目前为止,宏的最大问题是它们没有范围。仅此一项就可以使用typedef。而且,从语义上讲更清楚。当阅读您的代码的人看到一个定义时,他将不知道它的含义,直到他阅读并理解了整个宏。一个typedef告诉读者将定义一个类型名。(我应该提到我在谈论C ++。我不确定C的typedef作用域,但我想它是相似的)。


但是,正如我在问题中提供的示例中所示,typedef和#define使用相同数量的单词!另外,宏的这3个字不会引起任何混淆,因为这些字相同,而只是重新排列了。:)
c0da 2012年

2
是的,但这与短缺无关。宏除了类型定义外还可以做无数其他事情。但是我个人认为范围界定是最重要的。
陶Szelei

2
@ c0da-您不应将宏用于typedef,而应将typedef用于typedef。宏的实际效果可能非常不同,如各种响应所示。
Joris Timmermans,2012年

15

源代码主要是为其他开发人员编写的。计算机使用它的编译版本。

从这个角度来看,typedef它的含义#define没有。


6

Keith Thompson的回答非常好,再加上TamásSzelei关于范围界定的其他观点,应能提供您需要的所有背景。

您应该始终将宏视为绝望的最后手段。在某些情况下,您只能使用宏。即使这样,如果您真的想这样做,也应该认真思考。宏的巧妙破坏可能会导致调试上的痛苦,这是相当大的,并且会让您浏览经过预处理的文件。值得一试,以了解C ++中问题的范围-预处理文件的大小可能会让您大开眼界。


5

除了已经给出的有关范围和文本替换的所有有效说明外,#define也与typedef不完全兼容!即涉及函数指针的情况。

typedef void(*fptr_t)(void);

这声明了一个类型fptr_t,该类型是指向该类型的函数的指针void func(void)

您不能使用宏声明此类型。#define fptr_t void(*)(void)显然是行不通的。您将不得不编写一些晦涩难懂的内容,例如#define fptr_t(name) void(*name)(void),这在C语言中实际上没有任何意义,因为C语言中没有构造函数。

数组指针不能用#define声明: typedef int(*arr_ptr)[10];


尽管C语言不支持类型安全的语言值得一提,但typedef和#define不兼容的另一种情况是进行可疑的类型转换。如果使用typedef,则编译器和/或静态分析器工具可能会为此类转换给出警告。


1
更好的是将函数指针和数组指针组合在一起,例如char *(*(*foo())[])();foo函数是将指针返回指向指针的数组,而函数是将指针返回指向的函数char)。
约翰·波德

4

使用功能最强大的工具来完成任务,而使用警告最多的工具。#define是在预处理器中评估的,您在那里大都依靠自己。typedef由编译器评估。给出了检查,如名称所示,typedef只能定义类型。因此,在您的示例中,绝对要使用typedef。


2

除了针对define的常规参数外,您将如何使用Macros编写此函数?

template <typename IterType>
typename IterType::value_type Sum(
    const IterType& begin, 
    const IterType& end, 
    const IterType::value_type& initialValue)
{
    typename IterType::value_type result = initialValue;
    for (IterType i = begin; i != end; ++i)
        result += i;

    return result;
}

....

vector<int> values;
int sum = Sum(values.begin(), values.end(), 0);

这显然是一个简单的例子,但是该函数可以对实现加法*的任何类型的正向可迭代序列求和。这样使用的Typedef是泛型编程的重要元素。

*我只是在这里写的,我留给读者练习:-)

编辑:

这个答案似乎很容易引起混淆,所以让我多画一点。如果要查看STL向量的定义,您将看到类似于以下内容:

template <typename ValueType, typename AllocatorType>
class vector
{
public:
    typedef ValueType value_type;
...
}

在标准容器中使用typedef可以使泛型函数(如我上面创建的函数)引用这些类型。函数“求和”以容器(std::vector<int>)的类型为模板,而不是以容器(int)内的类型为模板。没有typedef,就不可能引用该内部类型。

因此,typedef是Modern C ++的核心,而使用Macros则不可能。


该问题询问的是宏vs typedef,而不是宏vs内联函数。
Ben Voigt 2012年

当然,这只有使用typedefs才可能。
克里斯·皮特曼

这不是typedef,而是模板编程。一个函数或函子不是一个类型,无论它是多么通用。

@Lundin我添加了更多详细信息:函数Sum只能使用,因为标准容器使用typedef公开其模板化的类型。我并不是说Sum是typedef。我说的是std :: vector <T> :: value_type是一个typedef。随意查看任何标准库实现的源进行验证。
克里斯·皮特曼

很公平。最好只发布第二个片段。

1

typedef符合C ++原则:在编译时进行所有可能的检查/声明。#define只是一个预处理器技巧,它对编译器隐藏了很多语义。您不应该担心编译性能,而不仅仅是代码正确性。

创建新类型时,您正在定义程序域可以操纵的新“事物”。因此,您可以使用此“事物”来组成函数和类,并且可以利用编译器来进行静态检查,以从中受益。无论如何,由于C ++与C兼容,因此在int基于int的类型和基于int的类型之间存在很多隐式转换,它们不会生成警告。因此,在这种情况下,您将无法获得静态检查的全部功能。不过,您可以将替换typedef为,enum以查找隐式转换。例如:如果您拥有typedef int Age;,则可以替换为,enum Age { };并且通过Age和之间的隐式转换会遇到各种错误int

另一件事:typedef可以在内namespace


1

在另一个重要方面(即类型特征的概念)下,使用define而不是typedef会造成麻烦。考虑不同的类(考虑标准容器),所有类都定义其特定的typedef。您可以通过引用typedef来编写通用代码。这样的示例包括常规容器要求(c ++标准23.2.1 [container.requirements.general]),例如

X::value_type
X::reference
X::difference_type
X::size_type

所有这些都无法通过宏以通用方式表示,因为它没有作用域。


1

不要忘记调试器中的含义。一些调试器不能很好地处理#define。在您使用的调试器中一起玩。请记住,与编写相比,您将花费更多的时间阅读它。


-2

“ #define”将替换您编写的内容,typedef将创建类型。因此,如果您要使用自定义类型-请使用typedef。如果需要宏,请使用define。


我只是爱那些拒绝投票甚至不愿发表评论的人。
Dainius
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.