مراجعة - نظام سجلات الطلاب
جزء من قسم Object Oriented Programming في رحلة C++ على Coddy — الدرس 39 من 104.
حان الوقت لوضع معرفتك بالتغليف (encapsulation) موضع التنفيذ! في هذا التحدي، ستقوم ببناء نظام سجلات الطلاب الذي يوضح الاستخدام الصحيح لمحددات الوصول، وإخفاء المعلومات، والفئات المتداخلة.
سيقوم نظامك بإدارة بيانات الطلاب باستخدام مبادئ التغليف هذه:
- أعضاء بيانات خاصة (Private data members) لحماية المعلومات الحساسة مثل الدرجات ومعرفات الطلاب
- واجهة عامة (Public interface) مع دوال الحصول (getters) ودوال التعيين (setters) المتحكم بها
- فئة متداخلة (nested class) لتمثيل درجات الدورات الفردية، مع إبقاء تفاصيل التنفيذ هذه محتواة داخل فئة الطالب
إليك الهيكل الذي ستقوم بتنفيذه:
class Student {
public:
class Grade { // فئة متداخلة لدرجات الدورات
// ...
};
// واجهة عامة للتفاعل مع بيانات الطلاب
private:
std::string name;
int studentId;
// مجموعة من الدرجات
};يجمع هذا التحدي كل شيء من هذا الفصل: اختيار مستويات الوصول المناسبة، وإخفاء تفاصيل التنفيذ خلف واجهة نظيفة، واستخدام الفئات المتداخلة لتنظيم الأنواع ذات الصلة. فكر جيداً فيما يجب أن يكون متاحاً وما يجب أن يظل مخفياً.
التحدي
سهللنقم ببناء نظام سجلات الطلاب الذي يجمع كل ما تعلمته حول التغليف (encapsulation). ستقوم بإنشاء نظام يتم فيه حماية بيانات الطلاب بشكل صحيح، وتتم إدارة الدرجات من خلال فئة متداخلة (nested class)، وتحدث جميع التفاعلات من خلال واجهة عامة نظيفة.
ستقوم بإنشاء ملفين لتنظيم الكود الخاص بك:
Student.h: قم بتعريف فئةStudentتوضح التغليف الصحيح مع فئةGradeمتداخلة.يجب أن تقوم فئة
Gradeالمتداخلة (المصرح عنها في القسم العام منStudent) بتخزين اسم الدورة (string) والدرجة (double). امنحها منشئًا (constructor) يأخذ كلتا القيمتين، ويوفر طريقتيgetCourseName()وgetScore()للوصول إلى البيانات. تحتفظ فئةGradeببياناتها خاصة — على الرغم من أنها متداخلة داخلStudent، إلا أنها لا تزال تحافظ على التغليف الخاص بها.يجب أن تحتوي فئة
Studentعلى أعضاء خاصين لاسم الطالب (string)، ومعرف الطالب (int)، ومجموعة من مؤشراتGrade. قم بتوفير:- منشئ يأخذ الاسم ومعرف الطالب
- دوال جلب
getName()وgetStudentId()(كلاهما const) - طريقة
addGrade(const std::string& course, double score)تنشئGradeجديدًا وتضيفه إلى سجل الطالب (أضفه فقط إذا كانت الدرجة بين 0 و 100 شاملة) - طريقة
getAverageGrade()(const) تحسب وتعيد متوسط جميع الدرجات، أو 0.0 إذا لم تكن هناك درجات - طريقة
displayRecord()(const) تعرض سجل الطالب الكامل
لا تنسَ تنظيف الدرجات المخصصة ديناميكيًا في المدمر (destructor)، وتضمين حراس الرأس (header guards) المناسبين.
main.cpp: اقرأ اسم الطالب، ومعرف الطالب، وثلاث درجات للدورات من المدخلات. سيكون تنسيق الإدخال خمسة أسطر: الاسم، المعرف، ثم ثلاثة أسطر يحتوي كل منها على اسم الدورة والدرجة مفصولين بمسافة (على سبيل المثال،Math 95.5).قم بإنشاء
Studentبالاسم والمعرف، وأضف جميع الدرجات الثلاث، ثم اعرض السجل الكامل.يجب أن يكون تنسيق الإخراج لـ
displayRecord()هو:Student: <name> (ID: <id>) Grades: <course1>: <score1> <course2>: <score2> <course3>: <score3> Average: <average>
قم بتنسيق جميع الدرجات والمتوسط بمنزلة عشرية واحدة باستخدام std::fixed و std::setprecision(1) من <iomanip>. قم بتحويل المعرف باستخدام std::stoi() والدرجات باستخدام std::stod().
يجمع هذا التحدي بين أعضاء البيانات الخاصة، ودوال الجلب العامة، ودوال التعيين المتحكم بها مع التحقق من الصحة، وفئة متداخلة — تعمل جميعها معًا لإنشاء نظام سجلات طلاب مغلف جيدًا.
ورقة مرجعية
يجمع التغليف (Encapsulation) بين محددات الوصول، وإخفاء المعلومات، والفئات المتداخلة لحماية البيانات وتنظيم الكود.
محددات الوصول لحماية البيانات
استخدم private لأعضاء البيانات الحساسة و public للواجهة البرمجية:
class Student {
public:
// طرق الواجهة العامة
private:
std::string name;
int studentId;
// بيانات خاصة أخرى
};الفئات المتداخلة
تقوم الفئات المتداخلة بتنظيم الأنواع ذات الصلة داخل فئة معينة، مع الحفاظ على التغليف الخاص بها:
class Student {
public:
class Grade { // فئة متداخلة
private:
std::string courseName;
double score;
public:
Grade(const std::string& course, double s)
: courseName(course), score(s) {}
std::string getCourseName() const { return courseName; }
double getScore() const { return score; }
};
private:
std::vector<Grade*> grades;
};الوصول المتحكم به مع التحقق من الصحة
يمكن للطرق (Methods) التحقق من صحة البيانات قبل تعديل الأعضاء الخاصين:
void addGrade(const std::string& course, double score) {
if (score >= 0 && score <= 100) {
grades.push_back(new Grade(course, score));
}
}الطرق الثابتة (Const Methods)
قم بتمييز الطرق التي لا تعدل البيانات بـ const:
std::string getName() const { return name; }
double getAverageGrade() const {
// حساب وإرجاع المتوسط
}إدارة الذاكرة
قم بتنظيف الكائنات المخصصة ديناميكيًا في المدمر (destructor):
~Student() {
for (Grade* grade : grades) {
delete grade;
}
}جرّب بنفسك
#include <iostream>
#include <string>
#include <sstream>
#include "Student.h"
int main() {
// قراءة اسم الطالب
std::string name;
std::getline(std::cin, name);
// قراءة الرقم التعريفي للطالب
std::string idStr;
std::getline(std::cin, idStr);
int studentId = std::stoi(idStr);
// قراءة درجات ثلاث مواد (التنسيق: "CourseName Score")
std::string line1, line2, line3;
std::getline(std::cin, line1);
std::getline(std::cin, line2);
std::getline(std::cin, line3);
// TODO: تحليل كل سطر لاستخراج اسم المادة والدرجة
// استخدم std::stod() لتحويل نصوص الدرجات إلى doubles
// TODO: إنشاء كائن Student بالاسم والرقم التعريفي
// TODO: إضافة جميع الدرجات الثلاث إلى الطالب
// TODO: عرض سجل الطالب الكامل
return 0;
}
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس Object Oriented Programming
1أساسيات الـ OOP
الملفات الخارجيةبناء وتجميع (Compilation) لغة C++ملفات الـ Header وملفات الـ Sourceالـ Namespaces والـ Scopeمقدمة في الـ OOP في C++الـ Classes مقابل الـ Objectsالمؤشر 'this'الـ Methods (الدوال الأعضاء)الـ Attributes (بيانات الأعضاء)أساسيات الـ Ctors والـ Dtorsمراجعة - آلة حاسبة بسيطة4خصائص الـ Class
أعضاء الـ Instance مقابل الـ Staticدوال الـ Getters والـ Settersدوال الأعضاء الثابتة (Const)الكلمة المفتاحية Mutableالدوال والمتغيرات الساكنة (Static)الدوال والفئات الصديقة (Friend)مراجعة - مدير الحساب البنكي7الوراثة
الوراثة الأساسيةمستويات الوصول في الوراثةترتيب استدعاء الـ Ctor والـ Dtorإعادة تعريف الدوال (Method Overriding)الدوال الافتراضية والـ VTableالوراثة المتعددةالوراثة الافتراضيةمراجعة - هيكلية الموظفين10نظرة عامة على STL
نظرة عامة وفلسفة STLحاويات STLالـ Iteratorsخوارزميات STLالـ Functors وتعبيرات Lambdaمراجعة - تكرار الكلمات13أنماط التصميم - الجزء الأول
مقدمة في أنماط التصميمنمط Singletonنمط Factory و Abstract Factoryنمط Builderنمط Observerنمط Strategy2إدارة الذاكرة
ذاكرة Stack مقابل Heapالمؤشرات والمراجعالذاكرة الديناميكية (new/delete)المؤشرات الذكية في C++RAII في C++مراجعة - مدير المصفوفات الديناميكية5التغليف (Encapsulation)
محددات الوصول في C++محددات الوصول بشكل متعمقإخفاء المعلوماتStruct مقابل Classالأصناف المتداخلة والداخليةمراجعة - نظام سجلات الطلاب8تعدد الأشكال (Polymorphism)
تعدد الأشكال: وقت التجميع مقابل وقت التشغيلالتحميل الزائد للدوال (Function Overloading)مراجعة الدوال الافتراضية (Virtual Functions)الدوال الافتراضية البحتة (Pure Virtual Functions)الأصناف المجردة (Abstract Classes)تصميم الواجهات (Interface) في C++التحويل الديناميكي (Dynamic Casting) و RTTIملخص - حاسبة الأشكال11مفاهيم OOP المتقدمة
التركيب مقابل الوراثةالـ Mixins عبر CRTPنمط Pimplمحو النوع (Type Erasure)أصناف Enum والأنواع القويةمعالجة الاستثناءات في OOPتسلسلات الاستثناءات المخصصة14أنماط التصميم - الجزء الثاني
نمط الأمرنمط المحولنمط المزيننمط قالب الطريقةنمط الحالةنمط التركيبRAII كنمط3المنشئات والموادم
المنشئ الافتراضيالمنشئ ذو المعاملاتمنشئ النسخمنشئ النقلقوائم تهيئة المنشئالمنشئات المفوضةتعمق في الموادمقاعدة الثلاثة / الخمسة / الصفرمراجعة - فئة String6تحميل العوامل (Operator Overloading)
مقدمة في تحميل العواملتحميل العوامل الحسابيةتحميل عوامل المقارنةعوامل Streamتحميل عامل التعيينتحميل العوامل [] و ()عوامل تحويل النوعمراجعة - Matrix Class9القوالب
قوالب الدوالقوالب الأصنافتخصيص القوالبالقوالب المتغيرةأساسيات SFINAE و Type Traitsمراجعة - الحاويات العامة