Как написать свой манипулятор?
Предположим, я хочу написать свой собственный манипулятор для ввода и вывода.
cin >> mymanip >> str;
или же
cout << mymanip << str;
Я хочу, чтобы mymanip переключал регистры, которые я читал из ввода, и присваивал результат одной строке.
Итак, если я наберу "QwErTy", я получу "qWeRtY" в строке.
Это очень простая задача с одной функцией, но я хочу больше узнать о манипуляторах.
Может кто-нибудь дать подсказку?
Спасибо.
4 ответа
Это немного сложнее, но это можно сделать, вы можете добавить собственный манипулятор для потока.
Во-первых, вам нужен ваш переключатель:
class toggle_t {};
constexpr toggle_t toggle;
Далее - версия для ostream
(случай для istream
очень похоже...):
После сдачи toggle
в ostream
- вам нужен какой-то особый объект:
struct toggled_ostream
{
std::ostream& os;
};
inline toggled_ostream operator << (std::ostream& os, toggle_t)
{
return { os };
}
Осторожно, что кто-то может поставить toggle
в неправильном месте: cout << toggle << 123
- поэтому он должен работать для всех других типов как обычный поток:
template <typename T>
std::ostream& operator << (toggled_ostream tos, const T& v)
{
return tos.os << v;
}
Итак - для типов символов (например, char
, const char*
, std::string
) Напиши свои перегрузки переключения. Я даю вам версию для char
- не должно быть проблемой написать версию для "более длинных" типов:
std::ostream& operator << (toggled_ostream tos, char v)
{
char c = std::isupper(v) ? std::tolower(v)
: std::islower(v) ? std::toupper(v) : v;
return tos.os << c;
}
Рабочая демка.
Все, что делает манипулятор, это устанавливает соответствующие биты в std::ios_base
Базовый класс.
Например, std::setprecision()
Манипулятор просто вызывает std::ios_base::precision() в управляемом потоке.
Реализация std::setprecision()
почти читабелен в заголовках gcc (редкость для реализации шаблона библиотеки C++):
inline _Setprecision setprecision(int __n)
{ return { __n }; }
std::setprecision()
возвращает внутренний std::_Precision
объект. Затем простая перегрузка шаблона для >>
(и <<
оператор, который похож) для оператора std::_Precision
объект, обрабатывает остальную магию:
template<typename _CharT, typename _Traits>
inline basic_istream<_CharT, _Traits>&
operator>>(basic_istream<_CharT, _Traits>& __is, _Setprecision __f)
{
__is.precision(__f._M_n);
return __is;
}
В вашем случае, нет битов в std::ios_base
класс, который реализует желаемое преобразование ввода / вывода. Таким образом, манипулятор, по сути, не будет работать здесь.
То, что вы пытаетесь сделать, требует совершенно другого, более сложного подхода:
Пользовательский подкласс
std::[io]stream
, который использует пользовательский подклассstd::streambuf
,std::streambuf
Подкласс читает или записывает из цепочечного потока, преобразуя ввод или вывод, как вы описали.Чтение или запись из пользовательского подкласса заканчивает чтением или записью из цепочечного потока, соответственно преобразовывая данные.
Тебе этого не сделать. Вместо этого вы можете использовать манипуляторы, которые будут принимать строку в качестве аргумента, т.е.
std::cout << toggle(str);
std::cin >> toggle(str);
Манипулятор - это всегда так называемый синтаксический сахар, то есть он может делать вещи более удобно, чем иначе. Например,
std::cout << std::setw(5) << x <<;
будет делать так же, как
std::cout.width(5);
std::cout << x;
но удобнее, так как позволяет связываться вместе с другими <<
операции.
Теперь нет поддержки форматирования того, что вы хотите (поменяйте местами символы в нижнем и верхнем регистре), и, следовательно, также нет способа обеспечить синтаксический сахар для этого.
Однако, если манипулятор может принять вашу строку в качестве аргумента, тогда, конечно, вы можете достичь того, чего хотите, реализованного стандартным способом реализации манипулятора. Например,
struct toggle_output
{ std::string const&str; }
inline toggle_output toggle(std::string const&str)
{ return {str}; }
inline std::ostream& operator<<(std::ostream&out, toggle_output const&t)
{
for(auto c:t.str)
if (std::islower(c)) out<<std::toupper(c);
else if(std::isupper(c)) out<<std::tolower(c);
else out<<c;
return out;
}
struct toggle_input
{ std::string &str; }
inline toggle_input toggle(std::string&str)
{ return {str}; }
inline std::istream& operator>>(std::istream&in, toggle_input &t)
{
in >> t.str;
for(auto&c:t.str)
if (std::islower(c)) c=std::toupper(c);
else if(std::isupper(c)) c=std::tolower(c);
return in;
}
Вам также может понадобиться (чтобы избежать путаницы)
inline std::ostream& operator<<(std::ostream&out, toggle_input const&t)
{ return out<<toggle_output(t.str); }
Как объясняют другие ответы, манипуляторы просто подражают существующим std::ios_base
функциональность.
Есть простое решение вашей проблемы, хотя я не уверен, что это можно назвать манипулятором:
struct toggle_in_helper
{
std::string & res;
};
toggle_in_helper toggle (std::string & res)
{
return {res};
}
std::istream & operator >> (std::istream & in, toggle_in_helper h)
{
in >> h.res;
for (auto & c : h.res)
// toggle the case of 'c'
;
return in;
}
То есть мы создаем вспомогательный класс toggle_in_helper
с перегрузкой operator >>
которая делает работу.