K&练习:我的代码有效,但感觉很臭;建议清理?
-
03-07-2019 - |
题
我正在研究K& R书。我读的比我做的更进一步,主要是因为时间不够。我正赶上来,并完成了第1章的几乎所有练习,这是教程。
我的问题是练习1-18。练习是:
编写程序以删除尾随空白和 输入行中的选项卡,以及完全删除空白行
我的代码(下面)做到了,并且有效。我的问题是我实施的修剪方法。感觉......错......不知何故。就像我在代码审查中看到C#中的类似代码一样,我可能会疯了。 (C#是我的专长之一。)
任何人都可以提供一些关于清理它的建议 - 所述建议必须仅使用K& S第1章的知识。 R.(我知道有很多方法可以使用完整的C库来清理它;我们只是在这里讨论第1章和基本的stdio.h。)另外,在给出建议时,你能解释它为什么有用吗? (毕竟,我是在努力学习!还有谁比这里的专家更好的学习?)
#include <stdio.h>
#define MAXLINE 1000
int getline(char line[], int max);
void trim(char line[], char ret[]);
int main()
{
char line[MAXLINE];
char out[MAXLINE];
int length;
while ((length = getline(line, MAXLINE)) > 0)
{
trim(line, out);
printf("%s", out);
}
return 0;
}
int getline(char line[], int max)
{
int c, i;
for (i = 0; i < max - 1 && (c = getchar()) != EOF && c != '\n'; ++i)
line[i] = c;
if (c == '\n')
{
line[i] = c;
++i;
}
line[i] = '\0';
return i;
}
void trim(char line[], char ret[])
{
int i = 0;
while ((ret[i] = line[i]) != '\0')
++i;
if (i == 1)
{
// Special case to remove entirely blank line
ret[0] = '\0';
return;
}
for ( ; i >= 0; --i)
{
if (ret[i] == ' ' || ret[i] == '\t')
ret[i] = '\0';
else if (ret[i] != '\0' && ret[i] != '\r' && ret[i] != '\n')
break;
}
for (i = 0; i < MAXLINE; ++i)
{
if (ret[i] == '\n')
{
break;
}
else if (ret[i] == '\0')
{
ret[i] = '\n';
ret[i + 1] = '\0';
break;
}
}
}
编辑:我感谢我在这里看到的所有有用的提示。我想提醒大家,我仍然是一个带有C的n00b,特别是还没有达到指针。 (记住关于K&amp; R-Ch.1的Ch.1的一点不做指针。)我“有点”。得到一些解决方案,但他们仍然是一个先进的我... ...
我正在寻找的大部分内容都是修剪方法本身 - 特别是我正在循环 3 次(这感觉很脏)。我觉得如果我只是一个更聪明的触摸(即使没有C的高级知识),这本来可以更清洁。
解决方案
没有理由有两个缓冲区,你可以修改输入线
int trim(char line[])
{
int len = 0;
for (len = 0; line[len] != 0; ++len)
;
while (len > 0 &&
line[len-1] == ' ' && line[len-1] == '\t' && line[len-1] == '\n')
line[--len] = 0;
return len;
}
通过返回行长度,可以通过测试非零长度行来消除空行
if (trim(line) != 0)
printf("%s\n", line);
编辑:假设ASCII编码,你可以使while循环更简单。
while (len > 0 && line[len-1] <= ' ')
line[--len] = 0;
其他提示
如果你坚持第1章,那对我来说看起来很不错。以下是我从代码审查的角度推荐的内容:
在C中检查相等性时,始终将常量放在第一位
if (1 == myvar)
这样你就不会意外地做这样的事情:
if (myvar = 1)
你无法在C#中使用它,但它在C中编译得很好并且可能是一个真正的调试恶魔。
trim()太大了。
我认为你需要的是一个strlen-ish函数(继续把它写成stringlength(const char * s))。
然后你需要一个名为int scanback(const char * s,const char * matches,int start)的函数,该函数从start开始,只要在匹配的s id中扫描的字符转到z,返回找到匹配项的最后一个索引。
然后你需要一个名为int scanfront(const char * s,const char * matches)的函数,它从0开始并向前扫描,只要在s处扫描的字符包含在匹配中,返回匹配的最后一个索引找到了。
然后你需要一个名为int charinstring(char c,const char * s)的函数,如果c包含在s中,则返回非零值,否则返回0。
你应该可以根据这些来编写修剪。
个人对于while构造:
我更喜欢以下内容:
while( (ret[i] = line[i]) )
i++;
为:
while ((ret[i] = line[i]) != '\0')
++i;
他们都检查!= 0但第一个看起来更清洁。如果char是0,那么循环体将执行,否则它将突破循环。
对于'for'语句,虽然在语法上有效,但我发现以下内容:
for ( ; i >= 0; --i)
对我来说看起来很奇怪,确实是潜在错误的潜在噩梦解决方案。如果我正在审查这段代码,那就像是一个发光的红色警告。通常,您希望使用for循环来迭代已知次数,否则cosider一会儿循环。 (一如既往有规则的例外,但我发现这通常是正确的)。以上陈述可能成为:
while (i)
{
if (ret[i] == ' ' || ret[i] == '\t')
{
ret[i--] = '\0';
}
else if (ret[i] != '\0' && ret[i] != '\r' && ret[i] != '\n')
{
break;
}
}
首先:
int main(void)
你知道main()的参数。他们什么都没有。 (或者argc&amp; argv,但我认为这不是第1章的材料。)
Stylewise,你可能想尝试K&amp; R风格的支架。它们在垂直空间上更容易:
void trim(char line[], char ret[])
{
int i = 0;
while ((ret[i] = line[i]) != '\0')
++i;
if (i == 1) { // Special case to remove entirely blank line
ret[0] = '\0';
return;
}
for (; i>=0; --i) { //continue backwards from the end of the line
if ((ret[i] == ' ') || (ret[i] == '\t')) //remove trailing whitespace
ret[i] = '\0';
else if ((ret[i] != '\0') && (ret[i] != '\r') && (ret[i] != '\n')) //...until we hit a word character
break;
}
for (i=0; i<MAXLINE-1; ++i) { //-1 because we might need to add a character to the line
if (ret[i] == '\n') //break on newline
break;
if (ret[i] == '\0') { //line doesn't have a \n -- add it
ret[i] = '\n';
ret[i+1] = '\0';
break;
}
}
}
(还添加了评论并修复了一个错误。)
一个很大的问题是使用MAXLINE常量 - main()专门用于行和 out 变量; trim(),只对它们起作用,不需要使用常量。您应该将大小作为参数传递,就像在getline()中一样。
就个人而言,我会把这样的代码放在:
ret[i] != '\0' && ret[i] != '\r' && ret[i] != '\n'
进入一个单独的函数(甚至是一个定义宏)
- trim应该只使用1个缓冲区(如@Ferruccio所说)。
- 修剪需要打破,正如@plinth所说
- trim不需要返回任何值(如果要检查空字符串,测试行[0] == 0)
- 获取额外的C风味,使用指针而不是索引 醇>
-go到行尾(终止0; - 虽然不在行的开头,当前字符是空格,将其替换为0。 - 关闭一个字符
char *findEndOfString(char *string) {
while (*string) ++string;
return string; // string is now pointing to the terminating 0
}
void trim(char *line) {
char *end = findEndOfString(line);
// note that we start at the first real character, not at terminating 0
for (end = end-1; end >= line; end--) {
if (isWhitespace(*end)) *end = 0;
else return;
}
}
做同样事情的另一个例子。通过使用C99特定的东西做了一些小的违规。在K&amp; R中找不到。还使用了assert()函数,它是starndard库的一部分,但可能没有在K&amp; R的第一章中介绍。
#include <stdbool.h> /* needed when using bool, false and true. C99 specific. */
#include <assert.h> /* needed for calling assert() */
typedef enum {
TAB = '\t',
BLANK = ' '
} WhiteSpace_e;
typedef enum {
ENDOFLINE = '\n',
ENDOFSTRING = '\0'
} EndofLine_e;
bool isWhiteSpace(
char character
) {
if ( (BLANK == character) || (TAB == character ) ) {
return true;
} else {
return false;
}
}
bool isEndOfLine(
char character
) {
if ( (ENDOFLINE == character) || (ENDOFSTRING == character ) ) {
return true;
} else {
return false;
}
}
/* remove blanks and tabs (i.e. whitespace) from line-string */
void removeWhiteSpace(
char string[]
) {
int i;
int indexOutput;
/* copy all non-whitespace character in sequential order from the first to the last.
whitespace characters are not copied */
i = 0;
indexOutput = 0;
while ( false == isEndOfLine( string[i] ) ) {
if ( false == isWhiteSpace( string[i] ) ) {
assert ( indexOutput <= i );
string[ indexOutput ] = string[ i ];
indexOutput++;
}
i++; /* proceed to next character in the input string */
}
assert( isEndOfLine( string[ i ] ) );
string[ indexOutput ] = ENDOFSTRING;
}
这是我在不知道第1章或K&amp; R.我假设指针?
#include "stdio.h"
size_t StrLen(const char* s)
{
// this will crash if you pass NULL
size_t l = 0;
const char* p = s;
while(*p)
{
l++;
++p;
}
return l;
}
const char* Trim(char* s)
{
size_t l = StrLen(s);
if(l < 1)
return 0;
char* end = s + l -1;
while(s < end && (*end == ' ' || *end == '\t'))
{
*end = 0;
--end;
}
return s;
}
int Getline(char* out, size_t max)
{
size_t l = 0;
char c;
while(c = getchar())
{
++l;
if(c == EOF) return 0;
if(c == '\n') break;
if(l < max-1)
{
out[l-1] = c;
out[l] = 0;
}
}
return l;
}
#define MAXLINE 1024
int main (int argc, char * const argv[])
{
char line[MAXLINE];
while (Getline(line, MAXLINE) > 0)
{
const char* trimmed = Trim(line);
if(trimmed)
printf("|%s|\n", trimmed);
line[0] = 0;
}
return 0;
}