问题 使用C ++中的流操作符在固定宽度字段中居中文本


我正在重构一些正在使用的遗留代码 printf 使用longs字符串(没有任何实际格式)来打印出明显文本表标题,这些标题看起来像这样:

|  Table   |  Column  | Header  |

目前正在制作如下:

printf("|  Table   |  Column  | Header  |");

我想用上面的代码生成上面的效果1

outputStream << "|" << std::setw(10) << std::center << "Table"
             << "|" << std::setw(10) << std::center << "Column"
             << "|" << std::setw(9) << std::center << "Header"
             << "|" << std::endl;

因为没有编译 <iomanip> 有流操纵者 std::leftstd::right 和 std::internal,但似乎没有 std::center。在标准C ++库中是否有一种干净的方法可以做到这一点,还是我必须手动计算必要的间距?


1尽管这比C代码更冗长,但从长远来看,由于数量较多,它的冗长程度会更低 printf 语句和字符串中的不加重复的数量。它也将更具可扩展性和可维护性。


8020
2018-02-13 19:02


起源



答案:


这是一个完成你想要的帮助类:

#include <string>
#include <iostream>
#include <iomanip>

template<typename charT, typename traits = std::char_traits<charT> >
class center_helper {
    std::basic_string<charT, traits> str_;
public:
    center_helper(std::basic_string<charT, traits> str) : str_(str) {}
    template<typename a, typename b>
    friend std::basic_ostream<a, b>& operator<<(std::basic_ostream<a, b>& s, const center_helper<a, b>& c);
};

template<typename charT, typename traits = std::char_traits<charT> >
center_helper<charT, traits> centered(std::basic_string<charT, traits> str) {
    return center_helper<charT, traits>(str);
}

// redeclare for std::string directly so we can support anything that implicitly converts to std::string
center_helper<std::string::value_type, std::string::traits_type> centered(const std::string& str) {
    return center_helper<std::string::value_type, std::string::traits_type>(str);
}

template<typename charT, typename traits>
std::basic_ostream<charT, traits>& operator<<(std::basic_ostream<charT, traits>& s, const center_helper<charT, traits>& c) {
    std::streamsize w = s.width();
    if (w > c.str_.length()) {
        std::streamsize left = (w + c.str_.length()) / 2;
        s.width(left);
        s << c.str_;
        s.width(w - left);
        s << "";
    } else {
        s << c.str_;
    }
    return s;
}

它只是通过调用使用 centered("String"),像这样:

int main(int argc, char *argv[]) {
    std::cout << "|" << std::setw(10) << centered("Table")
              << "|" << std::setw(10) << centered("Column")
              << "|" << std::setw(9)  << centered("Header") << "|"
              << std::endl;
}

8
2018-02-13 19:18



啊!!我喜欢。非常聪明。 - Keith Pinson
这仅适用于C ++ 0x吗?在MSVS 2010中,我得到“默认模板参数只允许在类模板上”。根据 这个答案 这已在C ++ 0x中修复。 - Macbeth's Enigma
@jep:奇怪。它在Clang中完美运行。在第24行,尝试删除默认模板参数,所以它只是 template<typename charT, typename traits>。那样有用吗? - Kevin Ballard
@jep:如果你改变那条线说什么会发生什么 return center_helper<std::string::value_type, std::string::traits_type>(str);? - Kevin Ballard
另外,我刚刚编辑了我的答案,删除了第24行的默认模板参数。它实际上并没有做任何事情,我不确定为什么我在那里首先使用它,可能只是因为我将模板参数与 center_helper 类。 - Kevin Ballard


答案:


这是一个完成你想要的帮助类:

#include <string>
#include <iostream>
#include <iomanip>

template<typename charT, typename traits = std::char_traits<charT> >
class center_helper {
    std::basic_string<charT, traits> str_;
public:
    center_helper(std::basic_string<charT, traits> str) : str_(str) {}
    template<typename a, typename b>
    friend std::basic_ostream<a, b>& operator<<(std::basic_ostream<a, b>& s, const center_helper<a, b>& c);
};

template<typename charT, typename traits = std::char_traits<charT> >
center_helper<charT, traits> centered(std::basic_string<charT, traits> str) {
    return center_helper<charT, traits>(str);
}

// redeclare for std::string directly so we can support anything that implicitly converts to std::string
center_helper<std::string::value_type, std::string::traits_type> centered(const std::string& str) {
    return center_helper<std::string::value_type, std::string::traits_type>(str);
}

template<typename charT, typename traits>
std::basic_ostream<charT, traits>& operator<<(std::basic_ostream<charT, traits>& s, const center_helper<charT, traits>& c) {
    std::streamsize w = s.width();
    if (w > c.str_.length()) {
        std::streamsize left = (w + c.str_.length()) / 2;
        s.width(left);
        s << c.str_;
        s.width(w - left);
        s << "";
    } else {
        s << c.str_;
    }
    return s;
}

它只是通过调用使用 centered("String"),像这样:

int main(int argc, char *argv[]) {
    std::cout << "|" << std::setw(10) << centered("Table")
              << "|" << std::setw(10) << centered("Column")
              << "|" << std::setw(9)  << centered("Header") << "|"
              << std::endl;
}

8
2018-02-13 19:18



啊!!我喜欢。非常聪明。 - Keith Pinson
这仅适用于C ++ 0x吗?在MSVS 2010中,我得到“默认模板参数只允许在类模板上”。根据 这个答案 这已在C ++ 0x中修复。 - Macbeth's Enigma
@jep:奇怪。它在Clang中完美运行。在第24行,尝试删除默认模板参数,所以它只是 template<typename charT, typename traits>。那样有用吗? - Kevin Ballard
@jep:如果你改变那条线说什么会发生什么 return center_helper<std::string::value_type, std::string::traits_type>(str);? - Kevin Ballard
另外,我刚刚编辑了我的答案,删除了第24行的默认模板参数。它实际上并没有做任何事情,我不确定为什么我在那里首先使用它,可能只是因为我将模板参数与 center_helper 类。 - Kevin Ballard


没有 std::center 机械手。我担心你必须自己做。您可以编写辅助函数来计算给定宽度和字符串的空间,以减少工作量。

这是辅助函数的样子。它需要一些工作才能提高效率等。

string helper(int width, const string& str) {
    int len = str.length();
    if(width < len) { return str; }

    int diff = width - len;
    int pad1 = diff/2;
    int pad2 = diff - pad1;
    return string(pad1, ' ') + str + string(pad2, ' ');
}

4
2018-02-13 19:07





我担心你必须手动完成。但事实并非如此 如果你使用字符串很难。就像是:

std::string
centered( std::string const& original, int targetSize )
{
    assert( targetSize >= 0 );
    int padding = targetSize - checked_cast<int>( original.size() );
    return padding > 0
        ? std::string( padding / 2, ' ' ) 
            + original
            + std::string( targetSize - (padding / 2), ' ' )
        : original;
}

应该做的伎俩。


3
2018-02-13 19:10





你可以使用NCURSES库......它有点吓人,你必须在你的编译命令中添加“-L NCURSES”(g ++ -L NCURSES yourProgram.cpp)它会工作加上你可以做颜色和其他'酷'东西[无论如何都很酷]。 只需阅读手册,中心内容非常简单。 http://tldp.org/HOWTO/NCURSES-Programming-HOWTO/


-1
2018-04-22 00:16



抱歉...... -lncurses是旗帜。我多年没用过ncurses了 - jEXEy