OC中extern、static、const和宏定义
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了OC中extern、static、const和宏定义相关的知识,希望对你有一定的参考价值。
参考技术A 基础知识:(参考Henry725的博客)extern用于变量的声明,告诉编译器:已经存在一个全局变量,但是不在当前的编译单元内,需要连接的时候在其他编译单元中寻找。
修改变量作用域为当前编译单元,变量生命周期不变;
避免重复定义全局变量
2.2. 修饰局部变量 -
修改变量的生命周期为整个工程周期,变量作用域不变
const修饰右边的变量,用来限制变量为只读属性。
在程序的预编译阶段进行替换处理。
区别:
1.define宏是在预处理阶段展开。
const常量是编译运行阶段使用。
2.define不做检查,不会报编译错误,只是替换。const会编译检查,会报编译错误
3.define在展开的时候才分配内存,展开几次分配几次内存。const在定义的时候会分配一次内存到静态区,使用时不重复分配
4.define可以定义一些简单的运算函数
声明一个只读的静态变量
在多个文件中经常使用的同一个全局变量。
使用场景:
1、.h文件中声明
2、.m文件中赋值
这里直接访问即可,都不用放到.pch文件中,因为默认权限是internal
public : 最大权限,可以在当前framework和其他framwork中访问;
internal : 默认权限,可以在当前framework中随意访问;
private : 私有权限,只能在当前文件中访问;
一般常量的话,都用extern const 来代替define。
因为一旦定义#define的方式,整个工程将被重新编译,这样带来的时间浪费可想而知
当然了很多情况还是代替不了的,一般定义常量的时候是应该使用这种方式来定义,不过也只是常量宏不被推荐,但是类函数宏用的还是很方便的,
const、#define的优缺点
编译器可以对const进行类型安全检查。而对#define只进行字符替换,没有类型安全检查,并且在字符替换可能会产生意料不到的错误。
这种情况还可能出现以下错误
Sending 'const NSString *__strong' to parameter of type 'NSString *' discards qualifiers
原因是需要 NSString* 的地方使用了 const NSString*
.h中 extern const NSString* 替换为 extern NSString* const
.m中 const NSString* 替换为 NSString* const
解释:前者相当于指针本身不可修改,后者表示指针指向的内容不可修改,两者的作用都是使字符串只可读不可写。
修饰符 extern/static/const/UIKIT_EXTERN(OC版extern) 的使用
先逐个分析每个修饰符单独代表的含义,然后再分析某些修饰符组合在一起的时候所代表的含义。
const
const 相对最好理解,就是修饰的东西不能再被修改。
没有 const 修饰的指针,指针 p 和 *p 都能被修改:
1 // // 定义一个指针 2 int *p = NULL; 3 // // 定义2 个int 类型的变量 4 5 int a = 10; 6 int b = 30; 7 8 // p 指向 a 9 NSLog(@"p = %p, a = %d, b = %d", p, a, b); // p = 0x0, a = 10, b = 30 10 p = &a; 11 NSLog(@"p = %p, *p = %d, a = %d, b = %d", p, *p, a, b); // p = 0x7fff52706a14, *p = 10, a = 10, b = 30 // 这里指针 p 本来指向 0x0,现在开始指向 a 的地址 12 *p = 20; 13 NSLog(@"p = %p, *p = %d, a = %d, b = %d", p, *p, a, b); // p = 0x7fff52706a14, *p = 20, a = 20, b = 30 // 这里指针 p 指向的仍是 a 的地址,这里修改 *p 的值即为修改 p 指向的地址的值,即为修改 a 的值,所以 a 的值也变为 20 14 // p 指向 b 15 p = &b; 16 NSLog(@"p = %p, *p = %d, a = %d, b = %d", p, *p, a, b); // p = 0x7fff52706a10, *p = 30, a = 20, b = 30 // 这里指针 p 本来指向的是 a 的地址,现在开始指向 b 的地址,所以 *p 的值即为 b 的值,是30 17 *p = 40; 18 NSLog(@"p = %p, *p = %d, a = %d, b = %d", p, *p, a, b); // p = 0x7fff52706a10, *p = 40, a = 20, b = 40 // 这里指针 p 指向的仍是 b 的地址,这里修改 *p 的值即为修改 p 指向的地址的值,即为修改 b 的值,所以 b 的值也变为 40
使用 const 修饰 *p,即表示指针 p 指向的地址里面存放的值不能再改变,*p 只能在初始化时被赋值一次,以后都不能再被赋值,即不能再被修改,但是指针 p 指向的地址是可以改变的。且 const 只要在 *p 前面,在类型符前面和后面的写法表示的意义都是一样的。
1 // const 修饰的 *p 2 int a = 10; 3 int b = 20; 4 5 const int *p = &a; 6 // int const *p = &a; 这里的这两种写法是一致的 7 NSLog(@" p = %p, *p = %d", p, *p); // p = 0x7fff57553a1c, *p = 10 8 p = &b; 9 // *p = b; // 编译错误: Read-only variable is not assignable 10 NSLog(@" p = %p, *p = %d", p, *p); // p = 0x7fff57553a18, *p = 20 这里指针 p 指向的地址改变了,同时表示的值也是该地址存放的值
使用 const 修饰 p,即表示指针 p 指向的地址不能再改变了, 指针 p 在初始化的时候被一个地址赋值,以后都不能再被赋值,即不能再被修改,但是指针 p 指向的地址里面存放的值是可以改变的。这里和 const 修饰 *p 表示的含义很类似,都保持其中一项不能改变。
1 int a = 10; 2 int b = 20; 3 4 int * const p = &a; 5 // p = &b; // 编译错误: Cannot assign to variable ‘p‘ with const-qualified type ‘int *const‘ 6 NSLog(@"p = %p, *p = %d, a = %d, b = %d", p, *p, a, b); // p = 0x7fff514caa1c, *p = 10, a = 10, b = 20 7 *p = b; 8 NSLog(@"p = %p, *p = %d, a = %d, b = %d", p, *p, a, b); // p = 0x7fff514caa1c, *p = 20, a = 20, b = 20 // 这里指针 p 指向的地址没有改变,但是该地址下存放的值变为了 20 ,同时 a 的值也变成了20
注意: 如果初始化的时候指针 p 指向 NULL,那其指向的地址是 0x0,通过 *p 修改改地址下存放的值在编译的时候不会报错,但是在运行的时候会直接崩溃,0x0 地址下存放的值是什么不能用 NSLog 打印,运行的时候也会直接崩溃。当没有用 const 修饰 p 时即 p 指向的地址可以改变的时候,可以把 p 由指向 0x0,指向为别的地址。不过好像指针在初始化的时候指向 NULL,貌似也没有什么意义。
1 int * const p = NULL;
static(重点学习)
静态全局变量
在全局变量前,加上关键字 static,该变量就成为了一个静态全局变量。
静态全局变量的特点:
1.该变量在全局数据区分配内存
2.在 OC 中,未被初始化的 NSString 类型的静态全局变量会被自动初始化为 null,指向 0x0 地址。
3.在 OC 中,在一个类的 .h 或者 .m 文件中定义的静态全局变量在它的整个 .m 文件里面都是可见的,当在另一个类中引入该类时,是能直接访问上一个类的 .h 里面定义的静态全局变量的,且该静态全局变量指向的地址和值仍是上一个类的 .h 文件初始化时的地址和值,即使在上一个类的 .m 里面对该静态全局变量赋了新值,如果去除 static 修饰,访问上一个类里面的全局变量会直接 Apple Mach-0 Linker Error。
4.在 OC 中,在一个类的 .h 中定义的静态全局变量并不属于该类的某个对象,只要引入该类,就能访问它的静态局部变量,并不需要创建该类的对象。静态全局变量时属于该类的,不是属于某个类对象的。
在 ViewController.h 定义了一个静态全局变量 tempStr:
1 static NSString *tempStr = @"HML";
在 ViewController.m 里面的把它赋新值:
1 NSLog(@"%@ %@ %p", self, tempStr, tempStr); 2 3 tempStr = @"CHM"; 4 NSLog(@"%@ %@ %p", self, tempStr, tempStr);
1 2017-05-18 21:38:06.083 UIKIT_EXTERN(OC版extern) 的使用[20843:2940956] <ViewController: 0x7fcd93d0bf10> HML 0x106e62068 2 2017-05-18 21:38:06.083 UIKIT_EXTERN(OC版extern) 的使用[20843:2940956] <ViewController: 0x7fcd93d0bf10> CHM 0x106e620c8
在 ViewController.m 里面加一个按钮,点击跳转到 TwoViewController,这时在 TwoViewController 里面打印 ViewController 的静态全局变量 tempStr:
1 NSLog(@"%@ %@ %p", self, tempStr, tempStr);
1 2017-05-18 21:38:11.847 UIKIT_EXTERN(OC版extern) 的使用[20843:2940956] <TwoViewController: 0x7fcd93e353d0> HML 0x106e62068
在 TwoViewController 里面修改该静态全局变量并打印:
1 tempStr = @"LOVE YOU";
1 2017-05-18 21:38:11.847 UIKIT_EXTERN(OC版extern) 的使用[20843:2940956] <TwoViewController: 0x7fcd93e353d0> LOVE YOU 0x106e620e8
点击 TwoViewController 左上角的返回,在 ViewController 的 viewDidAppear 里面打印该静态全局变量:
1 - (void)viewDidAppear:(BOOL)animated { 2 [super viewDidAppear:animated]; 3 NSLog(@"%@ %@ %p", self, tempStr, tempStr); 4 }
1 2017-05-18 21:38:21.277 UIKIT_EXTERN(OC版extern) 的使用[20843:2940956] <ViewController: 0x7fcd93d0bf10> CHM 0x106e620c8
打印结果就是这样,在屋里走了一圈,不知道为啥,那些赋的新值都到了哪去。
静态变量的内存位置?
静态变量(static 修饰的变量)都在全局数据区分配内存,包括静态全局变量和后面将要提到的静态局部变量。对于一个完整的程序,在内存中的分布情况如下:
一般程序把新产生的动态数据放在堆区,函数内部的自动变量 (自动变量,只在定义它们的时候才创建,在定义它们的函数返回时系统回收变量所占存储空间。对这些变量存储空间的分配和回收是由系统自动完成的。一般情况下,不作专门说明的局部变量,均是自动变量。自动变量也可用关键字auto作出说明。自动变量只有一种存储方式,就是存储在栈中。由于自动变量存储在栈中,所以自动变量的作用域只在函数内,其生命周期也只持续到函数调用的结束。这个过程是通过一个堆栈的机制来实现的。为自动变量分配内存就压栈,而函数返回时就退栈。) 放在栈区。自动变量一般会随着函数的退出而释放空间,静态数据(即使是函数内部的静态局部变量)也存放在全局数据区。全局数据区的数据并不会因为函数的退出而释放空间。
静态局部变量
在全局变量前,加上关键字 static,该变量就成为了一个静态局部变量。
静态局部变量的特点:
1.该变量在全局数据区分配内存
2.静态局部变量在程序执行到该对象的声明处时被首次初始化,即以后的函数调用不再进行初始化。
3.静态局部变量一般在声明处初始化,如果没有显式初始化,在 OC 中,未被初始化的 NSString 类型的静态局部变量会被自动初始化为 null,指向 0x0 地址。
4.静态局部变量始终驻留在全局数据区,直到程序运行结束,但其作用域为局部作用域,当定义它的函数或者语句块结束时,其作用域也随之结束。
通常,在函数内部定义一个变量,每当程序运行到该语句时都会给该局部变量分配栈内存,但随着该函数执行完毕,系统就会回收栈内存,局部变量就会释放。但有时候需要在两次调用之间对变量的值进行保存。
为什么要引入 static ?
函数内部定义的变量,在程序执行到它的定义处时,编译器为它在栈上分配空间,函数在栈上分配的空间在此函数执行结束时会释放掉,这样就产生了一个问题:如果想将函数中此变量的值保存至下一次调用时,如何实现?最容易想到的方法是定义一个全局的变量,但定义为一个全局变量有许多缺点,最明显的缺点是破坏了此变量的访问范围(使得在此函数中定义的变量,不仅仅受此函数控制)。
什么时候用 static ?
需要一个数据对象为整个类而非某个对象服务,同时又力求不破坏类的封装性,即要求此成员隐藏在类的内部,对外不可见。
static 内部机制?
静态数据成员要在程序一开始运行时就必须存在。因为函数在程序运行中被调用,所以静态数据成员不能在任何函数内分配空间和初始化。
static 优势?
可以节省内存,因为它是所有对象所公有的,因此,对多个对象来说,静态数据成员只存储一处,供所有对象共用。静态数据成员的值对每个对象都是一样,但它的值是可以更新的。只要对静态数据成员的值更新一次,保证所有对象存取更新后的相同的值,这样可以提高时间效率。
在静态数据区,内存中所有的字节默认值都是0x00。
static 问答?
1. static 全局变量与普通的全局变量有什么区别 ?
全局变量(外部变量)的说明之前再冠以 static 就构成了静态的全局变量。
全局变量本身就是静态存储方式, 静态全局变量当然也是静态存储方式。 这两者在存储方式上并无不同。
这两者的区别在于非静态全局变量的作用域是整个源程序, 当一个源程序由多个源文件组成时,非静态的全局变量在各个源文件中都是有效的。 而静态全局变量则限制了其作用域, 即只在定义该变量的源文件内有效, 在同一源程序的其它源文件中不能使用它。由于静态全局变量的作用域局限于一个源文件内,只能为该源文件内的函数公用,因此可以避免在其它源文件中引起错误。
static 全局变量只初使化一次,防止在其他文件单元中被引用;
2. static 局部变量和普通局部变量有什么区别 ?
把局部变量改变为静态变量后是改变了它的存储方式即改变了它的生存期。把全局变量改变为静态变量后是改变了它的作用域,限制了它的使用范围。
static 局部变量只被初始化一次,下一次依据上一次结果值;
3. static 函数与普通函数有什么区别?
static 函数与普通函数作用域不同,仅在本文件。只在当前源文件中使用的函数应该说明为内部函数(static 修饰的函数),内部函数应该在当前源文件中说明和定义。对于可在当前源文件以外使用的函数,应该在一个头文件中说明,要使用这些函数的源文件要包含这个头文件.
static 函数在内存中只有一份,普通函数在每个被调用中维持一份拷贝
extern
如果同时要在多个 .m 文件里面用到值相同(初值相同且不可改变)的且命名相同的一个变量(忽略PCH 文件中宏定义的使用),就比如使用一个 myURL 的变量:
1 NSString * const myURL = @"http://xxx.xxx.xxx.xxx";
建立了三个类 ViewController、TwoViewController、ThreeViewController,分别在它们的 .m 文件里面像上面定义 myURL,编译时直接报 myURL 重复定义:
1 duplicate symbol _myURL in: 2 /Users/jay/Library/Developer/Xcode/DerivedData/UIKIT_EXTERN(OC版extern)_的使用-gorhoqfprjpoemhcpaqgclagloav/Build/Intermediates/UIKIT_EXTERN(OC版extern) 的使用.build/Debug-iphonesimulator/UIKIT_EXTERN(OC版extern) 的使用.build/Objects-normal/x86_64/ViewController.o 3 /Users/jay/Library/Developer/Xcode/DerivedData/UIKIT_EXTERN(OC版extern)_的使用-gorhoqfprjpoemhcpaqgclagloav/Build/Intermediates/UIKIT_EXTERN(OC版extern) 的使用.build/Debug-iphonesimulator/UIKIT_EXTERN(OC版extern) 的使用.build/Objects-normal/x86_64/ThreeViewController.o 4 duplicate symbol _myURL in: 5 /Users/jay/Library/Developer/Xcode/DerivedData/UIKIT_EXTERN(OC版extern)_的使用-gorhoqfprjpoemhcpaqgclagloav/Build/Intermediates/UIKIT_EXTERN(OC版extern) 的使用.build/Debug-iphonesimulator/UIKIT_EXTERN(OC版extern) 的使用.build/Objects-normal/x86_64/ViewController.o 6 /Users/jay/Library/Developer/Xcode/DerivedData/UIKIT_EXTERN(OC版extern)_的使用-gorhoqfprjpoemhcpaqgclagloav/Build/Intermediates/UIKIT_EXTERN(OC版extern) 的使用.build/Debug-iphonesimulator/UIKIT_EXTERN(OC版extern) 的使用.build/Objects-normal/x86_64/TwoViewController.o 7 ld: 2 duplicate symbols for architecture x86_64 8 clang: error: linker command failed with exit code 1 (use -v to see invocation)
如果把 myURL 的定义放在 PCH 文件里面,就相当于每一个 .m 文件中都写了同样的一句代码,这时 myURL 的重复定义会在所有文件中出现:
1 duplicate symbol _myURL in: 2 /Users/jay/Library/Developer/Xcode/DerivedData/UIKIT_EXTERN(OC版extern)_的使用-gorhoqfprjpoemhcpaqgclagloav/Build/Intermediates/UIKIT_EXTERN(OC版extern) 的使用.build/Debug-iphonesimulator/UIKIT_EXTERN(OC版extern) 的使用.build/Objects-normal/x86_64/ViewController.o 3 /Users/jay/Library/Developer/Xcode/DerivedData/UIKIT_EXTERN(OC版extern)_的使用-gorhoqfprjpoemhcpaqgclagloav/Build/Intermediates/UIKIT_EXTERN(OC版extern) 的使用.build/Debug-iphonesimulator/UIKIT_EXTERN(OC版extern) 的使用.build/Objects-normal/x86_64/AppDelegate.o 4 duplicate symbol _myURL in: 5 /Users/jay/Library/Developer/Xcode/DerivedData/UIKIT_EXTERN(OC版extern)_的使用-gorhoqfprjpoemhcpaqgclagloav/Build/Intermediates/UIKIT_EXTERN(OC版extern) 的使用.build/Debug-iphonesimulator/UIKIT_EXTERN(OC版extern) 的使用.build/Objects-normal/x86_64/ViewController.o 6 /Users/jay/Library/Developer/Xcode/DerivedData/UIKIT_EXTERN(OC版extern)_的使用-gorhoqfprjpoemhcpaqgclagloav/Build/Intermediates/UIKIT_EXTERN(OC版extern) 的使用.build/Debug-iphonesimulator/UIKIT_EXTERN(OC版extern) 的使用.build/Objects-normal/x86_64/Person.o 7 duplicate symbol _myURL in: 8 /Users/jay/Library/Developer/Xcode/DerivedData/UIKIT_EXTERN(OC版extern)_的使用-gorhoqfprjpoemhcpaqgclagloav/Build/Intermediates/UIKIT_EXTERN(OC版extern) 的使用.build/Debug-iphonesimulator/UIKIT_EXTERN(OC版extern) 的使用.build/Objects-normal/x86_64/ViewController.o 9 /Users/jay/Library/Developer/Xcode/DerivedData/UIKIT_EXTERN(OC版extern)_的使用-gorhoqfprjpoemhcpaqgclagloav/Build/Intermediates/UIKIT_EXTERN(OC版extern) 的使用.build/Debug-iphonesimulator/UIKIT_EXTERN(OC版extern) 的使用.build/Objects-normal/x86_64/ThreeViewController.o 10 duplicate symbol _myURL in: 11 /Users/jay/Library/Developer/Xcode/DerivedData/UIKIT_EXTERN(OC版extern)_的使用-gorhoqfprjpoemhcpaqgclagloav/Build/Intermediates/UIKIT_EXTERN(OC版extern) 的使用.build/Debug-iphonesimulator/UIKIT_EXTERN(OC版extern) 的使用.build/Objects-normal/x86_64/ViewController.o 12 /Users/jay/Library/Developer/Xcode/DerivedData/UIKIT_EXTERN(OC版extern)_的使用-gorhoqfprjpoemhcpaqgclagloav/Build/Intermediates/UIKIT_EXTERN(OC版extern) 的使用.build/Debug-iphonesimulator/UIKIT_EXTERN(OC版extern) 的使用.build/Objects-normal/x86_64/TwoViewController.o 13 duplicate symbol _myURL in: 14 /Users/jay/Library/Developer/Xcode/DerivedData/UIKIT_EXTERN(OC版extern)_的使用-gorhoqfprjpoemhcpaqgclagloav/Build/Intermediates/UIKIT_EXTERN(OC版extern) 的使用.build/Debug-iphonesimulator/UIKIT_EXTERN(OC版extern) 的使用.build/Objects-normal/x86_64/ViewController.o 15 /Users/jay/Library/Developer/Xcode/DerivedData/UIKIT_EXTERN(OC版extern)_的使用-gorhoqfprjpoemhcpaqgclagloav/Build/Intermediates/UIKIT_EXTERN(OC版extern) 的使用.build/Debug-iphonesimulator/UIKIT_EXTERN(OC版extern) 的使用.build/Objects-normal/x86_64/main.o 16 ld: 5 duplicate symbols for architecture x86_64 17 clang: error: linker command failed with exit code 1 (use -v to see invocation)
参考链接:http://blog.csdn.net/yanhsheng304/article/details/50508894
http://blog.csdn.net/xpwang168/article/details/8087143
http://blog.csdn.net/hdfqq188816190/article/details/51435268
http://baike.baidu.com/item/自动变量
http://blog.csdn.net/keyeagle/article/details/6708077/
以上是关于OC中extern、static、const和宏定义的主要内容,如果未能解决你的问题,请参考以下文章
修饰符 extern/static/const/UIKIT_EXTERN(OC版extern) 的使用
static与const联合使用&&extern与const联合使用