2014-04-04 47 views
3

我感到困惑下面的代碼中的數據:返回爲const char *爲char *,然後改變

string _str = "SDFDFSD"; 
char* pStr = (char*)_str.data(); 
for (int i = 0; i < iSize; i++) 
    pStr[i] = ::tolower(pStr[i]); 

這裏_str.data()回報const char*。但我們將其分配給char*。我的問題是,

_str.data()正在返回指向常量數據的指針。如何將它存儲在指向數據的指針中?數據一直是正確的?如果我們將它指定給char指針,而不是像我們在for語句中所做的那樣改變它,這對於常量數據來說應該是不可能的。

回答

3

您在做什麼在標準庫級別無效(您違反了std::string contract),但在C++核心語言級別有效。

data返回的char *不應該寫入,因爲例如它可能在理論上(*)在具有相同值的不同字符串之間共享。

如果你想修改一個字符串,只需使用std::string::operator[],它會通知對象這個意圖,並且在該字符串最初被共享的情況下,將負責爲特定實例創建一個私有緩衝區。

從技術上講,你可以從指針或引用丟棄const,但是如果它是一個有效的操作,則取決於特定情況的語義。允許操作的原因是C++的主要哲學是程序員不會犯錯誤並且知道他們在做什麼。例如,從C++語言的角度來看,在技術上是合法的做memcpy(&x, "hello", 5)其中x是一個類實例,但結果很可能是「未定義的行爲」。

如果您認爲自己的代碼「有效」,那是因爲您對「作品」的真正含義應該有錯誤的理解(提示:「作品」並不意味着某人曾經觀察過代碼,但是在任何情況下都可以)。如果你運行該程序,一個有效的C++實現可以自由地做任何事情:你觀察到你認爲沒問題的東西並不意味着任何事情,可能你看起來不夠接近,或者你可能是幸運的實際上)不會發生事故。 (*)在現代,std :: string的COW(copy-on-write)實現的流行度很低,因爲它們帶來了很多問題(例如使用多線程)並且內存現在便宜很多。仍然std::string合約表示您不允許更改返回值data()指向的內存;如果你做任何事情可能發生。

+0

單獨第一段+1。我會將其添加到我的曲目中。 –

+0

+1不錯的答案。應該注意的是,如果被引用或指向的*原始對象*本身*非const,那麼在C++中簡要提到的const-casting是唯一可行的。即聲明一個'Object s',將它傳遞給一個函數,該函數採用'const Object&',該函數知道原始對象是非const的,可以對該引用進行const轉換並堅果。如果它聲明爲「const Object s;'它將成爲UB來在函數中強制轉換引用。不能保證'std :: string :: data()'和/或'std :: string :: c_str()'的結果最初是非const-ilk的指針。因此,*壞主意*。 – WhozCraig

5

不要這樣做。這可能是罰款,這種情況下,但作爲data()的文件說:

返回可以通過進一步呼籲該修改的對象等 成員函數是無效的指針。

程序不得更改此序列中的任何字符。

因此,如果將指針放在指針周圍,您可能會非常意外地寫入無效內存。或者,實際上,破壞了std :: string的實現。我幾乎可以說,這個功能不應該暴露。

std :: string爲此提供了一個非const operator[]

string _str = "SDFDFSD"; 
for (int i = 0; i < iSize; i++) 
    _str[i] = ::tolower(_str[i]); 
+0

我在做什麼是有效的。但我想知道爲什麼它是有效的?對我來說這似乎是錯誤的。謝謝。 – Tahlil

+0

@Tahlil:你所做的是無效的。在C++中,成功的編譯並不能保證有效;該標準充滿了未定義的行爲。 –

+0

這是無效的;這是未定義的行爲,它可以在任何時候停止工作(包括簡單和默默地放棄對字符串的更改,設置你的房子着火等) – Massa

0

字符串總是在堆上分配內存,所以這實際上不是const數據,它只是標記爲(在方法data()簽名中)以防止修改。

但是在C++中沒有什麼是不可能的,所以通過簡單的轉換,雖然不安全,但現在可以將可修改的內存空間對待。

+0

更正:「always」意味着默認字符串在這裏,你當然可以編寫自己的分配器。 – berkus

+0

它不需要在堆上分配。它也可以分配[例如短]字符串或硬盤上的大字符串。 –

+0

@ phresnel它實際上並沒有在所有情況下分配,所以有可能會出現這種情況。雖然n3290在效果部分提到,對於所有這些情況,「指向數組的第一個元素的第一個元素**,其第一個元素由s指向」 – berkus

-2

C/C++程序中的所有常量(如下面的"SDFDFSD")將存儲在單獨的區段.rodata中。在執行期間將二進制文件加載到內存中時,此部分映射爲只讀。

int main() 
{ 
    char* ptr = "SDFDFSD"; 
    ptr[0]='x'; //segmentation fault!! 
    return 0; 
} 

因此任何試圖在該位置來修改數據將導致運行時錯誤即段故障


即將對上述問題,創造一個串並分配一個字符串到它,一個new copy in memory now exists(存儲器用於保存字符串對象_str的屬性)時。這是在堆上,而不是映射到只讀部分。成員函數_str.data()指向映射爲讀/寫的內存中的位置。

const預選賽到返回類型的保證,此功能是不小心傳遞給期望一個非const char*指針字符串處理函數。

在您當前的迭代中,對於保存字符串對象數據的內存位置本身沒有限制;即它被映射爲具有讀/寫許可。因此,使用另一個非const指針修改位置,即在作業的左側工作,即pStr[i]不會導致運行時錯誤,因爲對存儲器位置本身沒有固有的限制。

再次這是不是保證工作,只是你觀察到的實現特定行爲(即它只是適合你),並不總是依賴於此。

+0

然後,代碼如何通過改變常量數據首先分配一個指向非常量數據的指針,然後改變它?該任務應該是錯誤的嗎? – Tahlil

+0

這是不正確的。編譯器當然可以這麼做,但它不是標準的要求。另外,臨時的'std :: string'返回的常量可能不是一個真正的常量。 –

+0

「構造字符串,其內容初始化爲**,並帶有由s **指向的以空字符結尾的字符串的副本。字符串的長度由第一個空字符確定。如果s不指向在CharT的至少Traits :: length(s)+1元素的數組中。「 – berkus

2

您絕對不可以直接更改從std::string::data()std::string::c_str()返回的數據。在一個字符串

std::string str1 = "test"; 
std::string str2 = str1; // copy. 

更改字符:

要創建std::string副本

std::string str1 = "test" 
str1[0] = 'T'; 
+0

+1:但是讓我編輯你的第一個短語 –

1

「正確」 的方法是使用std::transform代替:

std::transform(_str.begin(), _str.end(), _str.begin(), ::tolower); 
+0

但是我想知道爲什麼將'指向常量數據'指向數據指針是合法的嗎? – Tahlil

+1

@Tahlil不是。Don這很可能會導致未定義的行爲 –

+1

@Tahlil:我已經在幾分鐘前告訴過你,編譯成功並不意味着有效,你爲什麼不理會我的建議? - 編輯:我知道你只是現在太忙了:D –