C: المشروع النهائي: نظام إدارة المكتبة

بناء بيت ليس مجرد تعلم وضع الطوب — حان وقت الجمع بين جميع مهاراتك وبناء مبنى كامل، من الأساس إلى السقف.

1. تحليل المتطلبات

سنبنى نظام إدارة مكتبة لسطر الأوامر بالخصائص التالية:

  1. إضافة كتب (العنوان، المؤلف، ISBN، السعر)
  2. حذف كتب (بـ ISBN)
  3. تحديث معلومات الكتاب
  4. البحث عن كتب (بالعنوان أو ISBN)
  5. عرض جميع الكتب
  6. حفظ البيانات في ملف (استمرارية)
  7. تحميل البيانات من ملف

2. تصميم هياكل البيانات

يُمثّل معلومات الكتاب ببنية، وتُدار جميع الكتب بمصفوفة ديناميكية:

C
typedef struct {
    char isbn[14];
    char title[128];
    char author[64];
    double price;
} Book;

typedef struct {
    Book *books;
    int count;
    int capacity;
} Library;

تُدير Library مصفوفة ديناميكية: books هو مؤشر البيانات، وcount هو العدد الحالي للعناصر، وcapacity هي السعة المُخصصة. عندما يصل count إلى capacity، تتوسع تلقائيًا.


3. تنظيم ملفات المشروع

C
bookmanager/
├── Makefile
├── main.c
├── library.h
├── library.c
├── storage.h
└── storage.c

4. library.h

C
#ifndef LIBRARY_H
#define LIBRARY_H

typedef struct {
    char isbn[14];
    char title[128];
    char author[64];
    double price;
} Book;

typedef struct {
    Book *books;
    int count;
    int capacity;
} Library;

void library_init(Library *lib);
void library_free(Library *lib);
int library_add(Library *lib, const Book *book);
int library_remove(Library *lib, const char *isbn);
Book *library_find_by_isbn(Library *lib, const char *isbn);
void library_find_by_title(Library *lib, const char *keyword,
                           Book **results, int *result_count);
int library_update(Library *lib, const char *isbn, const Book *new_info);
void library_list(const Library *lib);

#endif

5. library.c

C
#include "library.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

static int ensure_capacity(Library *lib) {
    if (lib->count < lib->capacity) {
        return 1;
    }
    int new_cap = lib->capacity == 0 ? 4 : lib->capacity * 2;
    Book *new_books = realloc(lib->books, new_cap * sizeof(Book));
    if (new_books == NULL) {
        return 0;
    }
    lib->books = new_books;
    lib->capacity = new_cap;
    return 1;
}

void library_init(Library *lib) {
    lib->books = NULL;
    lib->count = 0;
    lib->capacity = 0;
}

void library_free(Library *lib) {
    free(lib->books);
    lib->books = NULL;
    lib->count = 0;
    lib->capacity = 0;
}

int library_add(Library *lib, const Book *book) {
    if (!ensure_capacity(lib)) {
        return 0;
    }
    lib->books[lib->count] = *book;
    lib->count++;
    return 1;
}

int library_remove(Library *lib, const char *isbn) {
    for (int i = 0; i < lib->count; i++) {
        if (strcmp(lib->books[i].isbn, isbn) == 0) {
            for (int j = i; j < lib->count - 1; j++) {
                lib->books[j] = lib->books[j + 1];
            }
            lib->count--;
            return 1;
        }
    }
    return 0;
}

Book *library_find_by_isbn(Library *lib, const char *isbn) {
    for (int i = 0; i < lib->count; i++) {
        if (strcmp(lib->books[i].isbn, isbn) == 0) {
            return &lib->books[i];
        }
    }
    return NULL;
}

void library_find_by_title(Library *lib, const char *keyword,
                           Book **results, int *result_count) {
    *result_count = 0;
    for (int i = 0; i < lib->count; i++) {
        if (strstr(lib->books[i].title, keyword) != NULL) {
            results[*result_count] = &lib->books[i];
            (*result_count)++;
        }
    }
}

int library_update(Library *lib, const char *isbn, const Book *new_info) {
    Book *existing = library_find_by_isbn(lib, isbn);
    if (existing == NULL) {
        return 0;
    }
    *existing = *new_info;
    return 1;
}

void library_list(const Library *lib) {
    if (lib->count == 0) {
        printf("No books in the library.\n");
        return;
    }
    printf("%-14s %-30s %-20s %-8s\n", "ISBN", "Title", "Author", "Price");
    printf("--------------------------------------------------------------\n");
    for (int i = 0; i < lib->count; i++) {
        printf("%-14s %-30s %-20s %-8.2f\n",
               lib->books[i].isbn,
               lib->books[i].title,
               lib->books[i].author,
               lib->books[i].price);
    }
    printf("Total: %d books\n", lib->count);
}
💡 نصيحة: ensure_capacity دالة داخلية، مزيّنة بـ static لتحديد نطاقها بهذا الملف. استراتيجية مضاعفة المصفوفة الديناميكية هي ممارسة قياسية، تضمن كفاءة إدراج بـ O(1) المُستهلك.


6. storage.h

TEXT 📖 للعرض فقط
#ifndef STORAGE_H
#define STORAGE_H

#include "library.h"

int storage_save(const Library *lib, const char *filename);
int storage_load(Library *lib, const char *filename);

#endif

7. storage.c

C
#include "storage.h"
#include <stdio.h>
#include <string.h>

int storage_save(const Library *lib, const char *filename) {
    FILE *fp = fopen(filename, "w");
    if (fp == NULL) {
        return 0;
    }
    for (int i = 0; i < lib->count; i++) {
        fprintf(fp, "%s|%s|%s|%.2f\n",
                lib->books[i].isbn,
                lib->books[i].title,
                lib->books[i].author,
                lib->books[i].price);
    }
    fclose(fp);
    return 1;
}

int storage_load(Library *lib, const char *filename) {
    FILE *fp = fopen(filename, "r");
    if (fp == NULL) {
        return 1;
    }
    Book book;
    while (fscanf(fp, "%13[^|]|%127[^|]|%63[^|]|%lf\n",
                  book.isbn, book.title, book.author, &book.price) == 4) {
        if (!library_add(lib, &book)) {
            fclose(fp);
            return 0;
        }
    }
    fclose(fp);
    return 1;
}
⚠️ ملاحظة: تنسيق %[^|] في fscanf يقرأ سلسلة حتى محدد |. %13[^|] يحدد القراءة بـ 13 حرفًا على الأكثر، مما يمنع التجاوز. هذه هي الطريقة الآمنة لاستخدام الإدخال/الإخراج المنسق.


8. main.c

TEXT 📖 للعرض فقط
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "library.h"
#include "storage.h"

#define DATA_FILE "library.dat"

static void input_string(const char *prompt, char *buf, int size) {
    printf("%s", prompt);
    fflush(stdout);
    if (fgets(buf, size, stdin) == NULL) {
        buf[0] = '\0';
        return;
    }
    buf[strcspn(buf, "\n")] = '\0';
}

static void cmd_add(Library *lib) {
    Book book;
    input_string("Enter ISBN: ", book.isbn, sizeof(book.isbn));
    input_string("Enter title: ", book.title, sizeof(book.title));
    input_string("Enter author: ", book.author, sizeof(book.author));

    char price_str[32];
    input_string("Enter price: ", price_str, sizeof(price_str));
    book.price = atof(price_str);

    if (library_find_by_isbn(lib, book.isbn) != NULL) {
        printf("Error: ISBN %s already exists\n", book.isbn);
        return;
    }

    if (library_add(lib, &book)) {
        printf("Added successfully!\n");
    } else {
        printf("Failed to add: out of memory\n");
    }
}

static void cmd_remove(Library *lib) {
    char isbn[14];
    input_string("Enter ISBN to remove: ", isbn, sizeof(isbn));

    if (library_remove(lib, isbn)) {
        printf("Removed successfully!\n");
    } else {
        printf("Book with that ISBN not found\n");
    }
}

static void cmd_find(Library *lib) {
    printf("1. Search by ISBN  2. Search by title\n");
    char choice[8];
    input_string("Choose: ", choice, sizeof(choice));

    if (strcmp(choice, "1") == 0) {
        char isbn[14];
        input_string("Enter ISBN: ", isbn, sizeof(isbn));
        Book *book = library_find_by_isbn(lib, isbn);
        if (book != NULL) {
            printf("ISBN:   %s\n", book->isbn);
            printf("Title:  %s\n", book->title);
            printf("Author: %s\n", book->author);
            printf("Price:  %.2f\n", book->price);
        } else {
            printf("Not found\n");
        }
    } else if (strcmp(choice, "2") == 0) {
        char keyword[128];
        input_string("Enter keyword: ", keyword, sizeof(keyword));
        Book *results[100];
        int result_count = 0;
        library_find_by_title(lib, keyword, results, &result_count);
        if (result_count == 0) {
            printf("No books containing \"%s\" found\n", keyword);
        } else {
            for (int i = 0; i < result_count; i++) {
                printf("%-14s %-30s %.2f\n",
                       results[i]->isbn, results[i]->title, results[i]->price);
            }
            printf("Found %d books\n", result_count);
        }
    }
}

static void cmd_update(Library *lib) {
    char isbn[14];
    input_string("Enter ISBN to update: ", isbn, sizeof(isbn));

    Book *existing = library_find_by_isbn(lib, isbn);
    if (existing == NULL) {
        printf("Book with that ISBN not found\n");
        return;
    }

    Book new_info = *existing;
    printf("Current title: %s (press Enter to keep)\n", existing->title);
    input_string("New title: ", new_info.title, sizeof(new_info.title));
    if (new_info.title[0] == '\0') {
        strcpy(new_info.title, existing->title);
    }

    printf("Current author: %s (press Enter to keep)\n", existing->author);
    input_string("New author: ", new_info.author, sizeof(new_info.author));
    if (new_info.author[0] == '\0') {
        strcpy(new_info.author, existing->author);
    }

    char price_str[32];
    printf("Current price: %.2f (press Enter to keep)\n", existing->price);
    input_string("New price: ", price_str, sizeof(price_str));
    if (price_str[0] != '\0') {
        new_info.price = atof(price_str);
    } else {
        new_info.price = existing->price;
    }

    strcpy(new_info.isbn, isbn);
    if (library_update(lib, isbn, &new_info)) {
        printf("Updated successfully!\n");
    }
}

static void show_menu(void) {
    printf("\n===== Library Management System =====\n");
    printf("1. Add book\n");
    printf("2. Remove book\n");
    printf("3. Search book\n");
    printf("4. Update book\n");
    printf("5. List all books\n");
    printf("6. Save data\n");
    printf("0. Exit\n");
    printf("=====================================\n");
}

int main(void) {
    Library lib;
    library_init(&lib);

    if (!storage_load(&lib, DATA_FILE)) {
        printf("Warning: Failed to load data, starting with empty library\n");
    }

    char choice[8];
    while (1) {
        show_menu();
        input_string("Choose an option: ", choice, sizeof(choice));

        if (strcmp(choice, "1") == 0) {
            cmd_add(&lib);
        } else if (strcmp(choice, "2") == 0) {
            cmd_remove(&lib);
        } else if (strcmp(choice, "3") == 0) {
            cmd_find(&lib);
        } else if (strcmp(choice, "4") == 0) {
            cmd_update(&lib);
        } else if (strcmp(choice, "5") == 0) {
            library_list(&lib);
        } else if (strcmp(choice, "6") == 0) {
            if (storage_save(&lib, DATA_FILE)) {
                printf("Saved successfully!\n");
            } else {
                printf("Failed to save!\n");
            }
        } else if (strcmp(choice, "0") == 0) {
            printf("Save data before exiting? (y/n): ");
            char confirm[8];
            if (fgets(confirm, sizeof(confirm), stdin) != NULL) {
                if (confirm[0] == 'y' || confirm[0] == 'Y') {
                    storage_save(&lib, DATA_FILE);
                    printf("Saved\n");
                }
            }
            break;
        } else {
            printf("Invalid choice\n");
        }
    }

    library_free(&lib);
    return 0;
}

9. Makefile

MAKEFILE
CC = gcc
CFLAGS = -Wall -Wextra -std=c99 -O2
SRCS = main.c library.c storage.c
OBJS = $(SRCS:.c=.o)
TARGET = bookmanager

$(TARGET): $(OBJS)
	$(CC) $(CFLAGS) -o $@ $^

%.o: %.c
	$(CC) $(CFLAGS) -c $< -o $@

main.o: main.c library.h storage.h
library.o: library.c library.h
storage.o: storage.c storage.h library.h

clean:
	rm -f $(OBJS) $(TARGET)

.PHONY: clean

البناء والتشغيل:

BASH
make
./bookmanager

10. تشغيل البرنامج

TEXT 📖 للعرض فقط
===== Library Management System =====
1. Add book
2. Remove book
3. Search book
4. Update book
5. List all books
6. Save data
0. Exit
=====================================
Choose an option: 1
Enter ISBN: 9787115279460
Enter title: C Primer Plus
Enter author: Stephen Prata
Enter price: 89.00
Added successfully!

Choose an option: 5
ISBN           Title                          Author               Price
--------------------------------------------------------------
9787115279460  C Primer Plus                  Stephen Prata        89.00
Total: 1 books

11. رؤى رئيسية من المشروع

(1) توسيع المصفوفة الديناميكية

تستخدم ensure_capacity استراتيجية المضاعفة. كلما وصل count إلى capacity، تتضاعف السعة. هذا يعني أن n إدراجًا تسبب O(log n) إعادة تخصيص إجمالية، مما يعطي O(1) مُستهلك لكل إدراج.

(2) اختيار تنسيق الملف

تنسيق التخزين يستخدم نصًا محددًا بـ | بدلاً من الثنائي. الفوائد:

(3) فصل الوحدات

library وstorage مفصولتان — المنطق الأساسي لا يعتمد على طريقة التخزين المحددة. التبديل إلى قاعدة بيانات لاحقًا يتطلب تعديل storage.c فقط، دون تغيير الكود الأساسي.

(4) أمان الإدخال

تستخدم input_string دالة fgets للإدخال، مع تحديد الطول تلقائيًا، ثم تزيل السطر الجديد بـ strcspn. هذه طريقة إدخال أكثر أمانًا من scanf.


▶ مثال

C
#include <stdio.h>
#include <string.h>

typedef struct {
    char title[64];
    char author[32];
    int id;
} Book;

int main(void) {
    Book b = {"C Primer Plus", "Stephen Prata", 1};
    printf("رقم:%d العنوان:%s المؤلف:%s\n", b.id, b.title, b.author);
    return 0;
}
▶ جرّب الكود
TEXT 📖 للعرض فقط
رقم:1 العنوان:C Primer Plus المؤلف:Stephen Prata


❓ أسئلة شائعة

س لماذا لا يُحفظ تلقائيًا عند الخروج؟
ج قد يكون المستخدم أجرى تغييرات عرضية. السؤال عن الحفظ يمنحه فرصة للتراجع. الحفظ التلقائي قد يُستبدل بيانات سابقة.
س لماذا يحتاج حذف عنصر من مصفوفة ديناميكية إلى إزاحة؟
ج لأن المصفوفات تُخزن بشكل متجاور في الذاكرة، بعد حذف عنصر وسط، يجب إزاحة العناصر اللاحقة للأمام لملء الفجوة — وإلا سيتعطل البحث والعبور.
س كم كتابًا يمكن أن يدعمه هذا المشروع؟
ج نظريًا محدود بالذاكرة فقط. كل كتاب نحو 210 بايت، لذا 1 جيجابايت ذاكرة يمكنها استيعاب نحو 5 ملايين كتاب. العائق العملي هو كفاءة البحث — البحث الخطي بـ O(n) يصبح بطيئًا مع مجموعات البيانات الكبيرة؛ جدول تجزئة أو شجرة بحث ثنائية سيكون أفضل.
س ماذا يعني سطر %.o في Makefile؟
ج هي قاعدة نمط تعني أن جميع ملفات .c تُترجم إلى ملفات .o بنفس الأمر. $< هي التبعية الأولى (ملف .c)، و$@ هي الهدف (ملف .o).

📖 ملخص

📝 تمارين

  1. أضف ميزة "البحث بالمؤلف" إلى نظام إدارة المكتبة
  2. أضف وظيفة الترتيب لعرض الكتب مرتبة بالعنوان أو السعر أو ISBN
  3. حسّن وحدة التخزين بالتبديل إلى تنسيق ملف ثنائي وقارن الإيجابيات والسلبيات مقابل التنسيق النصي
Web-Tutorial.com

فريق Web-Tutorial التقني

منصة دروس برمجية يديرها عدة مطورين. كل درس يتم كتابته ومراجعته بواسطة مطورين متخصصين في المجال. نعمل على ضمان دقة وموثوقية المحتوى — إذا لاحظت أي مشكلة، فيرجى إخبارنا.

100%