Const_Static_Extern

在 Effective Objective-C 中 第 4 条,说要少用 #define 多用常量 ,这里就聊聊 const static extern 这些关键字

const

关于常量,我们有以下几种定义方式

1
2
3
4
5
6
7
8
9
int one = 1;

const int *i = &one; // 1

int const *i = &one; // 2

int * const i = &one; // 3

const int * const i = &one; // 4

我们知道当我们声明一个指针的时候

1
2
3
int one = 1;
int two = 2;
int *i = &one;

其实 i 本身有一个地址 A ,而 i 这个变量存储了一个指针,这个指针指向了 one 的地址,也就是 B,而这个地址中存放的是 2。

其实简单的来说,const 的位置,决定了 A 是 const 还是 B 是 const,下边逐一分析。

👉int const *i 和 const int *i

关于 1 和 2 其实是一样的,都等于 int const* i,也就是 *i 是被 const 修饰的,我们无法再次改变 *i ,但我们可以改变 i。也就是我们无法 *i = two 但我们可以 i = &two

1
2
3
4
5
6
int one = 1;
int two = 2;
int const* i = &one;

*i = two; // error: Read-only variable is not assignable
i = &two; // correct

👉int * const i

这种情况来说,i 是被 const 修饰的,我们是可以改变 *i,而无法改变 i。也就是我们可以 *i = two 但无法 i = &two

1
2
3
4
5
6
int one = 1;
int two = 2;
int * const i = &one;

*i = two; // correct
i = &two; // error: Cannot assign to variable 'i' with const-qualified type 'int *const'

👉const int * const i

这种情况就比较简单了,我们无法改变 *i,也无法改变 i 的地址。也就是我们无法 *i = two,也无法 i = &two

1
2
3
4
5
6
int one = 1;
int two = 2;
int const * const i = &one;

*i = two; // error: Read-only variable is not assignable
i = &two; // error: Cannot assign to variable 'i' with const-qualified type 'int *const'

👉再看

我们来看一个有意思的事情

1
2
3
4
5
6
7
8
9
10
11
int a = 10;
int b = 11;
NSLog(@"%p", &a); // 1
NSLog(@"%p", &b); // 2
int * p;
p = &a;
NSLog(@"%d %p %p", *p, p, &p); // 3
*p = b;
NSLog(@"%d %p %p", *p, p, &p); // 4

NSLog(@"%d", a); // 5 是多少

想想在往下看

1
2
3
4
5
1. 0x7ffee643b908
2. 0x7ffee643b8f8
3. 10 0x7ffee643b90c 0x7ffee643b8f8
4. 11 0x7ffee643b90c 0x7ffee643b8f8
5. 11

怎么样,和你想的一样么,我们发现 a 被偷偷的改成了 11,这是为什么,我们分析下地址就知道了。

我们先看下地址

我们知道 *p 是取地址的内容,当我们在执行 *p = b 的时候,其实就是 取 0x7ffee643b90c 的内容,并修改为 11 ,而这个叫 a 的变量,他的地址其实还是原来的。当我们输出 a 的时候,a 变成了 11。如果我们想改变 *p 的值,安全的做法,是 p = &someValue不要使用 *p = someValue 所以当我们 int const *p 的时候,我们是无法修改 *p 的值的。

当我们知道一个数据的地址的时候,如何取地址中存放的数据呢,例如上边有个地址是 0x7ffee643b90c,如何知道 这个地址存放的是什么。 可以这样做 *(int *)(0x7ffee643b90c) 这样,就能取到这个地址中存放的数据了,但是前提是你需要知道这个地址中的数据是什么类型的,不然取出来,也是不正确的。

在 OC 中,其实是一样的 当我们创建一个对象 NSObject *obj1 = [NSObject new] 的时候, obj1 是一个地址,指向了真正的系统分配的内存空间,当我们用 NSObject * const obj1 来修饰的时候,我们的 obj1 就再也无法被赋值为其他值了,因为这个 obj1 是被 const 修饰的,而 *obj1 是可以被改的,有兴趣可以试试。

👉小结

关于 const 的位置,我们可以这么理解,const 后边的哪个东西,就是我们修饰的常量,是不可变的。当 int const* p 的时候,也就是这个 *p 是常量,我们无法改变他,而当 int * const p 的时候,这个 p 是常量,我们无法改变他。

static

static 有以下几点特性

👉内部可见

在 OC 中,当在我们在 .m 中写一个被 static 修饰的变量或者函数的时候,我们只能在 这个文件 中使用,而不能再别的地方调用。在其他地方调用,将会提示,找不到该定义。但如果在 .h 中定义的话,那么是可以被外部使用的。

👉只初始化一次

如下代码,static 修饰的 count 只会初始化一次,每次调用 count 会累加,而 不用 static 修饰的话,每次会重新初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
int fun() 
{
static int count = 0;
count++;
return count;
}

int main()
{
printf("%d ", fun()); // 1
printf("%d ", fun()); // 2
return 0;
}

👉存储在 data segment 中

被 static 修饰的存储在 data segment,而在函数中,没有被 static 修饰的,存放在 stack segment,更详细的分配,可以参见这篇文章

👉未初始化自动赋值为默认值

被 static 修饰的变量,如果没有指定值,将会被赋值为默认值

1
2
3
static int x ;
int y;
NSLog(@"%d %d", x, y); // 0 1082916864

👉只能是特定值,不能是函数

static 修饰的只能被赋值为特定的值,而不能是函数

1
2
3
4
5
6
7
static int x = initializer(); //error: Initializer element is not a compile-time constant
NSLog(@"%d", x);

int initializer(void)
{
return 50;
}

extern

这个就比较简单了,就是一个标记,外部可用,当我们在 .m 中声明一个常量的时候,需要供外部使用就需要这么写。在 Foundation 框架中,我们经常能看到这种写法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14

// Test.m
NSString *const kNotificationTest1 = @"kNotificationTest1";

@implementation Test

@end

// Test.h
extern NSString * const kNotificationTest1;

@interface Test : NSObject

@end

inline

inline 编译器标识符相当于在执行这段代码的时候,把这段代码直接拿过来,而不是像调用函数那样直接调用(函数会跳转,有一些开销)。

1
2
3
4
5
6
7
8
9
10
11
12

// Test.m 中

__inline__ __attribute__((always_inline)) void xiaoma(void) {
NSLog(@"ddd");
}


// Test.h 中

void xiaoma(void);