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,
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:
- Lập trình hướng đối tượng - Sự khởi tạo và hủy.pdf