我正在学习 Objective-C 和 Cocoa,并且遇到了这样的声明:
Cocoa 框架期望全局字符串常量而不是字符串字面量用于字典键、通知和异常名称,以及一些采用字符串的方法参数。
我只用过高级语言,所以从来不用考虑字符串的细节。字符串常量和字符串文字有什么区别?
我正在学习 Objective-C 和 Cocoa,并且遇到了这样的声明:
Cocoa 框架期望全局字符串常量而不是字符串字面量用于字典键、通知和异常名称,以及一些采用字符串的方法参数。
我只用过高级语言,所以从来不用考虑字符串的细节。字符串常量和字符串文字有什么区别?
在 Objective- C中,语法@"foo"
是. 它不会像 Mike 假设的那样从字符串文字生成常量字符串。NSString
Objective-C 编译器通常在编译单元内执行实习文字字符串——也就是说,它们合并同一文字字符串的多次使用——并且链接器可以在直接链接到单个二进制文件的编译单元之间进行额外的实习。(由于 Cocoa 区分了可变字符串和不可变字符串,而文字字符串也总是不可变的,所以这可以简单且安全。)
另一方面,常量字符串通常使用如下语法声明和定义:
// MyExample.h - declaration, other code references this
extern NSString * const MyExampleNotification;
// MyExample.m - definition, compiled for other code to reference
NSString * const MyExampleNotification = @"MyExampleNotification";
此处语法练习的重点是,您可以通过确保即使在同一地址空间中的多个框架(共享库)中也只有一个字符串实例在使用中,从而有效地使用字符串。(关键字的位置很重要;它保证指针本身是恒定的。)const
虽然燃烧内存并不像在 25MHz 68030 工作站和 8MB RAM 的时代那样大,但比较字符串是否相等可能需要时间。确保大多数时间相等的字符串也将是指针相等的帮助。
例如,您想按名称订阅来自对象的通知。如果您对名称使用非常量字符串,则NSNotificationCenter
在确定谁对它感兴趣时,发布通知可能会进行大量逐字节的字符串比较。如果这些比较中的大多数因为被比较的字符串具有相同的指针而被短路,那可能是一个很大的胜利。
字面量是一个值,根据定义它是不可变的。例如:10
常量是只读变量或指针。例如:const int age = 10;
字符串文字是一个表达式,如@""
. 编译器会将其替换为NSString
.
字符串常量是指向 的只读指针NSString
。例如:NSString *const name = @"John";
最后一行的一些评论:
objc_sendMsg
2不在乎你是否用const
. 如果您想要一个不可变对象,则必须在对象3中编写该不可变性代码。 @""
的表达式确实是不可变的。它们在编译时被4NSConstantString
的实例替换,这是NSString
具有固定内存布局的专用子类5。这也解释了为什么NSString
是唯一可以在编译时初始化的对象6。一个常量字符串将const NSString* name = @"John";
等效于NSString const* name= @"John";
. 在这里,语法和程序员的意图都是错误的:const <object>
被忽略,并且NSString
实例 ( NSConstantString
) 已经是不可变的。
1关键字const
适用于紧邻其左侧的任何事物。如果它的左边没有任何东西,它适用于它右边的任何东西。
2这是运行时用于在 Objective-C 中发送所有消息的函数,因此您可以使用它来更改对象的状态。
3示例: in const NSMutableArray *array = [NSMutableArray new]; [array removeAllObjects];
const 不会阻止最后一条语句。
4重写表达式的 LLVM 代码RewriteModernObjC::RewriteObjCStringLiteral
位于 RewriteModernObjC.cpp 中。
5要查看NSConstantString
定义,请在 Xcode 中 cmd+单击它。
6为其他类创建编译时常量很容易,但需要编译器使用专门的子类。这会破坏与旧 Objective-C 版本的兼容性。
Cocoa 框架期望全局字符串常量而不是字符串字面量用于字典键、通知和异常名称,以及一些采用字符串的方法参数。当你有选择的时候,你应该总是更喜欢字符串常量而不是字符串文字。通过使用字符串常量,您可以获得编译器的帮助来检查您的拼写,从而避免运行时错误。
它说文字容易出错。但这并不是说它们也更慢。比较:
// string literal
[dic objectForKey:@"a"];
// string constant
NSString *const a = @"a";
[dic objectForKey:a];
在第二种情况下,我使用带有 const 指针的键,因此[a isEqualToString:b]
,我可以这样做(a==b)
。的实现isEqualToString:
比较哈希然后运行C函数strcmp
,所以它比直接比较指针要慢。这就是为什么常量字符串更好的原因:它们比较起来更快,更不容易出错。
如果您还希望常量字符串是全局的,请这样做:
// header
extern NSString *const name;
// implementation
NSString *const name = @"john";
让我们使用 C++,因为我的 Objective C 完全不存在。
如果将字符串存储到常量变量中:
const std::string mystring = "my string";
现在,当您调用方法时,您使用 my_string,您使用的是字符串常量:
someMethod(mystring);
或者,您可以直接使用字符串文字调用这些方法:
someMethod("my string");
他们鼓励你使用字符串常量的原因大概是因为Objective C不做“实习”。也就是说,当您在多个地方使用相同的字符串文字时,它实际上是指向该字符串的单独副本的不同指针。
对于字典键,这有很大的不同,因为如果我可以看到两个指针指向同一个东西,这比必须进行整个字符串比较以确保字符串具有相等的值要便宜得多。
编辑: Mike,在 C# 中字符串是不可变的,具有相同值的文字字符串都指向相同的字符串值。我想这对于其他具有不可变字符串的语言也是如此。在具有可变字符串的 Ruby 中,它们提供了一种新的数据类型:符号(“foo”与 :foo,前者是可变字符串,后者是不可变标识符,通常用于哈希键)。