简体   繁体   中英

K&R C programming book exercise 1-18

I'm towards solving the exercise, but just half way, I find it so weird and cannot figure it out, the next is the code snippet, I know it is steps away from finished, but I think it's worth figuring out how come the result is like this!

#define MAXLINE 1000
int my_getline(char line[], int maxline);
int main(){
   int len;
   char line[MAXLINE];/* current input line */
   int j;
   while((len = my_getline(line, MAXLINE)) > 0 ){
        for (j = 0 ; j <= len-1 && line[j] != ' ' && line[j] != '\t'; j++){
                 printf("%c", line[j]);
        }
   }
   return 0;

}
int my_getline(char s[], int limit){
    int c,i;
    for (i = 0 ; i < limit -1 && (c = getchar()) != EOF && c != '\n'; i++)
        s[i] = c;
    if (c == '\n'){
        s[i] = c;
        ++i;
    }
    s[i] = '\0';
    return i;
}

It will be compiled successfully with cc: cc code.c. But the following result is subtle!

Iit is working for lines without \t and blanks:

hello
hello

but it does not work for the line in the picture:

I typed hel[blank][blank]lo[blank]\n: Could anyone help me a bit? many thanks!

在此处输入图像描述

The problem is that you are stuck because you try to get a full line and process it. It's better to process (and the problems of K&R are mostly this way all) the input char by char. If you don't print characters as you detect spaces, but save them in a buffer, and print them if there's a nontab character when you read one past the accumulated ones, then everything works fine. This is also true for new lines. You should keep the last (nonblank) character (as blanks are eliminated before a new line) read to see if it is a newline... in that case, the new line you have just read is not printed, and so, sequences of two or more newlines are only printed the first. This is a sample complete program that does this:

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

#define F(_f) __FILE__":%d:%s: "_f, __LINE__, __func__

int main()
{
    char buffer[1000];
    int bs = 0;
    int last_char = '\n', c;
    unsigned long
        eliminated_spntabs = 0,
        eliminated_nl = 0;

    while((c = getchar()) != EOF) {
        switch(c) {

        case '\t': case ' ':
            if (bs >= sizeof buffer) {
                /* full buffer, cannot fit more blanks/tabs */
                fprintf(stderr,
                    "we can only hold upto %d blanks/tabs in"
                    " sequence\n", (int)sizeof buffer);
                exit(1);
            }
            /* add to buffer */
            buffer[bs++] = c;
            break;

        default: /* normal char */
            /* print intermediate spaces, if any */
            if (bs > 0) {
                printf("%.*s", bs, buffer);
                bs = 0;
            }
            /* and the read char */
            putchar(c);
            /* we only update last_char on nonspaces and
             * \n's. */
            last_char = c;
            break;

        case '\n':
            /* eliminate the accumulated spaces */
            if (bs > 0) {
                eliminated_spntabs += bs;

                /* this trace to stderr to indicate the number of 
                 * spaces/tabs that have been eliminated.
                 * Erase it when you are happy with the code. */
                fprintf(stderr, "<<%d>>", bs);
                bs = 0;
            }
            if (last_char != '\n') {
                putchar('\n');
            } else {
                eliminated_nl++;
            }
            last_char = '\n';
            break;

        } /* switch */
    } /* while */

    fprintf(stderr,
            F("Eliminated tabs: %lu\n"),
            eliminated_spntabs);
    fprintf(stderr,
            F("Eliminated newl: %lu\n"),
            eliminated_nl);

    return 0;
}

The program prints (on stderr to not interfer the normal output) the number of eliminated tabs/spaces surrounded by << and >> . And also prints at the end the full number of eliminated blank lines and the number of no content lines eliminated. A line full of spaces (only) is considered a blank line, and so it is eliminated. In case you don't want blank lines with spaces (they will be eliminated anyway, as they are at the end) to be eliminated, just assign spaces/tabs seen to the variable last_char .

In addition to the good answer by @LuisColorado , there a several ways you can look at your problem that may simplify things for you. Rather than using multiple conditionals to check for c == ' ' and c == '\t' and c == '\n' , include ctype.h and use the isspace() macro to determine if the current character is whitespace. It is a much clearer way to go.

When looking at the return. POSIX getline uses ssize_t as the signed return allowing it to return -1 on error. While the type is a bit of an awkward type, you can do the same with long (or int64_t for a guaranteed exact width).

Where I am a bit unclear on what you are trying to accomplish, you appear to be wanting to read the line of input and ignore whitespace. (while POSIX getline() and fgets() both include the trailing '\n' in the count, it may be more advantageous to read (consume) the '\n' but not include that in the buffer filled by my_getline() -- up to you. So from your example output provided above it looks like you want both "hello" and "hel lo " , to be read and stored as "hello" .

If that is the case, then you can simplify your function as:

long my_getline (char *s, size_t limit)
{
    int c = 0;
    long n = 0;
    
    while ((size_t)n + 1 < limit && (c = getchar()) != EOF && c != '\n') {
        if (!isspace (c))
            s[n++] = c;
    }
    s[n] = 0;
    
    return n ? n : c == EOF ? -1 : 0;
}

The return statement is just the combination of two ternary clauses which will return the number of characters read, including 0 if the line was all whitespace, or it will return -1 if EOF is encountered before a character is read. (a ternary simply being a shorthand if... else... statement in the form test? if_true: if_false )

Also note the choice made above for handling the '\n' was to read the '\n' but not include it in the buffer filled. You can change that by simply removing the && c != '\n' from the while() test and including it as a simple if (c == '\n') break; at the very end of the while loop.

Putting together a short example, you would have:

#include <stdio.h>
#include <ctype.h>

#define MAXC 1024

long my_getline (char *s, size_t limit)
{
    int c = 0;
    long n = 0;
    
    while ((size_t)n + 1 < limit && (c = getchar()) != EOF && c != '\n') {
        if (!isspace (c))
            s[n++] = c;
    }
    s[n] = 0;
    
    return n ? n : c == EOF ? -1 : 0;
}

int main (void) {
    
    char str[MAXC];
    long nchr = 0;
    
    fputs ("enter line: ", stdout);
    if ((nchr = my_getline (str, MAXC)) != -1)
        printf ("%s  (%ld chars)\n", str, nchr);
    else
        puts ("EOF before any valid input");
}

Example Use/Output

With your two input examples, "hello" and "hel lo " , you would have:

$ ./bin/my_getline
enter line: hello
hello  (5 chars)

Or with included whitespace:

$ ./bin/my_getline
enter line: hel  lo
hello  (5 chars)

Testing the error condition by pressing Ctrl + d (or Ctrl + z on windows):

$ ./bin/my_getline
enter line: EOF before any valid input

There are many ways to put these pieces together, this is just one possible solution. Look things over and let me know if you have further questions.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM