Bài tập OOP: mức trung bình
Các bài luyện về kế thừa, đa hình, nạp chồng toán tử, composition và quyền truy cập.
Bài 1: tiếng kêu của động vật
Viết lớp cha DongVat có phương thức ảo keu(), và các lớp con Cho, Meo, Bo
mỗi lớp kêu khác nhau. Tạo một vector<DongVat*> rồi duyệt cho từng con kêu.
Gợi ý
Đánh dấu virtual void keu() ở lớp cha và override ở mỗi lớp con. Khi duyệt vector
con trỏ lớp cha và gọi p->keu(), đa hình sẽ chọn đúng hành vi theo kiểu thực. Nhớ
hủy tử ảo. Xem lại bài Kế thừa và đa hình.
Bài 2: tài khoản VIP dùng protected
Viết lớp TaiKhoan có protected long soDu, lớp con TaiKhoanVip : public TaiKhoan
thêm phương thức congLai() cộng 5% số dư.
Gợi ý
Để soDu là protected (không phải private) thì TaiKhoanVip mới truy cập trực
tiếp được trong congLai(). Nếu để private, lớp con sẽ bị chặn. Xem lại bài
Quyền truy cập.
Bài 3: phân số và toán tử cộng
Viết lớp PhanSo với tử và mẫu, nạp chồng operator+ để cộng hai phân số. Khuyến
khích rút gọn kết quả.
Gợi ý
Công thức: a/b + c/d = (a*d + c*b) / (b*d). operator+ trả về một PhanSo mới (đánh
dấu const). Muốn rút gọn thì chia tử và mẫu cho ước chung lớn nhất. Xem lại bài
Nạp chồng toán tử.
Bài 4: máy tính chứa linh kiện
Viết lớp MayTinh chứa một CPU và một RAM làm thành phần. MayTinh::khoiDong()
ủy nhiệm gọi phương thức của cả hai linh kiện.
Gợi ý
Đây là composition ("có một"): CPU cpu; và RAM ram; là hai trường thành viên.
khoiDong() không tự làm mà gọi cpu.tinhToan() rồi ram.nap(). Xem lại bài
Composition.
Bài 5: giao diện phát âm thanh
Định nghĩa interface AmThanh với hàm ảo thuần phat(). Viết các lớp Coi, Chuong,
Loa cài đặt theo cách riêng. Viết hàm nhận const AmThanh& và gọi phat().
Gợi ý
Hàm ảo thuần: virtual void phat() const = 0; khiến AmThanh thành lớp trừu tượng,
không tạo đối tượng trực tiếp được. Mỗi lớp con bắt buộc override. Xem lại bài
Lớp trừu tượng và interface.
Bài 6: đánh số thứ tự tự động
Viết lớp VeXem sao cho mỗi vé tạo ra nhận một số thứ tự tăng dần 1, 2, 3... và có
phương thức soVe() đọc số của chính nó.
Gợi ý
Một biến static int nextId làm bộ phát số, cộng một trường thường int id cho từng
đối tượng. Trong hàm khởi tạo: id = nextId; nextId++;. Kết hợp static (chung) và
thường (riêng). Xem lại bài Thành viên tĩnh.
Bài 7: in đối tượng ra cout
Cho lớp Diem2D có x và y. Nạp chồng operator<< sao cho cout << p; in ra dạng
(x, y). Thử in một vài điểm nối tiếp nhau trên cùng một dòng.
Gợi ý
operator<< phải là hàm tự do (không phải phương thức thành viên) vì toán hạng trái là
ostream, không phải Diem2D. Chữ ký: ostream& operator<<(ostream& os, const Diem2D& p),
in vào os rồi return os; để nối được cout << a << b;. Muốn đọc trường private thì
khai báo friend. Xem lại bài Nạp chồng toán tử.
Bài 8: bảng lương đa hình
Viết lớp cha NhanVien có phương thức ảo tinhLuong(). Lớp NhanVienChinhThuc trả lương
cố định, lớp CongTacVien trả theo số giờ nhân đơn giá. Duyệt vector<NhanVien*> cộng
tổng quỹ lương.
Gợi ý
virtual double tinhLuong() const; ở lớp cha, override ở mỗi lớp con với công thức
riêng. Khi cộng qua con trỏ lớp cha, đa hình chọn đúng cách tính theo kiểu thực. Nhớ để
hủy tử lớp cha là virtual vì ta xóa đối tượng qua con trỏ lớp cha. Xem lại bài
Kế thừa và đa hình.
Bài 9: nạp chồng phương thức vẽ
Viết lớp ButVe có nhiều phiên bản ve(): ve(int x, int y) vẽ một điểm, và
ve(int x1, int y1, int x2, int y2) vẽ một đoạn thẳng. Cùng tên hàm, khác danh sách
tham số.
Gợi ý
Đây là nạp chồng hàm (function overloading): nhiều hàm cùng tên trong một lớp, phân biệt bằng số lượng hoặc kiểu tham số. Trình biên dịch chọn phiên bản khớp lời gọi. Lưu ý chỉ khác kiểu trả về thì không đủ để nạp chồng. Xem lại bài Hàm.
Bài 10: tổng diện tích qua mảng con trỏ lớp cha
Cho lớp trừu tượng Hinh với dienTich() ảo. Viết HinhChuNhat và HinhTron, tạo một
mảng con trỏ Hinh* gồm nhiều hình khác nhau, rồi tính tổng diện tích qua đa hình.
Gợi ý
Gọi h->dienTich() trên từng con trỏ lớp cha, đa hình chọn đúng công thức theo kiểu thực.
Vì xóa đối tượng qua con trỏ lớp cha nên hủy tử lớp cha phải ảo.
#include <iostream>
using namespace std;
class Hinh {
public:
virtual double dienTich() const = 0; // pure virtual
virtual ~Hinh() {} // virtual destructor for a polymorphic base
};
class HinhChuNhat : public Hinh {
double d, r;
public:
HinhChuNhat(double dai, double rong) : d(dai), r(rong) {}
double dienTich() const override { return d * r; }
};
class HinhTron : public Hinh {
double b;
public:
HinhTron(double banKinh) : b(banKinh) {}
double dienTich() const override { return 3.14159 * b * b; }
};
int main() {
Hinh* ds[2] = { new HinhChuNhat(3, 4), new HinhTron(2) };
double tong = 0;
for (Hinh* h : ds) tong += h->dienTich(); // polymorphic call
cout << tong << "\n";
for (Hinh* h : ds) delete h; // safe thanks to virtual destructor
return 0;
}
Xem lại bài Kế thừa và đa hình.
Bài 11: so sánh bằng với operator==
Cho lớp PhanSo với tử và mẫu. Nạp chồng operator== sao cho hai phân số bằng nhau về
giá trị (ví dụ 1/2 và 2/4) được coi là bằng.
Gợi ý
Thay vì so sánh từng trường, hãy nhân chéo: a/b bằng c/d khi a*d == c*b. Nên đánh
dấu toán tử là const vì nó không đổi đối tượng.
#include <iostream>
using namespace std;
class PhanSo {
int tu, mau;
public:
PhanSo(int t, int m) : tu(t), mau(m) {}
// cross multiply so 1/2 equals 2/4
bool operator==(const PhanSo& o) const {
return tu * o.mau == o.tu * mau;
}
};
int main() {
PhanSo a(1, 2), b(2, 4), c(1, 3);
cout << (a == b) << "\n"; // 1 (true)
cout << (a == c) << "\n"; // 0 (false)
return 0;
}
Xem lại bài Nạp chồng toán tử.
Bài 12: cộng vector hai chiều
Viết lớp Vector2D với hai thành phần x và y. Nạp chồng operator+ để cộng hai
vector, trả về một vector mới.
Gợi ý
operator+ nhận tham chiếu hằng tới vector kia và trả về một Vector2D mới có tọa độ là
tổng từng thành phần. Không đổi hai toán hạng nên đánh dấu const.
#include <iostream>
using namespace std;
class Vector2D {
double x, y;
public:
Vector2D(double a = 0, double b = 0) : x(a), y(b) {}
Vector2D operator+(const Vector2D& o) const {
return Vector2D(x + o.x, y + o.y); // return a new vector
}
void in() const { cout << "(" << x << ", " << y << ")\n"; }
};
int main() {
Vector2D u(1, 2), v(3, 4);
Vector2D w = u + v;
w.in(); // (4, 6)
return 0;
}
Xem lại bài Nạp chồng toán tử.
Bài 13: phương tiện trừu tượng
Định nghĩa lớp trừu tượng PhuongTien với hàm ảo thuần tocDoToiDa(). Viết XeMay và
MayBay cài đặt riêng. Viết một hàm nhận const PhuongTien& và in tốc độ tối đa.
Gợi ý
Hàm ảo thuần = 0 khiến PhuongTien không tạo đối tượng trực tiếp được, buộc mỗi lớp con
phải override. Hủy tử lớp cha để ảo cho an toàn.
#include <iostream>
using namespace std;
class PhuongTien {
public:
virtual double tocDoToiDa() const = 0; // pure virtual makes the class abstract
virtual ~PhuongTien() {} // virtual destructor
};
class XeMay : public PhuongTien {
public:
double tocDoToiDa() const override { return 120; }
};
class MayBay : public PhuongTien {
public:
double tocDoToiDa() const override { return 900; }
};
void inTocDo(const PhuongTien& p) {
cout << p.tocDoToiDa() << "\n";
}
int main() {
XeMay xe;
MayBay may;
inTocDo(xe); // 120
inTocDo(may); // 900
return 0;
}
Xem lại bài Lớp trừu tượng và interface.
Bài 14: ghi đè và gọi lại phương thức lớp cha
Cho lớp NhanVien có phương thức ảo moTa(). Viết lớp con QuanLy ghi đè moTa() nhưng
vẫn gọi lại NhanVien::moTa() trước khi in thêm phần riêng của mình.
Gợi ý
Trong bản ghi đè, dùng cú pháp LopCha::phuongThuc() để tái sử dụng hành vi của lớp cha
thay vì viết lại từ đầu.
#include <iostream>
using namespace std;
class NhanVien {
public:
virtual void moTa() const {
cout << "nhan vien";
}
virtual ~NhanVien() {}
};
class QuanLy : public NhanVien {
public:
void moTa() const override {
NhanVien::moTa(); // reuse the base version first
cout << " (quan ly)\n";
}
};
int main() {
QuanLy q;
q.moTa(); // nhan vien (quan ly)
return 0;
}
Xem lại bài Kế thừa và đa hình.
Câu hỏi tự kiểm
- 1Xóa một đối tượng lớp con qua con trỏ lớp cha (DongVat*). Để hủy tử lớp con thật sự chạy thì cần điều kiện gì?
- 2operator<< để in một đối tượng ra cout thường được cài đặt thế nào?
- 3Từ khóa override khi ghi đè một phương thức ảo có tác dụng gì?
- 4Truyền một đối tượng lớp con vào hàm nhận tham số lớp cha theo GIÁ TRỊ (DongVat d) sẽ xảy ra chuyện gì?