有一些答案,但我认为它们都对您没有真正的帮助,因为它们描述了技术含义。
让我们看一下第一个声明:
NSArray *array …;
当有人谈论这段代码时,你会发现array 是NSArray 的实例对象。甚至每个有经验的 Objective-C 开发人员都知道这句话的真正含义,这是完全错误的。正确的说法是什么?
A. NSArray的实例对象
一个实例对象有一个状态,基本上是一组存储的数据。为此,它需要在创建对象时保留的内存。您不直接处理它,但它在+alloc 内部完成。这是在程序运行时在运行时显式完成的(“在堆上”、“堆分配”)。
… [NSArray alloc] … // Get memory for an instance object of type NSArray
您仅通过其地址(占用的内存区域的第一个内存单元的编号)来寻址此类对象。 (每个存储单元都有一个编号,称为地址。是的,这类似于通过街道上的房屋编号来寻址房屋中的居民。因此,您可以将记忆想象成一条非常非常长的街道。)
但是像array 这样的标识符只在编译时存在,并在程序编译时被删除。因此很明显,像array 这样的标识符永远不会表示实例对象。
简短版:实例对象是内存区域,仅通过第一个内存单元(位置)的编号进行寻址。
B.指向实例对象的指针(引用)
但是如果一个实例对象在运行时通过它的编号来寻址,我的代码如何处理它?诀窍是数字存储在一个变量中。 (看不完全正确的C标准。他们说它存储在一个对象中。但是这些对象与Objective-C对象无关,我将重点关注变量,对象的子类型。)
所以你可以有一个变量来存储实例对象的内存位置。这样的变量称为指针变量。它是用额外的* 声明的。所以
NSArray * array;
表示:标识符为array的指针变量,用于存储NSArray类型的实例对象的位置。
(地址是数字。它们是整数。因此指针变量和整数之间存在联系。您可以对这些数字应用计算,称为“指针算术”。在某些情况下,这对 C 开发人员很重要,但是不适合作为 Objective-C 开发人员的你。)
这个变量的内存不是用+alloc显式保留的,而是在你进入你的代码区域时隐式保留的,在该区域声明了变量。 (再次不完全正确,但足以解释这个解释。)所以让我们再看一下对象创建的一个非常简单的版本:
- (void)method
{
NSArray *array = [NSArray alloc];
}
该语句右边为对象实例保留内存并返回一个数字,即内存区域的地址。此编号分配给名为array 的引用。该引用的内存(它存储一些东西,因此需要内存)通过其定义隐式保留。
指向对象的指针通常称为引用。
短版:所以array是一个实例对象的引用,存储了实例对象的地址。
C.指向实例对象指针的指针
好的,我们有一个实例对象,它占用内存来存储对象状态,通过它的内存位置(地址)来寻址。然后我们有一个存储该地址的变量,引用array。您可以通过其标识符来解决这个问题。
但有时它也很有用——我将在下面有一个例子——通过它的地址来寻址引用变量。您可以使用地址运算符& 获取变量的地址。
&array
你得到的是:一个变量的地址存储了一个实例对象的地址。这种双重间接的类型(“地址……地址……”)是
NSArray ** array;
这是因为在变量定义中* 表示“地址”。
短版:指向引用的指针是一个变量,它存储了一个变量的地址,该变量存储了一个实例对象的地址。它用** 声明。 (是的,你可以有更多级别的间接......不,这不太容易理解。)
D.引用指针的用例
在 Objective-C 中通常不需要这种双重间接。但是有一个重要的用例,一个错误输出参数。为了理解这一点,我们将研究一个具有单个间接参数的方法,如您所知:
- (BOOL)methodThatCanProduceAnError:(NSError*)error
{
…
error = [NSError alloc] … // Create an error object and store its address to the reference variable error.
return NO;
…
}
此方法应通过其error 参数发出错误。
你用这样的代码“调用”这样一个方法:
…
NSError *error; // A reference variable pointing to an instance object of type NSError
error = nil; // I do not have an error, so it points to "nothing".
[anInstance methodThatCanProduceAnError:error];
会发生什么?您将实例对象的地址作为参数传递给该方法。你通过nil 说“我没有错误”。这很清楚,因为该方法应该传递对实例对象的引用。
所以有趣的部分在方法内部,当它创建错误对象并尝试将其传递出去时。 这不起作用!
在 Objective-C 中,参数总是按值传递。这意味着,“调用代码”中的参数值被获取并分配给“被调用代码”内的 new 变量。这个new 变量称为参数变量。当方法试图改变变量的值时
error = [NSError alloc] … // Create an error object and store its address to the reference variable error.
它仅更改方法内的变量副本的值。新的引用永远不会找到方法的出路,调用代码中的变量error 保持不变。在“调用代码”中,error 仍然具有旧的nil 值。
所以我们需要一种方法来改变“调用代码”中引用的内容。我们通过将引用的地址传递给方法来做到这一点:
- (BOOL)methodThatCanProduceAnError:(NSError**)error // double indirection
{
…
*error = [NSError alloc] … // *error is a reference to an object
return NO;
…
}
首先在方法头中,我们声明一个双重间接的参数变量:引用的地址。其次我们将实例对象的地址分配给错误指向的位置。 (这是由* 完成的。)赋值不是对参数变量进行,而是对位置进行,参数变量指向。
因此我们可以传递引用的地址:
NSError *error; // A reference variable pointing to an instance object of type NSError
error = nil; // I do not have an error, so it points to "nothing".
NSError ** pointerToError = &error; // The address of the reference.
[anInstance methodThatCanProduceAnError:pointerToError];
现在该方法更改了引用变量的内容。错误传递出去。