Menu
Coddy logo textTech
flag Ar iconالعربيةdown icon

مراجعة - نظام سجلات الطلاب

جزء من قسم 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;
    // مجموعة من الدرجات
};

يجمع هذا التحدي كل شيء من هذا الفصل: اختيار مستويات الوصول المناسبة، وإخفاء تفاصيل التنفيذ خلف واجهة نظيفة، واستخدام الفئات المتداخلة لتنظيم الأنواع ذات الصلة. فكر جيداً فيما يجب أن يكون متاحاً وما يجب أن يظل مخفياً.

challenge icon

التحدي

سهل

لنقم ببناء نظام سجلات الطلاب الذي يجمع كل ما تعلمته حول التغليف (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;
}
quiz iconاختبر نفسك

يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.

جميع دروس Object Oriented Programming