通过索引更改字符串

5

我是一名C++初学者,目前正在学习字符串。

我的问题是,为什么在编译下面提供的代码时,使用索引表示法可以获取字符串的字符,但无法使用cout获取字符串本身?

代码如下:

#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;
}

当我运行此代码时,字符串altered中的字符将正确显示,使用以下代码行:
cout << altered[0] << " " << altered[1] << " " << altered[2] << endl;

但是这行代码本身不会显示字符串:

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

我想知道为什么会发生这种情况。


1
虽然下面已经有答案了,但是尝试找出如何在程序中启用诊断。你现在的代码确实有缺陷,但是使用C++编译器提供的诊断模式,这个错误可以很容易地被检测出来。 - Ulrich Eckhardt
请现在仔细阅读所有答案,标记最能回答您问题的答案。留一个问题不解决可不太好哦 :-) - Benjamin R
1
请注意,C++代码不是脚本,而是源代码。脚本是解释的,而C++源代码是编译的。 - PaperBirdMaster
3个回答

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没有输出任何内容,考虑到对于std::string简化operator<<定义在功能上看起来像这样:

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 不知道您对其基础数组所做的更改以及您希望字符串增长的长度。
总之,<algorithm> 的方式来进行这种转换:
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>

在for循环之前添加altered.resize(original.size());。 - Anton Todua
@LogicStuff 我删掉了我的反对意见,因为在这个小的上下文中它是完全合理的。我想补充一下答案,你使用的 += 是字符串连接,这可能不是 OP 熟悉的。你已经完美地描述了问题,解决方案也很好,只是没有解释为什么它可以解决你所发现的问题。如果你愿意,我可以帮你编辑一下。 - Benjamin R
1
我自己可以做,没问题。只是我更急于解释这种行为。 - LogicStuff
@AntonTodua,你可以这样做,但为什么要这样呢?将altered实际初始化为original的副本会更容易和清晰地阅读和理解。事实上,前两行可以改为:string original = "abc" string altered = abc,然后迭代altered,增加每个字符的ASCII值。这将是LogicStuff答案的直接替代方案。尽管如此,我仍然认为他在original的每个字符上连接变体的选择实际上比我的方法更好。 - Benjamin R
1
@dep0 抱歉,我忘记回复了,是的,两次都正确。 - LogicStuff

2
在你的程序中,字符串 altered 是空的。它没有任何元素。 因此,你不能像你现在这样使用下标操作符来访问不存在的字符串元素。
 altered[i] = original[i] + 5;

所以您可以添加新字符的字符串。有几种方法可以实现这一点。例如:
 altered.push_back( original[i] + 5 );

或者

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

或者

 altered += original[i] + 5;

由于不能对空字符串使用下标运算符来赋值,因此最好使用范围for循环,因为实际上并没有使用索引本身。例如:

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

@BenjaminR 没有必要使用 char 类型的引用。没有引用,代码甚至可以更加高效。 - Vlad from Moscow
@BenjaminR 你不理解编译器如何生成目标代码。 - Vlad from Moscow
1
@BenjaminR 请自行查看将生成什么目标代码。 - Vlad from Moscow

2
altered 的大小始终为零 - 通过使用索引,您试图将值从 original 复制到 alteredaltered 没有的索引处。正如 LogicStuff 所说,这是未定义行为 - 它不会生成错误,因为当我们使用索引与 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()和字符串附加/字符串连接的工作方式。在这个小例子中还好,但如果original是一个包含要解析的书的前10页的字符串呢?或者如果它是一个拥有一百万个字符的原始输入呢?那么每次altereddata字段达到其限制时,都需要通过系统调用重新分配,并复制altered的内容,释放data字段的先前分配。这是一个重要的性能障碍,相对于original的大小而言增长--这只是不好的做法。总是更有效的做法是进行完整的复制,然后迭代,在复制后的字符串上进行必要的调整。同样适用于std::vector


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接