关于 const、enum 和 #define

关键词:const、enum、#define、static、枚举

文章是参考书籍与博客的总结,自己写下来也算是自我总结,加深印象~

一、前言

"#define" 定义了一个宏,在编译开始之前就会被替换;理论上说,#define 更高效、更安全;高效在现在的硬件来说,几乎可以忽略;而安全性体现在,对一个 #define 的值进行赋值时,编译器会报错;缺陷也很明显,由于重复定义不会警告或报错,举个例子,如果在一个头文件中定义了一个 #define SOMEVALUE,其他引用这个头文件的类中所有用到 SOMEVALUE 的都会被替换,可能其他文件中也定义了这个 #define,这就产生不一致的值,影响程序;

const 只是对变量进行修饰,修改会报错;理论上说,const 运行时占用空间,还需要一个内存的引用;但从时间上来说,这是无关紧要的;

在某些场景下应该使用 #define 而不是 const,例如想在大量的 .c 文件中使用一个常量,只需要使用 #define 放在头文件中;而使用 const,则需要在 .c 文件和头文件中都进行定义。

而对于整形常量,我们就可以使用 enum 枚举;

二、const static

变量一定要同时用 static 与 const 来声明。如果试图修改由 const 修饰符所声明的变量,那么编译器会报错。static 修饰符意味着该变量仅在定义此变量的便一单元中可见。假如声明此变量时不加 static, 则编译器会为它创建一个“外部符号”(external symbol)。此时,若是另一个编译单元中也声明了同名变量,那么编译器会抛出一条错误信息,如下图,ClassA 和 ViewController 中都声明了kAnimationDuring 变量,没有加 const,那么编译会报错:

关于 const、enum 和 #define_第1张图片
Paste_Image.png
关于 const、enum 和 #define_第2张图片
Paste_Image.png
关于 const、enum 和 #define_第3张图片
Paste_Image.png

实际上,如果一个变量既声明为 static,又声明为 const,那么编译器根本不会创建符号,而是会像 #define 预处理指令一样,把所有遇到的变量都替换为常值,只不过这种方式是带有类型信息的。

有时候需要对外公开某个常量,比如派发通知时。此常量需放在“全局符号表”(global symbol table) 中,以便可以在定义该常量的编译单元之外使用。因此,其定义方式上与 static const 有所不同。而是应该这样的:

//在 .h 头文件中
extern NSString *const AClassConst;
//在 .m 实现文件中
NSString *const AClassConst = @"value";

即在头文件中声明,并在实现文件中定义。编译器看到头文件中的 extern 关键字,就明白如何在引入此头文件的代码中处理常量。因为编译器知道,在全局符号表中有一个叫 AClassConst 的符号。编译器无需查看其定义,链接成二进制文件后,一定能找到这个常量。
此类常量必须要定义,且只能定义一次。通常定义在实现文件中,如果不定义的话,编译器在使用到这个值的时候会报错。为避免命名冲突,前缀加上类名。

注意 const 修饰符在常量类型中的位置。常量定义从右至左解读,如上例 AClassConst 就是一个 “一个常量,而这个常量是指针,指向 NSString 对象”。这与需求相符合:我们不希望有人改变此指针常量,使其指向另一个 NSString 对象。下面举例说明:

先说一下非指针类型的顺序的用法

static const CGFloat XXXHeight = 50.f;
static CGFloat const XXXHeight = 50.f;
这两种写法一样,修改值编译器都会报错
extern CGFloat const XXXHeight;
extern const CGFloat XXXHeight;
同样修改值的话,编译器会报错
再举个例子说一下 NSString 类型的用法
extern NSString *const AClassConst,正如之前所讲的,这种方式修改值的话会报错;
extern NSString const *AClassConst,而这么写的话,值可以被正常修改;
static const CGFloat XXXWidth = 40.f,不可被修改;
static CGFloat const XXXWidth = 40.f,不可被修改;

规范一下用法

staitc NSString *const XXXName = @"name";
static const CGFloat XXXWidth = 50.f;
extern NSString *const YYYName;
NSString *const YYYName = @"VALUE";
extern CGFloat const YYYHeight;
CGFloat const YYYHeight = 30.f;

关于 static、const、extern 等详细信息,自行网上查阅

三、多用枚举表示状态、选项等

枚举平时用到的非常多,注意使用最新规范的 NS_ENUM 和 NS_OPTIONS 宏。

如下,枚举的值是互斥的
typedef NS_ENUM(NSInteger, UITableViewRowAnimation) {
    UITableViewRowAnimationFade,
    UITableViewRowAnimationRight,           // slide in from right (or out to right)
    UITableViewRowAnimationLeft,
    UITableViewRowAnimationTop,
    UITableViewRowAnimationBottom,
    UITableViewRowAnimationNone,            // available in iOS 3.0
    UITableViewRowAnimationMiddle,          // available in iOS 3.2.  attempts to keep cell centered in the space it will/did occupy
    UITableViewRowAnimationAutomatic = 100  // available in iOS 5.0.  chooses an appropriate animation style for you
};
也可以指定从某个值开始
/**银行卡功能类型*/
typedef NS_ENUM(NSInteger, CardFundingType) {
    CardFundingTypeCredit = 1,    //信用卡
    CardFundingTypeDebit,         //借记卡
    CardFundingTypeBank,          //银行卡 (包含信用卡和借记卡)
    CardFundingTypeOther,
};

枚举相信大家使用的也非常多,常规的自不必说,这里着重说一下 NS_OPTIONS 的用法,就是在定义选项的时候,可以组合的情况,只要定义得对,各个选项之间就可以通过“按位或操作符”来组合。
先举个系统的例子:

typedef NS_OPTIONS(NSUInteger, UIViewAutoresizing) {
    UIViewAutoresizingNone                 = 0,
    UIViewAutoresizingFlexibleLeftMargin   = 1 << 0,
    UIViewAutoresizingFlexibleWidth        = 1 << 1,
    UIViewAutoresizingFlexibleRightMargin  = 1 << 2,
    UIViewAutoresizingFlexibleTopMargin    = 1 << 3,
    UIViewAutoresizingFlexibleHeight       = 1 << 4,
    UIViewAutoresizingFlexibleBottomMargin = 1 << 5
};
self.autoresizingMask = UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin| UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin;
看到这里可能大家就回想起来了

再例如,我们有个功能要分享到第三方应用,可以是微信、QQ、微博、朋友圈,QQ空间等等,而分享到哪里是可以由后端配置的,这时我们可以使用 NS_OPTIONS 来实现。将各个选项值定义为 2 的幂即可。

关于枚举的用法,要注意在 switch 语句使用时注意。我们总是习惯在 switch 语句中加上 default 分支。然而,若是用枚举来定义状态机,则最好不要有 default 分支。这样的话,如果稍后又加了一种状态,那么编译器就会发出警告信息,提示加入新的状态并在 switch 分支中处理。

typedef NS_ENUM(NSUInteger, KUIStyle) {
    KUIStyle0                 = 0,
    KUIStyle1                 = 1,
    KUIStyle2                 = 2,
    KUIStyle3                 = 3,
};
@property (nonatomic, assign) KUIStyle uiStyle;
如下图所示,如果注释掉 default,switch 分支枚举不全的时候编译器会报错,可以避免漏掉新加的类型。
1.png
四、总结

1.尽量避免使用 #define 预处理命令,它不包含任何类型信息,仅仅是在编译前替换。最终要的是重复定义并不会发出警告,容易在程序中产生不一致的值。

2.在源文件 .m 中定义的 static const 类型常量因为无需全局引用,所以命名不需要包含命名空间,在前面加个小写字母 k 即可。而在 .h 中定义的全局引用常量,需要关联定义在 .m 中的部分。因为全局,所以需要包含命名空间,通常类名最为前缀即可。

3.尽量使用 NS_ENUM 和 NS_OPTIONS 宏来实现枚举。

你可能感兴趣的:(关于 const、enum 和 #define)