私はObjective-CとCocoaを学んでいて、次のステートメントに出くわしました。
Cocoaフレームワークは、文字列リテラルではなくグローバル文字列定数が辞書キー、通知名と例外名、および文字列を受け取るいくつかのメソッドパラメーターに使用されることを想定しています。
私は高級言語でしか作業したことがないので、文字列の詳細をそれほど考慮する必要はありませんでした。文字列定数と文字列リテラルの違いは何ですか?
私はObjective-CとCocoaを学んでいて、次のステートメントに出くわしました。
Cocoaフレームワークは、文字列リテラルではなくグローバル文字列定数が辞書キー、通知名と例外名、および文字列を受け取るいくつかのメソッドパラメーターに使用されることを想定しています。
私は高級言語でしか作業したことがないので、文字列の詳細をそれほど考慮する必要はありませんでした。文字列定数と文字列リテラルの違いは何ですか?
Objective-C では、構文@"foo"
は の不変のリテラルインスタンスですNSString
。Mike が想定しているように、文字列リテラルから定数文字列を作成するわけではありません。
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";
ここでの構文演習のポイントは、同じアドレス空間内の複数のフレームワーク(共有ライブラリ) で使用されている文字列のインスタンスが 1 つだけであることを確認することで、文字列を効率的に使用できるようにすることです。(キーワードの配置は重要です。ポインター自体が一定であることが保証されます。)const
8MB の RAM を搭載した 25MHz の 68030 ワークステーションの時代ほどメモリの焼き付けは大したことではありませんが、文字列の比較には時間がかかる場合があります。等しい時間文字列のほとんどがポインターと等しいことを確認することも役立ちます。
たとえば、オブジェクトからの通知を名前でサブスクライブしたいとします。名前に非定数文字列を使用するとNSNotificationCenter
、通知を投稿すると、誰が興味を持っているかを判断するときに、バイトごとの文字列比較が多数行われる可能性があります。比較される文字列が同じポインターを持っているために、これらの比較のほとんどが短絡されている場合、それは大きな勝利になる可能性があります。
リテラルは値であり、定義上不変です。例:定数は、読み取り専用の変数またはポインターです10
。例:
文字列リテラルは のような式です。コンパイラは、これを のインスタンスに置き換えます。
文字列定数は、 への読み取り専用ポインタです。例えば:const int age = 10;
@""
NSString
NSString
NSString *const name = @"John";
最後の行のいくつかのコメント:
objc_sendMsg
2は、オブジェクトを で修飾してもかまいませんconst
。不変オブジェクトが必要な場合は、オブジェクト内でその不変性をコーディングする必要があります3。 @""
式は不変です。これらは、コンパイル時に4のインスタンスに置き換えられます。これは、固定メモリ レイアウトを持つNSConstantString
の特殊なサブクラスです5。これは、コンパイル時に初期化できる唯一のオブジェクトである理由も説明しています6。NSString
NSString
定数文字列はとconst NSString* name = @"John";
同等NSString const* name= @"John";
です。ここでは、構文とプログラマの意図の両方が間違っています:const <object>
は無視され、NSString
インスタンス ( NSConstantString
) はすでに不変でした。
1キーワードconst
apply は、すぐ左にあるものすべてに適用されます。左側に何もない場合、すぐ右側にあるものに適用されます。
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];
2 番目のケースでは、const ポインターを持つキーを使用しているので、代わり[a isEqualToString:b]
に(a==b)
. の実装はisEqualToString:
、ハッシュを比較してから C 関数を実行するstrcmp
ため、ポインターを直接比較するよりも遅くなります。これが、定数文字列の方が優れている理由です。比較が高速で、エラーが発生しにくいのです。
定数文字列もグローバルにしたい場合は、次のようにします。
// header
extern NSString *const name;
// implementation
NSString *const name = @"john";
私のObjectiveCはまったく存在しないので、C++を使用しましょう。
文字列を定数変数に格納する場合:
const std::string mystring = "my string";
メソッドを呼び出すときは、my_stringを使用し、文字列定数を使用します。
someMethod(mystring);
または、文字列リテラルを使用してこれらのメソッドを直接呼び出すことができます。
someMethod("my string");
おそらく、文字列定数の使用を推奨しているのは、ObjectiveCが「インターン」を行わないためです。つまり、同じ文字列リテラルを複数の場所で使用する場合、実際には、文字列の別のコピーを指す別のポインタになります。
辞書キーの場合、これは大きな違いになります。2つのポインターが同じものを指していることがわかる場合は、文字列全体を比較して文字列の値が等しいことを確認するよりもはるかに安価です。
編集:マイク、C#の文字列は不変であり、同じ値のリテラル文字列はすべて同じ文字列値を指します。これは、文字列が不変である他の言語にも当てはまると思います。可変文字列を持つRubyでは、シンボルという新しいデータ型が提供されます( "foo"と:foo。前者は可変文字列で、後者はハッシュキーによく使用される不変識別子です)。