#define
和常量的优缺点吗?我的大部分工作都是在C和Objective-C中完成的。正如0A0D所提到的,在C语言中有 #defines
、enums
和 const
变量。值得注意的是,在C语言中带有 const
修饰的变量不被视为编译时常量,因此在某些情况下不能使用(例如在声明数组大小时)。
enum
常量是编译时常量。对于整型数值而言,个人认为最好优先选择 enums
而不是 const
变量或者 #define
。
const
。除了适当的宏或有条件包含的标记外,请不要使用#define
。 - Pavel Minaevenum
类型。例如,enum { foo = 42 };
是匿名的;而enum bar { foo = 42 };
则不是。 - jamesdlin事实上,有三种定义这样的常量的方法:
在 C 中,除非另有规定,否则一切皆为 int 类型。当我有一些相关的整数常量时,我更喜欢使用枚举类型。当你不关心值是什么时,枚举类型显然更好。但即使你确实需要为所有常量指定值,我也喜欢枚举类型的概念上的分组。当你有了类型后,代码自己就能更好地记录下来。
Error MyFunc();
clearly返回特定一组错误代码之一,而
int MyFunc()
可能会返回Unix errno的# define列表之一,或者可能是其他内容,或者可能是这些加上一些特殊值 - 谁知道呢?如果您有多个返回代码集,则此函数使用哪个集合?
更具体的枚举类型名称有助于编辑器中的标签功能、grep、调试等。
严格的lint可能会给您一些关于将枚举用作整数的警告,例如如果您添加或将它们或将枚举传递给int。
一个const对象与枚举或#define不同,特别是在C语言中。在ANSI C中,const int占用的空间与常规int一样;大多数编译器还将生成对该地址的指针引用,而不是内联值。因此,在C中我很少使用const int。(C ++的语义略有不同,因此在那里的选择也不同。)
我使用过的每个编译器都有将枚举存储在最小空间中的选项。通常甚至是默认选项。当使用这种选项时,为了强制使用更宽的枚举,我通常会添加额外的无符号值:
typedef enum
{
MyEnumA,
MyEnumB,
MyEnumForce16 = 7fff
} MyEnum;
使用枚举常量(enum)相比于使用传统的符号常量风格 #define 有许多优点。这些优点包括更低的维护要求、改善程序可读性和更好的调试能力。
1)第一个优点是枚举常量由编译器自动生成。相反,符号常量必须由程序员手动分配值。
例如,如果您在程序中有一个错误代码的枚举常量类型,您的枚举定义可能如下所示:
enum Error_Code
{
OUT_OF_MEMORY,
INSUFFICIENT_DISK_SPACE,
LOGIC_ERROR,
FILE_NOT_FOUND
};
#define OUT_OF_MEMORY 0
#define INSUFFICIENT_DISK_SPACE 1
#define LOGIC_ERROR 2
#define FILE_NOT_FOUND 3
两种方法都能得到相同的结果:四个常量被分配数字值来表示错误代码。然而,考虑到需要维护的工作,如果要添加两个常量以表示错误代码DRIVE_NOT_READY
和CORRUPT_FILE
。使用枚举常量方法,您只需将这两个常量放在枚举定义的任何位置即可。编译器会为这些常量生成两个唯一的值。使用符号常量方法,您将不得不手动为这些常量分配两个新数字。此外,您还要确保为这些常量分配的数字是唯一的。
2) 使用枚举常量方法的另一个优点是,您的程序更易读,因此可以更好地被其他人理解,他们可能需要稍后更新您的程序。
3) 使用枚举常量的第三个优点是,某些符号调试器可以打印枚举常量的值。相反,大多数符号调试器无法打印符号常量的值。这对于调试程序非常有帮助,因为如果您的程序停在使用枚举类型的行上,您只需检查该常量并立即知道其值。另一方面,由于大多数调试器无法打印#define
值,您很可能不得不通过手动查找头文件中的值来查找它。
#define
语句是一个预编译指令。从技术上讲,任何以#开头的行都是供预编译器处理的内容。预编译器将使用定义的标记替换所有实例。因此,可以这样做:
#define DELAY 40
for (i=0;i<DELAY;i++) {
for (j=0;j<DELAY;j++) {
asm NOP;
}
}
就编译器而言,这与此完全相同:
for (i=0;i<40;i++) {
for (j=0;j<40;j++) {
asm NOP;
}
}
当编译器生成机器代码时,它将看到数字40并使用立即寻址模式来与累加器比较。数字40将存储在代码中,每次您引用时都会出现。在这种情况下,它出现了两次。以下是CodeWarrior Ver5生成的汇编代码:
7: char i,j;
8: for (i=0;i<DELAY;i++) {
0002 95 [2] TSX
0003 7f [2] CLR ,X
0004 [5] L4:
9: for (j=0;j<DELAY;j++) {
0004 6f01 [3] CLR 1,X
0006 [5] L6:
10: asm NOP;
0006 9d [1] NOP
0007 6c01 [4] INC 1,X
0009 e601 [3] LDA 1,X
000b a128 [2] CMP #40 ;<---- notice opcode a1 and immediate constant 40, which is $28 in hexadecimal
000d 25f7 [3] BCS L6
000f 7c [3] INC ,X
0010 f6 [2] LDA ,X
0011 a128 [2] CMP #40 ;<---- and here it is again.
0013 25ef [3] BCS L4
11: }
12: }
13: }
enum
的优点和与const
相比的劣势,以及与#define
相比的劣势。 - Pavel Minaev常量允许您指定数据类型,这通常是一个优势。宏更加灵活,因此如果您不小心使用,可能会带来更多麻烦。
最佳实践是尽可能多地使用常量,并仅在确实需要宏时才使用# define,而不仅仅是命名字面值。
const
不是真正的(编译时)常数,并且不能用于 case
标签、数组大小和其他需要常量表达式的上下文中。这就是为什么 C 中经常使用 #define
(而在 C++ 中更经常使用 const
)的原因,忽略它会导致糟糕的答案。 - Pavel Minaevenum
来表示整数常量表达式。这种方法有类型、作用域和避免命名冲突等优点,同时也是整数常量。对于其他类型(如浮点数、字符串等)不需要编译时计算的常量,使用const
就可以了。请注意,即使许多人认为没有匿名结构体,但匿名enum
在ANSI C89中是存在的。 - Pavel Minaevenum
的另一个小缺点是,任何数字后缀(如 U 或 LL)都将被编译器删除,因为在标准 C 中,枚举始终是 int
类型(尽管一些编译器建议在嵌入式软件中使用较小的类型)。 - calandoa常量的优点是有类型,因此在编译时使用不正确的常量可以被发现。也许这对你来说并不重要,但常量占用内存空间,而#defines不占用(因为它们在实际编译之前被替换)。
#define的解释:#define是一个立即值或宏定义。
常量的解释:常量是任何类型的值,其值永远不会改变。
您可以声明指向const的指针,但不能声明指向#define的指针,尽管#define可以是指针,例如:#define ADDRESS ((int *)0x0012)
那么我们为什么要使用常量呢?原因如下:
简而言之,const标识符的行为就像它们是语言的一部分,因为它们确实是语言的一部分。
在一个模块中,如果没有指针声明到常量,C编译器可以将const优化为#define。在CPU术语中,const将变成“立即”值。另一种选择是,由于const不会改变,它可以被放置在代码区而不是数据区。在某些机器上,声明指向常量的指针可能会导致异常,如果您尝试通过指针修改常量。 有时需要使用#define,但通常应该在有选择的情况下避免使用它。您应该根据业务价值(时间、金钱、风险)来评估是否使用const或#define。Const 是一个对象,你可以取它的地址,例如。而且它是类型安全的,也就是编译器知道常量的类型是什么。但是这并不适用于 #define。
const
产生一个左值,这意味着它的地址可以被取得。而 #define
则不行。#define
可能会导致无意中宏扩展,这可能很难调试。#define
没有与之关联的类型。总的来说,我会尽量避免使用预处理器,因为可能会出现意外扩展,而且 ALL_CAPS 的惯例非常丑陋。
使用define的好处是,一旦您为变量定义了值,例如:#define NUMBER 30,主函数中的所有代码都将使用该值为30的代码。如果您将30更改为40,则会直接更改使用此变量(NUMBER)的主函数中的所有值。
#define
指令不遵循作用域。 - GManNickG