1. 关于宏
预编译
编译器在编译源码之前会进行预编译,在预编译之前也会进行一些操作,如:删除 反斜线+换行符
的组合; 将各种形式的注释用空格替代等等。预编译在处理#define的时会从#开始,一直执行到遇到的第一个换行符(写代码的时候换行的作用)为止。
所以,正常情况下#define只会允许一行的宏定义。但是因为预编译之前会删除反斜线+换行符
的组合,所以我们可以利用反斜线+换行符
来定义多行宏,这样在预编译阶段的逻辑上#define定义就成了一行的宏了。
#define
在预处理阶段只进行文本的替换(相当于把代码拷贝粘贴),不会进行具体的计算,而且是直接替换(内联函数!!!),这个和函数有着很大的区别。
// 宏(#define)的基本语法:
#define 宏名 主体;
↓ ↓ ↓
#define PI 3.1415926
常见宏定义(#define)的形式
// 类对象宏:类对象宏一般用来定义数据常量或字符串常量。
#define PI 3.1415926
#define NAME @"SunSatan"
// 类函数宏:类函数宏就是宏名类似函数名,宏的主体为函数,
// 并可以帮助主体函数接受参数,使用起来就像是函数一样。
#define Log(x) NSLog(@"this is test: x = %@", x)
类函数宏和函数的区别是, 类函数宏的参数不指定类型, 具体的参数类型在调用宏的时候由传入的参数决定,这就可能会遇到类型错误的问题。
例:
#define power(x) x*x
int x = 2;
int pow_1 = power(x);
int pow_2 = power(x+1);
// 猜猜pow_1和pow_2的值分别为多少?
// 结果是pow_1=4 ,pow_2=5。
这个结果是不是和预想的不一样,pow_2不应该等于9吗,为什么是5?
注:宏真正的效果就是进行文本的替换。
宏(#define)中操作符的使用:
#
为字符串化操作符,需要放置在参数前,将类函数宏中传入的参数用""括起来变成了c语言的字符串。
#define Log(x) #x
// 代码替换后:
// Log(x) -> "x"
##
为符号连接操作符,需要放置在参数前,参数就会和##前面的符号连接起来。
#define single(name) +(instancetype)share##name;
// 代码替换后:
// single(SunSatan) -> +(instancetype)shareSunSatan;
2. 静态、常量、全局
static
用static来修饰一个变量时,它的生命周期、作用域会发生变化;其内存只被分配一次并存储到全局变量区,可供所有对象使用,其值在下次调用时仍维持上次的值。
有时,我们为了避免重复定义一个变量,也可以使用static。例如tableView的cell重用机制中,我们会定义一个cellIdentifier ...
-
static修饰局部变量
局部变量是存储在栈区的,一旦出了这个代码块,存储局部变量的这个栈内存就会被回收,局部变量也就被销毁。当用static修饰局部变量时,变量被称为静态局部变量
,和全局变量
,静态全局变量
一样,是存储在静态存储区
。存储在静态存储区的变量,其内存直到 程序结束才会被销毁,生命周期是整个源程序。静态局部变量的作用域是声明它的代码块内。 -
static修饰全局变量
当全局变量没有使用static修饰时,其存储在静态存储区
,直到程序结束才销毁。也就是其作用域是整个源程序。我们可以使用extern关键字来引用这个全局变量。
当全局变量使用static修饰时,其生命周期没有变,依旧是在程序结束时才销毁。但是其作用域发生变化,使用extern关键字无法引用这个全局变量(extern修饰的全局变量,一般定义在.m中,修饰在.h中。当然,.h中也是可以用static修饰变量的。)。
// .m中,仅本文件可见,不可再被extern
static int num;
@implementation
@end
const
const修饰的是其右边的值,只可初始化时赋值一次,const右边的这个整体的值不能再改变,只可读。const 在*前/后的区别如下:
// 两种写法等价,指针内容不可变
const NSString *name = @"bac";
NSString const * name = @"abc";
// 指针地址不可变
NSString * const str = @"abc";
extern
主要是用来引用全局变量
,它的原理是先在本文件中查找,查找不到再到其他文件中查找。
//// .h中
@interface PDConst : NSObject
extern NSString *const appBaseURL;
@end
////.m中
@implementation PDConst
NSString *const currentBaseURL = @"http://192....";
@end
3. 高级应用场景
- static和const的联合使用
//static和const联合使用是用来替代宏,把一个经常使用的字符串常量,定义成静态全局只读变量
//使用const修饰key,表示key只读,不允许修改
static NSString *const key = @“name”;
//如果const修饰 *key1,表示 *key1 只读,key1还是能改变的
static NSString const *key1 = @“myName”;
- 宏定义与static、const的联合使用
场景:封装一个三方库,其头文件中的代码如下,在引用了它的类中,我们能够使用DDLogVerbose(...)来打印log(当然三方库的其他文件中已经处理好了log的逻辑),那么我们应该怎么运行呢?
#ifndef LOG_LEVEL_DEF
#define LOG_LEVEL_DEF ddLogLevel
#endif
typedef NS_OPTIONS(NSUInteger, DDLogFlag){
DDLogFlagError = (1 << 0),
DDLogFlagWarning = (1 << 1),
DDLogFlagInfo = (1 << 2),
DDLogFlagDebug = (1 << 3),
DDLogFlagVerbose = (1 << 4)
};
typedef NS_ENUM(NSUInteger, DDLogLevel){
DDLogLevelOff = 0,
DDLogLevelError = (DDLogFlagError),
DDLogLevelWarning = (DDLogLevelError | DDLogFlagWarning),
DDLogLevelInfo = (DDLogLevelWarning | DDLogFlagInfo),
DDLogLevelDebug = (DDLogLevelInfo | DDLogFlagDebug),
DDLogLevelVerbose = (DDLogLevelDebug | DDLogFlagVerbose),
DDLogLevelAll = NSUIntegerMax
};
#define SerLog(format,...) [ServiceLogManager customLogWithFormatString:[NSString stringWithFormat:format, ##__VA_ARGS__]]
#define DDLogVerbose if (LOG_LEVEL_DEF & DDLogFlagVerbose) SerLog
#define DDLogDebug if (LOG_LEVEL_DEF & DDLogFlagDebug) SerLog
#define DDLogWarn if (LOG_LEVEL_DEF & DDLogFlagWarning) SerLog
#define DDLogInfo if (LOG_LEVEL_DEF & DDLogFlagInfo) SerLog
#define DDLogError if (LOG_LEVEL_DEF & DDLogFlagError) SerLog
在调用打log方法之前,如,在调用DDLogVerbose的文件内,我们首先要给定一个ddLogLevel变量:
static const DDLogLevel ddLogLevel = DDLogLevelVerbose;
这样,三方库中的宏在被调用处,预编译时就能自动将LOG_LEVEL_DEF文本替换为ddLogLevel变量。若不给定这个变量会报这样的错误,Use of undeclared identifier 'ddLogLevel'
,你可能不理解ddLogLevel为什么定义在三方库外面,却被三方库自动读取到?
替换的逻辑很简单:宏的替换动作不是在三方库源码内被进行的,而是在调用DDLogVerbose的位置,宏被展开,进而就直接在那个位置被替换掉。
似乎据此思路,我们可以做一些看起来很奇妙的事情🤩!
参考文章:
https://blog.csdn.net/qq_36557133/article/details/86476686
http://blog.sina.com.cn/s/blog_134451adb0102wfrn.html
https://my.oschina.net/u/4410805/blog/3391545
感谢!
网友评论