Technology will change our live

Bài Tập Lập Trình Hướng Đối Tượng C++


Đây là bài viết cuối cùng trong Series Lập Trình Hướng Đối Tượng. Và trong bài viết này chúng ta sẽ làm một số bài tập lập trình hướng đối tượng tổng hợp nhé.
Sau đây, chúng ta sẽ cùng nhau đi làm 1 bài tập lập trình hướng đối tượng khá cơ bản:

Bài Tập Lập Trình Hướng Đối Tượng Cơ Bản

Bài Tập Xây Dựng Lớp Phân Số

Đề bài:
Xây dựng lớp Phanso gồm:
  • Thuộc Tính: tuso, mauso.
  • Phương thức:
    • Hàm Khởi Tạo Không Tham Số, Hàm Hủy
    • Nhập , Xuất
    • Cong(), Tru(), Nhan(), Chia()
Tính Tổng, Hiệu, Tích, Thương 2 phân số obj1obj2 rồi in kết quả ra màn hình

Code xây dựng class Phân số:


#include <bits/stdc++.h>
using namespace std;

class Phanso
{
private:
    int tuso, mauso;

public:
    Phanso()
    {
        tuso = 0;
        mauso = 1;
    }

    ~Phanso()
    {
        tuso = 0;
        mauso = 1;
    }
//-----------------------------------------------------------------//
    void set()
    {
        cout << "Nhap Tu So: "; cin >> this->tuso;
        cout << "Nhap Mau So: "; cin >> this-> mauso;
    }

    void get()
    {
        cout << this->tuso << "/" << this->mauso << endl;
    }
//-----------------------------------------------------------------//
    Phanso Cong(Phanso obj1, Phanso obj2)
    {
        Phanso obj3;
        obj3.tuso = obj1.tuso * obj2.mauso + obj1.mauso * obj2.tuso;
        obj3.mauso = obj1.mauso * obj2.mauso;
        return obj3;
    }
    Phanso Tru(Phanso obj1, Phanso obj2)
    {
        Phanso obj3;
        obj3.tuso = obj1.tuso * obj2.mauso - obj1.mauso * obj2.tuso;
        obj3.mauso = obj1.mauso * obj2.mauso;
        return obj3;
    }
    Phanso Nhan(Phanso obj1, Phanso obj2)
    {
        Phanso obj3;
        obj3.tuso = obj1.tuso * obj2.tuso;
        obj3.mauso = obj1.mauso * obj2.mauso;
        return obj3;
    }
    Phanso Chia(Phanso obj1, Phanso obj2)
    {
        Phanso obj3;
        obj3.tuso = obj1.tuso * obj2.mauso;
        obj3.mauso = obj1.mauso * obj2.tuso;
        return obj3;
    }
};

int main()
{
    Phanso obj1, obj2, obj3;
    obj1.set(); obj2.set();

    obj3 = obj3.Cong(obj1, obj2); obj3.get();
    obj3 = obj3.Tru(obj1, obj2); obj3.get();
    obj3 = obj3.Nhan(obj1, obj2); obj3.get();
    obj3 = obj3.Chia(obj1, obj2); obj3.get();

    return 0;
}

Input:

1 2
3 4

Output

10/8
-2/8
3/8
4/6

Bài Tập Quản Lý Vận Động Viên

Đề bài
Xây dựng lớp vận động viên VanDongVien gồm:
  • Thuộc tính: hoten (chuỗi ký tự), tuoi (số nguyên), monthidau (chuỗi ký tự), cannang (số thực), chieucao (số thực).
  • Phương thức:
    • Thiết lập không tham số.
    • Thiết lập 5 tham số
    • Hủy bỏ
    • Nạp chồng toán tử nhập >>
    • Nạp chồng toán tử xuất <<
    • Nạp chồng toán tử so sánh > (một vận động viên là lớn hơn nếu chiều cao lớn hơn,
      trong trường hợp chiều cao bằng nhau thì xét cân nặng lớn hơn)
Xây dựng chương trình chính:
  • Khai báo p là đối tượng lớp Vandongvien (sử dụng hàm thiết lập 5 tham số), hiển thị thông tin của p ra màn hình.
  • Nhập vào một mảng gồm n vận động viên.
  • Hiển thị danh sách đã nhập ra màn hình.
  • Sắp xếp mảng đã nhập theo thứ tự tăng dần, hiển thị danh sách đã sắp ra màn hình.
Lời Giải:


#include <bits/stdc++.h>
using namespace std;

class VanDongVien
{
protected:
    string hoten, monthidau;
    int tuoi;
    float cannang, chieucao;

public:
    VanDongVien()
    {
        this->hoten = this->monthidau = "";
        this->tuoi = 0;
        this->cannang = this->chieucao = 0;
    }
    
    VanDongVien(string hoten, string monthidau, int tuoi, float cannang, float chieucao)
    {
        this->hoten = hoten;
        this->monthidau = monthidau;
        this->tuoi = tuoi;
        this->cannang = cannang;
        this->chieucao = chieucao;
    }
    ~VanDongVien()
    {
        this->hoten = this->monthidau = "";
        this->tuoi = 0;
        this->cannang = this->chieucao = 0;
    }
    //----------------------------------------------//
    friend istream &operator>>(istream &is, VanDongVien &obj)
    {
        cin.ignore();
        cout << "Nhap Ho Ten: "; fflush(stdin); getline(is, obj.hoten);
        cout << "Nhap Mon Thi Dau: "; fflush(stdin); getline(is, obj.monthidau);
        cout << "Nhap Tuoi: "; is >> obj.tuoi;
        cout << "Nhap Can Nang: "; is >> obj.cannang;
        cout << "Nhap Chieu Cao: "; is >> obj.chieucao;
        return is;
    }

    friend ostream &operator<<(ostream &os, VanDongVien obj)
    {
        cout << "Ho Ten: " << obj.hoten << endl;
        cout << "Mon Thi Dau: " << obj.monthidau << endl;
        cout << "Tuoi: " << obj.tuoi << endl;
        cout << "Can Nang: " << obj.cannang << endl;
        cout << "Chieu cao: " << obj.chieucao << endl;
        return os;
    }

    bool operator > (const VanDongVien &obj)
    {
        if (this->chieucao > obj.chieucao)
            return true;
        else if (this->chieucao < obj.chieucao)
            return false;
        else if (this->cannang > obj.cannang)
            return true;
        else
            return false;
    }
};

void swap(VanDongVien &a, VanDongVien &b)
{
    VanDongVien temp = a;
    a = b;
    b = temp;
}

void Bubblesort(VanDongVien arr[], int n)
{
    for (int i = 0; i < n - 1; i++)
        for (int j = 0; j < n - i - 1; j++)
            if (arr[j] > arr[j + 1])
                swap(arr[j], arr[j + 1]);
}

int main()
{  
    VanDongVien p("Nguyen Van A", "Bong Da", 20, 178, 70.5);
    cout << p;
    


    cout << "Nhap So Luong: "; int n; cin >> n;
    VanDongVien *arr = new VanDongVien[n];

    for (int i = 0; i < n; ++i) cin >> arr[i];

    cout << endl << endl;
    for (int i = 0; i < n; ++i) cout << arr[i] << endl;
    
    cout << "Sort" << endl;
    Bubblesort(arr,n);
    for (int i = 0; i < n; ++i) cout << arr[i] << endl;

    return 0;
}

Input

3
Nguyen Van B
Cau Long
20
80
190
Nguyen Van C
Bong Chuyen
21
78
188
Nguyen Van D
Boi Loi
19
81
188

Output

Ho Ten: Nguyen Van A
Mon Thi Dau: Bong Da
Tuoi: 20
Can Nang: 178
Chieu cao: 70.5

Ho Ten: Nguyen Van B
Mon Thi Dau: Cau Long
Tuoi: 20
Can Nang: 80
Chieu cao: 190

Ho Ten: Nguyen Van C
Mon Thi Dau: Bong Chuyen
Tuoi: 21
Can Nang: 78
Chieu cao: 188

Ho Ten: Nguyen Van D
Mon Thi Dau: Boi Loi
Tuoi: 19
Can Nang: 81
Chieu cao: 188

Sort
Ho Ten: Nguyen Van C
Mon Thi Dau: Bong Chuyen
Tuoi: 21
Can Nang: 78
Chieu cao: 188

Ho Ten: Nguyen Van D
Mon Thi Dau: Boi Loi
Tuoi: 19
Can Nang: 81
Chieu cao: 188

Ho Ten: Nguyen Van B
Mon Thi Dau: Cau Long
Tuoi: 20
Can Nang: 80
Chieu cao: 190

Bài Tập OOP Nâng Cao

Bài Tập Quản Lý Bán Vé Máy Bay

Đề bài
Xây dựng lớp Vemaybay gồm:
  • Thuộc tính: tenchuyen, ngaybay, giave
  • Phương thức:
    • Cấu tử
    • Hủy
    • Nhap
    • Xuat
    • getgiave() : hàm trả về giá vé
Xây dựng lớp Nguoi gồm:
  • Thuộc tính: hoten, gioitinh, tuoi
  • Phương thức:
    • Cấu tử
    • Hủy
    • Nhập
    • Xuất
Xây dựng lớp Hanhkhach (mỗi hành khách được mua nhiều vé) kế thừa lớp Nguoi bổ
sung thêm:
  • Thuộc tính: Vemaybay *ve; int soluong;
  • Phương thức:
    • Cấu tử
    • Hủy
    • Nhập
    • Xuất
    • tongtien(): trả về Tổng số tiền phải trả của hành khách
Chương trình chính: Nhập vào 1 danh sách n hành khách (n nhập từ bàn phím).
Hiển thị danh sách hành khách và số tiền phải trả tương ứng của mỗi khách hàng.
Sắp xếp danh sách hành khách theo chiều giảm dần của Tổng tiền.

Lời Giải


#include <bits/stdc++.h>
using namespace std;

class Date
{
protected:
    int day, month, year;

public:
    Date()
    {
        this->day = this->month = this->year = 0;
    }
    ~Date()
    {
        this->day = this->month = this->year = 0;
    }
    void input()
    {
        cout << "Nhap Ngay: ";
        cin >> this->day;
        cout << "Nhap Thang: ";
        cin >> this->month;
        cout << "Nhap Nam: ";
        cin >> this->year;
    }
    void output()
    {
        cout << "Ngay/Thang/Nam: " << this->day << "/" << this->month << "/" << this->year << endl;
    }
};
//-------------------------------------------------------------//
class Vemaybay
{
protected:
    string tenchuyen;
    Date ngaybay;
    int giave;

public:
    Vemaybay()
    {
        this->tenchuyen = "";
        this->giave = 0;
    }
    ~Vemaybay()
    {
        this->tenchuyen = "";
        this->giave = 0;
    }
    void input()
    {
        cin.ignore();
        cout << "Nhap Ten Chuyen: "; fflush(stdin); getline(cin, this->tenchuyen);
        cout << "Nhap Ngay Bay: " << endl;
        ngaybay.input();
        cout << "Nhap Gia Ve: "; cin >> this->giave;
    }
    void output()
    {
        cout << "Ten Chuyen: " << this->tenchuyen << endl;
        cout << "Ngay Bay: " << endl << "\t";
        this->ngaybay.output();
        cout << "Gia Ve: " << this->giave << endl;
    }
    int getgiave()
    {
        return this->giave;
    }
};

class Nguoi
{
protected:
    string hoten, gioitinh;
    int tuoi;

public:
    Nguoi()
    {
        this->hoten = this->gioitinh = "";
        this->tuoi = 0;
    }
    ~Nguoi()
    {
        this->hoten = this->gioitinh = "";
        this->tuoi = 0;
    }
    void input()
    {
        cin.ignore();
        cout << "Nhap Ho Ten: "; fflush(stdin); getline(cin, this->hoten);
        cout << "Nhap Gioi Tinh: "; fflush(stdin); getline(cin, this->gioitinh);
        cout << "Nhap Tuoi: "; cin >> this->tuoi;
    }
    void output()
    {
        cout << "Ho Ten: " << this->hoten << endl;
        cout << "Gioi Tinh: " << this->gioitinh << endl;
        cout << "Tuoi: " << this->tuoi << endl;
    }
};

class Hanhkhach : public Nguoi
{
protected:
    int soluong;
    //Vemaybay *ve;
    Vemaybay ve[1000];
    int tongtien;

public:
    Hanhkhach()
    {
        this->soluong = 0;
        //this->ve = new Vemaybay[this->soluong];
        ve[this->soluong];
        tongtien = 0;
    }
    ~Hanhkhach()
    {
        soluong = 0;
        delete []ve;
        tongtien = 0;
    }
    void input()
    {
        Nguoi :: input();
        cout << "Nhap So Luong Ve Hanh Khach Da Mua: "; cin >> this->soluong;
        //ve = new Vemaybay[this->soluong];
        ve[this->soluong];
        for (int i = 0; i < this->soluong; ++i)
        {
            ve[i].input();
            tongtien += ve[i].getgiave();
        }
    }
    void output()
    {
        cout << "- Thong Tin Khach Hang: " << endl;
        Nguoi :: output();
        cout << "- Thong Tin Chuyen Bay: " << endl;
        for (int i = 0; i < this->soluong; ++i)
        {
            ve[i].output();
            cout << endl;
        }
        cout << "==> Tong Tien = " << this->tongtien;
        cout << endl;
    }

    bool operator < (const Hanhkhach &obj)
    {
        if (this->tongtien < obj.tongtien) return true;
        else return false;
    }
};
//----------------------------------------------------------//

void swap(Hanhkhach &a, Hanhkhach &b)
{
    Hanhkhach temp = a;
    a = b;
    b = temp;
}


void Bubblesort(Hanhkhach arr[], int n)
{
    for (int i = 0; i < n - 1; ++i)
        for (int j = 0; j < n - i - 1; ++j)
            if (arr[j] < arr[j + 1])
                swap(arr[j], arr[j + 1]);
}
//----------------------------------------------------------//
int main()
{
    cout << "Nhap So Luong Khach Hang: "; int n; cin >> n;
    Hanhkhach *arr = new Hanhkhach[n];
    for (int i = 0; i < n; ++i) arr[i].input();
    cout << endl << endl << "Output" << endl << endl;
    for (int i = 0; i < n; ++i)
    {
        arr[i].output();
        cout << endl << "------------------" << endl << endl;
    }

    cout << "After Sort: " << endl;
    Bubblesort(arr, n);
    for (int i = 0; i < n; ++i)
    {
        arr[i].output();
        cout << endl << "------------------" << endl << endl;
    }
    return 0;
}

Input

3
Nguyen Van A
Nam
20
2
Ha Noi - Hai Phong
10 2 2020
500000
Hai Phong - Ha Noi
15 2 2020
450000
Nguyen Van B
Nam
21
1
Ha Noi - TP.Ho Chi Minh
20 2 2020
1500000
Nguyen Thi C
Nu
19
3
Ha Noi - Da Nang
19 2 2020
1200000
Ha Noi - Hue
18 2 2020
1250000
Hue - Da Nang
22 2 2020
500000

Output

Output

- Thong Tin Khach Hang: 
Ho Ten: Nguyen Van A
Gioi Tinh: Nam
Tuoi: 20
- Thong Tin Chuyen Bay: 
Ten Chuyen: Ha Noi - Hai Phong
Ngay Bay: 
 Ngay/Thang/Nam: 10/2/2020
Gia Ve: 500000

Ten Chuyen: Hai Phong - Ha Noi
Ngay Bay: 
 Ngay/Thang/Nam: 15/2/2020
Gia Ve: 450000

==> Tong Tien = 950000

------------------

- Thong Tin Khach Hang: 
Ho Ten: Nguyen Van B
Gioi Tinh: Nam
Tuoi: 21
- Thong Tin Chuyen Bay: 
Ten Chuyen: Ha Noi - TP.Ho Chi Minh
Ngay Bay: 
 Ngay/Thang/Nam: 20/2/2020
Gia Ve: 1500000

==> Tong Tien = 1500000

------------------

- Thong Tin Khach Hang: 
Ho Ten: Nguyen Thi C
Gioi Tinh: Nu
Tuoi: 19
- Thong Tin Chuyen Bay: 
Ten Chuyen: Ha Noi - Da Nang
Ngay Bay: 
 Ngay/Thang/Nam: 19/2/2020
Gia Ve: 1200000

Ten Chuyen: Ha Noi - Hue
Ngay Bay: 
 Ngay/Thang/Nam: 18/2/2020
Gia Ve: 1250000

Ten Chuyen: Hue - Da Nang
Ngay Bay: 
 Ngay/Thang/Nam: 22/2/2020
Gia Ve: 500000

==> Tong Tien = 2950000

------------------

After Sort: 
- Thong Tin Khach Hang: 
Ho Ten: Nguyen Thi C
Gioi Tinh: Nu
Tuoi: 19
- Thong Tin Chuyen Bay: 
Ten Chuyen: Ha Noi - Da Nang
Ngay Bay: 
 Ngay/Thang/Nam: 19/2/2020
Gia Ve: 1200000

Ten Chuyen: Ha Noi - Hue
Ngay Bay: 
 Ngay/Thang/Nam: 18/2/2020
Gia Ve: 1250000

Ten Chuyen: Hue - Da Nang
Ngay Bay: 
 Ngay/Thang/Nam: 22/2/2020
Gia Ve: 500000

==> Tong Tien = 2950000

------------------

- Thong Tin Khach Hang: 
Ho Ten: Nguyen Van B
Gioi Tinh: Nam
Tuoi: 21
- Thong Tin Chuyen Bay: 
Ten Chuyen: Ha Noi - TP.Ho Chi Minh
Ngay Bay: 
 Ngay/Thang/Nam: 20/2/2020
Gia Ve: 1500000

==> Tong Tien = 1500000

------------------

- Thong Tin Khach Hang: 
Ho Ten: Nguyen Van A
Gioi Tinh: Nam
Tuoi: 20
- Thong Tin Chuyen Bay: 
Ten Chuyen: Ha Noi - Hai Phong
Ngay Bay: 
 Ngay/Thang/Nam: 10/2/2020
Gia Ve: 500000

Ten Chuyen: Hai Phong - Ha Noi
Ngay Bay: 
 Ngay/Thang/Nam: 15/2/2020
Gia Ve: 450000

==> Tong Tien = 950000

------------------

Trong bài tập lập trình hướng đối tượng số 3 này, mình có 1 lưu ý nhỏ.
Nếu không sắp xếp các hành khách theo tổng tiền, thì mình vẫn có thể khai báo ve là một con trỏ kiểu Vemaybay, nhưng khi mình thực hiện sắp xếp, mình phải đổi chỗ các ô nhớ chứa hành khách. Nếu vẫn khai báo Vemaybay *ve, nghĩa là ve sẽ được lưu trong bộ nhớ chỉ đọc, nên khi đổi chỗ sẽ gây lỗi Segmentation Fault.
Lỗi Phân Đoạn - Bài Tập C++
Bạn có thể xem thêm về lỗi này tại đây
Bài tổng hợp các bài tập lập trình hướng đối tượng từ cơ bản đến nâng cao của mình đến đây là hết rồi. Cảm ơn tất cả mọi người đã ủng hộ mình trong thời gian qua. Chào mọi người, và có thể sẽ hẹn gặp lại mọi người trong một dịp gần nhất.

Các bạn có thể xem thêm lời giải và các bài tập lập trình hướng đối tượng khác tại:
  1. Bài tập lập trình C
  2. Bài tập lập trình hướng đối tượng C++
Share:

Tính Đa Hình Trong Lập Trình Hướng Đối Tượng


Trong 2 bài trước chúng ta đã cùng tìm hiểu về tính kế thừa và thực hành làm bài tập về nó. Trong bài này, ta sẽ tiếp tục tìm hiểu thêm 1 tính chất cũng quan trọng không kém đó là tính đa hình trong lập trình hướng đối tượng nhé.

Tính đa hình là gì?

Từ đa hình có nghĩa là có nhiều dạng. Nói một cách đơn giản, chúng ta có thể định nghĩa đa hình là khả năng của một thông điệp được hiển thị dưới nhiều dạng.
Mình lấy một ví dụ thực thế nhé:
Một người cùng một lúc có thể có đặc điểm khác nhau. Giống như một người đàn ông đồng thời là một người cha, một người chồng, một nhân viên. Vì vậy, cùng một người sở hữu những hành vi khác nhau trong các tình huống khác nhau. Điều này được gọi là đa hình.
Đa hình được coi là một trong những tính năng quan trọng của Lập trình hướng đối tượng.

Phân loại đa hình

Trong ngôn ngữ C ++, tính đa hình chủ yếu được chia thành hai loại:
  • Compile time Polymorphism.
  • Runtime Polymorphism.
Tính Đa Hình

Compile time Polymorphism

Tính đa hình này được sử dụng bằng cách nạp chồng hàm hoặc nạp chồng toán tử.
Các bạn có thể xem lại về nạp chồng hàm và nạp chồng toán tử: Tại Đây

Nạp chồng hàm

#include <bits/stdc++.h>
using namespace std;

class OOP
{
public:
    // Hàm có một tham số
    void func(int x)
    {
        cout << "value of x is " << x << endl;
    }

    // Hàm cùng tên có một tham số nhưng khác kiểu
    void func(double x)
    {
        cout << "value of x is " << x << endl;
    }

    // Hàm cùng tên nhưng có 2 tham số
    void func(int x, int y)
    {
        cout << "value of x and y is " << x << ", " << y << endl;
    }
};

int main()
{

    OOP obj;

    obj.func(7);
    obj.func(9.132);
    obj.func(85, 64);
    return 0;
}

Sau khi biên dịch và chạy chương trình, ta nhận được kết quả:

value of x is 7
value of x is 9.132
value of x and y is 85, 64

Trong ví dụ trên, ta chỉ dùng một hàm duy nhất có tên là func nhưng có thể dùng được cho 3 tình huống khác nhau. Đây là một thể hiện của tính đa hình.

Nạp chồng toán tử

#include <bits/stdc++.h>
using namespace std;

class SoPhuc
{
private:
    int thuc, ao;

public:
    SoPhuc(int thuc = 0, int ao = 0)
    {
        this->thuc = thuc;
        this->ao = ao;
    }

    ~SoPhuc()
    {
        this->thuc = 0;
        this->ao = 0;
    }

    SoPhuc operator+(SoPhuc const &obj)
    {
        SoPhuc res;
        res.thuc = thuc + obj.thuc;
        res.ao = ao + obj.ao;
        return res;
    }

    void print() { cout << this->thuc << " + " << this->ao << "i" << endl; }
};

int main()
{
    SoPhuc c1(10, 5), c2(2, 4);
    SoPhuc c3 = c1 + c2;
    c3.print();

    return 0;
}

Trong ví dụ trên, ta đã nạp chồng lại toán tử cộng.
Định nghĩa của toán tử cộng chỉ dùng cho số nguyên int, nhưng sau khi nạp chồng lại, ta có thể sử dụng chúng cho số phức.
Đây cũng là một thể hiện của tính đa hình.

Runtime Polymorphism

Tính đa hình được thể hiện ở cách nạp chồng toán tử trong kế thừa.

#include <bits/stdc++.h>
using namespace std;

class base
{
public:
    virtual void print()
    {
        cout << "print base class" << endl;
    }

    void show()
    {
        cout << "show base class" << endl;
    }
};

class derived : public base
{
public:
    void print()
    {
        cout << "print derived class" << endl;
    }

    void show()
    {
        cout << "show derived class" << endl;
    }
};
int main()
{
    base *bptr;
    derived d;
    bptr = &d;

    bptr->print();
    bptr->show();

    return 0;
}

Sau khi biên dịch và chạy chương trình ta có kết quả

print derived class
show base class

Tại sao lại có sự khác biệt ấy? Tại sao cùng là nạp chồng toán tử trong lớp kế thừa nhưng kết quả lại khác nhau?
Trong ví dụ trên mình đã thêm từ khóa virtual vào hàm print() trong lớp cơ sở base.
Từ khóa virtual này dùng để khai báo một hàm là hàm ảo.
Khi khai báo hàm ảo với từ khóa virtual nghĩa là hàm này sẽ được gọi theo loại đối tượng được trỏ (hoặc tham chiếu), chứ không phải theo loại của con trỏ (hoặc tham chiếu). Và điều này dẫn đến kết quả khác nhau:
  • Nếu không khai báo hàm ảo virtual trình biên dịch sẽ gọi hàm tại lớp cở sở base
  • Nếu dùng hàm ảo virtual trình biên dịch sẽ gọi hàm tại lớp dẫn xuất derived
Mục đích của hàm ảo là gì?
Các hàm ảo sẽ cho phép chúng ta tạo một danh sách các con trỏ lớp cơ sở và các phương thức của bất kỳ lớp dẫn xuất nào mà không cần biết loại đối tượng của lớp dẫn xuất.
Mình lấy một ví dụ cụ thể nhé:
Ta sẽ bắt đầu với một phần mềm quản lý nhân viên.
Đầu tiên, ta sẽ xây dựng một lớp Nhanvien sau đó xây dựng các hàm ảo tangluong(), chuyenphong(), ...
Từ lớp Nhanvien này ta sẽ cho kế thừa tới các lớp Baove, NhanvienphongA, NhanvienphongB, ... Và tất nhiên các lớp này có thể triển khai riêng biệt các hàm ảo có tại lớp cơ sở Nhanvien.
Trình biên dịch sẽ thực hiện Runtime Polymorphism như thế nào?
Trình biên dịch sẽ duy trì:
  • vtable: Đây là một bảng các con trỏ hàm được duy trì cho mỗi lớp
  • vptr: Đây là một con trỏ tới vtable và được duy trì cho mỗi một đối tượng.
    Bạn có thể xem một ví dụ đơn giản: Tại đây
Runtime Polymorphism
Trình biên dịch sẽ thêm code bổ sung tại 2 chỗ là:
Code trong mỗi hàm khởi tạo. Nó sẽ khởi tạo vptr của đối tượng được tạo và đăt vptr trỏ đến vtable của lớp.
Code với lệnh gọi hàm ảo. Tại bất cứ chỗ nào tính đa hình được thực hiện, trình biên dịch sẽ chèn code để tìm vptr trước bằng cách sử dụng con trỏ hoặc tham chiếu lớp cơ sở. Khi vptr được nạp, vptable của lớp dẫn xuất có thể được truy cập. Sử dụng vtable, địa chỉ của hàm ảo tại lớp dẫn xuất sẽ được truy cập và gọi.
Vậy thì đây có phải là một cách thực hiện Runtime polymorphism chuẩn hay không?
Trên thực tế thì C++ không bắt buộc một Runtime polymorphism chạy chính xác như thế này. Nhưng trình biên dịch thường sử dụng các mô hình với biến thể nhỏ, dựa trên mô hình cơ bản bên trên.
Hàm Pure Virtual trong C++
Với Pure Virtual nghĩa là bạn chỉ dùng hàm ảo tại lớp cơ sở để khai báo, chứ không có bất kì câu lệnh nào bên trong hàm đó.

#include <bits/stdc++.h>
using namespace std;

class base
{
public:
    virtual void print(); // Pure Virtual
    void show()
    {
        cout << "show base class" << endl;
    }
};

class derived : public base
{
public:
    void print()
    {
        cout << "print derived class" << endl;
    }

    void show()
    {
        cout << "show derived class" << endl;
    }
};

int main()
{
    base *bptr;
    derived d;
    bptr = &d;

    bptr->print();
    bptr->show();

    return 0;
}

Trong đoạn code trên, mình đã sửa hàm print() thành một Pure Virtual và tất nhiên kết quả vẫn không hề thay đổi.
Bài viết của mình đến đây là hết rồi, mình rất mong nhận được những ý kiến của các bạn để bài viết của mình ngày một tốt hơn. Vì thế đừng ngần ngại comment bất kì thắc mắc, hay đóng góp nào tại phần bình luận ngay phía dưới nhé. Cảm ơn mọi người rất nhiều. Hẹn gặp lại các bạn trong bài viết tiếp theo.

Tài liệu tham khảo
  1. Geeksforgeeks
Share:

Bài Tập C++ Về Tính Kế Thừa


Chào tất cả mọi người. Trong bài trước thì mình đã chia sẻ tới mọi người về tính kế thừa trong lập trình hướng đối tượng. Trong bài này chúng ta cùng làm một số bài tập C++ để thực hành về nó nhé

Bài Tập Quản Lý Sinh Viên

Đề bài

Tạo class People gồm:
  • Thuộc tính: name, age,address để lưu lần lượt các giá trị tên, tuổi và địa chỉ.
  • Phương thức trong class People gồm: set(), get() là hàm nhập và xuất; hàm khởi tạo không tham số và hàm huỷ.
Tạo class Students kế thừa từ class People.
Class Students sẽ có thêm:
  • Thuộc tính id để lưu mã sinh viên, math lưu điểm môn toán, physical để lưu điểm môn vật lý, chemistry để lưu điểm môn hoá học.
  • Phương thức: set(), get(), GPA() để tính điểm trung bình 3 môn học.

Lời giải

Với những bài tập dạng này, chúng ta sẽ chia từng phần một để làm.
Ở đây mình sẽ tạo class People trước.

class People
{
protected:
    string name, address;
    int age;

public:
    People()
    {
        name = address = "";
        age = 0;
    }
    ~People()
    {
        name = address = "";
        age = 0;
    }
    void set()
    {
        cout << "Input" << endl;
        fflush(stdin);
        cout << "Name: ";
        fflush(stdin);
        getline(cin, this->name);
        cout << "Address: ";
        fflush(stdin);
        getline(cin, this->address);
        cout << "Age: ";
        cin >> this->age;
    }

    void get()
    {
        cout << "Output" << endl;
        cout << "Name: " << this->name << endl;
        cout << "Address: " << this->address << endl;
        cout << "Age: " << this->age << endl;
    }
};

Tiếp theo đó mình sẽ tạo tiếp class Students


class Students : public People
{
private:
    string id;
    int math, physical, chemistry;

public:
    Students()
    {
        id = "";
        math = physical = chemistry = 0;
    }

    ~Students()
    {
        id = "";
        math = physical = chemistry = 0;
    }

    void set()
    {
        People ::set();
        cout << "ID: ";
        cin >> this->id;
        cout << "Math: ";
        cin >> this->math;
        cout << "Physical: ";
        cin >> this->physical;
        cout << "Chemistry: ";
        cin >> this->chemistry;
    }

    void get()
    {
        People ::get();
        cout << "ID: " << this->id << endl;
        cout << "Math: " << this->math << endl;
        cout << "Physical: " << this->physical << endl;
        cout << "Chemistry: " << this->chemistry << endl;
        cout << "GPA = " << GPA();
    }

    float GPA()
    {
        return (this->math + this->physical + this->chemistry) / 3;
    }
};

Trong class Students, tại 2 phương thức set()get() thay vì việc viết lại code để nhập tên, tuổi, địa chỉ, mình đã gọi lại hàm set()get() trong class cha của nó.
Để gọi phương thức từ một lớp, mình đã sử dụng toán tử ::.
Các bạn có thể xem lại Tại đây
Code hoàn chỉnh:

#include <bits/stdc++.h>
using namespace std;

class People
{
protected:
    string name, address;
    int age;

public:
    People()
    {
        name = address = "";
        age = 0;
    }
    ~People()
    {
        name = address = "";
        age = 0;
    }
    void set()
    {
        cout << "Input" << endl;
        fflush(stdin);
        cout << "Name: ";
        fflush(stdin);
        getline(cin, this->name);
        cout << "Address: ";
        fflush(stdin);
        getline(cin, this->address);
        cout << "Age: ";
        cin >> this->age;
    }

    void get()
    {
        cout << "Output" << endl;
        cout << "Name: " << this->name << endl;
        cout << "Address: " << this->address << endl;
        cout << "Age: " << this->age << endl;
    }
};

class Students : public People
{
private:
    string id;
    int math, physical, chemistry;

public:
    Students()
    {
        id = "";
        math = physical = chemistry = 0;
    }

    ~Students()
    {
        id = "";
        math = physical = chemistry = 0;
    }

    void set()
    {
        People ::set();
        cout << "ID: ";
        cin >> this->id;
        cout << "Math: ";
        cin >> this->math;
        cout << "Physical: ";
        cin >> this->physical;
        cout << "Chemistry: ";
        cin >> this->chemistry;
    }

    void get()
    {
        People ::get();
        cout << "ID: " << this->id << endl;
        cout << "Math: " << this->math << endl;
        cout << "Physical: " << this->physical << endl;
        cout << "Chemistry: " << this->chemistry << endl;
        cout << "GPA = " << GPA();
    }

    float GPA()
    {
        return (this->math + this->physical + this->chemistry) / 3;
    }
};

int main()
{
    Students obj;
    obj.set();
    cout << endl;
    obj.get();
}

Input

Nguyen Van A
Ha Noi
20
1
7 8 9

Output

Output
Name: Nguyen Van A
Address: Ha Noi
Age: 20
ID: 1
Math: 7
Physical: 8
Chemistry: 9
GPA = 8

Bài Tập Màu Sắc

Đề bài

Xây dựng lớp Color gồm:
Thuộc tính: TenMau, MaMau
Phương thức:
  • Cấu tử không tham số
  • Cấu tử có tham số
  • Hủy
  • Nạp chồng toán tử nhập
  • Nạp chồng toán tử xuất
  • getTenMau() : hàm trả về TenMau
Xây dựng lớp Point gồm:
Thuộc tính: int x, y
Phương thức:
  • Cấu tử không tham số
  • Cấu tử có tham số
  • Hủy
  • Nạp chồng toán tử nhập
  • Nạp chồng toán tử xuất
  • CheoChinh : hàm kiểm tra Point có thuộc đường chéo chính hay không (1 điểm thuộc đường chéo chính khi và chỉ khi tung độ bằng hoành độ).
Xây dựng lớp Pixel kế thừa từ lớp Color và Point bao gồm thêm:
Phương thức:
  • Cấu tử không tham số
  • Cấu tử có tham số
  • Nạp chồng toán tử nhập
  • Nạp chồng toán tử xuất
  • KiemTra: hàm kiểm tra Pixel thuộc đường chéo chính và có màu “Xanh” hay không?
    Chương trình chính: Nhập vào từ bàn phím n Pixel (n nhập từ bàn phím). Hiển thị thông
    tin các Pixel thuộc đường chéo chính và có màu xanh.

Lời Giải

Trước tiên chúng ta cũng chia bài tập này ra thành các phần nhỏ
Phần đầu tiên chúng ta sẽ tạo lớp Color


class Color
{
protected:
 string tenmau, mamau;

public:
 Color()
 {
  tenmau = mamau = "";
 }
 Color(string tenmau, string mamau)
 {
  this->tenmau = tenmau;
  this->mamau = mamau;
 }
 ~Color()
 {
  tenmau = mamau = "";
 }

 friend istream &operator>>(istream &is, Color &obj)
 {
  cout << "Nhap Ten Mau: "; fflush(stdin); getline(is, obj.tenmau);
  cout << "Nhap Ma Mau: "; fflush(stdin); getline(is, obj.mamau);
  return is;
 };

 friend ostream &operator<<(ostream &os, Color obj)
 {
  os << "Ten Mau: " << obj.tenmau << endl;
  os << "Ma Mau: " << obj.mamau << endl;
  return os;
 }
 string getTenMau()
 {
  return this->tenmau;
 }
};

Tiếp theo là tạo lớp Point


class Point
{
protected:
 int x, y;

public:
 Point()
 {
  x = y = 0;
 }
 Point(int x, int y)
 {
  this->x = x;
  this->y = y;
 }
 ~Point()
 {
  x = y = 0;
 }

 friend istream &operator>>(istream &is, Point &obj)
 {
  cout << "Nhap x, y";
  is >> obj.x >> obj.y;
  return is;
 };
 friend ostream &operator<<(ostream &os, Point obj)
 {
  os << "x = " << obj.x << endl;
  os << "y = " << obj.y << endl;
  return os;
 }

 bool CheoChinh()
 {
  if (x == y)
   return true;
  else
   return false;
 }
};

Cuối cùng là lớp Pixel kế thừa từ 2 lớp ColorPoint
class Pixel : public Color, public Point
{
public:
 Pixel()
 {
  x = y = 0;
  tenmau = mamau = "";
 }

 Pixel(int x, int y, string tenmau, string mamau)
 {
  this->x = x;
  this->y = y;
  this->tenmau = tenmau;
  this->mamau = mamau;
 }

 ~Pixel()
 {
  x = y = 0;
  tenmau = mamau = "";
 }
 friend istream &operator>>(istream &is, Pixel &obj)
 {
  cout << "Nhap x: "; is >> obj.x;
  cout << "Nhap y: "; is >> obj.y;
  cout << "Nhap Ten Mau: "; is >> obj.tenmau;
  cout << "Nhap Ma Mau: "; is >> obj.mamau;
  return is;
 }
 friend ostream &operator<<(ostream &os, Pixel obj)
 {
  os << "x = " << obj.x << endl;
  os << "y = " << obj.y << endl;
  os << "Ten Mau = " << obj.tenmau << endl;
  os << "Ma Mau = " << obj.mamau << endl;
  return os;
 }

 bool Check()
 {
  if (x == y && mamau == "#0000FF")
   return true;
  else
   return false;
 }
};

Code hoàn chỉnh cho bài tập

#include <bits/stdc++.h>
using namespace std;

class Color
{
protected:
 string tenmau, mamau;

public:
 Color()
 {
  tenmau = mamau = "";
 }
 Color(string tenmau, string mamau)
 {
  this->tenmau = tenmau;
  this->mamau = mamau;
 }
 ~Color()
 {
  tenmau = mamau = "";
 }

 friend istream &operator>>(istream &is, Color &obj)
 {
  cout << "Nhap Ten Mau: "; fflush(stdin); getline(is, obj.tenmau);
  cout << "Nhap Ma Mau: "; fflush(stdin); getline(is, obj.mamau);
  return is;
 };

 friend ostream &operator<<(ostream &os, Color obj)
 {
  os << "Ten Mau: " << obj.tenmau << endl;
  os << "Ma Mau: " << obj.mamau << endl;
  return os;
 }
 string getTenMau()
 {
  return this->tenmau;
 }
};

class Point
{
protected:
 int x, y;

public:
 Point()
 {
  x = y = 0;
 }
 Point(int x, int y)
 {
  this->x = x;
  this->y = y;
 }
 ~Point()
 {
  x = y = 0;
 }

 friend istream &operator>>(istream &is, Point &obj)
 {
  cout << "Nhap x, y";
  is >> obj.x >> obj.y;
  return is;
 };
 friend ostream &operator<<(ostream &os, Point obj)
 {
  os << "x = " << obj.x << endl;
  os << "y = " << obj.y << endl;
  return os;
 }

 bool CheoChinh()
 {
  if (x == y)
   return true;
  else
   return false;
 }
};

class Pixel : public Color, public Point
{
public:
 Pixel()
 {
  x = y = 0;
  tenmau = mamau = "";
 }

 Pixel(int x, int y, string tenmau, string mamau)
 {
  this->x = x;
  this->y = y;
  this->tenmau = tenmau;
  this->mamau = mamau;
 }

 ~Pixel()
 {
  x = y = 0;
  tenmau = mamau = "";
 }
 friend istream &operator>>(istream &is, Pixel &obj)
 {
  cout << "Nhap x: "; is >> obj.x;
  cout << "Nhap y: "; is >> obj.y;
  cout << "Nhap Ten Mau: "; is >> obj.tenmau;
  cout << "Nhap Ma Mau: "; is >> obj.mamau;
  return is;
 }
 friend ostream &operator<<(ostream &os, Pixel obj)
 {
  os << "x = " << obj.x << endl;
  os << "y = " << obj.y << endl;
  os << "Ten Mau = " << obj.tenmau << endl;
  os << "Ma Mau = " << obj.mamau << endl;
  return os;
 }

 bool Check()
 {
  if (x == y && mamau == "#0000FF")
   return true;
  else
   return false;
 }
};

int main()
{
 cout << "Nhap So Luong: "; int n; cin >> n;
 Pixel *arr = new Pixel[n];
 for (int i = 0; i < n; i++) cin >> arr[i];

 cout << "Output" << endl;
 for (int i = 0; i < n; i++)
  if (arr[i].Check() == true)
   cout << arr[i];
 return 0;
}
Input

3
1 2 Red #FF0000
2 2 Blue #0000FF
2 3 Yellow #FFFF00

Output
Output
x = 2
y = 2
Ten Mau = Blue
Ma Mau = #0000FF
x = 4
y = 5
Ten Mau = Xanh
Ma Mau = #Xanh

Bài viết của mình đến đây là hết rồi. Mình rất mong nhận được sự quan tâm cũng như những ý kiến đóng góp của các bạn để bài viết ngày một tốt hơn. Cảm ơn các bạn rất nhiều.

Nếu các bạn muốn xem thêm bài tập kèm lời giải thì có thể truy cập tại:
  1. Github
Share:

Lập Trình Hướng Đối Tượng Trong C++

Lập Trình Hướng Đối Tượng

Chào mọi người, đây là bài đầu tiên trong series Lập Trình Hướng Đối Tượng C++. Trong bài này chúng ta sẽ cùng đi tìm hiểu Lập Trình Hướng Đối Tượng là gì ? Những khái niệm và đặc điểm cơ bản của Lập Trình Hướng Đối Tượng.

Nội dung các bài trong Series

Phần 1: Lý Thuyết

  • Bài 1: Giới thiệu về lập trình hướng đối tượng ( bài hiện tại )
  • Bài 2: Lớp và đối tượng trong C++
  • Bài 3: Hàm khởi tạo và Hàm huỷ
  • Bài 4: Hàm bạn và Lớp bạn
  • Bài 5: Nạp chồng hàm và nạp chồng toán tử
  • Bài 6: Tính Kế Thừa trong lập trình hướng đối tượng
  • Bài 7: Tính Đa Hình trong lập trình hướng đối tượng

Phần 2: Bài Tập

  • Bài 8: Tạo lớp (class) trong C++
  • Bài 9: Bài tập về tính kế thừa
  • Bài 10: Bài tập tổng hợp từ cơ bản đến nâng cao

Lập trình Hướng đối tượng là gì?

Hướng Đối Tượng Là Gì?

Lập trình hướng đối tượng (Tiếng Anh: Object Oriented Programming, viết tắt: OOP) là một mẫu hình lập trình dựa trên khái niệm "công nghệ đối tượng".
Một đối tượng gồm:
  • Các dữ liệu gọi là: thuộc tính (Attribute).
  • Mã nguồn được tổ chức thành các phương thức (Method).
Các phương thức giúp cho đối tượng có thể truy xuất và hiệu chỉnh các trường dữ liệu của đối tượng khác, mà đối tượng hiện tại có tương tác (đối tượng được hỗ trợ các phương thức "this" hoặc "self").
Trong lập trình hướng đối tượng, chương trình máy tính được thiết kế bằng cách tách nó ra khỏi phạm vi các đối tượng tương tác với nhau. Ngôn ngữ lập trình hướng đối tượng khá đa dạng, phần lớn là các ngôn ngữ lập trình theo lớp (class), nghĩa là các đối tượng trong các ngôn ngữ này được xem như thực thể của một lớp, được dùng để định nghĩa một kiểu dữ liệu.

Các khái niệm cơ bản của OOP

Đối tượng (Object)

Trong lập trình hướng đối tượng, một đối tượng được hiểu giống như 1 thực thể: người, vật, ...
Một đối tượng bao gồm 2 thành phần: Thuộc Tính và Phương Thức
  • Thuộc Tính (Attribute): Là những đặc điểm, thông tin của đối tượng (Vd: Xét với đối tượng là một người thì sẽ có các thuộc tính là: Tên, Tuổi, Màu Da, Màu Tóc, Chiều Cao, Cân Nặng, ...)
  • Phương Thức (Method): Là những hành động mà đối tượng đó có thể thưc hiện (VD: Vẫn xét với đối tượng là một người thì sẽ có hành động: đi, nói, ăn, uống, ...)

Lớp (Class)

Các đối tượng có đặc tính tương như nhau sẽ được gom lại thành một lớp đối tượng.
Một lớp cũng sẽ có 2 thành phần là Thuộc Tính và Phương Thức.Ngoài ra, lớp cũng có thể được dùng để định nghĩa một kiểu dữ kiệu mới.

Các đặc điểm của lập trình hướng đối tượng

Tính Chất Của Lập Trình Hướng Đối Tượng

Lập trình hướng đối tượng là một phương pháp lập trình có 4 tính chất chính sau:

Tính trừu tượng (Abstraction)

Mục tiêu chính của nó là làm giảm sự phức tạp bằng cách ẩn các chi tiết không liên quan trực tiếp tới người dùng (người dùng ở đây không phải người dùng cuối mà là lập trình viên). Điều đó cho phép người dùng vẫn thực hiện được các công việc cần thiết dựa trên một thực thể trừu tượng được cung cấp mà không cần hiểu hoặc thậm chí không nghĩ về tất cả sự phức tạp ẩn giấu bên trong.
Lấy một ví dụ đơn giản nhé:
Khi bạn ra quán cafe, gọi nhân viên "Em ơi, cho anh một cốc cafe đen đá ". Khoảng 5 phút sau là bạn có ngay một cốc cafe trên tay mà không cần biết nhân viên đã pha chế cốc cafe đó như thế nào. Việc của bạn đơn giản chỉ là thông báo cho nhân viên biết bạn cần mua cafe, sau đó trả tiền và thưởng thức.

Tính đóng gói (Encapsulation) và che giấu dữ liệu (Data Hiding)

Tính đóng gói (Encapsulation) chỉ đơn giản là việc kết hợp một bộ các dữ liệu (data) liên quan đến nhau cùng với một bộ các hàm/phương thức (functions/methods) hoạt động trên các dữ liệu đó.Sau đó “gói” tất cả vào trong một cái gọi là lớp (class).  Các thực thể của các class thì được gọi là các đối tượng (objects) trong khi class giống như một công thức được sử dụng để tạo ra các đối tượng đó.
Encapsulation OOP C++
Một ví dụ về tính đóng gói:

class connguoi {
private:
    string ten;
    int tuoi;
public:
    void input()
 {
  cout << "Nhap Ten: ";
  fflush(stdin);
  getline(cin, this->ten);
  cout << "Nhap Tuoi: ";
  cin >> tuoi;
 }

    void output()
 {
  cout << "Ten: " << this->ten << endl;
  cout << "Tuoi: " << this->tuoi << endl;
 }
};




Tính đóng gói thể hiện ở đoạn code trên là kết hợp các thuộc tính ten, tuoi và phương thức input(), output() vào trong class connguoi.

Che giấu dữ liệu  (Data Hiding) là việc một số dữ liệu (data) và hàm/phương thức (functions/methods) được lớp (class) che giấu đi (ở dạng private) để đảm bảo rằng các dữ liệu đó sẽ được truy cập và sử dụng đúng mục đích, đúng cách thông qua các hàm/phương thức (functions/methods) ở dạng public mà class cung cấp. Bạn không thể truy cập đến các private data hoặc gọi đến private methods của class từ bên ngoài class đó.

Che giấu dữ liệu chỉ là một phương pháp kỹ thuật mà bạn áp dụng để xây dựng nên class mà thôi, nó không phải là tính chất đặc trưng của lập trình hướng đối tượng, việc bạn có áp dụng phương pháp này hay không là hoàn toàn không bắt buộc. Tuy nhiên trong các hệ thống thật, để nâng cao tính bảo mật, giảm phụ thuộc giữa các class, tránh lỗi đọc ghi dữ liệu sai cách, …  thì việc áp dụng phương pháp che giấu dữ liệu gần như là đương nhiên.
Nhìn lại đoạn code bên trên có thể thấy là các dữ liệu như ten, tuoi được để ở dạng privateBạn chỉ có thể truy cập chúng thông qua các phương thức public như: input(), output(). Đó chính là Data Hiding.

Tính đa hình (Polymorphism)

Trong lập trình hướng đối tượng và cụ thể là trong ngôn ngữ C++ thì tính đa hình có 2 dạng:
  • Dạng 1 – Compile time Polymorphism: Một class có nhiều hàm cùng tên nhưng khác nhau về số lượng tham số hoặc kiểu dữ liệu của tham số. Khi gọi hàm cùng tên đó thì trong quá trình biên dịch, compiler sẽ quyết định hàm nào (trong số các hàm cùng tên) sẽ được gọi dựa trên số lượng tham số và kiểu dữ liệu của tham số truyển vào hàm. Việc định nghĩa các hàm cùng tên được gọi là overloading – nạp chồng hàm.
  • Dạng 2 – Runtime Polymorphism: Cùng một class có thể cho ra nhiều biến thể, không phải được định nghĩa bởi lớp đó, mà bởi các lớp con của nó. Đây là một phương pháp để định nghĩa lại hành vi của lớp cơ sở mà không phải sửa code (còn gọi là Implementation) của lớp cơ sở. Nếu gọi hàm của đối tượng của lớp dẫn xuất thông qua con trỏ của lớp cơ sở thì việc hàm nào (của lớp cơ sở hay). Runtime Polymorphism được thực hiện bằng phương pháp overriding – ghi đè phương thức.
Ví dụ:

#include <bits/stdc++.h>
using namespace std;

class Data
{
public:
 void print(int i)
 {
  cout << "Print int: " << i << endl;
 }

 void print(float i)
 {
  cout << "Print float: " << i << endl;
 }

 void print(string i)
 {
  cout << "Print string: " << i << endl;
 }

}

int main()
{
 Data obj;
 obj.print(1);
 obj.print(2.5);
 obj.print("KingNNT");

 return 0;
}


Ta có lớp data có phương thức print cùng tên nhưng tham số truyền vào thuộc các kiểu dữ liệu khác nhau. Trong hàm main, ta gọi phương thức print 3 lần nhưng mỗi lần truyền một tham số thuộc kiểu khác, khi biên dịch chương trình sẽ tự nhận biết kiểu giá trị của tham số truyền vào từ đó xác định phương thức nào trong 3 phương thức cùng tên sẽ được gọi.

Tính kế thừa (Inheritance)

Đặc tính này cho phép một đối tượng có thể có sẵn các đặc tính mà đối tượng khác đã có thông qua kế thừa. Điều này cho phép các đối tượng chia sẻ hay mở rộng các đặc tính sẵn có mà không phải tiến hành định nghĩa lại. Tuy nhiên, không phải ngôn ngữ định hướng đối tượng nào cũng có tính chất này.
Lấy 1 ví dụ:
Coi con người là một class, class này sẽ có các thuộc tính: tên, tuổi, ... Một class sinh viên sẽ kế thừa class con người do sinh viên cũng có các thuộc tinhs: tên, tuổi, ... Ngoài ra còn có thêm các thuộc tính: mã sinh viên, điểm thành phần, ...
Bài viết đầu tiên của mình xin kết thúc tại đây. Rất mong mọi người đóng góp những ý kiến để bài viết của mình ngày càng hoàn thiện hơn. Cảm ơn tất cả. Chào tạm biệt và hẹn gặp lại mọi ngươi trong những bài viết tiếp theo của Series Hướng Đối Tượng C++ nhé.

Một số tài liệu tham khảo:
  1. Geeksforgeeks
  2. W3schools
Share:

Tính Kế Thừa Trong Lập Trình Hướng Đối Tượng


Xin chào tất cả mọi người, mình quay lại rồi đây. Thời gian vừa rồi mình khá bận nên không có thời gian chia sẻ tiếp với mọi người. Và hôm nay chúng ta tiếp tục tới phần tiếp theo là tính kế thừa trong lập trình hướng đối tượng nhé

Tính Kế Thừa Trong C++

Tính kế thừa là một trong những đặc tính quan trọng nhất của lập trình hướng đối tượng.
Nó là khả năng lấy một thuộc tính, đặc tính của một lớp cha để áp dụng lên lớp con.
Lớp kế thừa các thuộc tính từ một lớp khác được gọi là Lớp con hoặc Lớp dẫn xuất.
Lớp có các thuộc tính được kế thừa bởi lớp con được gọi là Lớp cha hoặc Lớp cơ sở.

Tại sao chúng ta cần dùng tính kế thừa? Và khi nào thì cần dùng nó?

Đầu tiên chúng ta lấy một ví dụ thực tế trước nhé. Chúng ta có 3 lớp: Class Bus, Class Car, Class Truck.
Các phương thức fuelAmount(), capacity(), applyBrakes() đều có trong 3 lớp này. Khi đó, nếu chúng ta tạo các lớp này thì chúng ta phải viết trong mỗi lớp đều có 3 phương thức trên.
Tính Kế Thừa
Tồi tệ hơn chút nữa, nếu bạn muốn sửa lại code trong một phương thức nào đó, thì bạn phải sửa chúng cả ở 3 lớp. Sẽ rất tốn thời gian, và có thể dễ sai sót đúng không nào.
Vì thế để tránh điều này và cũng đảm bảo dữ liệu sẽ không bị dư thừa, tính kế thừa sẽ được sử dụng ở đây.
Khi áp dụng tính kế thừa, đầu tiên ta sẽ lại một lớp: Class Vehical. Trong lớp này sẽ có cả 3 phương thức fuelAmount(), capacity() và applyBrakes().
Sau đó chúng ta mới tạo 3 lớp: Class Bus, Class Car, Class Truck. Rồi cho 3 lớp này kế thừa từ lớp Class Vehical.
Tính Kế Thừa
Hình ảnh trên cho thấy khi áp dụng tính kế thừa, ta chỉ cần viết một lần các phương thức kia trong lớp cha và cho các lớp con kế thừa lại.
Điều này sẽ tránh việc sai sót khi sửa và tăng khả năng sử dụng lại.
Khả năng sử dụng lại ở đây là: Nếu bạn muốn thêm một lớp Class Taxi chẳng hạn, bạn chỉ cần khai báo nó kế thừa từ Class Vehical là cũng có thể dùng được 3 phương thức trên rồi.

Cú pháp sử dụng tính kế thừa

Cú pháp chung để khai báo kế thừa như sau:

class subclass_name : access_mode base_class_name
{
  //body of subclass
};

Trong đó:
  • subclass_name là tên lớp con sẽ áp dụng kế thừa
  • base_class_name là tên lớp cha
  • access_mode có thể là public, private hoặc protected
Nếu các bạn không chỉ rõ access_mode thì mặc định sẽ là private.
Dưới đây là một ví dụ về sử dụng tính kế thừa trong C++.

#include <bits/stdc++.h>
using namespace std;

//Lớp Cha
class Parent {
public:
    int id_p;
};

// Lớp con kế thừa từ lớp cha
class Child : public Parent {
public:
    int id_c;
};

int main() {
    Child obj1;
    obj1.id_c = 7;
    obj1.id_p = 91;
    cout << "Child id is " << obj1.id_c << endl;
    cout << "Parent id is " << obj1.id_p << endl;

    return 0;
}

Sau khi chạy chương trình ta có kết quả sau

Child id is 7
Parent id is 91

Trong chương trình trên, class Child là lớp con, nó sẽ được kế thừa các thành viên dữ liệu dạng public từ class Parent
Nếu để các thành viên dữ liệu trên dạng private thì sẽ không thể dùng kế thừa
Bạn có thể xem lại các loại phạm vi truy cập: Tại đây

Các phạm vi kế thừa

Tiếp theo thì chúng ta sẽ tìm hiểu về các phạm vi kế thừa ( Access Mode ).
Ta sẽ có 3 loại chính đó là:
  1. public: Nếu kế thừa ở dạng này, sau khi kế thừa, tất cả các thành viên dạng public lớp cha sẽ public ở lớp con, dạng protected ở lớp cha vẫn sẽ là protected ở lớp con.
  2. protected: Nếu dùng protected thì sau khi kế thừa, tất cả các thành viên dạng public lớp cha sẽ trở thành protected tại lớp con.
  3. private: Trường hợp ta sử dụng private, thì sau khi kế thừa, tất cả các thành viên dạng publicprotected ở lớp cha sẽ thành private tại lớp con.
Bảng dưới đây sẽ tóm tắt các phạm vi kế thừa cho cả 3 loại public, protected, và private
Phạm Vi Truy Cập Tính Kế Thừa

Các loại kế thừa trong C++

Trong phần này, ta sẽ cùng tìm hiểu, ngôn ngữ C++ sẽ có các loại kế thừa nào nhé!

Đơn kế thừa (Single Inheritance)

Đơn kế thừa là gì?

Đơn kế thừa: nghĩa là một lớp chỉ được kế thừa từ đúng một lớp khác. Hay nói cách khác, lớp con chỉ có duy nhất một lớp cha.
Đơn Kế Thừa C++

Cú pháp khai báo đơn kế thừa

class subclass_name : access_mode base_class
{
  //body of subclass
};

Đây là một ví dụ về đơn kế thừa

#include <iostream>
using namespace std;

// Lớp cha
class Vehicle
{
public:
    Vehicle()
    {
        cout << "This is a Vehicle" << endl;
    }
};

// Lớp con kế thừa từ lớp cha
class Car : public Vehicle
{
};

// main function
int main()
{
    // creating object of sub class will
    // invoke the constructor of base classes
    Car obj;
    return 0;
}

Sau khi biên dịch và chạy chương trình ta có kết quả

This is a vehicle

Đa kế thừa (Multiple Inheritance)

Định nghĩa

Đa kế thừa là một tính năng của ngôn ngữ C++. Trong đó một lớp có thể kế thừa từ nhiều hơn một lớp khác. Nghĩa là một lớp con được kế thừa từ nhiều hơn một lớp cơ sở.
Đa Kế Thừa C++

Cú pháp khai báo đa kế thừa

class subclass_name : access_mode base_class1, access_mode base_class2, ....
{
  //body of subclass
};

Ở đây, các lớp cơ sở sẽ được phân tách bằng dấu phẩy , và phạm vi truy cập cho mọi lớp cơ sở phải được chỉ định.
Chúng ta cùng xem ví dụ sau:

#include <iostream>
using namespace std;

// Lớp cơ sở thứ nhất
class Vehicle
{
public:
    Vehicle()
    {
        cout << "This is a Vehicle" << endl;
    }
};

// Lớp cơ sở thứ hai
class FourWheeler
{
public:
    FourWheeler()
    {
        cout << "This is a 4 wheeler Vehicle" << endl;
    }
};

// Lớp con kế thừa từ 2 lớp cha
class Car : public Vehicle, public FourWheeler
{
};

// main function
int main()
{
    Car obj;
    return 0;
}

Sau khi chạy ta sẽ có kết quả sau
This is a Vehicle
This is a 4 wheeler Vehicle
Lưu ý: Khi đa kế thừa cần tránh trường hợp có nhiều lớp cơ sở có tên phương thức giống nhau. Vì khi gọi từ lớp con thì chương trình không biết nên gọi phương thức đó từ lớp cơ sở nào.
Nếu bạn cần tìm hiểu rõ hơn về đa kế thừa. Bạn có thể truy cập: Tại đây

Kế thừa đa cấp (Multilevel Inheritance)

Định nghĩa

Kế thừa đa cấp: Trong kiểu thừa kế này, một lớp dẫn xuất được tạo từ một lớp dẫn xuất khác.
Kế Thừa Đa Cấp

Ví dụ về kế thừa đa cấp

#include <iostream>
using namespace std;

// Lớp cha
class Vehicle
{
public:
    Vehicle()
    {
        cout << "This is a Vehicle" << endl;
    }
};
// Lớp con kế thừa từ lớp cha
class fourWheeler : public Vehicle
{
public:
    fourWheeler()
    {
        cout << "Objects with 4 wheels are vehicles" << endl;
    }
};
// Lớp con kế thừa từ lớp cha thứ 2
class Car : public fourWheeler
{
public:
    car()
    {
        cout << "Car has 4 Wheels" << endl;
    }
};

// main function
int main()
{
    Car obj;
    return 0;
}

Sau khi chạy ta có Output:

This is a Vehicle
Objects with 4 wheels are vehicles
Car has 4 Wheels

Kế thừa phân cấp (Hierarchical Inheritance)

Định nghĩa

Kế thừa phân cấp: Trong kiểu thừa kế này, sẽ có nhiều hơn một lớp con được kế thừa từ một lớp cha duy nhất.
Kế Thừa Phân Cấp C++

Ví dụ

#include <iostream>
using namespace std;

// Lớp cha
class Vehicle
{
public:
    Vehicle()
    {
        cout << "This is a Vehicle" << endl;
    }
};

// Lớp con thứ nhất
class Car : public Vehicle
{
};

// Lớp con thứ hai
class Bus : public Vehicle
{
};

// main function
int main()
{
    Car obj1;
    Bus obj2;
    return 0;
}
Sau khi chạy ta có kết quả:

This is a Vehicle
This is a Vehicle

Kế thừa lai (Kế thừa ảo) - Hybrid (Virtual) Inheritance

Định nghĩa

Kế thừa lai (Kế thừa ảo): được thực hiện bằng cách kết hợp nhiều hơn một loại thừa kế. Ví dụ: Kết hợp kế thừa phân cấp và đa kế thừa.
Hình ảnh dưới đây cho thấy sự kết hợp của phân cấp và đa kế thừa:
Kế Thừa Lai (Ảo) trong C++

Ví dụ

#include <iostream>
using namespace std;

// Lớp cha
class Vehicle
{
public:
    Vehicle()
    {
        cout << "This is a Vehicle" << endl;
    }
};

// Lớp cha
class Fare
{
public:
    Fare()
    {
        cout << "Fare of Vehicle\n";
    }
};

// Lớp con thứ nhất
class Car : public Vehicle
{
};

// Lớp con thứ hai
class Bus : public Vehicle, public Fare
{
};

// main function
int main()
{
    Bus obj2;
    return 0;
}

Sau khi chạy ta có kết quả như sau:

This is a Vehicle
Fare of Vehicle

Bài viêt của mình xin được kết thúc tại đây. Mình rất mong nhận được sự quan tâm, cũng như những góp ý từ các bạn để bài viết của mình ngày một hoàn thiện hơn. Cảm ơn tất cả mọi người. Xin chào và hẹn gặp lại.

Tài liệu tham khảo
  1. Geeksforgeeks
Share:

Nạp Chồng Hàm Và Nạp Chồng Toán Tử


Chào cả nhà, lại là mình đây. Trong bài tiếp theo chúng ta sẽ cùng tìm hiểu về Nạp chồng toán tử và Nạp chồng hàm là gì nhé.
Ngôn ngữ C++ cho phép bạn xác định nhiều hơn một định nghĩa cho một tên hàm hoặc một toán tử trong cùng phạm vi (scope), được gọi tương ứng là Nạp chồng hàm (function overloading) và Nạp chồng toán tử (operator overloading).
Nạp Chồng
Một khai báo nạp chồng là một khai báo mà đã được khai báo với cùng tên như một khai báo được khai báo trước đó trong cùng phạm vi, ngoại trừ rằng: cả hai khai báo có các tham số khác nhau và định nghĩa khác nhau.
Khi bạn gọi một hàm nạp chồng hoặc một toán tử nạp chồng, thì trình biên dịch quyết định định nghĩa thích hợp nhất để sử dụng bằng việc so sánh các kiểu tham số bạn đã sử dụng để gọi hàm hoặc toán tử với các kiểu tham số đã được xác định trong các định nghĩa. Tiến trình lựa chọn hàm nạp chồng hoặc toán tử nạp chồng thích hợp nhất này được gọi là phân giải nạp chồng ( overload resolution ).

Nạp Chồng Hàm

Nạp Chồng Hàm

Đặt vấn đề

#include <bits/stdc++.h>
using namespace std;

int int_max(int a, int b)
{
    if (a > b)
        return a;
    else
        return b;
}

int main()
{  
    cout << "int max = " << int_max(4, 5);

    return 0;
}

Trong ví dụ trên, sau khi chạy chương trình ta nhận được kết quả
int max = 5
Nhưng nếu ta thay int_max(4,5) thành int_max(4.4,5.5) thì kết quả nhận được vẫn là int max = 5giống phía trên, do khi khao báo hàm ta truyền vào 2 tham số thuộc kiểu int. Vậy để có kết quả đúng là 5.5 thì ta phải viết thêm một hàm mới.

/* Code by KingNNT */
#include <bits/stdc++.h>
using namespace std;

int int_max(int a, int b)
{
    if (a > b)
        return a;
    else
        return b;
}

int float_max(float a, float b)
{
    if (a > b)
        return a;
    else
        return b;
}

int main()
{
    cout << "int max = " << int_max(4, 5) << endl;
    cout << "float max = " << float_max(4.4, 5.5) << endl;

    return 0;
}

Như vậy, ta sẽ có nhiều hàm với các tên gọi khác nhau. Việc sử dụng tên như vậy sẽ gây bất lợi cho người lập trình khi gọi hàm. Nạp chồng hàm ra đời để giải quyết vấn đề trên.

Nạp chồng hàm

Nạp chồng hàm (Function Overloading) là một kiến thức khá mới mẻ đối với các bạn mới bắt đầu làm quen với C++. Bởi vì kiến thức này không hề tồn tại trong C mà chỉ có ở C++.
Kỹ thuật này cho phép sử dụng cùng một tên gọi cho các hàm “giống nhau” (có cùng mục đích). Nhưng khác nhau về kiểu dữ liệu tham số hoặc số lượng tham số.
Quay về với ví dụ trên
Nạp chồng hàm cho phép ta khai báo và định nghĩa các hàm trên cùng với một tên gọi.

#include <bits/stdc++.h>
using namespace std;

int max(int a, int b)
{
    if (a > b)
        return a;
    else
        return b;
}

int max(float a, float b)
{
    if (a > b)
        return a;
    else
        return b;
}

int main()
{
    cout << "int max = " << max(4, 5) << endl;
    cout << "float max = " << max(4.4, 5.5) << endl;

    return 0;
}

Sau khi chạy ta có kết quả

int max = 5
float max = 5.5

Nạp Chồng Toán Tử

Nạp Chồng Toán Tử

Giới thiệu về nạp chồng toán tử

Nạp chồng toán tử (Operator Overloading) được dùng để định nghĩa toán tử cho có sẵn trong c++ phục vụ cho dữ liệu riêng do bạn tạo ra.
Giả sử có lớp PhanSo và có các phương thức tính toán như Cong, Tru, Nhan, Chia.
Nếu gặp một biểu thức phức tạp, số lượng phép tính nhiều thì việc sử dụng các phương thức trên khá khó khăn và có thể gây rối cho người lập trình. Vì thế ta sẽ nạp chồng lại các toán tử để có thể tạo một cái nhìn trực quan vào code, giảm thiểu các lỗi sai không đáng có.

Các loại toán tử

  • C++ chỉ cho phép người dùng overloading lại các toán tử có sẵn trong c++
  • Một toán tử có thể được định nghĩa cho nhiều kiểu dữ liệu khác nhau.
Phân Loại Toán Tử

Toán tử đơn

Toán tử đơn là toán tử một ngôi (unary operator), có thể được dùng làm toán tử trước (prefix operator) và toán tử sau (postfix operator). Ví dụ phép tăng (++) hay phép giảm (–-)
Ví dụ:
  • prefix operator: ++i;
  • postfix operator: i++;

Toán tử đôi

Toán tử đôi là toán tử có 2 ngôi (binary operator).
Ví dụ: như A+B, A*B, hay toán tử chỉ mục […] cũng là toán tử đôi.

Các toán tử có thể nạp chồng

Nạp Chồng Toán Tử

Nạp chồng toán tử

Nạp chồng toán tử 1 ngôi

#include <bits/stdc++.h>
using namespace std;

class phanso 
{
private:
    int tu, mau;

public:
    phanso()
    {
        tu = mau = 0;
    }

    ~phanso()
    {
        tu = mau = 0;
    }
    
 void input()
 {
  cout << "Nhap tu so: ";
  cin >> this->tu;
  cout << "Nhap mau so: ";
  cin >> this->mau;
 }

 void output()
 {
  cout << this->tu << "/" << this->mau << endl;
 }
 
    
    phanso operator +(phanso b)
    {
     phanso c;
     c.tu = this->tu * b.mau + this->mau * b.tu;
     c.mau = this->mau * b.mau;
     return c;
    }    
};

int main()
{
    phanso a, b, c;

 a.input();
 b.input();

 c = a + b; c.output();
}
Ta truyền Input:

1 2
3 4

Sau khi chạy chương trình ta có kết quả:

10/8

Trong phần code trên, mình đã nạp chồng toán tử + cho lớp phân số bằng cách nạp chồng toán tử 1 ngôi ( chỉ có thể truyền vào một tham số )
Với cách nạp chồng này ta có thể coi nó là một phương thức của lớp:
  • Tên phương thức sẽ có dạng operator @ - trong đó @ là toán tử cần nạp chồng

Nạp chồng toán tử 2 ngôi

/* Code by KingNNT */
#include <bits/stdc++.h>
using namespace std;

class phanso 
{
private:
    int tu, mau;

public:
    phanso()
    {
        tu = mau = 0;
    }

    ~phanso()
    {
        tu = mau = 0;
    }
    
 void input()
 {
  cout << "Nhap tu so: ";
  cin >> this->tu;
  cout << "Nhap mau so: ";
  cin >> this->mau;
 }

 void output()
 {
  cout << this->tu << "/" << this->mau << endl;
 }
 
    friend phanso operator +(phanso a, phanso b)
    {
     phanso c;
     c.tu = a.tu * b.mau + a.mau * b.tu;
     c.mau = a.mau * b.mau;
     return c;
    }    
};

int main()
{
    phanso a, b, c;

 a.input();
 b.input();

 c = a + b; c.output();
}

Vẫn với Input như trên

1 2
3 4

Sau khi biên dịch và chạy chương trình, kết quả vẫn nhận được là:

10/8

Với cách nạp chồng toán tử 2 ngôi này, thì hàm nạp chồng được coi là một hàm bạn của lớp:
  • Có từ khoá friend ở đầu
  • Tên hàm là operator @- Trong đó @ vẫn là toán tử cần nạp chồng
  • 2 tham số được truyền vào là 2 giá trị thực hiện phép toán
Nếu không nhớ Hàm Bạn là gì? Bạn có thể xem lại: Tại Đây

Nạp chồng toán tử nhập, xuất

Việc nạp chồng toán tử nhập xuất cho phép người dùng dùng cin, cout nhập xuất nhanh một đối tượng mà không cần gọi lại cin,cout cho từng thuộc tính của dữ liệu dựa trên việc được định nghĩa trước.
Đối với nạp chồng toán tử nhập >> và toán tử xuất << ta sẽ dùng cách nạp chồng toán tử 2 ngôi.

#include <bits/stdc++.h>
using namespace std;

class phanso
{
private:
    int tu, mau;

public:
    phanso()
    {
        tu = mau = 0;
    }

    ~phanso()
    {
        tu = mau = 0;
    }
    friend istream &operator>>(istream &is, phanso &obj)
    {
        cout << "Nhap tu so: ";
        is >> obj.tu;
        cout << "Nhap mau so: ";
        is >> obj.mau;
        return is;
    }

    friend ostream &operator<<(ostream &os, phanso obj)
    {
        os << obj.tu << "/" << obj.mau << endl;
        return os;
    }
};

int main()
{
    phanso a;
    cin >> a;
    cout << a;
}

Input:

1 2

Output:

1/2

Đối với toán tử nhập >> kiểu trả về của nó sẽ là istream. Trong phần code trên, mình truyền tham số đầu tiền là tham chiếu is . Sau đó phần code trong thân hàm, tất cả cin mình sẽ thay bằng is và kết thúc hàm bằng return is. Tham số thứ 2 được truyền vào cũng dưới dạng tham chiếu obj thuộc lớp phanso.
Cách thức nạp chồng toán tử xuất << Cũng tương tự như nạp chồng toán tử nhập >>.
Điều khác biệt ở đây là os có kiểu trả về là ostream và tham số thứ 2 truyền vào có thể là tham trị chứ không nhất thiết phải là tham chiếu.
Bạn có thể xem tại Cách phân biệt Tham Chiếu và Tham Trị: Tại Đây

Nạp chồng toán tử gán

MyClass& MyClass::operator=(const MyClass &rhs)
{
    if (this == &rhs)      // kiểm tra có cùng đối tượng?
      return *this;        // Nếu trùng thì bỏ qua và return chính nó
    //Xử lí... (Cấp phát vùng nhớ mới, sao chép giá trị, ...)
    return *this;
}

Cú pháp và cách xử lý ở đây gần giống với Hàm khởi tạo sao chép
Bài viết của mình tới đây là hết rồi. Nếu có bất kì thắc mắc hay đóng góp nào, mọi người đừng ngần ngại comment ngay phía dưới nhé. Rất mong nhận được sự ủng hộ của các bạn. Xin chào và hẹn gặp lại.

Tài Liệu Tham Khảo

  1. Function Overloading - Geeksforgeeks
  2. Operator Overloading - Geeksforgeeks
Share:

Hàm Bạn Và Lớp Bạn Trong C++


Chào mọi người, trong bài trước thì mình đã hướng dẫn mọi người viết một class đơn giản. Trong phần tiếp theo, chúng ta cùng đi tìm hiểu hàm bạn là gì? Lớp bạn là gì nhé?

Đặt vấn đề cần truy cập dữ liệu

Giả sử ta có class sinhvien có thuộc tính masinhvienprivate. Ta cũng có class giangvien có thuộc tính magiangvien là private.
Yêu cầu là chỉ dùng một hàm để in 2 giá trị thuộc tính này ra.
Đối với hàm get() này:
  • Không thể thuộc lớp sinhvien
  • Không thể thuộc lớp giangvien
  • Cũng không thể là một hàm tự do (vì hàm không thuộc class sẽ không truy cập thuộc tính private)
Nếu không nhớ các quyền truy cập private, protected, public các bạn có thể xem lại TẠI ĐÂY

Hàm bạn (friend function) trong C++


Định nghĩa

  • Hàm bạn trong c++ là hàm tự do, không thuộc lớp. Tuy nhiên hàm bạn trong c++ có quyền truy cập các thành viên private của lớp.
  • Một lớp trong c++ có thể có nhiều hàm bạn, và chúng phải nằm bên ngoài class.

Ưu điểm

  • Kiểm soát các truy nhập ở cấp độ lớp. Nghĩa là không thể áp đặt hàm bạn cho một lớp, nếu như chưa khai báo hàm bạn trong lớp.
  • Giải quyết được vấn đề cần truy cập dữ liệu của lớp như trên.

Cú pháp

Đặt từ khoá friend phía trước, sau đó khai báo như một hàm thông thường
Ví dụ:

#include <bits/stdc++.h>
using namespace std;

class giangvien;
class sinhvien
{
private:
    string masinhvien;
public:
    sinhvien()
    {
        this->masinhvien = "";
    }
    ~sinhvien()
    {
        this->masinhvien = "";
    }

    void set()
    {
        cout << "Nhap Ma Sinh Vien"; fflush(stdin); getline(cin, this->masinhvien);
    }

    friend void get(sinhvien a, giangvien b); // Khai báo hàm bạn trong class
};

class giangvien
{
private:
    string magiangvien;
public:
    giangvien()
    {
        this->magiangvien = "";
    }
    ~giangvien()
    {
        this->magiangvien = "";
    }

    void set()
    {
        cout << "Nhap Ma Giang Vien: "; fflush(stdin); getline(cin, this->magiangvien);
    }

    friend void get(sinhvien a, giangvien b); // Khai báo hàm bạn trong class
};

void get(sinhvien a, giangvien b)
{
    cout << "Ma Sinh Vien: " << a.masinhvien << endl;
    cout << "Ma Giang Vien: " << b.magiangvien << endl;
}

int main()
{
    sinhvien a;
    giangvien b;
    a.set(); b.set();
    get(a,b);

}

Ta truyền Input:

MSV001
MGV001

Sau khi chạy chương trình ta sẽ nhận được kết quả là:

Ma Sinh Vien: MSV001
Ma Giang Vien: MGV001

Lớp bạn (class function) trong C++



Tương tự như hàm bạn, lớp bạn ( friend class ) trong C++ cũng cho phép lớp bạn của lớp kia truy cập các thành viên private

Tính chất và mối quan hệ của lớp bạn

  • Khai báo lớp A là bạn của lớp B không có nghĩa lớp B là bạn của lớp A (chỉ có tính 1 chiều). Điều đó có nghĩa là chỉ có lớp A truy cập được thành viên của lớp B, nhưng ngược lại lớp B không thể truy cập ngược lại của lớp A.
  • Không đối xứng.
  • Không bắc cầu.

Cú pháp

Ta cũng dùng từ khoá friend để khai báo giống như khai báo hàm bạn.

class A
{
private:
    int i;

public:
    friend class B; //Có lớp bạn là B
};

class B
{
public:
    void Change(A obj)
    {
        obj.i++;
    }
};

Như trong ví dụ trên, ta đã khai báo lớp B là bạn của lớp A, do đó lớp B có thể truy cập các thành viên trong lớp A ( Trong ví dụ là truy cập lớp A thông qua  Change(A obj) ).
Hãy thật lưu ý trong ví dụ trên B là bạn của A, B có thể truy cập vào A nhưng ngược lại thì không nhé.
Cảm ơn mọi người đã theo dõi và ủng hộ series của mình. Mình rất mong nhận được những ý kiến, đóng góp của các bạn để bài viết của mình ngày càng hoàng thiện hơn. Cảm ơn tất cả mọi người.
Share:

Tạo Class Trong C++


Chào mọi người, ở bài trước thì mình đã đưa ra định nghĩa và giải thích về lớp (class), các thành phần của lớp (class). Trong bài này thì chúng ta sẽ cùng đi làm một số thực hành bằng cách tạo các lớp (class) đơn giản nhé.

Tạo class đơn giản

Đề bài:

Viết một lớp sinhvien gồm các thuộc tính: ten, tuoi, masinhvien. Và các phương thức nhập xuất.
#include <bits/stdc++.h>
using namespace std;

class sinhvien
{
private:
    string ten, masinhvien;
    int tuoi;
public:
    void set()
    {
        cin.ignore();
        cout << "Nhap Ma Sinh Vien"; getline(cin, this->masinhvien);
        cout << "Nhap Ten: "; getline(cin, this->ten);
        cout << "Nhap Tuoi: "; cin >> this->tuoi;
    }

    void get()
    {
        cout << "Ma Sinh Vien: " << this->masinhvien << endl;
        cout << "Ten: " << this->ten << endl;
        cout << "Tuoi: " << this->tuoi << endl;
    }
};

int main()
{
    sinhvien obj;
    obj.set();
    obj.get();   
}

Nhập Input:

MSV1
KingNNT
18

Sau khi chạy chương trình ta sẽ có kết quả:
Ma Sinh Vien: MSV1
Ten: KingNNT
Tuoi: 18

Tạo class có hàm khởi tạo và hàm huỷ

Tạo class
Trong phần này, thay vì tạo class chỉ có 2 phương thức nhập xuất là setget. Mình sẽ tạo class sinhvien với hàm khởi tạo không tham số, hàm huỷ, và phương thức nhập xuất

#include <bits/stdc++.h>
using namespace std;

class sinhvien
{
private:
    string ten, masinhvien;
    int tuoi;
public:
    sinhvien()
    {
        this->masinhvien = "";
        this->ten = "";
        this->tuoi = 0;
    }
    ~sinhvien()
    {
        this->masinhvien = "";
        this->ten = "";
        this->tuoi = 0;
    }

    void set()
    {
        cout << "Nhap Ma Sinh Vien"; getline(cin, this->masinhvien);
        cout << "Nhap Ten: "; getline(cin, this->ten);
        cout << "Nhap Tuoi: "; cin >> this->tuoi;
    }

    void get()
    {
        cout << "Ma Sinh Vien: " << this->masinhvien << endl;
        cout << "Ten: " << this->ten << endl;
        cout << "Tuoi: " << this->tuoi << endl;
    }
};

int main()
{
    sinhvien obj;

    cout << "Before Set" << endl;
    obj.get();
    // Tại đây mình sẽ không set giá trị vào cho obj nhé.
    // Để mọi người có thể thấy khi có hàm khởi tạo, các giá trị sẽ được set giá trị ngay khi được tạo ra
    obj.set();

    cout << "After Set" << endl;
    obj.get();
}

Vẫn với Input cũ

MSV1
KingNNT
18

Sau khi chạy ta có kết quả

Before Set
Ma Sinh Vien: 
Ten: 
Tuoi: 0
After Set
Ma Sinh Vien: MSV1
Ten: KingNNT
Tuoi: 18

Như các bạn đã thấy, nếu khi khởi tạo obj mà ta không set giá trị cho chúng thì chúng sẽ nhận giá trị mặc định, là giá trị ta cài đặt tạo hàm khởi tạo.

Tạo class với hàm tính toán

ìmg
Nâng cao thêm một chút nhé, tại lớp sinhvien mình sẽ cho thêm 3 thuộc tính là: diemtoan diemvandiemanh. Thêm một yêu cầu là tính điểm trung bình của sinhvien đó.

#include <bits/stdc++.h>
using namespace std;

class sinhvien
{
private:
    string ten, masinhvien;
    int tuoi;
    float diemtoan, diemvan, diemanh;
public:
    sinhvien()
    {
        this->masinhvien = "";
        this->ten = "";
        this->tuoi = 0;
        this->diemtoan = this->diemvan = this->diemanh = 0;
    }
    ~sinhvien()
    {
        this->masinhvien = "";
        this->ten = "";
        this->tuoi = 0;
        this->diemtoan = this->diemvan = this->diemanh = 0;
    }

    void set()
    {
        cout << "Nhap Ma Sinh Vien"; getline(cin, this->masinhvien);
        cout << "Nhap Ten: "; getline(cin, this->ten);
        cout << "Nhap Tuoi: "; cin >> this->tuoi;
        cout << "Nhap Diem Toan - Van - Anh"; cin >> this->diemtoan >> this->diemvan >> this->diemanh;
    }

    void get()
    {
        cout << "Ma Sinh Vien: " << this->masinhvien << endl;
        cout << "Ten: " << this->ten << endl;
        cout << "Tuoi: " << this->tuoi << endl;
        cout << "Diem Trung Bình: " << TB() << endl;
    }

    float TB()
    {
        return (this->diemtoan + this->diemvan + this->diemanh)/3;
    }
};

int main()
{
    sinhvien obj;

    obj.set();
    obj.get();
}

Input là:

MSV1
KingNNT
18
8 9 10

Sau khi chạy ta có Output:

Ma Sinh Vien: MSV1
Ten: KingNNT
Tuoi: 18
Diem Trung Bình: 9

Bài viết của mình xin kêt thúc tại đây. Rất mong nhận được sự ủng hộ và những ý kiến, đóng góp của mọi người để những bài viết của mình ngày càng tốt hơn. Cảm ơn mọi người rất nhiều.

Giới thiệu Khoá Học Lập trình C cơ bản hoàn toàn miễn phí "Học C Bá Đạo": Xem Ngay
Share:

Hàm Khởi Tạo Và Hàm Huỷ


Chào cả nhà
Trong bài viết này chúng ta sẽ đi tìm hiểu một hàm thành viên rất quan trọng của lớp đó chính là hàm khởi tạo và hàm huỷ.

Hàm khởi tạo (Constructor)

Hàm khởi tạo là gì?

Hàm khởi tạo là một hàm thành viên đặc biệt của một lớp. Nó sẽ được tự động gọi đến khi một đối tượng của lớp đó được khởi tạo.

Sự khác biệt giữa hàm tạo và hàm thành viên thông thường

Một hàm tạo sẽ khác những hàm thông thường ở những điểm sau:
  • Có tên trùng với tên lớp
  • Không có kiểu dữ liệu trả về ( kể cả kiểu void)
  • Tự động được gọi khi một đối tượng thuộc lớp được tạo ra
  • Nếu chúng ta không khai báo một hàm tạo, trình biên dịch C++ sẽ tự động tạo một hàm tạo mặc định cho chúng ta (sẽ là hàm ​​không có tham số nào và có phần thân trống).
Hàm tạo có thể rất hữu ích để thiết lập các giá trị khởi tạo cho các biến thành viên cụ thể.
Ví dụ đơn giản về hàm khởi tạo:
class sinhvien
{
private:
    string ten;
    int tuoi;
public:
    sinhvien(); // Đây là hàm khởi tạo
    ~sinhvien();
};

Các loại hàm khởi tạo

Hàm khởi tạo về cơ bản sẽ được chia làm 3 loại:
  1. Hàm khởi tạo không tham số (Cũng có thể gọi là hàm tạo mặc định - Default Constructor )
  2. Hàm khởi tạo có tham số ( Parameterized Constructor )
  3. Hàm khởi tạo sao chép ( Copy Constructor )
Hàm Khởi Tạo

Hàm khởi tạo không tham số ( Default Constructor )

Hàm tạo loại này sẽ không truyền vào bất kì một đối số nào

class sinhvien
{
private:
    string ten;
    int tuoi;
public:
    sinhvien()
    {
        this->ten = "";
        this->tuoi = 0;
    }
    ~sinhvien();
};

Như trong ví dụ trên, hàm tạo sinhvien() không hề có đối số nào được truyền vào.
Theo ý kiến riêng của mình thì thông thường trong hàm loại này mình sẽ gán cho tất cả các thuộc tính về giá trị mặc định.
Trong ví dụ trên:
  • Thuộc tính ten thuộc kiểu string mình sẽ đưa về mặc định là một chuối rỗng "".
  • Thuộc tính tuoi thuộc kiểu int mình sẽ đưa về mặc định là 0.

Hàm khởi tạo có tham số ( Parameterized Constructor )

Với loại hàm tạo này ta có thể truyền đối số cho chúng. Thông thường, các đối số này giúp khởi tạo một đối tượng khi nó được tạo.
Để khai báo một hàm khởi tạo có tham số chỉ cần thêm các tham số vào nó giống như cách bạn thêm tham số bất kỳ hàm nào khác.Khi bạn xác định phần thân của hàm tạo, hãy sử dụng các tham số để khởi tạo đối tượng.

class sinhvien
{
private:
    string ten;
    int tuoi;
public:
    sinhvien(string param_ten, int param_tuoi)
    {
        this->ten = param_ten;
        this->tuoi = param_tuoi;
    }
    ~sinhvien();
};

Sau khi khai báo hàm trong lớp, ta có thể dễ dàng dùng nó bằng cách truyền tham số trong khi khởi tạo đối tượng.
int main()
{
    sinhvien obj("KingNNT", 5); // Ta truyền luôn tham số trong khi khới tạo đối tượng
    
}
Lưu ý:
  • Khi một đối tượng được khai báo trong hàm khởi tạo có tham số, các giá trị ban đầu phải được truyền dưới dạng đối số cho hàm tạo.
  • Cách khai báo đối tượng bình thường có thể sẽ gây lỗi.
    Điều này có nghĩa là bình thường để khai báo một đối tượng bạn sẽ khai báo bằng cú pháp:
sinhvien obj;

Nhưng do hàm khởi tạo là hàm có tham số nên cú pháp sẽ phải là:

sinhvien obj("KingNNT", 5);
  • Các hàm khởi tạo có thể được gọi một cách rõ ràng hoặc ngầm định.
    sinhvien obj = sinhvien("KingNNT", 5); // Đây là cách rõ ràng
    sinhvien obj("KingNNT", 5); // Đây là cách ngầm định
    Nhưng thông thường để tiết kiệm code thì chúng ta hay sử dụng các ngầm định hơn.
Công dụng của hàm khởi tạo có tham số
  • Nó được sử dụng để khởi tạo các thành phần dữ liệu khác nhau của các đối tượng khác nhau với các giá trị khác nhau khi chúng được tạo.
  • Nó được sử dụng để nạp chồng các hàm khởi tạo.
    Nạp chồng? Có thể hiểu đơn giản là ta sẽ có nhiều hơn một hàm khởi tạo trong cùng một lớp. Và phần này thì sẽ được mình trình bài trong bài sau nhé.

Hàm khởi tạo sao chép ( Copy Constructor )

Hàm khởi tạo sao chép là gì?
Hàm khởi tạo sao chép là một hàm tạo mà tạo một đối tượng bằng việc khởi tạo nó với một đối tượng của cùng lớp đó, mà đã được tạo trước đó.
Một hàm khởi tạo sao chép sẽ có nguyên mẫu chung như sau:
    ClassName(const ClassName &old_obj)
    {
        // Code
    }
Trong đó Classname là tên của lớp, old_obj là đối tượng cũ sẽ lấy làm gốc để sao chép sang đối tượng mới
Ví dụ đơn giản về hàm khởi tạo sao chép:

/* Code by KingNNT */
#include <bits/stdc++.h>
using namespace std;

class Point
{
private:
    int x, y;

public:
    Point(int x1, int y1)
    {
        x = x1;
        y = y1;
    }

    // Hàm khởi tạo sao chép
    Point(const Point &p2)
    {
        x = p2.x;
        y = p2.y;
    }

    int getX() { return x; }
    int getY() { return y; }
};

int main()
{
    Point p1(10, 15); // Hàm khởi tạo có tham số thông thường
    Point p2 = p1;    // hàm khởi tạo sao chép được gọi ở đây

    cout << "p1.x = " << p1.getX() << ", p1.y = " << p1.getY() << endl;
    cout << "p2.x = " << p2.getX() << ", p2.y = " << p2.getY() << endl;

    return 0;
}

Sau khi chạy chương trình ta sẽ có kết quả:

p1.x = 10, p1.y = 15
p2.x = 10, p2.y = 15
Một hàm khởi tạo sao chép sẽ được gọi khi nào?
Hàm khởi tạo sao chép sẽ được gọi khi:
  1. Khi một đối tượng của lớp được trả về bằng một giá trị.
  2. Khi một đối tượng của lớp được truyền đối số dưới dạng tham số của một hàm.
  3. Khi một đối tượng được tạo ra dựa trên một đối tượng khác cùng lớp.
  4. Khi trình biên dịch tạo một đối tượng tạm thời.
Tuy nhiên trên thực tế thì không chắc chắn rằng hàm khởi tạo sao chép sẽ được gọi trong tất cả 4 trường hợp ở phía trên. Vì C++ tiêu chuẩn sẽ cho phép trình biên dịch tối ưu hoá bản sao trong một số trường hợp nhất định.
Một ví dụ cho điều này là: Ví dụ về tối ưu hoá giá trị trả về ( Có thể gọi tắt là RVO). Xem tại đây

Lưu ý:
Nếu một hàm tạo sao chép không được định nghĩa trong một lớp, trình biên dịch sẽ tự nó định nghĩa nó. Vì thế phải thật lưu ý nếu lớp có các biến con trỏ hoặc có sử dụng cấp phát bộ nhớ động thì nên viết lại hàm.
Chia sẻ nhỏ một chút là mình đã từng mắc lỗi tại đây do khi sử dụng cấp phát bộ nhớ động mà không viết lại hàm khởi tạo sao chép do đó dẫn đến việc truy cập sai ô nhớ.

Hàm huỷ (Deconstructor)

Hàm huỷ là gì?

Hàm huỷ cũng là một hàm thành viên đặc biệt giống như hàm tạo, nó được dùng để phá huỷ hoặc xoá một đối tượng trong lớp.

Hàm huỷ sẽ được gọi khi nào?

Hàm hủy được gọi tự động khi một đối tượng thoát khỏi phạm vi của nó (Scope):
  1. Một chức năng kết thúc.
  2. Chương trình kết thúc.
  3. Một khối chứa các biến cục bộ kết thúc.
  4. Một toán tử delete được gọi

Hàm huỷ khác những hàm thành viên bình thường ở đâu?

  • Cũng giống với hàm tạo, hàm huỷ có tên trùng với tên của lớp, nhưng điểm khác biệt ở đây là sẽ có thêm ~ ở đầu.
  • Hàm huỷ là một hàm không có đối số truyền vào, và cũng không trả về giá trị ( kể cả void)
class sinhvien
{
private:
    string ten;
    int tuoi;
public:
    sinhvien()
    {
        this->ten = "";
        this->tuoi = 0;
    }
    ~sinhvien() // Đây là hảm huỷ
    {
        this->ten = "";
        this->tuoi = 0;
    }
}

Có thể có nhiều hơn một hàm huỷ ở trong cùng một lớp không?

Câu trả lời ở đây là không nhé.
Khác với hảm khởi tạo, hàm huỷ có thể có một và chỉ một mà thôi.

Khi nào thì ta cần tự định nghĩa một hàm huỷ?

Với C++ thì nếu ta không khai báo một hàm huỷ, trình biên dịch cũng sẽ tự định nghĩa một hàm huỷ. Thông thường thì hàm huỷ này hoạt động khá tốt, nhưng khi bài toán có sử dụng con trỏ, hoặc cấp phát bộ nhớ động thì ban nên khai báo một hàm huỷ riêng để tránh rỏ rỉ bộ nhớ.
Nhưng với bản thân mình thông thường mình vẫn sẽ khai báo một hàm huỷ cho dù có dùng con trỏ hay cấp phát động hay không, và trong hàm huỷ đó mình sẽ đưa các thuộc tính của lớp về giá trị mặc định ( giống với hàm khởi tạo không tham số ).

Một hàm huỷ có thể là một hàm ảo hay không ?

Tất nhiên là có rồi. Và bạn có thể xem chi tiết: Tại đây
Bài viết của mình xin được kết thúc tại đây. Mọi thể comment ở bên dưới nếu thấy bất cứ điều gì không chính xác, hoặc đơn giản là muốn chia sẻ thêm những kiến thức tới mọi người. Cảm ơn mọi người. Chào tạm biệt và hẹn gặp lại !

Tài liệu tham khảo

  1. Geeksforgeeks
  2. Tutorialspoint
Share:

Bài viết nổi bật

Lập Trình Hướng Đối Tượng Trong C++

Chào mọi người, đây là bài đầu tiên trong series  Lập Trình Hướng Đối Tượng C++.  Trong bài này chúng ta sẽ cùng đi tìm hiểu  Lập Trình H...

Follower

Biểu mẫu liên hệ

Tên

Email *

Thông báo *