Chương 2. Lớp và đối tượng (class & object) | Đại học Kinh tế Kỹ thuật Công nghiệp
Chương này cung cấp nền tảng về lớp và đối tượng trong lập trình hướng đối tượng, cũng như các nguyên tắc chính như đóng gói, kế thừa và đa hình. Những khái niệm này là rất quan trọng để xây dựng các ứng dụng phần mềm hiệu quả và có khả năng bảo trì cao.
Preview text:
CHƯƠNG 2. LỚP VÀ ĐỐI TƯỢNG (CLASS & OBJECT) Mục đích của chương
Trong chương này trình bày các vấn đề sau:
Khai báo và sử dụng 1 lớp
Khai báo và sử dụng đối tượng, mảng đối tượng, con trỏ đối tượng, tham chiếu đối tượng
Hàm thiết lập và hàm hủy bỏ Các hàm trực tuyến Hàm bạn 2.1 Khai báo lớp
Lớp là khái niệm trung tâm của lập trình hướng đối tượng. Nó là tập hợp các đối
tượng có cùng thuộc tính và hành vi. Một lớp bao gồm các thuộc tính (các thành phần dữ
liệu) và các hàm thành phần (các phương thức). Sau đây là dạng tổng quát định nghĩa 1 lớp: class {
[ phạm vi truy xuất : ]
[ phạm vi truy xuất : ] };
Trong đó: Tên lớp: Tên lớp do người lập trình tự đặt theo quy cách đặt tên trong C+
+ (Tên trong C++ bắt đầu bằng dấu gạch thấp hoặc ký tự, không chứa dấu cách, không
chứa ký tự đặc biệt, không trùng với từ khóa trong C++).
2.2 Khai báo các thành phần của lớp
2.2.1 Phạm vi truy xuất
Khi xây dựng một class, chắc chắn bạn sẽ phải xác định phạm vi truy cập cho các
thuộc tính và phương thức trong class đó. Mục đích của việc này nhằm quy định các thành
phần nào có thể được truy cập, thay đổi từ bên ngoài, thành phần nào là riêng tư.
Có thể hiểu phạm vi truy xuất này cũng giống như biến toàn cục và biến cục bộ.
Biến toàn cục có thể được truy cập từ tất cả các hàm sau khai báo nó, còn biến cục bộ chỉ
có thể được truy cập nội bộ trong hàm.
Trong C++, phạm vi truy cập được xác định qua 3 từ khóa: public, private và protected.
- public: Các thành phần mang thuộc tính này đều có thể được truy cập từ bất kỳ hàm
nào, dù ở trong hay ngoài lớp.
- private: Các thành phần mang thuộc tính này chỉ có thể được truy cập bên trong
phạm vi lớp. Vì trong C++ cho phép định nghĩa phương thức ngoài khai báo lớp nên
phạm vi lớp được hiểu là bên trong khai báo lớp và bên trong các định nghĩa thuộc lớp.
- protected: Các thành phần mang thuộc tính này chỉ có thể được truy cập bên trong
phạm vi lớp và các lớp con kế thừa nó. Như vậy, nếu một lớp không có lớp con kế
thừa nó thì phạm vi protected cũng giống như private.
Một ngoại lệ chỉ có trong C++ đó là định nghĩa friend. Một hàm hoặc lớp friend có
thể truy cập vào các thành phần private và protected của lớp với hàm đó (hàm friend) hoặc
với các đối tượng khác (lớp friend) với điều kiện phải được khai báo trước trong lớp.
Một số lưu ý về phạm vi truy xuất trong C++: -
Phạm vi truy xuất trong C++ được xác định trong qua các nhãn trong khai báo lớp.
Nhãn bao gồm từ khóa và dấu hai chấm. -
Mỗi nhãn có phạm vi ảnh hưởng từ lúc khai báo đến khi gặp nhãn khác hoặc hết khai báo lớp.
- Nếu không chỉ rõ nhãn đầu tiên thì ngầm định nó có phạm vi truy cập là private.
2.2.2 Các thành phần dữ liệu (thuộc tính) của lớp
Các thành phần dữ liệu (thuộc tính) của lớp: Có thể là các biến, mảng, con trỏ có
kiểu chuẩn (int, float, char, char*, long, double,…) hoặc kiểu ngoài chuẩn đã định nghĩa
trước (cấu trúc, hợp, lớp,…). Thuộc tính của lớp không thể có kiểu của chính lớp đó, nhưng
có thể là kiểu con trỏ lớp này, ví dụ: class A { …
A x ; // Không cho phép, vì x có kiểu lớp A A
*p ; // Cho phép, vì p là con trỏ kiểu lớp A … };
Khai báo thành phần dữ liệu: Tương tự như khai báo biến , cú pháp: ; Lưu ý:
- Trong các khai báo của các thuộc tính, có thể sử dụng từ khóa static nhưng không
được sử dụng các từ khóa auto, register, extern trong khai báo các thuộc tính.
- Thành phần dữ liệu không được khởi tạo giá trị ban đầu.
Các thành phần dữ liệu của lớp thường (không bắt buộc) khai báo là private để đảm
bao tính giấu kín, bảo vệ an toàn dữ liệu của lớp, không cho phép các hàm bên ngoài xâm
nhập vào dữ liệu của lớp.
2.2.3 Các thành phần hàm (phương thức của lớp)
Các phương thức của lớp: Có thể được được định ngay nghĩa bên trong lớp hoặc có
thể định nghĩa ngoài định nghĩa lớp. Khi định nghĩa bên ngoài lớp phải khai báo phương thức đó trong lớp.
Các phương thức thường được khai báo là public để chúng có thể được gọi tới từ các
hàm khác trong chương trình. Các phương thức trong lớp có thể được định nghĩa theo 2 cách như sau:
- Cách 1: Định nghĩa phương thức ngay trong lớp. Khi định nghĩa phương thức ngay
trong lớp chúng ta định nghĩa giống như hàm thông thường.
- Cách 2: Khai báo phương thức trong lớp và định nghĩa phương thức ngoài lớp. Khai
báo phương thức trong lớp vẫn khai báo theo cú pháp hàm thông thường. ([đối_số]) ;
Riêng việc định nghĩa phương thức ngoài lớp phải tuân theo cú pháp sau:
tên_lớp :: ([đối_số]) { // }
Và dù phương thức được định nghĩa trong lớp hay ngoài lớp thì đều có khả năng truy
xuất đến các thành phần khác trong lớp là như nhau.
Trong thân phương thức của 1 lớp (giả sử lớp A) có thể sử dụng:
- Các thuộc tính của lớp A
- Các phương thức của lớp A
- Các hàm tự do trong chương trình, vì phạm vi của các hàm tự do là toàn chương trình
Giá trị trả về của phương thức có thể có kiểu bất kỳ, gồm cả kiểu chuẩn và kiểu ngoài chuẩn.
Khai báo lớp hình chữ nhật gồm có 2 thành phần dữ liệu là chiều dài d và chiều rộng r, các
phương thức trong lớp gồm nhập, in, tính chu vi, tính diện tích của hình chữ nhật. class HCN { private: float d,r; public: void nhap() {
cout<<“nhap chieu dai: ”; cin>>d;
cout<<“nhap chieu rong: ”; cin>>r; } void inthongtin() { cout<<”(”<} float tinhchuvi() { return 2*(d+r); } float tinhdientich() { return d*r ; } };
Có thể khai báo các phương thức trong lớp và định nghĩa các phương thức ngoài lớp như sau: class HCN { private: float d,r; public: void nhap() ; void inthongtin(HCN h); float tinhchuvi(); float tinhdientich(); }; void HCN :: nhap() {
cout<<“nhap chieu dai: ”; cin>>d;
cout<<“nhap chieu rong: ”; cin>>r; } void HCN :: inthongtin() { cout<<”(”<} float HCN :: tinhchuvi() { return 2*(d+r); } float HCN :: tinhdientich() { return d*r ; }
2.3 Biến, mảng và con trỏ đối tượng
2.3.1 Đối tượng
Trong C++, khi lớp được định nghĩa, chỉ có đặc tả của đối tượng được định nghĩa,
không có vùng nhớ hay ô chứa nào được cấp phát. Để sử dụng dữ liệu và truy cập vào các
hàm định nghĩa trong lớp, chúng ta cần tạo đối tượng. Cú pháp khai báo tạo đối tượng trong C++ như sau:
tên_lớp tên_biến_đối_tượng;
Khi đó vùng nhớ được cấp phát cho một biến có kiểu lớp sẽ cho ta một khung của
đối tượng bao gồm dữ liệu là các thể hiện cụ thể của các mô tả dữ liệu trong khai báo lớp và
các thông điệp gửi tới các hàm thành phần.
Mỗi đối tượng sở hữu một tập các biến tương ứng với tên và kiểu của các thuộc tính
định nghĩa trong lớp. Chúng được gọi là các biến thể hiện của đối tượng. Trong đó, tất cả
các đối tượng cùng một lớp chung nhau định nghĩa của các phương thức.
Lớp là một kiểu dữ liệu, vì vậy cũng có thể khai báo con trỏ hay tham chiếu đến một
đối tượng thuộc lớp, và bằng cách ấy có thể truy nhập gián tiếp đến đối tượng. Tuy vậy cần
lưu ý, con trỏ và tham chiếu không phải là một thể hiện của lớp.
Trong ví dụ 2.1, lớp HCN đã được định nghĩa, chúng ta có thể tạo các đối tượng h1,
h2, h3 có kiểu lớp HCN như sau: HCN h1, h2, h3;
2.3.2 Mảng đối tượng
Vì lớp là một kiểu dữ liệu, nên chúng ta cũng có thể khai báo mảng các đối tượng theo cú pháp sau: tên_lớp
tên_mảng_đối_tượng [số_phần_tử] ;
Có thể khai báo mảng đối tượng H gồm 20 phần tử như sau: HCN H[20];
Mỗi đối tượng su khi khai báo sẽ được cấp phát một vùng nhớ riêng để chứa các
thuộc tính của chúng. Nhưng sẽ không có vùng nhớ riêng để chứa các phương thức cho mỗi
đối tượng. Các phương thức sẽ được sử dụng chung cho tất cả các đối tượng cùng lớp. Và
như vậy, về bộ nhớ cấp phát thì đối tượng giống như cấu trúc. Kích thước mỗi đối tượng sẽ
đúng bằng tổng kích thước của các thuộc tính.
2.3.3 Thuộc tính của đối tượng
Mỗi thuộc tính đều thuộc về một đối tượng, vì vậy không thể viết tên thuộc tính một
cách riêng rẽ mà bao giờ cũng phải có tên đối tượng đi kèm, giống như cách viết trong cấu
trúc của C++, cú pháp như sau:
Tên_đối_tượng . tên_thuộc_tính ;
Tên_mảng_đối_tượng[chỉ_số] . tên_thuộc_tính ;
Với các đối tượng h1, h2, h3 và mảng H có thể viết như sau: h1.d
// thuộc tính d của đối tượng h1 h2.d
// thuộc tính d của đối tượng h2 h3.r
// thuộc tính r của đối tượng h3 h1.d = 50; // gán 50 cho h1.d h2.r = 5.5; // gán 5.5 cho h2.r H[5].d
// thuộc tính d của phần tử H[5] H[5].r = 10; // gán 10 cho H[5].r
H[0].d = 100; // gán 10 cho H[0].d
2.3.4 Sử dụng các phương thức
Giống như hàm, phương thức được sử dụng thông qua lời gọi. Tuy nhiên, lời gọi
phương thức bao giờ cũng phải có tên đối tượng để chỉ rõ phương thức thực hiện trên các
thuộc tính của đối tượng nào. Cú pháp sử dụng phương thức như sau:
Tên_đối_tượng . tên_phương_thức (các_đối_số) ;
Tên_mảng_đối_tượng[chỉ_số].tên_phương_thức(các_đối_số) ; Chẳng hạn lời gọi:
//thực hiện nhập các thuộc tính d, r của đối tượng h1 h1 . nhap();
/ thực hiện nhập các thuộc tính H[i].d, H[i].r H [i] . nhap();
Chương trình minh họa sử dụng lớp và đối tượng, gồm nhập và in ra màn hình thông tin về hình chữ nhật đó. Ví dụ 2.1: #include #include class HCN { private: float d,r; public: void nhap() ; void inthongtin(); }; void HCN :: nhap() {
cout<<"nhap chieu dai: "; cin>>d;
cout<<"nhap chieu rong: "; cin>>r; } void HCN :: inthongtin() { cout<<"("<} int main() { HCN h1;
cout<<"Nhap thong tin hinh chu nhat:"<h1.nhap();
cout<<"In thong tin hinh chu nhat: "; h1.inthongtin(); return 0; }
Trường hợp sử dụng mảng các đối tượng minh họa qua chương trình sau. Chương
trình nhập vào thông tin 5 hình chữ nhật, in ra màn hình giá trị lớn nhất của chu vi các hình chữ nhật đã nhập. Ví dụ 2.2: #include #include class HCN { private: float d,r; public: void nhap() ; void inthongtin(); float tinhchuvi(); }; void HCN :: nhap() {
cout<<"nhap chieu dai: "; cin>>d;
cout<<"nhap chieu rong: "; cin>>r; } void HCN :: inthongtin() { cout<<"("<} float HCN :: tinhchuvi() { return 2*(d+r); } int main() { HCN H[5]; int i; float Maxcv;
//Nhap chieu dai và chieu rong cua 5 hinh chu nhat for(i=0;i<5;i++) {
cout<<"Nhap thong tin hinh chu nhat thu "<< i+1<<":"<H[i].nhap(); } / Tìm chu vi lon nhat Maxcv = H[0].tinhchuvi(); for(i=1;i<5;i++) {
if (Maxcv < H[i].tinhchuvi()) Maxcv = H[i].tinhchuvi(); }
cout<<"Gia tri lon nhat cua chu vi là: "<return 0; }
2.3 5 Con trỏ đối tượng
Con trỏ đối tượng dùng để chứa địa chỉ của biến, mảng đối tượng. Cú pháp khai báo như sau: Tên_lớp *con_trỏ ;
Chẳng hạn với lớp HCN có thể khai báo:
HCN *p1, *p2, *p3; // khai báo 3 con trỏ p1, p2, p3 HCN h1, h2;
// khai báo 2 đối tượng h1, h2 HCN H[20];
// khai báo mảng đối tượng H
Khi đó có thể thực hiện các câu lệnh sau:
p1 = &h1;// p1 chứa địa chỉ của h1, hay p1 trỏ tới h1 p2 = H; hoặc
p2 = &H[0]; /*p2 trỏ tới đầu mảng H hoặc p2 trỏ tới phần
tử đầu tiên của mảng H, 2 lệnh này tương đương nhau*/
p3 = new HCN; /*Tạo 1 đối tượng và chứa địa chỉ của nó vào p3*/
Để sử dụng thuộc tính của đối tượng thông qua con trỏ ta viết như sau:
Tên_con_trỏ -> Tên_thuộc_tính
Tên_mảng_đối_tượng[chỉ_số] -> Tên_thuộc_tính
Lưu ý: Nếu con trỏ chứa địa chỉ đầu của mảng, có thể dùng con trỏ như tên mảng.
Như vậy sau khi thực hiện các câu lệnh trên thì :
p1 -> d và h1.d là như nhau p2[i] -> r và H[i] . rlà như nhau
Chương trình dưới đây minh họa cho việc sử dụng con trỏ trong chương trình. Ví dụ 2.3: #include #include class HCN { private: float d,r; public: void nhap() ; void inthongtin(); }; void HCN :: nhap() {
cout<<"nhap chieu dai: "; cin>>d;
cout<<"nhap chieu rong: "; cin>>r; } void HCN :: inthongtin() { cout<<"("<} int main() { HCN *p; int i,n; p = new HCN[n+1]; cout<<"nhap n: "; cin>>n;
cout<<"Nhap thong tin cho cac hinh chu nhat:"<for(i=0;i{
cout<<"nhap kich thuoc hcn thu "<p[i].nhap(); cout<}
cout<<"In thong tin hinh chu nhat: "; for(i=0;i{
cout<<"thong tin hcn thu "<p[i].inthongtin(); cout<} return 0; }
2.3.6 Phép gán các đối tượng
Có thể thực hiện phép gán giữa 2 đối tượng cùng kiểu. Ví dụ a, b là 2 đối tượng
cùng kiểu lớp phân số PS, lớp PS có 2 thành phần dữ liệu là tử số ts và mẫu số ms. Khi
sum = sum + mt[i][j] * b.vt[j]; kq.vt[i]=sum; } return kq; } int main() { vector V(1,2,3); vector KQ;
double dlm[3][3]={1,2,3,4,5,6,7,8,9}; matran M=dlm; KQ = M.nhan(V);
cout<<"Ket qua phep nhan la: "; KQ.invt(); return 0; }
2.8.2.3 Tất cả các hàm thành phần của một lớp là bạn của một lớp khác
Để tất cả các hàm thành phần của lớp B là bạn của lớp A, trong khai báo của lớp A phải có chỉ thị: class A {... friend class B; .. . }
Khi đó còn gọi lớp B là bạn của lớp A (lớp bạn)
Lưu ý: Để biên dịch khai báo của lớp A, phải đặt khai báo class B; trước khai báo lớp A.
Trong chương trình sau, lớp ma trận mt và lớp vec tơ vt là bạn bè của nhau. Khi đó tất cả
các phương thức của lớp này đều là bạn của lớp kia và ngược lại [1]. Ví dụ 2.14 #include #include