题
我正在学习 Objective-C 和 Cocoa,并遇到过这样的说法:
Cocoa 框架期望将全局字符串常量而不是字符串文字用于字典键、通知和异常名称以及一些采用字符串的方法参数。
我只使用过高级语言,所以从来不需要考虑那么多字符串的细节。字符串常量和字符串文字有什么区别?
解决方案
在 Objective-C 中,语法 @"foo"
是一个 不可变的, 文字 的实例 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
关键词很重要;它保证指针本身保证是常量。)
虽然燃烧内存并不像具有 8MB RAM 的 25MHz 68030 工作站那样大,但比较字符串是否相等可能需要时间。确保大多数情况下字符串相等也将是指针相等会有所帮助。
例如,您想按名称订阅来自对象的通知。如果您使用非常量字符串作为名称,则 NSNotificationCenter
在确定谁对此感兴趣时,发布通知可能会进行大量的逐字节字符串比较。如果大多数比较因为被比较的字符串具有相同的指针而被短路,那么这可能是一个巨大的胜利。
其他提示
一些定义
A 文字 是一个值,根据定义它是不可变的。例如: 10
A 持续的 是只读变量或指针。例如: const int age = 10;
A 字符串字面量 是一个像这样的表达式 @""
. 。编译器会将其替换为一个实例 NSString
.
A 字符串常量 是一个只读指针 NSString
. 。例如: NSString *const name = @"John";
最后一行的一些评论:
- 这是一个常量指针,而不是一个常量对象1.
objc_sendMsg
2 不在乎你是否用以下方式限定对象const
. 。如果你想要一个不可变的对象,你必须在对象内部编写不可变性的代码3. - 全部
@""
表达式确实是不可变的。他们被替换了4 在编译时使用以下实例NSConstantString
, ,这是一个专门的子类NSString
具有固定的内存布局5. 。这也解释了为什么NSString
是唯一可以在编译时初始化的对象6.
A 常量字符串 将会 const NSString* name = @"John";
这相当于 NSString const* name= @"John";
. 。这里,语法和程序员的意图都是错误的: const <object>
被忽略,并且 NSString
实例 (NSConstantString
) 已经是不可变的。
1 关键词 const
适用于紧邻其左侧的任何内容。如果其左侧没有任何内容,则它适用于紧邻其右侧的任何内容。
2 这是运行时用来发送 Objective-C 中所有消息的函数,因此您可以使用它来更改对象的状态。
3 例子:在 const NSMutableArray *array = [NSMutableArray new]; [array removeAllObjects];
const 不会阻止最后一条语句。
4 重写表达式的LLVM代码是 RewriteModernObjC::RewriteObjCStringLiteral
在 RewriteModernObjC.cpp 中。
5 为了看到 NSConstantString
定义,在 Xcode 中 cmd+单击它。
6 为其他类创建编译时常量很容易,但需要编译器使用专门的子类。这会破坏与旧 Objective-C 版本的兼容性。
回到你的报价
可可框架期望将全局字符串常数而不是字符串文字用于字典键,通知和异常名称,以及一些采用字符串的方法参数。当您有选择时,您应该始终更喜欢字符串常数而不是字符串文字。通过使用字符串常数,您可以邀请编译器的帮助来检查您的拼写,从而避免运行时错误。
它说文字容易出错。但这并没有说它们也更慢。比较:
// 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,其中前者是可变字符串,后者是常用于哈希键的不可变标识符)。