C++ tolower/toupper указатель символа

Ребята, вы знаете, почему следующий код дает сбой во время выполнения?

char* word;
word = new char[20];
word = "HeLlo"; 
for (auto it = word; it != NULL; it++){        
    *it = (char) tolower(*it);

Я пытаюсь перевести char* (строку) в нижний регистр. Я использую визуальную студию.

Спасибо


person mask    schedule 23.11.2015    source источник
comment
new это C++, а не C.   -  person Barmar    schedule 24.11.2015
comment
вы используете auto в то же время, используя char*.. ищите std::string, и у вас будет меньше времени на отладку и больше времени для удовольствия   -  person Humam Helfawi    schedule 24.11.2015
comment
Вы перезаписали распределение word = new char[20]; на word = "HeLlo";? Если это так, вы не сможете delete word позже.   -  person Weather Vane    schedule 24.11.2015
comment
@HumamHelfawi Спасибо! просто хотел использовать char* вместо строки. Даже когда я использую char* it, все равно получаю ошибку времени выполнения. @Погода да.   -  person mask    schedule 24.11.2015
comment
Вы также забыли освободить выделенную память, что является очень распространенной и серьезной ошибкой.   -  person Marian Spanik    schedule 24.11.2015
comment
Вы (повторно) назначаете указатель на память, чтобы указать на константный литерал. Выражение word = "Hello"; назначает указатель и не копирует содержимое. Вместо этого используйте std::string.   -  person Thomas Matthews    schedule 24.11.2015
comment
Если вы хотите использовать указатель (почему?) вместо класса string, вам нужно понимать семантику указателей.   -  person curiousguy    schedule 24.11.2015


Ответы (6)


Вы не можете сравнивать it с NULL. Вместо этого вы должны сравнивать *it с '\0'. Или еще лучше, используйте std::string и никогда не беспокойтесь об этом :-)

Таким образом, при переборе строки в стиле C. Вы должны зацикливаться до тех пор, пока символ, который вы видите, не станет '\0'. Сам итератор никогда не будет NULL, так как он просто указывает место в строке. Тот факт, что итератор имеет тип, который можно сравнить с NULL, является деталью реализации, которую не следует трогать напрямую.

Кроме того, вы пытаетесь записать строковый литерал. А вот нет-нет :-).

EDIT: как отметили @Cheers и hth. - Альф, tolower может сломаться, если заданы отрицательные значения. К сожалению, нам нужно добавить приведение, чтобы убедиться, что это не сломается, если вы подадите ему данные в кодировке Latin-1 или что-то подобное.

Это должно работать:

char word[] = "HeLlo";
for (auto it = word; *it != '\0'; ++it) {
    *it = tolower(static_cast<unsigned char>(*it));
}
person Evan Teran    schedule 23.11.2015
comment
Лучше исправить вызов tolower, так как предположительно этот пример предназначен для демонстрации в целом корректного кода - person Cheers and hth. - Alf; 24.11.2015
comment
@Cheersandhth.-Альф, справедливо. Я добавлю кое-что для этого :-) - person Evan Teran; 24.11.2015

Вы устанавливаете word так, чтобы он указывал на строковый литерал, но литералы доступны только для чтения, поэтому это приводит к неопределенному поведению при назначении *it. Вам нужно сделать его копию в динамически выделенной памяти.

char *word = new char[20];
strcpy(word, "HeLlo");

Также в вашем цикле вы должны сравнить *it != '\0'. Конец строки обозначается символом, являющимся нулевым байтом, а не нулевым указателем.

person Barmar    schedule 23.11.2015

Данный код (пока я пишу это):

char* word;
word = new char[20];
word = "HeLlo"; 
for (auto it = word; it != NULL; it++){        
    *it = (char) tolower(*it);

Этот код имеет Undefined Behavior двумя различными способами, а UB также имел бы третий способ, если бы только текстовые данные немного отличались:

  • Переполнение буфера.
    Условие продолжения it != NULL не будет false до тех пор, пока указатель it не завершится в конце диапазона адресов, если это произойдет.

  • Изменение памяти, доступной только для чтения.
    Указатель word устанавливается так, чтобы указывать на первый char строкового литерала, а затем цикл перебирает эту строку и присваивает каждому char.

  • Передача возможного отрицательного значения в tolower.
    Функции классификации char требуют неотрицательного аргумента или специального значения EOF. Это прекрасно работает со строкой "HeLlo" в предположении ASCII или беззнакового типа char. Но вообще, т.е. со строкой "Blåbærsyltetøy" прямая передача каждого значения char в tolower приведет к передаче отрицательных значений; правильным вызовом с ch типа char является (char) tolower( (unsigned char)ch ).

Кроме того, в коде есть утечка памяти, когда часть памяти выделяется с помощью new, а затем просто забывается об этом.

Правильный способ кодирования очевидного намерения:

using Byte = unsigned char;

auto to_lower( char const c )
    -> char
{ return Byte( tolower( Byte( c ) ) ); }

// ...
string word = "Hello";
for( char& ch : word ) { ch = to_lower( ch ); }
person Cheers and hth. - Alf    schedule 23.11.2015

Уже есть два хороших ответа о том, как решить ваши проблемы, используя c-строки с нулевым завершением и указатели. Для полноты я предлагаю вам подход с использованием строк С++:

string word;           // instead of char* 
//word = new char[20]; // no longuer needed: strings take care for themseves
word = "HeLlo";        //  no worry about deallocating previous values: strings take care for themselves
for (auto &it : word)  // use of range for, to iterate through all the string elements      
    it = (char) tolower(it);
person Christophe    schedule 23.11.2015
comment
Лучше исправьте вызов tolower, так как предположительно этот пример предназначен для демонстрации в целом корректного кода. Также я бы поправил название. it не является итератором. - person Cheers and hth. - Alf; 24.11.2015
comment
@Cheersandhth.-Альф, хорошо, в более общем случае я бы выбрал it = (char)tolower(it, loc);, где loc - это локаль. - person Christophe; 24.11.2015
comment
Я бы не рекомендовал это. Это просто добавляет многословия, неэффективности и сложности (для обеспечения доступности локали). Напротив, функция C проста, достаточно эффективна и учитывает локаль. - person Cheers and hth. - Alf; 24.11.2015
comment
Если вы пишете нелокализованное англоязычное программное обеспечение, вы, безусловно, правы. Но для европейских кодировок отображение верхнего‹-›нижнего не совсем то же самое для ascii (не определено) iso8859 (например: 0xE4 ä 0xD4 Ä) и устаревшего iso646 (0x84 ä 0x8E Ä). Здесь накладные расходы локали на самом деле являются реальным преимуществом. - person Christophe; 24.11.2015
comment
Функция C учитывает локаль. Он использует локаль уровня C, установленную с помощью setlocale. Я не говорил об общих накладных расходах на локали, я говорил о накладных расходах на использование локалей C++. В любом случае обратите внимание, что в этом контексте не имеет смысла проводить различие между Latin-1 (ISO 8859) и Unicode (ISO 646). Latin-1 — это строгое подмножество Unicode, это первые 256 кодовых точек. - person Cheers and hth. - Alf; 24.11.2015

Его сбой, потому что вы изменяете строковый литерал.

person SEB    schedule 23.11.2015

для этого есть специальные функции: strupr для перевода строки в верхний регистр и strlwr для перевода строки в нижний регистр.

вот пример использования:

char str[ ] = "make me upper";
printf("%s\n",strupr(str));


char str[ ] = "make me lower";
printf("%s\n",strlwr (str));
person YOKO    schedule 03.11.2017