Objective-C中的指针变量

- (void)viewDidLoad {
    [super viewDidLoad];
    
    UILabel *label1 = [UILabel new];
    label1.frame = (CGRect){0, 0, 100, 100};
    label1.text = @"TEXT";
    [self.view addSubview:label1];
    [self xindong:label1];
    NSLog(@"%@", label1);
}

- (void)xindong:(UILabel *)label2 {
    label2.text = @"TEXT 1";
    label2.backgroundColor = [UIColor redColor];
    [label2 removeFromSuperview];
    label2 = nil;
}

上述代码的输出结果是否为 (null),为什么?

指针以及指针变量

首先,我们先来回顾下指针的基本概念。记得大学学习C语言的时候,比较晦涩难懂的地方便是指针。

<li>指针

计算机的内存是由连续的存储单元组成的,每个存储单元都有惟一确定的编号,这个编号就是“地址”。如果程序中定义了一个变量,编译系统在编译程序时,会根据变量的类型给这个变量分配一定长度并且连续的存储单元。例如:
  int i=1; char ch=‘A’; float f=2.5;
经编译后它们在内存中的存放示意情况如下图所示:

《Objective-C中的指针变量》 《C程序设计语言》

  图中,右边是变量的名称;中间是变量的值,也就是内存单元的内容;而左边是内存单元的编号,也就是内存单元的地址。内存单元地址和内存单元内容就好比一座旅馆中房间的编号和住在房间中的旅客,如果要拜访房间中的某位旅客首先要根据这位旅客所在的房间编号找到房间才能访问旅客,同样对内存单元的访问也要先获得内存单元地址。为了形象地描述这种指向关系,我们把内存单元地址称为指针,或者说指针是内存单元地址的别名。

<li>内存单元访问方式

对内存单元的访问有两种方式:直接访问和间接访问。直接访问是直接按地址1000找到 i 的存储单元,从而对变量 i 进行访问;而间接访问是指将变量 i 的地址存放在另一个内存单元中,当要对变量 i 进行存取时先读取另一个内存单元的值,得到要存取变量 i 的地址,再对该变量进行访问。例如要读取 i 的值时,如下图所示,先访问保存着 i 的地址的内存单元 1020,其中存放的数据 1000 是变量 i 的地址,再通过该地址找到变量 i 的内存单元,最后取出存放在其中的数值 3。

《Objective-C中的指针变量》 《C程序设计语言》

<li>指针变量

顾名思义,指针变量就是指存放地址的变量。指针变量是一种特殊的变量,它不同于一般的变量,一般变量存放的是数据本身,而指针变量存放的是数据的地址。

参数传递

在OC语言中,我们创建对象是通过这样的方式:NSObject *objc = [[NSObject alloc] init];习惯性将objc称为对象(或许是OC中淡化了指针的概念)。而实际上objc是一个指针变量,它存放[[NSObject alloc] init]这个对象的地址,或者说objc指向这个对象。当我们要访问所创建的对象时,先读取指针变量objc存储的值(即目标对象的地址),再通过该地址访问目标对象的内存单元。这也就是上述所说的间接访问。

接下来,我们可以回到文章开头的那两段代码。大多数人一眼就能看出来,label1的输出结果并不为(null)。但是它的的确确从父视图上移除了,这又怎么解释?
  1、首先我们创建了一个UILabel对象 ([UILabel new]),并通过一个指针变量label1来存放该对象的地址。
  2、在调用xindong:方法时,编译器为函数中的形参label2分配内存单元,将实参label1的拷贝传递给形参label2。在函数调用结束后,形参所占的内存单元被释放。若函数未发生调用,则形参不占内存。
所以label2也同样的存放我们最初创建的UILabel对象 ([UILabel new]) 的地址,或者说label2指向该对象。可以通过po命令验证下:

《Objective-C中的指针变量》

  
注意:这里输出的地址 0x7fe778509fb0 并不是label1label2的地址,而是它们所共同指向的对象的地址,也即它们所共同存储的值

label1label2占不同的内存单元,故它们的地址是不同的,如下:

《Objective-C中的指针变量》

  3、在函数中,我们对
label2进行背景色修改、文字显示修改、移除等操作,
UILabel对象均会显示相应变化,是因为
label2存放了
UILabel对象的地址,通过指针变量
label2即可访问到
UILabel对象 (间接访问) 。而执行语句
label2 = nil,表示将
label2这个指针变量指向nil,即不再指向
UILabel对象,但并没有将
label1这个指针变量指向nil,它依旧指向
UILabel对象,所以上述输出结果也就自然不是(null)了。

小扩展

简单了解原理之后,如果我们想让label1的输出结果也为(null),应该怎么做呢?其实很简单,只要在调用xindong:这个方法时,传递一个二级指针即可,即指针变量label1的地址,像这样:- (void)xindong:(UILabel *__strong *)label2,在函数体中执行语句*label2 = nil 即可达到期望效果。
  简单解释下:这里的label2是一个二级指针变量,它存放的值是一级指针变量label1地址。*label2 (指针解引用) 表示通过指向关系间接访问label1。看到这里或许你会有这样的疑问:按照上一句话的逻辑,那么间接访问UILabel对象应该是*label1,为什么我们都是通过label1进行访问而去掉星号*,加上星号*Xcode会报错?因为*lablel1 (指针解引用) 表示访问UILabel对象的值而并非对象的地址,也许是OC不允许我们直接访问对象的值,具体可以参考这里

参考资料

《C程序设计语言》
https://zh.m.wikipedia.org/zh-cn/%E6%8C%87%E6%A8%99_(%E9%9B%BB%E8%85%A6%E7%A7%91%E5%AD%B8)
http://baike.baidu.com/item/%E6%8C%87%E9%92%88%E5%8F%98%E9%87%8F
https://stackoverflow.com/questions/33979828/cannot-pass-object-with-interface-type-nsstring-by-value-to-variadic-function

    原文作者:心董儿
    原文地址: https://www.jianshu.com/p/e93188a987c6
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞