==================================================
Keywords: String Literal, Object, Array, Lvalue
Author: whyglinux
Date: 2007-05-16
==================================================
C 和 C++ 字串字面量(String Literal)既有相同之處,又有一些區別。瞭解這些內容對於加深字串字面量以及相關一些概念的理解、澄清一些常見的概念誤區不無助益。本文以一般字元串字面量 "hello" 為例總結說明如下。
如果你發現了本文中的錯誤,或者對本文有什麼感想或建議,可通過 whyglinux AT gmail DOT com 郵箱和作者聯絡。
相同點:
字串字面量是對象
C/C++ 中的對象(Object)指的是一Block Storage區。字串字面量是不需要建立過程就可使用的對象,所以它既沒有變數那樣的聲明或者定義(字串字面量是無名對象),也不需要象動態分配的對象那樣進行動態分配。由於這個原因,用來限定變數的類型限定符(如 const、volatile)以及儲存類別指示符(如 extern、static、auto、register)不能用在修飾字串字面量上。
數群組類型
字串字面量是數群組類型的對象,因而具有數組的一切特點。關於這一點在下面還會進一步說明。
靜態儲存期
C/C++ 中對象的生存期按照其儲存性質可分為三類:靜態儲存期(static storage duration)、自動儲存期(automatic storage duration)以及動態儲存裝置期(dynamic storage duration)。相應地,對象可根據儲存期性質分為靜態對象、自動對象和動態對象三種。
字串字面量是靜態對象,所以在程式運行期間會一直存在。
字串字面量是左值,而且是不可被更改的左值
例如,char s[] = "hello"; 中的 “hello” 是數群組類型的左值(lvalue),用於初始化 s 數組;sizeof( "hello" ) 以及 &"hello" 中的 "hello" 也都是左值。在這些情況下,"hello" 處於左值語義上下文環境中,所以不會產生下面將要提到的數群組轉換為指標的現象。
另外,有些運算不但要求其運算元是左值,還要求可變。例如,對對象進行賦值、自加、自減等運算。因為數組是不可被更改的左值,所以不能對數組進行這些操作,也就是說不存在數群組類型的賦值、自加、自減等運算。
字串字面量可以轉換為指向其首第一個字元的指標
處於右值語義環境中的字串字面量將被預設轉換為指向第一個字元的指標。例如,char* p = "hello"; 中的 “hello” 在轉換為字元指標後用於初始化指標變數 p;運算式 "hello"[0](相當於 *("hello" + 0) 或者 *"hello")中的 “hello” 也是同樣轉換為指標後參與下標運算,等等。
這種性質也是數群組類型的特點。在右值語義環境下,一般類型的對象表示的值是由其儲存內容決定的;而數群組類型的對象與此不同,它代表的值不是來源於其內容,而是來源於數組對象首元素所在的地址。這是數組最為特殊的地方,也是人們容易產生誤解的地方。
取址運算
字串字面量是一個可取址的對象。例如:&"hello" 是合法的運算式。
地址常量
靜態對象的地址在編譯期間即可被確定,所以其地址(如 &"hello")是常量;而字串字面量又可以從數群組類型自動轉換為指標(如 "hello" 轉換為指標後等同於 &"hello"[0]),所以字串字面量可以直接作為地址常量運算式來使用。
修改字串字面量的行為是無定義的
下面的操作都試圖修改字串字面量中的第一個字元從而改變字串字面量,所以其結果是無定義(Undefined)的:
”hello”[0] = ‘A’; /* Undefined */
char* p = “hello”; *p = ‘A’; /* Undefined */
使用了無定義行為的程式是錯誤的;避免在程式中出現無定義行為是程式員的責任。
區別點:
在類型限定上的不同
C 中的字串字面量 "hello" 是數群組類型 char[6](相應地,每個字元元素是無 const 限定的 char 型);作為右值使用的時候轉換為指標類型 char*。
在 C++ 中 "hello" 是 char const [6] 類型(相應地,每個字元元素的類型是 char const);轉換為指標使用的時候是 char const*、在特殊情況下也可以是 char*。
之所以在 C 中字串字面量不是 const 數組(也就是說每個字元元素的類型不是 char const),是因為 C 要照顧或者考慮到標準制定之前已經存在的大量代碼——那時的 C 語言還沒有 const 關鍵字,如果硬性規定為 const 數組,則 char* p = "hello"; 這樣的初始化或者 char* q; q = "hello"; 這樣的賦值就是非法的了(因為右邊的類型 char const* 不能預設轉換為左邊的類型 char* )。
同樣,為了使上述代碼能順利通過編譯過程,C++ 採取了另外一種策略:它規定了字串字面量的類型是 const 數組,同時又特別規定了字串字面量也可以有限制地轉換為指向非常量的指標(對於 "hello" 來說就是 char*),從而解決了上述代碼中存在的問題。不過,轉換到 char* 主要是為了相容以前的代碼,這種轉換被 C++ 標準標記為“Deprecated”,所以在寫程式的時候不應該依賴於這種轉換。
C++ 中的字串字面量是常量,而在 C 中不是常量。
正是由於標準在類型上的不同規定造成了在 C 和 C++ 中字串字面量常量性質上的差別。
在 C 中,除了 string literals 和 compound literals(C99 only)之外,其它的 literals 都是常量;而在 C++ 中,包括 string literals 在內的所有 literals 都是常量(注意:C++ 中不存在 compound literals。)
在現實中,經常可以看到用“字串常量”來指代“字串字面量”的情況,其實對於 C 來說這是不正確的,因為在 C 中字串字面量不屬於常量;而對於 C++ 來說,“字串常量”和“字串字面量”實際上是一回事,只不過看問題的角度不同罷了。
順便提一下:C++ 中的常量可以有對象常量(如字串字面量、const 限定的對象)和非對象常量之分,而 C 中的常量不包含對象,它們最明顯的特徵就是不能進行取址運算,因此常量只能作為非左值(即右值)來使用。
文法及語義上的區別
C 中的字串字面量不是常量,它的每個字元元素也不是常量,所以字元元素的不可變性僅僅表現在語義層面,但在文法和約束條件上沒有要求。而 C++ 中字串字面量是常量,每個字元元素也是常量,因此在語義和約束條件兩方面都要求不能改變其中的每個字元元素;另外,出於相容性考慮 C++ 還存在著特殊情況下的向非 const 指標的轉換。
下面用具體的代碼來對以上內容進行說明。
*"hello" = 'A';
運算式 *"hello" 代表字串字面量的第一個字元元素對象。上述語句試圖通過賦值操作改變第一個元素,當然這樣的行為在 C 和 C++ 中都是無定義的。除了這個相同點外,還有如下的一些細微的區別:
在 C++ 中,*"hello" 是一個 const 對象(其類型是 const char。注意:這裡的 "hello" 不會轉換為 char* 指標、從而 *"hello" 不會是 char 類型),所以上述賦值違反了賦值號左運算元必須是一個可被改變的左值的約束條件。在此情況下,標準要求給出診斷資訊。
在 C 中,*"hello" 是一個非 const 對象(其類型是 char),是一個可被改變的左值,所以不違背賦值的約束條件。在此情況下,儘管這個賦值操作是未定義的,標準對診斷資訊沒有要求。
char* p = "hello";
char* q; q = "hello";
void foo( char* s ); foo( "hello" );
上面的初始化和指派陳述式中 "hello" 都能轉換為 char* 指標類型,所以都是合法的。在 C++ 中,儘管 "hello" 作為指標使用時是 char const * 類型,在此情況下(如果不是 char* 類型則初始化或者賦值不能成立)基於對字串字面量的特殊規定使它可以轉換為 char * 使用。
要注意 C++ 中字串字面量轉換為指向非常量的指標是有限制的,僅僅在有明確的目標類型要求的情況下才能進行這樣的轉換,否則是非法的。比如下面的情況:
char* p = "hello" + 1;
char* q; q = "hello" + 1;
void foo( char* s ); foo( "hello" + 1 );
上述是合法的 C 代碼,但是作為 C++ 代碼是非法的。非法的原因在於:"hello" 轉換為 char const * 指標類型,而不能轉換為 char *,因為 + 運算子對其運算元的類型沒有轉換為 char* 這樣直接的要求(因為無論是 char const * 還是 char* 都能進行指標加法運算),所以指標加法運算式的結果仍然是 char const * 類型。這樣,上面指標的初始化或賦值操作就違反了在類型上的約束條件,需要給出診斷資訊。
(完)
轉自:http://bbs.chinaunix.net/viewthread.php?tid=936821&extra=&page=1