我想通过传递指针而不是在方法中使用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 *'
生锈了。
答案 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
超出了范围,它包含的对象可能会在那一刻或不久的某个时候被释放。 (它可能放在一个自动释放池中,这意味着它不会被释放,直到下一次程序返回并访问事件循环。在调用函数中看起来一切都会好的,然后,坏事就会发生。)