2015-11-22 91 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