Lập trình hướng đối tượng - Chương 6: Khởi tạo và Cleanup
Nó không chỉ cung cấp một sự thống nhất trong thành phần thư viện, mà còn che dấu tên các hàm trong tên của lớp. Trong chương 5, điều kiển truy nhập (thực hiện ẩn) được đưa vào. Nó cung cấp cách thiết kế để xác định ranh giới rõ ràng những gì lập trình viên có thể thao tác và những gì bị giới hạn. Có nghĩa là trong cơ chế hoạt động của một kiểu dữ liệu là dưới sự điều khiển và mô tả của thiết kế lớp, và nó phải rõ ràng để các lập trình viên của khách hàng có thể hiểu được.
Đóng gói và kiểm soát truy cập là bước tiến quan trọng việc cải thiện thư viên. Khá niệm “kiểu dữ liệu mới” cung cấp những phương pháp tốt hơn kiểu dữ liệu đã xây dựng trong C. Trình biên dịch C++ có thể cung cấp ngay kiểu kiểm tra an toàn cho dữ liệu và như vậy bảo đảm một mức độ an toàn khi dữ liệu đó đang được sử dụng.
các từ khoá mới, được giới thiệu trong Chương 4), nhưng nó cũng gọn gàng khởi gợi ý cho các liên kết đó. Bạn có thể thắc mắc tại sao các destructor cho liên kết không làm bất cứ điều gì - Đặc biệt, tại sao nó không xóa các con trỏ dữ liệu? Có hai vấn đề. Trong Chương 4, nơi Stack được giới thiệu, nó đã chỉ ra rằng bạn có thể không đúng cách xóa một con trỏ void nếu nó điểm đến một đối tượng (khẳng định này sẽ được chứng minh ở Chương 13). Nhưng ngoài ra, nếu liên kết destructor xóa con trỏ dữ liệu, pop() chắc chắn trỏ tới con trỏ của một đối tượng bị xóa, chắc chắn sẽ có lỗi. Điều này đôi khi được gọi là vấn đề quyền sở hữu: những liên kết và vì vậy các Stack chỉ giữ con trỏ, nhưng không chịu trách nhiệm làm sạch chúng. Điều này có nghĩa là bạn phải cẩn thận và biết biết được trách nghiệm thuộc về gì. Ví dụ, nếu bạn không pop () và xóa tất cả các con trỏ trên Stack, chúng sẽ không được làm sạch tự động bởi Stack's destructor. Điều này có thể là một vấn đề xấu và dẫn đến rò rỉ bộ nhớ, do đó, biết ai có trách nhiệm làm cho sự khác biệt giữa một chương trình thành công và một chiếc xe đẩy, đó là tại sao Strack::~Strack()in một thông báo nếu đối tượng strack không có sản phẩm nào khi tieu hủy. Bởi vì việc giao và dọn dẹp 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 tiềm ẩn - bạn không thấy nó xảy ra trong chương trình thử nghiệm, mặc dù bạn đang chịu trách nhiệm xóa các con trỏ mà quay lại từ cửa sổ pop (): //: C06:Stack3Test.cpp //{L} Stack3 //{T} Stack3Test.cpp // Constructors/destructors #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 popped và xóa bỏ,nhưng nếu không, bạn sẽ nhận được một yêu cầu () thông báo có nghĩa là rò rỉ bộ nhớ. Tổng hợp khởi tạo: Tổng hợp là cái gì đó nghe như là: một bó được kết lại cùng 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. Một mảng là một tổng hợp của một kiểu duy nhất. Tổng hợp có thể gây ra lỗi và dài dòng. Khởi tạo tổng hợp C++ thì an toàn hơn. Khi bạn tạo một đối tượng đó là một khối, tất cả các bạn phải làm là tạo một phân công, và khởi tạo sẽ được chăm sóc bởi trình biên dịch. Điều phân cồng này sẽ thêm vào một số chuyện, tùy thuộc vào kiểu của khối bạn đang xử lý, nhưng trong tất cả các trường hợp các yếu tố trong nhiệm vụ phải được bao quanh bởi ngoặc nhọn. Một mảng được xây dưng kiểu này khá đơn giản: int a[5] = { 1, 2, 3, 4, 5 }; Nếu bạn cố gắng cung cấp cho initializers nhiều hơn những yếu tố mảng, các 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 cho ít initializers? Ví dụ: int b[6] = {0}; Ở đây, trình biên dịch sẽ sử dụng initializer đầu tiên cho thành phần đầu tiên của mảng, và sau đó sử dụng số 0 cho tất cả các yếu tố mà không initializers. Thông báo khởi tạo việc này không xảy ra nếu bạn xác định một mảng mà không có một danh sách các initializers. Vì vậy, các diễn đạt 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.) Cách viết tắt thứ 2 cho mảng 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ố initializers: int c[] = { 1, 2, 3, 4 }; Bây giờ nếu bạn quyết định thêm một phần tử vào mảng đó, bạn chỉ cần thêm một initializer. Nếu bạn có thể đặt mã của bạn lên do đó, nó cần phải được thay đổi trong 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 đó? Biểu hiện sizeof c sizeof * c (kích thước của toàn bộ mảng chia kích thước của các yếu tố đầu tiên) như thế với một thủ thuật không cần phải được thay đổi nếu kích thước mảng thay đổi: 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 tương tự như cách đó?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 phân công 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 không. Nếu bất kỳ thành phần dữ liệu cá nhân (mà thường là trường hợp cho một lớp học đượ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ó constructor, những thứ khác nhau. Trong ví dụ trên, các initializers được phân công trực tiếp vào các yếu tố của khối, nhưng constructor có cách để buộc khởi tạo xảy ra thông qua một giao diện chính thức. Ở đây, các constructor phải được gọi là để thực hiện việc khởi tạo. Vì vậy, nếu bạn có một cấu trúc mà 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ất như sau: Y y1[] = { Y(1), Y(2), Y(3) }; Bạn nhận ba đối tượng và ba cuộc 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 tạo phải được thông qua việc xây dựng, ngay cả khi bạn đang sử dụng tổng hợp khởi tạo. Dưới đây là một ví dụ thứ hai đối số hiển thị nhiều constructor: //: C06:Multiarg.cpp // Multiple constructor arguments // with aggregate initialization #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 mỗi đối tượng trong mảng đó. Default constructs.(các hàm khởi tạo mặt định) Một hàm khởi tạo mặt định là một hàm có thể được gọi mà không cần các đối số. Một hàm khởi tạo mătj định được sử dụng để tạo ra một “đối tượng vani”, nhưng nó cũng quan trọng khi trình biên dịch bảo là tọ một đối tượng nhưng không định sẵn một cách chi tiết. Chẳng hạn, nếu bạn tạo stack Y được địng nghĩa trước. Y y2[2] = { Y(1) }; Trình biên dịch sẽ thông báo rằng nó không thể tìm hàm khởi tạo mặt định. Thứ hai, một đối tượng trong mảng muốn được tạo mà không cần cần có các đối số, và đó là chỗ trình biên dịch tìm kiếm hàm khởi tạo mặt định. Thực ra, nếu bạn định nghĩa một mảng của đối tượng Y, Y y3[7]; Trình biên dịch sẽ thông báo bởi vì phải có một hàm khởi tạo mặt định để thiết lập mỗi đối tượng trong mảng. Vấn đề xảy ra tương tự nếu bạn tạo một đối tượng như thế này: Y y4; Nhớ rằng, nếu bạn có một hàm khởi tạo, trình biên dịch bảo đảm rằng hàm khởi tạo luôn được gọi, bất chấp mọi tình huống. Hàm khởi tạo quan trọng đến nỗi mà nếu (và chỉ nếu) không có hàm khởi tạo cho cấu trúc (struct hay class), trình biên dịch sẽ tự động tạo một đối tượng. Do đó những việc này : //: C06:AutoDefaultConstructor.cpp // Automatically-generated default constructor class V { int i; // private }; // No constructor int main() { V v, v2[10]; } ///:~ Nếu bất kỳ hàm khởi tạo nào được định nghĩa, tuy nhiên, và không có hàm khởi tạo mặt định, thí dụ lớp V ở trên sẽ làm cho trình biên dịch báo lỗi. Bạn phải nghỉ rằng trình biên dịch tổng hơp hàm khơi tạo để làm cho quá trình khởi tạo dễ dàng hơn, giống như tất cả thiết lập cả bộ nhớ cho đối tượng là không. Nhưng nó không thêm vượt quá giới hạn nhưng sẽ bị ở ngoài chương trình điều khiển. nếu bạn muốn bộ nhớ được khởi tạo là không, bạn phải cho nó tự động khởi tạo bằng cách viết hàm khởi tạo mặt định rõ ràng. Mặt dù trình biên dịch sẽ tạo một hàm khởi tạo mặt định cho bạn, the behavior of the compiler-synthesized constructor is rarely what you want……………………………………………………………… Bạn nên xem xét điểm đặc biệt như là một mạng an toàn, nhưng việc sử dụng nó thì rất ít. Nhìn chung bạn nên định nghĩa các hàm khởi tạo rõ ràng và không cho phép trình biên dịch làm nó thay bạn. Tóm tắt Bề ngoài phức tạp của cơ cấu của C++ cho bạn những điều về khởi tạo và dọn dẹp hết sức quan trọng của ngôn ngữ lập trình. Môt trong những lời nhận xét đầu tiên, bạn làm được tạo ra trong C là phân chia ý nghĩa của chương trình Bài tập Viết một class gọi Simple với một hàm khởi tạo để xuất ra những điều bạn nói để nó được gọi. Trong hàm main() tạo một đối tượng của lớp. Thêm hàm hủy vào bài tập để xuất ra thông báo rằng nó đã được gọi. Sửa bài tập 2 để nội dung của lớp có có thành phần kiểu int. Chỉnh sửa hàm khởi tạo để đối số kiểu int chứa trong lớp. Cả hàm khởi tạo và hàm hủy sẽ xuất ra giá trị int, bạn cso thể thấy đối tượng khi chúng được tạo và hủy. Chứng minh hàm hủy vẫn được gọi khi hàm goto được dùng để nhảy ra khỏi vòng lặp. Viết hai vòng lặp for để xuất ra các giá trị từ 0 đến 10. Đầu tiên định nghĩa vòng lặp ###########counter trước vòng lặp for, và thứ hai, định nghĩa vòng lặp #########counter trong con trỏ biểu thức của vòng lặp for. Trong phần hai của bài tập này ……………………………………………….. Sửa đổi handler.h, handle.cpp và usehandle.cpp trong các file ở cuối chương 5 để sủ dụng hàm khởi tạo và hàm hàm hủy. #############Sử dụng khởi tạo một khối để tạo một mảng kiểu double và chỉ rõ kích thước cảu mảng nhưng không cung cấp đủ phần tử của mảng. Xuất ra mảng này sử dụng sizeof xác định kích thước của mảng
File đính kèm:
- Lập trình hướng đối tượng - Chương 6 Khởi tạo và Cleanup.docx