Originally Posted by
koplersky
I have a function which reads the name of a file using fgets, like this
I warmly recommend splitting the functionality into smaller functions:
Code:
/* Read a matrix using the specified file handle.
* Returns 0 if success, nonzero error code otherwise.
*/
int read_matrix(FILE *const in, struct matrix *const m)
{
int rows, cols, r, c;
/* First two integers define matrix rows and cols. */
if (fscanf(in, " %d %d", &rows, &cols) != 2)
return -1; /* Invalid file contents (not two integers). */
/* Bad dimensions? */
if (rows < 1 ||..cols < 1)
return -2; /* Bad matrix dimensions. */
/*
* TODO: Initialize and allocate matrix m.
*/
/* Read matrix elements in row-major order. Assume elements of double type. */
for (r = 0; r < rows; r++)
for (c = 0; c < cols; c++)
if (fscanf(in, " %lf", m->element[r * cols + c]) != 1)
return -3; /* Bad matrix data. */
/* Note: there may be extra data on this line in input.
* If there is only one matrix per file, it does not matter.
* Otherwise, you might consider skipping the rest of the line with
*
* while (1) {
* c = fgetc(in);
* if (c == '\n' || c == '\r' || c == EOF)
* break;
* if (!isspace(c))
* return -4; /* Extra data on line containing last element */
* }
*
* which consumes the rest of the line containing the last element,
* checking that there is nothing but whitespace.
* This way you also check that there is a newline or end-of-file
* after the last element, instead of garbage/extra data:
* 2 2 1 2 3 4
* 5
* would be accepted (as a 2-by-2 matrix, only the first line read,
* the 5 not read by this function), but
* 2 2 1 2 3 4 5
* would be rejected since there is a 5 following the matrix data.
*/
/* Success. */
return 0;
}
I would even write a small helper function to read a new line from standard input, removing leading and trailing whitespace:
Code:
#include <stdarg.h>
#include <stdio.h>
#include <ctype.h>
/* Read a line from standard input,
* printf()ing the parameters to standard error (unless NULL).
* Return NULL if end of input or read error.
*/
const char *prompt(const char *const msg, ...)
{
static char buffer[1024]; /* Static line buffer. */
char *line, *ends;
va_list args;
/* Prompt specified? */
if (msg) {
va_start(args, msg);
vfprintf(stderr, msg, args);
va_end(args);
}
/* Read a line of input. */
line = fgets(buffer, sizeof buffer, stdin);
/* No more input? */
if (!line)
return NULL;
/* Trim leading whitespace. */
while (isspace(*line))
line++;
/* Find the end of the line, */
ends = line + strlen(line);
/* and backtrack whitespace characters. */
while (ends > line && isspace(ends[-1]))
ends--;
/* Trim the line. */
*ends = '\0';
/* Because buffer has static storage,
* it exists between calls. New calls will
* overwrite the previous value. */
return line;
}
Because it uses variable argument lists (stdargs; ... in function parameter list, va_start(), va_end(), vfprintf()), you can supply it with NULL, or with same parameters you'd supply to printf(), to print out the prompt.
For a menu, I'd write a small function that would try to parse the menu selections, and nonnegative integer matching the input, or -1 if unrecognized:
Code:
int option(const char *const line)
{
/* NULL line? */
if (!line)
return -1;
/* Check if it is a single nonnegative integer? */
{
int value;
char dummy;
if (sscanf(line, " %d %c", &value, &dummy) == 1 && value >= 0)
return value; /* Yes, return it. */
}
/* Perhaps a word? "one" == "1": */
if (!strcmp(line, "one"))
return 1;
/* "quit" == "exit" == "0": */
if (!strcmp(line, "quit") || !strcmp(line, "exit"))
return 0;
/* Not recognized. */
return -1;
}
The important thing is you can write and test each of these smaller functions separately. That way there are less places where you might have errors or bugs, so writing the code (and reading it later!) is much easier.
I think you can see how to use lineptr = prompt("Please select ...:\n") to prompt the user for a menu option, then option(lineptr) to check if that option is valid (and if so, which one it is).
To prompt the user for a matrix name, you'd again use lineptr = prompt("Matrix file name?\n") , then handle = fopen(lineptr, "r"), and if successful, result = read_matrix(handle, matrixptr), followed by fclose(handle) and checking whether a suitable matrix was read (result == 0, and matrixptr is of suitable dimensions).