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

Но сама строка не отображается с этим line:

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().

Вот почему вы можете получить доступ к данным по отдельности (опять же, с УБ):

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

Чтобы изменить измененное .size() перед циклом for, правильно? Благодарим вас за ответ. – 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

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

Смежные вопросы