CHƯƠNG 11: ĐỐI TƯỢNG VÀ LỚP
• Nội dung
Đối tượng Lớp Con trỏ this Hàm thiết lập Hàm hủy bỏ Hàm thiết lập sao chép Các thành phần tĩnh Hàm bạn và lớp bạn
Đối tượng
• Đối tượng là một khái niệm trong lập trình hướng đối tượng biểu thị sự liên kết
giữa dữ liệu và các thủ tục (gọi là phương thức) thao tác trên dữ liệu
ĐỐI TƯỢNG = DỮ LIỆU + PHƯƠNG THỨC
•
Lớp
Lớp là một mô tả nhóm các đối tượng có cùng bản chất, ngược lại mỗi một đối
tượng là một biểu hiện cụ thể cho những mô tả trừu tượng đó.
khai báo lớp
class
{
private:
public:
}; <định nghĩa các hàm thành phần chưa được định nghĩa bên trong khai báo lớp>
• • • • • • • • •
Các thành phần của lớp có thể là thành phần dữ liệu (thuộc tính) hoặc hàm thành
phần (phương thức).
{ private:
int x, y; // Các thành phần dữ liệu riêng
public:
//Các hàm thành phần công cộng void Nhap(); void Xuat();
Thành phần dữ liệu được khai báo giống như khai báo biến Hàm thành phần được khai báo giống như khai báo hàm trong C. Có hai cách định nghĩa một hàm thành phần: định nghĩa trong lớp và ngoài lớp. Tất cả các thành phần được liệt kê sau từ khóa private hoặc protected chỉ được truy nhập bởi các thành phần bên trong lớp đó. Còn tất cả các thành phần được liệt kê sau từ khóa public có thể được truy nhập trong bất kỳ hàm nào. • Ví dụ 1: • Class Diem • • • • • • • • };
//Định nghĩa các hàm thành phần bên ngoài lớp void Diem::Nhap() {
cout <<“Nhap toa do:”; cin >> x >> y;
cout << x <<“,” << y <<“\n”;
} void Diem::Xuat() { } //Hàm chính void main() {
Diem a; //Khai báo một đối tượng a thuộc lớp Diem a.Nhap(); //Đối tượng a thực hiện lời gọi hàm thành phần Nhap() a.Xuat();
}
• • • • • • • • • • • • • • • • Chú ý: Lời gọi hàm thành phần luôn có một và chỉ một tham số ngầm định là đối tượng thực hiện lời gọi hàm. Như vậy các thành phần dữ liệu viết trong các hàm thành phần được hiểu là thuộc đối tượng dùng làm tham số ngầm định trong lời gọi hàm.
Con trỏ this
• C++ sử dụng con trỏ đặc biệt this trong các hàm thành phần. Con trỏ này
luôn trỏ tới đối tượng dùng làm tham số ngầm định trong lời gọi hàm thành phần.
• Ví dụ 2: Các hàm thành phần Nhap() và Xuat() có thể viết theo cách khác
như sau void Diem::Nhap() {
cout <<“Nhap toa do:”; cin >> this->x >> this->y;
} void Diem::Xuat() {
cout << this->x <<“,” << this->y <<“\n”;
• • • • • • • • • }
Hàm thiết lập
Hàm thiết lập là một hàm đặc biệt được gọi tự động mỗi khi có một đối
tượng được khai báo.
Chức năng của hàm thiết lập là để khởi tạo giá trị cho các thành phần dữ
liệu hoặc xin cấp phát vùng nhớ cho các thành phần dữ liệu động.
Hàm thiết lập được khai báo giống như một hàm thành phần với tên trùng
tên với lớp, không có giá trị trả về và không cần khai báo void.
Có thể có nhiều hàm thiết lập trong cùng một lớp (chồng hàm thiết lập).
Khi một lớp có nhiều hàm thiết lập, việc tạo ra các đối tượng phải kèm theo các tham số phù hợp với một trong các hàm thiết lập đã khai bào.
Khi người sử dụng không khai báo tường minh bất kỳ một hàm thiết lập
nào cho lớp thì trình biên dịch tự động phát sinh cho lớp một hàm thiết lập ngầm định không tham số, hàm này không thực hiện bất cứ nhiệm vụ nào ngoài việc “lấp chỗ trống”.
{ private:
int x, y;
public:
. . . Diem(); //Khai báo hàm thiết lập không đối số Diem(int xx); //Khai báo hàm thiết lập một đối số Diem(int xx, int yy); //Khai báo hàm thiết lập hai đối số
}; //Định nghĩa các hàm thành phần
{
cout << “Goi ham thiet lap khong doi so\n”; x = y = 0;
• Ví dụ 3: • Class Diem • • • • • • • • • • • Diem::Diem() • • • • }
{
cout << “Goi ham thiet lap mot doi so\n”; x = xx; y = 0;
}
{
cout << “Goi ham thiet lap hai doi so\n”; x = xx; y = yy;
} //Hàm chính void main() {
Diem a; //Gọi hàm thiết lập không đối số a.Xuat(); Diem b(2); //Gọi hàm thiết lập một đối số b.Xuat(); Diem c(2, -4); //Gọi hàm thiết lập hai đối số c.Xuat();
• Diem::Diem(int xx) • • • • • Diem::Diem(int xx, int yy) • • • • • • • • • • • • • • }
{ private:
int x, y;
public:
• Ví dụ 4: • Class Diem • • • • • • . . . Diem(int xx = 0, int yy = 0); /*Khai báo hàm thiết lập hai đối số có
giá trị ngầm định*/
};
{
cout << “Goi ham thiet lap hai doi so\n”; x = xx; y = yy;
} void main() {
// giống ví dụ 3
• • Diem::Diem(int xx, int yy) • • • • • • • }
Hàm hủy bỏ
Hàm hủy bỏ là một hàm đặc biệt được gọi tự động khi đối tượng tương ứng
bị xóa khỏi bộ nhớ.
Hàm hủy bỏ được khai báo giống như một hàm thành phần với tên bắt đầu bằng dấu ~ và tiếp theo là tên lớp tương ứng, không có đối số và không có giá trị trả về.
Một lớp chỉ có duy nhất một hàm hủy bỏ. Chức năng của hàm hủy bỏ thường dùng để giải phóng vùng nhớ động mà
đối tượng đang quản lý.
Khi người sử dụng không khai báo tường minh một hàm hủy bỏ nào thì
chương trình dịch sẽ tự động sản sinh ra một hàm hủy bỏ ngầm định, hàm này không làm gì cả ngoài việc “lấp chỗ trống”.
void fct(); //Nguyên mẫu hàm của một hàm tự do class Test { private:
int n;
public:
Test(int nn); //Khai báo hàm thiết lập một đối số ~Test(); //Khai báo hàm hủy bỏ
}; //Định nghĩa các hàm thành phần
{
n = nn; cout << “Goi ham thiet lap voi n = “ << n << \n”;
• Ví dụ 5: • • • • • • • • • • • Test::Test(int nn) • • • }
{
cout << “Goi ham huy bo voi n = “ << n << \n”;
} //Định nghĩa hàm tự do void fct(int r) {
Test u(2*r) //Gọi hàm thiết lập 1 đối số
} //Hàm chính void main() {
• Test::~Test() • • • • • • • • • • • •
Test a(1); //Gọi tới hàm thiết lập một đối số for(int i = 1; i <= 2; i++)
fct(i); //gọi hàm tự do fct()
• • }
Sự cần thiết của hàm thiết lập và hàm hủy bỏ
• Trên thực tế với các lớp không có thành phần dữ liệu động chỉ cần sử dụng hàm thiết lập và hủy bỏ ngầm định là đủ. Hàm thiết lập và hủy bỏ do người lập trình tạo ra rất cần thiết khi các lớp có chứa thành phần dữ liệu động. Khi tạo đối tượng hàm thiết lập xin cấp phát một bộ nhớ động, do đó hàm hủy bỏ sẽ giải phóng vùng nhớ động đã được cấp phát trước đó.
class Vector { private:
int n; //số chiều float *p; //con trỏ tới vùng nhớ tọa độ
public:
Vector(); Vector(int nn); ~vector(); void Nhap(); void Xuat();
• Ví dụ 6: • • • • • • • • • };
//Định nghĩa các hàm thành phần
{
cout << “Goi ham thiet lap khong doi\n”; cout << “Nhap so chieu:”; cin >> n; p =new float[n];
}
{
cout << “Goi ham thiet lap mot doi\n”; n = nn; p =new float[n];
}
{
cout << “Goi ham huy bo\n”; delete[]p; p = NULL;
• • Vector::vector() • • • • • Vector::vector(int nn) • • • • • Vector::~Vector() • • • • }
void Vector::Nhap() {
for(int i = 0; i < n; i++) {
cout << “Toa do thu “ << i <<“:”; cin >> p[i];
}
} void Vector::Xuat() {
for(int i = 0; i < n; i++)
cout << p[i] << “\t”
cout << “\n”;
• • • • • • • • • • • • • • }
//Hàm chính void main() {
Vector a; //Gọi hàm thiết lập không đối a.Nhap(); a.Xuat(); Vector b(3); //Gọi hàm thiết lập một đối b.Nhap(); b.Xuat();
}
• • • • • • • • • • • Chú ý: Nếu chỉ sử dụng hàm thiết lập mặc định thì đối tượng a được tạo ra bởi các lệnh khai báo sẽ chưa có bộ nhớ để chứa các tọa độ. Như vậy đối tượng chưa hoàn chỉnh và chưa dùng được.
Hàm thiết lập sao chép
Hàm thiết lập được gọi khi khai báo và khởi tạo nội dung một đối tượng
thông qua một đối tượng khác gọi là hàm thiết lập sao chép. Nhiệm vụ của hàm thiết lập sao chép là tạo ra một đối tượng giống hệt một đối tượng đã có trước đó. Dạng khai bao hàm thiết lập sao chép:
Ngoài tình huống trên đây, còn có hai tình huống cần dùng hàm thiết lập
sao chép: truyền đối tượng cho hàm bằng tham trị hoặc hàm trả về một đối tượng giống hệt một đối tượng cùng lớp đã có trước đó.
Giống như hàm thiết lập ngầm định, nếu không được khai báo tường minh, sẽ có một hàm thiết lập sao chép ngầm định do chương trình dịch cung cấp. Hàm này chỉ thực hiện thao tác tối thiểu: sao chép giá trị của các thành phần dữ liệu trong đối tượng nguồn cho các thành phần dữ liệu tương ứng trong đối tượng đích.
class Diem { private:
. . .
public:
. . . Diem(Diem &u); //Khai báo hàm thiết lập sao chép float KC(Diem u);
}; //Định nghĩa hàm thiết lập sao chép
{
cout << “Goi ham thiet lap sao chep\n”; x = u.x; y = u.y;
• Ví dụ 7: • • • • • • • • • • • Diem::Diem(Diem &u) • • • • }
void main() {
Diem a(2, -6); a.Xuat(); Diem b = a; //Gọi hàm thiết lập sao chép b.Xuat(); Diem c(-6, 8); float d = a.KC(c); //Gọi hàm thiết lập sao chép cout << “khoang cach tu a toi c la “ << d << “\n”;
}
• Ví dụ 7: • • • • • • • • • • • Chú ý: Hàm thiết lập sao chép được khai báo tường minh trong ví dụ trên có chức năng tương tự như hàm thiết lập sao chép mặc định. Nói chung, với các lớp không khai báo thành phần dữ liệu động thì chỉ cần sử dụng hàm thiết lập sao chép ngầm định là đủ. Tuy nhiên với những lớp có chứa thành phần dữ liệu động không được dùng hàm thiết lập sao chép ngầm định mà phải gọi hàm thiết lập sao chép tường minh.
class Vector { private:
. . .
public:
. . . Vector(Vector &u); //Hàm thiết lập sao chép int GetN() { return n;} float Nhan(Vector u); //Tích vô hướng hai vector
}; //Định nghĩa các hàm thành phần
{
cout << “Goi ham thiet lap sao chep của lop Vector\n”; n = u.n; p = new int [n]; //Cấp phát vnđ bằng kích thước có trong đt nguồn //Gán nội dung vnđ trong đt nguồn sang đt đích for(int i =0; i < n; i++)
p[i] = u.p[i];
• Ví dụ 8: • • • • • • • • • • • • Vector::Vector(Vector &u) • • • • • • • }
float Vector::Nhan(Vector u) {
float res = 0; for(i = 0; i < n; i++)
res += p[i] * u.p[i];
return res;
} //Hàm chính void main() {
. . . Vector c = a; //Gọi hàm thiết lập sao chép c.Xuat(); if(b.GetN() == c.GetN()) {
float kq = b.Nhan(c); //Gọi hàm thiết lập sao chép cout << “b * c = “ << kq <<“\n”;
} else cout << “Khong thuc hien duoc phép toán b * c\n”;
• • • • • • • • • • • • • • • • • }
• Chú ý: Việc truyền tham số đối tượng cho hàm bằng giá trị đòi hỏi phải có hàm thiết lập sao chép. Ngoài ra nó cũng làm cho chương trình chạy chậm vì phải mất thời gian sao chép một lượng lớn dữ liệu. Do đó người ta thường chọn cách truyền tham chiếu.
Cần phân biệt giữa hàm thiếp lập sao chép và phép gán. Phép gán thực hiện việc sao chép nội dung từ đối tượng này sang đối tượng khác, do vậy cả hai đối tượng trong phép gán đều đã tồn tại. Ngược lại hàm thiết lập thực hiện đồng thời hai nhiệm vụ: tạo đối tượng và thực hiện việc sao chép nội dung từ một đối tượng đã có sang đối tượng mới tạo ra đó.
void main() {
. . . b = a; //Phép gán Vector c = a; //Gọi tới hàm thiết lập sao chép
• Ví dụ 9: • • • • • • }
Hàm bạn và lớp bạn
• Các hàm hay lớp nếu được khai báo là bạn của lớp thì có thể truy nhập tới
mọi thành phần private của lớp.
class MaTran; //Khai báo trước lớp MaTran class Vector { . . . public:
. . . //Khai báo hàm tự do Nhan() là bạn của lớp vector friend Vector Nhan(MaTran u, Vector v);
}; class MaTran . . . { public:
. . . //Khai báo hàm tự do Nhan() là bạn của lớp MaTran friend Vector Nhan(MaTran u, vector v);
• Ví dụ 10: Xây dựng một hàm nhân MaTran với Vector • • • • • • • • • • • • • • • };
{
Vector res(u.m); for(int i = 0; i < u.m; i++) { res.p[i] = 0; for(int j = 0; j < v.n; j++)
res.p[i] += u.p[i][j] * v.p[j];
} return res;
} void main() {
Vector a(3); a.Xuat(); Matran b(2, 3); b.Xuat(); if(b.GetN() == a.GetN()) {
Vector c = Nhan(b, a); c.Xuat();
} else cout << “b * a khong thuc hien duoc\n”;
• Vector Nhan(Matran u, Vector v); //Khai báo nguyên mẫu //Định nghĩa hàm tự do Nhan() • • Vector Nhan(MaTran u, Vector v) • • • • • • • • • • • • • • • • }
Hàm thiết lập và đối tượng thành phần
class tamGiac { private:
Diem A, B, C;
public:
TamGiac(int x1, int y1, int x2, int y2, int x3, int y3); . . .
};
Một lớp có thành phần dữ liệu là đối tượng lớp khác được gọi là lớp bao • Ví dụ 12: • • • • • • • • • A, B, C là các đối tượng thành phần • TamGiac là lớp bao • Diem là lớp thành phần (của TamGiac)
Khi xây dựng hàm thiết lập của lớp bao, phải sử dụng các hàm thiết lập của lớp thành phần để khởi gán cho các đối tượng thành phần của lớp bao.
{
cout << “Goi ham thiet lap 6 doi so cua lop TamGiac\n”;
}
• Ví dụ 13: • TamGiac::TamGiac(int x1, int y1, int x2, int y2, int x3, int y3):A(x1, y1), B(x2, y2), C(x3, y3) • • • • • Trong hàm thiết lập ở trên của lớp TamGiac các đối tượng thành phần A,
B, C đều được khởi gán bằng hàm thiết lập hai đối của lớp Diem
Các thành phần tĩnh (static)
Các thành phần dữ liệu tĩnh của một lớp được dùng chung (chia sẽ) bởi mọi đối tượng thuộc về lớp đó. Các thành phần dữ liệu tĩnh được gọi là thành phần dữ liệu lớp.
{ private:
static int n; //Thành phần dữ liệu static
public:
Counter(); ~Counter();
• Ví dụ 11: Đếm số đối tượng được tạo ra void fct(); • • Class Counter • • • • • • •
}; int Counter::n = 0; // Khởi tạo thành phần dữ liệu tĩnh bên ngoài lớp
//Định nghĩa các hàm thành phần
cout<<“Tao: Bay gio co “ << ++n << “doi tuong\n”;
{ }
cout<<“Huy: Bay gio con “ << --n << “doi tuong\n”;
Counter u, v;
{ } //Hàm tự do void fct() { } //Hàm chính void main() {
Counter a; fct(); Counter b;
• • Counter::Counter() • • • Counter::~Counter() • • • • • • • • • • • • • }
Các hàm thành phần tĩnh của một lớp cũng độc lập với bất kỳ đối tượng
nào của lớp. Nó thường được dùng để xử lý chung trên tất cả các đối tượng của lớp, chẳng hạn để hiển thị thông tin liên quan đến các thành phần dữ liệu tĩnh. Cú pháp gọi hàm thành phần tĩnh như sau:
{ private:
static int n; //Thành phần dữ liệu static
public:
. . . static void Display(); //Hàm thành phần static
Ví dụ 12: Cải tiến ví dụ 11 void fct(); • • Class Counter • • • • • • •
}; int Counter::n = 0; // Khởi tạo thành phần dữ liệu tĩnh bên ngoài lớp
cout<<“Hien dang co “ << n << “doi tuong\n”;
//Định nghĩa các hàm thành phần void Counter::Display() { } //Định nghĩa hàm tự do void fct() {
Counter u; Counter::Display(); Counter v; Counter::Display();
} //Hàm chính void main() {
Counter a; fct(); Counter::Display(); Counter b;
• • • • • • • • • • • • • • • • • • }