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.

pdf21 trang | Chuyên mục: Lập Trình Hướng Đối Tượng | Chia sẻ: dkS00TYs | Lượt xem: 2003 | Lượt tải: 1download
Tóm tắt nội dung Lập trình hướng đối tượng với C++ - Chương 6: 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ề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:

  • pdfLập trình hướng đối tượng với C++ - Chương 6 Khởi tạo và hủy.pdf