PDA

View Full Version : [Tutor]Tổng kết OOP với C++(Part II)



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).

xp2002_
08-10-2002, 20:40
Chào bạn, mình nghĩ OOP trong C++ chưa tổng kết được đâu bạn ạ. Phía trước vẫn còn nhiều lắm. Vậy mình xin được viết tiếp OOP của bạn nhé.

Vẫn còn đó, sự kế thừa


Chắc các bạn cũng đã từng lướt qua trang này với những tiêu đề OOP phần X rồi chứ, và chắc các bạn cũng đã nắm được phần nào khái niệm về OOP, nên tôi xin đi ngay vào vấn đề chính là sự kế thừa trong C++.
Khi bạn tạo một lớp với những chức năng, thuộc tính riêng. Ví dụ bạn tạo một lớp xe với các thuộc tính:


class xe {
char *nhanhieu, *hangsanxuat;
int namsanxuat, mausac;

public:
xe(); {...}
xe(...); {...}
...
};

Và sau đó bạn cần tạo những lớp xe khác. Ví dụ: xe máy (có thêm thuộc tính: số máy, số sườn, số khung,...), xe đạp (thêm: dande, thangdia,...), xe bus(maytang,...),... nhưng chung quy chúng vẫn có những thuộc tính mà lớp xe cung cấp, khi đó bạn chỉ cần tạo một lớp xe mới và yêu cầu thừa hưởng những thuộc tính mà lớp xe củ đã có.

class xemay: private xe {
int somay, sosuon, sokhung;

public:
xemay() : xe(); {...}
xemay(...) : xe(...); {...}
...
};

Nhưng khi đó lớp xemay sẽ không thừa hưởng được những thuộc tính private của lớp xe, nên khi đó ta hãy để những thuộc tính đó trong phần public và như thế bên ngoài ta cũng có thể truy xuất được các thành phần đó dẫn đến OOP của chúng ta sụp đổ hoàn toàn. Và để giải quyết vấn đề này ta hãy cầu cứu đến thuộc tính protected. protected là kẻ trung gian giữa private và public, nó làm cho những thuộc tính nó chi phối sẽ được thừa hưởng cho lớp con cháu mà "người ngoài dòng họ" không mò đến được, một công cụ vô cùng hữu hiệu. Nên khi đó chúng ta sẽ khai báo lại lớp xe và những thuộc tính cho con cháu kế thừa sẽ được đưa vào protected. Và khi ở lớp con cháu hãy khai báo kế thừa với dạng protected (những cái gì thừa hưởng từ lớp cha sẽ là thuộc tính protected trong lớp con hiện tại).
Đặc biệt là trong C++ cho phép có thể thừa kế từ nhiều lớp khác nhau (thừa kế bội). Điều này xin không được mở ra theo hướng này. Bạn có thể nghiên cứu thêm trong những sách viết về OOP trong C++ hiện đang có rất nhiều trên thị trường.

>>còn tiếp

wheremylove?
08-10-2002, 22:18
OK, nhưng mình đã kết thúc đâu, tưởng không ai thèm đọc nữa nên chưa viết tiếp. Theo bạn thì OOP chưa kết thúc, tất nhiên, mình chỉ tổng kết những cái đã học để dễ xem lại khi cần mà. Bạn viết tiếp đi nhé, còn nhiều lắm.

danceswithwolves
09-10-2002, 11:20
viva u2...

làm ơn dùng tag [ code ] code here [ /code ]... tớ năn nỉ các bác đấy. Khó đọc thì làm sao những người mới học tiếp thu được ?

ntsasng
24-05-2010, 22:34
public:
xe(); {...}
xe(...); {...}

public:
xemay() : xe(); {...}
xemay(...) : xe(...); {...}
...

Thế này đã đúng chưa vậy :-/