按索引更改字符串

Change string by index

本文关键字:字符串 索引      更新时间:2023-10-16

我是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;

我想知道为什么会这样。

您尚未调整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> (

在程序字符串中,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;

altered 的大小始终为零 - 通过使用索引,您尝试将值从original复制到altered altered没有索引。正如 LogicStuff 所说,这是未定义的行为 - 它不会生成错误,因为当我们使用带有 std::string 的索引时,我们实际上是在std::string上调用运算符来访问字符串的data字段。使用[]运算符在C++标准中定义为没有范围检查 - 这就是没有抛出错误的原因。访问索引的安全方法是使用 at(i) 方法:如果altered.size() <= ialtered.at(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 .