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

