设置带空格的字符串格式

Format string with spaces

本文关键字:字符串 格式 空格 设置      更新时间:2023-10-16

我正在尝试用C++制作一个优雅的日志记录系统。我目前正在使用printf(),尽管cout也可以是一个选项。

我想要实现的是像这个

console_log( "ClassName", "funcName", "Message." );

我目前的代码很简单:

static void console_log( const std::string & className, const std::string & funcName, const std::string & message ) {
    printf( "%s : %s : %sn",  className.c_str(), funcName.c_str(), message.c_str() );
}

它打印得很好,就像这个

// For example:
console_log( "MenuPanel", "selectedThisButton", "Something happened." );
console_log( "MenuPanel", "selectedAnotherButton", "Another thing happened." );
// Output:
MenuPanel : selectedThisButton : Something happened.
MenuPanel : selectedAnotherButton : Another thing happened.

然而,我希望它以类似表格的方式打印,其中所有"列"都正确对齐。例如:

MenuPanel : selectedThisButton    : Something happened.
MenuPanel : selectedAnotherButton : Another thing happened.

如何使第一列和第二列的宽度/字符数完全相同,并在必要时添加空格?它不需要是动态的。将"列"设置为16个字符即可。

不过,我不希望使用任何第三方libs来实现如此简单的功能。如果可能的话,没有boost

我会使用I/O流而不是C样式的printf,对于打印具有特定宽度的东西(任何类型),您可以包括<iomanip>并设置字段宽度:

some_stream << std::setw(MY_WIDTH) << whatever;

示例:

#include <iostream>
#include <iomanip>
int main()
{
    std::cout << std::setw(10) << "hi";
}

输出:

        hi

如果你采用"保持简单"的方式,你可以替换:

printf( "%s : %s : %sn", className.c_str(), funcName.c_str(), 
message.c_str() ); 

printf( "%30s : %30s : %30sn", className.c_str(), funcName.c_str(), 
message.c_str() );

无论您喜欢什么宽度,都可以查看printf文档中的对齐修饰符。

一般的解决方案相当复杂,但如果您可以假设固定宽度字体(其中每个字符具有相同的宽度),并且知道前面每列的宽度,类似于:

std::string rPad( std::string const& original, int minWidth )
{
    return original
        + std::string( 
            std::max( minWidth - static_cast<int>( original.size() ), 0 ),
            ' ' );
}
void console_log(
    std::string const& className,
    std::string const& funcName,
    std::string const& message )
{
    std::cout << rPad( className, classNameWidth )
        << " : " << rPad( funcName, funcNameWidth )
        << " : " << rPad
        << std::endl;
}

或者,你可以做一些类似的事情:

void console_log(
    std::string const& className,
    std::string const& funcName,
    std::string const& message )
{
    std::cout << alignedText( classNameWidth ) << className
        << " : " << alignedText( funcNameWidth ) << funcName
        << " : " << message << std::endl;
}

操纵器alignedText通常很有用无论如何都应该把它放在你的工具箱里:

class alignedText
{
    mutable std::ostream* myOwner;
    mutable std::ostream::fmtflags myFlags;
    int myWidth;
public:
    alignedText( int width ) : myOwner myWidth( width ) {}
    ~alignedText()
    {
        if ( myOwner != nullptr ) {
            myOwner->flags( myFlags );
        }
    }
    void set( std::ostream& dest ) const
    {
        if ( myOwner == nullptr ) {
            myOwner = &dest;
            myFlags = myOwner->flags();
        }
        dest.setf( std::ios_base::left, std::ios_base::adjustfield );
        dest.width( myWidth );
    }
    friend std::ostream& operator<<( std::ostream& dest, alignedText const& manip )
    {
        manip.set( dest );
        return dest;
    }
};

(虽然使用printf也可以做同样的事情,但结果可读性差得多,完全无法维护,当然,你有printf的一般脆弱性。)