نسخه قابل چاپ

بخش ۱۱ - وراثت و چندریختی

در این بخش رابطه‌ی وراثت بین کلاس‌ها را بررسی می‌کنیم.

فهرست مثال‌ها

وراثت در اشخاص

در این مثال، اطلاعات مشترک بین دو کلاس دانشجو و کارمند که در سیسیتم اطلاعاتی دانشگاه نقش ایفا می‌کنند در قالب یک سوپرکلاس به نام Person فاکتورگیری شده است.

#include <iostream>
#include <string>
using namespace std;

class Person {
public:
	Person(string n, string c) : name(n), nat_code(c) {}

	string get_name() { return name; }
	string get_nat_code() { return nat_code; }
private:
	string name;
	string nat_code;
};

class Student : public Person {
public:
	Student(string n, string c, string sid)
		: Person(n, c),
		student_id(sid) {}

	string get_id() { return student_id; }
private:
	string student_id;
};

class Employee : public Person {
public:
	Employee(string n, string c, string ec, int bs)
		: Person(n, c), emp_code(ec), base_salary(bs) {}

	string get_emp_code() { return emp_code; }
	
	int calc_salary(int hours_worked) {
		int hourly_pay = base_salary / 240;
		return base_salary + (hours_worked - 240) * hourly_pay * 1.4;
	}
private:
	string emp_code;
	int base_salary;
};

int main()
{
	Student s("gholam", "0123456789", "810188123");
	cout << s.get_name() << endl;

	Employee e("ghamar", "1234567890", "1234", 500000);
   	cout << e.calc_salary(263) << endl;	
}

					
وراثت استاد از کارمند

اساتید دانشگاه کارمندان دانشگاه نیز محسوب می‌شوند. به همین دلیل کلاس مربوطه به عنوان زیرکلاس کارمند تعریف شده است.

#include <iostream>
#include <string>
using namespace std;

class Person {
public:
	Person(string n, string c) : name(n), nat_code(c) {}

	string get_name() { return name; }
	string get_nat_code() { return nat_code; }
private:
	string name;
	string nat_code;
};

class Student : public Person {
public:
	Student(string n, string c, string sid)
		: Person(n, c),
		student_id(sid) {}

	string get_id() { return student_id; }
private:
	string student_id;
};


class Employee : public Person {
public:
	Employee(string n, string c, string ec, int bs)
		: Person(n, c), emp_code(ec), base_salary(bs) {}

	string get_emp_code() { return emp_code; }
	
	int calc_salary(int hours_worked) {
		int hourly_pay = base_salary / 240;
		return base_salary + (hours_worked - 240) * hourly_pay * 1.4;
	}
private:
	string emp_code;
	int base_salary;
};

class Prefessor : public Employee {
public:
	Prefessor(string n, string c, string ec, int bs, int ut)
		: Employee(n, c, ec, bs), units_taught(ut) {}

	int calc_salary(int hours_worked) {
		int extra_units = units_taught - 10;
		return Employee::calc_salary(hours_worked) + extra_units * 50000;
	}
private:
	int units_taught;
};

int main()
{
	Prefessor f("ghodrat", "333333", "1235", 800000, 13);
	cout << f.get_name() << endl;
	cout << f.calc_salary(263) << endl;
}
					
چندریختی

در این مثال، اشکال هندسی (به عنوان نمونه، مستطیل و دایره) در سلسله‌مراتب وراثت از کلاس مجرد «شکل» به ارث می‌برند.

#include <vector>
#include <cstdlib>
#include <iostream>
using namespace std;

class IllegalArgumentException {};

class Shape {
public:
    Shape(int init_x, int init_y) : x(init_x), y(init_y) {}
    int get_x() const { return x; }
    int get_y() const { return y; }
    void move(int dx, int dy);

    virtual void scale(int s) = 0;
    virtual void print() = 0;
protected:
    int x;
    int y;
};

void Shape::move(int dx, int dy)
{
    x += dx;
    y += dy;
}

class Rect : public Shape {
public:
    Rect(int init_x, int init_y, int w, int h);
    virtual void scale(int s);
    virtual void print();
private:
    int width;
    int height;
};

Rect::Rect(int init_x, int init_y, int w, int h)
    : Shape(init_x, init_y)
{
    if (w <= 0 || h <= 0)
	    throw IllegalArgumentException();
    width = w;
    height = h;
}

void Rect::scale(int s)
{
    width *= s;
    height *= s;
}

void Rect::print()
{
    cout << "Rect: " << x << ',' << y << ',' << width << ',' << height << endl;
}

class Circle : public Shape {
public:
    Circle(int init_x, int init_y, int r);
    virtual void scale(int s);
    virtual void print();
private:
    int radius;
};

Circle::Circle(int init_x, int init_y, int r)
    : Shape(init_x, init_y)
{
    if (r <= 0)
	    throw IllegalArgumentException();
    radius = r;
}

void Circle::scale(int s)
{
    radius *= s;
}

void Circle::print()
{
    cout << "Circle: " << x << ',' << y << ',' << radius << endl;
}

int main()
{
    vector<Shape*> shapes;
    
    while (true) {
        cout << "r. New Rectangle\n";
        cout << "c. New Circle\n";
        cout << "p. Display Shapes\n";
        cout << "m. Move All\n";
        cout << "s. Scale All\n";
        cout << "q. Exit\n";
        
        char ch;    
        cin >> ch;
            
        if (ch == 'r') {
            int x, y, w, h;
            cout << "enter x, y, width, and height: ";
            cin >> x >> y >> w >> h;
            
            Rect *rect = new Rect(x, y, w, h);
            shapes.push_back(rect);
            
        } else if (ch == 'c') {
            int x, y, r;
            cout << "enter x, y, and radius: ";
            cin >> x >> y >> r;
            
            Circle *circ = new Circle(x, y, r);
            shapes.push_back(circ);
        
        } else if (ch == 'p') {
            for (int i = 0; i < shapes.size(); i++)
                shapes[i]->print();
            cout<< endl;

        } else if (ch == 'm') {
            int dx, dy;
            cout << "enter dx, dy: ";
            cin >> dx >> dy;
            for (int i = 0; i < shapes.size(); i++)
                shapes[i]->move(dx, dy);
            
        } else if (ch == 's') {
            int s;
            cout << "enter s: ";
            cin >> s;
            for (int i = 0; i < shapes.size(); i++)
                shapes[i]->scale(s);
        }
        else if (ch == 'q') {
            for (int i = 0; i < shapes.size(); i++)
                delete shapes[i];
            break;
        }
    } // while (true)
}	
					
مرتب‌سازی کلی

در این مثال، تلاش می‌کنیم تابعی باری مرتب‌سازی آرایه‌ها بنویسیم طوری که با تمام انواع داده‌ای کار کند. این نیاز در بخش‌های آینده با استفاده از الگوها (بخش ۱۵) و کتابخانه‌ی STL‌ (بخش ۱۷) به شکل بسیار ساده‌تری حل خواهد شد و این مثال صرفاً حالت آموزشی دارد.

#include <iostream>
#include <string>
using namespace std;

class object {
public:
    virtual int compare_to(const object* o) = 0;
    virtual string to_string() const = 0;
};

class incomparable_types_ex {
public:
    incomparable_types_ex(const object* _o1, const object* _o2) : o1(_o1), o2(_o2) {}
    const object* o1;
    const object* o2;
};

class duck : public object {
public:
    duck(string n) : name(n) {}

    int compare_to(const object* o) {
        const duck* p = dynamic_cast<const duck*>(o);
        if (p == NULL)
            throw incomparable_types_ex(this, o);
        return name.compare(p->name);
    }
    string to_string() const { return name; }
private:
    string name;
};

class student : public object {
public:
    student(string n, double d) : name(n), grade(d) {}

    int compare_to(const object* o) {
        const student* p = dynamic_cast<const student*>(o);
        if (p == NULL)
            throw incomparable_types_ex(this, o);
        return grade - p->grade;
    }

    string to_string() const { return name; }
private:
    string name;
    double grade;
};

int min_index(object* array[], int count, int from_index) {
    int min_idx = from_index;
    for (int i = 1; i < count; i++)
        if (array[i]->compare_to(array[min_idx]) < 0)
            min_idx = i;
    return min_idx;
}

void selection_sort(object* array[], int count, int from_index) {
    if (from_index >= count -1)
        return;
    int min_idx = min_index(array, count, from_index);
    
    object* temp = array[min_idx];
    array[min_idx] = array[from_index];
    array[from_index] = temp;

    selection_sort(array, count, from_index + 1);
}

int main() {
    duck donald("Donald");
    student moez("Moez", 2.0);

    try {
        cout << donald.compare_to(&moez);
    } catch (incomparable_types_ex e) {
        cerr << "Failed to compare incomparable objects: " 
            << e.o1->to_string() << " and " << e.o2->to_string() << endl;
    }

    object* studs[] = {
        new student("Masoud", 2.0),
        new student("Gholam", 15.5),
        new student("Ghamar", 17.8)
    };

    selection_sort(studs, sizeof(studs)/sizeof(object*), 0);

    for (int i = 0; i < sizeof(studs)/sizeof(object*); i++) {
        cout << studs[i]->to_string() << endl;
        delete studs[i];
    }
}
					
تمرین‌های کوتاه
  1. مزیت‌های استفاده از چندریختی چیست؟ به دو مورد اشاره کنید. می‌توانید از مثال «شکل‌ها» برای توضیح استفاده کنید.
  2. دلیل تعریف یک متد به عنوان «مجازی خالص» چیست؟
    • مشخص کنید هر یک از موارد زیر درست هستند یا نه. اگر نه، دلیل آن را توضیح دهید:
    • تمام متدهای مجازی در یک کلاس مجرد باید «مجازی خالص» تعریف شوند.
    • اشاره‌کردن به یک شیء از نوع یک زیرکلاس با اشاره‌گری با تایپ ابرکلاس خطرناک است.
    • اگر یک کلاس متدی را «مجازی خالص» تعریف کرده باشد، زیرکلاس‌های آن باید آن متد را پیاده‌سازی کنند تا مجرد محسوب نشوند.
    • استفاده از چندریختی باعث می‌شود خیلی جاها نیازی به جمله‌ی ‌switch نداشته باشیم.
  3. برای مثال «شکل‌ها»، یک زیرکلاس جدید از Shape تعریف کنید برای پیاده‌سازی مثلث متساوی‌الاضلاع. فرض کنید فیلدهای x و y، مرکز مثلث را مشخص می‌کنند. متدهای لازم را برای آن بازنویسی کنید.
  4. به کلاس‌های مثال «شکل‌ها»، متدی برای محاسبه‌ی مساحت اضافه کنید.
  5. در نرم‌افزارهای گرافیکی، معمولاً پاره‌خط را هم نوعی شکل محسوب می‌کنند. کلاسی برای این کار از Shape مشتق کنید. فیلدها، متدها و سازنده‌های لازم را برای آن تعریف کنید. اگر مانند سؤال قبل محاسبه‌ی مساحت هم جزء برنامه‌ی شما باشد، چه تغییراتی لازم است در برنامه ایجاد کنیم؟ توجه کنید که مساحت یک پاره‌خط صفر نیست، بلکه تعریف نشده است.
  6. در یک شرکت دو نوع کارمند مشغول هستند. نوع اول، حقوق ساعتی دریافت می‌کنند. این دسته، نرخ دستمزد ثابتی برای هر ساعت دارند و حقوق آنها حاصل‌ضرب این نرخ در تعداد ساعت کار آنها می‌باشد. نوع دوم، حقوق ثابت دارند، به این ترتیب که برای ۱۴۰ ساعت کارکرد خود در ماه، مبلغ ثابتی دریافت می‌کنند و برای بیشتر از ۱۴۰ ساعت، به ازای هر ساعت ۵۰٪ اضافه دریافت می‌کنند. به عنوان مثال، اگر حقوق ثابت کارمندی ۱۴ میلیون ریال باشد، یعنی به ازای هر ساعت صدهزار ریال دریافت می‌کند. حال اگر چنین فردی ۱۸۰ ساعت کار کند، برای ۱۴۰ ساعت اول ۱۴ میلیون ریال و برای ۴۰ ساعت اضافه‌کار، ساعتی صد و پنجاه هزار ریال دریافت می‌کند. در نتیجه کل درآمد این شخص برای ۱۸۰ ساعت بیست میلیون ریال خواهد بود. یک کلاس مجرد کارمند تعریف کنید که دو زیرکلاس برای دو نوع کارمند ساعتی و ثابت داشته باشد. متد int earnings(int hours) را برای این کلاس‌ها تعریف کنید. فیلدها، سازنده‌ها و متدهای مورد نیاز را نیز برای این کلاس‌ها تعریف کنید.
  7. در ادامه‌ی سؤال قبل، کلاسی به نام Organization (سازمان) تعریف کنید که تعداد کارمند (از هر دو نوع) را دارد، و احتمالاً تعدادی زیرسازمان (که خودشان از نوع سازمان هستند). متدی به شکل int total_earnings(int avg_hrs) برای کلاس سازمان تعریف کنید که مجموع درآمد تمام افراد سازمان (و زیرسازمان‌های آن) را برمی‌گرداند اگر تمام کارمندها avg_hrs ساعت در یک ماه کار کرده باشند.
  8. این کلاس‌ها را درنظر بگیرید:

    class A {
    public:
      void f() { cout << "A::f"; }
      virtual void g() { cout << "A::g"; }
      void h() { cout << "A::h"; f(); g(); }
    };

    class B : public A {
    public:
      void f() { cout << "B::f"; }
      void g() { cout << "B::g"; }
      void h() { cout << "B::h"; f(); g(); }
    };

    نتیجه‌ی قطعه کد زیر را (بدون اجرا کردن) مشخص کنید:

    A a;
    B b;
    A* ap = &b;
    ap->f();
    ap->g();
    ap->h();

  9. خطاهای کامپایل برنامه‌ی زیر را (بدون کامپایل کردن) مشخص کنید:

    #include <iostream>
    using namespace std;

    class A {
    public:
      void f() { cout << "A::f"; }
      virtual void g() = 0;
      void h() { cout << "A::h"; f(); g(); }
    private:
      int a;
      void k() { cout << "A::k"; }
    };

    class B : public A {
    public:
      int f() { cout << "B::f"; return 0; }
      int f(int x) { return x + a; }
      int k() { cout << "B::k"; }
    protected:
      void h() { cout << "B::h"; }
    };

    class C : public A {
    public:
      void g() { cout << "C::g"; }
    };

    int main() {
      A a;
      B b;
      C c;
      A* ap = &c;
      ap->g();
      B* bp = &c;
      bp->g();
    }