关键词: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,那么编译会报错:
实际上,如果一个变量既声明为 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.尽量避免使用 #define 预处理命令,它不包含任何类型信息,仅仅是在编译前替换。最终要的是重复定义并不会发出警告,容易在程序中产生不一致的值。
2.在源文件 .m 中定义的 static const 类型常量因为无需全局引用,所以命名不需要包含命名空间,在前面加个小写字母 k 即可。而在 .h 中定义的全局引用常量,需要关联定义在 .m 中的部分。因为全局,所以需要包含命名空间,通常类名最为前缀即可。
3.尽量使用 NS_ENUM 和 NS_OPTIONS 宏来实现枚举。