2015-11-22 90 views
5

我是C++的初學者,我目前正在使用字符串。 我的問題是爲什麼編譯我在下面提供的腳本時,我可以得到字符串的字符,當我使用索引符號,但不能使用cout獲得字符串本身。 這是代碼:C++:通過索引更改字符串

#include <iostream> 
#include <string> 

using namespace std; 

int main() 
{ 
    string original; // original message 
    string altered; // message with letter-shift 

    original = "abc"; 
    cout << "Original : " << original << endl; // display the original message 

    for(int i = 0; i<original.size(); i++) 
     altered[i] = original[i] + 5; 

    // display altered message 
    cout << altered[0] << " " << altered[1] << " " << altered[2] << endl; 
    cout << "altered : " << altered << endl; 

    return 0; 
} 

當我運行此腳本,「改變」的字符串中的字符都正確地與這條線顯示:

cout << altered[0] << " " << altered[1] << " " << altered[2] << endl; 

但是字符串本身是不是跟這個顯示行:

cout << "altered : " << altered << endl; 

我想知道爲什麼會發生這種情況。

+1

雖然您已經在下面找到了答案,但請嘗試瞭解如何在程序中啓用診斷。您擁有的代碼實際上是錯誤的,但使用C++編譯器提供的診斷模式,可以輕鬆檢測到該錯誤。 –

+0

請現在回答所有答案,並標出最能解答您問題的答案。打開問題並不好:-) –

+0

請注意,C++代碼不是[* scripts *](https://en.wikipedia.org/wiki/Scripting_language),而是*源代碼*。腳本被解釋並且C++源代碼是[編譯](https://en.wikipedia.org/wiki/Compiled_language)。 –

回答

5

沒有重新調整你的altered串循環之前,以適應original字符串的長度,這樣你的代碼具有未定義行爲

altered[i] = original[i] + 5; // UB - altered is empty 

爲了解決這個問題,在循環之前調整altered

altered.resize(original.size()); 

或者使用std::string::operator+=或類似附加到altered

altered += original[i] + 5; 

這樣,它可以在循環之前清空,它會自動調整其自身以包含附加字符。


說明

UB這裏發生的一切的辦法,就是你在靜態數組,這std::string採用短串優化寫入數據成功(std::string::operator[]做任何檢查,如果你是訪問該陣列通過std::string::size()),但std::string::size()仍然0,以及std::string::begin() == std::string::end()

這就是爲什麼你可以單獨訪問數據(再次,與UB):

cout << altered[0] << " " << altered[1] << " " << altered[2] << endl; 

cout << aligned沒有打印任何東西,考慮簡化operator<<定義std::string看起來功能這樣的:

std::ostream &operator<<(std::ostream &os, std::string const& str) 
{ 
    for(auto it = str.begin(); it != str.end(); ++it) // this loop does not run 
     os << *it; 

    return os; 
} 

用一句話,std::string不知道你對底層數組做了什麼,並且你的意思是字符串長度增長。


最後,<algoritm>方式做這個轉型:

std::transform(original.begin(), original.end(), 
    std::back_inserter(altered), // or altered.begin() if altered was resized to original's length 
    [](char c) 
    { 
     return c + 5; 
    } 

(必要標題:<algorithm><iterator>

+1

非常感謝。所以通過做你所建議的我將每個字符添加到字符串的末尾,對吧? – theodor

+0

或者你可以添加altered.resize(original.size());之前。 –

+0

爲了在for循環之前更改changed.size(),對嗎? 謝謝你的回答。 – theodor

2

在程序中的字符串altered是空的。它沒有元素。 因此,你可以不使用下標運算符來訪問字符串的不存在的元素,你正在做的

altered[i] = original[i] + 5; 

所以,你可以追加新字符的字符串。有幾種方法可以做到這一點。例如

altered.push_back(original[i] + 5); 

altered.append(1, original[i] + 5); 

altered += original[i] + 5; 

正如你可能不適用下標運算符空字符串分配一個值,那麼最好是使用範圍 - 基於循環,因爲索引本身實際上不被使用。例如

for (char c : original) altered += c + 5; 
+0

@BenjaminR沒有任何需要使用char類型的引用。沒有參考代碼可以更有效。 –

+0

@BenjaminR你不明白編譯器如何生成目標代碼。 –

+0

@BenjaminR看看自己會生成哪些目標代碼。 –

1

altered的大小始終爲零 - 用您試圖在指標從original複製值altered指標altered。正如LogicStuff所說,這是未定義的行爲 - 它不會產生錯誤,因爲當我們使用std::string索引時,實際上我們調用std::string上的運算符來訪問字符串的data字段。使用[]運算符在C++標準中被定義爲具有無範圍檢查 - 這就是爲什麼不拋出錯誤的原因。訪問索引安全方式是使用at(i)方法:altered.at(i)反而會拋出一個範圍錯誤如果altered.size() <= i

不過,我會把這作爲我的解決方案,因爲它是一個「現代C++」的方法(加上更短和完整)。

這是另類我會做什麼上面已經給出:

string original = "abc"; 
string altered = original; 
for (auto& c : altered) c += 5; // ranged for-loop - for each element in original, increase its value by 5 
cout << altered << endl; 

注意在代碼中顯著減少:-)

即使我做LogicStuff的方式,我仍然會像這樣做:

string original = "abc" 
string altered = ""; // this is actually what an empty string should be initialised to. 
for (auto& c : original) altered += (c+5); 

不過,我其實不推薦這種方式,因爲方式push_back()和字符串添加/串concatenatio工作。在這個小例子中,這很好,但是如果original是一個持有要解析書籍的前10頁的字符串呢?或者如果它是一百萬字符的原始輸入呢?然後每當altereddata字段達到其限制時,都需要通過系統調用重新分配,並複製altered的內容,並且釋放data字段的先前分配。這是一個重大的性能障礙,相對於original的大小而言增長 - 這只是不好的做法。做一個完整的副本然後迭代總是更高效,對複製的字符串進行必要的調整。這同樣適用於std::vector