8
\$\begingroup\$

The following program displays a menu, and prompts the user to choose an option. Options include to add a book, delete a book, view all books, and to exit the program. (Note that the actual code to implement the database functions will be added later.)

The program files were compiled with no error messages or warnings using the following command line...

$gcc -std=c99 -Wall -Wextra -Wpedantic main.c screen.c input.c database.c -o books

I would like your feedback on any potential issues, along with any improvements that can be made to the code.

Here are the files:

main.c

#include <stdio.h>
#include <stdlib.h>
#include "struct.h"
#include "screen.h"
#include "input.h"
#include "database.h"

/* display main menu and prompt user to choose menu option */
int main(int argc, char *argv[])
{
    if (argc != 1)
    {
        fprintf(stderr, "Usage:  %s\n", argv[0]);
        exit(EXIT_FAILURE);
    }

    /* name of file to store data in */
    const char dataFile[] = "database.bin";

    /* array of structs containing menu options (number, text, and function) */
    menuStruct menuArray[] = {
        { 1, "Add book", addBook },
        { 2, "Delete book", deleteBook },
        { 3, "View all books", viewAllBooks }
    };

    /* number of menu items */
    int menuArrayLength = sizeof(menuArray)/sizeof(menuArray[0]);

    int menuChoice;
    void(*menuFunction)(const char fname[]);

    /* prompt user to choose menu option */
    while (1) 
    {
        clearScreen();
        displayMainMenu(menuArray, menuArrayLength);
        menuChoice = getMenuChoice();
        if (menuChoice == (menuArrayLength + 1))
        {
            clearScreen();
            exit(EXIT_SUCCESS);
        }
        menuFunction = getMenuFunction(menuChoice, menuArray, menuArrayLength);
        if (menuFunction != NULL)
        {
            menuFunction(dataFile);
        }
        else
        {
            fprintf(stderr, "\n  Invalid entry, press ENTER to continue");
            flushInput();
        }
    }

    return 0;
}

struct.h

#ifndef STRUCT_H
    #define STRUCT_H

    #define MAXSTRLEN 50 /* max length for string */

    /* struct to hold a menu choice (number, text, and function) */
    typedef struct menuStruct
    {
        int menuChoice;
        char menuText[MAXSTRLEN];
        void (*menuFunction)(const char *fname);
    } menuStruct;

    /* struct to hold details of a book */
    typedef struct bookStruct
    {
        char id[MAXSTRLEN];
        char title[MAXSTRLEN];
        char author[MAXSTRLEN];
        double price;
    } bookStruct;

#endif

screen.h

#ifndef SCREEN_H
    #define SCREEN_H

    #include "struct.h"

    /* displayMainMenu(): display the main menu using menuArray where each element in the array
    is a struct containing a menu choice (number, text, and function), except the exit option */
    void displayMainMenu(const menuStruct menuArray[], const int menuArrayLength);

    /* displayAddBookHeader(): display the header for adding a book */
    void displayAddBookHeader();

    /* displayDeleteBookHeader(): display the header for deleting a book */
    void displayDeleteBookHeader();

    /* displayViewAllBooksHeader(): display the header for viewing all books */
    void displayViewAllBooksHeader();

    /* clearScreen():  clear the screen */
    void clearScreen();

#endif

screen.c

#include <stdio.h>
#include "screen.h"

void displayMainMenu(const menuStruct menuArray[], const int menuArrayLength)
{   
    printf("                                                                             \n"
           "=============================================================================\n"
           "                 Database for Book Collection                                \n"
           "                        Menu Choices                                         \n"
           "=============================================================================\n");

    for (int i = 0; i < menuArrayLength; i++)
    {
        printf("\n  %d. %s", menuArray[i].menuChoice, menuArray[i].menuText);
    }

    printf("\n  %d. Exit\n", menuArrayLength + 1); /* exit option for main menu */
}

void displayAddBookHeader()
{
    printf("                                                                             \n"
           "=============================================================================\n"
           "                 Database for Book Collection                                \n"
           "                         Add Book                                            \n"
           "=============================================================================\n");
}


void displayDeleteBookHeader()
{
    printf("                                                                             \n"
           "=============================================================================\n"
           "                 Database for Book Collection                                \n"
           "                        Delete Book                                          \n"
           "=============================================================================\n");
}

void displayViewAllBooksHeader()
{
    printf("                                                                             \n"
           "=============================================================================\n"
           "                 Database for Book Collection                                \n"
           "                      View All Books                                         \n"
           "=============================================================================\n");

}

void clearScreen()
{
    printf("\x1b[2J\x1b[1;1H");
}

input.h

#ifndef INPUT_H
    #define INPUT_H

    #include "struct.h"

    /* getMenuChoice(): prompt user to enter menu choice and return choice */
    int getMenuChoice();

    /* getMenuFunction(): search each struct within menuArray for option matching menuChoice and return corresponding function */
    void(*getMenuFunction(const int menuChoice, const menuStruct menuArray[], const int size))(const char fname[]);

    /* flush input from buffer */
    void flushInput();

#endif

input.c

#include <stdio.h>
#include <string.h>
#include "input.h"

#define MAXSTRLEN 50

int getMenuChoice()
{
    printf("\n  Enter Choice: ");

    char userInput[MAXSTRLEN], junk[MAXSTRLEN];
    int menuChoice;


    fgets(userInput, sizeof(userInput), stdin);
    if (strchr(userInput, '\n') == NULL)
    {
        flushInput();
        return 0;
    }
    if (sscanf(userInput, "%d%[^\n]", &menuChoice, junk) != 1)
    {
        return 0;
    }

    return menuChoice;
}

void(*getMenuFunction(const int menuChoice, const menuStruct menuArray[], const int size))(const char fname[])
{
    for (int i = 0; i < size; i++)
    {
        if (menuChoice == menuArray[i].menuChoice)
            return menuArray[i].menuFunction;
    }

    return NULL;
}

void flushInput()
{
    int c;
    while ((c = getchar()) != '\n' && c != EOF)
    {
        /* skip it */;
    }
}

database.h

#ifndef DATABASE_H
    #define DATABASE_H

    void addBook(const char fname[]);
    void deleteBook(const char fname[]);
    void viewAllBooks(const char fname[]);

#endif

database.c

#include <stdio.h>
#include <stdlib.h>
#include "input.h"
#include "database.h"

void addBook(const char fname[])
{
    printf("\n  Inside addBook(): fname = %s, press ENTER to continue", fname);
    flushInput();
}

void deleteBook(const char fname[])
{
    printf("\n  Inside deleteBook(): fname = %s, press ENTER to continue", fname);
    flushInput();
}

void viewAllBooks(const char fname[])
{
    printf("\n  Inside viewAllBooks(): fname = %s, press ENTER to continue", fname);
    flushInput();
}
\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

Be wary of this:

void clearScreen()
{
    printf("\x1b[2J\x1b[1;1H");
}

Whilst many terminals support the ANSI command set, not all do, so hard-coding this escape code will limit the program's flexibility.

There are libraries (such as Curses) that help with this, but that's likely overkill for this purpose. The pragmatic approach here is to outsource to the standard command using system() - on POSIX systems, you'll just invoke clear, for example.

That said, I'd advise against clearing screen repeatedly - it makes it much harder for the user to go back and review the actions that have been performed.

\$\endgroup\$
2
  • \$\begingroup\$ I thought clearing the screen would present things in a more "clean" fashion, but I agree with your point. I'll avoid it completely. Toby, thank you very much for your help. Cheers! \$\endgroup\$
    – Domenic
    Commented Feb 13, 2019 at 20:10
  • \$\begingroup\$ system() is horrible from a performance standpoint, and it's not portable either. I would say it's even less portable than the ANSI sequence. But I fully agree with not clearing the screen to begin with :) \$\endgroup\$
    – G. Sliepen
    Commented Oct 17, 2020 at 21:04

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.