C: المشروع النهائي: نظام إدارة المكتبة
بناء بيت ليس مجرد تعلم وضع الطوب — حان وقت الجمع بين جميع مهاراتك وبناء مبنى كامل، من الأساس إلى السقف.
1. تحليل المتطلبات
سنبنى نظام إدارة مكتبة لسطر الأوامر بالخصائص التالية:
- إضافة كتب (العنوان، المؤلف، ISBN، السعر)
- حذف كتب (بـ ISBN)
- تحديث معلومات الكتاب
- البحث عن كتب (بالعنوان أو ISBN)
- عرض جميع الكتب
- حفظ البيانات في ملف (استمرارية)
- تحميل البيانات من ملف
2. تصميم هياكل البيانات
يُمثّل معلومات الكتاب ببنية، وتُدار جميع الكتب بمصفوفة ديناميكية:
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. تنظيم ملفات المشروع
bookmanager/
├── Makefile
├── main.c
├── library.h
├── library.c
├── storage.h
└── storage.c
library.h/library.c: المنطق الأساسي لإدارة الكتب (CRUD)storage.h/storage.c: إدخال/إخراج الملفاتmain.c: نقطة الدخول الرئيسية للبرنامج وواجهة المستخدمMakefile: سكريبت البناء
4. library.h
#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
#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
#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
#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
#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
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
البناء والتشغيل:
make
./bookmanager
10. تشغيل البرنامج
===== 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.
▶ مثال
#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;
}
رقم:1 العنوان:C Primer Plus المؤلف:Stephen Prata
❓ أسئلة شائعة
$< هي التبعية الأولى (ملف .c)، و$@ هي الهدف (ملف .o).📖 ملخص
- تحليل المتطلبات هو نقطة البداية — حدد الخصائص قبل كتابة الكود
- تصميم هياكل البيانات يحدد معمارية البرنامج؛ المصفوفات الديناميكية حاوية قابلة للتوسع شائعة
- البرمجة النمطية: الملفات الرأسية تعلن الواجهات، وملفات المصدر تنفذ التفاصيل
- الاستمرارية بالملفات تضمن بقاء البيانات بعد خروج البرنامج
- Makefile يؤتمت البناء، متجنبًا إدخال أوامر الترجمة الطويلة يدويًا
📝 تمارين
- أضف ميزة "البحث بالمؤلف" إلى نظام إدارة المكتبة
- أضف وظيفة الترتيب لعرض الكتب مرتبة بالعنوان أو السعر أو ISBN
- حسّن وحدة التخزين بالتبديل إلى تنسيق ملف ثنائي وقارن الإيجابيات والسلبيات مقابل التنسيق النصي