Lập trình hướng đối tượng - Sự khởi tạo và hủy

Một sự ngầm định constructor có thể được gọi là không có đối số. Một

constructor mặc định được sử dụng để tạo ra một đối tượng “vanilla”, nhưng nó

cũng rất quan trọng khi trình biên dịch được nói để tạo ra một đối tượng nhưng

không đưa ra bất kỳ chi tiết nào. Ví dụ, nếu bạn đi struct Y được xác định trước

đó và sử dụng nó trong một định nghĩa như thế này:

Y y2[2] = { Y(1) };

Trình biên dịch sẽ than phiền rằng nó không thể tìm thấy một constructor

ngầm định. Đối tượng thứ hai trong mảng đó muốn được tạo ra không có đối số,

và đó là nơi trình biên dịch sẽ cho một constructor ngầm định. Trong thực tế, nếu

bạn chỉ cần định nghĩa một mảng các đối tượng Y,

pdf21 trang | Chuyên mục: Lập Trình Hướng Đối Tượng | Chia sẻ: dkS00TYs | Lượt xem: 1528 | Lượt tải: 0download
Tóm tắt nội dung Lập trình hướng đối tượng - Sự 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
nhiệm. Ví dụ, nếu bạn không pop () và xóa tất cả các con trỏ trên Stack, họ sẽ 
không nhận được làm sạch tự động bởi destructor của Stack. Điều này có thể là 
một vấn đề dính và dẫn đến rò rỉ bộ nhớ, để biết những người có trách nhiệm làm 
sạch lên một đối tượng có thể làm cho sự khác biệt giữa một chương trình thành 
công và là một trong xe đẩy - đó là lý do tại sao Stack:: ~ Stack () in một thông 
báo lỗi nếu Stack đối tượng là không có sản phẩm nào khi tiêu hủy. 
Bởi vì việc cấp phát và hủy của các đối tượng liên kết được ẩn trong Stack 
- nó là một phần của việc thực hiện ngầm- bạn không thấy nó xảy ra trong 
chương trình thử nghiệm, mặc dù bạn có trách nhiệm xóa các con trỏ mà quay lại 
từ cửa sổ pop (): 
#include "Stack3.h" 
#include "../require.h" 
#include 
#include 
#include 
using namespace std; 
int main(int argc, char* argv[]) { 
 requireArgs(argc, 1); // File name is argument 
 ifstream in(argv[1]); 
 assure(in, argv[1]); 
 Stack textlines; 
 string line; 
 // Read file and store lines in the stack: 
 while(getline(in, line)) 
 textlines.push(new string(line)); 
 // Pop the lines from the stack and print them: 
 string* s; 
 while((s = (string*)textlines.pop()) != 0) { 
 cout << *s << endl; 
 delete s; 
 } 
} ///:~ 
Trong trường hợp này, tất cả các dòng textlines đã được lấy và xóa bỏ, 
nhưng nếu chúng không được, bạn sẽ nhận được một require () thông báo rằng sẽ 
có nghĩa là có một sự rò rỉ bộ nhớ. 
VI .Khởi tạo tập hợp 
Một tập hợp giống như là : một nhóm những khối với nhau. Định nghĩa 
này bao gồm tập hợp các loại hỗn hợp, như cấu trúc và các lớp học. Một mảng là 
một tổng hợp của một loại duy nhất. 
Việc khởi tạo tập hợp có thể dễ bị lỗi và tẻ nhạt. C + +, khởi tạo tập hợp 
làm cho nó an toàn hơn nhiều. Khi bạn tạo một đối tượng mà là một tập hợp, tất 
cả những gì bạn phải làm là tạo một phân công, và việc khởi tạo sẽ được theo dõi 
bởi trình biên dịch.Tùy thuộc vào loại hình tập hợp bạn đang xử lý, nhưng trong 
tất cả các yếu tố trong trường hợp khai báo phải được bao quanh bởi các dấu 
ngoặc nhọn. Đối với một mảng loại built-in này khá đơn giản: 
 int a[5] = { 1, 2, 3, 4, 5 }; 
 Nếu bạn cố gắng cung cấp nhiều việc khởi tạo hơn, đó là những yếu tố 
mảng, trình biên dịch cho một thông báo lỗi. Nhưng những gì xảy ra nếu bạn 
cung cấp ít việc khởi tạo hơn? Ví dụ: 
 int b[6] = {0}; 
 Ở đây, trình biên dịch sẽ sử dụng sự khởi tạo đầu tiên cho các phần tử 
mảng đầu tiên, và sau đó sử dụng số 0 cho tất cả các yếu tố mà không được khởi 
tạo. Thông báo khởi tạo hành vi này không xảy ra nếu bạn định nghĩa một mảng 
mà không có một danh sách các phần tử bắt đầu. Vì vậy, các biểu hiện ở trên là 
một cách gọn gàng để khởi tạo một mảng về không, mà không cần sử dụng cho 
vòng lặp, và không có khả năng xảy ra một lỗi off-by-one (Tùy thuộc vào trình 
biên dịch, nó cũng có thể hiệu quả hơn hơn cho vòng lặp. ) 
Một giây viết tắt cho mảng thứ hai là tự động đếm, mà trong đó bạn cho 
phép trình biên dịch xác định kích thước của mảng đó dựa trên số lượng phần tử 
khởi đầu: 
 int c[] = { 1, 2, 3, 4 }; 
 Bây giờ nếu bạn quyết định thêm một yếu tố đến mảng đó, bạn chỉ cần 
thêm một phần tử khởi đầu. Nếu bạn có thể đặt mã của bạn lên vì vậy nó cần phải 
được thay đổi trong chỉ có một chỗ, bạn làm giảm cơ hội sai sót trong quá trình 
sửa đổi. Nhưng làm thế nào để bạn xác định kích thước của mảng đó? Những 
biểu hiện sizeof c sizeof * c (kích thước của toàn bộ mảng chia cho kích thước 
của phần tử đầu tiên) như thế với một thủ thuật mà không cần phải được thay đổi 
nếu các mảng thay đổi kích cỡ : 
 for(int i = 0; i < sizeof c / sizeof *c; i++) 
 c[i]++; 
 Bởi vì cấu trúc cũng được tập hợp, chúng có thể được khởi tạo trong một 
phong cách tương tự. Bởi vì một C-style struct có tất cả các thành viên public 
của mình, chúng có thể được gán giá trị trực tiếp: 
struct X 
{ 
 int i; 
 float f; 
 char c; 
}; 
X x1 = { 1, 2.2, 'c' }; 
 Nếu bạn có một mảng các đối tượng như vậy, bạn có thể khởi tạo chúng 
bằng cách sử dụng một bộ lồng nhau của các dấu ngoặc nhọn cho từng đối tượng: 
 X x2[3] = { {1, 1.1, 'a'}, {2, 2.2, 'b'} }; 
 Ở đây, đối tượng thứ ba là khởi tạo là “không”. 
Nếu bất kỳ thành viên dữ liệu là private (mà thường là trường hợp cho một 
lớp được thiết kế tốt trong C + +), hoặc thậm chí nếu tất cả mọi thứ của public, 
nhưng có một constructor, những thứ có khác nhau. Trong ví dụ trên, phần tử 
khởi đầu được gán trực tiếp vào các phần tử của tập hợp, nhưng constructor là 
một cách để buộc khởi tạo xảy ra một cách chính thức. Ở đây, các constructor 
phải được gọi là thực hiện khởi t ạo. Vì vậy, nếu bạn có một cấu trúc trông như 
thế này: 
struct Y 
{ 
 float f; 
 int i; 
 Y(int a); 
}; 
Bạn phải chỉ ra các cuộc gọi constructor. Phương pháp tốt nhất là rõ ràng 
như sau: 
Y y1[] = { Y(1), Y(2), Y(3) }; 
Bạn nhận ba đối tượng và ba lời gọi constructor. Bất cứ lúc nào bạn có 
một constructor, cho dù đó là một cấu trúc với tất cả các thành viên công cộng 
hoặc một lớp với các dữ liệu cá nhân thành viên, tất cả các khởi phải qua 
constructor, thậm chí nếu bạn đang sử dụng khởi tạo tổng hợp. 
Dưới đây là một ví dụ thứ hai về constructor nhiều tham số: 
#include 
using namespace std; 
class Z 
{ 
 int i, j; 
 public: 
 Z(int ii, int jj); 
 void print(); 
}; 
Z::Z(int ii, int jj) { 
 i = ii; 
 j = jj; 
} 
void Z::print() { 
 cout << "i = " << i << ", j = " << j << 
endl; 
} 
int main() { 
 Z zz[] = { Z(1,2), Z(3,4), Z(5,6), Z(7,8) 
}; 
 for(int i = 0; i < sizeof zz / sizeof *zz; 
i++) 
 zz[i].print(); 
} ///:~ 
Chú ý rằng nó giống như một constructor được gọi là rõ ràng cho từng đối 
tượng trong mảng đó. 
VII.Khởi tạo mặc định 
Một sự ngầm định constructor có thể được gọi là không có đối số. Một 
constructor mặc định được sử dụng để tạo ra một đối tượng “vanilla”, nhưng nó 
cũng rất quan trọng khi trình biên dịch được nói để tạo ra một đối tượng nhưng 
không đưa ra bất kỳ chi tiết nào. Ví dụ, nếu bạn đi struct Y được xác định trước 
đó và sử dụng nó trong một định nghĩa như thế này: 
 Y y2[2] = { Y(1) }; 
Trình biên dịch sẽ than phiền rằng nó không thể tìm thấy một constructor 
ngầm định. Đối tượng thứ hai trong mảng đó muốn được tạo ra không có đối số, 
và đó là nơi trình biên dịch sẽ cho một constructor ngầm định. Trong thực tế, nếu 
bạn chỉ cần định nghĩa một mảng các đối tượng Y, 
 Y y3[7]; 
 Trình biên dịch sẽ than phiền vì nó phải có một constructor ngầm định để 
khởi tạo mọi đối tượng trong mảng đó. 
Cùng một vấn đề xảy ra nếu bạn tạo một đối tượng cá nhân như thế này: 
 Y y4; 
Hãy nhớ rằng, nếu bạn có một constructor, trình biên dịch bảo đảm cho 
việc khởi tạo luôn luôn xảy ra, bất kể tình hình. 
Constructor ngầm định rất quan trọng đến nỗi nếu (và chỉ nếu) không có 
constructor cho một cấu trúc (struct hay lớp), trình biên dịch sẽ tự động tạo ra 
một cho bạn. Đây la công việc như vậy: 
class V 
{ 
 int i; // private 
}; // No constructor 
int main() 
{ 
 V v, v2[10]; 
} ///:~ 
 Nếu bất kỳ nhà thầu được định nghĩa, tuy nhiên, và không có constructor 
mặc định, các trường hợp của V ở trên sẽ tạo ra biên dịch lỗi. 
 Bạn có thể nghĩ rằng trình biên dịch, tổng hợp construct nên làm một số 
khởi tạo thông minh, như đặt tất cả các bộ nhớ cho đối tượng về không. Nhưng 
nó không - mà có thể gắn thêm trên không nhưng có thể nằm ngoài kiểm soát của 
lập trình viên. Nếu bạn muốn bộ nhớ để khởi tạo về không, bạn phải làm điều đó 
cho mình bằng cách viết các constructor mặc định một cách rõ ràng. 
 Mặc dù trình biên dịch sẽ tạo ra một constructor mặc định cho bạn, hành vi 
của trình biên dịch, tổng hợp construct là rất hiếm những gì bạn muốn. Bạn nên 
xử lý tính năng này như là một mạng lưới an toàn, nhưng ít sử dụng nó. Nói 
chung, bạn nên xác định nhà thầu của bạn một cách rõ ràng và không cho phép 
trình biên dịch để làm điều đó cho bạn. 
VIII.Tổng kết 
- Những kĩ thuật tỉ mỉ của C++ đã gợi ý cho bạn về việc khởi tạo và hủy một 
đối tượng một cách an toàn trong ngôn ngữ này.Theo Stroustrup, tác giả 
của ngôn ngữ C++, đã tìm hiểu rất kĩ về C, để nhận thấy rằng C có rất 
nhiều vấn đề trong việc khởi tạo và hủy đối tượng. Những lỗi này có thể 
gây ra những hậu quả nghiêm trọng, rất khó nhận ra, kể cả việc khởi tạo và 
hủy một biến. Constructor và destructor cho phép bạn kiểm soát an toàn 
hơn. 
- Việc khởi tạo và hủy còn bao gồm cả việc khởi tạo tập hợp, như tiếp thêm 
nguồn cảm hứng. Nó ngăn bạn làm cho những sai lầm khi khởi tạo các tập 
hợp và làm cho mã của bạn gọn gàng hơn. 
Bởi vậy, có thể nói rằng, an toàn trong viết code là một kết quả lớn của 
C++. Và như đã tìm hiểu ở trên, khởi tạo và hủy cũng an toàn như vậy. 
IX. Bài tập 
1. Viết một class, in ra một vài thứ, đẻ cho biết một hàm khởi tạo đã được 
gọi. 
2. Thêm vào một hàm hủy, trong đó có lệnh in ra cho biết hàm hủy đã được 
gọi. 
3. Sửa class đó, thêm vào một biến int, rồi viết hàm khởi tạo có tham số, rồi 
gán cho biến đó. 
4. Chứng minh rằng hàm hủy vẫn được gọi khi bị lệnh goto nhảy qua. 
5. Viết ra hai vòng lặp for, vòng thứ nhất khai báo một biến đếm ở ngoài 
vòng, vòng thứ hai khai báo một biến đếm ngay trong cấu trúc điều khiển 
của vòng lặp, theo dõi trình biên dịch xử lí thế nào ? 
6. Sử dụng khởi tạo tập hợp để tạo ra một mảng double nhưng không cung 
cấp hết giá trị cho các phần tử. In ra phần tử của mảng, sử dụng sizeof để 
tính kích thước mảng. Bây giờ tạo một mảng sử dụng khởi tạo tập hợp và 
tính kích thước tự động. 
7. Sử dụng khởi tạo mảng để tạo ra một mảng các chuỗi. Tạo ra một Stack 
giữ các chuỗi này, sau đó dùng pop() để xuất các chuỗi này ra. 
8. Tạo ra một class không có hàm khởi tạo, chứng minh rằng bạn có thể tạo 
một class với hàm khởi tạo mặc định. Bây giờ tạo một hàm khởi tạo có 
tham số, rồi thử lại. Giải thích vấn đề. 
Hết 
Nguyễn Công Huy – 08520148 
Nguyễn Trung Kiên – 08520184 

File đính kèm:

  • pdfLập trình hướng đối tượng - Sự khởi tạo và hủy.pdf