如何正确使用const,static,extern
一,const的使用1. 什么是const
* 介绍:const是一个C语言(ANSI C)的关键字,具有着举足轻重的地位。它限定一个变量不允许被改变,产生静态作用。使用const在一定程度上可以提高程序的安全性和可靠性。另外,在观看别人代码的时候,清晰理解const所起的作用,对理解对方的程序也有一定帮助* 定义:const修饰的数据类型是指常类型,常类型的变量或对象的值是不能被更新的。
* 目的:const 推出的初始目的,正是为了取代预编译指令,消除它的缺点,同时继承它的优点。
* const与宏的区别(面试题):
const简介:之前常用的字符串常量,一般是抽成宏,但是苹果不推荐我们抽成宏,推荐我们使用const常量。
编译时刻:宏是预编译(编译之前处理),const是编译阶段。
编译检查:宏不做检查,不会报编译错误,只是替换,const会编译检查,会报编译错误。
宏的好处:宏能定义一些函数,方法。 const不能。
宏的坏处:使用大量宏,容易造成编译时间久,每次都需要重新替换。
示例:
// 常见的常量:抽成宏
#define XMGAccount @"account"
#define XMGUserDefault [NSUserDefaults standardUserDefaults]
// 字符串常量
static NSString * const account = @"account";
- (void)viewDidLoad {
[super viewDidLoad];
// 偏好设置存储
// 使用宏
[XMGUserDefault setValue:@"123" forKey:XMGAccount];
// 使用const常量
[[NSUserDefaults standardUserDefaults] setValue:@"123" forKey:account];
}
* 主要作用(了解)
(1)可以定义const常量,具有不可变性。
例如:const int Max=100; Max++会产生错误;
(2)便于进行类型检查,使编译器对处理内容有更多了解,消除了一些隐患。
例如: void f(const int i) { .........} 编译器就会知道i是一个常量,不允许修改;
(3)可以避免意义模糊的数字出现,同样可以很方便地进行参数的调整和修改。 同宏定义一样,可以做到不变则已,一变都变!
如(1)中,如果想修改Max的内容,只需要:const int Max=you want;即可!
(4)可以保护被修饰的东西,防止意外的修改,增强程序的健壮性。 还是上面的例子,如果在函数体内修改了i,编译器就会报错;
例如: void f(const int i) { i=10;//error! }
(5) 可以节省空间,避免不必要的内存分配。 例如:
#define PI 3.14159 //常量宏
const double Pi=3.14159; //此时并未将Pi放入RAM中 ......
double i=Pi; //此时为Pi分配内存,以后不再分配!
double I=PI; //编译期间进行宏替换,分配内存
double j=Pi; //没有内存分配
double J=PI; //再进行宏替换,又一次分配内存!
const定义常量从汇编的角度来看,只是给出了对应的内存地址,而不是像#define一样给出的是立即数,所以,const定义的常量在程序运行过程中只有一份拷贝,而#define定义的常量在内存中有若干份拷贝。
(6) 提高了效率。
编译器通常不为普通const常量分配存储空间,而是将它们保存在符号表中,这使得它成为一个编译期间的常量,没有了存储与读内存的操作,使得它的效率也很高。
2、const一般使用
1.用const修饰指针变量(1)const 类型 * 变量名:可以改变指针的指向,不能改变指针指向的内容。
int x = 1;
int y = 2;
const int *px = &x; // 让指针px指向变量x
px = &y; // 改变指针px的指向,使其指向变量y
*px = 3; // 改变px指向的变量x的值,出错:Read-only variable is not assignable
(2)类型 * const 变量名:可以改变指针指向的内容,不能改变指针的指向。
int x = 1;
int y = 2;
int * const px = &x; // 让指针px指向变量x
px = &y; // 改变px的指向,出错:Read-only variable is not assignable
(*px) += 2; // 改变px指向的变量x的值
(3)const 类型 * const 变量名:指针的指向、指针指向的内容都不可以改变。
int x = 1;
int y = 2;
const int * const px = &x; // 让指针px指向变量x
px = &y; // 改变px的指向,出错:Read-only variable is not assignable
(*px) += 2; // 改变px指向的变量x的值,出错:Read-only variable is not assignable
2.创建文件域的字符串常量
(1)如果指针指向的是可变的字符串对象(如:NSMutableString),即使加const修饰该对象,该对象也是可变的,如:
- (void)viewDidLoad {
[super viewDidLoad];
//定义指针str, 指向只读对象
const NSMutableString * str =[NSMutableString stringWithString:@"OneDream,"];
NSLog(@"%@",str);
NSLog(@"%p",str);
NSLog(@"%p",&str);
NSLog(@"----分割线----");
//改变str指向的对象的值
[str appendString:@"OneWorld"];
NSLog(@"%@",str);
NSLog(@"%p",str);
NSLog(@"%p",&str);
NSLog(@"----分割线----");
//改变str的指向
str =[NSMutableString stringWithString:@"A new string"];
NSLog(@"%@",str);
NSLog(@"%p",str);
NSLog(@"%p",&str);
NSLog(@"----分割线----");
}
输出结果为:
2016-08-11 15:13:23.925 LXNetworkking[7113:115952] OneDream,
2016-08-11 15:13:23.925 LXNetworkking[7113:115952] 0x7ff222497ac0
2016-08-11 15:13:23.925 LXNetworkking[7113:115952] 0x7fff5a987a58
2016-08-11 15:13:23.926 LXNetworkking[7113:115952] ----分割线----
2016-08-11 15:13:23.926 LXNetworkking[7113:115952] OneDream,OneWorld
2016-08-11 15:13:23.926 LXNetworkking[7113:115952] 0x7ff222497ac0
2016-08-11 15:13:23.926 LXNetworkking[7113:115952] 0x7fff5a987a58
2016-08-11 15:13:23.926 LXNetworkking[7113:115952] ----分割线----
2016-08-11 15:13:23.926 LXNetworkking[7113:115952] A new string
2016-08-11 15:13:23.927 LXNetworkking[7113:115952] 0x7ff222643e00
2016-08-11 15:13:23.927 LXNetworkking[7113:115952] 0x7fff5a987a58
2016-08-11 15:13:23.927 LXNetworkking[7113:115952] ----分割线----
(2)如果指向的是不可变对象(如:NSString),就不用担心指向的对象的值被改变了,但是指针的指向也可以被改变:
- (void)viewDidLoad {
[super viewDidLoad];
//定义指针str, 指向只读对象
const NSString * str =[NSMutableString stringWithString:@"OneDream,"];
NSLog(@"%@",str);
NSLog(@"%p",str);
NSLog(@"%p",&str);
NSLog(@"----分割线----");
//改变str的指向
str =@"A new string";
NSLog(@"%@",str);
NSLog(@"%p",str);
NSLog(@"%p",&str);
NSLog(@"----分割线----");
}
输出结果为:
2016-08-11 15:17:18.075 LXNetworkking[7250:118893] OneDream,
2016-08-11 15:17:18.076 LXNetworkking[7250:118893] 0x7f8403ddcc80
2016-08-11 15:17:18.076 LXNetworkking[7250:118893] 0x7fff544caa58
2016-08-11 15:17:18.076 LXNetworkking[7250:118893] ----分割线----
2016-08-11 15:17:18.076 LXNetworkking[7250:118893] A new string
2016-08-11 15:17:18.076 LXNetworkking[7250:118893] 0x10b7350e0
2016-08-11 15:17:18.076 LXNetworkking[7250:118893] 0x7fff544caa58
2016-08-11 15:17:18.077 LXNetworkking[7250:118893] ----分割线----
(3) 如果想创建一个只读的字符串常量,应该设定指针只读,并指向不可变对象:
- (void)viewDidLoad {
[super viewDidLoad];
//定义指针str, 指向只读对象
NSString * const str =@"One Dream";
NSLog(@"%@",str);
NSLog(@"%p",str);
NSLog(@"%p",&str);
NSLog(@"----分割线----");
//改变str的指向
str =@"A new string"; 错误:Read-only variable is not assignable
NSLog(@"%@",str);
NSLog(@"%p",str);
NSLog(@"%p",&str);
NSLog(@"----分割线----");
}
3.总结:
* 那么声明文件域的字符串常量的做法是:在文件首部作以下声明static NSString * const gString = @"Global";
* 必须在声明时就对常量赋值。否则在使用时不能再对其赋值,从而使用的是指向nil的指针。
* 如果要声明文件域的是基本数据类型常量,在文件首部声明变量时用static const修饰,并赋初值。例如:
static const NSUInteger gInteger = 100;
二,extern
作用: 只是用来获取全局变量(包括全局静态变量)的值,不能用于定义变量* 怎么理解这句话呢?
就是说,我们在其他类(比如LXView)中定义了,全局变量. 但是我们想在某个类中(比如:ViewController)使用这个全局变量, 我们无需引入头文件,仅仅通过
extern +我们定义的全局变量. 就可以对相应的全局变量进行操作.
例如:LXView.h中声明, 在LXView.m中初始化, 在ViewController中引用
* 在 @interface~ @end的外部定义
lxView.h
#import <UIKit/UIKit.h>
NSString * name; //全局变量
@interface lxView : UIView
@end
lxView.m
#import "lxView.h"
@implementation lxView
NSString * name =@"hello!";
@end
ViewController.m
#import "ViewController.h"
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
extern NSString * name;
NSLog(@"%@",name);
}
打印结果:
2016-08-11 17:38:06.937 LXNetworkking[10672:170415] hello!
* 在 @interface~ @end的内部定义
#import <UIKit/UIKit.h>
@interface lxView : UIView
NSString *name; error
// * 这个是不合法的:因为OC的类会将这个全局变量当做成员属性来处理,而成员属性是需要加{}的,所以不合法;
// * 声明全局变量的时候默认带有extern,这里必须显式声明.即为: extern NSString *name ;
//假如 此处为@property(nonatomic) NSString * name; 则无需添加extern, 因为xocde在编译时会默认添加 extern 关键词
@end
lxView.m
#import "lxView.h"
@implementation lxView
NSString * name =@"hello!";
@end
ViewController.m
#import "ViewController.h"
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
extern NSString * name;
NSLog(@"%@",name);
}
打印结果:
2016-08-11 17:54:26.566 LXNetworkking[11120:177772] hello!
* 在 @interface~ @end的外部定义并初始化
lxView.h
#import <UIKit/UIKit.h>
extern NSString * name =@"hello!"; //必须添加 extern
@interface lxView : UIView
@end
lxView.m
* 在 @implementation ~ @end的内部定义并初始化
#import "lxView.h"
@implementation lxView
@end
ViewController.m
#import "ViewController.h"
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
extern NSString * name;
NSLog(@"%@",name);
}
打印结果:
2016-08-11 17:38:06.937 LXNetworkking[10672:170415] hello!
lxView.h
#import <UIKit/UIKit.h>
@interface lxView : UIView
@end
lxView.m
#import "lxView.h"
@implementation lxView
extern NSString * str = @"hello!"; //extern 可带可不带
@end
ViewController.m
#import "ViewController.h"
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
extern NSString * name;
NSLog(@"%@",name);
}
打印结果:
2016-08-11 17:38:06.937 LXNetworkking[10672:170415] hello!
* 在 @implementation ~ @end的外部定义并初始化lxView.h
#import <UIKit/UIKit.h>
@interface lxView : UIView
@end
lxView.m
<pre name="code" class="objc"> #import <UIKit/UIKit.h>
extern NSString * str = @"hello!";//extern 可带可不带
@interface lxView : UIView
@end
ViewController.m
#import "ViewController.h"
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
extern NSString * name;
NSLog(@"%@",name);
}
打印结果:
2016-08-11 17:38:06.937 LXNetworkking[10672:170415] hello!
总结:
* 工作原理:先在当前文件查找有没有全局变量,没有找到,才会去其他文件查找。
* extern声明,仅适于修饰全局变量,不能去修饰其他的变量。一般情况下默认,定义的全局变量都带有extern,所以不过多解释。
特点如下:
1)存储区:静态存储区没变(静态存储区在整个程序运行期间都存在);
2)作用域:全局静态变量在声明他的文件之外是不可见的。准确地讲从定义之处开始到文件结尾。非静态全局变量的作用域是整个源程序(多个源文件可以共同使用);而静态全局变量则限制了其作用域, 即只在定义该变量的源文件内有效, 在同一源程序的其它源文件中不能使用它。
好处:
1)不会被其他文件所访问,修改;
2)其他文件中可以使用相同名字的变量,不会发生冲突。
特点如下:
1)存储区:有栈变为静态存储区rw data,生存期为整个源程序,只能在定义该变量的函数内使用。退出该函数后, 尽管该变量还继续存在,但不能使用它;
2)作用域:作用域仍为局部作用域,当定义它的函数或者语句块结束的时候,作用域随之结束。
函数的定义和声明默认情况下是extern的,但静态函数只是在声明他的文件当中可见,不能被其他文件所用。只能被本文件中的函数调用,
而不能被同一程序其它文件中的函数调用
好处:
1)其他文件中可以定义相同名字的函数,不会发生冲突
2) 静态函数不能被其他文件所用。
开发使用场景:在一个文件中经常使用的字符串常量,可以使用static与const组合
* 开发中常用static修饰全局变量,只改变作用域
* 为什么要改变全局变量作用域,防止重复声明全局变量。
* 开发中声明的全局变量,有些不希望外界改动,只允许读取。
* 比如一个基本数据类型不希望别人改动
原因:
static与const组合:在每个文件都需要定义一份静态全局变量。
extern与const组合:只需要定义一份全局变量,多个文件共享。
全局常量正规写法:开发中便于管理所有的全局变量,通常搞一个GlobeConst文件,里面专门定义全局变量,统一管理,要不然项目文件多不好找。
* extern声明,仅适于修饰全局变量,不能去修饰其他的变量。一般情况下默认,定义的全局变量都带有extern,所以不过多解释。
三,static
1.static修饰全局变量
* 在全局变量前加static,全局变量就被定义成为一个全局静态变量(全局变量和静态全局变量的生命周期是一样的,都是在堆中的静态区,在整个工程执行期间内一直存在)特点如下:
1)存储区:静态存储区没变(静态存储区在整个程序运行期间都存在);
2)作用域:全局静态变量在声明他的文件之外是不可见的。准确地讲从定义之处开始到文件结尾。非静态全局变量的作用域是整个源程序(多个源文件可以共同使用);而静态全局变量则限制了其作用域, 即只在定义该变量的源文件内有效, 在同一源程序的其它源文件中不能使用它。
好处:
1)不会被其他文件所访问,修改;
2)其他文件中可以使用相同名字的变量,不会发生冲突。
2.static修饰局部变量
* 在局部变量之前加上关键字static,局部变量就被定义成为一个局部静态变量。特点如下:
1)存储区:有栈变为静态存储区rw data,生存期为整个源程序,只能在定义该变量的函数内使用。退出该函数后, 尽管该变量还继续存在,但不能使用它;
2)作用域:作用域仍为局部作用域,当定义它的函数或者语句块结束的时候,作用域随之结束。
3.static修饰函数
* 在函数的返回类型前加上关键字static,函数就被定义成为静态函数。函数的定义和声明默认情况下是extern的,但静态函数只是在声明他的文件当中可见,不能被其他文件所用。只能被本文件中的函数调用,
而不能被同一程序其它文件中的函数调用
好处:
1)其他文件中可以定义相同名字的函数,不会发生冲突
2) 静态函数不能被其他文件所用。
四、static与const联合使用
static与const作用:声明一个只读的静态变量开发使用场景:在一个文件中经常使用的字符串常量,可以使用static与const组合
* 开发中常用static修饰全局变量,只改变作用域
* 为什么要改变全局变量作用域,防止重复声明全局变量。
* 开发中声明的全局变量,有些不希望外界改动,只允许读取。
* 比如一个基本数据类型不希望别人改动
// 声明一个静态的全局只读常量
static const int a = 20;
// staic和const联合的作用:声明一个静态的全局只读常量
// iOS中staic和const常用使用场景,是用来代替宏,把一个经常使用的字符串常量,定义成静态全局只读变量.
// 开发中经常拿到key修改值,因此用const修饰key,表示key只读,不允许修改。
static NSString * const key = @"name";
// 如果 const修饰 *key1,表示*key1只读,key1还是能改变。
static NSString const *key1 = @"name";
五、extern与const联合使用
开发中使用场景:在多个文件中经常使用的同一个字符串常量,可以使用extern与const组合。原因:
static与const组合:在每个文件都需要定义一份静态全局变量。
extern与const组合:只需要定义一份全局变量,多个文件共享。
全局常量正规写法:开发中便于管理所有的全局变量,通常搞一个GlobeConst文件,里面专门定义全局变量,统一管理,要不然项目文件多不好找。
GlobeConst.h
extern NSString * const nameKey = @"name";
GlobeConst.m
#import <Foundation/Foundation.h>
NSString * const nameKey = @"name";