Центрировать текст в поле фиксированной ширины с помощью потоковых манипуляторов в C++

Я рефакторинг некоторого устаревшего кода, который использует printf со строками long (без какого-либо фактического форматирования) для вывода заголовков таблицы в виде простого текста, которые условно выглядят так:

|  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::left, std::right а также std::internal, но, похоже, не имеет каких-либо std::center, Есть ли чистый способ сделать это уже в стандартных библиотеках C++, или мне придется вручную вычислять необходимый интервал?


1 Несмотря на то, что это более многословно, чем код C, в долгосрочной перспективе оно будет менее многословным из-за количества printf заявления и количество вставленных дубликатов в их строках. Это также будет более расширяемым и обслуживаемым.

6 ответов

Решение

Вот вспомогательный класс, который выполняет то, что вы хотите:

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

В C++20 вы сможете использовать сделать это:

      outputStream << std::format("|{:^10}|{:^10}|{:^9}|\n",
                            "Table", "Column", "Header");

Выход:

      |  Table   |  Column  | Header  |

А пока вы можете использовать библиотеку {fmt} , основанную на. {fmt} также предоставляет printфункция, которая делает это еще проще и эффективнее (godbolt):

      fmt::print("|{:^10}|{:^10}|{:^9}|\n", "Table", "Column", "Header");

Отказ от ответственности : я являюсь автором {fmt} и C++20 std::format.

Здесь нет 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, ' ');
}

Боюсь, вам придется сделать это вручную. Но это не так сложно, если вы работаете со строками. Что-то вроде:

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

должен сделать свое дело.

Это только для тех, кто не нуждается в дополнительных изменениях в коде и не включает в себя новые библиотеки вроде меня.

Выше приведено множество ответов, для которых требуются новые вспомогательные классы или дополнительные библиотеки, но вместо этого есть простой способ взлома, который не потребует каких-либо дополнительных изменений. Вы можете разделить ширину на 2 и добавить метод setw () по обе стороны от печатаемого элемента. Как решение вопроса:

      outputStream << "|" << std::setw(5) << "Table" << std::setw(5)
             << "|" << std::setw(5) << "Column" << std::setw(5)
             << "|" << std::setw(5) << "Header" << std::setw(5)
             << "|" << std::endl;

Приведенный выше код выполнит всю работу.

Выход:

      |  Table   |  Column  | Header  |

Вы могли бы использовать библиотеку NCURSES... это своего рода фанк, и вам нужно добавить "-L NCURSES" в вашу команду компиляции (g++ -L NCURSES yourProgram.cpp), и она будет работать, плюс вы можете делать цвета и другие "крутые" вещи [круто для CLI в любом случае]. просто прочитайте руководство, центрирование довольно просто. http://tldp.org/HOWTO/NCURSES-Programming-HOWTO/

Другие вопросы по тегам