Chương 3
Phương pháp lập trình
hướng đối tượng
• Là phương pháp lập trình hỗ trợ công nghệ đối tượng.
• OOP được xem là giúp tăng năng suất, đơn giản hóa độ phức tạp
khi bảo trì cũng như mở rộng phần mềm bằng cách cho phép lập
trình viên tập trung vào các đối tượng phần mềm ở bậc cao hơn.
• Những đối tượng trong một ngôn ngữ OOP là các kết hợp giữa mã
và dữ liệu mà chúng được nhìn nhận như là một đơn vị duy nhất.
• Mỗi đối tượng có một tên riêng biệt và tất cả các tham chiếu đến đối
tượng đó được tiến hành qua tên của nó. Như vậy, mỗi đối tượng
có khả năng nhận vào các thông báo, xử lý dữ liệu (bên trong của
nó), và gửi ra hay trả lời đến các đối tượng khác hay đến môi
trường.
Lập trình hướng đối tượng
(OOP - object-oriented programming)
Đối tượng (Object)
• Đối tượng: Các dữ liệu và chỉ thị được kết hợp vào một đơn vị đầy đủ tạo nên
một đối tượng. Đơn vị này tương đương với một chương trình con và vì thế các
đối tượng sẽ được chia thành hai bộ phận chính: phần các phương thức
(method) và phần các thuộc tính (property).
• Trong thực tế, các phương thức của đối tượng là các hàm và các thuộc tính
của nó là các biến, các tham số hay hằng nội tại của một đối tượng (hay nói
cách khác tập hợp các dữ liệu nội tại tạo thành thuộc tính của đối tượng).
• Các phương thức là phương tiện để sử dụng một đối tượng trong khi các
thuộc tính sẽ mô tả đối tượng có những tính chất gì.
•
Các phương thức và các thuộc tính thường gắn chặt với thực tế các đặc tính và
sử dụng của một đối tượng.
•
Trong thực tế, các đối tượng thường được trừu tượng hóa qua việc định nghĩa
của các lớp (class).
• Tập hợp các giá trị hiện có của các thuộc tính tạo nên trạng thái của một đối
tượng.
• Mỗi phương thức hay mỗi dữ liệu nội tại cùng với các tính chất được định
nghĩa (bởi người lập trình) được xem là một đặc tính riêng của đối tượng.
3.1. Các tính chất cơ bản
 Tính trừu tượng (abstraction):
 Là khả năng của chương trình bỏ qua hay không chú ý đến một số khía cạnh
của thông tin mà nó đang trực tiếp thực hiện, nghĩa là nó có khả năng tập
trung vào những cốt lõi cần thiết.
 Mỗi đối tượng có thể hoàn tất các công việc một cách nội bộ, liên lạc với các
đối tượng khác mà không cần cho biết làm cách nào đối tượng tiến hành
được các thao tác. Tính chất này thường được gọi là sự trừu tượng của dữ
liệu.
 Tính trừu tượng còn thể hiện qua việc một đối tượng ban đầu có thể có một
số đặc điểm chung cho nhiều đối tượng khác như là sự mở rộng của nó.
 Tính trừu tượng này thường được xác định trong khái niệm gọi là lớp trừu
tượng hay lớp cơ sở trừu tượng.
 Tính đóng gói (encapsulation) và che dấu thông tin (information hiding):
 Tính chất này không cho phép người sử dụng các đối tượng thay đổi
trạng thái nội tại của một đối tượng.
 Chỉ có các phương thức nội tại của đối tượng cho phép thay đổi trạng thái
của nó.
 Việc cho phép môi trường bên ngoài tác động lên các dữ liệu nội tại của
một đối tượng theo cách nào đó là hoàn toàn tùy thuộc vào người viết mã.
Đây là tính chất đảm bảo sự toàn vẹn của đối tượng.
Tính đa hình (polymorphism):
Thể hiện thông qua việc gửi các thông điệp (message). Việc gửi các thông
điệp này có thể so sánh như việc gọi các hàm bên trong của một đối tượng.
Các phương thức dùng trả lời cho một thông điệp sẽ tùy theo đối tượng mà
thông điệp đó được gửi tới sẽ có phản ứng khác nhau.
Người lập trình có thể định nghĩa một đặc tính (chẳng hạn thông qua tên
của các phương thức) cho một loạt các đối tượng gần nhau nhưng khi thi
hành thì dùng cùng một tên gọi mà sự thi hành của mỗi đối tượng sẽ tự
động xảy ra tương ứng theo đặc tính của từng đối tượng mà không bị nhầm
lẫn.
Thí dụ khi định nghĩa hai đối tượng "hinh_vuong" và "hinh_tron" thì có một
phương thức chung là "chu_vi". Khi gọi phương thức này thì nếu đối tượng
là "hinh_vuong" nó sẽ tính theo công thức khác với khi đối tượng là
"hinh_tron".
 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.
3.2. Một số khái niệm
Lớp (class)
• Một lớp có thể được hiểu là khuôn mẫu để tạo ra các đối tượng.
Trong một lớp, người ta thường dùng các biến để mô tả các thuộc
tính và các hàm để mô tả các phương thức của đối tượng.
• Khi đã định nghĩa được lớp, ta có thể tạo ra các đối tượng từ lớp
này. Để việc sử dụng được dễ dàng, thông qua hệ thống hàm tạo
(constructor), người ta dùng lớp như một kiểu dữ liệu để tạo ra các
đối tượng.
Lớp con (subclass)
• Lớp con là một lớp thông thường nhưng có thêm tính chất kế thừa
một phần hay toàn bộ các đặc tính của một lớp khác. Lớp mà chia
sẽ sự kế thừa gọi là Lớp cha (parent class).
Lớp trừu tượng hay lớp cơ sở trừu tượng (abstract class)
• Lớp trừu tượng là một lớp mà nó không thể thực thể hóa thành một đối
tượng thực dụng được. Lớp này được thiết kế nhằm tạo ra một lớp có các
đặc tính tổng quát nhưng bản thân lớp đó chưa có ý nghĩa (hay không đủ ý
nghĩa) để có thể tiến hành viết mã cho việc thực thể hóa.
• Thí dụ: Lớp "hinh_thang" được định nghĩa không có dữ liệu nội tại và chỉ có
các phương thức (hàm nội tại) "tinh_chu_vi", "tinh_dien_tich". Nhưng vì lớp
hinh_thang này chưa xác định được đầy đủ các đặc tính của nó (cụ thể các
biến nội tại là tọa độ các đỉnh nếu là đa giác, là đường bán kính và toạ độ
tâm nếu là hình tròn, ...) nên nó chỉ có thể được viết thành một lớp trừu
tượng. Sau đó, người lập trình có thể tạo ra các lớp con chẳng hạn như là
lớp "tam_giac", lớp "hinh_tron", lớp "tu_giac",.... Và trong các lớp con này
người viết mã sẽ cung cấp các dữ liệu nội tại (như là biến nội tại r làm bán
kính và hằng số nội tại Pi cho lớp "hinh_tron" và sau đó viết mã cụ thể cho
các phương thức "tinh_chu_vi" và "tinh_dien_tich").
Phương thức (method)
• Phương thức của một lớp thường được dùng để mô tả các hành vi
của đối tượng (hoặc của lớp).
• Khi thiết kế, người ta có thể dùng các phương thức để mô tả và thực
hiện các hành vi của đối tượng.
• Mỗi phương thức thường được định nghĩa là một hàm, các thao tác
để thực hiện hành vi đó được viết tại nội dung của hàm.
• Một số loại phương thức đặc biệt:
Hàm tạo (constructor) là hàm được dùng để tạo ra một đối tượng,
cài đặt các giá trị ban đầu cho các thuộc tính của đối tượng đó.
Hàm hủy (destructor) là hàm dùng vào việc làm sạch bộ nhớ đã
dùng để lưu đối tượng và hủy bỏ tên của một đối tượng sau khi đã
dùng xong.
Thuộc tính (attribute)
• Thuộc tính của một lớp bao gồm các biến, các hằng, hay tham số
nội tại của lớp đó. Ở đây, vai trò quan trọng nhất của các thuộc tính
là các biến vì chúng sẽ có thể bị thay đổi trong suốt quá trình hoạt
động của một đối tượng.
• Các thuộc tính có thể được xác định kiểu và kiểu của chúng có thể
là các kiểu dữ liệu cổ điển hay đó là một lớp đã định nghĩa từ
trước.
Quan hệ giữa lớp và đối tượng
• Lớp trong là cách phân loại các thực thể dựa trên những đặc điểm chung
của các thực thể đó. Do đó lớp là khái niệm mang tính trừu tượng hóa rất
cao.
• Một đối tượng là thực thể hóa (instantiate) của một lớp đã được định nghĩa.
• Công cộng (public)
Là một tính chất được dùng để gán cho các phương thức, các biến nội tại,
hay các lớp mà khi khai báo thì người lập trình đã cho phép các câu lệnh bên
ngoài cũng như các đối tượng khác được phép dùng đến nó.
• Riêng tư (private)
Là sự thể hiện tính chất đóng mạnh nhất (của một đặc tính hay một lớp). Khi
dùng tính chất này gán cho một biến, một phương pháp thì biến hay phương
thức đó chỉ có thể được sử dụng bên trong của lớp mà chúng được định
nghĩa.
• Bảo vệ (protected)
Chỉ có trong nội bộ của lớp đó hay các lớp có quan hệ đặc biệt mới được
phép gọi đến hay dùng đến các thành phần được bảo vệ. Có sự khác nhau
trong các ngôn ngữ lập trình.
• Đa kế thừa (multiple inheritance)
Một lớp con có khả năng kế thừa trực tiếp cùng lúc nhiều lớp khác.
3.3. Các nguyên lý thiết kế
hướng đối tượng
1. Xác định lớp đối tượng
• Đặt câu hỏi "Ta đang nói về cái gì" chứ chưa đặt vấn đề "Ta muốn làm
gì", nghĩa là hướng đến đối tuợng trước. Để xác định đối tượng cần phải
xác dịnh lớp các đối tượng.
• Việc xem xét lớp đối tượng đáng quan tâm có thể dựa trên nguyên lý
sau:
Nếu ta nói về vấn đề nào đó bằng cách quy cho nó các tính chất hay
nếu phải thao tác trên các vấn đề này thì cần phải xác định lớp đối
tượng cho nó.
2. Tự lập và cục bộ:
• Trước hết phải nghĩ dưới dạng đối tượng rồi sau đó mới nghĩ đến
các phép tóan được áp dụng trên nó.
• Phải nghĩ đến việc yêu cầu các đối tượng thực hiện các hành động
của chúng chứ không đi tìm những hàm tòan cục tác động lên nó.
3. Hợp thành và làm mịn:
• Các đối tượng được hợp thành để tạo ra các đối tượng lớn hơn
bằng cách sử dụng kế thừa. Đây là việc làm tự nhiên.
• Làm mịn tức là mô tả một phần mềm theo những phần mềm đã có,
không phải mô tả chúng theo các thành phần mà bằng cách tạo ra
phần mềm mới từ việc kế thừa các đặc trưng của các phần mềm
tổng quát hơn đã có, và chỉ định nghĩa những phần nào là mới
trong phần mềm đang tạo ra.
4. Các bước trong thiết kế hướng đối tượng:
(1) Xác định thực thể trong miền:
Làm thế nào để xác định chính xác lớp đối tượng. Có các cánh sau:
C1: Xem xét một lõi nhỏ các lớp ban đầu được coi là "hiễn nhiên", rồi trong quá
trình thực hiện, có thể thêm vào các lớp mới bằng cách gộp nhóm các lớp hiện
có.
C2: Vét cạn tất cả, trong quá trinh thực hiện sẽ lọai bỏ dần.
C3: Gộp nhóm các tính chất và các quan hệ mà chưa rút ra ngay các lớp. Sau đó
phân lọai các tính chất và từ đó xác định đối tượng.
(2) Cấu trúc miền thông qua việc phân tích các tính chất và quan hệ:
Khi đã định xong các lớp, cần phải biết cách tổ chức và liên kết chúng lại
(3) Xác định các thao tác:
Đầu tiên các định các thao tác mà các dối tượng cần thíết phải có sau đó mới
nâng dần lên ở dạng trừu tượng hơn. Chẳng hạn có thể có thể năng thao tác
của một đối tượng X lên cho lớp cơ sở được nó kế thừa và có thể lớp cơ sở
thao tác này là "không làm gì cả".
(4) Mô tả chính xác các thao tác:
(5) Cho phép thực hiện
3.4. Hạn chế của phương pháp
lập trình OOP
• OOP mô phỏng đối tượng trong thế giới thực (sinh - họat động - tử ) nhưng
không thể làm cho đối tượng tiến hóa. Nếu OOP đưa vào các dự đoán
trước cho yêu cầu tiến triển thì phần mềm sẽ nặng nề.
• Thực tế, một hệ thống là sự đan xen nhau giữa các yêu cầu. Giả sử cần
phải thêm một yêu cầu mới cho một object, OOP buộc phải bổ sung thêm
code cho object đó. Điều này dẫn đến sự chồng chéo giữa các yêu cầu và
làm cho hệ thống trở nên lộn xộn, khó bảo trì, phát triển. Khi bổ sung thêm
một yêu cầu, OOP buộc phải thay đổi các yêu cầu về nghiệp vụ liên quan.
3.6. Nhìn qua về OOP trong C++
Các lớp (Class)
class class_name
{ access_specifier_1:
member1;
access_specifier_2:
member2;
...
} object_names;
• private: các thành phần của lớp chỉ
được truy xuất bởi các thành phần
khác bên trong của chính lớp lớp đó
hoặc từ các lớp là friend.
• protected: Các thành phần chỉ
được truy xuất từ các thành phần
khác cùng lớp và từ các lớp friend,
hoặc các lớp dẫn xuất từ lớp này.
• public: các thành phần được truy
xuất từ bất kỳ ở đâu đối tượng hiển
diện.
Lưu ý: Khia báo private có thể không cần
từ khoá private
Ví dụ:
#include <iostream>
using namespace std;
class CRectangle {
int x, y;
public:
void set_values (int,int);
int area () {return (x*y);}
};
void CRectangle::set_values (int a, int b) {
x = a;
y = b;
}
int main () {
CRectangle rect, rectb;
rect.set_values (3,4);
rectb.set_values (5,6);
cout << "rect area: " << rect.area() << endl;
cout << "rectb area: " << rectb.area() << endl;
return 0;
}
Constructor và destructor
• Cấu tử (Hàm tạo):
- Là một phương thức đặc biệt
được gọi tự động tại thời điểm
đối tượng được tạo
- Mục đích: Khởi tạo giá trị cho đối
tượng.
- Đặc điểm:
+ 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 không khai báo một hàm khởi
tạo, trình biên dịch C++ sẽ tự
động tạo một hàm khởi tạo mặc
định (​
​
không có tham số nào)
#include <iostream>
class CRectangle {
int width, height;
public:
CRectangle (int,int);
int area () {return (width*height);}
};
CRectangle::CRectangle (int a, int b) {
width = a;
height = b;
}
int main () {
CRectangle rect (3,4);
CRectangle rectb (5,6);
cout << "rect area: " << rect.area() << endl;
cout << "rectb area: " << rectb.area() << endl;
return 0;
}
• Các cấu tử overloading
#include <iostream>
class CRectangle {
int width, height;
public:
CRectangle ();
CRectangle (int,int);
int area (void) {return (width*height);}
};
CRectangle::CRectangle () {
width = 5;
height = 5;
}
CRectangle::CRectangle (int a, int b) {
width = a;
height = b;
}
int main () {
CRectangle rect (3,4);
CRectangle rectb;
cout << "rect area: " << rect.area() << endl;
cout << "rectb area: " << rectb.area() << endl;
return 0;
}
Lưu ý:
CRectangle rectb; // đúng
CRectangle rectb(); // sai
• Cấu tử ngầm định:
• Nếu không khai báo cấu tử, thì C++ sử dụng cấu tử ngầm định:
class CExample {
public:
int a,b,c;
void multiply (int n, int m) { a=n; b=m; c=a*b; };
};
Khi đó, có thể khai báo: CExample ex;
• Nếu có khai báo cấu tử:
class CExample {
public: int a,b,c;
CExample (int n, int m) { a=n; b=m; };
void multiply () { c=a*b; };
};
Khi đó:
CExample ex (2,3); //đúng
CExample ex; //sai
• Cấu tử copy:
Nếu khai báo một cấu tử là:
CExample::CExample (const CExample& rv)
{ a=rv.a; b=rv.b; c=rv.c; }
Khi đó:
CExample ex (2,3);
CExample ex2 (ex); // cấu tử copy, dữ liệu sẽ được sao chép từ ex
• Hủy tử (Hàm hủy):
- Hủy tử: Hủy đối tượng.
#include <iostream>
class CRectangle {
int *width, *height;
public:
CRectangle (int,int);
~CRectangle ();
int area () {return (*width * *height);}
};
CRectangle::CRectangle (int a, int b) {
width = new int;
height = new int;
*width = a;
*height = b;
}
CRectangle::~CRectangle () {
delete width;
delete height;
}
int main () {
CRectangle rect (3,4), rectb (5,6);
cout << "rect area: " << rect.area() << endl;
cout << "rectb area: " << rectb.area() << endl;
return 0;
}
Con trỏ đến class
Có thể khai báo các con trỏ đến các
class đã định nghĩa. Ví dụ:
CRectangle * p;
#include <iostream>
class CRectangle {
int width, height;
public:
void set_values (int, int);
int area (void) {return (width * height);}
};
void CRectangle::set_values (int a, int b) {
width = a;
height = b;
}
int main () {
CRectangle a, *b, *c;
CRectangle * d = new CRectangle[2];
b= new CRectangle;
c= &a;
a.set_values (1,2);
b->set_values (3,4);
d->set_values (5,6);
d[1].set_values (7,8);
cout << "a area: " << a.area() << endl;
cout << "*b area: " << b->area() << endl;
cout << "*c area: " << c->area() << endl;
cout << "d[0] area: " << d[0].area() << endl;
cout << "d[1] area: " << d[1].area() << endl;
delete[] d;
delete b;
return 0;
}
a area: 2
*b area: 12
*c area: 2
d[0] area: 30
d[1] area: 56
• Lưu ý cách viết:
Biểu thức Có thể hiểu
*x Trỏ bởi x
&x Điạ chỉ của x
x.y Thành phần y của đối tượng x
x->y Thành phần y của đối tượng được trỏ bởi x
(*x).y Thành phần y của đối tượng được trỏ bở x (như trên)
x[0] Đối tượng đầu tiên được trỏ bởi x
x[1] Đối tượng thứ hai được trỏ bởi x
x[n] Đối tượng thứ (n+1) được trỏ bởi x
Các toán tử overloading
Có thể sử dụng cú pháp sau để tạo ra các toán tử overloading:
type operator sign (parameters)
Các toán tử có thể overloading:
+ - * / = < > += -= *= /= << >>
<<= >>= == != <= >= ++ -- % & ^ ! |
~ &= ^= |= && || %= [] () , ->* -> new
delete new[] delete[]
Ví dụ:
#include <iostream>
class CVector {
public:
int x,y;
CVector () {};
CVector (int,int);
CVector operator + (CVector);
};
CVector::CVector (int a, int b) {
x = a;
y = b;
}
CVector CVector::operator+ (CVector param)
{
CVector temp;
temp.x = x + param.x;
temp.y = y + param.y;
return (temp);
}
int main () {
CVector a (3,1);
CVector b (1,2);
CVector c;
c = a + b;
cout << c.x << "," << c.y;
return 0;
}
Để ý rằng:
c = a + b;
Là tương đương với:
c = a.operator+ (b);
• Từ khoá this
Thừ khoá this biểu diễn một con
trỏ trỏ đến đối tượng mà ở đó
hàm thành phần được thực
hiện. Nó là một con trỏ trỏ đến
chính đối tượng hiện tại.
#include <iostream>
class CDummy {
public:
int isitme (CDummy& param);
};
int CDummy::isitme (CDummy& param)
{
if (&param == this) return true;
else return false;
}
int main () {
CDummy a;
CDummy* b = &a;
if ( b->isitme(a) )
cout << "yes, &a is b";
return 0;
}
Các thành phần static
• Được xem là thành phần “tổng thể” giữa các đối tượng thuộc lớp.
#include <iostream>
class CDummy {
public:
static int n;
CDummy () { n++; };
~CDummy () { n--; };
};
int CDummy::n=0;
int main () {
CDummy a;
CDummy b[5];
CDummy * c = new CDummy;
cout << a.n << endl;
delete c;
cout << CDummy::n << endl;
return 0;
}
Lưu ý:
Khởi tạo giá trị ban đầu:
int CDummy::n=0;
Cách gọi:
cout << a.n;
cout << CDummy::n;
Một số ví dụ khác
• Xây dựng lớp số phức
• Xây dựng lớp các tam thức bậc 2
• Xây dựng lớp hồ sơ sinh viên
Quan hệ bạn bè (friendship)
• Các hàm friend
Các thành phần được khai báo là private hoặc protect không thể truy xuất đến nó từ các thành
phần bên ngoài lớp ngoại trừ các thành phần được khai báo sẵn là friend trong lớp.
- 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. Các thành
phần friend phải được khai báo prototype trong lớp và bắt đầu với từ khoá friend:
#include <iostream>
class CRectangle {
int width, height;
public:
void set_values (int, int);
int area () {return (width * height);}
friend CRectangle duplicate (CRectangle);
};
void CRectangle::set_values (int a, int b) {
width = a;
height = b;
}
CRectangle duplicate (CRectangle rectparam)
{
CRectangle rectres;
rectres.width = rectparam.width*2;
rectres.height = rectparam.height*2;
return (rectres);
}
int main () {
CRectangle rect, rectb;
rect.set_values (2,3);
rectb = duplicate (rect);
cout << rectb.area();
return 0;
• Các lớp friend
• Lớp bạn cũng giống như
hàm bạn là nó có thể truy
cập các biến thành viên
private của lớp kia.
• B có thể truy xuất thành
phần private hoặc
protected của A, nhưng
chiều ngược lại thì không.
• Các lớp friend
#include <iostream>
class CSquare;
class CRectangle {
int width, height;
public:
int area ()
{return (width * height);}
void convert (CSquare a);
};
class CSquare {
private:
int side;
public:
void set_side (int a)
{side=a;}
friend class CRectangle;
};
void CRectangle::convert (CSquare a) {
width = a.side;
height = a.side;
}
int main () {
CSquare sqr;
CRectangle rect;
sqr.set_side(4);
rect.convert(sqr);
cout << rect.area();
return 0;
}
Kế thừa (Inheritance) giữa các lớp
Cú pháp khai báo lớp kế thừa:
class derived_class_name: public base_class_name
public protected private
Các thành phần cùng lớp Được Được Được
Các thành phần của lớp dẫn xuất Được Được Không
Không phải là thành phần thuộc lớp
nào
Được Không Không
Nhắc lại tính chất của các thành phần trong class:
public: Các thành phần kế thừa được “đưa vào” vùng public của lớp dẫn xuất
protected: Các thành phần kế thừa được “đưa vào” vùng protected của lớp dẫn xuất
private: Các thành phần kế thừa được “đưa vào” vùng private của lớp dẫn xuất
#include <iostream>
class CPolygon {
protected:
int width, height;
public:
void set_values (int a, int b)
{ width=a; height=b;}
};
class CRectangle: public CPolygon {
public:
int area ()
{ return (width * height); }
};
class CTriangle: public CPolygon {
public:
int area ()
{ return (width * height / 2); }
};
int main () {
CRectangle rect;
CTriangle trgl;
rect.set_values (4,5);
trgl.set_values (4,5);
cout << rect.area() << endl;
cout << trgl.area() << endl;
return 0;
}
Những gì được kế thừa từ lớp cơ sở?
• Một lớp dẫn xuất kế thừa các thành phần của lớp cơ sở, trừ các thành phần
sau:
– Các cấu tử và huỹ tử của nó
– Toán tử operator=()
– Các friend
• Mặc dù các cấu tử và hũy tử của lớp cơ sở không được kế thừa nhưng cấu
tử mặc định (tức cấu tử không tham số), hũy tử mặc định sẽ luôn luôn được
gọi khi một đối tượng của lớp dẫn xuất được tạo hoặc bị hũy bỏ.
• Nếu lớp cơ sở không định nghĩa cấu tử mặc định hoặc muốn một cấu tử
overload được gọi khi một đối tượng của lớp dẫn xuất được tạo thì có thể
chỉ ra nó trong mỗi cấu tử của lớp dẫn xuất theo cú pháp:
derived_constr_name (parameters) : base_constr_name (parameters)
{
...
}
#include <iostream>
class me {
public:
me ()
{ cout << “me: không tham sốn"; }
me (int a)
{ cout << “me: có tham sốn"; }
};
class congai : public me {
public:
congai (int a)
{ cout << “congai: có tham sốnn"; }
};
class contrai : public me {
public:
contrai (int a) : me (a)
{ cout << “contrai: có tham sốnn"; }
};
int main () {
congai g (0);
contrai t (0);
return 0;
}
me: không tham số
congai: có tham số
me: có tham số
contrai: có tham số
Để ý rằng:
- Cấu tử congai(int a): cấu tử
mặc định của me sẽ được gọi
- Cấu tử contrai(int a): cấu tử
overload me(int a) sẽ được gọi
Kế thừa bội (Multiple inheritance)
• Một lớp dẫn xuất có thể được kế thừa từ nhiều lớp cơ sở
• Các cấu tử và hũy tử của lớp dẫn xuất cũng có thể thiết lập tương tự như trên.
#include <iostream>
class CPolygon {
protected:
int width, height;
public:
void set_values (int a, int b)
{ width=a; height=b;}
};
class COutput {
public:
void output (int i);
};
void COutput::output (int i) {
cout << i << endl;
}
class CRectangle: public CPolygon, public COutput {
public:
int area ()
{ return (width * height); }
};
class CTriangle: public CPolygon, public COutput {
public:
int area ()
{ return (width * height / 2); }
};
int main () {
CRectangle rect;
CTriangle trgl;
rect.set_values (4,5);
trgl.set_values (4,5);
rect.output (rect.area());
trgl.output (trgl.area());
return 0;
}
20
10
Đa hình (Polymorphism)
• Chúng ta nhớ rằng
Lệnh Thể hiện trong:
int a::b(int c) { } Các lớp
a->b Các cấu trúc dữ liệu
class a: public b { }; Friend và inheritance
#include <iostream>
using namespace std;
class CPolygon {
protected:
int width, height;
public:
CPolygon(int a=0, int b=0) {
width=a;
height=b;
}
int area() {
cout << "Dien tich cua lop cha:" << "???" << endl;
return 0;
}
};
class CRectangle: public CPolygon {
public:
CRectangle(int a = 0, int b = 0) :CPolygon(a, b) { }
int area () {
return (width * height);
}
};
class CTriangle: public CPolygon {
public:
CTriangle(int a=0, int b=0):CPolygon(a, b) { }
int area () {
return (width * height / 2);
}
};
int main () {
CRectangle r(4, 5);
CTriangle t(4, 5);
CPolygon *p;
p = &r;
cout << p->area();
p = &t;
cout << p->area();
return 0;
}
Nhận xét:
Output: “Dien tich cua lop
cha ???
p là 1 con trỏ kiểu Cpolygon,
nó chỉ có thể gọi các thành
phần thuộc Cpolygon mà
Crectangle và Ctriangle đã
kế thừa.
Đa hình (Polymorphism)
• Đa hình (polymorphism) nghĩa là có nhiều hình thái khác nhau.
• Trong OOP và cụ thể là trọng ngôn ngữ C++ thì Polymorphism 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 call 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 call 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ủa lớp cơ
sở. Nếu call 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 call sẽ được quyết định lúc Runtime. Runtime Polymorphism được thực hiện bằng phương phápoverriding – ghi đè phương thức.
– Phương thức ảo (virtual method) trong C++ là cách thể hiện tính đa hình trong lập trình hướng đối tượng của C++, các phương thức ở class cơ sở có tính đa hình phải được định nghĩa là một phương thức ảo.
#include <iostream>
using namespace std;
class CPolygon {
protected:
int width, height;
public:
CPolygon(int a=0, int b=0) {
width=a;
height=b;
}
virtual int area() {
cout << "Dien tich cua lop cha:" << "???" << endl;
return 0;
}
};
class CRectangle: public CPolygon {
public:
CRectangle(int a = 0, int b = 0) :CPolygon(a, b) { }
int area () {
return (width * height);
}
};
class CTriangle: public CPolygon {
public:
CTriangle(int a=0, int b=0):CPolygon(a, b) { }
int area () {
return (width * height / 2);
}
};
int main () {
CPolygon *p;
p = new CRectangle(4, 5);
cout << p->area();
p = new CTriangle(4, 5);
cout << p->area();
return 0;
}
Các lớp cơ sở trừu tượng
(Abstract base classes)
Lớp cơ sở trừu tượng rất giống với lớp Cpolygon ở ví dụ trước, chỉ khác là ta có
thể định nghĩa một hàm area() với chức năng tối thiểu cho các đối tượng của lớp
CPolygon.
Ví dụ về lớp ảo CPolygon:
class CPolygon {
protected:
int width, height;
public:
void set_values (int a, int b)
{ width=a; height=b; }
virtual int area () =0;
};
Nhận xét:
• Chú ý rằng, ta thêm =0 vào hàm ảo
area() thay cho việc chỉ ra một sự
thực hiện của hàm. Kiểu này gọi là
hàm thuần nhất (pure virtual function)
• Các lớp có chứa ít nhất một hàm
thuần nhất được gọi là lớp cơ sở trừu
tượng.
• Điểm khác nhau chính giữa lớp cơ sở
trừu tượng với lớp đa hình ở chổ lớp
cơ sở trừu tượng có ít nhất một thành
phần của nó thiếu đi sự thực hiện. Ta
không thể tạo một thực thể (đối
tượng) của nó
Như vậy: khai báo
CPolygon p; là không thích hợp
Mà phải sử dụng con trỏ, kiểu như:
CPolygon * p1;
CPolygon * p2;
Ví dụ: class CPolygon {
protected:
int width, height;
public:
void set_values (int a, int b)
{ width=a; height=b; }
virtual int area (void) =0;
};
class CRectangle: public CPolygon {
public:
int area (void)
{ return (width * height); }
};
class CTriangle: public CPolygon {
public:
int area (void)
{ return (width * height / 2); }
};
int main () {
CRectangle r;
CTriangle t;
CPolygon * p1 = &r;
CPolygon * p2 = &t;
p1->set_values (4,5);
p2->set_values (4,5);
cout << p1->area() << endl;
cout << p2->area() << endl;
return 0;
}
20
10
Ví dụ sau đây tạo thêm một hàm printarea để in ra màn hình area:
#include <iostream>
class CPolygon {
protected:
int width, height;
public:
void set_values (int a, int b)
{ width=a; height=b; }
virtual int area (void) =0;
void printarea (void)
{ cout << this->area() << endl; }
};
class CRectangle: public CPolygon {
public:
int area (void)
{ return (width * height); }
};
class CTriangle: public CPolygon {
public:
int area (void)
{ return (width * height / 2); }
};
int main () {
CRectangle r;
CTriangle t;
CPolygon * p1 = &rect;
CPolygon * p2 = &trgl;
p1->set_values (4,5);
p2->set_values (4,5);
p1->printarea();
p2->printarea();
return 0;
}
20
10
Nhận xét:
Hàm printarea truy xuất đến area
được định nghĩa lại trong mỗi lớp.
Các thành phần ảo và các lớp trừu tượng hỗ trợ cho đặc tính đa hình của C++, nó
như là một phương tiện quan trọng cho các dự án lớn. Đặc trưng này có thể áp
dụng cho mảng các đối tượng hoặc các đối tượng cấp phát động.
Ví dụ: #include <iostream>
class CPolygon {
protected:
int width, height;
public:
void set_values (int a, int b)
{ width=a; height=b; }
virtual int area (void) =0;
void printarea (void)
{ cout << this->area() << endl; }
};
class CRectangle: public CPolygon {
public:
int area (void)
{ return (width * height); }
};
class CTriangle: public CPolygon {
public:
int area (void)
{ return (width * height / 2); }
};
int main () {
CPolygon * p1 = new CRectangle;
CPolygon * p2 = new CTriangle;
p1->set_values (4,5);
p2->set_values (4,5);
p1->printarea();
p2->printarea();
delete p1;
delete p2;
return 0;
}
20
10
Bài tập
Hết chương 3

Phương pháp lập trình hướng đối tượng với Java

  • 1.
    Chương 3 Phương pháplập trình hướng đối tượng
  • 2.
    • Là phươngpháp lập trình hỗ trợ công nghệ đối tượng. • OOP được xem là giúp tăng năng suất, đơn giản hóa độ phức tạp khi bảo trì cũng như mở rộng phần mềm bằng cách cho phép lập trình viên tập trung vào các đối tượng phần mềm ở bậc cao hơn. • Những đối tượng trong một ngôn ngữ OOP là các kết hợp giữa mã và dữ liệu mà chúng được nhìn nhận như là một đơn vị duy nhất. • Mỗi đối tượng có một tên riêng biệt và tất cả các tham chiếu đến đối tượng đó được tiến hành qua tên của nó. Như vậy, mỗi đối tượng có khả năng nhận vào các thông báo, xử lý dữ liệu (bên trong của nó), và gửi ra hay trả lời đến các đối tượng khác hay đến môi trường. Lập trình hướng đối tượng (OOP - object-oriented programming)
  • 3.
    Đối tượng (Object) •Đối tượng: Các dữ liệu và chỉ thị được kết hợp vào một đơn vị đầy đủ tạo nên một đối tượng. Đơn vị này tương đương với một chương trình con và vì thế các đối tượng sẽ được chia thành hai bộ phận chính: phần các phương thức (method) và phần các thuộc tính (property). • Trong thực tế, các phương thức của đối tượng là các hàm và các thuộc tính của nó là các biến, các tham số hay hằng nội tại của một đối tượng (hay nói cách khác tập hợp các dữ liệu nội tại tạo thành thuộc tính của đối tượng). • Các phương thức là phương tiện để sử dụng một đối tượng trong khi các thuộc tính sẽ mô tả đối tượng có những tính chất gì. • Các phương thức và các thuộc tính thường gắn chặt với thực tế các đặc tính và sử dụng của một đối tượng. • Trong thực tế, các đối tượng thường được trừu tượng hóa qua việc định nghĩa của các lớp (class). • Tập hợp các giá trị hiện có của các thuộc tính tạo nên trạng thái của một đối tượng. • Mỗi phương thức hay mỗi dữ liệu nội tại cùng với các tính chất được định nghĩa (bởi người lập trình) được xem là một đặc tính riêng của đối tượng.
  • 4.
    3.1. Các tínhchất cơ bản  Tính trừu tượng (abstraction):  Là khả năng của chương trình bỏ qua hay không chú ý đến một số khía cạnh của thông tin mà nó đang trực tiếp thực hiện, nghĩa là nó có khả năng tập trung vào những cốt lõi cần thiết.  Mỗi đối tượng có thể hoàn tất các công việc một cách nội bộ, liên lạc với các đối tượng khác mà không cần cho biết làm cách nào đối tượng tiến hành được các thao tác. Tính chất này thường được gọi là sự trừu tượng của dữ liệu.  Tính trừu tượng còn thể hiện qua việc một đối tượng ban đầu có thể có một số đặc điểm chung cho nhiều đối tượng khác như là sự mở rộng của nó.  Tính trừu tượng này thường được xác định trong khái niệm gọi là lớp trừu tượng hay lớp cơ sở trừu tượng.
  • 5.
     Tính đónggói (encapsulation) và che dấu thông tin (information hiding):  Tính chất này không cho phép người sử dụng các đối tượng thay đổi trạng thái nội tại của một đối tượng.  Chỉ có các phương thức nội tại của đối tượng cho phép thay đổi trạng thái của nó.  Việc cho phép môi trường bên ngoài tác động lên các dữ liệu nội tại của một đối tượng theo cách nào đó là hoàn toàn tùy thuộc vào người viết mã. Đây là tính chất đảm bảo sự toàn vẹn của đối tượng.
  • 6.
    Tính đa hình(polymorphism): Thể hiện thông qua việc gửi các thông điệp (message). Việc gửi các thông điệp này có thể so sánh như việc gọi các hàm bên trong của một đối tượng. Các phương thức dùng trả lời cho một thông điệp sẽ tùy theo đối tượng mà thông điệp đó được gửi tới sẽ có phản ứng khác nhau. Người lập trình có thể định nghĩa một đặc tính (chẳng hạn thông qua tên của các phương thức) cho một loạt các đối tượng gần nhau nhưng khi thi hành thì dùng cùng một tên gọi mà sự thi hành của mỗi đối tượng sẽ tự động xảy ra tương ứng theo đặc tính của từng đối tượng mà không bị nhầm lẫn. Thí dụ khi định nghĩa hai đối tượng "hinh_vuong" và "hinh_tron" thì có một phương thức chung là "chu_vi". Khi gọi phương thức này thì nếu đối tượng là "hinh_vuong" nó sẽ tính theo công thức khác với khi đối tượng là "hinh_tron".
  • 7.
     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.
  • 8.
    3.2. Một sốkhái niệm Lớp (class) • Một lớp có thể được hiểu là khuôn mẫu để tạo ra các đối tượng. Trong một lớp, người ta thường dùng các biến để mô tả các thuộc tính và các hàm để mô tả các phương thức của đối tượng. • Khi đã định nghĩa được lớp, ta có thể tạo ra các đối tượng từ lớp này. Để việc sử dụng được dễ dàng, thông qua hệ thống hàm tạo (constructor), người ta dùng lớp như một kiểu dữ liệu để tạo ra các đối tượng. Lớp con (subclass) • Lớp con là một lớp thông thường nhưng có thêm tính chất kế thừa một phần hay toàn bộ các đặc tính của một lớp khác. Lớp mà chia sẽ sự kế thừa gọi là Lớp cha (parent class).
  • 9.
    Lớp trừu tượnghay lớp cơ sở trừu tượng (abstract class) • Lớp trừu tượng là một lớp mà nó không thể thực thể hóa thành một đối tượng thực dụng được. Lớp này được thiết kế nhằm tạo ra một lớp có các đặc tính tổng quát nhưng bản thân lớp đó chưa có ý nghĩa (hay không đủ ý nghĩa) để có thể tiến hành viết mã cho việc thực thể hóa. • Thí dụ: Lớp "hinh_thang" được định nghĩa không có dữ liệu nội tại và chỉ có các phương thức (hàm nội tại) "tinh_chu_vi", "tinh_dien_tich". Nhưng vì lớp hinh_thang này chưa xác định được đầy đủ các đặc tính của nó (cụ thể các biến nội tại là tọa độ các đỉnh nếu là đa giác, là đường bán kính và toạ độ tâm nếu là hình tròn, ...) nên nó chỉ có thể được viết thành một lớp trừu tượng. Sau đó, người lập trình có thể tạo ra các lớp con chẳng hạn như là lớp "tam_giac", lớp "hinh_tron", lớp "tu_giac",.... Và trong các lớp con này người viết mã sẽ cung cấp các dữ liệu nội tại (như là biến nội tại r làm bán kính và hằng số nội tại Pi cho lớp "hinh_tron" và sau đó viết mã cụ thể cho các phương thức "tinh_chu_vi" và "tinh_dien_tich").
  • 10.
    Phương thức (method) •Phương thức của một lớp thường được dùng để mô tả các hành vi của đối tượng (hoặc của lớp). • Khi thiết kế, người ta có thể dùng các phương thức để mô tả và thực hiện các hành vi của đối tượng. • Mỗi phương thức thường được định nghĩa là một hàm, các thao tác để thực hiện hành vi đó được viết tại nội dung của hàm. • Một số loại phương thức đặc biệt: Hàm tạo (constructor) là hàm được dùng để tạo ra một đối tượng, cài đặt các giá trị ban đầu cho các thuộc tính của đối tượng đó. Hàm hủy (destructor) là hàm dùng vào việc làm sạch bộ nhớ đã dùng để lưu đối tượng và hủy bỏ tên của một đối tượng sau khi đã dùng xong.
  • 11.
    Thuộc tính (attribute) •Thuộc tính của một lớp bao gồm các biến, các hằng, hay tham số nội tại của lớp đó. Ở đây, vai trò quan trọng nhất của các thuộc tính là các biến vì chúng sẽ có thể bị thay đổi trong suốt quá trình hoạt động của một đối tượng. • Các thuộc tính có thể được xác định kiểu và kiểu của chúng có thể là các kiểu dữ liệu cổ điển hay đó là một lớp đã định nghĩa từ trước. Quan hệ giữa lớp và đối tượng • Lớp trong là cách phân loại các thực thể dựa trên những đặc điểm chung của các thực thể đó. Do đó lớp là khái niệm mang tính trừu tượng hóa rất cao. • Một đối tượng là thực thể hóa (instantiate) của một lớp đã được định nghĩa.
  • 12.
    • Công cộng(public) Là một tính chất được dùng để gán cho các phương thức, các biến nội tại, hay các lớp mà khi khai báo thì người lập trình đã cho phép các câu lệnh bên ngoài cũng như các đối tượng khác được phép dùng đến nó. • Riêng tư (private) Là sự thể hiện tính chất đóng mạnh nhất (của một đặc tính hay một lớp). Khi dùng tính chất này gán cho một biến, một phương pháp thì biến hay phương thức đó chỉ có thể được sử dụng bên trong của lớp mà chúng được định nghĩa. • Bảo vệ (protected) Chỉ có trong nội bộ của lớp đó hay các lớp có quan hệ đặc biệt mới được phép gọi đến hay dùng đến các thành phần được bảo vệ. Có sự khác nhau trong các ngôn ngữ lập trình. • Đa kế thừa (multiple inheritance) Một lớp con có khả năng kế thừa trực tiếp cùng lúc nhiều lớp khác.
  • 13.
    3.3. Các nguyênlý thiết kế hướng đối tượng 1. Xác định lớp đối tượng • Đặt câu hỏi "Ta đang nói về cái gì" chứ chưa đặt vấn đề "Ta muốn làm gì", nghĩa là hướng đến đối tuợng trước. Để xác định đối tượng cần phải xác dịnh lớp các đối tượng. • Việc xem xét lớp đối tượng đáng quan tâm có thể dựa trên nguyên lý sau: Nếu ta nói về vấn đề nào đó bằng cách quy cho nó các tính chất hay nếu phải thao tác trên các vấn đề này thì cần phải xác định lớp đối tượng cho nó.
  • 14.
    2. Tự lậpvà cục bộ: • Trước hết phải nghĩ dưới dạng đối tượng rồi sau đó mới nghĩ đến các phép tóan được áp dụng trên nó. • Phải nghĩ đến việc yêu cầu các đối tượng thực hiện các hành động của chúng chứ không đi tìm những hàm tòan cục tác động lên nó. 3. Hợp thành và làm mịn: • Các đối tượng được hợp thành để tạo ra các đối tượng lớn hơn bằng cách sử dụng kế thừa. Đây là việc làm tự nhiên. • Làm mịn tức là mô tả một phần mềm theo những phần mềm đã có, không phải mô tả chúng theo các thành phần mà bằng cách tạo ra phần mềm mới từ việc kế thừa các đặc trưng của các phần mềm tổng quát hơn đã có, và chỉ định nghĩa những phần nào là mới trong phần mềm đang tạo ra.
  • 15.
    4. Các bướctrong thiết kế hướng đối tượng: (1) Xác định thực thể trong miền: Làm thế nào để xác định chính xác lớp đối tượng. Có các cánh sau: C1: Xem xét một lõi nhỏ các lớp ban đầu được coi là "hiễn nhiên", rồi trong quá trình thực hiện, có thể thêm vào các lớp mới bằng cách gộp nhóm các lớp hiện có. C2: Vét cạn tất cả, trong quá trinh thực hiện sẽ lọai bỏ dần. C3: Gộp nhóm các tính chất và các quan hệ mà chưa rút ra ngay các lớp. Sau đó phân lọai các tính chất và từ đó xác định đối tượng. (2) Cấu trúc miền thông qua việc phân tích các tính chất và quan hệ: Khi đã định xong các lớp, cần phải biết cách tổ chức và liên kết chúng lại (3) Xác định các thao tác: Đầu tiên các định các thao tác mà các dối tượng cần thíết phải có sau đó mới nâng dần lên ở dạng trừu tượng hơn. Chẳng hạn có thể có thể năng thao tác của một đối tượng X lên cho lớp cơ sở được nó kế thừa và có thể lớp cơ sở thao tác này là "không làm gì cả". (4) Mô tả chính xác các thao tác: (5) Cho phép thực hiện
  • 16.
    3.4. Hạn chếcủa phương pháp lập trình OOP • OOP mô phỏng đối tượng trong thế giới thực (sinh - họat động - tử ) nhưng không thể làm cho đối tượng tiến hóa. Nếu OOP đưa vào các dự đoán trước cho yêu cầu tiến triển thì phần mềm sẽ nặng nề. • Thực tế, một hệ thống là sự đan xen nhau giữa các yêu cầu. Giả sử cần phải thêm một yêu cầu mới cho một object, OOP buộc phải bổ sung thêm code cho object đó. Điều này dẫn đến sự chồng chéo giữa các yêu cầu và làm cho hệ thống trở nên lộn xộn, khó bảo trì, phát triển. Khi bổ sung thêm một yêu cầu, OOP buộc phải thay đổi các yêu cầu về nghiệp vụ liên quan.
  • 17.
    3.6. Nhìn quavề OOP trong C++ Các lớp (Class) class class_name { access_specifier_1: member1; access_specifier_2: member2; ... } object_names; • private: các thành phần của lớp chỉ được truy xuất bởi các thành phần khác bên trong của chính lớp lớp đó hoặc từ các lớp là friend. • protected: Các thành phần chỉ được truy xuất từ các thành phần khác cùng lớp và từ các lớp friend, hoặc các lớp dẫn xuất từ lớp này. • public: các thành phần được truy xuất từ bất kỳ ở đâu đối tượng hiển diện. Lưu ý: Khia báo private có thể không cần từ khoá private
  • 18.
    Ví dụ: #include <iostream> usingnamespace std; class CRectangle { int x, y; public: void set_values (int,int); int area () {return (x*y);} }; void CRectangle::set_values (int a, int b) { x = a; y = b; } int main () { CRectangle rect, rectb; rect.set_values (3,4); rectb.set_values (5,6); cout << "rect area: " << rect.area() << endl; cout << "rectb area: " << rectb.area() << endl; return 0; }
  • 19.
    Constructor và destructor •Cấu tử (Hàm tạo): - Là một phương thức đặc biệt được gọi tự động tại thời điểm đối tượng được tạo - Mục đích: Khởi tạo giá trị cho đối tượng. - Đặc điểm: + 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 không khai báo một hàm khởi tạo, trình biên dịch C++ sẽ tự động tạo một hàm khởi tạo mặc định (​ ​ không có tham số nào) #include <iostream> class CRectangle { int width, height; public: CRectangle (int,int); int area () {return (width*height);} }; CRectangle::CRectangle (int a, int b) { width = a; height = b; } int main () { CRectangle rect (3,4); CRectangle rectb (5,6); cout << "rect area: " << rect.area() << endl; cout << "rectb area: " << rectb.area() << endl; return 0; }
  • 20.
    • Các cấutử overloading #include <iostream> class CRectangle { int width, height; public: CRectangle (); CRectangle (int,int); int area (void) {return (width*height);} }; CRectangle::CRectangle () { width = 5; height = 5; } CRectangle::CRectangle (int a, int b) { width = a; height = b; } int main () { CRectangle rect (3,4); CRectangle rectb; cout << "rect area: " << rect.area() << endl; cout << "rectb area: " << rectb.area() << endl; return 0; } Lưu ý: CRectangle rectb; // đúng CRectangle rectb(); // sai
  • 21.
    • Cấu tửngầm định: • Nếu không khai báo cấu tử, thì C++ sử dụng cấu tử ngầm định: class CExample { public: int a,b,c; void multiply (int n, int m) { a=n; b=m; c=a*b; }; }; Khi đó, có thể khai báo: CExample ex; • Nếu có khai báo cấu tử: class CExample { public: int a,b,c; CExample (int n, int m) { a=n; b=m; }; void multiply () { c=a*b; }; }; Khi đó: CExample ex (2,3); //đúng CExample ex; //sai
  • 22.
    • Cấu tửcopy: Nếu khai báo một cấu tử là: CExample::CExample (const CExample& rv) { a=rv.a; b=rv.b; c=rv.c; } Khi đó: CExample ex (2,3); CExample ex2 (ex); // cấu tử copy, dữ liệu sẽ được sao chép từ ex
  • 23.
    • Hủy tử(Hàm hủy): - Hủy tử: Hủy đối tượng. #include <iostream> class CRectangle { int *width, *height; public: CRectangle (int,int); ~CRectangle (); int area () {return (*width * *height);} }; CRectangle::CRectangle (int a, int b) { width = new int; height = new int; *width = a; *height = b; } CRectangle::~CRectangle () { delete width; delete height; } int main () { CRectangle rect (3,4), rectb (5,6); cout << "rect area: " << rect.area() << endl; cout << "rectb area: " << rectb.area() << endl; return 0; }
  • 24.
    Con trỏ đếnclass Có thể khai báo các con trỏ đến các class đã định nghĩa. Ví dụ: CRectangle * p; #include <iostream> class CRectangle { int width, height; public: void set_values (int, int); int area (void) {return (width * height);} }; void CRectangle::set_values (int a, int b) { width = a; height = b; } int main () { CRectangle a, *b, *c; CRectangle * d = new CRectangle[2]; b= new CRectangle; c= &a; a.set_values (1,2); b->set_values (3,4); d->set_values (5,6); d[1].set_values (7,8); cout << "a area: " << a.area() << endl; cout << "*b area: " << b->area() << endl; cout << "*c area: " << c->area() << endl; cout << "d[0] area: " << d[0].area() << endl; cout << "d[1] area: " << d[1].area() << endl; delete[] d; delete b; return 0; } a area: 2 *b area: 12 *c area: 2 d[0] area: 30 d[1] area: 56
  • 25.
    • Lưu ýcách viết: Biểu thức Có thể hiểu *x Trỏ bởi x &x Điạ chỉ của x x.y Thành phần y của đối tượng x x->y Thành phần y của đối tượng được trỏ bởi x (*x).y Thành phần y của đối tượng được trỏ bở x (như trên) x[0] Đối tượng đầu tiên được trỏ bởi x x[1] Đối tượng thứ hai được trỏ bởi x x[n] Đối tượng thứ (n+1) được trỏ bởi x
  • 26.
    Các toán tửoverloading Có thể sử dụng cú pháp sau để tạo ra các toán tử overloading: type operator sign (parameters) Các toán tử có thể overloading: + - * / = < > += -= *= /= << >> <<= >>= == != <= >= ++ -- % & ^ ! | ~ &= ^= |= && || %= [] () , ->* -> new delete new[] delete[]
  • 27.
    Ví dụ: #include <iostream> classCVector { public: int x,y; CVector () {}; CVector (int,int); CVector operator + (CVector); }; CVector::CVector (int a, int b) { x = a; y = b; } CVector CVector::operator+ (CVector param) { CVector temp; temp.x = x + param.x; temp.y = y + param.y; return (temp); } int main () { CVector a (3,1); CVector b (1,2); CVector c; c = a + b; cout << c.x << "," << c.y; return 0; } Để ý rằng: c = a + b; Là tương đương với: c = a.operator+ (b);
  • 28.
    • Từ khoáthis Thừ khoá this biểu diễn một con trỏ trỏ đến đối tượng mà ở đó hàm thành phần được thực hiện. Nó là một con trỏ trỏ đến chính đối tượng hiện tại. #include <iostream> class CDummy { public: int isitme (CDummy& param); }; int CDummy::isitme (CDummy& param) { if (&param == this) return true; else return false; } int main () { CDummy a; CDummy* b = &a; if ( b->isitme(a) ) cout << "yes, &a is b"; return 0; }
  • 29.
    Các thành phầnstatic • Được xem là thành phần “tổng thể” giữa các đối tượng thuộc lớp. #include <iostream> class CDummy { public: static int n; CDummy () { n++; }; ~CDummy () { n--; }; }; int CDummy::n=0; int main () { CDummy a; CDummy b[5]; CDummy * c = new CDummy; cout << a.n << endl; delete c; cout << CDummy::n << endl; return 0; } Lưu ý: Khởi tạo giá trị ban đầu: int CDummy::n=0; Cách gọi: cout << a.n; cout << CDummy::n;
  • 30.
    Một số vídụ khác • Xây dựng lớp số phức • Xây dựng lớp các tam thức bậc 2 • Xây dựng lớp hồ sơ sinh viên
  • 31.
    Quan hệ bạnbè (friendship) • Các hàm friend Các thành phần được khai báo là private hoặc protect không thể truy xuất đến nó từ các thành phần bên ngoài lớp ngoại trừ các thành phần được khai báo sẵn là friend trong lớp. - 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. Các thành phần friend phải được khai báo prototype trong lớp và bắt đầu với từ khoá friend: #include <iostream> class CRectangle { int width, height; public: void set_values (int, int); int area () {return (width * height);} friend CRectangle duplicate (CRectangle); }; void CRectangle::set_values (int a, int b) { width = a; height = b; } CRectangle duplicate (CRectangle rectparam) { CRectangle rectres; rectres.width = rectparam.width*2; rectres.height = rectparam.height*2; return (rectres); } int main () { CRectangle rect, rectb; rect.set_values (2,3); rectb = duplicate (rect); cout << rectb.area(); return 0;
  • 32.
    • Các lớpfriend • Lớp bạn cũng giống như hàm bạn là nó có thể truy cập các biến thành viên private của lớp kia. • B có thể truy xuất thành phần private hoặc protected của A, nhưng chiều ngược lại thì không.
  • 33.
    • Các lớpfriend #include <iostream> class CSquare; class CRectangle { int width, height; public: int area () {return (width * height);} void convert (CSquare a); }; class CSquare { private: int side; public: void set_side (int a) {side=a;} friend class CRectangle; }; void CRectangle::convert (CSquare a) { width = a.side; height = a.side; } int main () { CSquare sqr; CRectangle rect; sqr.set_side(4); rect.convert(sqr); cout << rect.area(); return 0; }
  • 34.
    Kế thừa (Inheritance)giữa các lớp Cú pháp khai báo lớp kế thừa: class derived_class_name: public base_class_name public protected private Các thành phần cùng lớp Được Được Được Các thành phần của lớp dẫn xuất Được Được Không Không phải là thành phần thuộc lớp nào Được Không Không Nhắc lại tính chất của các thành phần trong class: public: Các thành phần kế thừa được “đưa vào” vùng public của lớp dẫn xuất protected: Các thành phần kế thừa được “đưa vào” vùng protected của lớp dẫn xuất private: Các thành phần kế thừa được “đưa vào” vùng private của lớp dẫn xuất
  • 35.
    #include <iostream> class CPolygon{ protected: int width, height; public: void set_values (int a, int b) { width=a; height=b;} }; class CRectangle: public CPolygon { public: int area () { return (width * height); } }; class CTriangle: public CPolygon { public: int area () { return (width * height / 2); } }; int main () { CRectangle rect; CTriangle trgl; rect.set_values (4,5); trgl.set_values (4,5); cout << rect.area() << endl; cout << trgl.area() << endl; return 0; }
  • 36.
    Những gì đượckế thừa từ lớp cơ sở? • Một lớp dẫn xuất kế thừa các thành phần của lớp cơ sở, trừ các thành phần sau: – Các cấu tử và huỹ tử của nó – Toán tử operator=() – Các friend • Mặc dù các cấu tử và hũy tử của lớp cơ sở không được kế thừa nhưng cấu tử mặc định (tức cấu tử không tham số), hũy tử mặc định sẽ luôn luôn được gọi khi một đối tượng của lớp dẫn xuất được tạo hoặc bị hũy bỏ. • Nếu lớp cơ sở không định nghĩa cấu tử mặc định hoặc muốn một cấu tử overload được gọi khi một đối tượng của lớp dẫn xuất được tạo thì có thể chỉ ra nó trong mỗi cấu tử của lớp dẫn xuất theo cú pháp: derived_constr_name (parameters) : base_constr_name (parameters) { ... }
  • 37.
    #include <iostream> class me{ public: me () { cout << “me: không tham sốn"; } me (int a) { cout << “me: có tham sốn"; } }; class congai : public me { public: congai (int a) { cout << “congai: có tham sốnn"; } }; class contrai : public me { public: contrai (int a) : me (a) { cout << “contrai: có tham sốnn"; } }; int main () { congai g (0); contrai t (0); return 0; } me: không tham số congai: có tham số me: có tham số contrai: có tham số Để ý rằng: - Cấu tử congai(int a): cấu tử mặc định của me sẽ được gọi - Cấu tử contrai(int a): cấu tử overload me(int a) sẽ được gọi
  • 38.
    Kế thừa bội(Multiple inheritance) • Một lớp dẫn xuất có thể được kế thừa từ nhiều lớp cơ sở • Các cấu tử và hũy tử của lớp dẫn xuất cũng có thể thiết lập tương tự như trên. #include <iostream> class CPolygon { protected: int width, height; public: void set_values (int a, int b) { width=a; height=b;} }; class COutput { public: void output (int i); }; void COutput::output (int i) { cout << i << endl; } class CRectangle: public CPolygon, public COutput { public: int area () { return (width * height); } }; class CTriangle: public CPolygon, public COutput { public: int area () { return (width * height / 2); } }; int main () { CRectangle rect; CTriangle trgl; rect.set_values (4,5); trgl.set_values (4,5); rect.output (rect.area()); trgl.output (trgl.area()); return 0; } 20 10
  • 39.
    Đa hình (Polymorphism) •Chúng ta nhớ rằng Lệnh Thể hiện trong: int a::b(int c) { } Các lớp a->b Các cấu trúc dữ liệu class a: public b { }; Friend và inheritance
  • 40.
    #include <iostream> using namespacestd; class CPolygon { protected: int width, height; public: CPolygon(int a=0, int b=0) { width=a; height=b; } int area() { cout << "Dien tich cua lop cha:" << "???" << endl; return 0; } }; class CRectangle: public CPolygon { public: CRectangle(int a = 0, int b = 0) :CPolygon(a, b) { } int area () { return (width * height); } }; class CTriangle: public CPolygon { public: CTriangle(int a=0, int b=0):CPolygon(a, b) { } int area () { return (width * height / 2); } }; int main () { CRectangle r(4, 5); CTriangle t(4, 5); CPolygon *p; p = &r; cout << p->area(); p = &t; cout << p->area(); return 0; } Nhận xét: Output: “Dien tich cua lop cha ??? p là 1 con trỏ kiểu Cpolygon, nó chỉ có thể gọi các thành phần thuộc Cpolygon mà Crectangle và Ctriangle đã kế thừa.
  • 41.
    Đa hình (Polymorphism) •Đa hình (polymorphism) nghĩa là có nhiều hình thái khác nhau. • Trong OOP và cụ thể là trọng ngôn ngữ C++ thì Polymorphism 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 call 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 call 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ủa lớp cơ sở. Nếu call 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 call sẽ được quyết định lúc Runtime. Runtime Polymorphism được thực hiện bằng phương phápoverriding – ghi đè phương thức. – Phương thức ảo (virtual method) trong C++ là cách thể hiện tính đa hình trong lập trình hướng đối tượng của C++, các phương thức ở class cơ sở có tính đa hình phải được định nghĩa là một phương thức ảo.
  • 42.
    #include <iostream> using namespacestd; class CPolygon { protected: int width, height; public: CPolygon(int a=0, int b=0) { width=a; height=b; } virtual int area() { cout << "Dien tich cua lop cha:" << "???" << endl; return 0; } }; class CRectangle: public CPolygon { public: CRectangle(int a = 0, int b = 0) :CPolygon(a, b) { } int area () { return (width * height); } }; class CTriangle: public CPolygon { public: CTriangle(int a=0, int b=0):CPolygon(a, b) { } int area () { return (width * height / 2); } }; int main () { CPolygon *p; p = new CRectangle(4, 5); cout << p->area(); p = new CTriangle(4, 5); cout << p->area(); return 0; }
  • 43.
    Các lớp cơsở trừu tượng (Abstract base classes) Lớp cơ sở trừu tượng rất giống với lớp Cpolygon ở ví dụ trước, chỉ khác là ta có thể định nghĩa một hàm area() với chức năng tối thiểu cho các đối tượng của lớp CPolygon. Ví dụ về lớp ảo CPolygon: class CPolygon { protected: int width, height; public: void set_values (int a, int b) { width=a; height=b; } virtual int area () =0; }; Nhận xét: • Chú ý rằng, ta thêm =0 vào hàm ảo area() thay cho việc chỉ ra một sự thực hiện của hàm. Kiểu này gọi là hàm thuần nhất (pure virtual function) • Các lớp có chứa ít nhất một hàm thuần nhất được gọi là lớp cơ sở trừu tượng. • Điểm khác nhau chính giữa lớp cơ sở trừu tượng với lớp đa hình ở chổ lớp cơ sở trừu tượng có ít nhất một thành phần của nó thiếu đi sự thực hiện. Ta không thể tạo một thực thể (đối tượng) của nó
  • 44.
    Như vậy: khaibáo CPolygon p; là không thích hợp Mà phải sử dụng con trỏ, kiểu như: CPolygon * p1; CPolygon * p2; Ví dụ: class CPolygon { protected: int width, height; public: void set_values (int a, int b) { width=a; height=b; } virtual int area (void) =0; }; class CRectangle: public CPolygon { public: int area (void) { return (width * height); } }; class CTriangle: public CPolygon { public: int area (void) { return (width * height / 2); } }; int main () { CRectangle r; CTriangle t; CPolygon * p1 = &r; CPolygon * p2 = &t; p1->set_values (4,5); p2->set_values (4,5); cout << p1->area() << endl; cout << p2->area() << endl; return 0; } 20 10
  • 45.
    Ví dụ sauđây tạo thêm một hàm printarea để in ra màn hình area: #include <iostream> class CPolygon { protected: int width, height; public: void set_values (int a, int b) { width=a; height=b; } virtual int area (void) =0; void printarea (void) { cout << this->area() << endl; } }; class CRectangle: public CPolygon { public: int area (void) { return (width * height); } }; class CTriangle: public CPolygon { public: int area (void) { return (width * height / 2); } }; int main () { CRectangle r; CTriangle t; CPolygon * p1 = &rect; CPolygon * p2 = &trgl; p1->set_values (4,5); p2->set_values (4,5); p1->printarea(); p2->printarea(); return 0; } 20 10 Nhận xét: Hàm printarea truy xuất đến area được định nghĩa lại trong mỗi lớp.
  • 46.
    Các thành phầnảo và các lớp trừu tượng hỗ trợ cho đặc tính đa hình của C++, nó như là một phương tiện quan trọng cho các dự án lớn. Đặc trưng này có thể áp dụng cho mảng các đối tượng hoặc các đối tượng cấp phát động. Ví dụ: #include <iostream> class CPolygon { protected: int width, height; public: void set_values (int a, int b) { width=a; height=b; } virtual int area (void) =0; void printarea (void) { cout << this->area() << endl; } }; class CRectangle: public CPolygon { public: int area (void) { return (width * height); } }; class CTriangle: public CPolygon { public: int area (void) { return (width * height / 2); } }; int main () { CPolygon * p1 = new CRectangle; CPolygon * p2 = new CTriangle; p1->set_values (4,5); p2->set_values (4,5); p1->printarea(); p2->printarea(); delete p1; delete p2; return 0; } 20 10
  • 47.
  • 48.

Editor's Notes

  • #19 Mục đích của hàm khởi tạo là để khởi tạo các thành viên dữ liệu của đối tượng.
  • #41 Đa hình trong c++ được thể hiện qua các hàm ảo trong lớp cơ sở Sau đó, tại các lớp con kế thừa sẽ định nghĩa lại (override-ghi đè) lên hàm ở lớp cha. Do đó, với một con trỏ kiểu lớp cha, nhưng new bởi lớp con thì đối tượng đó sẽ thuộc lớp con.
  • #42 Hàm area() mang tính đa hình bởi vì các đối tượng thuộc hình cn và hình tam giác có cách tính diện tích khác nhau => set virtual Hàm area ở hai lớp kế thừa sẽ được định nghĩa lại. Chúng ta đã có các phương thức ảo, vậy vấn đề hiện tại là làm sao để chương trình biết phương thức của đối tượng nào để thực hiện cho đúng? con trỏ của class cơ sở có thể trỏ đến đối tượng của class dẫn xuất Tùy thuộc vào đối tượng đang được trỏ tới thuộc lớp dẫn xuất nào mà hàm sẽ được gọi cho đúng. Lúc đó, hàm trong lớp dẫn xuất sẽ ghi đè (override) hàm ảo trong lớp cơ sở.