如何在C语言中对齐数字?

40

我需要使用 printf() 在 C 语言中对一系列数字进行对齐,就像这个例子:

-------1
-------5
------50
-----100
----1000

当然,这些之间还有一些数字,但对于手头的问题并不相关...哦,将破折号视为空格,我使用破折号是为了更容易理解我的意思。

我只能做到这个程度:

----1---
----5---
----50--
----100-
----1000

或者这个:

---1
---5
--50
-100
1000
但是这不是我想要的,而且我不能仅使用printf()函数来实现第一个示例显示的效果。这样真的可能吗? 编辑: 抱歉,我太匆忙了,没有解释清楚……我的最后一个示例以及你们所有的建议(例如使用“%8d”之类的内容)都无法正常工作,因为尽管最后一个数字是1000,但它不一定会到达1000,甚至不到100或10。
无论要显示多少位数字,我只想要最多4个前导空格以显示最大的数字。假设我必须显示从1到1000(A)和从1到100(B)的数字,并且对于两者都使用“%4d”,这将是输出结果:
---1
....
1000
我想要的输出是什么...
---1
....
-100

这并不是我想要的输出结果,实际上我希望得到这个:

--1
...
100

但是就像我说的,我不知道我需要打印的数字的确切数量,它可以有1位数字,也可以有2位、3位或更多位数字,函数应该为所有情况做好准备。而且我想要额外添加四个前导空格,但这并不那么重要。

编辑2:看起来我想要的方式,我需要的方式是不可能的(请查看David Thornley和Blank Xavier的答案以及我的评论)。谢谢大家的时间。


1
我不确定C语言,但是在C++中,您可以将输出写入文件并调整宽度精度。如果在C语言中也可以这样做,请告诉我,我会尝试帮助回答。 - TStamper
这不只是一个右对齐问题吗? - Ungeheuer
11个回答

56

为什么 printf("%8d\n", intval); 对你来说不起作用?它应该可以正常工作...

你没有展示任何一个“不起作用”的例子的格式字符串,所以我不确定还能告诉你什么。

#include <stdio.h>

int
main(void)
{
        int i;
        for (i = 1; i <= 10000; i*=10) {
                printf("[%8d]\n", i);
        }
        return (0);
}

$ ./printftest
[       1]
[      10]
[     100]
[    1000]
[   10000]

编辑:回答问题的澄清:

#include <math.h>
int maxval = 1000;
int width = round(1+log(maxval)/log(10));
...
printf("%*d\n", width, intval);

宽度计算采用以10为底数的对数加1,这会得到数字的位数。神奇的 * 可以让您在格式字符串中使用变量的值。

对于任何给定运行的最大值仍然需要知道,但在任何语言或纸笔上都无法避免。


对于可变宽度这个概念,我完全不知道,太酷了! - undefined

53

我查阅了我的便携式 Harbison & Steele 书籍…

确定字段的最大宽度。

int max_width, value_to_print;
max_width = 8;
value_to_print = 1000;
printf("%*d\n", max_width, value_to_print);

请记住,max_width必须是int类型才能与星号一起使用,您将不得不根据所需的空间量来计算它。在您的情况下,您需要计算最大数字的最大宽度,并添加4。


问题在于我不知道最大的数字,直到我达到它之前我永远不会知道。而且我需要即时完成这个任务... - rfgamaral
17
换句话说,你在问一个从定义上来说不可能回答的问题? - Michael Myers
4
我猜...但在看到你的回答之前,我不知道这是不可能的。 - rfgamaral
这不就是一个右对齐的问题吗?至少我是这样理解的。 - Ungeheuer
假设你正在读取文件并显示行号... 你将能够知道最大的数字。一直读到结尾,计算 \n 然后关注内容。从内存角度来看没有限制的程序可以直接将2GB文件加载到内存中,这样只需要进行一组文件操作。 - Jimmio92

4

[我意识到这个问题已经有很多年了,但它的核心问题(或两个问题)涉及到OP,编程教学以及假设。]

包括一个mod在内的一些人认为这是不可能的。在一些(包括最明显的)情况下,确实如此。但有趣的是,这对于OP来说并不是显而易见的。

这种不可能性假定上下文是在基于行的文本控制台上运行从C编译生成的可执行文件(例如console+sh或X-term+csh或Terminal+bash),这是一个非常合理的假设。但是,“正确”的答案(“%8d”)对于OP来说并不够好,同时也并不显然,这表明附近可能存在一个相当大的问题...

考虑Curses(以及它的许多变体)。在其中,您可以导航“屏幕”,“移动”光标,并“重绘”文本输出的部分(窗口)。在Curses环境中,绝对是可能的,即通过动态调整“窗口”大小以容纳更多数量。但即使是Curses也只是一个屏幕“绘画”抽象。没有人建议这样做,可能是正确的,因为在C中的Curses实现并不意味着它是“严格的C”。好吧。

但这到底意味着什么?为了回答“这是不可能的”这个问题,这意味着我们正在对运行时系统做出一些说法。换句话说,这不是理论性的(例如,“如何对静态分配的int数组进行排序?”),这可以解释为完全忽略运行时任何方面的“封闭系统”。

但是,在这种情况下,我们有I / O:具体来说,是printf()的实现。但是,这就是机会,可以在响应中提供更有趣的信息(尽管可以承认的是,询问者可能没有深挖)。

假设我们使用不同的假设。假设OP相当“聪明”,并且理解无法编辑基于行的流中的先前行(如何纠正线打印机输出的字符的水平位置?)。还假设OP不只是一个在做家庭作业并且没有意识到这是一个旨在探讨“流抽象”的“恶作剧”问题的孩子。进一步假设,OP想知道:“等等...如果C的运行时环境支持STDOUT的概念 - 如果STDOUT只是一个抽象层次 - 那么为什么没有一个终端抽象,既可以垂直滚动,又可以支持可定位光标?两者都是在屏幕上移动文本。”

因为如果我们试图回答的问题是这样的,那么您只需要看到:

ANSI 转义序列

几乎所有视频终端制造商都添加了供应商特定的转义序列,以执行诸如在屏幕上任意位置放置光标等操作。例如,VT52 终端允许通过发送 ESC 字符、Y 字符,然后是两个数字值分别等于 x,y 位置加 32 的字符来将光标放置在屏幕的 x,y 位置(从 ASCII 空格字符开始并避免控制字符)。Hazeltine 1500 也有类似的功能,使用 ~、DC1 和 X 和 Y 位置之间用逗号隔开。虽然这两个终端在这方面具有相同的功能,但必须使用不同的控制序列来调用它们。

第一个支持这些序列的流行视频终端是 1978 年推出的 Digital VT100。该型号在市场上非常成功,引发了各种 VT100 克隆产品,其中最早和最受欢迎的是 1979 年推出的价格更便宜的 Zenith Z-19。其他产品包括 Qume QVT-108、Televideo TVI-970、Wyse WY-99GT,还有许多其他品牌的可选“VT100”或“VT103”或“ANSI”模式,其兼容性不同。这些的流行逐渐导致越来越多的软件(特别是公告栏系统和其他在线服务)假定转义序列有效,从而导致几乎所有新终端和仿真器程序都支持它们。

事实上,早在 1978 年就已经可以使用 "ANSI" 转义序列了。C 语言诞生于 1972 年,K&R 版本于 1978 年确立。如果我们也能假定: "好吧,假设您的终端具有 VT100 功能。" 那么就有一个 "C 语言" 的答案。顺便说一下,不支持 ANSI 转义序列的控制台是什么?没错,是 Windows 和 DOS 控制台。但在几乎所有其他平台(Unix、Vaxen、Mac OS、Linux)上,您都可以期望支持它们。

简而言之——没有不陈述运行时环境假设的合理答案。由于大多数运行时环境(除非您使用桌面计算机市场份额的 80 年代和 90 年代来计算 '大多数')自 VT-52 以来就具备了这个功能,所以我认为完全不可能——只是为了使其成为可能,需要进行大量的工作,并且不像 %8d 那么简单,尽管问题看起来像 OP 已经知道了。

我们只需要澄清假设即可。

如果有人认为输入/输出是例外情况,即我们只需要考虑运行时环境(或甚至是硬件)的时候,请查看 IEEE 754 浮点数异常处理。对于那些感兴趣的人:

英特尔浮点数案例研究

根据加州大学伯克利分校的William Kahan教授的说法,1996年6月发生了一个经典案例。一枚名为Ariane 5的卫星运载火箭在发射后不久便倒立翻滚,并将自身和价值超过5亿美元的有效载荷散落在了法属圭亚那的一个沼泽地区。Kahan发现灾难可以归咎于一种编程语言,该语言忽略了IEEE 754中默认的异常处理规范。在发射时,传感器报告了强烈的加速度,导致了旨在重新校准火箭惯性引导系统的软件出现整数转换溢出。


虽然你没有直接回答问题,但我很喜欢你的发现 :) 但是还有一个问题存在 - 将太多的环境考虑进去可能会导致任意困难或复杂情况。在我看来,这是人生中持续学习的一部分 - 学会在哪里切割事物以使它们保持方便... - jerch

4
    printf("%8d\n",1);
    printf("%8d\n",10);
    printf("%8d\n",100);
    printf("%8d\n",1000);

2
所以,您想要一个宽度为8个字符的字段,并以空格作为填充?请尝试使用“%8d”。这里是参考资料编辑:您正在尝试做的不是仅通过printf处理的事情,因为它不知道您要写的最长数字是多少。在执行任何printf之前,您需要计算最大数字,然后确定要用作字段宽度的数字数量。然后,您可以使用snprintf或类似工具即时创建printf格式。
char format[20];
snprintf(format, 19, "%%%dd\\n", max_length);
while (got_output) {
    printf(format, number);
    got_output = still_got_output();
}

printf("%*d", width, value) 比构造格式字符串少了很多工作,可能更少出错,也更清晰。由于 printf() 需要额外的参数(考虑到大量变量字段),因此它可能会慢一些,所以你展示的技巧在某些情况下确实有价值。 - RBerteig

1
尝试将其转换为字符串,然后使用“%4.4s”作为格式说明符。这将使其成为固定宽度格式。

0
#include<stdio.h>
int main()
{
 int i,j,n,b;
 printf("Enter no of rows ");
 scanf("%d",&n);
 b=n;
 for(i=1;i<=n;++i)
 {
    for(j=1;j<=i;j++)
    {
        printf("%*d",b,j);
        b=1;
    }
        b=n;
    b=b-i;
    printf("\n");


 }
 return 0;
}

0
据我所知,根据您拥有的数据,您想要的填充量会有所不同。因此,唯一的解决方案是在打印之前扫描数据,找出最宽的数据,然后使用星号运算符将宽度值传递给printf,例如:
loop over data - get correct padding, put into width

printf( "%*d\n", width, datum );

1
那么你就做不到了。如果你需要打印一个数据,而你所打印的内容取决于接下来的数据,而你又不知道这些数据会是什么,那么你正在尝试一项不可能的任务。 - user82238

0

如果您无法提前知道宽度,那么您唯一可能的答案就是在某种临时缓冲区中分阶段输出。对于小型报告,只需收集数据并将输出推迟到输入受限为止即可。

对于大型报告,如果收集的数据超出了合理的内存限制,则可能需要一个中间文件。

一旦您拥有了数据,那么使用惯用语printf("%*d", width, value)处理每个值,将其后处理成报告就很简单了。

或者,如果输出通道允许随机访问,您可以直接编写假定(短)默认宽度的报告草稿,并在任何时候编辑它以更改宽度假设。这还假定您可以以某种无害的方式填充该字段之外的报告行,或者您愿意通过读取-修改-写入过程替换到目前为止的输出并放弃草稿文件。

但是,除非您能够预测正确的宽度,否则没有形式的两遍算法,您将无法实现所需的操作。


0

看着编辑后的问题,您需要找到要呈现的最大数字的位数,然后使用sprintf()生成printf()格式,或者使用%*d将传递为int的数字作为*和值。一旦您获得了最大的数字(您必须提前确定),您可以使用“整数对数”算法(在达到零之前可以除以10多少次)或使用snprintf()来确定数字的位数,缓冲区长度为零,格式为%d,字符串为空;返回值告诉您将格式化多少个字符。

如果您不知道并且无法确定最大数字何时出现,那么您就陷入了困境-您无能为力。


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