C Chuonga C Chuonga

Màu nền
Font chữ
Font size
Chiều cao dòng
                                               Chương 4

Hàm tạo, hàm huỷ và các

 vấn đề liên quan

Chương này trình bầy một số vấn đề có tính chuyên sâu hơn về lớp như:

+ Hàm tạo (constructor)

+ Hàm huỷ (destructor)

+ Toán tử gán và hàm tạo sao chép

+ Mối liên quan giữa hàm tạo và đối tượng thành phần

+ Các thành phần tĩnh

+ Lớp bạn, hàm bạn

+ Đối tượng hằng

+ Phương thức inline

§ 1. Hàm tạo (constructor)

1.1. Công dụng

Hàm tạo cũng là một phương thức của lớp (nhưng khá đặc biệt) dùng để tạo dựng một đối tượng mới. Chương trình dịch sẽ cấp phát bộ nhớ cho đối tượng sau đó sẽ gọi đến hàm tạo. Hàm tạo sẽ khởi gán giá trị cho các thuộc tính của đối tượng và có thể thực hiện một số công việc khác nhằm chuẩn bị cho đối tượng mới.

1.2. Cách viết hàm tạo

1.2.1. Điểm khác của hàm tạo và các phương thức thông thường

Khi viết hàm tạo cần để ý 3 sự khác biệt của hàm tạo so với các phương thức khác như sau:

+ Tên của hàm tạo: Tên của hàm tạo bắt buộc phải trùng với tên của lớp.

150                                                                                                                                                                                                                                             151

+ Không khai báo kiểu cho hàm tạo.

+  Hàm tạo không có kết quả trả về.

1.2.2. Sự giống nhau của hàm tạo và các phương thức thông thường

Ngoài 3 điểm khác biệt trên, hàm tạo được viết như các phương thức khác:

+ Hàm tạo có thể được xây dựng bên trong hoặc bên ngoài định nghĩa lớp.

+ Hàm tạo có thể có đối hoặc không có đối.

+ Trong một lớp có thể có nhiều hàm tạo (cùng tên nhưng khác bộ đối).

Ví dụ sau định nghĩa lớp DIEM_DH (Điểm đồ hoạ) có 3 thuộc tính:

int x;  // hoành độ (cột) của điểm

int y;  // tung độ (hàng) của điểm

int m;  // mầu của điểm

và đưa vào 2 hàm tạo để khởi gán cho các thuộc tính của lớp:

// Hàm tạo không đối: Dùng các giá trị cố định để khởi gán cho

// x, y, m

DIEM_DH() ;

// Hàm tạo có đối: Dùng các đối x1, y1, m1 để khởi gán cho

// x, y, m

// Đối m1 có giá trị mặc định 15 (mầu trắng)

DIEM_DH(int x1, int y1, int m1=15) ;

class DIEM_DH

{

private:

int x, y, m ;

public:

//Hàm tạo không đối: khởi gán cho x=0, y=0, m=1

// Hàm này viết bên trong định nghĩa lớp

DIEM_DH()

{

x=y=0;

m=1;

// Hàm tạo này xây dựng bên ngoài định nghĩa lớp

DIEM_DH(int x1, int y1, int m1=15) ;

// Các phương thức khác

} ;

// Xây dựng hàm tạo bên ngoài định nghĩa lớp

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

{

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

}

1.3. Dùng hàm tạo trong khai báo

+ Khi đã xây dựng các hàm tạo, ta có thể dùng chúng trong khai báo để tạo ra một đối tượng đồng thời khởi gán cho các thuộc tính của đối tượng được tạo. Dựa vào các tham số trong khai báo mà Trình biên dịch sẽ biết cần gọi đến hàm tạo nào.

+ Khi khai báo một biến đối tượng có thể sử dụng các tham số để khởi gán cho các thuộc tính của biến đối tượng.

+ Khi khai báo mảng đối tượng không cho phép dùng các tham số để khởi gán.

+ Câu lệnh khai báo một biến đối tượng sẽ gọi tới hàm tạo 1 lần

+ Câu lệnh khai báo một mảng n đối tượng sẽ gọi tới hàm tạo n lần.

Ví dụ:

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

152                                                                                                                                                                                                                                             153

                        //  Kết quả d.x=0, d.y=0, d.m=1

DIEM_DH u(200,100,4);  // Gọi tới hàm tạo có đối.

                                          //  Kết quả u.x=200, u.y=100, d.m=4

DIEM_DH v(300,250);  // Gọi tới hàm tạo có đối.

                                       //  Kết quả v.x=300, v.y=250, d.m=15

DIEM_DH  p[10] ; // Gọi tới hàm tạo không đối 10 lần

Chú ý: Với các hàm có đối kiểu lớp, thì đối chỉ xem là các tham số hình thức, vì vậy khai báo đối (trong dòng đầu của hàm) sẽ không tạo ra đối tượng mới và do đó không gọi tới các hàm tạo.

1.4. Dùng hàm tạo trong cấp phát bộ nhớ

+ Khi cấp phát bộ nhớ cho một đối tượng có thể dùng các tham số để khởi gán cho các thuộc tính của đối tượng, ví dụ:

DIEM_DH *q =new DIEM_DH(50,40,6);//Gọi tới hàm tạo có đối

                                          // Kết quả  q->x=50, q->y=40, q->m=6

DIEM_DH   *r = new DIEM_DH ; // Gọi tới hàm tạo không đối

                                           // Kết quả  r->x=0, r->y= 0, r->m=1

+ Khi cấp phát bộ nhớ cho một dẫy đối tượng không cho phép dùng tham số để khởi gán, ví dụ:

int n=20;

DIEM_DH   *s = new DIEM_DH[n] ; // Gọi tới hàm tạo không

// đối 20 lần.

1.5. Dùng hàm tạo để biểu diễn các đối tượng hằng

+ Như đã biết, sau khi định nghĩa lớp DIEM_DH thì có thể xem lớp này như một kiểu dữ liệu như int, double, char, ...

Với kiểu int chúng ta có các hằng int, như 356.

Với kiểu double chúng ta có các hằng double, như 98.75

Khái niệm hằng kiểu int, hằng kiểu double có thể mở rộng cho hằng kiểu DIEM_DH

+ Để biểu diễn một hằng đối tượng (hay còn gọi: Đối tượng hằng) chúng ta phải dùng tới hàm tạo. Mẫu viết như sau:

Tên_lớp(danh sách tham số) ;

Ví dụ đối với lớp DIEM_DH nói trên, có thể viết như sau:

DIEM_DH(345,123,8)  // Biểu thị một đối tượng kiểu DIEM_DH

                                         // có các thuộc tính x=345, y=123, m=8

Chú ý: Có thể sử dụng một hằng đối tượng như một đối tượng. Nói cách khác, có thể dùng hằng đối tượng để thực hiện một phương thức, ví dụ nếu viết:

DIEM_DH(345,123,8).in();

thì có nghĩa là thực hiện phương thức in() đối với hằng đối tượng.

1.6. Ví dụ minh hoạ

Chương trình sau đây minh hoạ cách xây dựng hàm tạo và cách sử dùng hàm tạo trong khai báo, trong cấp phát bộ nhớ và trong việc biểu diễn các hằng đối tượng.

//CT4_02.CPP

#include <conio.h>

#include <iostream.h>

#include <iomanip.h>

class DIEM_DH

{

private:

int x,y,m;

public:

// Hàm bạn dùng để in đối tượng DIEM_DH

friend void in(DIEM_DH d)

{

cout <<"

" << d.x << " "<< d.y<<" " << d.m ;

}

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

void in()

154                                                                                                                                                                                                                                             155

{

cout <<"

" << x << " "<< y<<" " << m ;

}

//Hàm tạo không đối

DIEM_DH()

{

x=y=0;

m=1;

}

//Hàm tạo có đối, đối m1 có giá trị mặc định là 15 (mầu trắng)

DIEM_DH(int x1,int y1,int m1=15);

};

//Xây dựng hàm tạo

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

{

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

}

void main()

{

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

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

DIEM_DH *d;

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

clrscr();

in(d1);  //Gọi hàm bạn in()

d2.in();//Gọi phương thức in()

in(*d); //Gọi hàm bạn in()

DIEM_DH(2,2,2).in();//Gọi phương thức in()

DIEM_DH t[3];  // 3 lần gọi hàm tạo không đối

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

cout << "

N= ";

cin >> n;

q=new DIEM_DH[n+1]; // (n+1) lần gọi hàm tạo không đối

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

q[i]=DIEM_DH(300+i,200+i,8);//(n+1) lần gọi hàm tạo có đối

for (i=0;i<=n;++i)

q[i].in();     // Gọi phương thức in()

for (i=0;i<=n;++i)

DIEM_DH(300+i,200+i,8).in();// Gọi phương thức in()

getch();

}

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

Các chương trình nêu trong chương 3 đều không có hàm tạo. Vậy khi đó các đối tượng được hình thành như thế nào ?

2.1. 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:

//CT4_03.CPP

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

#include <conio.h>

#include <iostream.h>

class DIEM_DH

{

156                                                                                                                                                                                                                                             157

private:

int x,y,m;

public:

// Phuong thuc

void in()

{

cout <<"

" << 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();

}

2.2. Nếu trong lớp đã có ít nhất một hàm tạo, thì hàm tạo mặc định sẽ không được phát sinh nữa. Khi đó 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 <conio.h>

#include <iostream.h>

class DIEM_DH

{

private:

int x,y,m;

public:

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

void in()

{

cout <<"

" << 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:

158                                                                                                                                                                                                                                             159

- 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 <conio.h>

#include <iostream.h>

class DIEM_DH

{

private:

int x,y,m;

public:

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

void in()

{

cout <<"

" << 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()

{

DIEM_DH d1(200,200,10); // Gọi tới hàm tạo, không dùng

// tham số mặc định

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

d2= DIEM_DH(300,300); // Gọi tới hàm tạo, dùng 1 tham số

// mặc định

d1.in();

d2.in();

getch();

}

§ 3. Lớp đa thức

Chương trình dưới đây là sự cải tiến chương trình trong mục 8.5 của chương 3 bằng cách đưa vào 2 hàm tạo:

//Hàm tạo không đối

DT()

{

this->n=0; this->a=NULL;

}

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

DT(int n1)

{

this->n=n1 ;

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

}

Hàm tạo có đối sẽ tạo một đối tượng mới (kiểu DT) gồm 2 thuộc tính là biến nguyên n và con trỏ a. Ngoài ra còn cấp phát bộ vùng nhớ (cho a) để chứa các hệ số của đa thức. 

Nếu không xây dựng hàm tạo, mà sử dụng hàm tạo mặc định thì các đối tượng (kiểu DT) tạo ra bởi các lệnh khai báo sẽ chưa có bộ nhớ để chứa đa thức. Như vậy đối tượng tạo ra chưa hoàn chỉnh và chưa dùng được. Để có một đối tượng hoàn chỉnh phải qua 2 bước:

+ Dùng khai báo để tạo các đối tượng, ví dụ:

DT d;

+ Cấp phát vùng nhớ (cho đối tượng) để chứa đa thức, ví dụ:

d.n =  m;

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

160                                                                                                                                                                                                                                             161

Quy trình này được áp dụng trong các phương thức toán tử của chương trình trong mục 8.5 chương 3. Rõ ràng quy trình này vừa dài vừa không tiện lợi, lại hay mắc lỗi, vì người lập trình hay quên không cấp phát bộ nhớ.

Việc dùng các hàm tạo để sản sinh ra các đối tượng hoàn chỉnh tỏ ra tiện lợi hơn, vì tránh được các thao tác phụ (như cấp phát bộ nhớ) nằm bên ngoài khai báo. Phương án dùng  hàm tạo sẽ được sử dụng trong các phương thức toán tử của chương trình dưới đây:

+ Nội dung chương trình gồm:

- Nhập, in các đa thức p, q, r, s

- Tính đa thức:  f = -(p + q)*(r - s)

- Nhập các số thực x1 và x2

- Tính  f(x1) (bằng cách dùng phương thức operator^)

- Tính  f(x2) (bằng cách dùng hàm F)

// CT4_05.CPP

#include <conio.h>

#include <iostream.h>

#include <math.h>

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];

}

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

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

DT operator-();

DT operator+(const DT &d2);

DT operator-(DT d2);

DT operator*(const DT &d2);

double operator^(const double &x); // Tinh gia tri da thuc

double operator[](int i)

{

if (i<0)

return double(n);

else

return a[i];

}

} ;

// Ham tinh gia tri da thuc

double F(DT d,double x)

{

double s=0.0 , t=1.0;

n = int(d[-1]);

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

{

s += d[i]*t;

t *= x;

}

return s;

162                                                                                                                                                                                                                                             163

}

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 << " - Bac da thuc: " ;

cin >> d.n;

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

cout << "Nhap cac he so da thuc:

" ;

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

{

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

is >> d.a[i] ;

}

return is;

}

DT DT::operator-()

{

DT p(this->n);

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

p.a[i] = -a[i];

return p;

}

DT DT::operator+(const DT &d2)

{

int k,i;

k = n > d2.n ? n : d2.n ;

DT d(k);

for (i=0; i<=k ; ++i)

if (i<=n && i<=d2.n)

d.a[i] = a[i] + d2.a[i];

else if (i<=n)

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

else

d.a[i] = d2.a[i];

i=k;

while(i>0 && d.a[i]==0.0) --i;

d.n = i;

return d ;

}

DT DT::operator-(DT d2)

{

return (*this + (-d2));

}

DT DT::operator*(const DT &d2)

{

int k, i, j;

k =  n + d2.n ;

DT d(k);

for (i=0; i<=k; ++i) d.a[i] = 0;

164                                                                                                                                                                                                                                             165

for (i=0 ; i<= n ; ++i)

for (j=0 ; j<= d2.n ; ++j)

d.a[i+j] += a[i]*d2.a[j] ;

return d;

}

double DT::operator^(const double &x)

{

double s=0.0 , t=1.0;

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

{

s += a[i]*t;

t *= x;

}

return s;

}

void main()

{

DT p,q,r,s,f;

double x1,x2,g1,g2;

clrscr();

cout <<"

Nhap da thuc P " ; cin >> p;

cout << "

Da thuc p " << p ;

cout <<"

Nhap da thuc Q " ; cin >> q;

cout << "

Da thuc q " << q ;

cout <<"

Nhap da thuc R " ; cin >> r;

cout << "

Da thuc r " << r ;

cout <<"

Nhap da thuc S " ; cin >> s;

cout << "

Da thuc s " << s ;

f = -(p+q)*(r-s);

cout << "

Nhap so thuc x1: " ; cin >> x1;

cout << "

Nhap so thuc x2: " ; cin >> x2;

g1 = f^x1;

g2 = F(f,x2);

cout << "

Da thuc f " << f ;

cout  << "

f("<<x1<<") = " << g1;

cout  << "

f("<<x2<<") = " << g2;

getch();

}  

§ 4. Hàm tạo sao chép (copy constructor)

4.1. 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:

166                                                                                                                                                                                                                                             167

- 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 minh hoạ 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.

//CT4_06.CPP

// Ham tao sao chep mac dinh

#include <conio.h>

#include <iostream.h>

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 << " -  Nhap tu va mau:  " ;

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

return is;

}

};

void main()

{

PS d;

cout << "

Nhap PS d"; cin >> d;

cout << "

PS d " << d;

PS u(d);

cout << "

PS u " << u;

getch();

}

4.2. 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:

168                                                                                                                                                                                                                                             169

int t,m ;

public:

PS (const PS  &p)

{

this->t = p.t ;

this->m = p.m ;

}

...

} ;

4.3. 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ụ lớp DT (đa thức) trong §3:

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];

}

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

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

....

} ;

Bây giờ chúng ta hãy theo rõ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ớ.

*/

170                                                                                                                                                                                                                                             171

Nhận xét: Mục đích của ta 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 hoạ 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.

//CT4_07.CPP

#include <conio.h>

#include <iostream.h>

#include <math.h>

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];

}

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 << " - Bac da thuc: " ;

cin >> d.n;

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

cout << "Nhap cac he so da thuc:

" ;

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 <<"

Nhap da thuc d " ; cin >> d;

DT u(d);

cout << "

Da thuc d " << d ;

cout << "

Da thuc u " << u ;

cout <<"

Nhap da thuc d " ; cin >> d;

cout << "

Da thuc d " << d ;

cout << "

Da thuc u " << u ;

cout <<"

Nhap da thuc u " ; cin >> u;

cout << "

Da thuc d " << d ;

cout << "

Da thuc u " << u ;

     getch();

172                                                                                                                                                                                                                                             173

}

4.4. 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 hoạ đ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.

//CT4_08.CPP

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

#include <conio.h>

#include <iostream.h>

#include <math.h>

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)

{

174                                                                                                                                                                                                                                             175

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 << " - Bac da thuc: " ;

cin >> d.n;

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

cout << "Nhap cac he so da thuc:

" ;

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 <<"

Nhap da thuc d " ; cin >> d;

DT u(d);

cout << "

Da thuc d " << d ;

cout << "

Da thuc u " << u ;

cout <<"

Nhap da thuc d " ; cin >> d;

cout << "

Da thuc d " << d ;

cout << "

Da thuc u " << u ;

cout <<"

Nhap da thuc u " ; cin >> u;

cout << "

Da thuc d " << d ;

cout << "

Da thuc u " << u ;

getch();

}

§ 5. Hàm huỷ (Destructor)

5.1. Công dụng của hàm huỷ

Hàm huỷ là một hàm thành viên của lớp (phương thức) có chức năng ngược với hàm tạo. Hàm huỷ được gọi trước khi giải phóng (xoá bỏ) một đối tượng để thực hiện một số công việc có tính “dọn dẹp” trước khi đối tượng được huỷ bỏ, ví dụ như giải phóng một vùng nhớ mà đối tượng đang quản lý, xoá đối tượng khỏi màn hình nếu như nó đang hiển thị, ...

Việc huỷ bỏ một đối tượng thường xẩy ra trong 2 trường hợp sau:

+ Trong các toán tử và các hàm giải phóng bộ nhớ, như delete, free,...

+  Giải phóng các biến, mảng cục bộ khi thoát khỏi hàm, phương thức.

5.2. Hàm huỷ mặc định

Nếu trong lớp không định nghĩa hàm huỷ, thì một hàm huỷ mặc định không làm gì cả được phát sinh. Đối với nhiều lớp thì hàm huỷ mặc định là đủ, và không cần đưa vào một hàm huỷ mới.

5.3. Quy tắc viết hàm huỷ

Mỗi lớp chỉ có một hàm huỷ viết theo các quy tắc sau:

176                                                                                                                                                                                                                                             177

+ Kiểu của hàm: Hàm huỷ cũng giống như hàm tạo là hàm không có kiểu, không có giá trị trả về.

+ Tên hàm: Tên của hàm huỷ gồm một dẫu ngã (đứng trước) và tên lớp:

~Tên_lớp

+ Đối: Hàm huỷ không có đối

Ví dụ có thể xây dựng hàm huỷ cho lớp DT (đa thức) ở §3 như sau:

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;

delete this->a;

}

...

} ;

5.4. Vai trò của hàm huỷ trong lớp DT

5.4.1. Khiếm khuyết của chương trình trong §3

Chương trình trong §3 định nghĩa lớp DT (đa thức) khá đầy đủ gồm:

+ Các hàm tạo

+ Các hàm toán tử nhập >>, xuất <<

+ Các hàm toán tử thực hiện các phép tính + - *

Tuy nhiên vẫn còn thiếu hàm huỷ để giải phóng vùng nhớ mà đối tượng kiểu DT (cần huỷ) đang quản lý.

Chúng ta hãy phân tích các khiếm khuyết của chương trình này:

+ Khi chương trình gọi tới một phương thức toán tử để thực hiện các phép tính cộng, trừ, nhân đa thức, thì một đối tượng trung gian được tạo ra. Một vùng nhớ được cấp phát và giao cho nó (đối tượng trung gian) quản lý.

+ Khi thực hiện xong phép tính sẽ ra khỏi phương thức. Đối tượng trung gian bị xoá, tuy nhiên chỉ vùng nhớ của các thuộc tính của đối tượng này được giải phóng. Còn vùng nhớ (chứa các hệ số của đa thức) mà đối tượng trung gian đang quản lý thì không hề bị giải phóng. Như vậy số vùng nhớ bị chiếm dụng vô ích sẽ tăng lên.

5.4.2. Cách khắc phục

Nhược điểm trên dễ dàng khắc phục bằng cách đưa vào lớp DT hàm huỷ viết trong 5.3 (mục trên).

5.5. Lớp hình tròn đồ hoạ

Chương trình dưới đây gồm:

Lớp HT (hình tròn) với các thuộc tính:

int r;    // Bán kính

int m ; // Mầu hình tròn

int  xhien,yhien;  // Vị trí hiển thị hình tròn trên màn hình

char *pht;         // Con trỏ trỏ tới vùng nhớ chứa ảnh hình tròn

int  hienmh;      // Trạng thái hiện (hienmh=1), ẩn (hienmh=0)

Các phương thức:

+ Hàm tạo không đối

HT();

thực hiện việc gán giá trị bằng 0 cho các thuộc tính của lớp.

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

HT(int r1,int m1=15);

thực hiện các việc:

- Gán  r1 cho r, m1 cho m

178                                                                                                                                                                                                                                             179

- Cấp phát bộ nhớ cho pht

- Vẽ hình tròn và lưu ảnh hình tròn vào vùng nhớ của pht

+ Hàm huỷ

~HT();

thực hiện các việc:

- Xoá hình tròn khỏi màn hình (nếu đang hiển thị)

- Giải phóng bộ nhớ đã cấp cho pht

+ Phương thức

void hien(int x, int y);

có nhiệm vụ hiển thị hình tròn tại (x,y)

+ Phương thức

void an();

có nhiệm vụ làm ẩn hình tròn

Các hàm độc lập:

void ktdh();  //Khởi tạo đồ hoạ

void ve_bau_troi(); // Vẽ bầu trời đầy sao

void ht_di_dong_xuong(); // Vẽ một cặp 2 hình tròn di

// chuyển xuống

void ht_di_dong_len();// Vẽ một cặp 2 hình tròn di

// chuyển lên trên

Nội dung chương trình là tạo ra các chuyển động xuống và lên của các hình tròn.

//CT4_09.CPP

// Lop do hoa

// Ham huy

// Trong ham huy co the goi PT khac

#include <conio.h>

#include <iostream.h>

#include <math.h>

#include <stdlib.h>

#include <graphics.h>

#include <dos.h>

void ktdh();

void ve_bau_troi();

void ht_di_dong_xuong();

void ht_di_dong_len();

int xmax,ymax;

class HT

{

private:

int r,m ;

int xhien,yhien;

char *pht;

int hienmh;

public:

HT();

HT(int r1,int m1=15);

~HT();

void hien(int x, int y);

void an();

};

HT:: HT()

{

r=m=hienmh=0;

xhien=yhien=0;

pht=NULL;

}

180                                                                                                                                                                                                                                             181

HT::HT(int r1,int m1)

{

r=r1; m=m1; hienmh=0;

xhien=yhien=0;

if (r<0) r=0;

if (r==0)

{

pht=NULL;

}

else

{

int size; char *pmh;

size = imagesize(0,0,r+r,r+r);

pmh = new char[size];

getimage(0,0,r+r,r+r,pmh);

setcolor(m);

circle(r,r,r);

setfillstyle(1,m);

floodfill(r,r,m);

pht = new char[size];

getimage(0,0,r+r,r+r,pht);

putimage(0,0,pmh,COPY_PUT);

delete pmh;

pmh=NULL;

}

}

void HT::hien(int x, int y)

{

if (pht!=NULL && !hienmh) // chua hien

{

hienmh=1;

xhien=x; yhien=y;

putimage(x,y,pht,XOR_PUT);

}

}

void HT::an()

{

if (hienmh) // dang hien

{

hienmh=0;

putimage(xhien,yhien,pht,XOR_PUT);

}

}

HT::~HT()

{

an();

if (pht!=NULL)

{

delete pht;

pht=NULL;

}

}

void ktdh()

{

int mh=0,mode=0;

initgraph(&mh,&mode,"");

xmax = getmaxx();

ymax = getmaxy();

}

182                                                                                                                                                                                                                                             183

void ve_bau_troi()

{

for (int i=0;i<2000;++i)

putpixel(random(xmax), random(ymax), 1+random(15));

}

void ht_di_dong_xuong()

{

HT h(50,4);

HT u(60,15);

h.hien(0,0);

u.hien(40,0);

for (int x=0;x<=340;x+=10)

{

h.an();

u.an();

h.hien(x,x);

delay(200);

u.hien(x+40,x);

delay(200);

}

}

void ht_di_dong_len()

{

HT h(50,4);

HT u(60,15);

h.hien(340,340);

u.hien(380,340);

for (int x=340;x>=0;x-=10)

{

h.an();

u.an();

h.hien(x,x);

delay(200);

u.hien(x+40,x);

delay(200);

}

}

void main()

{

ktdh();

ve_bau_troi();

ht_di_dong_xuong();

ht_di_dong_len();

getch();

closegraph();

}

Các nhận xét:

1. Trong thân hàm huỷ gọi tới phương thức an().

2. Điều gì xẩy ra khi bỏ đi hàm huỷ:

+ Khi gọi hàm ht_di_dong_xuong() thì có 2 đối tượng kiểu HT được tạo ra. Trong thân hàm sử dụng các đối tượng này để vẽ các hình tròn di chuyển xuống. Khi thoát khỏi hàm thì 2 đối tượng (tạo ra ở trên) được giải phóng. Vùng nhớ của các thuộc tính của chúng bị thu hồi, nhưng vùng nhớ cấp phát cho thuộc tính pht chưa được giải phóng và ảnh của 2 hình tròn (ở phía dưới màn hình) vẫn không được cất đi.

184                                                                                                                                                                                                                                             185

+ Điều tương tự xẩy ra sau khi ra khỏi hàm ht_di_dong_len() : vùng nhớ cấp phát cho thuộc tính pht chưa được giải phóng và ảnh của 2 hình tròn (ở phía trên màn hình) vẫn không được thu dọn.

§ 6. Toán tử gán

6.1. Toán tử gán mặc định

Toán tử gán (cho lớp) là một trường hợp đặc biệt so với các toán tử khác. Nếu trong lớp chưa định nghĩa một phương thức toán tử gán thì Trình biên dịch sẽ phát sinh một toán tử gán mặc định để thực hiện câu lệnh gán 2 đối tượng của lớp, ví du:

HT  h1, h2(100,6);

h1 = h2 ;  // Gán h2 cho h1

Toán tử gán mặc định sẽ sẽ sao chép đối tượng nguồn (h2) vào đối tượng đích (h1) theo từng bit một.

Trong đa số các trường hợp khi lớp không có các thành phần con trỏ hay tham chiếu thì toán tử gán mặc định là đủ dùng và không cần định nghĩa một phương thức toán tử gán cho lớp. Nhưng đối với các lớp có thuộc tính con trỏ như lớp DT (đa thức), lớp HT (hình tròn) thì toán tử gán mặc định không thích hợp và việc xây dựng toán tử gán là cần thiết.

6.2. Cách viết toán tử gán

Cũng giống như các phương thức khác, phương thức toán tử gán dùng đối con trỏ this để biểu thị đối tượng đích và dùng một đối tường minh để biểu thị đối tượng nguồn. Vì trong thân của toán tử gán không nên làm việc với bản sao của đối tượng nguồn, mà phải làm việc trực tiếp với đối tượng nguồn, nên kiểu đối tường minh nhất thiết phải là kiểu tham chiếu đối tượng.

Phương thức toán tử gán có thể có hoặc không có giá trị trả về. Nếu không có giá trị trả về (kiểu void), thì khi viết chương trình không được phép viết câu lệnh gán liên tiếp nhiều đối tượng, như:

u = v = k = h ;

Nếu phương thức toán tử gán trả về tham chiếu của đối tượng nguồn, thì có thể dùng toán tử gán thể thực hiện các phép gán liên tiếp nhiều đối tượng.

Ví dụ đối với lớp HT (trong mục trước), có thể xây dựng toán tử gán như sau:

void  HT::operator=(const HT &h)

{

r = h.r ; m = h.m ;

xhien = yhien = 0;

hienmh = 0 ;

if (h.pht==NULL)

pht = NULL;

else

{

int size;

size = imagesize(0,0,r+r,r+r);

pht = new char[size];

memcpy(pht,h.pht,size);

}

}

Với toán tử gán này, chỉ cho phép gán đối tượng nguồn cho một đối tượng đích.

Như vậy câu lệnh sau là sai:

HT  u, v, h ;

u = v = h ;

Bây giờ ta sửa lại toán gán để nó trả về tham chiếu đối tượng nguồn như sau:

const HT  & HT::operator=(const HT &h)

{

r = h.r ; m = h.m ;

xhien = yhien = 0;

hienmh = 0 ;

if (h.pht==NULL)

186                                                                                                                                                                                                                                             187

pht = NULL;

else

{

int size;

size = imagesize(0,0,r+r,r+r);

pht = new char[size];

memcpy(pht,h.pht,size);

}

return h ;

}

Với toán tử gán mới này, ta có thể viết câu lệnh để gán đối tượng nguồn cho nhiều  đối tượng đích. Như vậy các câu lệnh sau là được:

HT  u, v, h ;

u = v = h ;

6.3. Toán tử gán và hàm tạo sao chép

+ Toán tử gán không tạo ra đối tượng mới, chỉ thực hiện phép gán giữa 2 đối tượng đã tồn tại.

+ Hàm tạo sao chép được dùng để tạo một đối tượng mới và gán nội dung của một đối tượng đã tồn tại cho đối tượng mới vừa tạo.

+ Nếu đã xây dựng toán tử gán mà lại dùng hàm tạo sao chép mặc định thì chưa đủ, vì việc khởi gán trong câu lệnh khai báo sẽ không gọi tới toán tử gán mà lại gọi tới hàm tạo sao chép.

+ Như vậy đối với lớp có thuộc tính con trỏ, thì ngoài hàm tạo, cần xây dựng thêm:

- Hàm huỷ

- Hàm tạo sao chép

- Phương thức toán tử gán

Chú ý:  Không phải mọi câu lệnh chứa có dấu = đều gọi đến toán tử gán. Cần phân biệt 3 trường hợp:

1. Câu lệnh new (chứa dấu =) sẽ gọi đến hàm tạo, ví dụ:

HT *h= new HT(50,6); // gọi đến hàm tạo có đối

2. Câu lệnh khai báo và khởi gán (dùng dấu =) sẽ gọi đến hàm tạo sao chép, ví dụ:    

HT k=*h;      // gọi đến hàm tạo sao chep

3. Câu lệnh gán sẽ gọi đến toán tử gán, ví dụ:

HT u;

u=*h; // gọi đến phương thức toán tử gán

6.4. Ví dụ minh hoạ

Chương trình dưới đây định nghĩa lớp HT (hình tròn) và minh hoạ:

+ Hàm tạo và hàm huỷ

+ Phương thức toán tử gán có kiểu tham chiếu

+ Hàm tạo sao chép

+ Cách dùng con trỏ this trong hàm tạo sao chép

+  Cách dùng con trỏ  _new_handler để kiểm tra việc cấp phát bộ nhớ.

//CT4_10.CPP

// Lop do hoa

// Ham huy

// toan tu gan - tra ve tham chieu

// Ham tao sao chep

// Trong ham huy co the goi PT khac

#include <conio.h>

#include <iostream.h>

#include <stdlib.h>

#include <graphics.h>

#include <new.h>

#include <mem.h>

static void kiem_tra_bo_nho() ;

void ktdh();

188                                                                                                                                                                                                                                             189

int xmax,ymax;

void  kiem_tra_bo_nho()

{

outtextxy(1,1,"LOI BO NHO");

getch();

closegraph();

exit(1);

}

class HT

{

private:

int r,m ;

int xhien,yhien;

char *pht;

int hienmh;

public:

HT();

HT(int r1,int m1=15);

HT(const HT &h);

~HT();

void hien(int x, int y);

void an();

const HT &operator=(const HT &h);

};

const HT & HT::operator=(const HT &h)

{

// outtextxy(1,1,"Gan"); getch();

r = h.r ; m = h.m ;

xhien = yhien = 0;

hienmh = 0 ;

if (h.pht==NULL)

pht = NULL;

else

{

int size;

size = imagesize(0,0,r+r,r+r);

pht = new char[size];

memcpy(pht,h.pht,size);

}

return h;

}

HT::HT(const HT &h)

{

//outtextxy(300,1,"constructor sao chep"); getch();

*this = h;

}

HT:: HT()

{

r=m=hienmh=0;

xhien=yhien=0;

pht=NULL;

}

HT::HT(int r1,int m1)

{

r=r1; m=m1; hienmh=0;

xhien=yhien=0;

if (r<0) r=0;

190                                                                                                                                                                                                                                             191

if (r==0)

{

pht=NULL;

}

else

{

int size; char *pmh;

size = imagesize(0,0,r+r,r+r);

pmh = new char[size];

getimage(0,0,r+r,r+r,pmh);

setcolor(m);

circle(r,r,r);

setfillstyle(1,m);

floodfill(r,r,m);

pht = new char[size];

getimage(0,0,r+r,r+r,pht);

putimage(0,0,pmh,COPY_PUT);

delete pmh;

pmh=NULL;

}

}

void HT::hien(int x, int y)

{

if (pht!=NULL && !hienmh) // chua hien

{

hienmh=1;

xhien=x; yhien=y;

putimage(x,y,pht,XOR_PUT);

}

}

void HT::an()

{

if (hienmh) // dang hien

{

hienmh=0;

putimage(xhien,yhien,pht,XOR_PUT);

}

}

HT::~HT()

{

an();

if (pht!=NULL)

{

delete pht;

pht=NULL;

}

}

void ktdh()

{

int mh=0,mode=0;

initgraph(&mh,&mode,"");

xmax = getmaxx();

ymax = getmaxy();

}

void main()

{

_new_handler = kiem_tra_bo_nho ;

ktdh();

192                                                                                                                                                                                                                                             193

HT *h= new HT(50,6); // gọi hàm tạo có đối

h->hien(100,200);

HT k=*h;      // gọi hàm tạo sao chép

k.hien(200,200);

HT t,v,u;

t = v = u = *h;   // gọi toán tử  gán

u.hien(300,200);

v.hien(400,200);

t.hien(500,200);

getch();

closegraph();

}

6.5. Vai trò của phương thức toán tử gán

Chương trình trên sẽ vẽ 5 hình tròn trên màn hình. Điều gì sẽ xẩy ra nếu bỏ đi phương thức toán tử gán và hàm tạo sao chép?

+ Nếu bỏ cả hai, thì chỉ xuất hiên một hình tròn tại vị trí (100,200).

+ Nếu bỏ toán tử gán (giữ hàm tạo sao chép) thì chỉ xuất hiện 2 hình tròn tại các vị trí (100,200) và (200,200).

+ Nếu bỏ hàm tạo sao chép (giữ toán tử gán) thì xuất hiện 4 hình tròn.

§ 7. Phân loại phương thức, phương thức inline

7.1. Phân loại các phương thức

Có thể chia phương thức thành các nhóm:

1. Các phương thức thông thường

2. Các phương thức dùng để xây dựng và huỷ bỏ đối tượng gồm:

+ Hàm tạo không đối,

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

+ Hàm tạo sao chép

+ Hàm huỷ

3. Các phương thức toán tử

7.2. Con trỏ this

Mọi phương thức đều dùng con trỏ this như đối thứ nhất (đối ẩn). Ngoài ra trong phương thức có thể đưa vào các đối tường minh được khai báo như đối của hàm.

+ Với các phương thức thông thường, thì đối ẩn biểu thị đối tượng chủ thể trong lời gọi phương thức.

+ Với các hàm tạo, thì đối ẩn biểu thị đối tượng mới được hình thành.

+ Với các hàm huỷ, thì đối ẩn biểu thị đối tượng sắp bị huỷ bỏ.

+ Với các phương thức toán tử, thì đối ẩn biểu thị toán hạng đối tượng thứ nhất.

7.3. Phương thức inline.

Có 2 cách để biên dịch phương thức theo kiểu inline:

Cách 1: Xây dựng phương thức bên trong định nghĩa lớp.

Cách 2: Thêm từ khoá inline vào định nghĩa phương thức (bên ngoài định nghĩa lớp).

Chú ý là chỉ các phương thức ngắn không chứa các câu lệnh phức tạp (như chu trình, goto, switch, đệ quy) mới có thể trơ thành inline. Nếu có ý định biên dịch theo kiểu inline các phương thức chứa các câu lệnh phức tạp nói trên, thì Trình biên dịch sẽ báo lỗi.

Trong chương trình dưới đây, tất cả các phương thức của lớp PS (phân số) đều là phương thức inline

//CT4_11.CPP

// Lop PS

// Inline

#include <conio.h>

194                                                                                                                                                                                                                                             195

#include <iostream.h>

class PS

{

private:

int t,m ;

public:

PS()

{

t=0;m=1;

}

PS(int t1, int m1);

void nhap();

void in();

PS operator*=(PS p2)

{

t*=p2.t;

m*=p2.m;

return *this;

}

};

inline PS::PS(int t1, int m1)

{

t=t1;

m=m1;

}

inline void PS::nhap()

{

cout << "

Nhap tu va mau: " ;

cin >> t >> m;

}

inline void PS::in()

{

cout << "

PS = " << t << "/" << m ;

}

void main()

{

PS q,p,s(3,5);

cout << "

Nhap PS p";

p.nhap();

s.in();

p.in();

q = p*=s;

p.in();

q.in();

getch();

}

§ 8. Hàm tạo và đối tượng thành phần

8.1. Lớp bao, lớp thành phần

Một lớp có thuộc tính là đối tượng của lớp khác gọi là lớp bao, ví dụ:

class A

{

private:

int a, b;

...     

196                                                                                                                                                                                                                                             197

} ;

class B

{

private:

double  x, y, z;

...     

} ;

class C

{

private:

int m, n;

A  u;

B   p, q;

...

} ;

Trong ví dụ trên thì:

C là lớp bao

A, B là các lớp thành phần (của C)

8.2. Hàm tạo của lớp bao

+ Chú ý là trong các phương thức của lớp bao không cho phép truy nhập trực tiếp đến các thuộc tính của các đối tượng của các lớp thành phần.

+ Vì vậy, khi xây dựng hàm tạo của lớp bao, phải sư dụng các hàm tạo của lớp thành phần để khởi gán cho các đối tượng thành phần của lớp bao.

Ví dụ khi xây dựng hàm tạo của lớp C, cần dùng các hàm tạo của lớp A để khởi gán cho đối tượng thành phần u và dùng các hàm tạo của lớp B để khởi gán cho các đối tượng thành phần p, q.

8.3. Cách dùng hàm tạo của lớp thành phần để xây dựng hàm tạo của lớp bao

+ Để dùng hàm tạo (của lớp thành phần) khởi gán cho đối tưọng thành phần của lớp bao, ta sử dụng mẫu:

tên_đối_tượng(danh sách giá trị)

+ Các mẫu trên cần viết bên ngoài thân hàm tạo, ngay sau dòng đầu tiên. Nói một cách cụ thể hơn,  hàm tạo sẽ có dạng:

tên_lớp(danh sách đối) : tên_đối_tượng( danh sách giá trị),

...

tên_đối_tượng( danh sách giá trị)

{

// Các câu lệnh trong thân hàm tạo

}  

Chú ý là các dấu ngoặc sau tên đối tượng luôn luôn phải có, ngay cả khi danh sách giá trị bên trong là rỗng.   

+ Danh sách giá trị lấy từ danh sách đối. Dựa vào danh sách giá trị, Trình biên dịch sẽ biết cần dùng hàm tạo nào để khởi gán cho đối tượng. Nếu danh sách giá trị là rỗng thì  hàm tạo không đối sẽ được sử dụng.

+ Các đối tượng muốn khởi gán bằng hàm tạo không đối có thể bỏ qua, không cần phải liệt kê trong hàm tạo. Nói cách khác: Các đối tượng không được liệt kê trên dòng đầu hàm tạo của lớp bao, đều được khởi gán bằng hàm tạo không đối của lớp thành phần.

Ví dụ:

class A

{

private:

int a, b;

public:

198                                                                                                                                                                                                                                             199

A()

{

a=b=0;

}

A(int a1, int b1)

{

a = a1; b = b1;

...     

} ;

class B

{

private:

double  x, y, z;

public:

B()

{

x = y = z = 0.0 ;

}

B(double x1, double y1)

{

x = x1;  y = y1; z = 0.0 ;

}

B(double x1, double y1, double z1)

{

x = x1;  y = y1; z = z1 ;

}

...     

} ;

class C

{

private:

int m, n;

A  u, v;

B   p, q, r;

public:

C(int m1, int n1,int a1, int b1, double x1, double y1, double x2, double y2, double z2) : u(), v(a1,b1), q(x1,y1), r(x2,y2,z2)

{

m = m1 ; n = n1;

}

} ;

Trong hàm tạo nói trên của lớp C, thì các đối tượng thành phần được khởi gán như sau:

u được khởi gán bằng hàm tạo không đối của lớp A

v được khởi gán bằng hàm tạo 2 đối của lớp A

q được khởi gán bằng hàm tạo 2 đối của lớp B

r được khởi gán bằng hàm tạo 3 đối của lớp B

p (không có mặt) được khởi gán bằng hàm tạo không đối của lớp B

8.4. Sử dụng các phương thức của lớp thành phần

Mặc dù lớp bao có các thành phần đối tượng, nhưng trong lớp bao lại không được phép truy nhập đến các thuộc tính của các đối tượng này. Vì vậy giải pháp thông thường là:

+ Trong các lớp thành phần, xây dựng sẵn các phương thức để có thể lấy ra các thuộc tính của lớp.

200                                                                                                                                                                                                                                             201

+ Trong lớp bao dùng các phương thức của lớp thành phần để nhận các thuộc tính của các đối tượng thành viên cần dùng đến.

8.5. Các ví dụ

Hai chương trình dưới đây minh hoạ các điều đã nói trong các mục trên.

Ví dụ 1:

Trong ví dụ này xét 2 lớp:

DIEM (Điểm) và DT (Đoạn thẳng)

Lớp DIEM là lớp thành phần của lớp DT

//CT4_12.CPP

// Thuoc tinh doi tuong

#include <conio.h>

#include <iostream.h>

class DIEM

{

private:

int x,y ;

public:

DIEM()

{

x=y=0;

}

DIEM(int x1, int y1)

{

x= x1; y=y1;

}

void in()

{

cout << "(" << x << "," << y << ")" ;

}

} ;

class DT

{

private:

DIEM d1, d2;

public:

DT() : d1(), d2()

{

m=0;

}

DT(int m1,int x1, int y1, int x2, int y2) : d1(x1,y1), d2(x2,y2)

{

m=m1;

}

DT(int m1,DIEM t1, DIEM t2)

{

m=m1;

d1 = t1;

d2 = t2;

}

void in()

{

cout << "

Diem dau : "; d1.in();

cout << "

Diem cuoi: "; d2.in();

cout << "

Mau : " << m;

}

202                                                                                                                                                                                                                                             203

};

void main()

{

DT  u, v(1,100,100,200,200), s(2,DIEM(300,300),

DIEM(400,400)) ;

clrscr();

u.in();

v.in();

s.in();

getch();

}

Ví dụ 2:

Trong ví dụ này xét 3 lớp:

Diem (Điểm)

DTron (Đường tròn)

HTron (Hình tròn)

Lớp DTron có một lớp thành phần là lớp Diem.

Lớp HTron có 2 lớp thành phần là lớp DTron và lớp Diem.

Trong lớp DTron đưa vào phương thức vẽ đường tròn.

Trong lớp HTron đưa vào phương thức vẽ và tô mầu hình tròn.

Khi xây dựng phương thức của lớp bao cần sử dụng các phương thức của lớp thành phần.

//CT4_13.CPP

// Thuoc tinh doi tuong

#include <conio.h>

#include <iostream.h>

#include <graphics.h>

class Diem

{

private:

int x,y ;

public:

Diem()

{

x=y=0;

}

Diem(int x1, int y1)

{

x= x1; y=y1;

}

int getx()

{

return x;

}

int gety()

{

return y;

}

} ;

class DTron   // Duong tron

{

private:

Diem t ; // tam

public:

DTron()

{

r=m=0;

204                                                                                                                                                                                                                                             205

}

DTron(int x1,int y1,int r1,int m1): t(x1,y1)

{

m=m1; r=r1;

}

int mau()

{

return m;

}

void ve()

{

setcolor(m);

circle(t.getx(),t.gety(),r);

}

};

class HTron

{

private:

DTron  dt;

Diem d;

public:

HTron()

{

m=0;

}

HTron(int x1, int y1, int r1, int m1,

int x, int y, int mt): dt(x1,y1,r1,m1), d(x,y)

{

m = mt;

}

void ve()

{

dt.ve();

setfillstyle(1,m);

floodfill(d.getx(),d.gety(),dt.mau());

}

} ;

void main()

{

int mh=0, mode=0;

initgraph(&mh,&mode,"");

setbkcolor(1);

DTron dt(100,100,80,6);

HTron ht(300,300,150,15,300,300,4);

dt.ve();

ht.ve();

getch();

closegraph();

}

§ 9. Các thành phần tĩnh

9.1. Thành phần dữ liệu tĩnh

+ Thành phần dữ liệu được khai báo bằng từ khoá static gọi là tĩnh, ví dụ:

class A

{

private:

static   int  ts ;  // Thành phần tĩnh

int    x;              

....

206                                                                                                                                                                                                                                             207

} ;

+  Thành phần tĩnh được cấp phát một vùng nhớ cố định. Nó tồn tại ngay cả khi lớp chưa có một đối tượng nào cả.

+ Thành phần tĩnh là chung cho cả lớp, nó không phải là riêng của mỗi đối tượng. Ví dụ xét 2 đối tượng:

A   u,v ; // Khai báo 2 đối tượng

thì giữa các thành phần x và ts có sự khác nhau như sau:

u.x và v.x   có 2 vùng nhớ khác nhau

u.ts  và   v.ts  chỉ là một, chúng cùng biểu thị một vùng nhớ

thành phần ts tồn tại ngay khi u và v chưa khai báo

+ Để biểu thị thành phần tĩnh, ta có thể dùng tên lớp, ví du: Đối với ts thì 3 cách viết sau là tương đương:

          A::ts               u.ts               v.ts

+ Khai báo và khởi gán giá trị cho thành phần tĩnh

Thành phần tĩnh sẽ được cấp phát bộ nhớ và khởi gán giá trị ban đầu bằng một câu lệnh khai báo đặt sau định nghĩa lớp (bên ngoài các hàm, kể cả hàm main), theo các mẫu:

int A::ts ;              // Khởi gán cho ts giá trị 0

int  A::ts = 1234; // Khởi gán cho ts giá trị 1234

Chú ý: Khi chưa khai báo thì thành phần tĩnh chưa tồn tại. Ví dụ xét chương trình sau:

#include <conio.h>

#include <iostream.h>

class HDBH    // Hoá đơn bán hàng

{

private:

char *tenhang ;  // Tên hàng

double tienban ;  // Tiền bán

static int tshd ;    // Tổng số hoá đơn

static double tstienban ; // Tổng số tiền bán

public:

static void in()

{

cout <<"

" << tshd;

cout <<"

" << tstienban;

}

} ;

void main()

{

HDBH::in();

getch();

}

Các thành phần tĩnh tshd và tstienban chưa khai báo, nên chưa tồn tại. Vì vậy các câu lệnh in giá trị các thành phần này trong phương thức in là không logic. Khi dịch chương trình, sẽ nhận được các thông báo lỗi (tại phương thức in) như sau:

Undefined  symbol HDBH::tshd  in module ...

Undefined  symbol HDBH::tstienban  in module ...

Có thể sửa chương trình trên bằng cách đưa vào các lệnh khai báo các thành phần tĩnh tshd và tstienban như sau:

//CT4_14.CPP

// thanh phan tinh

// Lop HDBH (hoa don ban hang)

#include <conio.h>

#include <iostream.h>

class HDBH

{

private:

208                                                                                                                                                                                                                                             209

int shd ;

char *tenhang ;

double tienban ;

static int tshd ;

static double tstienban ;

public:

static void in()

{

cout <<"

" << tshd;

cout <<"

" << tstienban;

}

} ;

int HDBH::tshd=5;

double HDBH::tstienban=20000.0;

void main()

{

HDBH::in();

getch();

}

9.2. Khi nào cần sử dụng các thành phần dữ liệu tĩnh

Xét một ví dụ về quản lý các hoá đơn bán hàng. Mỗi hoá đơn có: Tên hàng, số tiền bán. Rõ ràng các thuộc tính nói trên là riêng của mỗi hoá đơn. Mặt khác nếu chúng ta quan tâm đến tổng số hoá đơn đã bán, tổng số tiền đã bán, thì các thông tin này là chung. Vì vậy khi thiết kế lớp HDBH (hoá đơn bán hàng) , thì ta có thể đưa vào 4 thành phần dữ liệu là:

tenhang   (tên hàng)

tienban (tiền bán)

tshd (tổng số hoá đơn)

tstienban  (tổng số tiền bán)

Các thuộc tính tenhang và tienban là riêng của mỗi hoá đơn, nên chúng được chọn là các thuộc tính thông thường. Còn các thuộc tính tshd và tstienban là chung cho cả lớp nên chúng được chọn là các thuộc tính tĩnh.

9.3. Phương thức tĩnh

+ Có 2 cách viết  phương thức tĩnh:

Cách 1: Dùng từ khoá static đặt trước định nghĩa phương thức viết bên trong định nghĩa lớp (như phương thưc in() ví dụ cuối của mục 9.1).

Cách 2: Nếu phương thức xây dựng bên ngoài định nghĩa lớp, thì dùng từ khoá static đặt trước khai báo phương thức bên trong định nghĩa lớp. Chú ý không cho phép dùng từ khoá static đặt trước định nghĩa phương thức viết bên ngoài định nghĩa lóp. 

 + Phương thức tĩnh là chung cho cả lớp, nó không lệ thuộc vào một đối tượng cụ thể, nó tồn tại ngay khi lớp chưa có đối tượng nào (xem ví dụ trong mục 9.1).

 + Lời gọi phương thức tĩnh:

Có thể xuất phát từ một đối tượng nào đó (như vẫn dùng khi gọi các phương thức khác)

Có thể dùng tên lớp

Ví dụ xét lớp HDBH trong mục 9.1 và xét các câu lênh:

HDBH   u, v;

Khi đó để gọi phương thức tĩnh in() có thể dùng một trong các lệnh sau:

u.in();

v.in();

HDBH::in();

210                                                                                                                                                                                                                                             211

+ Vì phương thức tĩnh là độc lập với các đối tượng, nên không thể dùng phương thức tĩnh để xử lý dữ liệu của các đối tượng chủ thể trong lời gọi phương thức tĩnh. Nói cách khác không cho phép truy nhập tới các thuộc tính (trư thuộc tính tĩnh) trong thân phương thức tĩnh. Điều đó cũng đồng nghĩa với việc không cho phép dùng con trỏ this trong phương thức tĩnh.

Ví dụ nếu lập phương thức tĩnh in() để in các thuộc tính của lớp HDBH như sau:

class HDBH

{

private:

int shd ;

char *tenhang ;

double tienban ;

static int tshd ;

static double tstienban ;

public:

static void in()

{

cout <<"

" << tshd;

cout <<"

" << tstienban;

cout <<"

" << tenhang;

cout <<"

" << tienban;

}

} ;

 thì sẽ bị lỗi, vì trong thân phương thức tĩnh không cho phép truy nhập đến các thuộc tính tenhang và tienban.

9.4. Ví dụ minh hoạ việc dùng phương thức tĩnh

Xét bài toán quản lý hoá đơn bán hàng. Mỗi hoá đơn có 2 dữ liêu là tên hàng và tiền bán. Sử dụng hàm tạo để tạo ra các hoá đơn, dùng  hàm huỷ để bỏ đi (loại đi) các hoá đơn không cần lưu trữ, dùng một phương thức để sửa chữa nội dung hoá đơn (nhập lại tiền bán). Vấn đề đặt ra là sau một số thao tác: Tạo, sửa và huỷ hoá đơn thì tổng số hoá đơn còn lại là bao nhiêu và tổng số tiền trên các hoá đơn còn lại là bao nhiêu?

Chương trình dưới đây nhằm đáp ứng yêu cầu đặt ra.

//CT4_14.CPP

// thanh phan tinh

// Lop HDBH (hoa don ban hang)

#include <conio.h>

#include <iostream.h>

class HDBH

{

private:

char *tenhang ;

double tienban ;

static int tshd ;

static double tstienban ;

public:

HDBH(char *tenhang1=NULL,double tienban1=0.0 )

{

tienban=tienban1;

tenhang=tenhang1;

++tshd;

tstienban += tienban;

}

~HDBH()

{

--tshd;

tstienban -= tienban;

212                                                                                                                                                                                                                                             213

}

void sua();

static void in();

} ;

int HDBH::tshd=0;

double HDBH::tstienban=0;

void HDBH::in()

{

cout <<"

Tong so hoa don: " << tshd;

cout <<"

Tong so tien: " << tstienban;

}

void HDBH::sua()

{

cout << "

Ten hang: " << tenhang;

cout << "

Tien ban : " << tienban;

tstienban -= tienban;

cout << "

Sua tien ban thanh : " ;

cin >> tienban;

tstienban += tienban;

}

void main()

{

HDBH *h1 = new HDBH("Xi mang",2000);

HDBH *h2 = new HDBH("Sat thep",3000);

HDBH *h3 = new HDBH("Ti vi",4000);

clrscr();

HDBH::in();

getch();

delete h1;

HDBH::in();

getch();

h2->sua();

HDBH::in();

getch();

delete h3;

HDBH::in();

getch();

}

§ 10. Mảng đối tượng

10.1. Khai báo

Có thể dùng tên lớp để khai báo mảng đối tượng (giống như khai báo mảng int, float, char, ...)   theo mẫu:

Tên_lớp   tên_mảng[kích_cỡ] ;

Ví dụ giả sử đã định nghĩa lớp DIEM (Điểm), khi đó có thể khai báo các mảng đối tượng DIEM như sau:

DIEM   a[10], b[20] ;

ý nghĩa:   a là mảng kiểu DIEM gồm 10 phần tử

             b là mảng kiểu DIEM gồm 20 phần tử

Câu lệnh khai báo mảng sẽ gọi tới hàm tạo không đối để tạo các phần tử mảng. Trong ví dụ trên, hàm tạo được gọi 30 lần để tạo 30 phần tử mảng đối tượng.

10.2. Khai báo và khởi gán

Để khai báo mảng và khởi gán giá trị cho các phần tử mảng đối tượng, cần dùng các hàm tạo có đối theo mẫu sau:

Tên_lớp  tên_mảng[kích_cớ] = { Tên_lớp(các tham số), ...,

214                                                                                                                                                                                                                                             215

Tên_lớp(các tham số) } ;

Ví dụ giả sử lớp DIEM đã định nghĩa:

class DIEM

{

private:

int x, y ;

public:

DIEM()

{

x=y=0;

}

DIEM(int x1, int y1)

{

x=x1; y=y1;

}

void nhapsl();

void ve_doan_thang(DIEM d2, int mau) ;

};

Khi đó các câu lệnh khai báo dưới đây đều đúng:

DIEM  d[5] = {DIEM(1,1),DIEM(200,200)};

DIEM  u[] = {DIEM(1,1),DIEM(200,200)};

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

Câu lệnh đầu gọi tới hàm tạo 2 lần để khởi gán cho d[1], d[2] và gọi tới hàm tạo không đối 3 lần để tạo các phần tử d[3], d[4] và d[5].

Câu lệnh sau gọi tới hàm tạo 2 lần để khởi gán cho u[1], u[2]. Mảng u sẽ gồm 2 phần tử.

10.3. Biểu thị thành phần của phần tử mảng

Để biểu thị thuộc tính của phần tử mảng đối tượng, ta viết như sau:

Tên_mảng[chỉ số] . Tên_thuộc_tính

Để thực hiện phương thức đối với phần tử mảng ta viết như sau:

Tên_mảng[chỉ số] . Tên_phương_thức(danh sách tham số) ;

Ví dụ để vẽ đoạn thẳng nối điểm d[1] với d[2] theo mầu đỏ, ta có thể dùng phương thức ve_doan_thang như sau:

d[1].ve_doan_thang(d[2], 4);// Thực hiện phương thức đối với d[1]

10.4. Ví dụ

Chương trình dưới đây đưa vào lớp TS (thí sinh) và xét bài toán: Nhập một danh sách thí sinh, sắp xếp danh sách theo thứ tự giảm của tổng điểm. Chương trình minh hoạ:

+ Cách dùng mảng đối tượng.

+ Vai trò con trỏ this (trong phương thức hv(hoán vị)) .

+ Các hàm tạo, hàm huỷ.

+ Vai trò của toán tử gán (nếu sử dụng phép gán mặc định chương trình sẽ cho kết quả sai).

//CT4_15.CPP

// mang doi tuong

// Lop TS (thi sinh)

// Chu y vai tro cua toan tu gan

#include <conio.h>

#include <iostream.h>

#include <string.h>

class TS

{

private:

char *ht;

double td;

public:

TS()

216                                                                                                                                                                                                                                             217

{

ht = new char[20];

td = 0;

}

~TS()

{

delete ht;

}

const TS &operator=(const TS &ts2)

{

this->td = ts2.td;

strcpy(this->ht,ts2.ht);

return ts2;

}

void nhap(int i);

void in();

double gettd()

{

return td;

}

void hv(TS &ts2)

{

TS tg;

tg = *this ;

*this = ts2 ;

ts2 = tg;

}

} ;

void TS::in()

{

cout << "

Ho ten: " << ht << "    Tong diem: " << td;

}

void TS::nhap(int i)

{

cout << "

Nhap thi sinh " << i ;

cout << "

Ho ten: " ; cin >> ht;

cout << "Tong diem: " ; cin >> td;

}

void main()

{

TS  ts[100];

int n, i, j;

clrscr();

cout << "

So thi sinh: " ;

cin >> n;

for (i=1; i<= n; ++i)

ts[i].nhap(i);

cout <<"

Danh sach nhap vao:";

for (i=1; i<= n; ++i)

ts[i].in();

for (i=1; i<n ; ++i)

for (j=i+1 ; j<=n; ++j)

if (ts[i].gettd() < ts[j].gettd())

ts[i].hv(ts[j]);

cout <<"

Danh sach sau khi sap xep:";

for (i=1; i<= n; ++i)

ts[i].in();

getch();

218                                                                                                                                                                                                                                             219

}

§ 11. cấp phát bộ nhớ cho đối tượng

11.1. Cách cấp phát bộ nhớ cho đối tượng

Có thể dùng new và tên lớp để cấp phát một vùng nhớ cho một hoặc một dẫy các đối tượng. Bộ nhớ cấp phát được quản lý bởi một con trỏ kiểu đối tượng. Ví dụ sau khi đã định nghĩa lớp DIEM như trong mục trên, ta có thể thực hiện các lệnh cấp phát bộ nhớ như sau:

int n = 10;

DIEM  *p, *q, *r ;

p = new DIEM ;  // Cấp phát bộ nhớ cho một đối tượng

q = new DIEM[n] ; //Cấp phát bộ nhớ cho n đối tượng

r = new DIEM(200,100); // Cấp phát bộ nhớ và khởi gán cho

// một đối tượng

11.2. Làm việc với đối tượng thông qua con trỏ

+ Giả sử con trỏ p trỏ tới vùng nhớ của một đối tượng nào đó. Khi đó:

- Để biểu thị một thành phần (thuộc tính hoặc phương thức) của đối tượng, ta dùng mẫu viết sau:

p -> tên_thành_phần

- Để biểu thị đối tượng, ta dùng mẫu viết sau:

*p

+ Giả sử con trỏ q trỏ tới địa chỉ đầu vùng nhớ của một dẫy đối tượng. Khi đó:

- Để biểu thị một thành phần (thuộc tính hoặc phương thức) của đối tượng thứ i, ta dùng một trong các mẫu viết sau:

q[i].tên_thành_phần

(q+i)-> tên_thành_phần

- Để biểu thị đối tượng thứ i, ta dùng một trong các mẫu viết sau:

q[i]

*(q+i)

11.3. Bài toán sắp xếp thí sinh

Trong mục 10.4. đã sử dụng mảng đối tượng để giải quyết bài toán: Nhập một danh sách thí sinh, sắp xếp danh sách theo thứ tự giảm của tổng điểm. Dưới đây sẽ đưa ra phương án mới bằng cách dùng con trỏ và cấp phát bộ nhớ cho các đối tượng. Chương trình chỉ thay đổi hàm main() như sau:

void main()

{

TS  *ts;

int n, i, j;

clrscr();

cout << "

So thi sinh: " ;

cin >> n;

ts = new TS[n+1];

for (i=1; i<= n; ++i)

ts[i].nhap(i);

cout <<"

Danh sach nhap vao:";

for (i=1; i<= n; ++i)

ts[i].in();

for (i=1; i<n ; ++i)

for (j=i+1 ; j<=n; ++j)

if (ts[i].gettd() < ts[j].gettd())

ts[i].hv(ts[j]);

cout <<"

Danh sach sau khi sap xep:";

for (i=1; i<= n; ++i)

ts[i].in();

getch();

}

220                                                                                                                                                                                                                                             221

Nhận xét: Sự khác biệt giữa hàm main mới và hàm main trong 10.4 là rất ít.

11.4. Danh sách móc nối

Chương trình dưới đây định nghĩa lớp tự trỏ TS (lớp có thuộc tính kiểu *TS). Lớp này được dùng để tổ chức danh sách móc nối. Chương trình nhập một danh sách thí sinh và chứa trong một danh sách móc nối. Sau đó duyệt trên danh sách này để in các thí sinh trúng tuyển. So với lớp TS nêu trong mục 10.4, lớp TS ở đây có một số điểm khác như sau:

+ Thêm thuộc tính:

TS *dc;  // Dùng để chứa địa chỉ của một đối tượng kiểu TS

+ Thêm các phương thức:

void setdc(TS *dc1) ;  // Gán dc1 cho thuộc tính dc

TS *getdc() ;               //  Nhận giá trị của dc

  + Phương thức nhap trong chương trình trước có kiểu void nay sửa là:        

int nhap(int i);

Phương thức trả về 1 nếu họ tên nhập vào khác trống, trả về 0 nếu trái lại.

+ Bỏ đi các phương thức không dùng đến như: Toán tử gán, hoán vị.

//CT4_16.CPP

// Danh sách móc nối

// Lop TS (thi sinh)

#include <conio.h>

#include <iostream.h>

#include <string.h>

#include <ctype.h>

#include <stdio.h>

class TS

{

private:

char *ht;

double td;

TS *dc;

public:

TS()

{

ht = new char[20];

td = 0;

dc=NULL;

}

~TS()

{

delete ht; dc=NULL ;

}

int nhap(int i);

void in();

double gettd()

{

return td;

}

void setdc(TS *dc1)

{

dc=dc1;

}

TS *getdc()

{

return dc;

}

222                                                                                                                                                                                                                                             223

} ;

void TS::in()

{

cout << "

Ho ten: " << ht << "    Tong diem: " << td;

}

int TS::nhap(int i)

{

cout << "

Nhap thi sinh " << i ;

cout << "

Ho ten (Bấm Enter để kết thúc nhập): " ;

fflush(stdin);

gets(ht);

if (ht[0]==0) return 0;

cout << "Tong diem: " ; cin >> td;

dc=NULL;

return 1;

}

void main()

{

int i=0;

TS  *pdau,*p,*q;

pdau=NULL;

clrscr();

while(1)

{

q=new TS;

++i;

if (q->nhap(i)==0)

{

delete q; break;

}

if (pdau==NULL)

pdau = p = q;

else

{

p->setdc(q) ;

p = q;

}

}

/* In */

double diemchuan;

cout << "

Diem chuan: " ;

cin >> diemchuan;

cout <<"

Danh sach trung tuyen:" ;

p=pdau;

while (p!=NULL)

{

if (p->gettd()>=diemchuan)

p->in();

p = p->getdc();

}

getch();

}

§ 12. Đối tượng hằng, phương thức hằng

+ Cũng giống như các phần tử dữ liệu khác, một đối tượng có thể được khai báo là hằng bằng cách dùng từ khoá const. Ví dụ:

class DIEM

{

private:

int x, y;

224                                                                                                                                                                                                                                             225

public:

DIEM()

{

x = y = m = 0;

}

DIEM(int x1, int y1, int m1=15)

{

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

}

...

} ;

const  DIEM  d = DIEM(200,100);  // Khai báo đối tượng hằng

+ Khi khai báo cần sử dụng các hàm tạo để khởi gán giá trị cho đối tượng hằng. Giá trị khởi tạo có thể là các hằng, các biến, các biểu thức và các hàm, ví dụ:

int  x0=100, y0=50; m0 =4;

const  DIEM  d5 = DIEM(x0 + getmaxx()/2, y0 + getmaxy()/2, m0);

+ Các phương thức có thể sử dụng cho các đối tượng hằng là hàm tạo và hàm huỷ. Về lý thuyết các đối tượng hằng không thể bị thay đổi, mà chỉ được tạo ra hoặc huỷ bỏ đi.

Khi dùng một phương thức cho đối tượng hằng, thì CTBD (Chương trình biên dich) sẽ cảnh báo (warning):

Non-const function called for const object

Tuy nhiên, chương trình EXE vẫn được tạo và khi thực hiện chương trình, thì nội dung các đối tượng hằng vẫn bị thay đổi. Chương trình dưới đây sẽ minh hoạ điều này. Chương trình đưa vào lớp PS (phân số). Phương thức toán tử ++ vẫn có thể làm thay đổi đối tượng hằng (mặc dù khi biên dịch có 3 cảnh báo).

//CT4_19.CPP

// doi tuong const

// Lop PS (phan so)

#include <conio.h>

#include <iostream.h>

#include <string.h>

#include <math.h>

class PS

{

private:

int t,m;

public:

PS()

{

t = m = 0;

}

PS(int t1, int m1)

{

t = t1; m = m1;

}

PS operator++()

{

t += m ;

return *this ;

}

void in()

{

cout << "

PS= " << t << "/" << m;

}

void nhap()

{

226                                                                                                                                                                                                                                             227

cout << "

Nhap tu va mau: " ;

cin >> t >> m;

}

} ;

void main()

{

int t1=-3, m1=5;

const PS p = PS(abs(t1)+2,m1+2);  // Khai báo đối tượng hằng

clrscr();

p.in();

++p;

p.in();

getch();

}

+ Phương thức const

Để biến một phương thức thành const ta chỉ việc viết thêm từ khoá const vào sau dòng đầu của phương thức.

Chú ý: Nếu phương thức được khai báo bên trong và định nghĩa bên ngoài lớp, thì từ khoá const cần được bổ sung cả trong khai báo và định nghĩa phương thức.

Trong thân phương thức const không cho phép làm thay đổi các thuộc tính của lớp. Vị vậy việc dùng phương thức const cho các đối tượng hằng sẽ đảm bảo giữ nguyên nội dung của các đối tượng hằng.

Đương nhiên các phương thức const vẫn dùng được cho các đối tượng khác.

Ví dụ sau về lớp PS (phân số) minh hoạ việc dùng phương thức const.

// Đối tượng const

// Phương thức const

// Lop PS (phan so)

#include <conio.h>

#include <iostream.h>

#include <string.h>

#include <math.h>

class PS

{

private:

int t,m;

public:

PS()

{

t = m = 0;

}

PS(int t1, int m1)

{

t = t1; m = m1;

}

PS operator++()

{

t += m ;

return *this ;

}

void in() const ;

void nhap()

{

cout << "

Nhap tu va mau: " ;

cin >> t >> m;

}

228                                                                                                                                                                                                                                             229

} ;

void PS::in() const

{

cout << "

PS= " << t << "/" << m;

}

void main()

{

int t1=-3, m1=5;

const PS p = PS(abs(t1)+2,m1+2);

PS q;

clrscr();

q.nhap();

p.in();

q.in();

getch();

}

§ 13. Hàm bạn, lớp bạn

13.1. Hàm bạn (xem mục §6, chương 3) của một lớp, tuy không phải là phương thức của lớp, nhưng có thể truy nhập đến các thành phần riêng (private) của lớp. Một hàm có thể là bạn của nhiều lớp.

13.2. Nếu lớp A được khai báo là bạn của lớp B thì tất cả các phương thức của A đều có thể truy nhập đến các thành phần riêng của lớp B. Một lớp có thể là bạn của nhiều lớp khác. Cũng có thể khai báo A là bạn của B và B là bạn của A.

13.3. Cách khai báo lớp bạn

Giả sử có 3 lớp A, B và C. Để khai báo lớp này là bạn của lớp kia, ta viết theo mẫu sau:

// Khai báo trước các lớp

class A;

class B ;

class C;

// Định nghĩa các lớp

class A

{

...

friend class B ;  // Lớp B là bạn của A

friend class C ;  // Lớp C là bạn của A

...

};

class B

{

...

friend class A ;  // Lớp A là bạn của B

friend class C ;  // Lớp C là bạn của B

...

};

class C

{

...

friend class B ;  // Lớp B là bạn của C

...

};

13.4. Ví dụ

Chương trình dưới đây có 2 lớp:

MT (ma trận vuông)

230                                                                                                                                                                                                                                             231

VT (véc tơ)

Lớp MT là bạn của VT và lớp VT là bạn của MT. Trong chương trình sử dụng các phương thức trùng tên:

2 phương thức nhap():

nhập ma trận

nhập véc tơ

2 phương thức in():

in ma trận

in véc tơ

4 phương thức tich():

tích ma trận với ma trận, kết quả là ma trận

tích ma trận với véc tơ, kết quả là véc tơ

tích véc tơ với ma trận, kết quả là véc tơ

tích véc tơ với véc tơ, kết quả là số thực

Nội dung chương trình là:

+  Nhập các ma trận A, B, C

+  Nhập các véc tơ

+  Tính tích  D = AB

+ Tính tích  u = Dy

+ Tính tích  v = xC

+ Tính tích  s = vu

//CT4_17.CPP

// Lop ban

// Lop MT , lop VT

#include <conio.h>

#include <iostream.h>

class MT;

class VT;

class MT

{

private:

double a[10][10];

public:

friend class VT;

MT()

{

n=0;

}

void nhap();

void in();

VT tich(const VT &y);

MT tich(const MT &b) ;

} ;

class VT

{

private:

double x[10];

public:

friend class MT;

VT()

{

n=0;

}

void nhap();

void in();

VT tich(const MT &b);

double tich(const VT &y) ;

232                                                                                                                                                                                                                                             233

} ;

void MT::nhap()

{

cout << "

Cap ma tran: " ;

cin >> n;

for (int i=1; i<=n; ++i)

for (int j=1; j<=n; ++j)

{

cout << "

Phan tu hang " << i << "  cot " << j << " = " ;

cin >> a[i][j];

}

}

void MT::in()

{

for (int i=1; i<=n; ++i)

{

cout << "

" ;

for (int j=1; j<=n; ++j)

cout << a[i][j] << " " ;

}

}

void VT::nhap()

{

cout << "

Cap vec to: " ;

cin >> n;

for (int i=1; i<=n; ++i)

{

cout << "

Phan tu thu " << i <<  " = " ;

cin >> x[i];

}

}

void VT::in()

{

for (int i=1; i<=n; ++i)

cout <<  x[i] << " " ;

}

VT MT::tich(const VT &y)

{

VT z;

int i,j;

for (i=1; i<=n; ++i)

{

z.x[i] = 0.0 ;

for (j=1; j<=n; ++j)

z.x[i] += a[i][j]*y.x[j];

}

z.n = n;

return z;

}

MT MT::tich(const MT &b)

{

MT c;

int i,j,k;

for (i=1; i<=n; ++i)

for (j=1; j<=n; ++j)

{

c.a[i][j] = 0.0 ;

for (k=1; k<=n; ++k)

c.a[i][j] += a[i][k]*b.a[k][j];

}

c.n = n;

return c;

234                                                                                                                                                                                                                                             235

}

VT VT::tich(const MT &b)

{

VT z;

int i,j;

for (j=1; j<=n; ++j)

{

z.x[j] = 0.0 ;

for (i=1; i<=n; ++i)

z.x[j] += b.a[i][j]*x[i];

}

z.n = n;

return z;

}

double VT::tich(const VT &y)

{

double tg=0.0;

for (int i=1; i<=n; ++i)

tg += x[i]*y.x[i];

return tg;

  }

void main()

{

MT a,b,c;

VT x,y;

clrscr();

cout << "

Ma tran A";

a.nhap();

cout << "

Ma tran B";

b.nhap();

cout << "

Ma tran C";

c.nhap();

cout << "

vec to X";

x.nhap();

cout << "

vec to Y";

y.nhap();

MT d= a.tich(b);

VT u = d.tich(y);

VT v = x.tich(c);

double s = v.tich(u);

cout << "

Vec to v

";

v.in();

cout << "

Ma tran D";

d.in();

cout << "

Vec to y

";

y.in();

cout << "

S= vDy = " << s;

getch();

}

236                                                                                                                                                                                                                                            

Bạn đang đọc truyện trên: TruyenFun.Vip