使用 C/C++ 多年的人對下面這個字符串賦值語句都不會陌生吧。
char* p = "test";
同時,我也相信,各位在使用這種語句後吃過很多苦頭也不少吧?只要你想利用指針 p 來改變字符串的內容,你的程序都會得到一個讓你顏面盡失一個內存非法操作。比如,下面的這些語句:
p[0] = 's';
strcpy(p, "haoel");
原因就在於, char* p = "test"; 這個聲明,聲明瞭一個指針,而這個指針指向的是全局的 const 內存區 , const 內存區當然不會讓你想改就改的。所以,如果你一定要寫這塊內存的話,那就是一個非常嚴重的內存錯誤。另,之所以加粗 “ 全局 const 內存區 ” ,是強調一下,如果你不信的話,你可以試試下面這段代碼,看看 p1 和 p2 的地址是不是一樣的。
char* p1 = "anything";
char* p2 = "anything";
printf(“ p1=%x, p2=%x /n”, p1, p2);
我想這應該是一個衆所周知的問題吧。取而代之的,應該是使用數組來做初始化聲明。如: char str[] = “hello world”; 如果現在還有哪本書中的 C 的示例採用了使用 const 字符串初始化指針的這種方式,那麼你就可以把那本書撕了,如果這本書是 C++ 的書話,那麼你應該把這個作者和這個出版社告上法庭,因爲你不應該容忍這種學術騙子。如果你的部門的開發人員還有人寫出這種代碼的話,如果他是 C 程序員,我想你可以在打過他的屁股後告訴他下不爲例,如果他是一個 C++ 程序員的話,我想你可以懷疑他是否有資格做一個 C++ 程序員了。
至於你問我爲什麼要對學 C++ 的人那麼苛刻,那是因爲學過 C++ 的人都知道 C++ 中的 const 關鍵字的有着什麼樣的權力,你也應該知道 C++ 對 const 有着無比的照顧和關愛,幾乎所有關於 C++ 的書都會提到 const 這東西。所以,如果作爲一個 C++ 的程序員來說,如果你不知道的話,那就太說不過去了。
我們知道,雙引號引起來的字符串是 const 的,所以,在 C++ 的世界中,你應該進行如下的聲明才比較穩妥:
const char *p = "test";
這樣,當你修改這個字符串的內容時,編譯器會給你一個錯誤而導致你的程序編譯不通過,從而不會產生運行時的內存錯誤。
可問題是,像 C++ 這種對類型要求很嚴格的語言來說,爲什麼它在編譯諸如 char *p="test" 程序的時候不出錯,甚至連個警告都沒有( g++ 和 vc++7 )?難道這是他的一個 bug ?我想,這應該是對古老的 C 的一個向下兼容。因爲,在 C 的世界中,這種用法太多了。
在 C++ 中,比如:函數的參數和異常的捕獲都存在這種問題,如下所示:(因編譯器而定,在 gcc 3.4.3 版中,下例中的異常示例不能被捕獲,但 VC++6 中卻可以被捕獲)
func( char* p) { } // 以這種方式調用函數 func(“abc”);
try { thow “exception”; } catch (char* p) { }
這些都是 C++ 編譯器默認了可以把 const char* 轉成 char* 的罪行,無疑會對大家是一個誤導。甚至讓人無所畏懼地走入其中,並自以爲走入了正途。這樣看來,這種向下兼容的 C++ 標準,就顯得有點誤人不淺了 。
不過好在, C++ 標準委員會早已意識到了這一點。這個 C++ 的 feature 被定義爲了 “Deprecated Feature” ,即 “ 不被建議使用的特性 ” 。意思就是,在將來,這種特性將被從 C++ 中移出,於是,你目前的這種程序將無法在新的 C++ 編譯器上編譯通過。對於程序的可移植性來說,我們今天所寫的代碼尤其要注意這些 “Deprecated Feature” 。
據我所知,目前 C++ 中被列爲 “Deprecated Feature” 如下所示(可能不準確,請大家指正)下面的這些 feature 都已被 C++ 標準委員會訂爲廢除 featrue 了。
一、 隱晦的字符串的 const 轉換。
char *p = "test";
w_char *pw = L"test";
把一個 const 的字符串類型轉成 non-const 的。包括指針和數組。
二、 隱晦的類型聲明。
func() {} // 函數的隱晦返回類型是 int
static num; // 變量的隱晦類型是 int
這種 feature 在 C89 中還可以使用,但在 C99 和 C++ 中都被去除了。( gcc 3.4 版本對於這種聲明會給出編譯錯誤,而 VC++6.0 會認爲這是合法的程序)
三、 布爾變量的累加操作。
bool isConn = false;
isConn++; // 這個操作會把 isConn 變爲 true
就目前而言,幾乎所有的編譯器都認可這種操作,但這種用法也是不被建議的,終有一天會被取消。
四、 更改父類成員的存取權限。
class B
{
protected:
int i;
};
class D : public B
{
public:
B::i; // 這種方式可能大家很少看到。
};
對於這種語法,子類重新暴露了父類的私有成員。這會帶來很大的安全性問題。目前而言,這個 feature 對於所有的編譯器來說應該都是可以編譯通過的(連個 Warning 都沒有)。但這個 feature 也是要被廢除的。
五、 文件中域的 static 聲明
static int i;
static void func()
據說,這種舊的在 C 中的爲了實現其作用域在本文件中的 feature 在未來的 C++ 中也要被取消。
文章到這裏應該結束了,在結束之前,讓我再給大家共享一個有趣的關於 const 的例子(在網上看到的)
const int a = 1;
int *p = const_cast<int*>(&a);
*p = 2;
cout << “value a=”<< a << endl;
cout << “value *p=” <<*p << endl;
cout << “address a=” <<&a << endl;
cout << “address p=” <<p << endl;
這段代碼輸出的結果如下:
value a=1
value *p=2
address a=0xbff1d48c
address p=0xbff1d48c
地址都是一樣的,可值爲什麼不一樣呢?呵呵。這個問題看起來有點 “ 學術味 ” 過濃,不過是個好例子,可以讓你知道 C++ 的一些用法和一些原理。有以下幾個方面大家可以考慮一下:
1 ) const int a = 1 是不是和宏有點像,會不會被編譯器優化了?
2 )去修改一個 const 的值,本來應該是不對的。這可能會是向舊的 C 兼容。是否會讓編譯器產生未知行爲?
所以,這個示例也告訴我們,我們應該遵循 C++ 中的 const 和 non-const 的語義,任何想要破壞這個語義的事情都會給我們帶來未知的結果。