Proper input method to take char string input in C

How would I best take input in a program that asks the user to enter a student name separated by a space and then the student score, EX:

zach 85

Because of the null terminator, will there be two enters that i will have to account for? I'm already using two scanfs in my program.

int main()
{
   const int row = 5;
   const int col = 10;
   int i;
   char names[row][col];
   int scores[row];
   int total;

   // Read names and scores from standard input into names and scores array  
   // Assume that each line has a first name and a score separated by a space
   // Assume that there are five such lines in the input
   // 10 points

   for(int i = 0; i<row; i++)
   {
       printf("Enter student name: \n");
       scanf("%s",&names);
       scanf("%s", &scores);
   }
   // Print the names and scores
   // The print out should look the same as the input source
   // 10 points

   for(int i = 0; i<row; i++)
   {
       printf( "%s %d \n", names[i]  /*, scores[i] */ );       
   }
}
728x90

3 Answers Proper input method to take char string input in C

first look scores and names are is defined as arrays so

   scanf("%s",names[i]);
   scanf("%s", &scores[i]);

second scores are int so "%d" instead of "%s"

   scanf("%s",names[i]);
   scanf("%d", &scores[i]);

third, you've already defined int i; so the one in for loop is not really making any sense, do it only at a single place.

fourth, if your input names contains spaces scanf is not the right option

from manual pages of scanf

Each conversion specification in format begins with either the character '%' or the character sequence "%n$" (see below for the distinction) followed by:

   ·      An  optional decimal integer which specifies the maximum field width.  Reading of characters stops either when this maximum is reached or when a non‐
          matching character is found, whichever happens first.  Most conversions discard initial white space characters (the exceptions are noted below),  and
          these discarded characters don't count toward the maximum field width.  String input conversions store a terminating null byte ('\0') to mark the end
          of the input; the maximum field width does not include this terminator.

few more points you might want to restrict the number of characters to be scanned during scanf to certain limit example "%20s"

5 months ago

You are almost there. However you have to make sure you do things cleanly Let's just do this step by step:

Step 1, get ONE name and ONE score

#include <stdio.h>

#define MAX_NAME_LENGTH 30

int main() {
      char name[MAX_NAME_LENGTH+1]; /* an array of characters making up ONE name (+1 for terminating NUL char in case of max-length name) */
      unsigned int score; /* a score */

      scanf("%30s", name); /* scan a name (assuming NO spaces in the name)*/
      /* also notice that name has no & in front of it because it already IS a pointer to the array name[MAX_NAME_LENGTH] */

      scanf("%u", &score);

      printf("%s scored %u in the test\n", name, score);
      return 0;
}

(See it run at http://tpcg.io/jS3woS)

STEP 2 -- Iterate to get multiple scores

Now let's read in 5 pairs and then print out 5 pairs.

#include <stdio.h>

#define MAX_NAME_LENGTH 30
/* i called rows iterations here just to provide contrast */
/* you can call them ROWS if you want but it then creates a confusion about name length */


#define ITERATIONS 5 

int main() {
      char name[ITERATIONS][MAX_NAME_LENGTH+1]; /* an array of names where each name is MAX_NAME_LENGTH long (notice the order) */
      unsigned int score[ITERATIONS]; /* score */

      int i; 

      for(i = 0; i < ITERATIONS; i++ ) {
           scanf("%30s", name[i]); /* scan a name (assuming NO spaces in the name)*/
           /* notice that name[i] has no & in front of it because name[i] is the pointer to the i-th row */

           scanf("%u", &score[i]);
      }

      for(i = 0; i < ITERATIONS; i++ ) {
           printf("%s scored %u in the test\n", name[i], score[i]);
      }

      return 0;
}

See it in action here (http://tpcg.io/iTj4ag)

4 months ago

Your type for scores (int scores[row];) does not correspond to your attempt to read scores with scanf (scanf("%s", &scores);). The "%s" conversion specifier is for converting whitespace separated strings, not integers. The "%d" conversion specifier is provided for integer conversions.

Before looking at specifics. Any time you have a coding task of coordinating differing types of data as a single unit, (e.g. student each with a name (char*) and a score (int), you should be thinking about using a struct containing the name and score as members. That way there is only a single array of struct needed rather than trying to coordinate multiple arrays of differing types to contain the same information.

Also, don't skimp on buffer size for character arrays. You would rather be 10,000 characters too long than 1-character too short. If you think your maximum name is 10-16 character, use a 64-char (or larger) buffer to insure you can read the entire line of data - eliminating the chance that a few stray characters typed could result in characters remaining unread in stdin.

A simple stuct is all that is needed. You can add a typedef for convenience (to avoid having to type struct name_of_struct for each declaration or parameter), e.g.

 #include <stdio.h>

#define ROW 5       /* if you need a constant, #define one (or more) */
#define COL 64

typedef struct {        /* delcare a simple struct with name/score */
    char name[COL];     /* (add a typedef for convenience)         */
    int score;
} student_t;

Now you have a structure that contains your student name and score as a single unit rather than two arrays, one char and one int you have to deal with.[1]

All that remains is declaring an array of student_t for use in your code, e.g.

int main (void) {

    int n = 0;      /* declare counter */
    student_t student[ROW] = {{ .name = "" }};  /* array of struct */

    puts ("\n[note; press Enter alone to end input]\n");

With the array of struct declared, you can turn to your input handling. A robust way of handling input is to loop continually, validating that you receive the input you expects on each iteration, handling any errors that arise (gracefully so that your code continues), and keeping track of the number of inputs made so that you can protect your array bounds and avoid invoking Undefined Behavior by writing beyond the end of your array.

You could begin your input loop, prompting and reading your line of input with fgets as mentioned in my comment. That has multiple advantages over attempting each input with scanf. Most notably because what remains unread in the input buffer (stdin here) doesn't depend on the conversion specifier used. The entire line (up to and including the trailing '\n') is extracted from the input buffer and place in buffer you give fgets to fill. You can also check if the user simply presses Enter which you can use to conveniently indicate end of input, e.g.

    for (;;) {  /* loop until all input given or empty line entered */
        char buf[COL];              /* declare buffer to hold line */

        fputs ("Enter student name: ", stdout); /* prompt */
        if (!fgets (buf, sizeof buf, stdin))    /* read/validate line */
            break;
        if (*buf == '\n')   /* check for empty line */
            break;

(note you can (and should) additionally check the string length of the buffer filled to (1) check that the last character read is '\n' ensuring the complete line was read; and (2) if the last char isn't '\n' checking whether the length is equal to the maximum length (-1) indicating that characters may be left unread. (that is left to you)

Now that you know you have a line of input and it's not empty, you can call sscanf to parse the line into the name and score for each student while handling any failure in conversion gracefully, e.g.

        /* parse line into name and score - validate! */
        if (sscanf (buf, "%63s %d", student[n].name, &student[n].score) != 2)
        {   /* handle error */
            fputs ("  error: invalid input, conversion failed.\n", stderr);
            continue;
        }
        n++;                /* increment row count - after validating */
        if (n == ROW) {     /* check if array full (protect array bounds) */
            fputs ("\narray full - input complete.\n", stdout);
            break;
        }
    }

If you are paying attention, you can see one of the benefits of using the fgets and sscanf approach from a robustness standpoint. You get independent validations of (1) the read of user input; and (2) the separation (or parsing) of that input into the needed values. A failure in either case can be handled appropriately.

Putting all the pieces together into a short program, you could do the following:

#include <stdio.h>

#define ROW 5       /* if you need a constant, #define one (or more) */
#define COL 64

typedef struct {        /* delcare a simple struct with name/score */
    char name[COL];     /* (add a typedef for convenience)         */
    int score;
} student_t;

int main (void) {

    int n = 0;      /* declare counter */
    student_t student[ROW] = {{ .name = "" }};  /* array of struct */

    puts ("\n[note; press Enter alone to end input]\n");

    for (;;) {  /* loop until all input given or empty line entered */
        char buf[COL];              /* declare buffer to hold line */

        fputs ("Enter student name: ", stdout); /* prompt */
        if (!fgets (buf, sizeof buf, stdin))    /* read/validate line */
            break;
        if (*buf == '\n')   /* check for empty line */
            break;
        /* parse line into name and score - validate! */
        if (sscanf (buf, "%63s %d", student[n].name, &student[n].score) != 2)
        {   /* handle error */
            fputs ("  error: invalid input, conversion failed.\n", stderr);
            continue;
        }
        n++;                /* increment row count - after validating */
        if (n == ROW) {     /* check if array full (protect array bounds) */
            fputs ("\narray full - input complete.\n", stdout);
            break;
        }
    }

    for (int i = 0; i < n; i++) /* output stored names and values */
        printf ("%-16s %3d\n", student[i].name, student[i].score);
}

Example Use/Output

When ever you write an input routine -- Go Try and Break It!. Intentionally enter invalid data. If your input routine breaks -- Go Fix It!. In the code as noted the only check left for you to implement and handle is input greater than COL number of characters (e.g. the cat steps on the keyboard). Exercise your input:

$ ./bin/studentnamescore

[note; press Enter alone to end input]

Enter student name: zach 85
Enter student name: the dummy that didn't pass
  error: invalid input, conversion failed.
Enter student name: kevin 96
Enter student name: nick 56
Enter student name: martha88
  error: invalid input, conversion failed.
Enter student name: martha 88
Enter student name: tim 77

array full - input complete.
zach              85
kevin             96
nick              56
martha            88
tim               77

While you can use two separate arrays, a single array of struct is a much better approach. Look things over and let me know if you have further questions.

footnotes:

  1. Be aware that POSIX specifies that names ending with suffix _t are reserved for its use. (size_t, uint64_t, etc...) Also be aware you will see that suffix used in common practice. So check before you make up your own (but we no there is no POSIX student_t type).

5 months ago