指针在Objective-C中传递问题

时间:2016-08-22 01:12:30

标签: objective-c pointers

我想通过传递指针而不是在方法中使用return来更改外部内容。

所以我写了一些像这样的代码:

NSString *str = @"Hello";
[self changeString:str];

//....

-(void)changeString:(NSString *)str
{
    *str = @"World";
}

我知道上面的代码不起作用,因为它应该使用指向指针((NSString **)str)的指针来改变任何外部变量。

但我想知道上述代码不起作用的原因是什么。

我认为*str应该是local str(内部)和外部str指向的内容,因此更改它应该更改外部内容{ {1}}。

但是Xcode给出了错误:

str

那么有人可以解释这个机制吗?我的Assigning to 'NSString' from incompatible type 'NSString *' 生锈了。

2 个答案:

答案 0 :(得分:2)

考虑你的代码:

-(void)changeString:(NSString *)str
{
    *str = @"World";
}

str的类型为NSString*。因此,*str的类型为NSString(在某种程度上甚至有意义;继续阅读)。

@"World"的类型为NSString*。该类型与*str的类型不匹配。正如编译错误所说:

Assigning to 'NSString' from incompatible type 'NSString *'

请注意该错误消息中的两种不同类型。

从语法上讲,这类似于拥有struct foo并尝试执行:

struct foo* a;
struct foo b;
b = a;

这不起作用,因为您无法将指针值分配给结构变量。

但问题更深入了。

对于结构体,可以将一个分配给另一个,如下所示:

struct foo a;
struct foo b;
b = a;

编译器生成代码以将a的值复制到b。这通常类似于简单的memcpy()。但这不是一个正确的语义副本,因为可能需要专门处理某些字段。例如,struct foo可能包含一个指针,可能每个实例都应该有自己独立的指针而不是共享指针。或者指针可能需要引用计数。

使用Objective-C对象指针,情况仍然更加复杂。您可能有一个NSString*类型的变量,它包含一个指向字符串对象的指针。但是该对象本身不是NSString的实例。 NSString是类集群根的抽象类。存在用于不同专用目的的各种私有具体子类,并且每个字符串对象是其中之一的实例。 (还有公共子类NSMutableString,您也可以在任何NSString*变量中存储指向其具体子类之一的指针。)

因此,str可能指向一个类的实例,而@"World"可能是另一个类的实例。它们可能具有不同的,不兼容的内部布局和大小。通过另一个存储器直接复制一个内容将无法正常工作。它甚至可能不适合分配的空间。

此外,在现代Objective-C运行时,Objective-C类不再是简单结构周围的语法糖。为了解决脆弱的基类问题,不假定Objective-C类/实例的大小和布局在编译时是已知的。还有标记指针,其中对象指针并不真正指向任何已分配的内存;对象的值完全在指针值本身内编码。

因此,编译器甚至没有发出副本代码所需的信息。你甚至不能有意义地取消引用对象指针类型。这就是我上面提到的NSString并不是真正意义上的正确类型。除了*str之外,您可以使用&*str做任何事情。{/ 1}}。

答案 1 :(得分:0)

对象在Objective-C(以及Swift)中通过引用传递。

当您将NSString作为参数传递时,您传递指针是正确的,但是您将指针传递给字符串对象,而不是传递给内存中的字节数组。您不应该尝试取消引用指针并写入内存。这不是它的工作方式。

如果你想传入一个字符串,让它被修改,并传回修改后的结果,你有几个选择。

tl; dr:使用可变字符串。

细节:

一种选择是传入一个可变字符串(类NSMutableString。然后更改字符串的内容。

-(void)changeString:(NSMutableString *)str
{
    [str setString:  @"World"
}

在该代码中,您告诉字符串对象str丢弃它的旧内容并将其替换为" World"。返回时,调用者可能仍然会引用它传递给changeString的字符串对象,但该字符串现在将包含新内容。

第二个选项是传入指向字符串指针的指针:

-(void)changeString:(NSString **)str
{
    *str = @"World";
}

在第二个选项中,传入指向字符串指针的指针。该函数将调用者的指针更改为str,以便指向不同的字符串对象。然而,第二种选择是有风险的。上面的代码可以工作,因为我们用字符串文字替换字符串,字符串文字是在编译时创建的,并且不会被释放。相反,我们使用这样的代码:

-(void)changeString:(NSString **)str
{
    NSString *newString = [NSString stringWithFormat: @"Number %d", intVar];
    *str = newString;
}

然后会发生坏事。第一行将创建一个新的字符串对象并将其保存到变量newString中。变量newString是一个强引用,因此新创建的对象在函数的生命周期内持续存在。

但是,在下一行中,将指向newString的指针复制到变量str中,编译器并不知道。没有进行内存管理以使str对新创建的字符串对象拥有引用。

当你点击这个函数的右大括号时,newString超出了范围,它包含的对象可能会在那一刻或不久的某个时候被释放。 (它可能放在一个自动释放池中,这意味着它不会被释放,直到下一次程序返回并访问事件循环。在调用函数中看起来一切都会好的,然后,坏事就会发生。)