回声所有Palindromes,在C

我喜欢Brian Kernighan和Rob Pike的书“UNIX编程环境”中提出的想法,他们专注于在一个环境中工作,在这个环境中可以在命令行上集成许多(小的,精确的,很好理解的)程序完成许多编程任务。

我正在研究严格的ANSI C惯例,并试图坚持这一理念。 在本书的某个地方(如果需要,我可以得到一个确切的页码),他们build议在这个环境中的所有程序都应该遵循以下原则:

  1. 如果在命令行上显示input,则作为程序本身的参数,处理该input。

  2. 如果命令行中没有input,则从stdin处理input。

这是我写的一个C程序,它将回显任何回文input(数字或字母)。 我的问题具体是:

这是一个很好的C程序吗? 换句话说,Kernighan和Pike认为这是一个命令行应用程序的最佳行为吗?

#include <stdio.h> #include <string.h> /* for strlen */ int main(int argc, char* argv[]) { char r_string[100]; if (argc > 1) { int length = (int)strlen(argv[1]); int i = 0; int j = length; r_string[j] = (char)NULL; j--; for (i = 0; i < length; i++, j--) { r_string[j] = argv[1][i]; } if (strcmp(argv[1], r_string) == 0) { printf("%s\n", argv[1]); } } else { char* i_string; while (scanf("%s", i_string) != EOF) { int length = (int)strlen(i_string); int i = 0; int j = length; r_string[j] = (char)NULL; j--; for (i = 0; i < length; i++, j--) { r_string[j] = i_string[i]; } if (strcmp(i_string, r_string) == 0) { printf("%s\n", i_string); } } } return 0; } 

是的,我认为你正在遵循R&K的建议。 正如雨果所说,你可以把这个参数作为一个文件名,这个简单的程序,我认为把参数作为回文本身可能会更有意义。

另外,如果你允许我额外的建议,我会分开阅读一个字符串的功能来检查它是否是回文,因为你现在有这个代码重复。

 int ispalindrome(const char* c) { size_t len = strlen(c); size_t limit = len/2; size_t i; for (i = 0; i < limit; i++) { if(c[i]!=c[len-i-1]) break; /* Different character found */ } return i==limit; /* If we reached limit, it's a palyndrome */ } 

当然,我非常肯定这可以改进(甚至可能有一个错误,我打字速度很快),但是一旦你有你的字符串,无论是从命令行或用户输入,你可以调用这个函数或像这样的功能。

注:编辑反映马克的评论,非常感谢,马克!

你有一个问题是潜在的缓冲区溢出,因为你正在写一个任意长度的输入到一个具有固定大小的缓冲区。 您可以通过拒绝太长的输入或动态创建正确大小的数组来解决此问题。 我会避免使用scanf

关于实际算法,您不需要复制颠倒的字符串,然后比较两个字符串。 你可以使用只有一个字符串和一个指针在两端的指针检查,都向中间移动。

这里是一些代码来显示原理:

 char* a = /* pointer to first character in string */; char* b = /* pointer to last character in string (excluding the null terminator) */; while (a < b && *a == *b) { a++; b--; } if (a >= b) { // Is palindrome. } 

我同意Javier的意见,你把回文检查代码分解成一个单独的函数。

关于您指定的原则,我相信这些工具通常会将其参数作为文件名的内容进行处理。 相反,你把它们当作输入本身。

sort为例。 如果你没有指定任何参数,stdin的内容将被排序。 否则,您指定的文件名中的内容将被排序。 这不是被处理的论证本身。

代码是这样的:

 FILE * input = stdin; if (argc > 1) { input = fopen(argv[1], "r"); // handle possible errors from the fopen } while (fscanf(input, "%s", i_string) != EOF) // check if i_string is a palindrome and output to stdout 

另外,你应该小心Mark Byers指定的缓冲区溢出。

你没有正确处理字符串读取。 i_string缓冲区没有初始化,即使是这样,你应该限制scanf读取的字节数以避免提到的溢出:

 char i_string[1000]; while (scanf("999%s", i_string) != EOF) if (is_palindrome(i_string)) /* Use any function defined in the other answers */ printf("%s\n", i_string); 

您必须始终保留一个字节(1000与999)来说明NULL字符串终止符。 如果你想允许任意长度的字符串,我想你必须dinamically分配缓冲区,并调整大小,以防大字符串存在的情况下。 这会稍微复杂一些。

这对文本过滤器很有用,例如只打印具有回文的行来通过命令行参数指定输入文件的程序,例如,它允许:

 $ palindromes input*.txt # file patterns $ find -name '*.txt' -print0 | xargs -0 palindromes 

这是常用的惯例,是由许多语言支持。 下面是Perl,Python,C中具有相同用法的脚本:

 用法:palindromes [文件]
在每个FILE中打印polindromes的行。

如果没有FILE,或者FILE是 - 读取标准输入。 

在Perl中

 #!/usr/bin/perl -w while (<>) { # read stdin or file(s) specified at command line $line = $_; s/^\s+//; # remove leading space s/\s+$//; # remove trailing space print $line if $_ eq reverse $_; # print line with a palindrome } 

在Python中

 #!/usr/bin/env python import fileinput, sys for line in fileinput.input(): # read stdin or file(s) specified at command line s = line.strip() # strip whitespace characters if s == s[::-1]: # is palindrome sys.stdout.write(line) 

在C

 #!/usr/local/bin/tcc -run -Wall #include <ctype.h> #include <errno.h> #include <stdbool.h> #include <stdio.h> #include <string.h> enum { MATCH, NO_MATCH, ERROR }; bool is_palindrome(char *first, char *last) { /** Whether a line defined by range [first, last) is a palindrome. `last` points either to '\0' or after the last byte if there is no '\0'. Leading and trailing spaces are ignored. All characters including '\0' are allowed */ --last; // '\0' for ( ; first < last && isspace(*first); ++first); // skip leading space for ( ; first < last && isspace(*last); --last); // skip trailing space for ( ; first < last; ++first, --last) if (*first != *last) return false; return true; } int palindromes(FILE *fp) { /** Print lines that are palindromes from the file. Return 0 if any line was selected, 1 otherwise; if any error occurs return 2 */ int ret = NO_MATCH; char *line = NULL; size_t line_size = 0; // line size including terminating '\0' if any ssize_t len = -1; // number of characters read, including '\n' if any, // . but not including the terminating '\0' while ((len = getline(&line, &line_size, fp)) != -1) { if (is_palindrome(line, line + len)) { if (printf("%s", line) < 0) { ret = ERROR; break; } else ret = MATCH; } } if (line) free(line); else ret = ERROR; if (!feof(fp)) ret = ERROR; return ret; } int main(int argc, char* argv[]) { int exit_code = NO_MATCH; if (argc == 1) // no input file; read stdin exit_code = palindromes(stdin); else { // process each input file FILE *fp = NULL; int ret = 0; int i; for (i = 1; i < argc; i++) { if (strcmp(argv[i], "-") == 0) ret = palindromes(stdin); else if ((fp = fopen(argv[i], "r")) != NULL) { ret = palindromes(fp); fclose(fp); } else { fprintf(stderr, "%s: %s: could not open: %s\n", argv[0], argv[i], strerror(errno)); exit_code = ERROR; } if (ret == ERROR) { fprintf(stderr, "%s: %s: error: %s\n", argv[0], argv[i], strerror(errno)); exit_code = ERROR; } else if (ret == MATCH && exit_code != ERROR) // return MATCH if at least one line is a MATCH, propogate error exit_code = MATCH; } } return exit_code; } 

如果选择任何行,则退出状态为0,否则为1; 如果发生任何错误,退出状态是2.它使用GNU getline() ,允许任意大的行作为输入。