Khởi tạo và hủy

-Trongbấtcứmột ngôn ngữlập trình nào trước khi thao tác hayxử lý

trênbấtkỳmột đốitượng nào chúng tabắt buộc phảitạo (khởitạo)

chúng. Điều này là dĩ nhiên!!.

-Sau khikết thúc chương trình hoặc không dùng đếnmột đốitượng nào

đócủa chương trình, chúng ta nên xóa (hủy) đốitượng tránh việc đối

tượng chiếm giữ mãi mãi vùng nhớ đượccấp cho đốitượngbởi chương

trình để những đốitượng khác có thể được khởitạo (tránh lãng phíbộ

nhớ và làmtăng kích thước chương trình khi chạy). Điều này là không

bắt buộc nhưng chắc chắnmột điều lànếu chúng ta không làm điều này

thì chương trìnhcủa chúng tasẽ gây ramộtsốlỗi nghiêm trọng có thể

ảnhhưởng đếnhệ thống.

pdf15 trang | Chuyên mục: Lập Trình Hướng Đối Tượng | Chia sẻ: dkS00TYs | Lượt xem: 2330 | Lượt tải: 0download
Tóm tắt nội dung Khởi tạo và hủy, để xem tài liệu hoàn chỉnh bạn click vào nút "TẢI VỀ" ở trên
 tự động gọi khi đối tượng được 
định nghĩa, còn hàm hủy thì ngược lại được trình biên dịch tự động 
gọi khi đối tượng ra khỏi phạm vi của nó. 
· Hàm khởi tạo có thể được nạp chồng còn hàm hủy thì không, tức là 
mỗi lớp có nhiều nhất một hàm hủy. 
Ví dụ 1: 
class ViDu 
{ public: 
 ViDu() 
{ cout<<"Constructor"<<endl; 
} 
~ViDu() 
{ cout<<"Destructor"<<endl; 
} 
}; 
int main() 
{ ViDu a; 
 { ViDu b; 
 for (int i=0; i<2; ++i) 
 { ViDu c; 
 getch(); 
 } 
} 
getch(); 
return 0; 
} 
Ø Kết quả: Constructor 
 Constructor 
 Constructor 
 Destructor 
 Constructor 
Destructor 
 Destructor 
 Destructor 
*Giải thích: Hai dòng đầu tiên là kết quả của khai báo ViDu a, ViDu b 
được trình biên dịch tự động gọi hàm khởi tạo mặc định a.ViDu(), 
b.ViDu(). Tương tự cho khai báo ViDu c khi vào vòng lặp for, sau đó 
đợi nhập một phím bất kỳ, ra khỏi vòng lặp và bắt đầu một vòng lặp 
mới, khi đó hàm hủy cho đối tượng c được tự động gọi, tương tự cho 
vòng lặp thứ hai. Tiếp theo hàm hủy của đối tượng b được gọi tự động vì 
nó đã ra khỏi phạm vi định nghĩa của nó. Cuối cùng, chờ nhập một phím 
bất kỳ và hàm hủy của đối tượng a được tự động gọi, kết thúc chương 
trình. 
Ví dụ 2: 
class ViDu 
{ private: 
 int* x; 
 public: 
 ViDu(int a=0) 
 { x=new int; 
 *x=a; 
} 
~ViDu() 
{ delete x; 
} 
} 
int main() 
{ ViDu* a=new ViDu; 
 delete a; 
 return 0; 
} 
*Giải thích: a được định nghĩa là một con trỏ trỏ đến đối tượng của lớp 
ViDu, khi đó new ViDu sẽ gọi hàm khởi tạo của lớp với tham số mặc 
định và đối tượng này được con trỏ a trỏ đến. Nếu trong ví dụ này ta 
không định nghĩa hàm hủy thì câu lệnh delete a vẫn thu hồi vùng nhớ 
được cấp phát cho đối tượng được nó trỏ đến nhưng vùng nhớ do thành 
viên con trỏ của lớp vẫn không được thu hồi. Khi ta định nghĩa hàm hủy 
thì delete a trước hết sẽ gọi đến hàm hủy của đối tượng thu hồi vùng nhớ 
của thành viên con trỏ, sau đó sẽ thu hồi vùng nhớ của đối tượng. 
2. Cơ chế hoạt động của hàm hủy: 
-Cũng như hàm khởi tạo, hàm hủy trong C++ được tự động gọi bởi trình 
biên dịch và không có một sự lựa chọn nào cho lập trình viên. 
-Hàm hủy thường được sử dụng khi trong lớp có thành viên con trỏ được 
cấp phát động bởi toán tử new. Khi đó ta phải định nghĩa hàm hủy để 
thu hồi vùng nhớ mà thành viên con trỏ chiếm giữ trong Heap bằng toán 
tử delete. Bởi vì khi một vùng nhớ được cấp phát bởi toán tử new thì nó 
chỉ được thu hồi bởi toán tử delete, trình biên dịch không giúp ta trong 
trường hợp này. 
Ví dụ: 
class A 
{ private: 
 int* x; 
 public: 
 A(int a=0) 
 { x=new int; 
 *x=a; 
} 
~A() 
{ delete x; 
} 
}; 
-Khác với C++, trong ngôn ngữ C# và Java vấn đề không khó khăn như 
trong C++. Bởi vì, trong C# và Java cung cấp sẵn một cơ chế thu hồi 
vùng nhớ rất mạnh mẽ và linh hoạt, đó là trình thu gom rác garbage-
collector (gc). Vì tất cả đối tượng trong C# và Java đều là kiểu tham 
chiếu nên khi định nghĩa một đối tượng ta bắt buộc phải khởi tạo chúng, 
khi đó chúng được cấp phát một vùng nhớ thuộc trình garbage-collector 
(gc) quản lý và thời gian sống cũng như vị trí của chúng được quản lý 
bởi trình này, chúng ta chỉ thao tác với đối tượng thông qua tham chiếu 
đến đối tượng mà thôi. 
-Trình thu gom gc được tự động gọi mỗi khi máy tính rảnh rỗi hoặc khi 
định nghĩa một đối tượng hoặc khi một đối tượng ra khỏi phạm vi của 
mình. Trình thu gom có cách biết khi nào một đối tượng không cần đến 
nữa trong chương trình và tự động thu hồi vùng nhớ do đối tượng đó 
chiếm giữ. Trình thu gom có thể tự quyết định di chuyển đối tượng trong 
bộ nhớ (tránh làm phân mảnh bộ nhớ) để nhường chỗ cấp phát cho 
những đối tượng mới và nhằm tiết kiệm bộ nhớ. Do vậy mà trong 
chương trình địa chỉ của đối tượng có thể thay đổi nhưng tham chiếu đến 
đối tượng đó vẫn không thay đổi. 
Ví dụ: Trong C#: 
 class A{} 
 class Test 
 { public static void main() 
 { A a=new A(); 
 A b=new A(); 
 a=b; 
} 
} 
*Giải thích: Ở đây phép gán a=b sẽ đưa hai đối tượng a, b trở thành một, 
tức là cả a và b cùng tham chiếu đến một đối tượng trong bộ nhớ, khi đó 
đối tượng do a tham chiếu lúc đầu sẽ được trình thu gom thu hồi vùng 
nhớ. Bởi vì đối tượng đó không có một tham chiếu nào đến nó cả, tức là 
nó không còn dùng được trong chương trình. 
C. MỘT SỐ VẤN ĐỀ KHÁC: 
1. Hàm khởi tạo: 
-Giả sử ta có một lớp có constructor đơn giản như sau: 
class A 
{ public: 
 A() {} 
}; 
int main() 
{ A a; 
 return 0; 
} 
Khi ta định nghĩa A a, nghĩa là khi biên dịch trình biên dịch tự động thay 
vào đó là lời gọi hàm A::A() của đối tượng a. Và tất cả các lời gọi hàm 
thành viên của một đối tượng luôn đi kèm với con trỏ ẩn – this – là một 
con trỏ ẩn của lớp được trình biên dịch tự động thêm vào, nó chứa địa 
chỉ của đối tượng đang được gọi, tức là nó trỏ tới chính đối tượng đang 
chứa nó. 
2. Hàm hủy: 
-Phải chú ý một điều là trình biên dịch không tự động gọi hàm hủy một 
đối tượng được trỏ bởi con trỏ kiểu void. Khi đó nếu ta không để ý thì 
vùng nhớ của đối tượng vẫn được thu hồi nhưng vùng nhớ của thành 
viên con trỏ của đối tượng trỏ đến sẽ không được thu hồi. 
Ví dụ: 
class A 
{ private: 
 char* s; 
 public: 
 A(char c='A') 
 { s=new char; 
 *s=c; 
} 
~A() 
{ delete s; 
} 
}; 
int main() 
{ A* a=new A; 
 delete a; 
 void* b=new A('Z'); 
 delete b; 
} 
Khi gọi delete a hàm hủy của đối tượng được a trỏ tới sẽ được gọi trước 
sau đó vùng nhớ do a trỏ tới sẽ được thu hồi, do đó vùng nhớ do a trỏ tới 
lẫn vùng nhớ do con trỏ thành viên của lớp trỏ tới đều được thu hồi. 
Trong khi đó, delete b sẽ không gọi đến hàm hủy (vì b có kiểu void*), 
do đó chỉ thu hồi được vùng nhớ do b trỏ tới. 
-Trong C++, hàm hủy phải làm tất cả, từ việc thu hồi vùng nhớ của các 
thành viên đến các việc dọn dẹp đối tượng trước khi hủy luôn đối tượng 
(ví dụ như xóa hình vẽ của đối tượng trên màn hình). Vì vậy trong C++ 
hàm hủy quan trọng không kém hàm khởi tạo. 
-Nhưng trong C# và Java, điều đó không cần thiết lắm vì chúng có cơ 
chế quản lý và thu hồi bộ nhớ rất linh hoạt (gc) nên hàm hủy chỉ đóng 
vai trò dọn dẹp đối tượng trước khi trình thu gom thu hồi vùng nhớ của 
đối tượng. 
-Một thông báo lỗi sẽ xuất hiện lúc biên dịch khi ta sử dụng goto hoặc 
switch để nhảy qua câu lệnh mà tại đó đối tượng được định nghĩa. Do 
vậy, ta không nên dùng goto hoặc định nghĩa đối tượng trong switch. 
Ví dụ: 
class A 
{ public: 
 A() {} 
}; 
int main() 
{ int i; 
 cin>>i; 
 switch(i) 
 { case 1: 
 A a; break; 
case 2: //Lỗi 
 A b; break; 
} 
 if (i) goto Nhay; //Lỗi 
 A a; 
 Nhay: 
 getch(); 
 return 0; 
} 
Nếu lớp A của ta không định nghĩa hàm khởi tạo thì sẽ không xuất hiện 
thông báo lỗi, nhưng khi đó các biến thành viên của lớp sẽ chứa giá trị 
rác và dẫn đến kết quả không mong muốn khi chạy chương trình. 
3. Phạm vi của biến và đối tượng: 
-Khác với C, biến và đối tượng trong C++ có thể được định nghĩa bất cứ 
ở đâu trong chương trình. 
-Biến và đối tượng chỉ tồn tại từ lúc chúng được định nghĩa cho đến khi 
ra ngoài khối mà chúng được định nghĩa, khối đó được đánh mở đầu và 
kết thúc bằng cặp ngoặc nhọn {…}. 
-Đối với đối tượng có hàm hủy, khi ra khỏi phạm vi định nghĩa thì trình 
biên dịch tự động gọi hàm hủy của chính đối tượng đó rồi thu hồi vùng 
nhớ của đối tượng (trừ những đối tượng được cấp phát động). 
-Trong C++, biến đếm được định nghĩa bên trong vòng lặp for (điều mà 
ở C không có). Ở một số trình biên dịch cũ không cho phép biến được 
định nghĩa trong vòng lặp for vẫn còn tồn tại sau khi ra khỏi vòng lặp, ví 
dụ như: for (int i=0; i<10; ++i); cout<<i; sẽ bị báo lỗi khi dịch. Nhưng 
ở một số trình biên dịch khác như VC++ thì ngược lại, trình biên dịch 
vẫn cho phép biến được định nghĩa trong vòng lặp vẫn còn tồn tại sau 
khi ra khỏi vòng lặp. 
4. Đối tượng hằng (Constant Object): 
-Cũng như các biến hằng, các đối tượng hằng cũng được khai báo giống 
như biến. Khi được khai báo là đối tượng hằng, trình biên dịch sẽ không 
cho phép bất cứ một sự thay đổi nào trong dữ liệu thành viên của đối 
tượng trong quá trình sống của nó. Bởi vậy, chúng phải được định nghĩa 
ngay khi khai báo và chỉ nhưng hàm thành viên hằng mới được tác động 
lên đối tượng hằng. 
5. Hàm thành viên hằng: 
-Hàm thành viên hằng là hàm thành viên mà nội dung của hàm phải đảm 
bảo rằng không làm thay đổi bất kỳ thành viên dữ liệu nào của đối 
tượng. Nó được khai báo bằng từ khóa const đặt phía sau của khai báo 
hàm. Hàm hằng ngoài việc thao tác được với các đối tượng bình thường 
thì nó có thể thao tác được với các đối tượng hằng. 
Ví dụ: 
class Diem 
{ private: 
 int x; 
 int y; 
 public: 
 Diem(int a=0, int b=0) 
 { x=a; 
 y=b; 
} 
void InDiem() const 
{ cout<<x<<" "<<y<<endl; 
} 
} 
int main() 
{ Diem a; 
 const Diem b(1, 3); 
 a.InDiem(); //OK 
 b.InDiem(); //OK 
 return 0; 
} 
Nếu hàm InDiem() không phải hàm hằng thì câu lệnh b.InDiem() sẽ bị 
lỗi!! 
6. Con trỏ hằng và hằng con trỏ: 
-Con trỏ trỏ đến đối tượng hằng (con trỏ hằng) là con trỏ mà không được 
phép thay đổi giá trị của đối tượng mà nó trỏ tới. 
Ví dụ: int x=2; 
 int y=1; 
 const int* a=&x; hoặc int const* a=&x; //a là con trỏ hằng 
 x=3; //OK 
 *a=4 //Lỗi 
 a=&y; //OK 
-Hằng con trỏ là con trỏ mà giá trị của nó được xác định ngay khi khai 
báo và không được thay đổi giá trị của nó. Tức là hằng con trỏ trỏ đến 
một đối tượng cố định, không thể thay đổi để nó trỏ tới một đối tượng 
khác và do đó nó phải được khởi tạo ngay khi khai báo. 
Ví dụ: int x=3; 
 int y=4; 
 int* const a=&x; 
 *a=5; //OK 
 a=&y; //Lỗi 
7. Tham chiếu hằng: 
-Tham chiếu hằng là là biến mà tham chiếu của nó không được phép 
thay đổi giá trị. 
Ví dụ: int x=3; 
const int& a=x; 
x=5; //OK 
a=4; //Lỗi 
-Bởi vì bản chất của tham chiếu là hằng, luôn gắn liền với một đối tượng 
và phải được khởi tạo khi khai báo nên không có hằng tham chiếu. 

File đính kèm:

  • pdfKhởi tạo và hủy.pdf
Tài liệu liên quan