我喜欢Brian Kernighan和Rob Pike的书“UNIX编程环境”中提出的想法,他们专注于在一个环境中工作,在这个环境中你可以将许多(小的,精确的,易于理解的)程序放在一起用于完成许多编程任务的命令行。
我正在研究严格的ANSI C约定并试图坚持这一理念。在本书的某处(如果需要,我可以获得确切的页码),他们建议此环境中的所有程序都应遵循以下原则:
如果在命令行中显示输入,则作为程序本身的参数,处理该输入。
如果命令行中没有输入,则从stdin处理输入。
这是我编写的一个C程序,它将回显作为回文的任何输入(数字或字母)。我的具体问题是:
这是一个表现良好的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;
}
答案 0 :(得分:3)
您遇到的一个问题是潜在的缓冲区溢出,因为您正在将任意长度的输入写入具有固定大小的缓冲区。您可以通过拒绝太长的输入或动态创建正确大小的数组来解决此问题。我会避免使用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的观点,即你将回文检查代码分解为一个单独的函数。
答案 1 :(得分:3)
是的,我认为您正在遵循R&amp; 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 */
}
当然,我很确定这可以改进(它甚至可能有一个bug,我速度非常快),但是一旦你拥有了你的字符串,无论是从命令行还是用户输入,你都可以调用它功能或这样的功能。
注意:编辑反映Mark的评论,非常感谢,Mark!
答案 2 :(得分:1)
关于您指定的原则,我认为这些工具通常将其参数作为要处理其内容的文件名。相反,你将它们视为输入本身。
以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字符串终止符。如果你想允许任意长度的字符串,我认为你必须动态地分配缓冲区,并在存在更大字符串的情况下调整它的大小。这会稍微复杂一些。
答案 3 :(得分:1)
对于文本过滤器非常有用,例如程序只打印带有回文的行,通过命令行参数指定输入文件,例如,它允许:
$ palindromes input*.txt # file patterns
$ find -name '*.txt' -print0 | xargs -0 palindromes
许多语言都支持这种惯例。以下是Perl,Python,C中具有相同用法的脚本:
Usage: palindromes [FILE] Print lines that are polindromes in each FILE. With no FILE, or when FILE is -, read standard input.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)
在#!/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()
。