Hàm khởi tạo và hủy bỏ

Phần này không chỉ cung cấp một điểm thống

nhất đơn để vào hàm thư viện, nhưng nó cũng ẩn đi

tên của những hàm trong tên lớp.Trong chương 5 ,

quyền truy nhập sẽ được giới thiệu. Phần này cung

cấp cách thiết kế lớp để thiết lập rõ ràng ranh giới

cho sự định đoạt những người lập trình nào thì được

phép hoạt động và kết thúc phạm vi hoạt động của

mình. Nó có nghĩa là những cơ cáu nội bộ của sự

hoạt động một kiêủ dữ liệu thì dươí quyền điều khiển

và định đoạt của người thiết kế lớp, và sự rõ ràng của

nó đến nhưng người lập trình nào có thể và nên chú ý

tới.

pdf33 trang | Chuyên mục: Lập Trình Hướng Đối Tượng | Chia sẻ: dkS00TYs | Lượt xem: 2185 | Lượt tải: 0download
Tóm tắt nội dung Hàm khởi tạo và hủy bỏ, để xem tài liệu hoàn chỉnh bạn click vào nút "TẢI VỀ" ở trên
hile(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;
} ///:~
Và khi gọi hàm cleanup( ) sẽ loại bỏ giá trị vừa 
tạo,nhưng hàm hủy vẫn tiếp tục được gọi một cách tự 
động khi intStash và stringStash ra khỏi phạm vi 
hoạt động.
Trong ví dụ Stask chúng ta thấy rõ:Chỉ sử dụng kiểu 
nội tại nếu không gọi hàm hủy,nếu cố tình sao chép 
lớp đối tượng vào Stask nó có thể thi hành không 
đúng.Thư viện chuẩn của C++ có thể sao chép đối 
tượng chính xác vào nơi chứa chúng, nhưng dây là 
một quá trình diễn ra rối rắm và phức tạp.Trong 
những ví dụ stack tiếp theo ta sẽ dùng con trỏ.
STACK với hàm tạo và hàm hủy
Trong stack với hàm tạo và hàm hủy sẽ làm việc một 
cách hiệu quả với new và delete.
// 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 ///:~
Không chỉ stack dùng hàm tạo và hàm hủy mà cả lớp 
Link:
//: C06:Stack3.cpp {O}
// 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");
} ///:~
Hàm tạo Link::Link( ) khởi tạo con trỏ data và 
next vì vậy trong Stack::push( ) có khai báo :
head = new Link(dat,head);
Không chỉ cho phép tạo liên kết mới(tạo dối 
tượng với từ khóa new,đống thời nó còn khởi tạo một 
con trỏ cho liên kết này.
Tại sao hàm hủy của Link không làm mọi thứ,tại sao 
nó không delete con trỏ data?Có 2 vấn đề.Stack là 
một con trỏ, chúng ta không thể delete một con trỏ 
không có giá trị trả về nếu nó không trỏ đến bất kì đối 
tượng nào.Nhưng ngoài ra,hàm hủy Link xóa con trỏ 
data, pop()sẽ không trả về con trỏ để xóa một đối 
tượng,điều này chắc chắn sẽ gây ra sự cố.Điều này 
thỉnh thoảng xem như vấn đề của quyền sở hữu: Link 
và Stack chỉ giữ những con trỏ,nhưng nó không chịu 
trách nhiệm phải hủy chúng.Điều này có nghĩa là bạn 
phải cẩn thận.Ví dụ,nếu bạn không pop() và delete 
tất cả những con trỏ được tạo ra trên Stack,chúng sẽ 
không tự động được giải phóng bởi hàm hủy của 
Stack.Điều này có gây ra vân đề và dẫn tới việc lãng 
phí bộ nhớ,vì vậy việc hiểu biết về giải phóng đối 
tượng có thể làm chương trình hoạt động tốt hay mắc 
phải những sai sót.Điều này cũng giải thích tại sao 
Stack::~Stack( )in thông báo lỗi nếu đối tượng Stack 
không được giải phóng trước hàm hủy. 
 Bởi vì phân công và giải phóng của đốit tượng 
Link bị ẩn bên dưới Stack-nó là một bên dưới bộ 
thực thi-chúng ta không thể nhận biết được nó trong 
những chương trình,mặc dù ta chịu trách nhiệm giải 
phóng con trỏ:
//: 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ả những dòng trong 
textline đều đã được lấy ra và delete,nhưng nếu 
ngược lại,ta phải tạo một thông báo require( )rằng đó 
chính là lỗ hổng bộ nhớ.
Khởi tạo khối tập hợp
Một khối tập hợp thì giống như là một lượng nhóm 
lại với nhau.Định nghĩa này bao gồm cả khối tập hợp 
các kiểu nhóm chung với nhau, như là class và 
struct.Một mảng là một khối tập hợp nhưng cùng 
một kiểu dữ liệu.
Khi ta tạo ra một đối tượng là một khối các tập hợp, 
người biên dịch phải khởi tạo chúng một cách cẩn 
thận.Điều này tùy thuộc vào khối tập hợp ta đang đề 
cập đến, nhưng trong mọi trường hợp những thành 
phần trong đó phải được bao bọc bởi dấu{...}.Ví dụ 
cho một mảng tĩnh:
int a[5] = { 1, 2, 3, 4, 5 }; 
Nếu ta khởi tạo hơn số thành phần của mảng, 
trình biên dịch sẽ thông báo lỗi.Nếu khởi tạo ít hơn 
thì sao? Ví dụ:
int b[6] = {0};
Ở đây, trình biên dịch sẽ gán giá trị đầu tiên cho 
phần tử đầu tiên của mảng,và gán 0 cho tất cả những 
phần tử mảng còn lại.Điều này sẽ không xảy ra nếu 
ta định rõ một mảng mà không khởi tạo.Vì vậy cách 
diễn đạt ở trên là cách ngắn gọn, để tạo ra một mảng 
các số 0.
Điều lưu ý thứ hai về mảng là khả năng ”tự động 
đếm”, trong đó trình biên dịch sẽ xác định kích thước 
mảng dựa trên các số được khởi tạo:
int c[] = { 1, 2, 3, 4 };
Nếu bạn muốn tăng số phần tử của mảng, hãy 
thêm giá trị khởi tạo khác.
Nếu muốn tăng số câu lệnh, cần phải có một vài thay 
đổi, ta cần phải sửa lại một vài lỗi.Nhưng làm thế nào 
ta có thể xác định kích thước của mảng? Ta dùng 
sizeof c /sizeof *c(kích thước của mảng chia cho kích 
thước phần tử đầu tiên của mảng).
for(int i = 0; i < sizeof c / sizeof *c; i++)
c[i]++;
Vì structures đồng thời là một khối tập hợp, 
chúng có thể được khởi tạo theo những cách tương 
tự.Vì một struct trong C có những thành viên là dạng 
public, nên chúng có thể được tạo một cách 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 như các đối tượng, bạn có thể 
khởi tạo cho chúng bằng cách dùng:
X x2[3] = { {1, 1.1, 'a'}, {2, 2.2, 'b'} };
Ở ví dụ trên, đối tượng thứ 3 sẽ được gán là 0.
Nếu bất kì thành phần dữ liệu nào là private (một 
trường hợp đặc thù trong một lớp C++), hay là kiểu 
public, nhưng nó sẽ khác nếu ở đó là một hàm tạo.Ở 
ví dụ trên, khởi tạo một cách trực tiếp thành phần của 
khối đối tượng, nhưng hàm khởi tạo một cách khởi 
tạo theo một giao diện hình thức.Vì vậy nếu có một 
struct như sau:
struct Y {
float f;
int i;
Y(int a);
};
Bạn phải chỉ rõ hàm tạo gọi.Cách tốt nhất là:
Y y1[] = { Y(1), Y(2), Y(3) };
Bạn phải tạo ra ba đối tượng và lời gọi hàm 
tạo.Bất kể là một struct với thành phần public hay 
một class với thành phần dữ liệu private, tất cả những 
sự khởi tạo phải thông qua hàm tạo,nếu bạn đang sử 
dụng khởi tạo khối dữ liệu.
Ở ví dụ thứ hai cho thấy những đối số của hàm 
khởi tạo:
//: 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ư hàm tạo được gọi cho 
mỗi đối tượng trong mảng.
Hàm tạo mặc định
Một hàm tạo mặc định là một hàm tạo được gọi 
mà không có đối số.Một hàm tạo mặc định được 
dùng để tạo ra một “ đối tượng vanilla”, nhưng nó 
đồng thời cũng quan trọng khi trình biên dịch tạo ra 
một đối tượng nhưng không đưa vào bất kì chi tiết 
nào.Ví dụ, nếu bạn có struct Y được định nghĩa 
trước và dùng nó:
Y y2[2] = { Y(1) };
Trình biên dịch sẽ không thể tìm thấy một hàm 
tạo mặc định.Đối tượng thứ hai trong mảng muốn tạo 
ra sẽ không có đối số, và trình biên dịch sẽ tìm kiếm 
một hàm tạo mặc định cho nó.Thực ra, nếu bạn định 
nghĩa một cách đơn giản một mảng đối tượng Y,
Y y3[7];
Sẽ có rắc rối bởi vì nó phải có hàm tạo mặc định 
để khởi tạo mỗi đối tượng trong mảng.Vấn đề tương 
tự sẽ xảy ra nếu bạn tạo ra một đối tượng riêng như 
là:
Y y4;
Hãy nhớ, nếu bạn có một hàm tạo, trình biên 
dịch sẽ luôn tạo giá trị dù trong bất kì tình huống nào.
Hàm tạo mặc định rất quan trọng, nếu không có 
một hàm tạo cho một structure(struct hay class), 
trình biên dịch sẽ tự động tạo nó cho bạn.
// Automatically-generated default constructor
class V {
int i; // private
}; // No constructor
int main() {
V v, v2[10];
} ///:~
Nếu có một hàm tạo được định nghĩa, thì sẽ 
không có hàm tạo mặc định.
Có thể bạn nghĩ rằng trình biên dịch hàm tạo làm 
việc khôn ngoan, cũng như vùng nhớ của đối tượng 
là 0.Nhưng không phải – điều đó sẽ tạo thêm chi phí 
hoạt động cho hệ thống nhưng không phải do lập 
trình viên tạo ra.Nếu bạn muốn bộ nhớ khởi tạo giá 
trị 0, bạn phải viết một hàm mặc định rõ ràng để thực 
hiện điều này.
Mặc dù trình biên dịch sẽ tạo một hàm tạo mặc 
định cho bạn, nhưng bộ khởi tạo này thường không 
phải điều bạn muốn.Vì vậy bạn nên cẩn thận khi thực 
hiện nó.Nói chung, bạn nên định nghĩa hàm tạo một 
cách rõ ràng và không nên để trình biên dịch làm điều 
đó.
SUMARRY
C++ cung cấp cho ta cơ chế khá phức tạp nhưng 
mạnh mẽ về tầm quan trọng của việc khởi tạo và giải 
phóng.Một trong những vấn mà C++ cải tiến từ C 
chính là việc khởi tạo giá trị.Và một trong những lỗi 
khó phát hiện chính là việc giải phóng bộ nhớ không 
đúng cách.Vì hàm tạo và hàm hủy cho phép việc khởi 
tạo và giải phóng một cách thích hợp(trình biên dịch 
sẽ không cho phép một đối tượng được tạo và hủy 
nếu không gọi hàm tạo và hàm hủy thích hợp).
Khởi tạo một khối tập hợp được xây dựng theo 
cách tương tự, nó giúp bạn tránh những lỗi khi xây 
dựng kiểu dữ liệu tập hợp và làm cho code ngắn gọn 
hơn.
Việc tạo và hủy là một trong những phần quan 
trọng giúp cho chương trình thực hiện trơn tru hơn 
trong C++.

File đính kèm:

  • pdfHàm khởi tạo và hủy bỏ.pdf