Professional Documents
Culture Documents
KTLT
KTLT
I_ Basic Definitions
// Các khái niệm cơ bản
Nội dung
V_ Virtual Destructors
// Hàm hủy ảo
Nội dung
Ví dụ: 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 vừa là học sinh, đồng thời là một người con
trai, một người bạn, một người anh. 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.
Ví dụ
#include <iostream>
class TamGiac: public Hinh{
using namespace std;
public:
TamGiac( int a=0, int b=0):Hinh(a, b) { }
int dt=0;
int dientich ()
class Hinh {
{
protected:
dt= (chieurong * chieucao / 2);
int chieurong, chieucao;
cout << "Dien tich cua lop TamGiac la: " << dt
public:
<< endl;
Hinh( int a=0, int b=0)
}
{
};
chieurong = a;
// ham main cua chuong trinh
chieucao = b;
int main( )
}
{
int dientich()
Hinh *hinh;
{
HinhChuNhat hcn(13,6);
cout << "Dien tich cua lop cha:" <<endl;
TamGiac tag(8, 9);
return 0;
}
// luu giu dia chi cua HinhChuNhat
};
hinh = &hcn;
class HinhChuNhat: public Hinh{
// goi dien tich cua hinh chu nhat.
public:
hinh->dientich();
HinhChuNhat( int a=0, int b=0):Hinh(a, b) { }
int dientich ()
// luu giu dia chi cua TamGiac
{
hinh = &tag;
dt= (chieurong * chieucao);
// goi dien tich cua tam giac.
cout << "Dien tich cua lop HinhChuNhat la: " << dt <<
hinh->dientich();
endl;
return 0;
}
}
};
HÀM ẢO
Virtual Member 02
Function
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ở
- 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
Hàm ảo
Nếu trình biên dịch biết mọi thông tin cần biết
để gọi được biết ngay trong thời gian biên dịch,
tức là biết hàm nào sẽ được gọi ngay trong thời
gian biên dịch thì gọi là early binding
Còn nếu trình biên dịch không biết hàm nào được
gọi cho đến khi thời gian chạy thì gọi là late
binding, liên quan đến những hàm chỉ được thực
Liên kết tĩnh
class Base {
public:
Gọi hàm trực void display() {
cout << "In Base class" << endl;
tiếp: <iostream>
#include }
};
#include <iostream>
int main(){
void (*ptrFunc)(int) =
display;
ptrFunc(5);
return 0;
}
Output: 5
Liên kết động
Con trỏ hàm:
Trong ví dụ này, trình biên dịch ko biết
#include <iostream> hàm nào đang được lưu trong con trỏ hàm
cho tới thời gian chạy, thế nên ko thể là liên
using namespace std; kết tĩnh (early binding)
Khi đó, trình biên dịch giữ nó lại đến khi
void display(int x){ tham chiếu của hàm xuất hiện, máy tính sẽ trỏ
cout << x; tới địa chỉ của con trỏ hàm, sau đó gọi hàm
} tới chương trình.
int main(){
void (*ptrFunc)(int) =
display;
ptrFunc(5);
return 0;
}
Output: 5
Sau đây là ví dụ Hàm ảo với lớp:
Liên kết động
#include<iostream>
using namespace std;
class Base {
public:
virtual void display() {
cout << "In Base class" << endl;
}
};
}
cout << "In Derived class" << endl; Output: In Derived class
};
int main(){
Có một giải pháp đơn giản cho vấn đề này – khai báo
một hàm hủy lớp cơ sở là hàm ảo (virtual). Nó sẽ làm
cho tất cả các hàm hủy ở lớp thừa kế sẽ là ảo ngay cả
khi nó không có cùng tên với hàm hủy lớp cơ sở. Bây
giờ, nếu đối tượng thừa kế bị hủy một cách rõ ràng
bằng cách gọi toán tử delete của lớp cơ sở cho đối
tượng ở lớp thừa kế, thì hàm hủy của lớp tương ứng
sẽ được gọi.
Ví dụ: Giả sử có lớp cha Base và một lớp con Derived được hiện thực như dư
đây.
Trường hợp 1: Phương thức lớp cha không đánh dấu Virtual
class Base
{
public:
Base() {};
~Base() { cout << "Destructor Base\n"; };
};
int main()
{
Base *b = new Derived();
delete b;
return 0;
}
int main()
{
Base *b = new Derived();
delete b;
return 0;
}
Trường hợp 2: Phương thức lớp cha có đánh dấu Virtual
Trường hợp 2: Phương thức lớp cha có đánh dấu Virtual
class Base
{
Như vậy phương thức hủy public:
của lớp con được gọi trước Base() {};
sau đó mới gọi phương thức virtual ~Base() { cout << "Destructor Base\n"; };
hủy lớp cha và các thủ tục };
cần thiết trước khi hủy các
đối tượng đã được thực hiện
đầy đủ. class Derived : public Base
{
public:
Derived() {};
~Derived() { cout << "Destructor Derived\n"; }
};
int main()
{
Base *b = new Derived();
delete b;
return 0;
}
Destructor Derived
Output
Destructor Base
Tại Sao Bạn Nên Khai Báo Một Hàm Hủy Là Ảo
Giả sử có class Base và class Derived kế thừa từ Base
Ta định nghĩa con trỏ: Base * b = new Derived();
Lúc này để tạo ra Derived(), thì Base() phải được tạo ra trước. Khi chúng ta delete b, thì cả 2 đối tượng
này cũng phải được gọi Destructor. Vậy nếu không khai báo virtual cho hàm Destructor của Base, thì
chỉ Destructor của Base được gọi, đối tượng Derived() vẫn còn đó. Nói ngắn gọn đơn giản, hàm hủy
ảo giúp chúng ta hủy các tài nguyên theo thứ tự đúng, khi chúng ta muốn xóa con trỏ trỏ từ lớp
cha đến đối tượng lớp con. Tránh rò rỉ bộ nhớ.
Lời khuyên ở đây, bất cứ khi nào trong chương trình của chúng ta sử dụng đến class có tính đa
hình hoặc class đó có một hoặc nhiều hàm ảo, chúng ta nên viết kèm thêm một hàm hủy ảo cho lớp
cha.
LỚP TRỪU TƯỢNG
& PHƯƠNG TIỆN
Abstract classes 06
& Intefaces
Lớp trừu tượng
Class trong C++ là khái niệm được người dùng định nghĩa như một
kiểu dữ liệu đơn giản hay một cấu trúc dữ liệu, được khai báo bằng từ
khóa class, nó chứa các biến (còn gọi là thuộc tính) hay các hàm (còn
gọi là phương thức).
Các phần tử của class được quản lý bởi ba thuộc tính truy cập:
private, protected hoặc public (thuộc tính mặc định khi truy cập vào
một phần tử trong class là private). Các phần tử private không thể được
truy cập bên ngoài class mà chỉ có thể được truy cập thông qua các
phương thức của class chứa chúng. Ngược lại, các phần tử public có thể
được truy cập ở bất kỳ class nào.
Lớp trừu tượng
Lớp trừu tượng trong C++ là một lớp mà không thể tạo đối tượng từ
nó trực tiếp. Điều này thường được sử dụng để tạo ra một giao diện
chung cho các lớp con, mà sau đó các lớp con cụ thể sẽ triển khai các
phương thức ảo của nó. Lớp trừu tượng có thể chứa các phương thức
ảo, các phương thức không ảo, các biến dữ liệu và các hàm cũng như
các hằng số và phương thức static.
Để định nghĩa một lớp trừu tượng, bạn cần có ít
nhất một phương thức ảo thuần túy (pure virtual
method), được khai báo bằng cách sử dụng từ khóa
virtual và đặt dấu = 0 ở cuối. Một lớp trừu tượng
cũng có thể chứa các phương thức không ảo và dữ
liệu thành viên thông thường.
// Hàm hủy ảo
virtual ~Drawable() {}
};
//Interface
Đặc điểm của interface
class Drawable { + Chỉ chứa khai báo không chứa phần định nghĩa.
public:
// Phương thức ảo thuần túy để vẽ + Việc ghi đè một thành phần trong interface cũng
virtual void draw() const = 0;
không cần từ khóa override
// Phương thức ảo thuần túy để xóa
virtual void erase() const = 0; + Không thế khai báo phạm vi truy cập trong
Interface và các thành phần này mặc định là public
// Hàm hủy ảo
virtual ~Drawable() {} + Interface không chứa các thuộc tính (các biến) dù là
};
hằng số hay biến tĩnh
// Lớp con triển khai interface
class Circle : public Drawable { + Interface k có constructor và k có destructor
public:
// Triển khai phương thức vẽ
+ Các lớp có thể thực thi nhiều interface cung một lục
void draw() const override { ( Đây là đặc điểm được coi như là thay thế tính đa kế
std::cout << "Drawing Circle." << std::endl;
} thừa)
// Triển khai phương thức xóa + Một interface có thể kế thừa nhiều interface khác
void erase() const override {
std::couit << "Erasing Circle." << std::endl; nhưng không thể kế thừa bất kỳ lớp nào.
} Interface giúp tạo ra một giao diện chung cho các lớp,
};
từ đó tăng tính linh hoạt và tái sử dụng trong mã
Abstract Class vs.
Interfaces
Giống nhau
+ Đều được sử dụng để đạt được tính trừu tượng trong OOP
+ Đêù được sử dụng trong kế thừa
+ Đều có thể chứa cả phương thức và các trường dữ liệu (variables)
+ Đều không thể tạo đội tượng cụ thể với keyword new
Abstract class Interface
Keyword abstract Khai báo tường minh Mặc định
Các kiểu biến Có thể chứa mọi loại biến: final, non-final, Chỉ có thể chứa các biến static và final
static, non-static
Hằng số Có thể có hoặc không Mặc định các biến số trong interface là hằng
số
Khác nhau Loại phương thức Hỗ trợ tất cả các loại phương thức : Chỉ chứa các phương thức: abstract, default,
constructor, static, non-static, abstract static, private
Sự triển khai lẫn nhau Abstract class có thể cung cấp triển khai Interface không thê cung cấp triền khai cho
cho interface abtract class
Đa kế thừa Không hỗ trợ đa kế thừa với nhiều lớp khác Một interface có thể kế thừa nhiều interface
khác
Đa thực thi Abstract class có thể mở rộng nhiều lớp Interface chỉ có thể extend các interface
khác và thực hiện nhiều interface tùy ý khác
Access modifiers Hỗ trọ tất cả các loại access modifiers Mặc định các thành phần của interface là
(Quyền truy cập) public
Vậy phải làm thế nào để thu được kết quả chính
xác?
Hàm ảo và Ghi đè (Virtual Functions & Override)
#include <iostream> // Lớp kế thừa từ Rectangle: Hình vuông
class Square : public Shape {
// Lớp cơ sở public:
class Shape { // Hàm khởi tạo
public: Square (double side) : Rectangle(side, side){}
// Hàm thành viên ảo để tính diện tích
virtual double area() const = 0; // Định nghĩ lại hàm thành viên ảo area() để tính diện
}; tích hình vuông
double area() const override {
// Lớp kế thừa từ Shape: Hình chữ nhật return side * side; // hoặc return Rectangle::area();
class Rectangle : public Shape { }
protected: };
double length;
double width; int main(){
// Tạo một hình chữ nhật
public: Rectangle rectangle(4, 5);
// Hàm khởi tạo // Tính diện tích hình chữ nhật
Rectangle (double l, double w) : length(l), width(w){} std::cout << "Diện tích hình chữ nhật: " <<
rectangle.area() << std::endl;
// Định nghĩ lại hàm thành viên ảo area() để tính diện
tích hình chữ nhật // Tạo một hình chữ nhật
double area() const override { Square square(3);
return length * width; // Tính diện tích hình vuông
} std::cout << "Diện tích hình vuông: " << square.area()
}; << std::endl;
return 0;
}
Hàm ảo và Ghi đè (Virtual Functions & Override)
#include <iostream> // Lớp kế thừa từ Rectangle: Hình vuông
class Square : public Shape {
// Lớp cơ sở public:
class Shape { // Hàm khởi tạo
public: Square (double side) : Rectangle(side, side){}
// Hàm thành viên ảo để tính diện tích
virtual double area() const = 0; // Định nghĩ lại hàm thành viên ảo area() để tính diện
}; tích hình vuông
double area() const override {
// Lớp kế thừa từ Shape: Hình chữ nhật return side * side; // hoặc return Rectangle::area();
class Rectangle : public Shape { }
protected: };
double length;
double width; int main(){
// Tạo một hình chữ nhật
public: Rectangle rectangle(4, 5);
// Hàm khởi tạo // Tính diện tích hình chữ nhật
Rectangle (double l, double w) : length(l), width(w){} std::cout << "Diện tích hình chữ nhật: " <<
rectangle.area() << std::endl;
// Định nghĩ lại hàm thành viên ảo area() để tính diện
tích hình chữ nhật // Tạo một hình chữ nhật
double area() const override { Square square(3);
return length * width; // Tính diện tích hình vuông
} std::cout << "Diện tích hình vuông: " << square.area()
}; << std::endl;
return 0;
}
Ưu điểm của hàm virtual so vs việc ghi đè
• Tiện lợi cho quản lý mã nguồn lớn
• Khả năng mở rộng dễ dàng
• Tính đa hình linh hoạt
• Giúp tránh nhầm lẫn