如何编写自己的操纵器?

时间:2016-11-27 22:14:52

标签: c++ manipulators

假设我想编写自己的输入和输出操纵器。

cin >> mymanip >> str;

cout << mymanip << str;

我希望mymanip做的是切换案例我从输入中读取的字符并将结果分配给一个字符串。

所以,如果我输入&#34; QwErTy&#34;我得到了&#34; qWeRtY&#34;在字符串中。

这是一个非常基本的任务,只有一个功能,但我想了解更多关于操纵器的知识。

有人可以提供线索吗?

谢谢。

4 个答案:

答案 0 :(得分:6)

操纵器所做的就是设置std::ios_base基类中的相应位。

例如,操纵流上的std::setprecision()操纵器只是invokes 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类中没有用于实现所需输入/输出转换的位。因此,操纵者本身在这里不起作用。

您要做的事情需要采用完全不同,更复杂的方法:

  1. std::[io]stream的自定义子类,它使用std::streambuf的自定义子类。

  2. std::streambuf子类从链接流中读取或写入,按照您的描述转换输入或输出。

  3. 从自定义子类读取或写入最终会从链接流中读取或写入,从而相应地转换数据。

答案 1 :(得分:3)

你做不到。你可以做的是将字符串作为参数的操纵者,即

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); }

答案 2 :(得分:3)

方式有点棘手 - 但可以做到,你可以为流添加自己的操纵器。

首先,您需要切换:

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;
}

所以 - 对于字符类型(例如charconst 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;
}

工作demo

答案 3 :(得分:1)

正如其他答案所解释的那样,操纵者只是模仿现有的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 >>来完成这项工作。