Tekst centreren in veld met vaste breedte met stroommanipulatoren in C++

Ik ben een oude code aan het herstructureren die printfgebruikt met lange tekenreeksen (zonder enige opmaak) om tabelkoppen in platte tekst af te drukken die er theoretisch als volgt uitzien:

|  Table   |  Column  | Header  |

die momenteel als volgt worden geproduceerd:

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

Ik wil het bovenstaande produceren met code in de zin van1:

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

die niet compileert omdat <iomanip>de stream-manipulatoren std::left, std::righten std::internal, maar lijkt geen std::centerte hebben. Is er een schone manier om dit al in standaard C++-bibliotheken te doen, of moet ik handmatig de benodigde afstand berekenen?


1Hoewel dit uitgebreider is dan de C-code, zal het op de lange termijn minder uitgebreid zijn vanwege het aantal printf-instructies en de hoeveelheid infixed duplicatie in hun strings. Het zal ook beter uitbreidbaar en onderhoudbaar zijn.


Antwoord 1, autoriteit 100%

Hier is een helperklasse die bereikt wat je wilt:

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

Het wordt eenvoudig gebruikt door centered("String")aan te roepen, zoals:

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

Antwoord 2, Autoriteit 131%

In C++ 20 kun je std::formatom dit te doen:

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

Uitgang:

|  Table   |  Column  | Header  |

In de tussentijd kunt u de {FMT} -bibliotheek , std::formatis gebaseerd op. {FMT} biedt ook de print-functie die dit nog eenvoudiger en efficiënter maakt (godbolt ):

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

Disclaimer : Ik ben de auteur van {FMT} en C++ 20 std::format.


Antwoord 3, Autoriteit 46%

Er is geen std::centerManipulator. Ik ben bang dat je het zelf moet doen. Je zou een helperfunctie kunnen schrijven om de ruimtes te berekenen die de breedte en de tekenreeks krijgen, om de inspanningen te verminderen.

Hier is een voorbeeld van wat er een helperfunctie eruit zou kunnen zien. Het heeft wat werk nodig om het efficiënter te maken, enz.

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

Antwoord 4, Autoriteit 23%

Ik ben bang dat je het handmatig moet doen. Maar dat is dat niet
hard als je met snaren werkt. Zoiets als:

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

zou de truc moeten doen.


Antwoord 5

U kunt de NCURSES-bibliotheek gebruiken … het is een soort funk en u moet “-L ncurses” toevoegen aan uw compileeropdracht (G ++ -L ncurses yourprogram.cpp) en het zal werken plus u kunt kleuren en andere werken ‘Cool’ dingen [sowieso cool voor CLI].
Lees gewoon de handleiding, het centreren van spullen is vrij eenvoudig.
http://tldp.org/howto/ncurses-programming-howto/


Antwoord 6

Dit is alleen voor degenen die geen aanvullende wijzigingen in de code nodig hebben en geen nieuwe bibliotheken zoals ik bevatten.

Er zijn veel antwoorden hierboven gegeven die nieuwe helperklassen of extra bibliotheken vereisen, maar er is een eenvoudige hack in plaats daarvan die geen aanvullende wijzigingen vereisen.
U kunt de breedte met 2 verdelen en de SETW-methode aan weerszijden van het element toevoegen dat moet worden afgedrukt.
Als een oplossing voor de vraag:

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

De bovenstaande code doet het werk.

Uitgang:

|  Table   |  Column  | Header  |

Other episodes