Bài giảng Lập trình hướng đối tượng C++ - Chương 4: Hàm tạo, hàm hủy và các vấn đề liên quan

doc 45 trang hoanguyen 4150
Bạn đang xem 20 trang mẫu của tài liệu "Bài giảng Lập trình hướng đối tượng C++ - Chương 4: Hàm tạo, hàm hủy và các vấn đề liên quan", để tải tài liệu gốc về máy bạn click vào nút DOWNLOAD ở trên

Tài liệu đính kèm:

  • docbai_giang_lap_trinh_huong_doi_tuong_c_chuong_4_ham_tao_ham_h.doc

Nội dung text: Bài giảng Lập trình hướng đối tượng C++ - Chương 4: Hàm tạo, hàm hủy và các vấn đề liên quan

  1. Chương 4 + Hàm tạo không có kết quả trả về. Hàm tạo, hàm huỷ và các 1.2.2. Sự giống nhau của hàm tạo và các phương thức thông vấn đề liên quan 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 Chương này trình bầy một số vấn đề có tính chuyên sâu hơn về thức khác: lớp như: + Hàm tạo có thể được xây dựng bên trong hoặc bên ngoài định + Hàm tạo (constructor) nghĩa lớp. + Hàm huỷ (destructor) + Hàm tạo có thể có đối hoặc không có đối. + 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 + Trong một lớp có thể có nhiều hàm tạo (cùng tên nhưng khác bộ đối). + Các thành phần tĩnh + Lớp bạn, hàm bạn Ví dụ sau định nghĩa lớp DIEM_DH (Điểm đồ hoạ) có 3 thuộc tính: + Đối tượng hằng + Phương thức inline 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 § 1. Hàm tạo (constructor) và đưa vào 2 hàm tạo để khởi gán cho các thuộc tính của lớp: 1.1. Công dụng // Hàm tạo không đối: Dùng các giá trị cố định để khởi gán cho Hàm tạo cũng là một phương thức của lớp (nhưng khá đặc biệt) // x, y, m 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 DIEM_DH() ; 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 // Hàm tạo có đối: Dùng các đối x1, y1, m1 để khởi gán cho số công việc khác nhằm chuẩn bị cho đối tượng mới. // x, y, m 1.2. Cách viết hàm tạo // Đối m1 có giá trị mặc định 15 (mầu trắng) DIEM_DH(int x1, int y1, int m1=15) ; 1.2.1. Điểm khác của hàm tạo và các phương thức thông thường class DIEM_DH 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: private: + 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 int x, y, m ; của lớp. public: + Không khai báo kiểu cho hàm tạo. //Hàm tạo không đối: khởi gán cho x=0, y=0, m=1 150 151
  2. // Hàm này viết bên trong định nghĩa lớp // Kết quả d.x=0, d.y=0, d.m=1 DIEM_DH() DIEM_DH u(200,100,4); // Gọi tới hàm tạo có đối. { 152 // Kết quả u.x=200, u.y=100, d.m=4 153 x=y=0; DIEM_DH v(300,250); // Gọi tới hàm tạo có đối. m=1; // 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 // Hàm tạo này xây dựng bên ngoài định nghĩa lớp Chú ý: Với các hàm có đối kiểu lớp, thì đối chỉ xem là các tham DIEM_DH(int x1, int y1, int m1=15) ; số hình thức, vì vậy khai báo đối (trong dòng đầu của hàm) sẽ không // Các phương thức khác 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ớ // Xây dựng hàm tạo bên ngoài định nghĩa lớp + Khi cấp phát bộ nhớ cho một đối tượng có thể dùng các tham số DIEM_DH:: DIEM_DH(int x1, int y1, int m1) để 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 x=x1; y=y1; m=m1; // 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 1.3. Dùng hàm tạo trong khai báo // Kết quả r->x=0, r->y= 0, r->m=1 + Khi đã xây dựng các hàm tạo, ta có thể dùng chúng trong khai + Khi cấp phát bộ nhớ cho một dẫy đối tượng không cho phép báo để tạo ra một đối tượng đồng thời khởi gán cho các thuộc tính dùng tham số để khởi gán, ví dụ: của đối tượng được tạo. Dựa vào các tham số trong khai báo mà int n=20; Trình biên dịch sẽ biết cần gọi đến hàm tạo nào. DIEM_DH *s = new DIEM_DH[n] ; // Gọi tới hàm tạo không + 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. // đối 20 lần. + Khi khai báo mảng đối tượng không cho phép dùng các tham số 1.5. Dùng hàm tạo để biểu diễn các đối tượng hằng để khởi gán. + Như đã biết, sau khi định nghĩa lớp DIEM_DH thì có thể xem + 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 lớp này như một kiểu dữ liệu như int, double, char, + 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 Với kiểu int chúng ta có các hằng int, như 356. lần. Với kiểu double chúng ta có các hằng double, như 98.75 Ví dụ: Khái niệm hằng kiểu int, hằng kiểu double có thể mở rộng cho DIEM_DH d; // Gọi tới hàm tạo không đối. hằng kiểu DIEM_DH
  3. + Để biểu diễn một hằng đối tượng (hay còn gọi: Đối tượng hằng) // Phương thức dùng để in đối tượng DIEM_DH chúng ta phải dùng tới hàm tạo. Mẫu viết như sau: void in() 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: cout x=x1; y=y1; m=m1; #include #include } class DIEM_DH void main() { { private: DIEM_DH d1; // Gọi tới hàm tạo không đối int x,y,m; DIEM_DH d2(200,200,10); // Gọi tới hàm tạo có đối public: DIEM_DH *d; // Hàm bạn dùng để in đối tượng DIEM_DH friend void in(DIEM_DH d) d= new DIEM_DH(300,300); // Gọi tới hàm tạo có đối { clrscr(); cout <<"\n " << d.x << " "<< d.y<<" " << d.m ; in(d1); //Gọi hàm bạn in() } d2.in();//Gọi phương thức in()
  4. in(*d); //Gọi hàm bạn in() #include DIEM_DH(2,2,2).in();//Gọi phương thức in() class DIEM_DH 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 private: int n; int x,y,m; 156 157 cout > n; // Phuong thuc q=new DIEM_DH[n+1]; // (n+1) lần gọi hàm tạo không đối void in() for (int i=0;i in(); gì cả. Như vậy một đối tượng tạo ra chỉ được cấp phát bộ nhớ, còn getch(); 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 2.2. Nếu trong lớp đã có ít nhất một hàm tạo, thì hàm tạo mặc // Hàm tạo mặc định đị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 #include 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
  5. 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: Trong các câu lệnh trên, chỉ có câu lệnh thứ 2 trong hàm main() là #include 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 #include chưa được xây dựng. class DIEM_DH 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. private: - Gán giá trị mặc định cho tất cả các đối x1, y1 và m1 của hàm tạo int x,y,m; đã xây dựng ở trên. 158 159 public: Theo phương án 2, chương trình có thể sửa như sau: // Phương thức dùng để in đối tượng DIEM_DH #include void in() #include { class DIEM_DH cout <<"\n " << x << " "<< y<<" " << m ; { } private: int x,y,m; //Hàm tạo có đối public: DIEM_DH::DIEM_DH(int x1,int y1,int m1) // Phương thức dùng để in đối tượng DIEM_DH { void in() x=x1; y=y1; m=m1; { } cout <<"\n " << x << " "<< y<<" " << m ; }; } //Hàm tạo có đối , tất cả các đối đều có giá trị mặc định void main() DIEM_DH::DIEM_DH(int x1=0,int y1=0,int m1=15) { { DIEM_DH d1(200,200,10); // Gọi tới hàm tạo có đối x=x1; y=y1; m=m1; 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(); void main() d2.in(); { getch(); DIEM_DH d1(200,200,10); // Gọi tới hàm tạo, không dùng
  6. // tham số mặc định + Dùng khai báo để tạo các đối tượng, ví dụ: DIEM_DH d2; // Gọi tới hàm tạo , dùng 3 tham số mặc định DT d; d2= DIEM_DH(300,300); // Gọi tới hàm tạo, dùng 1 tham số + Cấp phát vùng nhớ (cho đối tượng) để chứa đa thức, ví dụ: // mặc định d.n = m; d1.in(); d.a = new double[m+1] ; d2.in(); getch(); 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ớ. § 3. Lớp đa thức Việc dùng các hàm tạo để sản sinh ra các đối tượng hoàn chỉnh tỏ Chương trình dưới đây là sự cải tiến chương trình trong mục 8.5 160ra tiện lợi hơn, vì tránh được các thao tác phụ (như cấp phát bộ nhớ) 161 của chương 3 bằng cách đưa vào 2 hàm tạo: 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: //Hàm tạo không đối + Nội dung chương trình gồm: DT() - Nhập, in các đa thức p, q, r, s { - Tính đa thức: f = -(p + q)*(r - s) this->n=0; this->a=NULL; - 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^) //Hàm tạo có đối - Tính f(x2) (bằng cách dùng hàm F) DT(int n1) // CT4_05.CPP { #include this->n=n1 ; #include this->a = new double[n1+1]; #include } class DT 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. private: Nếu không xây dựng hàm tạo, mà sử dụng hàm tạo mặc định thì int n; // Bac da thuc 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ộ double *a; // Tro toi vung nho chua cac he so da thuc nhớ để chứa đa thức. Như vậy đối tượng tạo ra chưa hoàn chỉnh và // a0, a1, chưa dùng được. Để có một đối tượng hoàn chỉnh phải qua 2 bước:
  7. public: double s=0.0 , t=1.0; DT() int n; { n = int(d[-1]); this->n=0; this->a=NULL; for (int i=0; i n=n1 ; } this->a = new double[n1+1]; return s; } } friend ostream& operator > (istream& is,DT &d); 162 { 163 DT operator-(); os > (istream& is,DT &d) { { if (i > d.n; return a[i]; d.a = new double[d.n+1]; } cout > d.a[i] ;
  8. } DT DT::operator-(DT d2) return is; { } return (*this + (-d2)); DT DT::operator-() } { DT DT::operator*(const DT &d2) DT p(this->n); { for (int i=0 ; i d2.n ? n : d2.n ; return d; DT d(k); } for (i=0; i 0 && d.a[i]==0.0) i; return s; d.n = i; } return d ; void main() } {
  9. DT p,q,r,s,f; PS p1, p2 ; double x1,x2,g1,g2; PS *p = new PS ; clrscr(); + Ta cũng có thể dùng lệnh khai báo để tạo một đối tượng mới từ cout > p; một đối tượng đã tồn tại, ví dụ: cout > q; PS v(u) ; // Tạo v theo u cout > r; - 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 cout > s; vậy các vùng nhớ của u và v sẽ có nội dung như nhau. Rõ ràng trong cout > x1; đủ và không cần xây dựng một hàm tạo sao chép mới. 166 167 cout > x2; - Nếu trong lớp PS đã có hàm tạo sao chép (cách viết sẽ nói sau) g1 = f^x1; thì câu lệnh: g2 = F(f,x2); PS v(u) ; cout >) và xuất ( các đối tượng mới, ví dụ:
  10. #include 4.2. Cách xây dựng hàm tạo sao chép class PS + 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: private: Tên_lớp (const Tên_lớp & dt) int t,m ; { public: // Các câu lệnh dùng các thuộc tính của đối tượng dt friend ostream& operator > (istream& is, PS &p) private: { int t,m ; cout > p.t >> p.m ; 168 PS (const PS &p) 169 return is; { } this->t = p.t ; }; this->m = p.m ; void main() } { PS d; } ; cout > d; cout << "\n PS d " << d; 4.3. Khi nào cần xây dựng hàm tạo sao chép PS u(d); + 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. cout << "\n PS u " << u; + Khi lớp không có các thuộc tính kiểu con trỏ hoặc tham chiếu, getch(); thì dùng hàm tạo sao chép mặc định là đủ. }
  11. + Khi lớp có các thuộc tính con trỏ hoặc tham chiếu, thì hàm tạo /* Nhập đối tượng d , gồm: nhập một số nguyên dương và sao chép mặc định chưa đáp ứng được yêu cầu. Ví dụ lớp DT (đa gán cho d.n, cấp phát vùng nhớ cho d.a, nhập các hệ số thức) trong §3: của đa thức và chứa vào vùng nhớ được cấp phát class DT */ { private: DT u(d) ; int n; // Bac da thuc /* Dùng hàm tạo mặc định để xây dựng đối tượng u theo d double *a; // Tro toi vung nho chua cac he so da thuc Kết quả: u.n = d.n và u.a = d.a. Như vậy 2 con trỏ u.a và // a0, a1, d.a cùng trỏ đến một vùng nhớ. public: */ DT() 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ó this->n=0; this->a=NULL; 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 DT(int n1) 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. this->n=n1 ; Ví dụ sau sẽ minh hoạ nhận xét trên: Khi d thay đổi thì u cũng this->a = new double[n1+1]; 170thay đổi và ngược lại khi u thay đổi thì d cũng thay đổi theo. 171 } //CT4_07.CPP friend ostream& operator friend istream& operator>> (istream& is,DT &d); #include #include } ; class DT { Bây giờ chúng ta hãy theo rõi xem việc dùng hàm tạo mặc định private: trong đoạn chương trình sau sẽ dẫn đến sai lầm như thế nào: int n; // Bac da thuc DT d ; double *a; // Tro toi vung nho chua cac he so da thuc // Tạo đối tượng d kiểu DT // a0, a1, cin >> d ; public:
  12. DT() } { return is; this->n=0; this->a=NULL; } } DT(int n1) void main() { { this->n=n1 ; DT d; this->a = new double[n1+1]; clrscr(); } cout > d; friend ostream& operator > (istream& is,DT &d); cout > d; { cout > u; os > (istream& is,DT &d) } { 172 173 if (d.a!=NULL) delete d.a; 4.4. Ví dụ về hàm tạo sao chép cout > d.n; 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.a = new double[d.n+1]; dụ d) theo các yêu cầu sau: cout > d.a[i] ; của u.a
  13. Như vây chúng ta sẽ tạo được đối tượng u có nội dung ban đầu this->n=0; this->a=NULL; 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 DT(int n1) dựng như sau: { DT::DT(const DT &d) this->n=n1 ; { this->a = new double[n1+1]; this->n = d.n; } this->a = new double[d.n+1]; DT(const DT &d); for (int i=0;i a[i] = d.a[i]; friend istream& operator>> (istream& is,DT &d); } } ; Chương trình sau sẽ minh hoạ điều này: Sự thay đổi của d không DT::DT(const DT &d) 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. this->n = d.n; //CT4_08.CPP this->a = new double[d.n+1]; // Viết hàm tạo sao chép cho lớp DT for (int i=0;i this->a[i] = d.a[i]; #include } #include ostream& operator > (istream& is,DT &d) { {
  14. if (d.a!=NULL) delete d.a; } cout > d.n; d.a = new double[d.n+1]; § 5. Hàm huỷ (Destructor) cout > d.a[i] ; 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ị, return is; 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, void main() + Giải phóng các biến, mảng cục bộ khi thoát khỏi hàm, phương { thức. DT d; clrscr(); 5.2. Hàm huỷ mặc định cout > d; 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ỷ DT u(d); mặc định là đủ, và không cần đưa vào một hàm huỷ mới. cout > d; Mỗi lớp chỉ có một hàm huỷ viết theo các quy tắc sau: cout > u; tên lớp: cout << "\nDa thuc d " << d ; 176 177 ~Tên_lớp cout << "\nDa thuc u " << u ; + Đối: Hàm huỷ không có đối getch();
  15. Ví dụ có thể xây dựng hàm huỷ cho lớp DT (đa thức) ở §3 như được tạo ra. Một vùng nhớ được cấp phát và giao cho nó (đối tượng sau: trung gian) quản lý. class DT + 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ố private: của đa thức) mà đối tượng trung gian đang quản lý thì không hề bị int n; // Bac da thuc giải phóng. Như vậy số vùng nhớ bị chiếm dụng vô ích sẽ tăng lên. double *a; // Tro toi vung nho chua cac he so da thuc 5.4.2. Cách khắc phục // a0, a1, Nhược điểm trên dễ dàng khắc phục bằng cách đưa vào lớp DT public: hàm huỷ viết trong 5.3 (mục trên). ~DT() 5.5. Lớp hình tròn đồ hoạ { Chương trình dưới đây gồm: this->n=0; Lớp HT (hình tròn) với các thuộc tính: delete this->a; 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 5.4. Vai trò của hàm huỷ trong lớp DT int hienmh; // Trạng thái hiện (hienmh=1), ẩn (hienmh=0) 5.4.1. Khiếm khuyết của chương trình trong §3 Các phương thức: Chương trình trong §3 định nghĩa lớp DT (đa thức) khá đầy đủ + Hàm tạo không đối gồm: HT(); + Các hàm tạo thực hiện việc gán giá trị bằng 0 cho các thuộc tính của lớp. + Các hàm toán tử nhập >>, xuất << + Hàm tạo có đối + Các hàm toán tử thực hiện các phép tính + - * HT(int r1,int m1=15); Tuy nhiên vẫn còn thiếu hàm huỷ để giải phóng vùng nhớ mà đối thực hiện các việc: tượng kiểu DT (cần huỷ) đang quản lý. - Gán r1 cho r, m1 cho m Chúng ta hãy phân tích các khiếm khuyết của chương trình này: - Cấp phát bộ nhớ cho pht + Khi chương trình gọi tới một phương thức toán tử để thực hiện - Vẽ hình tròn và lưu ảnh hình tròn vào vùng nhớ của pht các phép tính cộng, trừ, nhân đa thức, thì một đối tượng trung gian 178 179
  16. + Hàm huỷ #include ~HT(); #include thực hiện các việc: void ktdh(); - Xoá hình tròn khỏi màn hình (nếu đang hiển thị) void ve_bau_troi(); - Giải phóng bộ nhớ đã cấp cho pht void ht_di_dong_xuong(); + Phương thức void ht_di_dong_len(); void hien(int x, int y); int xmax,ymax; có nhiệm vụ hiển thị hình tròn tại (x,y) class HT + Phương thức { void an(); private: có nhiệm vụ làm ẩn hình tròn int r,m ; Các hàm độc lập: int xhien,yhien; void ktdh(); //Khởi tạo đồ hoạ char *pht; void ve_bau_troi(); // Vẽ bầu trời đầy sao int hienmh; void ht_di_dong_xuong(); // Vẽ một cặp 2 hình tròn di public: // chuyển xuống HT(); void ht_di_dong_len();// Vẽ một cặp 2 hình tròn di HT(int r1,int m1=15); // chuyển lên trên ~HT(); Nội dung chương trình là tạo ra các chuyển động xuống và lên void hien(int x, int y); của các hình tròn. void an(); //CT4_09.CPP }; // Lop do hoa HT:: HT() // Ham huy { // Trong ham huy co the goi PT khac r=m=hienmh=0; #include xhien=yhien=0; #include pht=NULL; #include } #include
  17. HT::HT(int r1,int m1) if (pht!=NULL && !hienmh) // chua hien { { 180 r=r1; m=m1; hienmh=0; hienmh=1; 181 xhien=x; yhien=y; xhien=yhien=0; putimage(x,y,pht,XOR_PUT); if (r<0) r=0; } if (r==0) } { void HT::an() pht=NULL; { } if (hienmh) // dang hien else { { hienmh=0; int size; char *pmh; putimage(xhien,yhien,pht,XOR_PUT); size = imagesize(0,0,r+r,r+r); } pmh = new char[size]; } getimage(0,0,r+r,r+r,pmh); HT::~HT() setcolor(m); { circle(r,r,r); an(); setfillstyle(1,m); if (pht!=NULL) floodfill(r,r,m); { pht = new char[size]; delete pht; getimage(0,0,r+r,r+r,pht); pht=NULL; putimage(0,0,pmh,COPY_PUT); } delete pmh; } pmh=NULL; void ktdh() } { } int mh=0,mode=0; void HT::hien(int x, int y) initgraph(&mh,&mode,""); { xmax = getmaxx();
  18. ymax = getmaxy(); HT u(60,15); } h.hien(340,340); u.hien(380,340); 182void ve_bau_troi() for (int x=340;x>=0;x-=10) 183 { { for (int i=0;i<2000;++i) h.an(); putpixel(random(xmax), random(ymax), 1+random(15)); u.an(); h.hien(x,x); } delay(200); void ht_di_dong_xuong() u.hien(x+40,x); { delay(200); HT h(50,4); } HT u(60,15); } h.hien(0,0); void main() u.hien(40,0); { for (int x=0;x<=340;x+=10) ktdh(); { ve_bau_troi(); h.an(); ht_di_dong_xuong(); u.an(); ht_di_dong_len(); h.hien(x,x); getch(); delay(200); closegraph(); u.hien(x+40,x); } delay(200); 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ỷ: void ht_di_dong_len() + 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 HT h(50,4); ra ở trên) được giải phóng. Vùng nhớ của các thuộc tính của chúng
  19. bị thu hồi, nhưng vùng nhớ cấp phát cho thuộc tính pht chưa được Phương thức toán tử gán có thể có hoặc không có giá trị trả về. 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 Nếu không có giá trị trả về (kiểu void), thì khi viết chương trình được cất đi. không được phép viết câu lệnh gán liên tiếp nhiều đối tượng, như: + Điều tương tự xẩy ra sau khi ra khỏi hàm ht_di_dong_len() : u = v = k = h ; vùng nhớ cấp phát cho thuộc tính pht chưa được giải phóng và ảnh Nếu phương thức toán tử gán trả về tham chiếu của đối tượng của 2 hình tròn (ở phía trên màn hình) vẫn không được thu dọn. 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. 184 185 § 6. Toán tử gán 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: 6.1. Toán tử gán mặc định void HT::operator=(const HT &h) 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 r = h.r ; m = h.m ; hiện câu lệnh gán 2 đối tượng của lớp, ví du: xhien = yhien = 0; HT h1, h2(100,6); hienmh = 0 ; h1 = h2 ; // Gán h2 cho h1 if (h.pht==NULL) Toán tử gán mặc định sẽ sẽ sao chép đối tượng nguồn (h2) vào pht = NULL; đối tượng đích (h1) theo từng bit một. else 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 int size; lớp có thuộc tính con trỏ như lớp DT (đa thức), lớp HT (hình tròn) size = imagesize(0,0,r+r,r+r); 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ử pht = new char[size]; gán là cần thiết. memcpy(pht,h.pht,size); 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ử Với toán tử gán này, chỉ cho phép gán đối tượng nguồn cho mộ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 đối tượng đích. làm việc trực tiếp với đối tượng nguồn, nên kiểu đối tường minh Như vậy câu lệnh sau là sai: nhất thiết phải là kiểu tham chiếu đối tượng. HT u, v, h ; u = v = h ;
  20. Bây giờ ta sửa lại toán gán để nó trả về tham chiếu đối tượng + 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 nguồn như sau: định thì chưa đủ, vì việc khởi gán trong câu lệnh khai báo sẽ không const HT & HT::operator=(const HT &h) 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: r = h.r ; m = h.m ; - Hàm huỷ xhien = yhien = 0; - Hàm tạo sao chép hienmh = 0 ; - Phương thức toán tử gán if (h.pht==NULL) Chú ý: Không phải mọi câu lệnh chứa có dấu = đều gọi đến toán pht = NULL; tử gán. Cần phân biệt 3 trường hợp: else 186 1. Câu lệnh new (chứa dấu =) sẽ gọi đến hàm tạo, ví dụ: 187 { HT *h= new HT(50,6); // gọi đến hàm tạo có đối int size; 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 size = imagesize(0,0,r+r,r+r); sao chép, ví dụ: pht = new char[size]; HT k=*h; // gọi đến hàm tạo sao chep memcpy(pht,h.pht,size); 3. Câu lệnh gán sẽ gọi đến toán tử gán, ví dụ: } HT u; return h ; u=*h; // gọi đến phương thức toán tử gán } 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 6.4. Ví dụ minh hoạ nguồn cho nhiều đối tượng đích. Như vậy các câu lệnh sau là được: Chương trình dưới đây định nghĩa lớp HT (hình tròn) và minh HT u, v, h ; hoạ: u = v = h ; + Hàm tạo và hàm huỷ + Phương thức toán tử gán có kiểu tham chiếu 6.3. Toán tử gán và hàm tạo sao chép + 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 + Cách dùng con trỏ this trong hàm tạo sao chép giữa 2 đối tượng đã tồn tại. + Cách dùng con trỏ _new_handler để kiểm tra việc cấp phát + Hàm tạo sao chép được dùng để tạo một đối tượng mới và gán bộ nhớ. nội dung của một đối tượng đã tồn tại cho đối tượng mới vừa tạo. //CT4_10.CPP // Lop do hoa
  21. // Ham huy HT(); // toan tu gan - tra ve tham chieu HT(int r1,int m1=15); // Ham tao sao chep HT(const HT &h); // Trong ham huy co the goi PT khac ~HT(); #include void hien(int x, int y); #include void an(); #include const HT &operator=(const HT &h); #include }; #include const HT & HT::operator=(const HT &h) #include { static void kiem_tra_bo_nho() ; // outtextxy(1,1,"Gan"); getch(); void ktdh(); r = h.r ; m = h.m ; int xmax,ymax; xhien = yhien = 0; void kiem_tra_bo_nho() hienmh = 0 ; 188 189 { if (h.pht==NULL) outtextxy(1,1,"LOI BO NHO"); pht = NULL; getch(); else closegraph(); { exit(1); int size; } size = imagesize(0,0,r+r,r+r); class HT pht = new char[size]; { memcpy(pht,h.pht,size); private: } int r,m ; return h; int xhien,yhien; } char *pht; HT::HT(const HT &h) int hienmh; { public:
  22. //outtextxy(300,1,"constructor sao chep"); getch(); pht = new char[size]; *this = h; getimage(0,0,r+r,r+r,pht); } putimage(0,0,pmh,COPY_PUT); delete pmh; HT:: HT() pmh=NULL; { } r=m=hienmh=0; } xhien=yhien=0; pht=NULL; void HT::hien(int x, int y) } { if (pht!=NULL && !hienmh) // chua hien HT::HT(int r1,int m1) { { hienmh=1; r=r1; m=m1; hienmh=0; xhien=x; yhien=y; xhien=yhien=0; putimage(x,y,pht,XOR_PUT); if (r<0) r=0; } if (r==0) } { void HT::an() 190 191 pht=NULL; { } if (hienmh) // dang hien else { { hienmh=0; int size; char *pmh; putimage(xhien,yhien,pht,XOR_PUT); size = imagesize(0,0,r+r,r+r); } pmh = new char[size]; } getimage(0,0,r+r,r+r,pmh); setcolor(m); HT::~HT() circle(r,r,r); { setfillstyle(1,m); an(); floodfill(r,r,m); if (pht!=NULL)
  23. { } delete pht; 6.5. Vai trò của phương thức toán tử gán pht=NULL; 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í void ktdh() (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). int mh=0,mode=0; + Nếu bỏ hàm tạo sao chép (giữ toán tử gán) thì xuất hiện 4 hình initgraph(&mh,&mode,""); tròn. xmax = getmaxx(); ymax = getmaxy(); § 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 void main() Có thể chia phương thức thành các nhóm: { 1. Các phương thức thông thường _new_handler = kiem_tra_bo_nho ; 2. Các phương thức dùng để xây dựng và huỷ bỏ đối tượng gồm: ktdh(); + Hàm tạo không đối, HT *h= new HT(50,6); // gọi hàm tạo có đối + Hàm tạo có đối h->hien(100,200); + Hàm tạo sao chép 192 193 HT k=*h; // gọi hàm tạo sao chép + Hàm huỷ k.hien(200,200); 3. Các phương thức toán tử HT t,v,u; t = v = u = *h; // gọi toán tử gán 7.2. Con trỏ this u.hien(300,200); 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 v.hien(400,200); khai báo như đối của hàm. t.hien(500,200); + Với các phương thức thông thường, thì đối ẩn biểu thị đối tượng getch(); chủ thể trong lời gọi phương thức. closegraph();
  24. + Với các hàm tạo, thì đối ẩn biểu thị đối tượng mới được hình } thành. PS(int t1, int m1); + Với các hàm huỷ, thì đối ẩn biểu thị đối tượng sắp bị huỷ bỏ. void nhap(); + Với các phương thức toán tử, thì đối ẩn biểu thị toán hạng đối void in(); tượng thứ nhất. PS operator*=(PS p2) 7.3. Phương thức inline. { Có 2 cách để biên dịch phương thức theo kiểu inline: t*=p2.t; m*=p2.m; Cách 1: Xây dựng phương thức bên trong định nghĩa lớp. return *this; 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. inline PS::PS(int t1, int m1) 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. t=t1; Trong chương trình dưới đây, tất cả các phương thức của lớp PS m=m1; (phân số) đều là phương thức inline } //CT4_11.CPP // Lop PS inline void PS::nhap() // Inline { #include cout cin >> t >> m; class PS } 194 { inline void PS::in() 195 private: { int t,m ; cout << "\nPS = " << t << "/" << m ; public: } PS() void main() { { t=0;m=1; PS q,p,s(3,5);
  25. cout << "\n Nhap PS p"; class C p.nhap(); { s.in(); private: p.in(); int m, n; q = p*=s; A u; p.in(); B p, q; q.in(); getch(); } ; } Trong ví dụ trên thì: C là lớp bao § 8. Hàm tạo và đối tượng thành phần A, B là các lớp thành phần (của C) 8.1. Lớp bao, lớp thành phần 8.2. Hàm tạo của lớp bao 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í + Chú ý là trong các phương thức của lớp bao không cho phép dụ: 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 class A 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 private: 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. int a, b; 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. class B 8.3. Cách dùng hàm tạo của lớp thành phần để xây dựng hàm 196 { tạo của lớp bao 197 private: + Để 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: double x, y, z; 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:
  26. 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ị) } ; { class B // Các câu lệnh trong thân hàm tạo { } private: Chú ý là các dấu ngoặc sau tên đối tượng luôn luôn phải có, ngay double x, y, z; cả khi danh sách giá trị bên trong là rỗng. public: + Danh sách giá trị lấy từ danh sách đối. Dựa vào danh sách giá B() 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. x = y = z = 0.0 ; + 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 B(double x1, double y1) 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. { x = x1; y = y1; z = 0.0 ; Ví dụ: } class A { B(double x1, double y1, double z1) private: { int a, b; x = x1; y = y1; z = z1 ; public: } A() { } ; 198 a=b=0; class C 199 } { A(int a1, int b1) private: { int m, n; a = a1; b = b1; A u, v;
  27. B p, q, r; Trong ví dụ này xét 2 lớp: public: DIEM (Điểm) và DT (Đoạn thẳng) C(int m1, int n1,int a1, int b1, double x1, double y1, Lớp DIEM là lớp thành phần của lớp DT double x2, double y2, double z2) : u(), v(a1,b1), //CT4_12.CPP q(x1,y1), r(x2,y2,z2) // Thuoc tinh doi tuong { #include m = m1 ; n = n1; #include } class DIEM } ; { Trong hàm tạo nói trên của lớp C, thì các đối tượng thành phần private: được khởi gán như sau: int x,y ; u được khởi gán bằng hàm tạo không đối của lớp A public: v được khởi gán bằng hàm tạo 2 đối của lớp A DIEM() 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 x=y=0; 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 DIEM(int x1, int y1) 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 x= x1; y=y1; 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ó void in() thể lấy ra các thuộc tính của lớp. { + 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. cout << "(" << x << "," << y << ")" ; } 8.5. Các ví dụ 200 } ; 201 Hai chương trình dưới đây minh hoạ các điều đã nói trong các class DT mục trên. { Ví dụ 1: private:
  28. DIEM d1, d2; DT u, v(1,100,100,200,200), s(2,DIEM(300,300), int m; DIEM(400,400)) ; public: clrscr(); DT() : d1(), d2() u.in(); { v.in(); m=0; s.in(); } getch(); } DT(int m1,int x1, int y1, int x2, int y2) : d1(x1,y1), d2(x2,y2) Ví dụ 2: { Trong ví dụ này xét 3 lớp: m=m1; Diem (Điểm) } DTron (Đường tròn) DT(int m1,DIEM t1, DIEM t2) HTron (Hình tròn) { Lớp DTron có một lớp thành phần là lớp Diem. m=m1; Lớp HTron có 2 lớp thành phần là lớp DTron và lớp Diem. d1 = t1; Trong lớp DTron đưa vào phương thức vẽ đường tròn. d2 = t2; 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. void in() //CT4_13.CPP { // Thuoc tinh doi tuong cout cout cout class Diem } { }; private: void main() int x,y ; 202 { 203 public:
  29. Diem() DTron(int x1,int y1,int r1,int m1): t(x1,y1) { { x=y=0; m=m1; r=r1; } } Diem(int x1, int y1) int mau() { { x= x1; y=y1; return m; } } int getx() void ve() { { return x; setcolor(m); } circle(t.getx(),t.gety(),r); } int gety() }; { class HTron return y; { } private: } ; DTron dt; class DTron // Duong tron Diem d; { int m; private: public: Diem t ; // tam HTron() int r ; { int m; m=0; public: } DTron() HTron(int x1, int y1, int r1, int m1, { int x, int y, int mt): dt(x1,y1,r1,m1), d(x,y) r=m=0; { } m = mt; 204 205
  30. } void ve() } ; { 206+ Thành phần tĩnh được cấp phát một vùng nhớ cố định. Nó tồn 207 dt.ve(); tại ngay cả khi lớp chưa có một đối tượng nào cả. setfillstyle(1,m); + Thành phần tĩnh là chung cho cả lớp, nó không phải là riêng của floodfill(d.getx(),d.gety(),dt.mau()); 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: void main() 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ớ int mh=0, mode=0; thành phần ts tồn tại ngay khi u và v chưa khai báo initgraph(&mh,&mode,""); + Để biểu thị thành phần tĩnh, ta có thể dùng tên lớp, ví du: Đối setbkcolor(1); với ts thì 3 cách viết sau là tương đương: DTron dt(100,100,80,6); A::ts u.ts v.ts HTron ht(300,300,150,15,300,300,4); + Khai báo và khởi gán giá trị cho thành phần tĩnh dt.ve(); Thành phần tĩnh sẽ được cấp phát bộ nhớ và khởi gán giá trị ban ht.ve(); đầu bằng một câu lệnh khai báo đặt sau định nghĩa lớp (bên ngoài getch(); các hàm, kể cả hàm main), theo các mẫu: closegraph(); 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ụ § 9. Các thành phần tĩnh xét chương trình sau: 9.1. Thành phần dữ liệu tĩnh #include + Thành phần dữ liệu được khai báo bằng từ khoá static gọi là #include tĩnh, ví dụ: class HDBH // Hoá đơn bán hàng class A { { private: private: static int ts ; // Thành phần tĩnh char *tenhang ; // Tên hàng int x;
  31. double tienban ; // Tiền bán class HDBH static int tshd ; // Tổng số hoá đơn { static double tstienban ; // Tổng số tiền bán private: public: int shd ; static void in() char *tenhang ; 208 209 { double tienban ; cout đã bán, tổng số tiền đã bán, thì các thông tin này là chung. Vì vậy khi #include
  32. thiết kế lớp HDBH (hoá đơn bán hàng) , thì ta có thể đưa vào 4 thành u.in(); phần dữ liệu là: v.in(); tenhang (tên hàng) HDBH::in(); tienban (tiền bán) + Vì phương thức tĩnh là độc lập với các đối tượng, nên không thể tshd (tổng số hoá đơn) dùng phương thức tĩnh để xử lý dữ liệu của các đối tượng chủ thể tstienban (tổng số tiền bán) 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 Các thuộc tính tenhang và tienban là riêng của mỗi hoá đơn, nên tĩnh. Điều đó cũng đồng nghĩa với việc không cho phép dùng con trỏ chúng được chọn là các thuộc tính thông thường. Còn các thuộc tính this trong phương thức tĩnh. tshd và tstienban là chung cho cả lớp nên chúng được chọn là các 210 211 thuộc tính 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: 9.3. Phương thức tĩnh class HDBH + 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 private: viết bên trong định nghĩa lớp (như phương thưc in() ví dụ cuối của int shd ; mục 9.1). char *tenhang ; 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 double tienban ; nghĩa lớp. Chú ý không cho phép dùng từ khoá static đặt trước định static int tshd ; nghĩa phương thức viết bên ngoài định nghĩa lóp. static double tstienban ; + Phương thức tĩnh là chung cho cả lớp, nó không lệ thuộc vào public: 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). static void in() + 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 cout <<"\n" << tshd; các phương thức khác) cout <<"\n" << tstienban; Có thể dùng tên lớp cout <<"\n" << tenhang; Ví dụ xét lớp HDBH trong mục 9.1 và xét các câu lênh: cout <<"\n" << tienban; 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:
  33. thì sẽ bị lỗi, vì trong thân phương thức tĩnh không cho phép truy tstienban += tienban; 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 ~HDBH() 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 tshd; 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 tstienban -= tienban; đề đặ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 void sua(); là bao nhiêu? 212 static void in(); 213 Chương trình dưới đây nhằm đáp ứng yêu cầu đặt ra. } ; //CT4_14.CPP int HDBH::tshd=0; // thanh phan tinh double HDBH::tstienban=0; // Lop HDBH (hoa don ban hang) void HDBH::in() #include { #include cout > tienban; tstienban += tienban; tienban=tienban1; } tenhang=tenhang1; ++tshd; void main()
  34. { 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 HDBH *h1 = new HDBH("Xi mang",2000); 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. HDBH *h2 = new HDBH("Sat thep",3000); HDBH *h3 = new HDBH("Ti vi",4000); 10.2. Khai báo và khởi gán clrscr(); Để khai báo mảng và khởi gán giá trị cho các phần tử mảng đối HDBH::in(); tượng, cần dùng các hàm tạo có đối theo mẫu sau: getch(); Tên_lớp tên_mảng[kích_cớ] = { Tên_lớp(các tham số), , delete h1; Tên_lớp(các tham số) } ; HDBH::in(); Ví dụ giả sử lớp DIEM đã định nghĩa: getch(); 214class DIEM 215 h2->sua(); { HDBH::in(); private: getch(); int x, y ; delete h3; public: HDBH::in(); DIEM() getch(); { } x=y=0; } DIEM(int x1, int y1) § 10. Mảng đối tượng { 10.1. Khai báo x=x1; y=y1; 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ỡ] ; void nhapsl(); Ví dụ giả sử đã định nghĩa lớp DIEM (Điểm), khi đó có thể khai void ve_doan_thang(DIEM d2, int mau) ; báo các mảng đối tượng DIEM như sau: }; DIEM a[10], b[20] ; Khi đó các câu lệnh khai báo dưới đây đều đúng: ý nghĩa: a là mảng kiểu DIEM gồm 10 phần tử DIEM d[5] = {DIEM(1,1),DIEM(200,200)}; b là mảng kiểu DIEM gồm 20 phần tử DIEM u[] = {DIEM(1,1),DIEM(200,200)};
  35. ý nghĩa của các lệnh này như sau: #include 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à #include 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]. class TS 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ử. private: 10.3. Biểu thị thành phần của phần tử mảng char *ht; Để biểu thị thuộc tính của phần tử mảng đối tượng, ta viết như double td; sau: public: Tên_mảng[chỉ số] . Tên_thuộc_tính TS() Để 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ố) ; ht = new char[20]; Ví dụ để vẽ đoạn thẳng nối điểm d[1] với d[2] theo mầu đỏ, ta có 216 td = 0; 217 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] ~TS() { 10.4. Ví dụ delete ht; 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ạ: const TS &operator=(const TS &ts2) + Cách dùng mảng đối tượng. { + Vai trò con trỏ this (trong phương thức hv(hoán vị)) . this->td = ts2.td; + Các hàm tạo, hàm huỷ. strcpy(this->ht,ts2.ht); + Vai trò của toán tử gán (nếu sử dụng phép gán mặc định chương return ts2; trình sẽ cho kết quả sai). } //CT4_15.CPP void nhap(int i); // mang doi tuong void in(); // Lop TS (thi sinh) double gettd() // Chu y vai tro cua toan tu gan { #include
  36. return td; ts[i].nhap(i); } cout > ht; trong mục trên, ta có thể thực hiện các lệnh cấp phát bộ nhớ như sau: cout > td; int n = 10; } DIEM *p, *q, *r ; void main() 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 TS ts[100]; r = new DIEM(200,100); // Cấp phát bộ nhớ và khởi gán cho int n, i, j; // một đối tượng clrscr(); cout > n; + Giả sử con trỏ p trỏ tới vùng nhớ của một đối tượng nào đó. Khi for (i=1; i<= n; ++i) đó:
  37. - Để biểu thị một thành phần (thuộc tính hoặc phương thức) for (i=1; i tên_thành_phần cout tên_thành_phần cout > n; void setdc(TS *dc1) ; // Gán dc1 cho thuộc tính dc ts = new TS[n+1]; TS *getdc() ; // Nhận giá trị của dc
  38. + Phương thức nhap trong chương trình trước có kiểu void nay { sửa là: delete ht; dc=NULL ; 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 int nhap(int i); trái lại. void in(); + Bỏ đi các phương thức không dùng đến như: Toán tử gán, hoán double gettd() vị. { //CT4_16.CPP return td; // Danh sách móc nối } void setdc(TS *dc1) // Lop TS (thi sinh) { #include dc=dc1; #include } #include TS *getdc() { #include return dc; #include } class TS } ; { void TS::in() private: 222 223 { char *ht; double td; cout << "\nHo ten: " << ht << " Tong diem: " << td; TS *dc; } public: int TS::nhap(int i) TS() { { cout << "\nNhap thi sinh " << i ; ht = new char[20]; td = 0; cout << "\nHo ten (Bấm Enter để kết thúc nhập): " ; dc=NULL; fflush(stdin); } gets(ht); ~TS() if (ht[0]==0) return 0;
  39. cout > td; cin >> diemchuan; dc=NULL; cout gettd()>=diemchuan) { p->in(); int i=0; p = p->getdc(); TS *pdau,*p,*q; } pdau=NULL; getch(); clrscr(); } while(1) { § 12. Đối tượng hằng, phương thức hằng q=new TS; + Cũng giống như các phần tử dữ liệu khác, một đối tượng có thể ++i; được khai báo là hằng bằng cách dùng từ khoá const. Ví dụ: if (q->nhap(i)==0) class DIEM { { delete q; break; private: } int x, y; if (pdau==NULL) int m; pdau = p = q; public: 224 225 else DIEM() { { p->setdc(q) ; x = y = m = 0; p = q; } } DIEM(int x1, int y1, int m1=15) } { /* In */ x= x1; y= y1; m= m1; double diemchuan; } cout << "\nDiem chuan: " ;
  40. int t,m; } ; public: const DIEM d = DIEM(200,100); // Khai báo đối tượng hằng PS() + 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 t = m = 0; thức và các hàm, ví dụ: } int x0=100, y0=50; m0 =4; PS(int t1, int m1) const DIEM d5 = DIEM(x0 + getmaxx()/2, y0 + getmaxy()/2, { m0); t = t1; m = m1; + 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 PS operator++() đổ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): t += m ; return *this ; 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. void in() 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 cout cin >> t >> m; #include 226 } 227 #include } ; #include class PS void main() { { private: int t1=-3, m1=5; const PS p = PS(abs(t1)+2,m1+2); // Khai báo đối tượng hằng
  41. clrscr(); int t,m; p.in(); public: ++p; PS() p.in(); { getch(); t = m = 0; } } + Phương thức const PS(int t1, int m1) Để 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. t = t1; m = m1; 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 PS operator++() 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 += m ; tượng hằng sẽ đảm bảo giữ nguyên nội dung của các đối tượng hằng. return *this ; Đương nhiên các phương thức const vẫn dùng được cho các đối } tượng khác. void in() const ; Ví dụ sau về lớp PS (phân số) minh hoạ việc dùng phương thức const. void nhap() // Đối tượng const { // Phương thức const cout > t >> m; #include } #include } ; #include void PS::in() const 228 229 #include { class PS cout << "\nPS= " << t << "/" << m; { } private: void main()
  42. { { int t1=-3, m1=5; const PS p = PS(abs(t1)+2,m1+2); friend class B ; // Lớp B là bạn của A PS q; friend class C ; // Lớp C là bạn của A clrscr(); q.nhap(); }; p.in(); class B q.in(); { getch(); } friend class A ; // Lớp A là bạn của B friend class C ; // Lớp C là bạn của B § 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 class C 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 friend class B ; // Lớp B là bạn của C 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 13.4. Ví dụ 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: Chương trình dưới đây có 2 lớp: // Khai báo trước các lớp MT (ma trận vuông) class A; VT (véc tơ) class B ; Lớp MT là bạn của VT và lớp VT là bạn của MT. Trong chương 230trình sử dụng các phương thức trùng tên: 231 class C; 2 phương thức nhap(): // Định nghĩa các lớp nhập ma trận class A nhập véc tơ
  43. 2 phương thức in(): MT() in ma trận { in véc tơ n=0; 4 phương thức tich(): } tích ma trận với ma trận, kết quả là ma trận void nhap(); tích ma trận với véc tơ, kết quả là véc tơ void in(); tích véc tơ với ma trận, kết quả là véc tơ VT tich(const VT &y); tích véc tơ với véc tơ, kết quả là số thực MT tich(const MT &b) ; Nội dung chương trình là: } ; + Nhập các ma trận A, B, C class VT + Nhập các véc tơ { + Tính tích D = AB private: + Tính tích u = Dy double x[10]; + Tính tích v = xC int n; + Tính tích s = vu public: //CT4_17.CPP friend class MT; // Lop ban // Lop MT , lop VT VT() #include { #include n=0; class MT; } class VT; void nhap(); class MT void in(); { VT tich(const MT &b); private: double tich(const VT &y) ; double a[10][10]; } ; int n; void MT::nhap() 232 233 public: { friend class VT; cout << "\n Cap ma tran: " ;
  44. cin >> n; for (int i=1; i > a[i][j]; int i,j; } for (i=1; i > n; { for (int i=1; i > x[i]; } } c.n = n; } return c; void VT::in() } { VT VT::tich(const MT &b) 234 235
  45. { b.nhap(); VT z; cout << "\nMa tran C"; int i,j; c.nhap(); for (j=1; j<=n; ++j) cout << "\nvec to X"; { x.nhap(); z.x[j] = 0.0 ; cout << "\nvec to Y"; for (i=1; i<=n; ++i) y.nhap(); z.x[j] += b.a[i][j]*x[i]; MT d= a.tich(b); } VT u = d.tich(y); z.n = n; VT v = x.tich(c); return z; double s = v.tich(u); } cout << "\n\nVec to v\n"; double VT::tich(const VT &y) v.in(); { cout << "\n\nMa tran D"; double tg=0.0; d.in(); for (int i=1; i<=n; ++i) cout << "\n\nVec to y\n"; tg += x[i]*y.x[i]; y.in(); return tg; cout << "\n\nS= vDy = " << s; } getch(); void main() } { MT a,b,c; VT x,y; clrscr(); cout << "\nMa tran A"; a.nhap(); cout << "\nMa tran B"; 236