Lập trình hướng đối tượng với C++ - Chương 6: Khởi tạo và hủy
Tập hợp thì nghe như là một nhóm các thứ cùng kết nối với nhau. Struct và class là tập hợp
được định nghĩa bằng cách pha trộn các kiểu dữ liệu. Một mảng là tập hợp có cùng 1 kiểu dữ
liệu.
Cài đặt tập hợp thì thường thiên về việc ước lượng thường sai sót và thường nhàm chán. Khởi
tạo tập hợp trong C++ thì phải đảm bảo chắc chắn. Khi bạn tạo tập hợp đối tượng thì cần phải
xác định công việc cần làm và việc cài đặt với việc xóa thì phải được xóa bởi trình biên dịch.
Sự phân công thì cần nhiều thứ, nó phụ thuộc vào kiểu kết hợp của tập hợp, do đó bạn cần
phải chia nhỏ nó ra.
. Nhiều hàm con khơng chỉ hữu dụng mà cịn dễ tìm ra những lỗi của chương trình. Cấp phát vùng nhớ Một biến có thể được định nghĩa tại bất cứ nơi nào trong khối (khối lệnh của chương trình). Và dường như là vùng nhớ dành cho biến thì không được định nghĩa cho đến khi nó được định nghĩa. Đúng hơn là trình biên dịch sẽ thực hiện tiếp theo sau khi C cấp phát mọi vùng nhớ cho khối nhằm làm tăng thêm phạm vi của khối. Nó thì không có vấn đề. Một lập trình viên thì sẽ không thể nào truy cập được đến vùng nhớ cho đến khi vùng nhớ được xác định. Mặc dù vùng nhớ đã được cấp phát lúc bắt đầu khối lệnh nhưng mà hàm constructor sẽ không được gọi cho đến khi tới nơi trong khối lệnh mà biến đã được định nghĩa. Trình biên dịch sẽ kiểm tra để chắc chắn là các tham số được định nghĩa trước khi khởi tạo. Chúng ta có thể xét ví dụ sau để làm sáng tỏ hơn Vd: class X { public: X(); }; X::X() {} void f(int i) { if(i < 10) { //! goto jump1; // Error: goto bypasses init } X x1; // Constructor được gọi ở đây jump1: switch(i) { case 1 : X x2; // Constructor break; //! case 2 : // Error: case bypasses init X x3; // Constructor called here break; } } int main() { f(9); f(11); }///:~ Trong đoạn code ỡ trên thì lệnh goto có khả năng nhảy xuyên qua đoạn code gọi hàm constructor. Do đó thì nếu như tham số đó được sử dụng thì trình biên dịch sẽ xuât ra 1 dòng lỗi vì hàm constructor đã không được gọi lúc nó được định nghĩa. Do đó cân phải chú ý đến việc tạo và gán giá trị. Và việc khởi tạo vùng nhớ thì được di chuyển đi xuống bằng cách sử dụng stack của trình biên dịch. Stash với constructor và destructor Cấu trúc của 1 stash //: C06:Stash2.h // With constructors & destructors #ifndef STASH2_H #define STASH2_H class Stash { int size; // kích thước của mỗi phần tử int quantity; //số phần tử int next; // không gian trống tiếp theo unsigned char* storage; void inflate(int increase); public: Stash(int size); ~Stash(); int add(void* element); void* fetch(int index); int count(); }; #endif // STASH2_H ///:~ #include "Stash2.h" #include "../require.h" #include #include using namespace std; const int increment = 100; //gán hằng số cho biến increment Stash::Stash(int sz) //hàm khởi tạo constructor { size = sz; quantity = 0; storage = 0; next = 0; } int Stash::add(void* element) { if(next >= quantity) // đủ dữ liệu bên trái? inflate(increment); startBytes = next * size; unsigned char* e = (unsigned char*)element; for(int i = 0; i < size; i++) storage[startBytes + i] = e[i]; next++; return(next - 1); // Index number } void* Stash::fetch(int index) { require (0 <= index, "Stash::fetch (-)index"); if(index >= next) return 0; return &(storage[index * size]); } int Stash::count() { return next; } void Stash::inflate(int increase) { require(increase > 0, "Stash::inflate zero or negative increase"); int newQuantity = quantity + increase; int newBytes = newQuantity * size; int oldBytes = quantity * size; unsigned char* b = new unsigned char[newBytes]; for(int i = 0; i < oldBytes; i++) b[i] = storage[i]; delete [](storage storage = b; quantity = newQuantity; } Stash::~Stash() { if(storage != 0) { cout << "freeing storage" << endl; delete []storage; } } ///:~ Hàm chính #include "Stash2.h" #include "../require.h" #include #include #include using namespace std; int main() { Stash intStash(sizeof(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"); assure(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 hàm constructor và destructor Code dùng cho stack File header // 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(); }; #endif // STACK3_H ///:~ Trong class link // Constructors/destructors #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, "Stack not empty"); } ///:~ Trong hàm main #include "Stack3.h" #include "../require.h" #include #include #include using namespace std; int main(int argc, char* argv[]) { require Args(argc, 1); ifstream in(argv[1]); assure(in, argv[1]); Stack textlines; string line; while(getline(in, line)) textlines.push(new string(line)); string* s; while((s = (string*)textlines.pop()) != 0) { cout << *s << endl; delete s; } } ///:~ Khởi tạo tập hợp Tập hợp thì nghe như là một nhóm các thứ cùng kết nối với nhau. Struct và class là tập hợp được định nghĩa bằng cách pha trộn các kiểu dữ liệu. Một mảng là tập hợp có cùng 1 kiểu dữ liệu. Cài đặt tập hợp thì thường thiên về việc ước lượng thường sai sót và thường nhàm chán. Khởi tạo tập hợp trong C++ thì phải đảm bảo chắc chắn. Khi bạn tạo tập hợp đối tượng thì cần phải xác định công việc cần làm và việc cài đặt với việc xóa thì phải được xóa bởi trình biên dịch. Sự phân công thì cần nhiều thứ, nó phụ thuộc vào kiểu kết hợp của tập hợp, do đó bạn cần phải chia nhỏ nó ra. Việc xây dựng 1 mảng thì đơn giản Vd: int a[5] = { 1, 2, 3, 4, 5 }; Nếu bạn thêm vào nhiều phần tử hơn so với định nghĩa ban đầu thì trình biên dịch sẽ báo lỗi cho bạn Vd: trong mảng a ở trên có 5 phần tử nếu bạn thêm vào a[6]=0; thì sẽ có lỗi xảy ra. Phần tử đầu tiên của mảng là phần tử 0 Vd:a[0] là phần tử đầu tiên của mảng. Cấu trúc là một tập hợp do đó việc khởi tạo phải theo 1 khuôn mẫu. Bởi thành phần của kiểu struct trong C là những “public" nên việc phân công sẽ được ngay lập tức struct X { int i; float f; char c; }; X x1 = { 1, 2.2, 'c' }; và nếu mảng của bạn là 1 mảng các đối tượng thì bạn sẽ phải khởi tạo bằng cách cài đặt cho mỗi đối tượng : vd: X x2[3] = { {1, 1.1, 'a'}, {2, 2.2, 'b'} }; trong đó thì thành phần thứ 3 sẽ được mặt đinh là 0. Nhưng trong đối tượng thì lại tồn tại kiểu “private” nên sẽ không đơn giản như vậy. Ơû đây thì hàm khởi tạo phải được gọi nhằm thực hiện việc khởi tạo. Vd: struct Y { float f; int i; Y(int a); }; Với kiểu như vậy thì bạn sẽ phải chỉ ra việc gọi hàm constructor và cách tiếp cận tốt nhất là như sau: Y y1[] = { Y(1), Y(2), Y(3) }; Bạn sẽ có 3 đối tượng và 3 constructor được gọi. Và mọi việc khởi tạo sẽ phải xuyên suốt trong hàm constructor. Sau đây là 1 ví dụ về hàm khởi tạo tập hợp Vd: #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(); } ///:~ Hàm constructor mặc định (default constructor) Default constructor là một hàm mặc định có thể gọi mà không cần phải có bất kỳ tham số nào. Trình biên dịch sẽ báo lỗi nếu như không tìm thấy hàm constructor mặc định. default constructor thường được sử dụng khi bạn muốn gọi đối tượng nhưng không muốn đưa ra bất kỳ 1 chi tiết. Vd: nếu bạn muốn dùng struct Y đã được định nghĩa trước và dùng nó trong như sau: Y y2[2] = { Y(1) }; Thì trình biên dịch sẽ báo là không thể nào tìm được hàm constructor default. Ngoài ra nếu bạn muốn sử dụng mảng mà không cần phải có tham số. Nếu bạn đã định nghĩa 1 hàm constructor thì trình biên dịch sẽ cho phép hàm constructor luôn được diễn ra. Nếu bạn không tạo 1 constructor thì trình biên dịch sẽ tự động tao cho bạn 1 constructor. Mặc dù constructor default được trình biên dịch tạo tự động nhưng tốt nhất vẫn là bạn tạo 1 hàm constructor cho riêng chính mình và nhớ là nên định nghĩa nó 1 cách dứt khoát và rõ ràng. Tổng kết Có vẻ như C++ đưa ra cho bạn những cơ cấu phức tạp, nó sẽ đưa ra cho bạn những lời hướng dẫn về những vấn đề then chốt trong việc khởi tạo và xóa bỏ. Như là một sự cố gắn của việc thiết kế ngôn ngữ C++, người thiết kế đã tăng hiệu suất của C một cách đáng kể trong việc làm giảm những lỗi của chương trình trong việc khai báo không đúng chổ. Đó là 1 lỗi kỹ thuật khó tìm.
File đính kèm:
- Lập trình hướng đối tượng với C++ - Chương 6 Khởi tạo và hủy.pdf