使用千位分隔符和百万位分隔符打印整数

19

关于将整数使用千位/百万位分隔符打印的问题。

我有一个文本文件,其中包含国家、城市和总人口。

我需要读取文件,并按国家进行排序。如果国家相同,则必须按人口数量降序排序。

文本文件格式如下:

澳大利亚........悉尼.........10.123.456

巴西...........圣保罗.......7.123.345

我将所有三个字符串读入并分开处理。然后我从人口字符串中删除所有“。”。之后我使用atoi()将人口字符串转换为整数。

现在,如果国家相同,我就可以按人口数量排序了。这种排序是正确的。

到目前为止一切都好。但是我需要在人口的打印中加上千位/百万位分隔符。

如果我使用带有“.”的字符串进行排序,则不会得到正确的结果。

它会像这样排序:

x........x......1.123456

x........x......10.123.456

x........x......2.123.232

它应该看起来像:

澳大利亚........悉尼.........10.123.456

澳大利亚........布里斯班.......8.123.456

有没有办法通过再次添加分隔符来操作打印的整数呢?

非常感谢!


使用具有该分隔符的区域设置?(有关更多信息,请参见C++ 本地化功能。) - Some programmer dude
当然排序不正确了...你把那些数字当作字符串处理,这意味着10小于7,因为17之前。你应该在内部将数字保留为整数,并且只在真正进行输出时将它们转换为格式化版本。 - Marc B
人口是整数。我只是使用字符串进行读取,然后删除所有的“.”并将它们转换为整数。 - d0zer
2个回答

27

imbue() 函数可以使用带有所需分隔符的locale 来设置输出流。例如:

#include <iostream>
#include <locale>

int main()
{
    // imbue the output stream with a locale.
    int i = 45749785;
    std::cout << i << "\n";

    std::cout.imbue(std::locale(""));
    std::cout << i << "\n";
}

在我的机器上的输出(以及在线演示):

45749785
45,749,785

James Kanze所评论和回答的那样,通过使用imbue函数将输入流设置为特定的locale,可以在不手动修改输入的情况下读取分隔的整数值。


有关locale的详细概述,请参见Stroustrop的附录D:Locale


他还应该在输入流上使用相同的本地化,这样他就不必手动剥离分隔符。(而且,本地化 "" 是否会这样做将有很大的差异。在 Unix 机器上,除非用户设置了至少一个 LC_... 环境变量,否则 "" 将给出 "Posix" 本地化,它与 "C" 本地化相同,不使用千位分隔符。另一方面,如果用户将其设置为 "fr_FR",那么千位分隔符将是 '.',而不是 ',')。 - James Kanze
@JamesKanze,这段代码是为了说明如何使用imbue()函数来设置流的区域设置,我知道用户的区域设置可能会有所不同。识别适当的区域设置是提问者的责任(据我所知,这些区域设置没有标准名称)。关于输入流方面,我们达成了一致,谢谢。 - hmjd
@d0zer 当你执行 locale::global( locale( "German_germany" ) ) 时,你正在改变全局语言环境;这将成为构建新流时默认使用的语言环境。(很难让它默认影响 std::cout。这就是为什么你应该在程序开始时始终注入标准流。)至于在同一字符串上多次更改语言环境:如果两个语言环境具有不同的 codecvt,则会有非常严格的限制。否则,就不应该有问题。 - James Kanze
@JohnDibling,谢谢。说实话,我只是几周前才阅读了我在答案中链接的文档。非常有趣和有用。 - hmjd
@hmjd:看看Langer&Kreft的“标准IOStreams和Locales”。 - John Dibling
显示剩余4条评论

20

使用支持所需分隔符的语言环境来读取文件(以便将值读取为整数),并使用相同的语言环境编写数据。

请注意,您可能没有此类语言环境可用,或者如果有,您可能不知道其名称(并且使用命名语言环境可能会更改其他您不想更改的内容)。在我的机器上,imbue“”行为不同,这取决于编译器(或者可能是我从中调用它的shell) - 如果您有严格的格式要求,不应使用语言环境“”。(使用语言环境“”的情况是您希望格式取决于用户环境规范的情况。)

在这种情况下,最好显式提供本地化:

class MyNumPunct : public std::numpunct<char>
{
protected:
    virtual char do_thousands_sep() const { return ','; }
    virtual std::string do_grouping() const { return "\03"; }
};

int
main()
{
    std::cout.imbue( std::locale( std::locale::classic(), new MyNumPunct ) );
    std::cout << 123456789 << std::endl;
    return 0;
}

当然,您也希望将此区域设置用于输入。

(此代码将为您提供仅更改分组的" C "区域设置。)


可以运行。但如何确保新的 MyNumPunt 被垃圾回收? - user13947194
2
@user13947194 的 facets 是引用计数的。在这段代码中,创建的 std::locale 将会增加 MyNumPunct 对象的引用计数,而它的析构函数将会减少引用计数并销毁 facet。你不需要额外做任何事情来获得这种行为。 - Remy Lebeau

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接