如何改进以下代码

时间:2012-02-19 01:57:15

标签: c

我正在学习C,经过几个小时的努力,我终于解决了以下练习:

“编写一个程序,从两个文件中交替合并行并写入结果 到stdout。如果一个文件的行数少于另一个文件,则剩下的行数来自 应该将较大的文件复制到stdout。“

但是,我对代码不满意。我觉得我过于复杂,而且有 一个更简单的解决方案。

如何改进此代码?

#include <stdio.h>
#include <stdbool.h>

int main (void)
{
    char file1[11], file2[11];
    FILE *input1, *input2;
    int c, d, i = 0;
    bool end_of_file1 = false, end_of_file2 = false;
    bool file1_newline = false, file2_newline = false;

    printf ("Enter the name of the two files to be merged,\
 separated by space: ");
    scanf ("%10s %10s", file1, file2);

    input1 = fopen (file1, "r");
    input2 = fopen (file2, "r");

    while ( end_of_file1 == false ) {
    if ( file1_newline == false )
        c = getc (input1);

    if ( end_of_file2 == true && end_of_file1 == false
                                         && i == 0 ) {
        putc ('\n', stdout);
        i = 1;
    }

    if ( c == '\n' && end_of_file2 == true )
        i = 0;

    if ( (c == '\n' && file1_newline == false) ||
               (c == EOF && file1_newline == false) ) {
        file1_newline = true;
        putc (' ', stdout);
    }

    if ( file1_newline == false )
        putc (c, stdout);

    if ( file1_newline == true )
        d = getc (input2);
    if ( d == EOF ) {
        end_of_file2 = true;
        if ( c == EOF )
        end_of_file1 = true;
    }
    if ( file1_newline == true && end_of_file2 == false )
        putc (d, stdout);
    if ( (d == '\n' && c != EOF) || end_of_file2 == true )
        file1_newline = false;
    }

    fclose (input1);
    fclose (input2);

    return 0;
}

3 个答案:

答案 0 :(得分:5)

算法问题

...让我们看看问题,而不是代码...

  

编写一个程序,从两个文件交替合并行,并将结果写入stdout。如果一个文件的行数少于另一个文件,则较大文件中的其余行应简单地复制到stdout

鉴于你应该处理线条,看起来整条线似乎更好。为此,您应该使用fgets()getline()(尽管后者的可用性不如前者)。

char line1[4096];
char line2[4096];

...

char *l1 = fgets(line1, sizeof(line1), input1);
char *l2 = fgets(line2, sizeof(line2), input2);

while (l1 != 0 && l2 != 0)
{
    fputs(line1, stdout);
    fputs(line2, stdout);
    l1 = fgets(line1, sizeof(line1), input1);
    l2 = fgets(line2, sizeof(line2), input2);
}

/* One file has reached EOF */

if (l1 != 0)
{
    fputs(line1, stdout);
    while (fgets(line1, sizeof(line1), input1) != 0)
        fputs(line1, stdout);
}
if (l2 != 0)
{
    fputs(line2, stdout);
    while (fgets(line2, sizeof(line2), input2) != 0)
        fputs(line2, stdout);
}

挑剔风格

就我个人而言,我不喜欢在函数的括号周围有空格的方式 - K&amp; R区分运算符,例如iffor,其中有一个空格分隔关键字和表达式和函数调用没有这样的空间。但这是一个风格问题,因此非常主观。

这些代码行提供了充足的弹药:

    bool end_of_file1 = false, end_of_file2 = false;
    bool file1_newline = false, file2_newline = false;

    printf ("Enter the name of the two files to be merged,\
 separated by space: ");
    scanf ("%10s %10s", file1, file2);

    input1 = fopen (file1, "r");
    input2 = fopen (file2, "r");

不要在一行上组合多个声明,尤其是初始化时。

bool end_of_file1 = false;
bool end_of_file2 = false;
bool file1_newline = false;
bool file2_newline = false;

(但是你使用后缀1和2而不是'无后缀'和2来获得加分。)

不要在带有反斜杠的行之间拆分字符串文字。这是一种非常古老的方式。自1989年以来使用字符串连接,标准(并修复语法)。请注意,在反斜杠换行技术的许多缺陷中,它会破坏代码的缩进,并且非常容易受到编辑错误的影响。

printf("Enter the names of the two files to be merged,"
       " separated by space: ");

在阅读之前考虑fflush(stdout);。在实践中,它通常不是必需的,但值得考虑。请注意,用户可以在单独的行中输入两个名称;那也行。我认为,将文件名限制为10个字符是相当简约的。你应该允许至少256个字符。您可以在格式参数中指定字符串的大小,并且正确执行(在sizeof(array)-1,而不是sizeof(array))。一个更有用的程序设计可能会从程序的命令行参数中获取文件名,而不是提示用户输入名称。

始终测试scanf()的结果:

if (scanf("%10s %10s", file1, file2) != 2)
    ...something went wrong...

始终测试fopen()的结果:

if ((input1 = fopen (file1, "r")) == 0)
    ...something went wrong...
if ((input2 = fopen (file2, "r")) == 0)
    ...something went wrong...

您的更多代码

while ( end_of_file1 == false ) {
if ( file1_newline == false )
    c = getc (input1);

if ( end_of_file2 == true && end_of_file1 == false
                                     && i == 0 ) {
    putc ('\n', stdout);
    i = 1;
}

将循环体缩进一级(或者,在StackOverflow上,不要使用制表符)。对int(以及之后的cd使用{{1}}是正确的。

循环中的逻辑是......模糊不清。目前尚不清楚你在做什么。一般来说,你想尽快关闭EOF;你在做那个测试之前等了一会儿。循环的主体对我来说是不可理解的 - 非常复杂的逻辑(好吧,它看起来很复杂;我怀疑底层逻辑很简单,但因为没有解释它的作用,它看起来很复杂)。

答案 1 :(得分:1)

  • 一般情况下,但特别是当变量名称“读得好”时,请不要使用== false== true
  • 将复杂的布尔表达式提取为可读的布尔变量。
  • 如果用途不明确,请使用可读变量名称(例如i)。
  • 使用具有可读名称的附加功能,用于具有特定目的的代码块。
  • if重新组织(合并,嵌套,排除,排序)为最简单的形式。从最普遍的意义上讲,Karnaugh maps可能有所帮助;无论如何,它通常是在纸上手动完成的。
  • 删除未使用的变量(例如file2_newline
  • 如果您有任何适用于不同参数的功能,则将功能提取到单独的功能是必须
  • 如果您进行基于行的编辑/解析,请使用基于行的函数而不是基于字符的函数。
  • 尽量保持功能小,不要将多个目的/意图混合到一个功能中。

除此之外(以及其他人给出的其他建议),您可以获得的最佳建议是阅读代码。阅读很多代码,已知是很好的代码。我对FreeBSD tcp/ip stack codebase有很好的体验,还有很多其他很好的例子。


我以为我会尝试将自己的上述建议应用到您的代码中,这就是我所得到的:

#include <stdio.h>
#include <stdbool.h>
#include <string.h>

#define BUFSIZE 10
char buffer[BUFSIZE];

bool append_line_and_check_eof(FILE *input, char newline_replacement);
bool process_line(FILE *to_process, FILE *other, char newline_replacement);

int main(void) {
    char file1[11], file2[11];
    FILE *input1, *input2;

    printf("Enter the name of the two files to be merged, separated by space: \n");
    fflush(stdout);
    scanf("%10s %10s", file1, file2);

    input1 = fopen(file1, "r");
    input2 = fopen(file2, "r");
    if (!input1 || !input2)
        return 1;

    while (process_line(input1, input2, ' ') && process_line(input2, input1, 0))
        printf("\n");

    fclose(input1);
    fclose(input2);

    return 0;
}

// prints a line from 'to_process', appends all from 'other' if eof is reached.
// returns whether to continue processing or not.
bool process_line(FILE *to_process, FILE *other, char newline_replacement) {
    bool eof = append_line_and_check_eof(to_process, newline_replacement);
    if (eof) {
        /* append rest from 'other' */
        while (fgets(buffer, BUFSIZE, other))
            printf("%s", buffer);
        return false;
    }
    return true;
}

bool append_line_and_check_eof(FILE *input, char newline_replacement) {
    bool newline;

    do {
        if (!fgets(buffer, BUFSIZE, input))
            return true;

        /* discriminate between full buffer and eof */
        int len = strlen(buffer);
        newline = buffer[len - 1] == '\n';
        if (newline)
            buffer[len - 1] = newline_replacement;
        printf("%s", buffer);
    } while (!newline);

    return false;
}

代码在功能上与您的代码等效,编写了一种希望更好地读取的方式,并遵循我的建议。

答案 2 :(得分:1)

这是另一种方法。

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char **argv)
{
    if(argc != 3) return 1;

    FILE *fp, *fp2;
    char *line, *line2, buf[BUFSIZ];

    fp = fopen(argv[1], "r");
    if(!fp) {
        perror(argv[1]);
        return 0;
    }

    fp2 = fopen(argv[2], "r");
    if(!fp2) {
        perror(argv[2]);
        fclose(fp);
        return 0;
    }

    do {
        line  = fgets(buf, BUFSIZ, fp);
        if(line) printf("%s", line);

        line2  = fgets(buf, BUFSIZ, fp2);
        if(line2) printf("%s", line2);

    } while( line || line2 );


    fclose(fp);
    fclose(fp2);
    return 0;
}