Formatting C++ console output

2019-01-24 11:36发布

问题:

I've been trying to format the output to the console for the longest time and nothing is really happening. I've been trying to use as much of iomanip as I can and the ofstream& out functions.

void list::displayByName(ostream& out) const
{
    node *current_node  = headByName;

    // I have these outside the loop so I don't write it every time.

    out << "Name\t\t" << "\tLocation" << "\tRating " << "Acre" << endl;
    out << "----\t\t" << "\t--------" << "\t------ " << "----" << endl;

    while (current_node)
    {
        out << current_node->item.getName() // Equivalent tabs don't work?
            << current_node->item.getLocation()
            << current_node->item.getAcres()
            << current_node->item.getRating()
            << endl;

        current_node = current_node->nextByName;
    }

    // The equivalent tabs do not work because I am writing names,
    // each of different length to the console. That explains why they
    // are not all evenly spaced apart.
}

Is their anything that I can use to get it all properly aligned with each other? The functions that I'm calling are self-explanatory and all of different lengths, so that don't align very well with each other.

I've tried just about everything in iomanip.

回答1:

You can write a procedure that always print the same number of characters to standard output.

Something like:

string StringPadding(string original, size_t charCount)
{
    original.resize(charCount, ' ');
    return original;
}

And then use like this in your program:

void list::displayByName(ostream& out) const
{
    node *current_node  = headByName;

    out << StringPadding("Name", 30)
        << StringPadding("Location", 10)
        << StringPadding("Rating", 10)
        << StringPadding("Acre", 10) << endl;
    out << StringPadding("----", 30)
        << StringPadding("--------", 10)
        << StringPadding("------", 10)
        << StringPadding("----", 10) << endl;

    while ( current_node)
    {
        out << StringPadding(current_node->item.getName(), 30)
            << StringPadding(current_node->item.getLocation(), 10)
            << StringPadding(current_node->item.getRating(), 10)
            << StringPadding(current_node->item.getAcres(), 10)
            << endl;
        current_node = current_node->nextByName;
    }
}


回答2:

Think of it like using Microsoft Excel :) You think of your stream as fields. So you set the width of the field first then you insert your text in that field. For example:

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

int main()
{
    using namespace std;

    string firstName = "firstName",
            secondName = "SecondName",
            n = "Just stupid Text";
    size_t fieldWidth = n.size(); // length of longest text

    cout << setw(fieldWidth) << left << firstName << endl // left padding
         << setw(fieldWidth) << left << secondName << endl
         << setw(fieldWidth) << left << n << endl;

    cout << setw(fieldWidth) << right << firstName << endl // right padding
         << setw(fieldWidth) << right << secondName << endl
         << setw(fieldWidth) << right << n << endl;
}

......

......

The field width means nothing but the width of the text + spaces. You could fill anything other than spaces:

string name = "My first name";
cout << setfill('_') << setw(name.size() + 10) << left << name;

.....

output::
My first name__________

......

I think the best way is to figure out your format then, write a new formatter that does all what you want:

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

std::ostream& field(std::ostream& o)
{
    // usually the console is 80-character wide.
    // divide the line into four fields.
    return o << std::setw(20) << std::right;
}

int main()
{
    using namespace std;

    string firstName = "firstName",
            secondName = "SecondName",
            n = "Just stupid Text";
    size_t fieldWidth = n.size();

    cout << field << firstName << endl
         << field << secondName << endl
         << field << n << endl;
}

If you started thinking about parametrized manipulators, only that accept one int or long parameter are easy to implement, other types are really obscure if you are not familiar with streams in C++.



回答3:

Boost has a format library that allows you to easily format the ourput like the old C printf() but with type safety of C++.

Remember that the old C printf() allowed you to specify a field width. This space fills the field if the output is undersized (note it does not cope with over-sized fields).

#include <iostream>
#include <iomanip>
#include <boost/format.hpp>

struct X
{  // this structure reverse engineered from
   // example provided by 'Mikael Jansson' in order to make this a running example

    char*       name;
    double      mean;
    int         sample_count;
};
int main()
{
    X   stats[] = {{"Plop",5.6,2}};

    // nonsense output, just to exemplify

    // stdio version
    fprintf(stderr, "at %p/%s: mean value %.3f of %4d samples\n",
            stats, stats->name, stats->mean, stats->sample_count);

    // iostream
    std::cerr << "at " << (void*)stats << "/" << stats->name
              << ": mean value " << std::fixed << std::setprecision(3) << stats->mean
              << " of " << std::setw(4) << std::setfill(' ') << stats->sample_count
              << " samples\n";

    // iostream with boost::format
    std::cerr << boost::format("at %p/%s: mean value %.3f of %4d samples\n")
                % stats % stats->name % stats->mean % stats->sample_count;
}


回答4:

Give up on the tabs. You should be able to use io manipulators to set the field width, the fill character, and the format flag (to get left or right justification). Use the same values for the headings as you do for the data, and everything should come out nicely.

Also beware that you've switched Rating and Acres in your example.