Tác giả phạm hồng thái bài giảng ngôn ngữ LẬp trình c/C++



tải về 1.98 Mb.
trang46/55
Chuyển đổi dữ liệu07.07.2016
Kích1.98 Mb.
1   ...   42   43   44   45   46   47   48   49   ...   55

Lớp không có hàm tạo và hàm tạo mặc định

Nếu lớp không có hàm tạo


Chương trình dịch sẽ cung cấp một hàm tạo mặc định không đối (default), hàm này thực chất không làm gì cả. Như vậy một đối tượng tạo ra chỉ được cấp phát bộ nhớ, còn các thuộc tính của nó chưa được xác định. Chúng ta có thể kiểm chứng điều này, bằng cách chạy chương trình sau:

// Hàm tạo mặc định

#include

#include

class DIEM_DH

{

private:



int x, y, m;

public:


// Phương thức

void in() { cout <<"\n '' << x << '' ''<< y<<'' " << m ; }

};
void main()

{

DIEM_DH d;



d.in();

DIEM_DH *p;

p = new DIEM_DH[10];

clrscr();

d.in();

for (int i = 0; i<10; ++i) (p+i)->in();

getch();

}

Nếu trong lớp đã có ít nhất một hàm tạo


Khi đó hàm tạo mặc định sẽ không được phát sinh nữa và mọi câu lệnh xây dựng đối tượng mới đều sẽ gọi đến một hàm tạo của lớp. Nếu không tìm thấy hàm tạo cần gọi thì chương trình dịch sẽ báo lỗi. Điều này thường xẩy ra khi chúng ta không xây dựng hàm tạo không đối, nhưng lại sử dụng các khai báo không tham số như ví dụ sau:

#include

#include

class DIEM_DH

{

private:


int x, y, m;

public:


// Phương thức dùng để in đối tượng DIEM_DH

void in()

{

cout <<"\n'' << x << " "<< y<<" " << m ;



}

//Hàm tạo có đối

DIEM_DH::DIEM_DH(int x1, int y1 , int m1)

{

x = x1; y = y1 ; m = m1;



}

};
void main()

{

DIEM_DH d1(200, 200, 10); // Gọi tới hàm tạo có đối



DIEM_DH d2; // Gọi tới hàm tạo không đối

d2 = DIEM_DH(300, 300, 8); // Gọi tới hàm tạo có đối

d1.in();

d2.in();

getch();

}

Trong các câu lệnh trên, chỉ có câu lệnh thứ 2 trong hàm main() là bị báo lỗi. Câu lệnh này sẽ gọi tới hàm tạo không đối, mà hàm này chưa được xây dựng.



Giải pháp: có thể chọn một trong 2 giải pháp sau:

Xây dựng thêm hàm tạo không đối.

Gán giá trị mặc định cho tất cả các đối x1, y1 và m1 của hàm tạo đã xây dựng ở trên.

Theo phương án 2, chương trình có thể sửa như sau:

#include

#include

class DIEM_DH

{

private:



int x, y, m;

public:


// Phương thức dùng để in đối tượng DIEM_DH

void in() { cout <<''\n '' << x << " "<< y<<" " << m ; }

// Hàm tạo có đối , tất cả các đối đều có giá trị mặc định

DIEM_DH::DIEM_DH(int x1 = 0, int y1 = 0, int m1 = 15)

{

x = x1; y = y1; m = m1;



}

};
void main()

{

// Gọi tới hàm tạo, không dùng tham số mặc định



DIEM_DH d1(200, 200, 10);

// Gọi tới hàm tạo, dùng 3 tham số mặc định

DIEM_DH d2;

// Gọi tới hàm tạo, dùng 1 tham số mặc định

d2 = DIEM_DH(300, 300);

d1.in();

d2.in();

getch();

}

Hàm tạo sao chép (Copy Constructor)

Hàm tạo sao chép mặc định


Giả sử đã định nghĩa một lớp nào đó, ví dụ lớp PS (phân số). Khi đó:

Ta có thể dùng câu lệnh khai báo hoặc cấp phát bộ nhớ để tạo các đối tượng mới, ví dụ:

PS p1, p2 ;

PS *p = new PS ;

Ta cũng có thể dùng lệnh khai báo để tạo một đối tượng mới từ một đối tượng đã tồn tại, ví dụ:

PS u;


PS v(u) ; // Tạo v theo u

ý nghĩa của câu lệnh này như sau:

Nếu trong lớp PS chưa xây dựng hàm tạo sao chép, thì câu lệnh này sẽ gọi tới một hàm tạo sao chép mặc định (của C++). Hàm này sẽ sao chép nội dung từng bit của u vào các bit tương ứng của v. Như vậy các vùng nhớ của u và v sẽ có nội dung như nhau. Rõ ràng trong đa số các trường hợp, nếu lớp không có các thuộc tính kiểu con trỏ hay tham chiếu, thì việc dùng các hàm tạo sao chép mặc định (để tạo ra một đối tượng mới có nội dung như một đối tượng cho trước) là đủ và không cần xây dựng một hàm tạo sao chép mới.

Nếu trong lớp PS đã có hàm tạo sao chép (cách viết sẽ nói sau) thì câu lệnh: PS v(u); sẽ tạo ra đối tượng mới v, sau đó gọi tới hàm tạo sao chép để khởi gán v theo u.

Ví dụ sau sẽ minh họa cách dùng hàm tạo sao chép mặc định:

Trong chương trình đưa vào lớp PS (phân số):

Các thuộc tính gồm: t (tử số) và m (mẫu).

Trong lớp không có phương thức nào cả mà chỉ có 2 hàm bạn là các hàm toán tử nhập (>>) và xuất (<<).

Nội dung chương trình là: Dùng lệnh khai báo để tạo một đối tượng u (kiểu PS) có nội dung như đối tượng đã có d.

// Ham tao sao chep mac dinh

#include

#include

class PS

{

private:



int t, m ;

public:


friend ostream& operator<< (ostream&os, const PS &p)

{

os << " = " << p.t << "/" << p.m;



return os;

}

friend istream& operator>> (istream& is, PS &p)



{

cout << "\n Nhap tu va mau: " ;

is >> p.t >> p.m ;

return is;

}

} ;
void main()



{

PS d;


cout << "\n Nhap PS d "; cin >> d;

cout << "\n PS d " << d;

PS u(d);

cout << "\n PS u "<< u;

getch();

}

Cách xây dựng hàm tạo sao chép


Hàm tạo sao chép sử dụng một đối kiểu tham chiếu đối tượng để khởi gán cho đối tượng mới. Hàm tạo sao chép được viết theo mẫu:

Tên_lớp (const Tên_lớp & dt)

{

// Các câu lệnh dùng các thuộc tính của đối tượng dt

// để khởi gán cho các thuộc tính của đối tượng mới

}

Ví dụ có thể xây dựng hàm tạo sao chép cho lớp PS như sau:

class PS

{

private:



int t, m ;

public:


PS (const PS &p)

{

this->t = p.t ;



this->m = p.m ;

}

...



} ;

Khi nào cần xây dựng hàm tạo sao chép


Nhận xét: Hàm tạo sao chép trong ví dụ trên không khác gì hàm tạo sao chép mặc định.

Khi lớp không có các thuộc tính kiểu con trỏ hoặc tham chiếu, thì dùng hàm tạo sao chép mặc định là đủ.

Khi lớp có các thuộc tính con trỏ hoặc tham chiếu, thì hàm tạo sao chép mặc định chưa đáp ứng được yêu cầu.

Ví dụ:


class DT

{

private:



int n; // Bac da thuc

double *a; // Tro toi vung nho chua cac he so da thuc a0, a1, ...

public:

DT() { this->n0; this->a = NULL; }



DT(int n1)

{

this->n = n1;



this->a = new double[n1+1];

}

friend ostream& operator << (ostream& os, const DT &d);



friend istream& operator>> (istream& is, DT &d);

...


} ;

Bây giờ chúng ta hãy theo dõi xem việc dùng hàm tạo mặc định trong đoạn chương trình sau sẽ dẫn đến sai lầm như thế nào:

DT d ; // Tạo đối tượng d kiểu DT

cin >> d ;

/* Nhập đối tượng d, gồm: nhập một số nguyên dương và gán cho d.n, cấp phát vùng nhớ cho d.a, nhập các hệ số của đa thức và chứa vào vùng nhớ được cấp phát */

DT u(d);

/* Dùng hàm tạo mặc định để xây dựng đối tượng u theo d. Kết quả: u.n = d.n và u.a = d.a. Như vậy 2 con trỏ u.a và d.a cùng trỏ đến một vùng nhớ */

Nhận xét: Mục đích là tạo ra một đối tượng u giống như d, nhưng độc lập với d. Nghĩa là khi d thay đổi thì u không bị ảnh hưởng gì. Thế nhưng mục tiêu này không đạt được, vì u và d có chung một vùng nhớ chứa hệ số của đa thức, nên khi sửa đổi các hệ số của đa thức trong d thì các hệ số của đa thức trong u cũng thay đổi theo. Còn một trường hợp nữa cũng dẫn đến lỗi là khi một trong 2 đối tượng u và d bị giải phóng (thu hồi vùng nhớ chứa đa thức) thì đối tượng còn lại cũng sẽ không còn vùng nhớ nữa.

Ví dụ sau sẽ minh họa nhận xét trên: Khi d thay đổi thì u cũng thay đổi và ngược lại khi u thay đổi thì d cũng thay đổi theo.

#include

#include

#include

class DT


{

private:


int n; // Bac da thuc

double *a; // Tro tơi vung nho chua cac he so da thuc a0, a1 , ...

public:

DT() { this->n = 0; this->a = NULL; }



DT(int n1)

{

this->n = n1 ;



this->a = new double[n1+1];

}

friend ostream& operator<< (ostream& os, const DT &d);



friend istream& operator>> (istream& is, DT &d);

} ;
ostream& operator<< (ostream& os, const DT &d)

{

os << " Cac he so (tu ao): ";



for (int i = 0 ; i< = d.n ; ++i)

os << d.a[i] <<" " ;

return os;

}
istream& operator >> (istream& is, DT &d)

{

if (d.a! = NULL) delete d.a;



cout << " \n Bac da thuc: " ;

cin >> d.n;

d.a = new double[d.n+1];

cout << ''Nhap cac he so da thuc:\n" ;

for (int i = 0 ; i< = d.n ; ++i)

{

cout << "He so bac "<< i << " = " ;



is >> d.a[i] ;

}

return is;



}
void main()

{

DT d;



clrscr();

cout <<"\n Nhap da thuc d " ; cin >> d;

DT u(d);

cout << "\n Da thuc d "<< d ;

cout << "\n Da thuc u " << u ;

cout <<"\n Nhap da thuc d " ; cin >> d;

cout << "\nDa thuc d " << d;

cout <<"\n Da thuc u " << u ;

cout <<"\n Nhap da thuc u " ; cin >> u;

cout << "\n Da thuc d "<< d ;

cout << "\n Da thuc u " << u ;

getch();

}

Ví dụ về hàm tạo sao chép


Trong chương trình trên đã chỉ rõ: Hàm tạo sao chép mặc định là chưa thoả mãn đối với lớp DT. Vì vậy cần viết hàm tạo sao chép để xây dựng đối tượng mới (ví dụ u) từ một đối tượng đang tồn tại (ví dụ d) theo các yêu cầu sau:

Gán d.n cho u.n

Cấp phát một vùng nhớ cho u.a để có thể chứa được (d.n + 1) hệ số.

Gán các hệ số chứa trong vùng nhớ của d.a sang vùng nhớ của u.a

Như vậy chúng ta sẽ tạo được đối tượng u có nội dung ban đầu giống như d, nhưng độc lập với d.

Để đáp ứng các yêu cầu nêu trên, hàm tạo sao chép cần được xây dựng như sau:

DT::DT(const DT &d)

{

this ® n = d.n ;



this ® a = new double[d.n+1];

for (int i = 0; i< = d.n; ++i)

this ® a[i] = d.a[i];

}

Chương trình sau sẽ minh họa điều này: Sự thay đổi của d không làm ảnh hưởng đến u và ngược lại sự thay đổi của u không làm ảnh hưởng đến d.



// Viết hàm tạo sao chép cho lớp DT

#include

#include

#include

class DT

{

private:



int n; // Bac da thuc

double *a; // Tro toi vung nho chua cac he so da thuc a0, a1 , ...

public:

DT() { this ® n = 0; this ® a = NULL; }



DT(int n1)

{

this ® n = n1 ;



this ® a = new double[n1+1];

}

DT(const DT &d);



friend ostream& operator<< (ostream& os, const DT&d);

friend istream& operator>> (istream& is, DT&d);

};
DT::DT(const DT&d)

{

this ® n = d.n;



this ® a = new double[d.n+1];

for (int i = 0; i< = d.n; ++i)

this ® a[i] = d.a[i];

}
ostream& operator<< (ostream& os, const DT &d)

{

os << " Cac he so (tu ao): " ;



for (int i = 0 ; i< = d.n ; ++i) os << d.a[ i] <<" " ;

return os;

}
istream& operator>> (istream& is, DT &d)

{

if (d.a! = NULL) delete d.a;



cout << "\n Bac da thuc: '' ;

cin >> d.n;

d.a = new double[d.n+1];

cout << ''Nhap cac he so da thuc:\n'' ;

for (int i = 0 ; i< = d.n ; ++i)

{

cout << "He so bac " << i << " = " ;



is >> d.a[i] ;

}

return is;



}
void main()

{

DT d;



clrscr();

cout <<"\n Nhap da thuc d " ; cin >> d;

DT u(d);

cout <<"\n Da thuc d " << d ;

cout << "\n Da thuc u " << u ;

cout <<"\n Nhap da thuc d " ; cin >> d;

cout << "\n Da thuc d "<< d ;

cout <<"\n Da thuc u " << u ;

cout <<"\n Nhap da thuc u " ; cin >> u;

cout << "\n Da thuc d " << d ;

cout <<"\n Da thuc u " << u ;

getch();

}

1   ...   42   43   44   45   46   47   48   49   ...   55


Cơ sở dữ liệu được bảo vệ bởi bản quyền ©hocday.com 2016
được sử dụng cho việc quản lý

    Quê hương