Giáo trình C++ - Lập trình hướng đối tượng (Phần 2)
Chương 13. Lập trình hướng đối tượng
CHƯƠNG 13. LẬP TRÌNH HƯỚNG ĐỐI
TƯỢNG
Lịch sử hình thành
Trước khi kĩ thuật lập trình hướng đối tượng ra đời, con người đ~ trải qua
các thời kì lập trình tuyến tính, lập trình hướng thủ tục.
1. Lập trình tuyến tính
M|y tính đầu tiên được lập trình bằng mã nhị phân, sử dụng các công
tắc cơ khí để nạp chương trình. Cùng với sự xuất hiện của các thiết bị lưu
trữ lớn và bộ nhớ m|y tính có dung lượng lớn, nên các ngôn ngữ lập trình
cấp cao bắt đầu xuất hiện. Các ngôn ngữ lập trình n{y được thiết kế làm
cho công việc lập trình trở nên đơn giản hơn. C|c chương trình ban đầu
chủ yếu liên quan đến tính toán, chúng tương đối ngắn. Chúng chủ yếu
Nhược điểm:
o Nếu ta cần sử dụng một đoạn lệnh n{o đó nhiều lần, thì ta phải sao chép
nó nhiều lần.
o Không có khả năng kiểm soát phạm vi nhìn thấy của biến.
o Chương trình dài dòng, khó hiểu, khó nâng cấp.
2. Lập trình hướng thủ tục
Với những nhược điểm trên, đòi hỏi có một ngôn ngữ lập trình mới thay
Về bản chất, chương trình được chia nhỏ thành c|c modul (đơn vị chương
trình). Mỗi đơn vị chương trình chứa các hàm hay thủ tục (nên gọi là
hướng thủ tục). Tuy tách rời thành các modul riêng biệt, nhưng ngôn ngữ
lập trình hướng thủ tục vẫn đảm bảo thông tin thông suốt giữa các modul
nhờ v{o cơ chế hoạt động của h{m, cơ chế truyền theo tham biến và tham
trị. Với lập trình hướng thủ tục, một chương trình lớn có thể được chia nhỏ
Trang | 120
Chương 13. Lập trình hướng đối tượng
th{nh c|c modul, để mỗi lập trình viên có thể đảm nhận. Tiêu biểu trong số
này là C, Pascal.
Nhược điểm:
o Các hàm và thủ tục thường gắn kết với nhau, nếu muốn nâng cấp
chương trình, thường phải chỉnh sửa tất cả các hàm và thủ tục liên
quan.
o Không phù hợp với xu thế hiện đại vì không mô tả được thực thể trong
cuộc sống thực.
3. Lập trình hướng đối tượng
a. Giới thiệu
Với xu thế hiện đại, ngôn ngữ lập trình hướng đối tượng đ~ ra đời. Cơ
sở của lập trình hướng đối tượng l{ đối tượng. Đối tượng là sự thể hiện của
một thực thể trong thế giới thực. Một thực thể trong thế giới thực thường
có: c|c đặc trưng v{ c|c h{nh động. Ví dụ: con người trong thế giới thực có
c|c đặc trưng như - tên gọi, tuổi, màu tóc, màu mắt, m{u da… v{ c|c h{nh
chính xác các sự vật, con người trong thế giới thực.
Bây giờ, ta sẽ xét một vài ví dụ để cho thấy sự cần thiết của lập trình
hướng đối tượng.
Ví dụ 1. Chúng ta muốn xây dựng một chương trình quản lý sinh viên.
Khi đó, ta cần lưu trữ c|c thông tin liên quan đến đối tượng sinh viên này:
họ tên sinh viên, mã số sinh viên, ng{y th|ng năm sinh, quê qu|n, điểm các
môn, điểm tổng kết,…. v{ rất nhiều thông tin khác liên quan. Sau khi kết
thúc năm học, sinh viên sẽ nhận được đ|nh gi| kết quả học tập của mình.
Chúng ta cần có phương thức tiếp nhận kết quả để sinh viên đó có thể phản
học tập, tham gia vào các hoạt động của trường, của khoa… đó l{ những
h{nh động mà mỗi sinh viên cần thực hiện.
Ví dụ 2. Chúng ta sẽ điểm qua một số tính năng trong chương trình soạn
thảo văn bản Word của Microsoft. Chúng ta sẽ thảo luận về c|c đối tượng
Drawing trong Word. Mỗi đối tượng đều có các thuộc tính: màu viền, dạng
đường viền, kích thước viền, màu sắc viền, màu nền, có văn bản hay không
trong đối tượng drawing…Khi chúng ta biến đổi hình dạng của mỗi đối
T r a n g | 121
Chương 13. Lập trình hướng đối tượng
tượng: kéo giãn, làm lệch xiêng, quay vòng… chúng ta cần đưa ra một
thông điệp để c|c đối tượng hình thể n{y thay đổi theo. C|c h{nh động này
thuộc quyền sở hữu của đối tượng.
Trong hai ví dụ minh họa trên, chúng ta thấy rằng hướng tiếp cận theo
lập trình hướng đối tượng là rất gần gũi với cuộc sống thực. Chúng ta
không quan t}m đến những khía cạnh không cần thiết của đối tượng,
chúng ta chỉ tập trung v{o c|c đặc trưng v{ c|c h{nh động của đối tượng.
Kể từ thời điểm này trở đi, chúng ta sẽ gọi các đặc trưng của đối tượng là
các thuộc tính thành viên của đối tượng đó (hoặc dữ liệu thành viên, biến
thành viên của đối tượng) v{ c|c h{nh động của đối tượng l{ c|c phương
thức thành viên (hay hàm thành viên) của đối tượng. Các cách gọi dữ liệu
thành viên, thuộc tính thành viên, biến thành viên hay thuộc tính (tương
ứng phương thức th{nh viên, h{m th{nh viên, phương thức) là không có
sự phân biệt. Tôi chỉ đưa ra nhiều cách gọi kh|c nhau để chúng ta có thể
quen khi tham khảo các giáo trình khác nhau. Bởi lẽ, nhiều giáo trình chọn
lựa các cách gọi khác nhau. Các cách gọi n{y cũng tùy thuộc vào ngôn ngữ
thành viên – member function, trong khi đó, c|c ngôn ngữ như Java, Delphi
hay C# lại sử dụng khái niệm phương thức – method và thuộc tính –
property). Khái niệm thành viên sẽ áp dụng cho cả dữ liệu thành viên lẫn
hàm thành viên.
Phương ch}m của lập trình hướng thủ tục theo gi|o sư Niklaus Wirth
Chương trình = Cấu trúc dữ liệu + Giải thuật
Còn phương ch}m của lập trình hướng đối tượng là
Chương trình = Đối tượng + Dữ liệu
Tiêu biểu trong số này là C++, Java, C#, Delphi, Python…
phân tích và thiết kế c|c đối tượng. Từ sơ đồ cấu trúc nhận được, chúng ta
có thể xây dựng nên chương trình. Chi tiết về cách thức phân tích và thiết
kế đối tượng, chúng ta sẽ được tìm hiểu kĩ hơn trong học phần phân tích
thiết kế hệ thống thông tin. Trong nội dung của giáo trình này, chúng ta chỉ
thảo luận một phần nhỏ, để giúp các bạn có thể xây dựng nên một cấu trúc
chương trình theo hướng đối tượng. Sơ đồ cấu trúc trong lập trình hướng
đối tượng được sử dụng phổ biến l{ sơ đồ mô tả trên ngôn ngữ UML
(Unified Modeling Languages). UML là ngôn ngữ chuyên dùng để mô hình
T r a n g | 122
Chương 13. Lập trình hướng đối tượng
hóa c|c đối tượng. Nó không chỉ được áp dụng trong lập trình, m{ còn được
sử dụng rộng r~i trong c|c lĩnh vực khác của cuộc sống. Trong UML có
nhiều dạng sơ đồ được hoạch định. Nhưng trong phạm trù của lập trình
hướng đối tượng, sơ đồ lớp là sự mô tả gần gũi nhất. Do đó, tôi sẽ trình bày
cách xây dựng một chương trình được mô tả bằng sơ đồ lớp.
Một số kí hiệu cần lưu ý trong UML. Trước khi tìm hiểu cách mô hình hóa
một bài toán trong UML, chúng ta cần tìm hiểu một số kí hiệu được sử dụng
trong UML. Các kí hiệu này có thể khác nhau trong nhiều chương trình mô
phỏng. Những kí hiệu mà tôi sử dụng ở đ}y l{ kí hiệu dùng trên Visual
Studio 2010.
Kí hiệu
Ý nghĩa
Lớp
Thuộc tính hoặc phương thức private
Thuộc tính hoặc phương thức protected
Thuộc tính hoặc phương thức public
hướng về lớp cơ sở. Chiều còn lại luôn
chỉ vào lớp con.
Thuộc tính
Phương thức
Phân tích và thiết kế mô hình. Việc phân tích và thiết kế một mô hình là
toán phân tích và thiết kế, không phải chỉ có duy nhất một mô hình kết quả,
mà có thể có một vài, thậm chí là nhiều mô hình khác nhau. Tuy nhiên, công
việc chọn lựa một mô hình tối ưu l{ điều cần thiết. Trong nội dung giáo
trình này, tôi chỉ giới thiệu sơ qua về cách hoạch định một mô hình. Chúng
ta sẽ không đi sâu nghiên cứu vấn đề này. Trong học phần phân tích và thiết
kế hệ thống thông tin sẽ trình bày chi tiết và cụ thể hơn.
Các bước phân tích và thiết kế. Để phân tích và thiết kế một mô hình
hướng đối tượng, cần thực hiện c|c giai đoạn sau đ}y:
T r a n g | 123
Chương 13. Lập trình hướng đối tượng
- Bước 1. Mô tả bài toán. Một bài toán sẽ được miêu tả dưới dạng ngôn ngữ
tự nhiên. Nó chủ yếu dựa vào yêu cầu của khác hàng và sự trợ giúp khách
hàng.
- Bước 2. Đặc tả các yêu cầu. Sau khi phân tích các nhân tố tham gia vào
trong mô hình, ta cần tiến h{nh xem xét c|c t|c nh}n t|c động vào từng
nhân tố. Mối quan hệ giữa các nhân tố…
- Bước 3. Trích chọn đối tượng. Sau khi tổng hợp các tác nhân và nhân tố
trong mô hình. Chúng ta cần tiến hành lựa chọn chúng. Việc loại bỏ các
nhân tố và tác nhân không cần thiết là rất quan trọng. Nó sẽ giúp cho mô
hình tập trung vào các nhân tố quan trọng và cần thiết, tránh phân tích và
thiết kế tràn lan.
- Bước 3. Mô hình hóa các lớp đối tượng. Sau khi chọn lựa c|c đối tượng
cần thiết. Chúng ta phân tích từng đối tượng. Khi ph}n tích đối tượng, ta cần
lưu ý tập trung vào những thứ cốt lõi của mỗi đối tượng, tr|nh đưa v{o
những phương thức và thuộc tính không cần thiết, không quan trọng của
đối tượng – đó chính l{ tính trừu tượng hóa của dữ liệu. Khi phân tích,
chúng ta cũng cần lưu ý đến các tính chất chung của từng đối tượng. Nếu
c|c đối tượng có nhiều tính chất chung, chúng ta nên xây dựng một đối
tượng mới, chứa các tính chất chung đó, mỗi đối tượng còn lại sẽ thừa kế từ
tượng phải có c|c phương thức và thuộc tính riêng lẫn c|c phương thức và
thuộc tính chia sẻ. C|c phương thức riêng chỉ có bản th}n đối tượng mới có
quyền thay đổi. C|c phương thức chia sẻ có thể được truy cập bởi đối tượng
khác theo các mức khác nhau.
- Bước 5. Xây dựng và kiểm thử mô hình. Bắt tay vào xây dựng mô hình. Ở
đ}y, chúng ta sử dụng ngôn ngữ UML để mô tả. Sau khi xây dựng xong mô
hình, cần tiến hành kiểm thử mô hình. Kiểm thử các mô hình trong các tình
huống thực tế là cần thiết để đảm bảo rằng mô hình nhận được là phù hợp,
trước khi bắt tay vào viết chương trình.
Trên đ}y, chỉ là những bước đề nghị để chúng ta có một cái nhìn tổng quát
khi phân tích và thiết kế. Có thể có nhiều c|ch để phần tích và thiết kế một
Mỗi khi có một lỗi xuất hiện, chúng ta cũng cần biết khoanh vùng để thu
nhỏ phạm vi phát hiện lỗi.
Chúng ta sẽ lấy một ví dụ nhỏ. Ph}n tích hướng đối tượng mô hình quản
lý cửa hàng bán xe đạp. Trong mô hình này, ta cần quản lý c|c nhóm đối
tượng sau: đối tượng xe đạp, đối tượng chi nhánh bán hàng, đối tượng
kh|ch h{ng v{ đối tượng nhân viên bán hàng.
- Đối tượng xe đạp: chúng ta cần quản lý mã số xe (mã số gồm hai phần:
phần id chi nhánh bán hàng + mã số vạch), loại xe, màu sắc, gi| b|n, nước
T r a n g | 124
Chương 13. Lập trình hướng đối tượng
sản xuất (các thuộc tính chung). Đối tượng xe đạp địa hình: số b|nh răng,
c|ch lên răng (bằng tay/tự động), chống sooc. Đối tượng xe đạp du lịch: xe
đơn/đôi, tự động (hỗ trợ tự chạy bằng điện hay không), chiếu sáng
(có/không). Xe đua thể thao: điều chỉnh độ cao (có/không), các chế độ đạp
(đạp thư giản, đạp tăng tốc, đạp chậm…).
- Đối tượng khách hàng và nhân viên bán hàng: họ và tên, số CMND. Đối
tượng khách hàng: cách thức thanh toán (tiền mặt/chuyển khoản), cách
thức giao hàng (nhận tại chỗ/ đưa đến tận nh{). Đối tượng nhân viên bán
h{ng: id chi nh|nh b|n h{ng, ng{y th|ng năm sinh, quê qu|n, địa chỉ, mã số
thuế, lương…
- Đối tượng chi nh|nh b|n h{ng: id chi nh|nh b|n h{ng, địa chỉ.
Nếu yêu cầu quản lý nhiều hơn c|c thông tin của từng đối tượng, khi đó ta
cần bổ sung thêm các thuộc tính tương ứng này.
Đối với c|c phương thức thực thi h{nh động, tương ứng với mỗi thuộc tính,
ta có hai phương thức để chỉ định và tiếp nhận. Ví dụ, đối tượng nhân viên,
có họ v{ tên. Tương ứng với thuộc tính n{y, ta có phương thức chỉ định để
đặt tên cho nh}n viên (đặt tên là thiết lập tên gọi trong phần mềm quản lý)
và tiếp nhận tên khi có yêu cầu.
Đối tượng kh|ch h{ng có phương thức quyết định (quyết định thực hiện
h{ng còn đầy thì không tiếp nhận thêm).
Theo như ph}n tích ở trên, đối tượng xe đạp l{ đối tượng chung. C|c đối
tượng xe đạp thể thao, xe đạp du lịch, xe đạp địa hình kế thừa từ lớp xe đạp.
Đối tượng con người để quản lý thông tin chung. V{ c|c đối tượng nhân
viên và khách hàng thừa kế từ lớp con người. Cuối cùng l{ đối tượng chi
nh|nh b|n h{ng. Theo như c|ch ph}n tích n{y, ta có sơ đồ lớp như sau:
Hình 20 – Minh họa sơ đồ lớp
T r a n g | 125
Chương 13. Lập trình hướng đối tượng
Trong sơ đồ n{y, c|c phương thức và thuộc tính của mỗi lớp đối tượng như
đ~ ph}n tích ở trên. Để tr|nh rườm r{, c|c phương thức chỉ biểu diễn sơ
lược.
Lớp và đối tượng
Lớp là sự biểu diễn của đối tượng trong lập trình v{ ngược lại đối
tượng là sự thể hiện của lớp. Một đối tượng gồm có: thuộc tính v{ phương
thức. Chúng ta có thể xem một lớp như l{ một kiểu dữ liệu, còn đối tượng là
biến. Lớp được khai báo nhờ từ khóa class
class tên_lớp{
các_thuộc_tính
các_phương_thức
} [tên_đối_tượng];
tên_lớp: là tên của lớp.
tên_đối_tượng: là tên của đối tượng.
Khai báo lớp tương đối giống khai báo struct.
khai b|o như khai b|o h{m. Chúng có thể được chỉ định bằng một trong ba
từ khóa: private, protected và public.
o private: các thành viên (biến thành viên hoặc hàm thành viên) của cùng
một lớp hoặc từ một hàm bạn của nó có thể truy cập.
o protected: các thành viên của cùng một lớp hoặc từ một hàm bạn của nó
hoặc từ một lớp dẫn xuất của nó hoặc bạn của một lớp dẫn xuất của nó
đều có thể truy cập. Mức truy cập lớn nhất trong trường hợp này là bạn
của lớp dẫn xuất. Chúng ta sẽ thảo luận thêm trong những phần sau.
o public: các thành viên có thể truy cập lẫn nhau từ mọi lớp.
class Humans{
string name;
int age;
public:
void setName(string);
void setAge(string);
string getName(void);
int getAge(void);
T r a n g | 126
Chương 13. Lập trình hướng đối tượng
}man;
Humans là tên lớp, chứa các thuộc tính l{ name v{ age, chúng không được
chỉ định từ khóa, nên private sẽ được sử dụng. C|c phương thức setName,
setAge, getName và getAge được chỉ định là public. Trong trường hợp này,
man là một đối tượng thể hiện của lớp Humans.
Sau khi khai báo lớp, ta cần bổ sung phần thân lớp – tương ứng với các hàm
thành viên. Hoặc ta có thể bổ sung trực tiếp vào trong lớp – tương tự khai
báo hàm trực tiếp, hoặc sử dụng khai báo prototype. Đối với khai báo
prototype, để x|c định một phương thức là của một lớp n{o đó, ta sử dụng
toán tử phạm vi :: theo sau tên lớp.
Chương trình
Kết quả
#include<iostream>
The man: Jack, age 21
using namespace std;
class Humans{
string name;
int age;
void setAge(int);
string getName(void);
int getAge(void);
};
void Humans::setName(string s){
name = s;
}
void Humans::setAge (int a){
age = a;
}
string Humans::getName(void){
int Humans::getAge(void){
return age;
}
int main(){
Humans man;
man.setName(“Jack”);
man.setAge(21);
cout<<”The man: “<<man.getName()<<”, age
T r a n g | 127
Chương 13. Lập trình hướng đối tượng
“<<man.getAge();
return 0;
}
Giải thích: Hàm setName sẽ gán biến s cho th{nh viên name, tương tự cho
hàm setAge sẽ gán biến a cho thành viên age. Hàm getName trả về dữ liệu
nhận được từ thành viên name và hàm getAge – nhận được dữ liệu từ
thành viên age. Hai phương thức setName và setAge gọi l{ phương thức
setter. Phương thức getName và getAge gọi l{ phương thức getter. Các
phương thức setter dùng để nhập dữ liệu cho các thuộc tính thành viên, các
phương thức getter dùng để nhận giá trị từ các thuộc tính th{nh viên đ~
được nhập bởi setter (hoặc phương thức khởi tạo). Chúng ta cần lưu ý rằng,
hàm main không thuộc lớp Humans, do đó, nó không thể truy xuất đến các
thuộc tính th{nh viên trong trường hợp n{y, vì chúng được khai báo mặc
định là private.
Toán tử phạm vi :: sẽ giới hạn sự truy cập bất hợp lệ của những hàm
không thuộc lớp Humans hay là bạn của Humans. Đ}y l{ một cách thức để
quy địh mộpơng hức cphải lnh vin cmột lớp hay kô.
chất, hai cách này không có sự khác biệt nào.
Lớp cũng có t|c dụng như l{ một kiểu dữ liệu, do đó, ta cũng có thể
khai báo nhiều đối tượng của cùng một lớp hay mảng c|c đối tượng.
Chương trình
Kết quả
#include<iostream>
using namespace std;
Name and age of Student
1
Nam
21
class Humans{
string name;
Name and age of Student
22
void setName(string);
void setAge(int);
string getName(void);
int getAge(void);
};
Name and age of Student
3
Xuan
22
Name and age of Student
4
Tuan
void Humans::setName(string s){
name = s;
T r a n g | 128
Chương 13. Lập trình hướng đối tượng
}
21
void Humans::setAge (int a){
Name and age of Student
5
age = a;
}
Lan
22
string Humans::getName(void){
return name;
=====Students=====
The man: Nam, age 21
The man: Binh, age 22
The man: Xuan, age 22
The man: Tuan, age 21
The man: Lan, age 22
}
int Humans::getAge(void){
return age;
}
#define MAX 5
int main(){
Humans man[MAX];
for(int i=0; i<MAX; i++){
string s;
int a;
cout<<"Name and age of Student "
<<(i+1)<<endl;
man[i].setName(s);
man[i].setAge(a);
}
cout<<"=====Students====="<<endl;
for(int i=0; i<MAX; i++)
cout<<"The man: "<<man[i].getName()<<",
age "<<man[i].getAge()<<endl;
return 0;
}
Trong trường hợp này, biến man là một mảng c|c đối tượng Humans.
Chương trình minh họa cho việc nhập tên sinh viên, tuổi của họ v{ lưu v{o
lớp Humans, nhưng c|c đối tượng man[1], man[2],… có c|c thuộc tính hoàn
toàn khác nhau.
Cơ sở của lập trình hướng đối tượng là dữ liệu thành viên và các hàm
thành viên của một đối tượng. Chúng ta hoàn toàn không sử dụng một tập
các biến toàn cục để truyền qua một hàm (hay tập các biến cục bộ truyền
theo tham biến), m{ thay v{o đó, chúng ta sử dụng c|c đối tượng cùng với
T r a n g | 129
Chương 13. Lập trình hướng đối tượng
dữ liệu thành viên và hàm thành viên của nó. C|c h{m th{nh viên t|c động
trực tiếp lên các dữ liệu thành viên.
Hàm tạo và hàm hủy
Trước khi sử dụng một đối tượng, chúng ta cần khởi tạo giá trị cho nó
để tránh gặp phải những giá trị không mong muốn khi thực thi chương
trình. Một cách thức m{ chúng ta đ~ sử dụng ở trên là sử dụng phương thức
setter. Một phương thức đơn giản hơn, chúng ta có thể sử dụng hàm khởi
tạo (hay gọi tắt là hàm tạo). Việc khai báo hàm tạo cũng tương tự như khai
báo hàm thành viên khác, tuy nhiên nhất thiết tên hàm tạo phải trùng với
tên lớp.
Chương trình
Ví dụ
#include<iostream>
The man: Jack, age 21
using namespace std;
class Humans{
string name;
int age;
string getName(void);
int getAge(void);
};
Humans::Humans (string s, int a){
name = s;
age = a;
}
string Humans::getName(void){
return name;
}
int Humans::getAge(void){
int main(){
Humans man(“Jack”, 21);
cout<<”The man: “<<man.getName()<<”, age
“<<man.getAge();
return 0;
}
Hàm tạo không có kiểu dữ liệu trả về - tương ứng với kiểu void. Tuy nhiên
chúng ta sẽ không sử dụng từ khóa void trước khai báo hàm tạo.
T r a n g | 130
Chương 13. Lập trình hướng đối tượng
Nếu một đối tượng đ~ được tạo ra, nhưng ta không muốn sử dụng
đến nó nữa, để thu hồi bộ nhớ, ta cần sử dụng một phương thức để hủy bỏ
các dữ liệu thành viên của nó – đó l{ h{m hủy. Hàm hủy cũng l{ một hàm
thành viên của lớp. Nó không có kiểu dữ liệu trả về, nhưng ta cũng không
sử dụng từ khóa void trước khai báo hàm hủy. Hàm hủy có tên trùng với
tên lớp v{ phía trước tên hàm hủy là dấu ~. Hàm hủy sẽ tự động được gọi
khi phạm vi hoạt động của đối tượng kết thúc. Phạm vi hoạt động của một
đối tượng cũng giống như phạm vi hoạt động của một biến cục bộ - khai báo
trong phạm vi nào, thì chỉ có tác dụng trong phạm vi đó.
Chương trình
Ví dụ
#include<iostream>
The man: Jack, age 21
using namespace std;
class Humans{
string name;
int age;
public:
Humans(string, int);
int getAge(void);
};
Humans::Humans (string s, int a){
name = s;
age = a;
}
Humans::~Humans()
{
//do something
//delete pointer;
}
}
int Humans::getAge(void){
return age;
}
int main(){
Humans man(“Jack”, 21);
cout<<”The man: “<<man.getName()<<”, age
“<<man.getAge();
T r a n g | 131
Chương 13. Lập trình hướng đối tượng
return 0;
}
Chồng chất hàm tạo
Cũng như c|c h{m kh|c trong C++ cho phép chồng chất hàm, hàm tạo
cũng có thể bị chồng chất. Khi chồng chất hàm tạo thì danh sách tham số
phải khác nhau (kiểu dữ liệu hoặc số lượng tham số). Chúng ta cần nhớ
rằng, khi chồng chất hàm thì trình biên dịch sẽ gọi một h{m tương ứng với
danh sách tham số của nó. Trong trường hợp chồng chất hàm tạo, thì quá
trình gọi là tự động khi đối tượng được tạo ra, do đó, một hàm tạo sẽ được
gọi tự động tương ứng với danh sách tham số của nó.
Chương trình
#include<iostream>
using namespace std;
class Humans{
string name;
Ví dụ
The man: Jack, age 21
The default: Default, age
21
int age;
Humans(string, int);
string getName(void);
int getAge(void);
};
Humans::Humans (void){
name = “Default”;
age = 21;
}
Humans::Humans (string s, int a){
name = s;
age = a;
return name;
}
int Humans::getAge(void){
return age;
}
int main(){
Humans man(“Jack”, 21);
Humans default;
T r a n g | 132
Chương 13. Lập trình hướng đối tượng
cout<<”The man: “<<man.getName()<<”, age
“<<man.getAge()<<endl;
cout<<”The default: “<<default.getName()<<”,
age “<<default.getAge();
return 0;
}
Giải thích: trong trường hợp ví dụ trên, để tạo đối tượng thuộc lớp
Humans, ta có thể sử dụng một trong hai hàm tạo tương ứng:
Humans(void) hoặc Humans(string, int). Nếu gọi theo phương thức hàm
tạo không đối số, thì tên gọi và tuổi sẽ được tạo mặc định. Còn nếu gọi theo
phương thức có đối số, thì tên gọi và tuổi sẽ được tạo theo tham số truyền
vào. Ta cũng cần lưu ý trong c|ch gọi hàm tạo, đối với hàm tạo có đối số, thì
sau tên đối tượng, chúng ta cần cung cấp tham số tương ứng với tham số
hàm tạo bên trong dấu (). Còn đối với hàm tạo không đối số, thì hãy khai
b|o nó như khai b|o biến mà không hề có bất kì dấu () nào.
Humans man(); //Sai
Humans man; //Đúng
khai báo hàm tạo mặc định có tham số, thì việc khai báo một đối tượng theo
c|ch Humans man; l{ không được phép. Nếu ta không tạo ra một hàm tạo
n{o, thì điều này là hợp lệ.
Sao chép hàm tạo
Một đối tượng có thể được tạo ra từ hàm tạo theo cách khởi gán các
dữ liệu thành viên của nó cho một giá trị n{o đó. Chúng ta hoàn toàn có thể
khởi tạo một đối tượng từ một đối tượng khác bằng cách sử dụng toán tử
gán. Tuy nhiên, trong thực tế, nếu dữ liệu của đối tượng lớn, phức tạp, thì
việc sử dụng toán tử gán sẽ thực thi rất chậm và có thể gây ra một số lỗi liên
ta có thể sử dụng hàm tạo sao chép. Khi sử dụng hàm tạo sao chép, trình
biên dịch sẽ sao chép toàn bộ dữ liệu thành viên của đối tượng đó sang đối
tượng khởi tạo.
Humans man(“Jack”, 21);
//Sao chép trực tiếp
Humans man2 = man;
//Sao chép hàm tạo
T r a n g | 133
Chương 13. Lập trình hướng đối tượng
Humans::Humans(const Humans& m){
name = m.name;
age = m.age;
}
Humans man2(man);
Chúng ta lưu ý rằng, việc sao chép hàm tạo sẽ được quy định theo tham
chiếu hằng const Humans&. Nếu ta không viết hàm sao chép hàm tạo, thì
trình biến dịch sẽ tự động làm giúp(nghĩa l{ ta luôn có thể sử dụng cách
khởi tạo đối tượng theo kiểu Object newObj(oldObj); với oldObj l{ đối
tượng thuộc lớp Object đ~ được tạo, dù ta có tạo ra phương thức sao chép
hàm tạo hay không). Hay nói cách khác, hàm tạo sao chép là mặc định đối
với đại đa số trình biên dịch ANSI C++ hiện đại (GCC, Visual C++,
Borland C++, Intel C++).
Tham chiếu hằng. Phương thức sao chép hàm tạo (hoặc tổng quát là các
phương thức có sử dụng tham chiếu hằng đến lớp đối tượng) có thể thực
hiện theo tham chiếu hoặc tham chiếu hằng (tương ứng với không hoặc có
từ khóa const), nhưng hãy luôn quy định là tham chiếu (có toán tử &). Khi
đối tượng gốc. Dữ liệu của đối tượng tham chiếu sẽ được ánh xạ theo địa chỉ
của đối tượng được tham chiếu (không thực hiện việc sao chép trực tiếp mà
là gián tiếp thông qua địa chỉ của biến tham chiếu). Tuy nhiên, cũng vì lí do
n{y m{ đối tượng được tham chiếu có thể bị thay đổi giá trị (tương tự như
truyền theo tham biến). Điều này làm vi phạm tính đóng gói trong lập trình
hướng đối tượng. Cũng vì lí do n{y, C++ cung cấp cho ta từ khóa const để
quy định một đối tượng khi được tham chiếu sẽ không bị làm thay đổi dữ
liệu th{nh viên v{ nó được gọi là tham chiếu hằng. Như vậy, ta cần phân
biệt ba cách truyền tham số đối tượng trong một phương thức: truyền theo
tham trị – dữ liệu của đối tượng có thể được thay đổi bên trong phương
trường hợp này là thực thi trực tiếp nên thường chỉ áp dụng cho các kiểu
dữ liệu nguyên thủy đơn giản; truyền theo tham chiếu – dữ liệu của đối
tượng có thể bị thay đổi trong phương thức và nó được lưu lại, nó thực hiện
việc sao chép dữ liệu một cách gián tiếp nên dữ liệu có cấu trúc phức tạp
(như lớp đối tượng, con trỏ) có thể được sao chép nhanh hơn rất nhiều so
với truyền theo tham trị; tham chiếu hằng – tương tự như tham chiếu
nhưng không cho phép thay đổi dữ liệu của đối tượng ngay cả trong
phương thức.
T r a n g | 134
Chương 13. Lập trình hướng đối tượng
Hợp lệ
Không hợp lệ
…
…
class Humans{
class Humans{
string name;
int age;
string name;
int age;
public:
Humans(string, int);
public:
Humans(string, int);
Humans(const Humans&);
string getName(void);
Humans(const Humans&);
~Human();
int getAge(void);
string getName(void);
int getAge(void);
};
Humans::Humans(const Humans&
m){
};
Humans::Humans(const Humans&
name = m.name;
age = m.age;
m){
name = m.name;
}
…
age = m.age = 22;//Error
}
…
Ta có thể thấy trong trường hợp không hợp lệ, chúng ta quy định đối
đổi thuộc tính age của nó. Trong trường hợp n{y, chương trình sẽ phát sinh
lỗi. Nếu quy định là tham chiếu bình thường (bỏ đi từ khóa const) thì khai
b|o được xem là hợp lệ (tuy nhiên vi phạm tính đóng gói).
Khi một phương thức của lớp đối tượng sử dụng tham số chính là đối tượng
của lớp đó, chúng ta có thể truy cập trực tiếp đến thuộc tính của đối tượng
tham chiếu kể cả nó được quy định là private (cả tham chiếu lẫn không
tham chiếu). Bên cạnh đó, nếu ta quy định là tham chiếu bình thường, thì ta
có thể sử dụng c|c phương thức như getter v{ setter để truy cập đến các
thuộc tính của nó. Nhưng nếu ta sử dụng tham chiếu hằng, thì không được
ta chỉ có thể truy cập đến các phương thức hằng của đối tượng tham chiếu
hằng n{y. Phương thức hằng là những phương thức được bổ sung vào từ
khóa const vào cuối khai b|o phương thức trong tiêu đề hàm prototype và
tiêu đề trong khai b|o h{m đầy đủ. Hãy quan sát các ví dụ sau đ}y.
Ví dụ Tham chiếu Hằng
Ví dụ Tham chiếu
class PhanSo
…
…
class PhanSo
T r a n g | 135
Chương 13. Lập trình hướng đối tượng
{
{
private:
private:
int Tu;
int Mau;
int Tu;
int Mau;
public:
public:
//Khai báo các hàm tạo
PhanSo Nhan(const PhanSo&);
int GetTu(void);
int GetMau(void);
//Khai báo các hàm tạo
PhanSo Nhan(PhanSo&);
int GetTu(void);
int GetMau(void);
};
};
PhanSo PhanSo::Nhan(const
PhanSo& p)
PhanSo PhanSo::Nhan(PhanSo& p)
{
{
return PhanSo(Tu*p.GetTu(),
Mau*p.GetMau());
//hoặc PhanSo(Tu*p.Tu,
Mau*p.Mau);
return PhanSo(Tu*p.Tu,
Mau*p.Mau);
//không được phép viết
PhanSo(Tu*p.GetTu(),
Mau*p.GetMau());
}
int PhanSo::GetTu(void)
return Tu;
{
}
return Tu;
}
int PhanSo::GetMau(void)
{
int PhanSo::GetMau(void)
{
return Mau;
}
return Mau;
…
}
…
Trong trường hợp ta muốn sử dụng phương thức cho đối tượng tham chiếu
Khi đó, chúng ta sử dụng cú ph|p sau đ}y:
int GetTu(void) const;
int GetMau(void) const;
T r a n g | 136
Chương 13. Lập trình hướng đối tượng
…
class PhanSo
{
private:
int Tu;
int Mau;
public:
//Khai báo các hàm tạo
PhanSo Nhan(const PhanSo&);
int GetTu(void) const;
int GetMau(void) const;
};
PhanSo PhanSo::Nhan(const PhanSo& p)
{
return PhanSo(Tu*p.Tu, Mau*p.Mau);
//hoặc PhanSo(Tu*p.GetTu(), Mau*p.GetMau());
}
return Tu;
}
int PhanSo::GetMau(void) const
{
return Mau;
}
…
Việc bổ sung từ khóa const v{o sau khai b|o phương thức sẽ giúp cho đối
tượng tham chiếu hằng có thể gọi phương thức hằng này.
biến – ta có thể gán trực tiếp một giá trị biến cho phương thức đó m{ không
gặp phải một trở ngại nào.
Chương trình
#include <iostream>
using namespace std;
T r a n g | 137
Chương 13. Lập trình hướng đối tượng
class complex{
private:
float img;
float real;
public:
complex();
complex(float, float);
float &getimg();
float &getreal();
};
complex::complex( float img, float real )
{
this->img = img;
this->real = real;
}
complex::complex()
{
}
float & complex::getreal()
{
return real;
}
float & complex::getimg()
{
return img;
}
complex c;
c.getreal() = 2;
c.getimg() = 1;
cout<<c.getreal()<<" + I*"<<c.getimg();
}
Trong ví dụ này, chúng ta thấy c|c phương thức getter được khai báo là
phương thức tham chiếu. Ta có thể gán trực tiếp giá trị cho c|c phương
thức n{y. Trong trường hợp n{y, phương thức getter có cả hai tính năng:
T r a n g | 138
Chương 13. Lập trình hướng đối tượng
vừa là getter vừa là setter. Nhưng với tính năng của một phương thức setter
đơn (tức chỉ thiết lập một giá trị duy nhất).
Tính đóng gói – Encapsulation
Ví dụ trên đưa ra cho ta hai phương |n: nên hay không nên sử dụng
từ khóa const. Câu trả lời là h~y nên quy định việc sao chép hàm tạo là
truyền theo tham chiếu hằng, bởi lẽ c|c đối tượng khác nhau, không có
quyền chỉnh sửa dữ liệu thành viên của nhau, nó chỉ có thể truyền thông
điệp cho nhau mà thôi, việc chỉnh sửa dữ liệu thành viên là do bản thân của
đối tượng đó. Điều này là sự thể hiện tính đóng gói trong lập trình hướng
đối tượng. Tính đóng gói của lập trình hướng đối tượng còn thể hiện ở các
mức độ cho phép truy cập đối với dữ liệu và hàm thành viên – tương ứng
với từ kho| private, protected v{ public m{ ta đ~ thảo luận ở trên.
Khái niệm: tính đóng gói l{ tính chất không cho phép người dùng hay
đối tượng kh|c thay đổi dữ liệu thành viên của đối tượng nội tại. Chỉ có các
hàm thành viên của đối tượng đó mới có quyền thay đổi trạng thái nội tại
của nó mà thôi. C|c đối tượng khác muốn thay đổi thuộc tính thành viên của
quyết định thay đổi hay không vẫn do đối tượng nội tại quyết định.
Chúng ta có thể khảo sát ví dụ sau: nếu một bệnh nhân cần phải thay
nội tạng để có thể sống, thì việc thay thế nội tạng đó cần phải có sự đồng ý
của bệnh nhân. Không ai có thể tự động thực hiện điều này (chỉ khi bệnh
nh}n đ~ rơi v{o tình trạng hôn mê, thì người nhà bệnh nhân mới quyết định
thay họ). Nội tạng là các thuộc tính cố hữu của bệnh nhân. C|c phương thức
thay thế nội tạng của đối tượng b|c sĩ không phải l{ phương thức thành
viên của đối tượng bệnh nhân (bệnh nhân không thể tự thay thế nội tạng
cho mình v{ b|c sĩ không có quyền thay thế nội tạng cho bệnh nhân nếu
nhân). Phương thức đồng ý của bệnh nh}n n{y cũng không thể nào áp dụng
cho bệnh nhân kia (bệnh nhân A không thể quyết định thay thế nội tạng cho
bệnh nhân B). Như vậy, dữ liệu thành viên của đối tượng nào, thì chỉ có đối
tượng đó mới có quyền thay đổi.
Trong một vài giáo trình, tính chất này còn được gọi l{ tính đóng gói
và ẩn dấu thông tin (encapsulation and information hiding).
T r a n g | 139
Tải về để xem bản đầy đủ
Bạn đang xem 20 trang mẫu của tài liệu "Giáo trình C++ - Lập trình hướng đối tượng (Phần 2)", để tải tài liệu gốc về máy hãy click vào nút Download ở trên
File đính kèm:
giao_trinh_lap_trinh_huong_doi_tuong_phan_2.pdf

