Планировщик событий в C

Одно из моих университетских заданий попросило нас создать программу, используя struct, чтобы создать простой планировщик событий. Эта программа предназначена только для одного дня, но не для нескольких дней.

Пример ввода /вывода

По нескольким причинам я решил создать .GIF, чтобы отобразить образец ввода /вывода:

Пример сохранения файла:

 8 30 dentist_appointment
14 0 pickup_friend_from_airport
17 0 buisness_meeting_at_the_office
20 30 dinner_reservation



  

Отказ от ответственности /уведомление: следующий код не копируется без надлежащего кредита. Он лицензируется под cc by-sa 3.0 с требуемым атрибутом .

 #include <stdio.h>
#include <string.h>
#include <stdbool.h>
#include <stdlib.h>
#include <ctype.h>

#define _MAX_EVENTS 10 // 10 Events Max
#define _MAX_DESCRIPTION 101 // 100 Character Description Max

typedef struct { // typedef a struct called event

    int hour; // Store the hour / HH
    int minute; // Store the minute / MM
    char description[_MAX_DESCRIPTION]; // Store the event description

} event;

// Print the menu selection
void printMenu() {

    puts("+------ SCHEDULER ------+\n"
        "|  1. New Event         |\n"
        "|  2. Delete Event      |\n"
        "|  3. Display Schedule  |\n"
        "|  4. Save Schedule     |\n"
        "|  5. Load Schedule     |\n"
        "|  6. Exit              |\n"
        "+-----------------------+\n");

}

// Return true if an event is NULL, false otherwise
bool isNull(const event *e) { return e == NULL; }

// Allocate memory for and initialize an event
event *initEvent() {
    event *e = (event*)malloc(sizeof(event));

    e->hour = 0;
    e->minute = 0;
    strcpy(e->description, "");

    return e;
}

// Take user input until value is between min and max inclusive, return the input
int inputRange(const int min, const int max) {

    int input = 0;
    char temp[21];
    char *prompt = "| Enter a number between %d and %d: ";

    printf(prompt, min, max);

    fgets(temp, 21, stdin);
    input = atoi(temp);

    while (input > max || input < min) { // Data validation
        printf(prompt, min, max);
        fgets(temp, 21, stdin);
        input = atoi(temp);
    }

    return input;

}

// Setup a new event with user input and return a pointer to the same event
event* newEvent(event *e) {

    if (isNull(e)) { // If e is NULL
        e = initEvent(); // Initialize it
    }

    char *seperator = "+--------------------------------+";

    printf("\n%s\n|           NEW EVENT            |\n%s\n\n", seperator, seperator);

    puts("+---------- EVENT TIME ----------+");

    e->hour = inputRange(0, 23);
    e->minute = inputRange(0, 59);

    puts(seperator);

    puts("\n+--- EVENT DESCRIPTION ---+");

    printf("%s", "| Enter a description: ");

    fgets(e->description, _MAX_DESCRIPTION, stdin);

    puts("+-------------------------+\n");

    puts("| Event successfully added.\n");

    return e;

}

// Add an event to an event list at a specified index
void addEventAtIndex(event list[], const event e, const int i) {

    if (isNull(&e)) { // if our event is NULL, return
        return;
    }

    list[i].hour = e.hour;
    list[i].minute = e.minute;
    strcpy(list[i].description, e.description);

}

// Insertion sort by swapping struct members
void sort(event list[], const int size) {

    for (int i = 1; i < size; i++) {
        for (int j = i; j > 0 && (list[j - 1].hour > list[j].hour || (list[j - 1].hour == list[j].hour && list[j - 1].minute > list[j].minute)); j--) {
            int hourJ = list[j].hour;
            int minuteJ = list[j].minute;
            char descriptionJ[_MAX_DESCRIPTION];
            strcpy(descriptionJ, list[j].description);

            int hourJMinus1 = list[j - 1].hour;
            int minuteJMinus1 = list[j - 1].minute;
            char descriptionJMinus1[_MAX_DESCRIPTION];
            strcpy(descriptionJMinus1, list[j - 1].description);

            list[j].hour = hourJMinus1;
            list[j].minute = minuteJMinus1;
            strcpy(list[j].description, descriptionJMinus1);

            list[j - 1].hour = hourJ;
            list[j - 1].minute = minuteJ;
            strcpy(list[j - 1].description, descriptionJ);
        }
    }

}

// Add an event to an event list by sorting it into position
void sortInsert(event list[], int *size, event e) {

    addEventAtIndex(list, e, *size); // Add event to the end of the list

    (*size)++; // Increment size

    // Insertion Sort
    sort(list, *size);

}

// Display an event in a readable format: [ID] HH:MM - DESCRIPTION
void printEvent(const event e) {

    char h1 = { (e.hour / 10) + '0' }; // Extract the first digit and convert to char (if any, else 0)
    char h2 = { (e.hour - (e.hour / 10) * 10) + '0' }; // Extract the second digit and convert to char

    char m1 = { (e.minute / 10) + '0' };
    char m2 = { (e.minute - (e.minute / 10) * 10) + '0' };

    printf("%c%c:%c%c - %s", h1, h2, m1, m2, e.description);

}

// Display all events in an event list
void printEventList(const event list[], const int size) {

    if (size == 0) {
        puts("\n| You have no events scheduled!\n");
        return;
    }

    char *seperator = "+--------------------------------+";

    printf("\n%s\n|          MY SCHEDULE           |\n%s\n\n", seperator, seperator);

    for (int i = 0; i < size; i++) {
        printf("| [%d] ", i);
        printEvent(list[i]);

    }

    putchar('\n');

}

// Delete an event from an event list
void deleteEvent(event list[], int *size) {

    if (*size == 0) { // If list is empty
        puts("\n| Event list already empty.\n");
        return;
    }

    char temp[21];
    int id;

    char *seperator = "\n+--------------------------------+";
    printf("%s\n|          DELETE EVENT          |%s\n\n", seperator, seperator);

    for (int i = 0; i < *size; i++) { // Display the event list so the user can see which event to delete
        printf("| [%d] ", i);
        printEvent(list[i]);
    }

    printf("%s", "\n| Enter the ID of an event to delete: ");

    fgets(temp, 21, stdin);
    id = atoi(temp);

    if (id > *size - 1) {
        printf("\n| No event located at %d\n", id);
        return;
    }

    printf("| Event [%d] deleted successfully.\n\n", id);

    // Set hour and minute to some trivially large value for sorting purposes
    list[id].hour = 99;
    list[id].minute = 99;
    strcpy(list[id].description, "");

    if (id != (*size - 1)) { // If the event to remove is already last, there's no need to sort it to last
        sort(list, *size);
    }

    (*size)--; // Decrement the size of the list

}

// Replace all spaces in a string with an underscore
char *encode(char *s) {

    for (int i = 0; i < strlen(s); i++) {
        if (s[i] == ' ') {
            s[i] = '_';
        }
    }

    return s;

}

// Replace all underscores in a string with an spaces
char *decode(char *s) {

    for (int i = 0; i < strlen(s); i++) {
        if (s[i] == '_') {
            s[i] = ' ';
        }
    }

    return s;

}

// Save an event list to file
void saveEventList(char *filename, event list[], int size) {

    FILE *f = fopen(filename, "w");

    if (f == NULL) { // If our file is NULL, return
        return;
    }

    for (int i = 0; i < size; i++) {
        fprintf(f, "%d %d %s", list[i].hour, list[i].minute, encode(list[i].description)); // Encode the description (replace spaces with underscores) before saving it into the file
    }

    printf("\n| %d %s successfully saved into \"%s\".\n\n", size, (size == 1) ? "event" : "events", filename); // Tenary expression to make sure we're grammatically correct

    fclose(f);

}

// Load an event list from file
void loadEventList(char *filename, event list[], int *size) {

    FILE *f = fopen(filename, "r");
    char temp[6 + _MAX_DESCRIPTION]; // ## ## MAX_DESCRIPTION_LENGTH

    if (f == NULL) {
        printf("\n| File \"%s\" not found.\n\n", filename);
        return;
    }

    *size = 0; // Set size to 0

    while (fgets(temp, sizeof(temp), f)) {

        char *word = strtok(temp, " "); // Use space as the token delimiter, get the first token (hour)
        list[*size].hour = atoi(word); // Store the token into the list

        word = strtok(NULL, " "); // Get the second token (minute)
        list[*size].minute = atoi(word);

        word = strtok(NULL, " "); // Get the third token (description)
        strcpy(list[*size].description, decode(word)); // Decode our word before copying it (remove underscores)

        (*size)++; // Increment size with each line (event) added

    }

    printf("\n| %d %s successfully loaded from \"%s\".\n", *size, (*size == 1) ? "event" : "events", filename);

    printEventList(list, *size); // Display the event list when finished, show the user what's been loaded

}

int main() {

    event list[_MAX_EVENTS];
    int index = 0; // Number of elements in list
    int selection = 0;
    char file[FILENAME_MAX];
    char response = 'Y';
    char temp[21];

    while (selection != 6) {

        printMenu(); // Print the menu

        printf("%s", "| Please select an option: "); // Prompt for input
        fgets(temp, 21, stdin);
        selection = atoi(temp); // Convert string input to int

        switch (selection) {

        case 1: // New Event
            if (index + 1 > _MAX_EVENTS) {
                printf("| You can only have %d active events at one time!\n\n", index);
                break;
            }
            sortInsert(list, &index, *newEvent(&list[index]));
            break;
        case 2: // Delete Event
            deleteEvent(list, &index);
            break;
        case 3: // Display Schedule
            printEventList(list, index);
            break;
        case 4: // Save Schedule
            if (index == 0) { // No events, don't save anything
                puts("| You have no events in your schedule!\n");
            }
            else {
                printf("%s", "| Please enter a \"filename.txt\": ");
                fgets(file, FILENAME_MAX, stdin);
                strtok(file, "\n"); // Strip newline from filename
                saveEventList(file, list, index);
            }
            break;
        case 5: // Load Schedule
            if (index > 0) {
                printf("%s", "| Are you sure you want to discard your current schedule? (Y/N): ");
                response = toupper(getc(stdin));
                char c;
                while (((c = getchar()) != '\n') && (c != EOF)); // Clear buffer, from getc();
            }
            if (response == 'Y') {
                printf("%s", "| Please enter a \"filename.txt\": ");
                fgets(file, FILENAME_MAX, stdin);
                strtok(file, "\n"); // Strip newline from filename
                loadEventList(file, list, &index);
            }
            break;
        case 6: // Exit Program
            puts("\n| Thank you!\n");
            break;
        default: // Error
            puts("\n| Error in selection\n");
            break;

        }

    }

}

Вопросы /проблемы

Надеюсь, я улучшился с момента моего последнего основного обзора кода . Я попытался сохранить комментарии полезными, так что скажите мне, если я все еще избыточен или вам нужно смягчить это. Есть несколько вещей, которые я имел в виду при написании этого, в том числе не повторял себя как лучше всего насколько это возможно.

Было ли форматирование ASCII при печати хорошей идеей? Или нужно просто оставить это меню? Каким образом я могу улучшить, что я не должен делать, и что мне делать?

36 голосов | спросил Insane 24 MarpmThu, 24 Mar 2016 14:27:43 +03002016-03-24T14:27:43+03:0002 2016, 14:27:43

5 ответов


37

Прежде всего : Хороший фрагмент кода, который вы поставили здесь. Хорошее начало! Я думаю, что вы сделали очень хорошо - общая структура прочная, и я думаю, что цель большинства переменных понятна читателю.

Вот некоторые вещи, которые я заметил, глядя на ваш код без особого порядка:

  • Метод isNull : Я думаю, что метод isNull не нужен, так как единственное, что он делает, это проверить, что-то равно NULL. Я думаю, что NULL так же ясно, как if (somePointer == NULL). Вы даже можете использовать if (isNull(somePointer)), что делает то же самое, и я думаю, что это достаточно ясно. Я знаю, что эта часть программного обеспечения не касается производительности, но вы можете определенно сохранить дополнительные накладные расходы на вызов функции здесь. Вы даже сделали это в if (!somePtr) - saveEventList .
  • if (f != NULL) в do-While : эта часть вашего кода:

    inputRange

    может быть написано немного короче, используя printf(prompt, min, max); fgets(temp, 21, stdin); input = atoi(temp); while (input > max || input < min) { // Data validation printf(prompt, min, max); fgets(temp, 21, stdin); input = atoi(temp); } -loop. Что-то вроде этого:

    do-while
  • Нет необходимости в кастовании do { printf(prompt, min, max); fgets(temp, 21, stdin); input = atoi(temp); } while (input > max || input < min); . Вы делаете это, например, в malloc, и нет необходимости в явном литье. На самом деле использование явного приведения не рекомендуется, как описано здесь .

  • Проверьте initEvent для malloc : вам обязательно нужно проверить возвращаемое значение NULL. Когда есть проблема с распределением malloc, возвращается malloc, и вы сможете ее обработать. В этом случае это:

    NULL

    завершится с неопределенным поведением , поскольку вы пытаетесь разыменовать event *e = (event*)malloc(sizeof(event)); // e is NULL e->hour = 0; e->minute = 0; strcpy(e->description, ""); . В таком случае NULL может возвращать initEvent(), а вызывающий должен обрабатывать его (предупреждение печати, это событие не может быть создано).

    NULL
  • Незначительный : Распечатайте информацию о том, что нужно прочитать в :     Я думаю, вы должны распечатать начальное сообщение перед ожиданием ввода от пользователя (вместо печати только диапазона , напечатать , что собирается прочитать - час, минут ).

    event *e = (event*)malloc(sizeof(event));
    
    if (!e) {
        return NULL;
    }
    
    ...
    
  • Зачем проверять e->hour = inputRange(0, 23); e->minute = inputRange(0, 59); на не указателе? : Я уверен, что эта часть в NULL не нужна:

    addEventAtIndex()

    , поскольку вы передали фактическую структуру функции. Вы уже получили ошибку при попытке разыменования if (isNull(&e)) { // if our event is NULL, return return; } (нет способа случайно передать NULL с адресом struct).

  • Метод свопинга : часть подкачки функции сортировки должна быть дополнительной функцией для уточнения, и я также думаю, что вы можете немного ее сократить:

    NULL

    Это существенно заменит ваше тело цикла в алгоритме сортировки.

  • Хорошее использование переменных в void swapEvents(event list[], int index1, int index2) { int tmpHour = list[index1].hour; int tmpMinute = list[index1].minute; char tmpDescription[_MAX_DESCRIPTION]; strcpy(tmpDescription, list[index1].description); list[index1].hour = list[index2].hour; list[index1].minute = list[index2].minute; strcpy(list[index1].description, list[index2].description); list[index2].hour = tmpHour; list[index2].minute = tmpMinute; strcpy(list[index2].description, tmpDescription); } . Я думаю, что хорошо, что вы использовали дополнительные переменные в printEvent вместо того, чтобы ставить все арифметические как printEvent -arguments. Он сильно поддерживает читаемость.

Заключительные мысли . Это было, пожалуй, действительно хорошее упражнение для новичка. Хорошая работа, которую вы там делали. Вы могли бы подумать об использовании массива указателей на события вместо событий. При этом сортировка значительно улучшится, поскольку вам не нужно копировать целые структуры взад и вперед, но толькоуказатели подкачки - молниеносно .

ответил 24 MarpmThu, 24 Mar 2016 16:04:28 +03002016-03-24T16:04:28+03:0004 2016, 16:04:28
9

Самодокументирующий код

char h1 = { (e.hour / 10) + '0' }; // Extract the first digit and convert to char (if any, else 0)

Поскольку вы считали, что деление на десять и 0 слишком заманчивое, вы добавили комментарий, это намного лучше, чем оставлять читателя так хорошо.

Даже лучший вариант, хотя и указывает ваше намерение в коде:

char h1 = convert_to_char(first_digit(e.hour))

Где вы можете легко определить:

char convert_to_char(int x) {
    return x + '0';
}

и

int first_digit(int x) {
    return x / 10;
}

Необходимость поддерживать /читать код и комментарии вдвое больше, чем только код.

ответил Caridorc 24 MarpmThu, 24 Mar 2016 16:10:04 +03002016-03-24T16:10:04+03:0004 2016, 16:10:04
6

Несмотря на то, что @Herickson уже сделал большую часть очков, но он оставил для меня одно замечание: strlen(3) возвращает тип size_t, который может быть чем угодно (но главным образом unsigned long). Вы должны получить предупреждение от компилятора.

C - это язык, который довольно опасен для проницаемости ваших ног, и вы не должны рисковать, отключив предупреждения компилятора. Неправильный тип может привести к совершенно неожиданному поведению, например: если вы смешиваете целые числа с подписью и без знака, целые числа разных размеров или различаются между разными типами. Все возможно и даже полезно иногда, но все же опасно.

  • проверьте обратную связь. Все возвращается. Всегда. (даже printf(3) может выйти из строя при некоторых обстоятельствах и со встроенными процессорами и является одним из исключений вышеприведенного правила).

  • вы не проверяете длину строки fgets(3) gets (stdin также может быть недоступен, но я не согласен).

  • ни один из них не проверяет возврат getc(3). Если пользователь не вводит письмо, а enter, он «зависает» только для пользователя, потому что вы не проверяете EOF. Конечно, он не висит, пользователь может ввести письмо и продолжить, но это плохой UX, если пользователь не получил немедленного ответа в случае ошибки. (Вы также должны использовать fgetc(3), если возможно, потому что getc может быть реализован как макрос. Не имеет значения здесь, но будет иметь значение в будущем)

  • ваши собственные функции также должны возвращать что-то, что указывает на отказ /успех. Да, вы позволяете пользователю знать, что что-то не удалось, также что и почему, но вы сами не знаете.

  • два разных потока для ошибок и для нормального вывода лучше. Стандартный поток для ошибок - stderr, а для вывода - stdout. Если вы сделаете так, пользователь может перенаправить вывод программы в файл и увидеть только ошибки или наоборот.

  • Константы
  • , особенно если они произвольны, должны иметь имя. Макрос препроцессора достаточно в большинстве случаев (например: у вас есть 99, установленный как флаг в deleteEvent), может быть макросом или 21 для размер временного массива символов в inputRange, он должен быть макросом)

  • существует более одного цикла, который ожидает ввода пользователя, кроме одного в main(). Вы должны держать все это вместе, если это возможно.

  • вы не удаляете записи в списке, вы просто перезаписываете их. Но я думаю, что связанный список - это следующая вещь, которую вы узнаете, поэтому я сейчас задержу язык, -)

Повсюду: хорошо, нет причин опасаться следующего урока (но вы действительно должны узнать о функциях strto*()).

ответил deamentiaemundi 25 MaramFri, 25 Mar 2016 07:23:07 +03002016-03-25T07:23:07+03:0007 2016, 07:23:07
5

Небольшая точка, хотя она может вызвать у вас проблемы:

Из руководства GCC раздел 1.3.3 - Зарезервированные имена :

  

зарезервированные имена включают все внешние идентификаторы (глобальные функции и переменные), которые начинаются с символа подчеркивания ('_'), и все идентификаторы, независимо от использования, начинающиеся с двух символов подчеркивания или подчеркивания, за которыми следует заглавная буква, являются зарезервированными именами

Я могу неправильно интерпретировать это, но для меня это, похоже, рекомендует избегать любых имен идентификаторов, начинающихся с подчеркивания. Поэтому вы можете переименовать макросы препроцессора _MAX_EVENTS и _MAX_DESCRIPTION.

ответил Digital Trauma 25 MaramFri, 25 Mar 2016 08:20:28 +03002016-03-25T08:20:28+03:0008 2016, 08:20:28
3
 for (int i = 1; i < size; i++) {
        for (int j = i; j > 0 && (list[j - 1].hour > list[j].hour || (list[j - 1].hour == list[j].hour && list[j - 1].minute > list[j].minute)); j--) {

Условие во втором для довольно трудно читать. Вы должны извлечь его в функцию.

ответил raven 25 MarpmFri, 25 Mar 2016 17:44:47 +03002016-03-25T17:44:47+03:0005 2016, 17:44:47

Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132