Некоторое время назад кто-то с высокой репутацией на StackOverflow написал в комментарии, что необходимо разыграть char
Аргумент к unsigned char
перед звонком std::toupper
(и аналогичные функции).
С другой стороны, Бьярн Страуструп не упоминает о необходимости делать это на языке программирования C ++.
Он просто использует toupper
лайк
string name = "Niels Stroustrup";
void m3() {
string s = name.substr(6,10); // s = "Stroustr up"name.replace(0,5,"nicholas"); // name becomes "nicholas Stroustrup"name[0] = toupper(name[0]); // name becomes "Nicholas Stroustrup"}
(Цитируется из упомянутой книги, 4-е издание.)
Ссылка говорит, что вход должен быть представлен как unsigned char
,
Для меня это звучит так, как будто char
поскольку char
а также unsigned char
имеют одинаковый размер.
Так что этот актёрский состав ненужен или Страуструп был небрежным?
Изменить: руководство по libstdc ++ упоминает, что входной символ должен быть из основной исходный набор символов, но не бросает. Я предполагаю, что это покрыто ответом @Keith Thompson, все они имеют положительное представление как signed char
а также unsigned char
?
Да, аргумент toupper
должен быть преобразован в unsigned char
чтобы избежать риска неопределенного поведения.
Типы char
, signed char
, а также unsigned char
три разных типа. char
имеет тот же диапазон и представление, что и или signed char
или же unsigned char
, (Plain char
очень часто подписывается и может представлять значения в диапазоне -128 .. + 127.)
toupper
функция занимает int
аргумент и возвращает int
результат. Цитируя стандарт С, раздел 7.4, пункт 1:
Во всех случаях аргумент является
int
, значение которого должно быть
представима какunsigned char
или должен быть равен значению
макросEOF
. Если аргумент имеет любое другое значение,
поведение не определено.
(C ++ включает в себя большую часть стандартной библиотеки C и переносит ее определение в стандарт C.)
[]
оператор индексации на std::string
возвращает char
значение. Если обычный char
является типом со знаком, и если значение возвращается name[0]
бывает отрицательным, то выражение
toupper(name[0])
имеет неопределенное поведение.
Язык гарантирует, что, даже если простой char
подписан, все члены базового набора символов имеют неотрицательные значения, поэтому с учетом инициализации
string name = "Niels Stroustrup";
программа не рискует неопределенным поведением. Но да, в общем char
значение передано toupper
(или к любой из функций, объявленных в <cctype>
/ <ctype.h>
должен быть преобразован в unsigned char
, так что неявное преобразование в int
не приведет к отрицательному значению и приведет к неопределенному поведению.
<ctype.h>
Функции обычно реализуются с использованием справочной таблицы. Что-то вроде:
// assume plain char is signed
char c = -2;
c = toupper(c); // undefined behavior
может индексировать за пределами этой таблицы.
Обратите внимание, что преобразование в unsigned
:
char c = -2;
c = toupper((unsigned)c); // undefined behavior
не избегает проблемы. Если int
32 бита, преобразуя char
значение -2
в unsigned
доходность 4294967294
, Затем это неявно преобразуется в int
(тип параметра), который наверное доходность -2
,
toupper
Можно быть реализован таким образом, что он ведет себя разумно для отрицательных значений (принимая все значения из CHAR_MIN
в UCHAR_MAX
), но это не обязательно. Кроме того, функции в <ctype.h>
должны принять аргумент со значением EOF
, который обычно -1
,
Стандарт C ++ вносит коррективы в некоторые функции стандартной библиотеки C. Например, strchr
и некоторые другие функции заменены перегруженными версиями, которые обеспечивают const
правильность. Для функций, объявленных в <cctype>
,
В С, toupper
(и многие другие функции) взять int
даже если вы ожидаете, что они примут char
s. Дополнительно, char
подписан на некоторых платформах и не подписан на других.
Совет бросить на unsigned char
перед звонком toupper
правильно для C. Я не думаю, что это необходимо в C ++, если вы передадите его Я не могу найти ничего конкретного о том, нужно ли это в C ++.int
это в диапазоне
Если вы хотите обойти проблему, используйте toupper
определяется в <locale>
. Это шаблон, который принимает любой приемлемый тип символов. Вы также должны передать это std::locale
, Если вы не знаете, какой язык выбрать, используйте std::locale("")
, который должен быть предпочтительным языком пользователя:
#include <algorithm>
#include <iostream>
#include <iterator>
#include <locale>
#include <string>
int main()
{
std::string name("Bjarne Stroustrup");
std::string uppercase;
std::locale loc("");
std::transform(name.begin(), name.end(), std::back_inserter(uppercase),
[&loc](char c) { return std::toupper(c, loc); });
std::cout << name << '\n' << uppercase << '\n';
return 0;
}
Ссылка относится к значению, являющемуся представима как unsigned char
не к этому являющийся unsigned char
, То есть поведение не определено, если фактическое значение не находится между 0 и UCHAR_MAX
(обычно 255). (Или же EOF
что является в основном причиной, по которой int
вместо char
.)
К сожалению, Страуструп был неосторожен 🙁
И да, латинские буквенные коды должны быть неотрицательными (и приведение не требуется) …
Некоторые реализации корректно работают без приведения к unsigned char …
По определенному опыту, это может стоить нескольких часов, чтобы найти причину ошибки такого таппера (когда известно, что есть ошибка) …
И есть также isupper, islower и т. Д.
Вместо того, чтобы приводить аргумент как беззнаковый символ, вы можете привести функцию. Вам нужно будет включить функциональная заголовок. Вот пример кода:
#include <string>
#include <algorithm>
#include <functional>
#include <locale>
#include <iostream>
int main()
{
typedef unsigned char BYTE; // just in case
std::string name("Daniel Brühl"); // used this name for its non-ascii character!
std::transform(name.begin(), name.end(), name.begin(),
(std::function<int(BYTE)>)::toupper);
std::cout << "uppercase name: " << name << '\n';
return 0;
}
Выход:
uppercase name: DANIEL BRüHL
Как и ожидалось, toupper не влияет на символы не-ascii. Но этот кастинг полезен для избежания неожиданного поведения.