Trình bày chương 6 trong cuốn Thinking in C++ Volum 1
Chức năng của hàm khởi tạo:
1. Khởi tạo bộ nhớ đúng cách.
2. Constructor đối số cung cấp cho bạn một
cách để bảo đảm rằng tất cả các phần của
đối tượng của bạn được khởi tạo các giá trị
thích hợp. Tránh việc trả về các giá trị
không mong muốn.
ví dụ: Tree t(12); // 12-foot tree
= 0; i < oldBytes; i++) b[i] = storage[i]; // Copy old to new delete [](storage); // Old storage storage = b; // Point to new memory quantity = newQuantity; } Stash::~Stash() { if(storage != 0) { cout << "freeing storage" << endl; delete []storage; } } ///:~ Stash với constructors và destructor Stash with constructors and destructor Bạn ó thể thấy rằng các chức năng require.h đang được sử dụng để xem lỗi lập trình, thay vì assert( ). Đầu ra của một thất bại assert( ).không phải là hữu ích như là của các chức năng require.h Stash với constructors và destructor Stash with constructors and destructor //: C06:Stash2Test.cpp //{L} Stash2 // Constru tors & destructors #include "Stash2.h" #include "../require.h" #include #include #include int main() { Stash intStash(siz of(int)); for(int i = 0; i < 100; i++) intStash.add(&i); for(int j = 0; j < intStash.count(); j++) cout << "intStash.fetch(" << j << ") = " << *(int*)intStash.fetch(j) << endl; const int bufsize = 80; Stash stringStash(sizeof(char) * bufsize); ifstream in("Stash2Test.cpp"); ass re(in, " Stash2Test.cpp"); string line; while(getline(in, line)) stringStash.add((char*)line.c_str()); int k = 0; char* cp; while((cp = (char*)stringStash.fetch(k++))!=0) cout << "stringStash.fetch(" << k << ") = " << cp << endl; } ///:~ Stack với constructors và destructor Stack with constructors and destructor Các dan sách liên kết (bên trong Stack) với constructor và destructors cho thấy cách gọn gàng constructor và làm việc với destructors mới và xóa. Đây là tiêu đề sửa đổi tập tin: //: C06:Stack3.h // With constructors/destructors #ifndef STACK3_H #define STACK3_H class Stack { struct Link { void* data; Link* next; Link(void* dat, Link* nxt); ~Link(); }* head; public: Stack(); ~Stack(); void push(void* dat); void* peek(); void* pop(); }; Stack với constructors và destructor Stack with constructors and destructor Không những Stack có một constructor và destructor, mà cò kết hợp lớp Link: //: C06:Stack3.cpp {O} // Constructors/de tructors #include "Stack3.h" #include "../require.h" using namespace std; Stack::Link::Link(void* dat, Link* nxt) { data = dat; next = nxt; } Stack::Link::~Link() { } Stack::Stack() { head = 0; } void Stack::push(void* dat) { head = new Link(dat,head); } void* Stack::peek() { require(head != 0, "Stack empty"); return head->data; } void* Stack::pop() { if(head == 0) return 0; void* result = head->data; Link* oldHead = head; head = head->next; delete oldHead; return result; } Stack::~Stack() { require(head == 0, "Sta k not empty"); } ///:~ Stack với constructors và destructor Stack with constructors and destructor Link::Link( ) nstructor chỉ đơn giản khởi data và con trỏ next, vì vậy trong Stack::push( ) có dòng Line head = new Link(dat,head); không những phân bổ một liên kết mới (sử dụng đối tượng năng động, sáng tạo với các từ khóa mới), mà còn gọn gàng khởi gợi ý cho các liên kết đó. Stack với constructors và destructor Stack with onstructors and destructor //: 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, rgv[1]); Stack textlines; tri g 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 texlines được popped xóa bỏ Nhưng nếu chúng không được, bạn sẽ nhận được require( ) Thông báo rằng có sự rò rỉ bộ nhớ. TỔNG QUAN VỀ KHỞI TẠO Aggregate initialization C + +, Aggregate initialization làm cho khởi tạo an toàn hơn nhiều. Khi bạn tạo một đối tượng mà là một tổng hợp, 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. Chuyển nhượng này có trong một số hương vị, 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 chuyển nhượng phải được bao quanh bởi các dấu ngoặc nhọn. Đối với một mảng xây dựng trong các loại này khá đơn giản: Ví dụ: int a[5] = { 1, 2, 3, 4, 5 }; TỔNG QUAN VỀ KHỞI TẠO Aggregate initialization Nếu bạn cố gắng cung cấp cho initializers hơ đó 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 cho initializers ít hơn? Ví dụ: int b[6] = {0}; TỔNG QUAN VỀ KHỞI TẠO Aggregate initialization Ở đây, trình biên dịch sẽ sử dụng initializer đầu tiên cho các phần tử mảng đầu tiên, và sau đó sử dụ số không cho tất cả các yếu tố mà không initializers. Thông báo khởi 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 initializers. 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 off-by-one trong những lỗi (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. ) A 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 initializers: int c[ ] = { 1, 2, 3, 4 }; TỔNG QUAN VỀ KHỞI TẠO Aggregate initialization 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 trick trong một cách mà không cần phải được thay đổ nếu kích thước mảng changes5: for(int i = 0; i < sizeof c / sizeof *c; i++) c[i]++; Nếu bạn có một mảng các đối tượng, bạn có thể khởi tạo 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'} }; TỔNG QUAN VỀ KHỞI TẠO Aggregate initialization * Các hàm khởi tạo phải được gọi là thực hiện. Vì vậy, nếu bạ 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à một trong những rõ ràng như sau: Y y1[] = { Y(1), Y(2), Y(3) }; TỔNG QUAN VỀ KHỞI TẠO Aggregate initialization Dưới đây là một ví dụ thứ hai đối số hiển thị nhiều hàm dựng: #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 hàm dựng được gọi là rõ ràng cho từng đối tượng trong mảng đó. CÁC CONSTRUCTOR MẶC ĐỊNH Default c structors - Một construct r mặc định là một hàm không có đối số. - Một constructor mặc định được sử dụng để tạo ra một “vanilla object”, nhưng nó cũng rất quan trọng khi nói với trình biên dịch là để tạo ra một đối tượng nhưng không đưa ra bất kỳ chi tiết. 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 mặc đị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 mặc định. CÁC CONSTRUCTOR MẶC ĐỊNH Default constructors 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ẽ khiếu nại vì nó phải có một constructor mặc định để khởi tạo mọi đối tượng trong mảng vấn đề này cùng xảy ra. nếu bạn tạo một đối tượng cá nhân như thế này: Y y4; nếu bạn có một hàm dựng, trình biên dịch bảo đảm cho xây dựng luôn luôn xảy ra, bất kể tình hình. Các constructor mặc định là quan trọng như vậy là nếu (và chỉ nếu) không có constructor cho một cấu trúc (struct hay class), trình biên dịch sẽ tự động tạo ra cho bạn CÁC CONSTRUCTOR MẶC ĐỊNH Default constructors / / Tự động ạo ra constructor mặc định class V { int i; // private }; // No constructor int main() { V v, v2[10]; } ///:~ Nên xác định constructor một cách đàng hoàn, không nên để trình biên dịch làm việc đó thay bạn. TỔNG KẾT Summary costructor và destru tors cho phép khởi tạo phù hợp và đảm bảo sạch (trình biên dịch sẽ không cho phép một đối tượng được tạo ra và bị phá hủy mà không có hàm dựng phù hợp và các cuộc gọi destructor), bạn sẽ có toàn quyền kiểm soát và an toàn khởi tạo tổng hợp thu được bao gồm trong một tĩnh mạch tương tự. -- nó ngăn bạn làm cho những sai lầm khởi điển hình với các uẩn của built-in các loại và làm cho mã của bạn thêm gọn gàng an toàn trong quá trình mã hóa. Là một vấn đề lớn trong C + +. Khởi tạo và dọn dẹp là một phần quan trọng. Exercises 1. Viết một lớp đơn giản gọi là đơn giản với một constructor à in cái gì để cho bạn biết rằng nó được gọi là. Trong main () làm cho một đối tượng của class của bạn. 2. Thêm một destructor để Exercise 1 mà in ra một thông báo cho bạn biết rằng nó được gọi là. 3. Bài tập 2 sửa đổi để các lớp có chứa một thành viên int. Sửa đổi các constructor để nó phải mất một đối số int rằng các cửa hàng trong thành viên lớp. Cả hai constructor và destructor nên in ra các giá trị int là một phần của thông điệp của họ, vì vậy bạn có thể nhìn hấy các đối ượng khi chúng được tạo ra và tiêu huỷ. 4. Chứng minh rằng destructors vẫn còn gọi là ngay cả khi goto được sử dụng để nhảy ra khỏi một vòng. The End Thực hiện bởi: Lâm T anh Cường 08520526 Nguyễn Chí Công 08520525
File đính kèm:
- Trình bày chương 6 trong cuốn Thinking in C++ Volum 1.pdf
- CStack.rar