С++: изменить строку по индексу

Я новичок в С++, и в настоящее время я работаю со строками. Мой вопрос в том, почему при компиляции script, которую я предоставляю ниже, я могу получить строковые символы, когда я использую нотацию индекса, но не могу получить строку, используя 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</string></iostream>
<p>Когда я запускаю этот script, символы в строке "изменены" отображаются правильно с этой строкой:</p> <pre class="prettyprint linenums">cout &lt;&lt; altered[0] &lt;&lt; " " &lt;&lt; altered[1] &lt;&lt; " " &lt;&lt; altered[2] &lt;&lt; endl;</pre> <p>Но сама строка не отображается с этой строкой:</p> <pre class="prettyprint linenums">cout &lt;&lt; "altered : " &lt;&lt; altered &lt;&lt; endl;</pre> <p>Я хотел бы знать, почему это происходит.</p>
3 ответа

Вы не изменили размер вашей строки altered, чтобы соответствовать длине строки original перед циклом, поэтому ваш код демонстрирует поведение undefined:

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 не знает, что вы сделали с его базовым массивом, и что вы имели в виду, что строка должна увеличиваться в длину.

В заключение, способ сделать это преобразование:

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

(требуемые заголовки: , )


В вашей строке программы 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;


Размер altered всегда равен нулю - с помощью индексов, которые вы пытаетесь скопировать значения от original до altered, в индексах altered не существует. Как сказал LogicStuff, это поведение undefined - оно не генерирует ошибку, потому что, когда мы используем индексы с std::string, мы фактически вызываем оператор на std::string для доступа к полю data строки, Использование оператора [] определяется в стандарте С++ как без проверки диапазона - поэтому не было сделано никаких ошибок. Безопасный способ доступа к индексам - использовать метод at(i): altered.at(i) вместо этого будет использовать ошибку диапазона, если altered.size() <= i

Однако я собираюсь дать это как свое решение, потому что это подход "Современный С++" (плюс короче и полно).

Это альтернатива, которую я бы сделал с тем, что было дано выше:

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 страниц книги, которую нужно разобрать? Или что, если это сырой вклад в миллион символов? Затем каждый раз, когда поле data для altered достигает своего предела, его необходимо перераспределить с помощью системного вызова, а содержимое altered будет скопировано, а предварительное выделение для поля data будет освобождено. Это значительное препятствие производительности, которое растет относительно размера original - это просто плохая практика. Всегда было бы более эффективно выполнять полную копию, а затем выполнять итерацию, внося необходимые корректировки в скопированную строку. То же самое относится к std::vector.

licensed under cc by-sa 3.0 with attribution.