修饰符 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)
要解决这个重复定义,可以学习苹果的方法,如苹果在 UIWindow.h 里面定义的:
1 // Each notification includes a nil object and a userInfo dictionary containing the 2 // begining and ending keyboard frame in screen coordinates. Use the various UIView and 3 // UIWindow convertRect facilities to get the frame in the desired coordinate system. 4 // Animation key/value pairs are only available for the "will" family of notification. 5 UIKIT_EXTERN NSNotificationName const UIKeyboardWillShowNotification __TVOS_PROHIBITED; 6 UIKIT_EXTERN NSNotificationName const UIKeyboardDidShowNotification __TVOS_PROHIBITED; 7 UIKIT_EXTERN NSNotificationName const UIKeyboardWillHideNotification __TVOS_PROHIBITED; 8 UIKIT_EXTERN NSNotificationName const UIKeyboardDidHideNotification __TVOS_PROHIBITED;
ViewController 作为应用程序 window 的根控制器,在 ViewController.h 和 TwoViewController.h 里面定义 myURL 且没有做初始化:
1 UIKIT_EXTERN NSString * const myURL;
然后在 ViewController.m 里面定义 myURL 并做初始化,去掉 UIKIT_EXTERN 的前缀修饰:
1 NSString * const myURL = @"http://xxx.xxx.xxx.xxx";
这时在 viewDidLoad 里面打印 myURL:
1 2017-05-19 13:24:07.810 UIKIT_EXTERN(OC版extern) 的使用[22005:3218573] <ViewController: 0x7ff731f1aea0> http://xxx.xxx.xxx.xxx 0x107ded090
点击 ViewController 中间的 button 跳转到 TwoViewController,并在它的 viewDidLoad 里面打印 myURL:
1 2017-05-19 13:24:11.102 UIKIT_EXTERN(OC版extern) 的使用[22005:3218573] <TwoViewController: 0x7ff731f046d0> http://xxx.xxx.xxx.xxx 0x107ded090
可以看到两次打印 myURL 指向的地址是一样的,且 myURL 只是在 ViewController.m 里面赋了值。
下面要看一下 UIKIT_EXTERN,它是经过处理的 extern,在 UIKitDefines.h 里面的定义:
1 #ifdef __cplusplus 2 #define UIKIT_EXTERN extern "C" __attribute__((visibility ("default"))) 3 #else 4 #define UIKIT_EXTERN extern __attribute__((visibility ("default"))) 5 #endif
首先是条件编译,如果定义过宏 __cplusplus 则执行 #else 上面的语句,否则执行 #else 下面的语句。
extern "C" 是为了兼容以前的 C 程序, 告诉编译器按照以前 C 编译方式对(全局)函数或变量进行编译, 否则按照 C++ 方式进行编译。
C 方式编译函数 extern void fx(int , int),不对函数名进行特殊处理,编译后函数名为_fx (.obj文件中),但是 C++ 方式编译后,(为了支持重载) fx 函数变成类似于 _fx_int_int 这样的函数名,fx(int , float) 会编译成 _fx_int_float (.obj文件中)。
__attribute__ 是设置函数属性(或者变量属性, 类型属性), 可以设置的属性包括:
1 // packed, cleanup, common, no common, deprecated, mode, section, shared, tls_model, transparent_union 等
visibility 属性是设置将本项目的函数作为库使用时的可见性。
g++ 编译时,加入 -fvisibility = hidden 参数,会将所有默认 public 属性设为 hidden,导致库外文件不可见,但是如果设置了 __attribute__((visibility ("default"))) 的函数,其 public 属性仍能对外可见,而不是 hidden。可见编译指令 -fvisibility 是对所有属性符号进行处理,而 __attribute__((visibility ("default"))) 是对特定函数可见性进行设置,避免冲突。
1 #define UIKIT_STATIC_INLINE static inline
静态内联。
static 关键字修饰函数表示这是一个本地函数, 不能被没有保护该文件(对该文件具有可见性的其他文件)引用、链接,编译阶段就能强制检查。
inline 关键字表示建议编译器将该函数作为一个内联函数,将函数内的代码直接嵌入到每一个引用处,有点类似于宏定义直接替换,但是却包含了编译器严格的类型检查,所以比宏定义安全,却又有同样的执行速度。
UIKIT_EXTERN简单来说,就是将函数修饰为兼容以往C编译方式的、具有extern属性(文件外可见性)、public修饰的方法或变量库外仍可见的属性。
extern 原理
先在当前文件查找有没有全局变量,没有找到,才会去其他文件查找。
static 和 const 联合使用
static 将一个全局变量变成局部变量。
const 将一个局部变量变成局部常量。
static const 与 #define 的比较
使用 static const 修饰变量 和 宏定义比较:
相同点:
1.都不能再被修改
2.一处修改,其它都改了。(static const 指向的地址可变,指向地址的该地址下的值不可变)
不同点:
1.static const 修饰变量只有一份内存。(全局数据区)
2.宏定义只是简单的替换。
const 运用
可以新建一个类 CustomConst 类,用来存放程序中所有用到的常量(PCH 会造成重复定义)
使用 const 修饰,就能在编译的时候直接检测该常量是否被修改了,如果修改了则编译不通过。
CustomConst.h 文件中:
1 extern const CGFloat HML; 2 extern NSString * const CHM; 3 // 也可使用 UIKIT_EXTERN 代替 extern 4 UIKIT_EXTERN const CGFloat HML; 5 UIKIT_EXTERN NSString * const CHM;
CustomConst.m 文件中:
1 const CGFloat HML = 0.52; 2 NSString * const CHM = @"iLOVEYOU";
为常量赋初值,在需要使用的类里面导入 CustomConst 即可。
const 与宏的区别
使用字符串常量时,一般会定义成宏,但是苹果不推荐定义成宏,推荐使用const常量。
编译时刻: 宏是预编译的(编译之前处理),const 修饰的常量是编译阶段。
编译检查: 宏不做检查,不会报编译错误,只是替换,const 会编译检查,会报编译错误。
宏的优点: 宏能定义一些函数方法,const 不能。
宏的坏处: 使用宏太多,会造成编译时间太久,每次需要重新替换。
宏定义的是常量,都放在常量区,在不同的类里面引用宏,打印它们的地址,它们的地址都是一样的,一个宏在程序里只是占据一份内存。
参考链接: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/
http://www.jianshu.com/p/2fd58ed2cf55
END