I'm working on the K&R book. I've read farther ahead than I've done exercises, mostly for lack of time. I'm catching up, and have done almost all the exercises from chapter 1, which is the tutorial.
My issue was exercise 1-18. The exercise is to:
Write a program to remove trailing blanks and tabs from line of input, and to delete entirely blank lines
My code (below) does that, and works. My problem with it is the trim method I implemented. It feels ... wrong ... somehow. Like if I saw similar code in C# in a code review, I'd probably go nuts. (C# being one of my specialties.)
Can anyone offer some advice on cleaning this up -- with the catch that said advice has to only use knowledge from Chapter 1 of K & R. (I know there are a zillion ways to clean this up using the full C library; we're just talking Chapter 1 and basic stdio.h here.) Also, when giving the advice, can you explain why it will help? (I am, after all, trying to learn! And who better to learn from than the experts here?)
#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;
}
}
}
EDIT: I appreciate all the helpful tips I'm seeing here. I would like to remind folks that I'm still a n00b with C, and specifically haven't gotten up to pointers yet. (Remember the bit about Ch.1 of K&R -- Ch.1 doesn't do pointers.) I "kinda" get some of those solutions, but they're still a touch advanced for where I'm at ...
And most of what I'm looking for is the trim method itself -- specifically the fact that I'm looping through 3 times (which feels so dirty). I feel like if I were just a touch more clever (even without the advanced knowledge of C), this could have been cleaner.
If you are sticking with chapter 1, that looks pretty good to me. Here's what I would recommend from a code-review standpoint:
When checking equality in C, always put the constant first
if (1 == myvar)
That way you will never accidentally do something like this:
if (myvar = 1)
You can't get away with that in C#, but it compiles fine in C and can be a real devil to debug.
There is no reason to have two buffers, you can trim the input line in place
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;
}
By returning the line length, you can eliminate blank lines by testing for non-zero length lines
if (trim(line) != 0)
printf("%s\n", line);
EDIT: You can make the while loop even simpler, assuming ASCII encoding.
while (len > 0 && line[len-1] <= ' ')
line[--len] = 0;
trim() is too big.
What I think you need is a strlen-ish function (go ahead and write it int stringlength(const char *s)).
Then you need a function called int scanback(const char *s, const char *matches, int start) which starts at start, goes down to z as long as the character being scanned at s id contained in matches, return the last index where a match is found.
Then you need a function called int scanfront(const char *s, const char *matches) which starts at 0 and scans forward as long as the character being scanned at s is contained in matches, returning the last index where a match is found.
Then you need a function called int charinstring(char c, const char *s) which returns non-zero if c is contained in s, 0 otherwise.
You should be able to write trim in terms of these.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With