我正在为学校做一个项目,我遇到了一些麻烦。该项目的要点是编写一个程序,该程序读入文本文件并格式化该文件,使其适合特定宽度。要格式化此文件,用户指定输入文件,输出行的长度,以及输出文本的理由。一个例子就是:
$ ./format test.dat 15 right The quick brown fox jumps over the lazy old dog. $ ./format test.dat 15 left The quick brown fox jumps over the lazy old dog. $ ./format test.dat 15 center The quick brown fox jumps over the lazy old dog.
无论如何,我基本上坚持如何基于此输出文件。下面是我在文件中读取的代码,以及我在输出文件时所做的一切。我主要是寻找有关如何去做的提示或建议。我知道我需要使用宽度等的printf,但我对如何移动到下一行很困惑。
char **inputFile(FILE *fp, int size) {
int i = 0;
char *token;
char **str;
str = malloc(sizeof(char *) * size);
token = readToken(fp);
while(!feof(fp)) {
if(i+1 == size) {
realloc(str, size * 2);
}
str[i] = token;
token = readToken(fp);
i++;
}
return str;
}
void toPrint(char **string, int width, int indent) {
int i;
int curLineLength;
if(indent == 0) {
for(i = 0; I < strlen(string); i++
char *token = string[i];
if(curLineLength + strlen(*string) > width) {
if(curLineLength > 0) {
printf("\n");
curLineLength = 0;
}
}
printf("%s ", token);
curLineLength += strlen(*string);
}
/*
if(indent == 1) {
}
if(indent == 2) {
}
*/
}
答案 0 :(得分:1)
继续评论之后,你对线条进行验证的任务对于构造输出函数来说是一个逻辑挑战,而不是一个困难的逻辑挑战。你越来越近了。有很多方法可以做到这一点,您可能需要添加错误检查以确保width
不小于最长的行。
这是一个可以从中抽取的快速示例。确保你理解为什么每一行都按照它的方式编写,并密切关注可变长度数组定义(你需要编译为c99 - 或者如果使用gcc,则依赖于gcc扩展(默认))。如果您有疑问,请告诉我们:
/* format 'n' lines of output of 't' justified as specified in 'just'
(left 'l', right 'r' or centered 'c') within a width 'w'.
NOTE: 'w' must be larger than the longest line in 't'.
*/
void formatted (char **t, char just, size_t w, size_t n)
{
if (!t || !*t) return;
size_t i = 0;
size_t lmax = 0;
size_t len[n];
/* calculate the length of each line, set lmax */
for (i = 0; i < n; i++) {
len[i] = strlen (t[i]);
if (len[i] > lmax) lmax = len[i];
}
/* handle w < lmax reformat or error here */
if (w < lmax) {
fprintf (stderr, "%s() error: invalid width < lmax (%zu).\n",
__func__, lmax);
return;
}
/* left justified output */
if (just == 'l') {
for (i = 0; i < n; i++) {
printf ("%s\n", t[i]);
}
return;
}
/* center or right justified output */
for (i = 0; i < n; i++) {
int spaces = w - len[i];
if (just == 'c')
printf ("%*s%s\n", spaces/2, " ", t[i]);
else if (just == 'r')
printf ("%*s%s\n", spaces, " ", t[i]);
}
}
注意:如果你在Windows上,请将__func__
更改为每个错误陈述中的函数名称。
功能逻辑 - 长版
让我们更接近一下这个功能正在做什么以及它为什么会这样做。首先,让我们看看它所采用的参数:
void formatted (char **t, char just, size_t w, size_t n)
char **t
,您的&#39;字符串&#39; ,以及......实际上您的数组或指向 char*
的指针。当您将指针数组传递给函数时,这可能是您的困惑之处,您只有2种方法可以遍历数组,打印每行文本:(1)传递指向包含文本的字符串的有效指针的数量,或(2)在数组中提供 sentinel ,指针指向指向最后一个指针的指针一行有效的文字。 (通常只是NULL
)标记作为数组中的指针告诉你(&#34;嘿假人,停止尝试打印行 - 你已经打印了最后一行......&# 34; )
这需要更多解释。考虑一下内存中的指针数组。通常,您将始终分配一些合理预期的指针数来填充,当您填充最后一个指针时,您将realloc
数组包含更多空间。这意味着你将在数组末尾至少有一个未填充的指针。如果你初始化你的数组以包含NULL
指针(通过分配calloc
而不是malloc
) - 你自动提供一个标记,或者,你总是可以明确地设置在填充数组时指向NULL
的下一个指针。这将使您的char *指针数组看起来类似于以下内容:
Pointer The pointers in the array of pointers to char*, char **t;
Address point to the first character in each associated string.
+-----------+
| 0x192a460 | --> The quick brown
+-----------+
| 0x192a480 | --> fox jumps over
+-----------+
| 0x192a4a0 | --> a lazy
+-----------+
| 0x192a4c0 | --> dog.
+-----------+
| NULL |
+-----------+
| NULL |
+-----------+
...
了解:您的string
,我的t
是array of pointers to type char*
而不是字符串本身。您的string
是上方指针的左栏。 string
是每个真实字符串的起始地址数组,您的string[i]
将是实际字符串本身的起点 - 在该地址。 i
的范围为0-3
(4
总数)string[0] = "The quick brown"
,string[1] = "fox jumps over"
等。要提供帮助,请在代码中更改{{1 }} string
或array
以帮助保持这一点。
迭代数组以打印每个字符串的2个选项变为(1)(大小&#39; n&#39;传递给函数):
str_array
或(2)(依赖哨兵):
for (i = 0; i < n; i++)
printf ("%s\n", t[i]);
(虽然这里不太明显,但是这会带来很大的好处,因为你的代码变得更加复杂,你需要在许多不同的函数之间传递数组)
既然您已经知道如何访问每个字符串,那么首先要打印左对齐的字符串。您不关心长度,也不关心宽度(只要您的字符串适合 while (t[i]))
printf ("%s\n", t[i++]);
)。您需要做的就是打印每个字符串。由于我们将字符串width
的数量传递给函数,我们可以简单地使用'n'
循环(方法1)来打印每个字符串。 (在打印之前计算长度以确保每个字符串适合for
,因为我们稍后将使用它,我们将每个长度存储在可变长度数组width
中,因此我们没有以后对len
进行减少调用
更有趣的案例是strlen
和center
合理的案例。除了&#39; n&#39;你需要知道用户想要的理由。您可以将要传递该信息的任何类型标志传递给该函数。 right
(char
)只是效率最高的,并且在作为程序输入读取时不需要转换为数字。这就是为什么我选择将1-byte
作为just
而不是char
(int
+输入转换)(或4-bytes
({{1}等)..)
让我们首先看看我们如何对输出进行右对齐。为了便于讨论,让我们考虑一下你想要对你的字符串进行右对齐的short
个字符的输出宽度。你的第一个字符串是2-bytes
(可打印)字符长(实际上是它的#)由于最后的20
字符,内存中存在15
个字符。让我们在20个字符的缓冲区中可视化我们的可打印字符串(您可以使用它来保存字符串的右对齐副本,而不是打印)
16
这使我们可以在开始打印字符串之前轻松查看需要多少空格。这里的任务是将其转换为一个通用的代码片段来处理任何长度的字符串。不太难:
null-terminating
然后利用它来打印右对齐的字符串,我们使用|<------- 20 character width -------->|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| | | | | |T|h|e| |q|u|i|c|k| |b|r|o|w|n|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|<-- 15 character length -->|
格式字符串中的spaces = width - length;
指令。这里特别有用的是能够将字段宽度指令指定为minimum field width
参数列表中的变量。构建格式字符串和参数列表以实现我们的目标,我们有:
printf
其中说,为字符串printf
打印最小字段宽度printf ("%*s%s\n", spaces, " ", t[i]);
(基本上打印spaces
个空格 - 名称选择不当)后跟实际的字符串我们的指针数组" "
。
再次查看图表,我们需要做些什么才能将字符串移动到20个字符宽度的中心?我们只能将spaces
移动那么多,而不是将整个t[i]
数量的空间移动,而它最终会在我们想要的地方移动。 (我可以听到你头上的齿轮磨,我可以闻到烟雾 - &#34;但是,等等...... width - length
是一个奇数,我们正在使用整数!&#34; - 没关系,整数除法会处理它,如果我们移动2而不是2.5,它就好了,你就不能打印1/2个字符......所以把它们放在一起,处理居中或右对齐的文本,你只需要:
1/2
将所有内容与其余内容放在一起
有时看到整个事物如何融合在一起有助于。同样的规则。逐行逐行,逐行扫描,并在遇到问题时提出问题。 C是一种低级语言,这意味着你必须要了解内存中的内容。当谈到它时,编程实际上是关于如何操作加载到内存中的内容。其他语言试图隐藏你的。 C没有。这是它的优势,也是你必须集中精力学习的地方。足够的唠叨,这是代码:
5
示例使用/输出
for (i = 0; i < n; i++) {
int spaces = w - len[i];
if (just == 'c')
printf ("%*s%s\n", spaces/2, " ", t[i]);
else if (just == 'r')
printf ("%*s%s\n", spaces, " ", t[i]);
}