wheremylove?
05-10-2002, 09:14
3. Hàm thiết lập (khởi tạo, constructor) và hàm huỷ (destructor), hàm thiết lập sao chép.
a. Hàm thiết lập.
Contructor được gọi tự động khi một đối tượng được khai báo. Hàm thiết lập có chức năng là khởi tạo các giá trị thành phần dữ liệu của đối tượng, xin cấp phát bộ nhớ cho các thành phần dữ liệu động (bởi toán tử new).
Ở lớp TheMan, chúng ta phải gán các dữ liệu của đối tượng bằng hàm setTheMan(...).Để các dữ liệu này được gán tự động khi tạo một đối tượng ta có thể xây dựng các hàm thiết lập cho lớp TheMan. Đặc điểm của hàm thiết lập là có tên trùng với tên lớp và không có kiểu trả về, các hàm thiết lập cũng có thể được overload hay có các tham số có giá trị ngầm định, và chúng phải có thuộc tính truy xuất là public.
Ví dụ:
#include< iostream.h>
class TheMan{
private:
char* name;
double height;
double weight;
double age;
public:
TheMan(){
name="Nguyen Hung";
age=25;
height=1.70;
weight=60;
}
TheMan(char* n,double a,double h,double w=60){
name=n;height=h; weight=w;age=a;
}
void display();
};
void TheMan::display(){
cout<<"Anh "<<name<<endl;
cout<<"Tuoi "<<age<<"\n";
cout<<"Chieu cao "<<height<<"cm\n";
cout<<"Can nang "<<weight<<"kg"<<endl;
if (age>=20)
cout<<"Da du tuoi lay vo\n";
else
cout<<"Chua duoc lay vo dau nhe. Con tre con lam"<<endl;
}
void main(){
TheMan me;//gọi contructor không tham số
me.display();
TheMan he("Phan Anh",30,1.79,70);
he.display();
}
b. Hàm huỷ destructor.
Hàm huỷ có chức năng trái ngược với hàm khởi tạo. Hàm huỷ được gọi khi đối tượng bị xoá khỏi bộ nhớ (dùng toán tử delete). Hàm huỷ và hàm thiết lập chỉ cần thiết trong các lớp có các thành phần dữ liệu động.
Lý do sử dụng hàm hủy là để "dọn rác", tức là khi chúng ta không cần sử dụng các dữ liệu nữa, ta cần xoá nó khỏi bộ nhớ. Để làm được điều đó cần phải sử dụng hàm hủy.
Đặc điểm cùa hàm huỷ là có tên bắt đầu bằng dấu "~", sau đó là tên lớp. Hàm huỷ cũng phải có thuộc tính truy xuất là public. Hàm huỷ không có tham số, và mỗi lớp chỉ có một hàm huỷ. Hàm huỷ cũng không có giá trị trả về.
Example:
#include< iostream.h>
class TheMan{
private:
char* name;
double height;
double weight;
double age;
public:
TheMan(){ //hàm constructor không tham số, hay hàm constructor ngầm định
name="Nguyen Hung";
age=25;
height=1.70;
weight=60;
}
TheMan(char* n,double a,double h,double w=60){ //hàm constructor với bốn tham số, trong đó có một tham số có giá trị ngầm định
name=n;height=h; weight=w;age=a;
}
~TheMan(){ //hàm destructor
cout<<"Goodbye! Mr "<<name<<" \n";
}
void display();
};
void TheMan::display(){
cout<<"Anh "<<name<<endl;
cout<<"Tuoi "<<age<<"\n";
cout<<"Chieu cao "<<height<<"cm\n";
cout<<"Can nang "<<weight<<"kg"<<endl;
if (age>=20)
cout<<"Da du tuoi lay vo\n";
else
cout<<"Chua duoc lay vo dau nhe. Con tre con lam"<<endl;
}
void main(){
TheMan me;//gọi contructor không tham số
me.display();
TheMan he("Phan Anh",30,1.79,70);
he.display();
}
Khi thực hiện chương trình, sau khi display ra các thông tin của hai người, chương trình sẽ tự động gọi hàm huỷ, kết quả là bạn thu được:
.......
Goodbye! Mr Phan Anh
Goodbye!Mr Nguyen Hung
Hàm huỷ được gọi khi kết thúc chương trình.
Như vậy là đối tượng nào được tạo ra sau thì lại bị huỷ bỏ trước (sinh sau chết trước) (bất công quá), lý do (theo tớ nghĩ) là đối tượng nào được tạo ra trước nó sẽ ở địa chỉ sau cùng của phần bộ nhớ mà các đối tượng đó chiếm giữ, đối tượng tạo ra sau sẽ ở trên cùng, vì vậy khi hàm huỷ thực hiện nó phải lôi từ phần tử đầu tiên đến phần tử sau cùng.
Nếu bạn sử dụng toán tử new để tạo một đối tượng mới, ví dụ:
TheMan *me;//khai báo con trỏ đối tượng
me=new TheMan("Nguyen Hung",23,1.78,67);
thì bạn phải gọi hàm huỷ một cách rõ ràng bằng toán tử delete:
delete me;
Câu lệnh này sẽ gọi hàm destructor.
Chú ý là nếu bạn sử dụng khai báo con trỏ đối tượng khi truy nhập đến các thành phần của lớp bạn phải sử dụng toán tử "->" thay cho toán tử ".". Ví dzụ:
me->display();
Khi sử dụng delete bạn có thể xoá bất kỳ đối tượng nào trước tuỳ thích vì đây là các dữ liệu động. Tất nhiên khi bạn đã delete nó thì bạn không thể gọi nó được nữa.
Ví dụ sử dụng hàm huỷ trong lớp có dữ liệu động.
#include<iostream.h>
class vector{
static int N;
int* v;// thành ph?n d? li?u d?ng
public:
vector();
vector(vector&);
~vector();
void display();
};
int vector::N=3;
vector::vector(){
v=new int[N];
for(int i=0;i<N;i++){
v[i]=i;
}
}
vector::~vector(){
cout<<"Giai phong \n";
delete v;
}
void vector::display(){
int i;
cout<<"So chieu:"<<N<<endl;
for(i=0;i<N;i++)
cout<<v[i]<<" ";
cout<<endl;
}
void main(){
vector v1;
v1.display();
vector *v2;
v2=new vector();
v2->display();
delete v2;
}
c. Hàm thiết lập sao chép.
Giả sử trong lớp TheMan , bạn muốn tạo một đối tượng theother giống như đối tượng he, bạn có thể làm như sau:
//Tạo đối tượng theother và gán theother bằng me
TheMan theother;
theother=he;
Hai câu lệnh trên chỉ đơn thuần là phép gán, không liên quan gì đến hàm thiết lập sao chép.
Hoặc bạn sử dụng hàm thiết lập sao chép như sau:
TheMan theother=me;//câu lệnh này sử dụng hàm thiết lập sao chép ngầm định.
Điều này với lớp TheMan là OK, vì lớp TheMan không có các thành phần dữ liệu động. Nhưng với lớp vector trên, bạn phải xây dựng một hàm thiết lập sao chép một cách tường minh.
Hàm thiết lập sao chép có tên trùng với tên lớp và một tham số tham chiếu kiểu lớp. Ví dụ với lớp vector, hàm thiết lập sao chép là:
vector(vector& u);
Hàm thiết lập sao chép thực hiện việc sao chép nội dung từ một đối tượng sang một đối tượng khác. Và bạn chỉ cần khi lớp có các thành phần dữ liệu động, ngược lại chương trình sẽ tự động gọi hàm sao chép ngầm định.
Ví dụ hàm thiết lập sao chép:
#include<iostream.h>
class vector{
static int N;
int* v;
public:
vector();
vector(vector&);
~vector();
void display();
};
int vector::N=3;
vector::vector(){
v=new int[N];
for(int i=0;i<N;i++){
v[i]=i;
}
}
vector::vector(vector& u){
int n;
v=new int[n=u.N];
for(int i=0;i<n;i++)
v[i]=u.v[i];
}
vector::~vector(){
cout<<"Giai phong \n";
delete v;
}
void vector::display(){
int i;
cout<<"So chieu:"<<N<<endl;
for(i=0;i<N;i++)
cout<<v[i]<<" ";
cout<<endl;
}
void main(){
vector v1;
v1.display();
vector v2=v1;
v2.display();
}
Nếu bạn không xây dựng hàm thiết lập sao chép một cách tường minh trong các lớp có thành phần dữ liệu động, thì sẽ gây ra lỗi run-time (khi complie thì OK nhưng khi execute thì sẽ có lỗi).
a. Hàm thiết lập.
Contructor được gọi tự động khi một đối tượng được khai báo. Hàm thiết lập có chức năng là khởi tạo các giá trị thành phần dữ liệu của đối tượng, xin cấp phát bộ nhớ cho các thành phần dữ liệu động (bởi toán tử new).
Ở lớp TheMan, chúng ta phải gán các dữ liệu của đối tượng bằng hàm setTheMan(...).Để các dữ liệu này được gán tự động khi tạo một đối tượng ta có thể xây dựng các hàm thiết lập cho lớp TheMan. Đặc điểm của hàm thiết lập là có tên trùng với tên lớp và không có kiểu trả về, các hàm thiết lập cũng có thể được overload hay có các tham số có giá trị ngầm định, và chúng phải có thuộc tính truy xuất là public.
Ví dụ:
#include< iostream.h>
class TheMan{
private:
char* name;
double height;
double weight;
double age;
public:
TheMan(){
name="Nguyen Hung";
age=25;
height=1.70;
weight=60;
}
TheMan(char* n,double a,double h,double w=60){
name=n;height=h; weight=w;age=a;
}
void display();
};
void TheMan::display(){
cout<<"Anh "<<name<<endl;
cout<<"Tuoi "<<age<<"\n";
cout<<"Chieu cao "<<height<<"cm\n";
cout<<"Can nang "<<weight<<"kg"<<endl;
if (age>=20)
cout<<"Da du tuoi lay vo\n";
else
cout<<"Chua duoc lay vo dau nhe. Con tre con lam"<<endl;
}
void main(){
TheMan me;//gọi contructor không tham số
me.display();
TheMan he("Phan Anh",30,1.79,70);
he.display();
}
b. Hàm huỷ destructor.
Hàm huỷ có chức năng trái ngược với hàm khởi tạo. Hàm huỷ được gọi khi đối tượng bị xoá khỏi bộ nhớ (dùng toán tử delete). Hàm huỷ và hàm thiết lập chỉ cần thiết trong các lớp có các thành phần dữ liệu động.
Lý do sử dụng hàm hủy là để "dọn rác", tức là khi chúng ta không cần sử dụng các dữ liệu nữa, ta cần xoá nó khỏi bộ nhớ. Để làm được điều đó cần phải sử dụng hàm hủy.
Đặc điểm cùa hàm huỷ là có tên bắt đầu bằng dấu "~", sau đó là tên lớp. Hàm huỷ cũng phải có thuộc tính truy xuất là public. Hàm huỷ không có tham số, và mỗi lớp chỉ có một hàm huỷ. Hàm huỷ cũng không có giá trị trả về.
Example:
#include< iostream.h>
class TheMan{
private:
char* name;
double height;
double weight;
double age;
public:
TheMan(){ //hàm constructor không tham số, hay hàm constructor ngầm định
name="Nguyen Hung";
age=25;
height=1.70;
weight=60;
}
TheMan(char* n,double a,double h,double w=60){ //hàm constructor với bốn tham số, trong đó có một tham số có giá trị ngầm định
name=n;height=h; weight=w;age=a;
}
~TheMan(){ //hàm destructor
cout<<"Goodbye! Mr "<<name<<" \n";
}
void display();
};
void TheMan::display(){
cout<<"Anh "<<name<<endl;
cout<<"Tuoi "<<age<<"\n";
cout<<"Chieu cao "<<height<<"cm\n";
cout<<"Can nang "<<weight<<"kg"<<endl;
if (age>=20)
cout<<"Da du tuoi lay vo\n";
else
cout<<"Chua duoc lay vo dau nhe. Con tre con lam"<<endl;
}
void main(){
TheMan me;//gọi contructor không tham số
me.display();
TheMan he("Phan Anh",30,1.79,70);
he.display();
}
Khi thực hiện chương trình, sau khi display ra các thông tin của hai người, chương trình sẽ tự động gọi hàm huỷ, kết quả là bạn thu được:
.......
Goodbye! Mr Phan Anh
Goodbye!Mr Nguyen Hung
Hàm huỷ được gọi khi kết thúc chương trình.
Như vậy là đối tượng nào được tạo ra sau thì lại bị huỷ bỏ trước (sinh sau chết trước) (bất công quá), lý do (theo tớ nghĩ) là đối tượng nào được tạo ra trước nó sẽ ở địa chỉ sau cùng của phần bộ nhớ mà các đối tượng đó chiếm giữ, đối tượng tạo ra sau sẽ ở trên cùng, vì vậy khi hàm huỷ thực hiện nó phải lôi từ phần tử đầu tiên đến phần tử sau cùng.
Nếu bạn sử dụng toán tử new để tạo một đối tượng mới, ví dụ:
TheMan *me;//khai báo con trỏ đối tượng
me=new TheMan("Nguyen Hung",23,1.78,67);
thì bạn phải gọi hàm huỷ một cách rõ ràng bằng toán tử delete:
delete me;
Câu lệnh này sẽ gọi hàm destructor.
Chú ý là nếu bạn sử dụng khai báo con trỏ đối tượng khi truy nhập đến các thành phần của lớp bạn phải sử dụng toán tử "->" thay cho toán tử ".". Ví dzụ:
me->display();
Khi sử dụng delete bạn có thể xoá bất kỳ đối tượng nào trước tuỳ thích vì đây là các dữ liệu động. Tất nhiên khi bạn đã delete nó thì bạn không thể gọi nó được nữa.
Ví dụ sử dụng hàm huỷ trong lớp có dữ liệu động.
#include<iostream.h>
class vector{
static int N;
int* v;// thành ph?n d? li?u d?ng
public:
vector();
vector(vector&);
~vector();
void display();
};
int vector::N=3;
vector::vector(){
v=new int[N];
for(int i=0;i<N;i++){
v[i]=i;
}
}
vector::~vector(){
cout<<"Giai phong \n";
delete v;
}
void vector::display(){
int i;
cout<<"So chieu:"<<N<<endl;
for(i=0;i<N;i++)
cout<<v[i]<<" ";
cout<<endl;
}
void main(){
vector v1;
v1.display();
vector *v2;
v2=new vector();
v2->display();
delete v2;
}
c. Hàm thiết lập sao chép.
Giả sử trong lớp TheMan , bạn muốn tạo một đối tượng theother giống như đối tượng he, bạn có thể làm như sau:
//Tạo đối tượng theother và gán theother bằng me
TheMan theother;
theother=he;
Hai câu lệnh trên chỉ đơn thuần là phép gán, không liên quan gì đến hàm thiết lập sao chép.
Hoặc bạn sử dụng hàm thiết lập sao chép như sau:
TheMan theother=me;//câu lệnh này sử dụng hàm thiết lập sao chép ngầm định.
Điều này với lớp TheMan là OK, vì lớp TheMan không có các thành phần dữ liệu động. Nhưng với lớp vector trên, bạn phải xây dựng một hàm thiết lập sao chép một cách tường minh.
Hàm thiết lập sao chép có tên trùng với tên lớp và một tham số tham chiếu kiểu lớp. Ví dụ với lớp vector, hàm thiết lập sao chép là:
vector(vector& u);
Hàm thiết lập sao chép thực hiện việc sao chép nội dung từ một đối tượng sang một đối tượng khác. Và bạn chỉ cần khi lớp có các thành phần dữ liệu động, ngược lại chương trình sẽ tự động gọi hàm sao chép ngầm định.
Ví dụ hàm thiết lập sao chép:
#include<iostream.h>
class vector{
static int N;
int* v;
public:
vector();
vector(vector&);
~vector();
void display();
};
int vector::N=3;
vector::vector(){
v=new int[N];
for(int i=0;i<N;i++){
v[i]=i;
}
}
vector::vector(vector& u){
int n;
v=new int[n=u.N];
for(int i=0;i<n;i++)
v[i]=u.v[i];
}
vector::~vector(){
cout<<"Giai phong \n";
delete v;
}
void vector::display(){
int i;
cout<<"So chieu:"<<N<<endl;
for(i=0;i<N;i++)
cout<<v[i]<<" ";
cout<<endl;
}
void main(){
vector v1;
v1.display();
vector v2=v1;
v2.display();
}
Nếu bạn không xây dựng hàm thiết lập sao chép một cách tường minh trong các lớp có thành phần dữ liệu động, thì sẽ gây ra lỗi run-time (khi complie thì OK nhưng khi execute thì sẽ có lỗi).