Giáo trình Lập trình hướng đối tượng (Phần 2) - Nghề: Lập trình máy tính - Trình độ: Cao đẳng nghề

pdf 41 trang Gia Huy 17/05/2022 5270
Bạn đang xem 20 trang mẫu của tài liệu "Giáo trình Lập trình hướng đối tượng (Phần 2) - Nghề: Lập trình máy tính - Trình độ: Cao đẳng nghề", để 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:

  • pdfgiao_trinh_lap_trinh_huong_doi_tuong_phan_2_nghe_lap_trinh_m.pdf

Nội dung text: Giáo trình Lập trình hướng đối tượng (Phần 2) - Nghề: Lập trình máy tính - Trình độ: Cao đẳng nghề

  1. friend istream& operator>>(istream&,SO&); friend ostream& operator >so1; cin>>so2; cout >(istream& nhap,SO& so) { cout > so.giatri; return nhap; } ostream& operator > trên lớp Time (bài tập 1 chương 3). 2. Định nghĩa các phép toán tải bội =, ==, ++, , +=, > trên lớp Date (bài tập 2 chương 3). 1. Định nghĩa các phép toán tải bội +, -, *, =, ==, != trên lớp các ma trận vuông. 2. Định nghĩa các phép toán tải bội +, -, * trên lớp đa thức. 3. Định nghĩa các phép toán tải bội +, -, *, /, =, ==, +=, -=, *=, /= , , =, != , ++, trên lớp Phanso (bài tập 10 chương 3). 4. Ma trận được xem là một vect mà mỗi thành phần của nó là một vector. Theo nghĩa đó, hãy định nghĩa lớp Matran dựa trên vector. Tìm cách để chương trình dịch hiểu được phép truy nhập m[i][j], trong đó m là một đối tượng thuộc lớp Matran. BÀI 6 SỰ KẾ THỪA MÃ BÀI: ITPRG02.6 69
  2. Giới thiệu: Kế thừa là một trong các khái niệm cơ sở của phương pháp lập trình hướng đối tượng. Tính kế thừa cho phép định nghĩa các lớp mới từ các lớp đã có. Một lớp có thể là lớp cơ sở cho nhiều lớp dẫn xuất khác nhau. Lớp dẫn xuất sẽ kế thừa một số thành phần (dữ liệu và hàm) của lớp cơ sở, đồng thời có thêm những thành phần mới. Mục tiêu thực hiện: Học xong bài này học viên sẽ có khả năng: - Kế thừa được các lớp của ngôn ngữ C++ trong lập trình hướng đối tượng. - Sử dụng được các hàm tạo và hàm hủy đối với các lớp dẫn xuất. - Sử dụng hàm ảo đúng, hiệu quả. Nội dung: 6.1. Các loại kế thừa 6.2. Đơn kế thừa 6.3. Đa kế thừa 6.1. Các loại kế thừa Có hai loại kế thừa là: đơn kế thừa và đa kế thừa, có thể minh họa qua các hình vẽ sau đây: A B Hình 6.1. Đơn kế thừa, lớp A là lớp cơ sở của lớp B A A A B C B C B C D D (a) (b) (c) Hình 6.2. Đa kế thừa Hình (a): Lớp A là lớp cơ sở của lớp B, lớp B là lớp cơ sở của lớp C Hình (b): Lớp A là lớp cơ sở của các lớp B, C, D Hình (c): Lớp A, B, C là lớp cơ sở của lớp D 6.2. Đơn kế thừa 70
  3. 6.2.1. Định nghĩa lớp dẫn xuất từ một lớp cơ sở Gi sử đã định nghĩa lớp A. Cú pháp để xây dựng lớp B dẫn xuất từ lớp A như sau: class B: mode A { private: // Khai báo các thuộc tính của lớp B public: // Định nghĩa các hàm thành phần của lớp B }; Trong đó mode có thể là private hoặc public với ý nghĩa như sau: - Kế thừa theo kiểu public thì tất cả các thành phần public của lớp cơ sở cũng là thành phần public của lớp dẫn xuất. - Kế thừa theo kiểu private thì tất cả các thành phần public của lớp cơ sở sẽ trở thành các thành phần private của lớp dẫn xuất. Chú ý: Trong cả hai trường hợp ở trên thì thành phần private của lớp cơ sở là không được kế thừa. Như vậy trong lớp dẫn xuất không cho phép truy nhập đến các thành phần private của lớp cơ sở. 6.2.2. Truy nhập các thành phần trong lớp dẫn xuất Thành phần của lớp dẫn xuất bao gồm: các thành phần khai báo trong lớp dẫn xuất và các thành phần mà lớp dẫn xuất thừa kế từ các lớp cơ sở. Quy tắc sử dụng các thành phần trong lớp dẫn xuất được thực hiện theo theo mẫu như sau: Tên đối tượng.Tên_lớp::Tên_thành_phần Khi đó chương trình dịch C++ dễ dàng phân biệt thành phần thuộc lớp nào. Ví dụ Giả sử có các lớp A và B như sau: class A { public: int n; void nhap() { cout >n; } }; class B: public A { public: int m; void nhap() { cout<<”\n Nhap m = ”; 71
  4. cin>>m; } }; Xét khai báo: B ob; Lúc đó: ob.B::m là thuộc tính n khai báo trong B ob.A::n là thuộc tính n thừa kế từ lớp A ob.D::nhap() là hàm nhap() định nghĩa trong lớp B ob.A::nhap() là hàm nhap() định nghĩa trong lớp A Chú ý: Để sử dụng các thành phần của lớp dẫn xuất, có thể không dùng tên lớp, chỉ dùng tên thành phần. Khi đó chương trình dịch phải tự phán đoán để biết thành phần đó thuộc lớp nào: trước tiên xem thành phần đang xét có trùng tên với các thành phần nào của lớp dẫn xuất không? Nếu trùng thì đó thành phần của lớp dẫn xuất. Nếu không trùng thì tiếp tục xét các lớp cơ sở theo thứ tự: các lớp có quan hệ gần với lớp dẫn xuất sẽ được xét trước, các lớp quan hệ xa hơn xét sau. Chú ý trường hợp thành phần đang xét có mặt đồng thời trong 2 lớp cơ sở có cùng một đẳng cấp quan hệ với lớp dẫn xuất. Trường hợp này chương trình dịch không thể quyết định được thành phần này thừa kế từ lớp nào và sẽ đưa ra một thông báo lỗi. 6.2.3. Định nghĩa lại các hàm thành phần của lớp cơ sở trong lớp dẫn xuất Trong lớp dẫn xuất có thể định nghĩa lại hàm thành phần của lớp cơ sở. Như vậy có hai phiên bản khác nhau của hàm thành phần trong lớp dẫn xuất. Trong phạm vi lớp dẫn xuất, hàm định nghĩa lại “che khuất” hàm được định nghĩa. Việc sử dụng hàm nào cần tuân theo quy định ở trên. Chú ý: Việc định nghĩa lại hàm thành phần khác với định nghĩa hàm quá tải. Hàm định nghĩa lại và hàm bị định nghĩa lại giống nhau về tên, tham số và giá trị trả về. Chúng chỉ khác nhau về vị trí: một hàm đặt trong lớp dẫn xuất và hàm kia thì ở trong lớp cơ sở. Trong khi đó, các hàm quá tải chỉ có cùng tên, nhưng khác nhau về danh sách tham số và tất cả chúng thuộc cùng một lớp. Định nghĩa lại hàm thành phần chính là cơ sở cho việc xây dựng tính đa hình của các hàm. C++ cho phép đặt trùng tên thuộc tính trong các lớp cơ sở và lớp dẫn xuất. Các thành phần cùng tên này có thể cùng kiểu hay khác kiểu. Lúc này bên trong đối tượng của lớp dẫn xuất có tới hai thành phần khác nhau có cùng tên, nhưng trong phạm vi lớp dẫn xuất, tên chung đó nhằm chỉ định thành phần được khai báo lại trong lớp dẫn xuất. Khi muốn chỉ định thành phần trùng tên trong lớp cơ sở phải dùng tên lớp toán tử ‘::’ đặt trước tên hàm thành phần. Ví dụ Xét các lớp A và B được xây dựng như sau: class A { 72
  5. private: int a, b, c; public: void nhap() { cout >a; cout >b; cout >c; } void hienthi() { cout >d; } }; Lớp B kế thừa theo kiểu private từ lớp A, các thành phần public của lớp A là các hàm nhap() và hienthi() trở thành thành phần private của lớp B. Xét khai báo : B ob; Lúc đó các câu lệnh sau đây là lỗi : ob.nhap(); ob.d=10; ob.a = ob.b = ob.c = 5; ob.hienthi(); bởi vì đối tượng ob không thể truy nhập vào các thành phần private của lớp A và B. Ví dụ Chương trình minh họa đơn kế thừa theo kiểu public: #include #include class A { int a; //Bien private, khong duoc ke thua public: int b; //Bien public, se duoc ke thua void get_ab(); int get_a(void); void show_a(void); }; 73
  6. class B: public A { int c; public: void mul(void); void display(void); }; void A::get_ab(void) { a = 5; b = 10;} int A::get_a() { return a;} void A::show_a() { cout << "a = " << a << " \n";} void B::mul() { c = b*get_a();} void B::display() { cout << "a = " << get_a() << "\n"; cout << "b = " << b << "\n"; cout << "c = " << c << "\n"; } void main() { clrscr(); B d; d.get_ab(); //Ke thua tu A d.mul(); d.show_a(); //Ke thua tu A d.display(); d.b = 20; d.mul(); d.display(); getch(); } Chương trình cho kết quả: a = 5 a = 5 b = 10 c = 50 a = 5 b = 20 74
  7. c = 100 Ví dụ Chương trình sau là minh họa khác cho trường hợp kế thừa đơn: #include #include class Diem { private: double x, y; public: void nhap() { cout >x; cout >y; } void hienthi() { cout >r; } double get_r() { return r; } }; void main() { Hinhtron h; clrscr(); cout<<"\n Nhap toa do tam va ban kinh hinh tron"; h.nhap(); h.nhap_r(); 75
  8. cout #include class Diem { private: double x, y; public: Diem() { 76
  9. x=y=0.0; } Diem(double x1, double y1) { x=x1; y=y1; } void in() { cout<<”\nx=”<<x<<”y=”<<y; } }; class Hinhtron: public Diem { private: double r; public: Hinhtron() { r = 0.0; } Hinhtron(double x1,double y1,double r1): Diem (x1, y1) { r=r1; } double get_r() { return r; } }; void main() { Hinhtron h(2.5, 3.5, 8); clrscr(); cout<<”\n Hinh tron co tam:”; h.in(); cout<<”\n Co ban kinh =” << h.get_r(); getch(); } Chú ý: Các tham số mà hàm tạo của lớp dẫn xuất truyền cho hàm tạo của lớp cơ sở không nhất thiết phải lấy hoàn toàn y như từ các tham số nó nhận được. Ví dụ: 77
  10. Hinhtron(double x1,double y1,double r1):Diem (x1/2, y1/2) 6.2.5. Hàm hủy đối với tính kế thừa Hàm hủy của lớp cơ sở cũng không được kế thừa. Khi cả lớp cơ sở và lớp dẫn xuất có các hàm hủy và hàm tạo, các hàm tạo thi hành theo thứ tự dẫn xuất. Các hàm hủy được thi hành theo thứ tự ngược lại. Nghĩa là, hàm tạo của lớp cơ sở thi hành trước hàm tạo của lớp dẫn xuất, hàm hủy của lớp dẫn xuất thi hành trước hàm hủy của lớp cơ sở. Ví dụ #include #include class CS { public: CS() {cout<<"\nHam tao lop co so";} ~CS() {cout<<"\nHam huy lop co so";} }; class DX:public CS { public: DX() {cout<<"\nHam tao lop dan xuat";} ~DX() {cout<<"\nHam huy lop dan xuat";} }; void main() { clrscr(); DX ob; getch(); } Chương trình này cho kết quả như sau: Ham tao lop co so Ham tao dan xuat Ham huy lop dan xuat Ham huy lop co so 6.2.6. Khai báo protected Ta đã biết các thành phần khai báo private không được kế thừa trong lớp dẫn xuất. Có thể giải quyết vấn đề này bằng cách chuyển chúng sang vùng public. Tuy nhiên cách làm này lại phá vỡ nguyên lý che dấu thông tin của LTHĐT. C++ đưa ra cách giải quyết khác là sử dụng khai báo protected. Các thành phần 78
  11. protected có phạm vi truy nhập rộng hơn so với các thành phần private, nhưng hẹp hơn so với các thành phần public. Các thành phần protected của lớp cơ sở hoàn toàn giống các thành phần private ngoại trừ một điểm là chúng có thể kế thừa từ lớp dẫn xuất trực tiếp từ lớp cơ sở. Cụ thể như sau: - Nếu kế thừa theo kiểu public thì các thành phần proteted của lớp cơ sở sẽ trở thành các thành phần protected của lớp dẫn xuất. - Nếu kế thừa theo kiểu private thì các thành phần proteted của lớp cơ sở sẽ trở thành các thành phần private của lớp dẫn xuất. 6.2.7. Dẫn xuất protected Ngoài hai kiểu dẫn xuất đã biết là private và public, C++ còn đưa ra kiểu dẫn xuất protected. Trong dẫn xuất loại này thì các thành phần public, protected trong lớp cơ sở trở thành các thành phần protected trong lớp dẫn xuất. 6.3. Đa kế thừa 6.3.1. Định nghĩa lớp dẫn xuất từ nhiều lớp cơ sở Gi sử đã định nghĩa các lớp A, B. Cú pháp để xây dựng lớp C dẫn xuất từ lớp A và B như sau: class C: mode A, mode B { private: // Khai báo các thuộc tính public: // Các hàm thành phần }; trong đó mode có thể là private, public hoặc protected. ý nghĩa của kiểu dẫn xuất này giống như trường hợp đơn kế thừa. 6.3.2. Một số ví dụ về đa kế thừa Ví dụ Chương trình sau minh họa một lớp kế thừa từ hai lớp cơ sở: #include #include #include class M { protected : int m; public : void getm(int x) {m=x;} }; 79
  12. class N { protected : int n; public : void getn(int y) {n=y;} }; class P : public M,public N { public : void display(void) { cout #include #include class sinhvien { char hoten[25]; protected: int sbd; 80
  13. public: void nhap() { cout >sbd; } void hienthi() { cout >d1>>d2; } void hienthi_diem() { cout >n; 81
  14. clrscr(); for(i=0;i #include #include class sinhvien { char hoten[25]; protected : int sbd; public : void nhap() { cout >sbd; } void hienthi() { cout >d1>>d2; } void hienthi_diem() { cout<<"Diem mon 1 :"<<d1<<endl; cout<<"Diem mon 2 :"<<d2<<endl; } 82
  15. }; class uutien { protected : float ut; public : void nhap_ut() { cout >ut; } void hienthi_ut() {cout >n; clrscr(); for(i=0;i<n;++i) { sv[i].nhap(); sv[i].nhap_diem(); sv[i].nhap_ut(); } for(i=0;i<n;++i) sv[i].display(); getch(); } Ví dụ Xem sơ đồ kế thừa các lớp như sau: Building 83
  16. House Office Hình 6.3: sơ đồ kế thừa các lớp Trong đó lớp cơ sở Building lưu trữ số tầng của một tòa nhà, tổng số phòng và tổng diện tích của tòa nhà. Lớp dẫn xuất House kế thừa lớp Building và lưu trữ số phòng ngủ, số phòng tắm. Lớp dẫn xuất Office từ lớp Building lưu trữ số máy điện thoại và số bình cứu hỏa. Chương trình sau minh họa việc tổ chức lưu trữ theo s đồ kế thừa này. #include #include class Building { protected : int floors; //tong so tang int rooms; //tong so phong double footage; //tong so dien tich }; class house : public Building { int bedrooms; //tong so phong ngu int bathrooms; //tong so phong tam public : house(int f, int r, int ft, int br, int bth) { floors=f; rooms=r; footage=ft; bedrooms=br; bathrooms=bth; } void show() { cout<<'\n'; cout<<" So tang : " <<floors <<'\n'; cout<<" So phong : " <<rooms <<'\n'; cout<<" So tong dien tich : " <<footage<<'\n'; cout<<" So phong ngu : " <<bedrooms <<'\n'; cout<<" So phong tam : " <<bathrooms<<'\n'; } }; class office : public Building { int phones; //tong so may dien thoai int extis; //tong so binh cuu hoa public : office(int f, int r, int ft, int p, int ext) 84
  17. { floors=f; rooms=r; footage=ft; phones=p; extis=ext; } void show() { cout<<'\n'; cout<<" So tang : " <<floors <<'\n'; cout<<" So phong : " <<rooms <<'\n'; cout<<" So tong dien tich : " <<footage <<"\n”; cout<<" So may dien thoai : " <<phones << "\n"; cout<<" So binh cuu hoa : " <<extis<<'\n'; } }; void main() { house h_ob(2,12,5000,6,4); office o_ob(4,25,12000,30,8); cout<<"House : "; h_ob.show(); cout<<"Office : "; o_ob.show(); getch(); } Chương trình cho kết quả như sau: House : So tang : 2 So phong : 12 So tong dien tich : 5000 So phong ngu : 6 So phong tam : 4 Office : So tang : 4 So phong : 25 So tong dien tich : 12000 So may dien thoai : 30 So binh cuu hoa : 8 Bài tập: Câu 1: Cho ví dụ và cài đặt một mô hình các lớp có sử dụng phương pháp đơn kế thừa? Câu 2: Cho ví dụ và cài đặt một mô hình các lớp sử dụng phương pháp đa kế thừa? 85
  18. BÀI 7 HÀM ẢO VÀ TÍNH TƯƠNG ỨNG BỘI MÃ BÀI: ITPRG02.7 Giới thiệu: Khi lớp dẫn xuất kế thừa lớp cơ sở có thể kế thừa hàm thành phần có cùng tên. Việc gọi hàm thành phần trong lớp dẫn xuất có thể gây ra sự không rõ ràng. Việc định nghĩa hàm ảo trong lớp cơ sở là một giải pháp giúp người lập trình xây dựng khuôn dạng của lớp ban đầu. Muốn sử dụng được hàm này, trong lớp dẫn xuất phải định nghĩa lại. Mục tiêu thực hiện: Học xong bài này học viên sẽ có khả năng: - Định nghĩa được hàm ảo. - Sử dụng được hàm ảo. - Khai báo và sử dụng được lớp cơ sở ảo. Nội dung: 7.1. Hàm ảo 7.1.1 Đặt vấn đề 7.1.2. Định nghĩa hàm ảo 7.1.3. Quy tắc gọi hàm ảo 7.1.4. Quy tắc gán địa chỉ đối tượng cho con trỏ lớp cơ sở 7.2 Lớp cơ sở ảo 7.2.1. Khai báo lớp cơ sở ảo 7.2.2. Hàm tạo và hàm hủy đối với lớp cơ sở ảo 7.1. Hàm ảo 7.1.1 Đặt vấn đề Trước khi đưa ra khái niệm về hàm ảo, ta hãy thảo luận ví dụ sau: Giả sử có 3 lớp A, B và C được xây dựng như sau: class A { public: void xuat() { cout <<”\n Lop A”; } }; class B : public A { 86
  19. public: void xuat() { cout xuat(); q->xuat(); r->xuat(); Cả 3 câu lệnh trên đều gọi tới hàm thành phần xuat() của lớp A, bởi vì các con trỏ p, q, r đều có kiểu lớp A. Sở dĩ như vậy là vì một lời gọi (xuất phát từ một đối tượng hay con trỏ) tới hàm thành phần luôn luôn liên kết với một hàm thành phần cố định và sự liên kết này xác định trong quá trình biên dịch chương trình. Ta bảo đây là sự liên kết tĩnh. Có thể tóm lược cách thức gọi các hàm thành phần như sau: 87
  20. 1. Nếu lời gọi xuất phát từ một đối tượng của lớp nào đó, thì hàm thành phần của lớp đó sẽ được gọi. 2. Nếu lời gọi xuất phát từ một con trỏ kiểu lớp, thì hàm thành phần của lớp đó sẽ được gọi bất kể con trỏ chứa địa chỉ của đối tượng nào. Vấn đề đặt ra là: Ta muốn tại thời điểm con trỏ đang trỏ đến đối tượng nào đó thì lời gọi hàm phải liên kết đúng hàm thành phần của lớp mà đối tượng đó thuộc vào chứ không phụ thuộc vào kiểu lớp của con trỏ. C++ giải quyết vấn đề này bằng cách dùng khái niệm hàm ảo. 7.1.2. Định nghĩa hàm ảo Hàm ảo là hàm thành phần của lớp, nó được khai báo trong lớp cơ sở và định nghĩa lại trong lớp dẫn xuất. Để định nghĩa hàm ảo thì phần khai báo hàm phải bắt đầu bằng từ khóa virtual. Khi một lớp có chứa hàm ảo được kế thừa, lớp dẫn xuất sẽ định nghĩa lại hàm ảo đó cho chính mình. Các hàm ảo triển khai tư tưởng chủ đạo của tính đa hình là “ một giao diện cho nhiều hàm thành phần”. Hàm ảo bên trong lớp cơ sở định nghĩa hình thức giao tiếp đối với hàm đó. Việc định nghĩa lại hàm ảo ở lớp dẫn xuất là thi hành các tác vụ của hàm liên quan đến chính lớp dẫn xuất đó. Nói cách khác, định nghĩa lại hàm ảo chính là tạo ra phương thức cụ thể. Trong phần định nghĩa lại hàm ảo ở lớp dẫn xuất, không cần phải sử dụng lại từ khóa virtual. Khi xây dựng hàm ảo, cần tuân theo những quy tắc sau: 1. Hàm ảo phải là hàm thành phần của một lớp ; 2. Những thành phần tĩnh (static) không thể khai báo ảo; 3. Sử dụng con trỏ để truy nhập tới hàm ảo; 4. Hàm ảo được định nghĩa trong lớp cơ sở, ngay khi nó không được sử dụng; 5. Mẫu của các phiên bản (ở lớp cơ sở và lớp dẫn xuất) phải giống nhau. Nếu hai hàm cùng tên nhưng có mẫu khác nhau thì C++ sẽ xem như hàm tải bội; 6. Không được tạo ra hàm tạo ảo, nhưng có thể tạo ra hàm hủy ảo; 7. Con trỏ của lớp cơ sở có thể chứa địa chỉ của đối tượng thuộc lớp dẫn xuất, nhưng ngược lại thì không được; 8. Nếu dùng con trỏ của lớp cơ sở để trỏ đến đối tượng của lớp dẫn xuất thì phép toán tăng giảm con trỏ sẽ không tác dụng đối với lớp dẫn xuất, nghĩa là không phải con trỏ sẽ trỏ tới đối tượng trước hoặc tiếp theo trong lớp dẫn xuất. Phép toán tăng giảm chỉ liên quan đến lớp cơ sở. Ví dụ: class A { virtual void hienthi() { cout<<”\nDay la lop A”; }; 88
  21. }; class B : public A { void hienthi() { cout<<”\nDay la lop B”; } }; class C : public B { void hienthi() { cout<<”\nDay la lop C”; } }; class D : public A { void hienthi() { cout<<”\nDay la lop D”; } }; Chú ý: Từ khoá virtual không được đặt bên ngoài định nghĩa lớp. Xem ví dụ: class A { virtual void hienthi(); }; virtual void hienthi() // sai { cout<<”\nDay la lop A”; } 7.1.3. Quy tắc gọi hàm ảo Hàm ảo chỉ khác hàm thành phần thông thường khi được gọi từ một con trỏ. Lời gọi tới hàm ảo từ một con trỏ chưa cho biết rõ hàm thành phần nào (trong số các hàm thành phần cùng tên của các lớp có quan hệ thừa kế) sẽ được gọi. Điều này sẽ phụ thuộc vào đối tượng cụ thể mà con trỏ đang trỏ tới: con trỏ đang trỏ tới đối tượng của lớp nào thì hàm thành phần của lớp đó sẽ được gọi. 89
  22. 7.1.4. Quy tắc gán địa chỉ đối tượng cho con trỏ lớp cơ sở C++ cho phép gán địa chỉ đối tượng của một lớp dẫn xuất cho con trỏ của lớp cơ sở bằng cách sử dụng phép gán = và phép toán lấy địa chỉ &. Ví dụ: Giả sử A là lớp cơ sở và B là lớp dẫn xuất từ A. Các phép gán sau là đúng: A *p; // p là con trỏ kiểu A A a; // a là biến đối tượng kiểu A B b; // b là biến đối tượng kiểu B p = &a; // p và a cùng lớp A p = &b; // p là con trỏ lớp cơ sở, b là đối tượng lớp dẫn xuất Chú ý: Không cho phép gán địa chỉ đối tượng của lớp cơ sở cho con trỏ của lớp dẫn xuất, chẳng hạn với khai báo B *q; A a; thì câu lệnh q = &a; là sai. Ví dụ Chương trình sau đây minh họa việc sử dụng hàm ảo: #include #include class A { public: virtual void hienthi() { cout <<"\n Lop A"; } }; class B : public A { public: void hienthi() { cout <<"\n Lop B"; } }; class C : public B { public: void hienthi() { cout <<"\n Lop C"; } }; 90
  23. void main() { clrscr(); A *p; A a; B b; C c; a.hienthi(); //goi ham cua lop A p = &b; //p tro to doi tuong b cua lop B p->hienthi(); //goi ham cua lop B p=&c; //p tro to doi tuong c cua lop C p->hienthi(); //goi ham cua lop C getch(); } Chương trình này cho kết quả như sau: Lop A Lop B Lop C Chú ý :  Cùng một câu lệnh p->hienthi(); được tương ứng với nhiều hàm khác nhau khác nhau khi hienthi() là hàm ảo. Đây chính là sự tương ứng bội. Kh năng này cho phép xử lý nhiều đối tượng khác nhau theo cùng một cách thức.  Cũng với lời gọi: p->hienthi(); (hienthi() là hàm ảo) thì lời gọi này không liên kết với một phương thức cố định, mà tùy thuộc và nội dung con trỏ. Đó là sự liên kết động và phương thức được liên kết (được gọi) thay đổi mỗi khi có sự thay đổi nội dung con trỏ trong quá trình chạy chương trình. Ví dụ Chương trình sau tạo ra một lớp cơ sở có tên là num lưu trữ một số nguyên, và một hàm ảo của lớp có tên là shownum(). Lớp num có hai lớp dẫn xuất là outhex và outoct. Trong hai lớp này sẽ định nghĩa lại hàm ảo shownum() để chúng in ra số nguyên dưới dạng số hệ 16 và số hệ 8. #include #include class num { public : int i; num(int x) { i=x; } virtual void shownum() { cout<<"\n So he 10 : "; cout <<dec<<i<<'\n'; } }; 91
  24. class outhex : public num { public : outhex(int n) : num(n) {} void shownum() { cout shownum(); //goi ham cua lop co so, 100 p=&o; p->shownum(); //goi ham cua lop dan xuat, 12 p=&h; p->shownum(); //goi ham cua lop dan xuat, f getch(); } Chương trình trên cho kết quả: So he 10 : 1234 So he 10 : 342 So he 8 : 526 So he 10 : 747 So he 16 : 2eb 92
  25. 7.2 Lớp cơ sở ảo 7.2.1. Khai báo lớp cơ sở ảo Một vấn đề tồn tại là khi nhiều lớp cơ sở được kế thừa trực tiếp bởi một lớp dẫn xuất. Để hiểu rõ hơn vấn đề này, xét tình huống các lớp kế thừa theo sơ đồ như sau: A B C D Hình 7. Các lớp cơ sở được kế thừa trực tiếp từ một lớp dẫn xuất ở đây, lớp A được kế thừa bởi hai lớp B và C. Lớp D kế thừa trực tiếp cả hai lớp B và C. Như vậy lớp A được kế thừa hai lần bởi lớp D: lần thứ nhất nó được kế thừa thông qua lớp B, và lần thứ hai được kế thừa thông qua lớp C. Bởi vì có hai bản sao của lớp A có trong lớp D nên một tham chiếu đến một thành phần của lớp A sẽ tham chiếu về lớp A được kế thừa gián tiếp thông qua lớp B hay tham chiếu về lớp A được kế thừa gián tiếp thông qua lớp C? Để giải quyết tính không rõ ràng này, C++ có một cơ chế mà nhờ đó chỉ có một bản sao của lớp A ở trong lớp D: đó là sử dụng lớp cơ sở ảo. Trong ví dụ trên, C++ sử dụng từ khóa vitual để khai báo lớp A là ảo trong các lớp B và C theo mẫu như sau: clacc B : virtual public A { }; clacc C : virtual public A { }; clacc D : public B, public C { }; Việc chỉ định A là ảo trong các lớp B và C nghĩa là A sẽ chỉ xuất hiện một lần trong lớp D. Khai báo này không ảnh hưởng đến các lớp B và C. Chú ý: Từ khóa virtual có thể đặt trước hoặc sau từ khóa public, private, protected. Ví dụ #include #include class A { float x,y; public: void set(float x1, float y1) 93
  26. { x = x1; y = y1; } float getx() { return x; } float gety() { return y; } }; class B : virtual public A { }; class C : virtual public A { }; class D : public B, public C { }; void main() { clrscr(); D d; cout<<"\nd.B::set(2,3)\n"; d.B::set(2,3); cout<<"\nd.C::getx() = "; cout<<d.C::getx()<<endl; cout<<"\nd.B::getx() = "; cout<<d.B::getx()<<endl; cout<<"\nd.C::gety() = "; cout<<d.C::gety()<<endl; cout<<"\nd.B::gety() = "; cout<<d.B::gety()<<endl; cout<<"\nd.C::set(2,3)\n"; d.C::set(2,3); cout<<"\nd.C::getx() = "; cout<<d.C::getx()<<endl; cout<<"\nd.B::getx() = "; cout<<d.B::getx()<<endl; cout<<"\nd.C::gety() = "; cout<<d.C::gety()<<endl; cout<<"\nd.B::gety() = "; cout<<d.B::gety()<<endl; getch(); } Chương trình trên sẽ cho kết quả: d.B::set(2,3) d.C::getx() = 2 d.B::getx() = 2 d.C::gety() = 3 d.B::gety() = 3 94
  27. d.C::set(2,3) d.C::getx() = 2 d.B::getx() = 2 d.C::gety() = 3 d.B::gety() = 3 7.2.2. Hàm tạo và hàm hủy đối với lớp cơ sở Ta đã biết, khi khởi tạo đối tượng lớp dẫn xuất thì các hàm tạo được gọi theo thứ tự xuất hiện trong danh sách các lớp cơ sở được khai báo, rồi đến hàm tạo của lớp dẫn xuất. Thông tin được chuyển từ hàm tạo của lớp dẫn xuất sang hàm tạo của lớp cơ sở. Trong tình huống có lớp cơ sở ảo, chẳng hạn như hình vẽ 5.4., cần phải tuân theo quy định sau: Thứ tự gọi hàm tạo: Hàm tạo của một lớp ảo luôn luôn được gọi trước các hàm tạo khác. Với sơ đồ kế thừa như hình vẽ 5.4., thứ tự gọi hàm tạo sẽ là A, B, C và cuối cùng là D. Chương trình sau minh họa điều này: Ví dụ #include #include class A { float x,y; public: A() {x = 0; y = 0;} A(float x1, float y1) { cout<<"A::A(float,float)\n"; x = x1; y = y1; } float getx() { return x; } float gety() { return y; } }; class B : virtual public A { 95
  28. public: B(float x1, float y1):A(x1,y1) { cout<<"B::B(float,float)\n"; } }; class C : virtual public A { public: C(float x1, float y1):A(x1,y1) { cout<<"C::C(float,float)\n"; } }; class D : public B, public C { public: D(float x1, float y1):A(x1,y1), B(10,4), C(1,1) { cout<<"D::D(float,float)\n"; } }; void main() { clrscr(); D d(2,3); cout<<"\nD d (2,3)\n"; cout<<"\nd.C::getx() = "; cout<<d.C::getx()<<endl; cout<<"\nd.B::getx() = "; cout<<d.B::getx()<<endl; cout<<"\nd.C::gety() = "; cout<<d.C::gety()<<endl; cout<<"\nd.B::gety() = "; cout<<d.B::gety()<<endl; cout<<"\nd1 (10,20) \n"; D d1 (10,20); cout<<"\nd.C::getx() = "; cout<<d.C::getx()<<endl; cout<<"\nd.B::getx() = "; cout<<d.B::getx()<<endl; cout<<"\nd.C::gety() = "; cout<<d.C::gety()<<endl; cout<<"\nd.B::gety() = "; cout<<d.B::gety()<<endl; getch(); } 96
  29. Kết quả chương trình trên như sau: A::A(float,float) B::B(float,float) C::C(float,float) D::D(float,float) D d (2,3) d.C::getx() = 2 d.B::getx() = 2 d.C::gety() = 3 d.B::gety() = 3 d1 (10,20) A::A(float,float) B::B(float,float) C::C(float,float) D::D(float,float) d.C::getx() = 2 d.B::getx() = 2 d.C::gety() = 3 d.B::gety() = 3 Bài tập 1. Xây dựng lớp có tên là Stack với các thao tác cần thiết. Từ đó hãy dẫn xuất từ lớp stack để đổi một số nguyên dương sang hệ đếm bất kỳ. 2. Viết một phân cấp kế thừa cho các lớp hình tứ giác, hình thang, hình bình hành, hình chữ nhật, hình vuông. 3. Tạo một lớp cơ sở có tên là airship để lưu thông tin về số lượng hành khách tối đa và trọng lượng hàng hóa tối đa mà máy bay có thể chở được. Từ đó hãy tạo hai lớp dẫn xuất airplane và balloon, lớp airplane lưu thông tin về kiểu động cơ (gồm động c cánh quạt và động cơ phản lực), lớp balloon lưu thông tin về loại nhiên liệu sử dụng cho khí cầu (gồm hai loại là hydrogen và helium). Hãy viết chương trình minh họa. 4. Một nhà xuất bản nhận xuất bn sách. Sách có hai loại: loại có hình ảnh ở trang bìa và loại không có hình ảnh ở trang bìa. Loại có hình ảnh ở trang bìa thì phải thuê họa sĩ vẽ bìa. Viết chương trình thực hiện các yêu cầu : - Tạo một lớp cơ sở có tên là SACH để lưu thông tin về tên sách, tác giả, số trang, giá bán và định nghĩa hàm thành phần cho phép nhập dữ liệu cho các đối tượng của lớp SACH. 97
  30. - Tạo lớp BIA kế thừa từ lớp SACH để lưu các thông tin : Mã hình ảnh, tiền vẽ và định nghĩa hàm thành phần cho phép nhập dữ liệu cho các đối tượng của lớp BIA. - Tạo lớp HOASY để lưu các thông tin họ tên, địa chỉ của họa sỹ và định nghĩa hàm thành phần cho phép nhập dữ liệu cho các đối tượng của lớp HOASY. - Tạo lớp SACHVEBIA kế thừa từ lớp BIA và lớp HOASY và định nghĩa hàm thành phần cho phép nhập dữ liệu cho các đối tượng của lớp SACHVEBIA. Viết hàm main() cho phép nhập vào hai danh sách : danh sách các sách có vẽ bìa và danh sách các sách không có vẽ bìa (có thể dùng mảng tĩnh hoặc mảng con trỏ). 5. Xây dựng lớp hình vuông có tên là HVUONG với các dữ liệu thành phần như sau: độ dài cạnh. Các hàm thành phần để nhập dữ liệu, hiển thị dữ liệu, tính diện tích, chu vi hình vuông. Từ lớp HVUONG, xây dựng lớp dẫn xuất có tên là CHUNHAT, là lớp kế thừa của lớp HVUONG và được bổ sung thêm thuộc tính sau: độ dài cạnh thứ hai. Các hàm thành phần để nhập dữ liệu, hiển thị dữ liệu để tính diện tích và chu vi hình chữ nhật. Viết chương trình minh họa. 6. Xây dựng lớp cơ sở CANBO có dữ liệu thành phần là mã cán bộ, mã đơn vị, họ tên, ngày sinh. Các hàm thành phần bao gồm: nhập dữ liệu cán bộ, hiển thị dữ liệu. Lớp dẫn xuất LUONG kế thừa lớp CANBO và có thêm các thuộc tính: phụ cấp, hệ số lương, bảo hiểm. Hàm thành phần để tính lương cán bộ theo công thức: lương = hệ số lương *290000 + phụ cấp – bảo hiểm Hãy thiết kế chương trình để đáp ứng các yêu cầu: - Nhập danh sách cán bộ - In bảng lương các cán bộ theo từng đơn vị. 7. Nhân viên trong một cơ quan được lĩnh lương theo các dạng khác nhau. Dạng người lao động hưởng lương từ ngân sách Nhà nước gọi là cán bộ, công chức (dạng biên chế). Dạng người lao động lĩnh lương từ ngân sách của cơ quan gọi là người làm hợp đồng. Như vậy hệ thống có hai đối tượng: biên chế và hợp đồng. - Hai loại đối tượng này có đặc tính chung là viên chức làm việc cho cơ quan. Từ đây có thể tạo nên lớp cơ sở để quản lý một viên chức ( lớp Nguoi) bao gồm mã số, họ tên, lương. - Hai lớp kế thừa từ lớp cơ sở trên: + Lớp Bienche gồm các thuộc tính: hệ số lương, tiền phụ cấp chức vụ. + Lớp Hopdong gồm các thuộc tính: tiền công lao động, số ngày làm việc trong tháng, hệ số vượt giờ. Hãy thiết kế các lớp trên và viết chương trình minh họa. 98
  31. 8. Viết chương trình quản lý sách báo, tạp chí của thư viện trong trường đại học, hằng tháng gởi về khoa họ tên của các giáo viên và sinh viên đã quá thời hạn mượn sách. 9. Viết chương trình tính và in bảng lương hàng tháng của giáo viên và người làm hợp đồng trong một trường đại học. Giả sử việc tính toán tiền lương được căn cứ vào các yếu tố sau: - Đối với giáo viên: số tiết dạy trong tháng, tiền dạy một tiết. - Đối với người làm hợp đồng: tiền công ngày, số ngày làm việc 10. Giả sử cuối năm học cần trao phần thưởng cho các sinh viên giỏi, các giáo viên có tham gia nghiên cứu khoa học. Điều kiện khen thưởng của sinh viên là có điểm trung bình lớn hơn 8. Điều kiện khen thưởng của giáo viên là có ít nhất một bài báo nghiên cứu khoa học. Sơ đồ cấu trúc phân cấp lớp như sau: Nguoi Họ tên Ngày sinh Các phương thức Sinhvien Lớp Điểm trung bình 99
  32. Giaovien Bộ môn Các phương Số bài báo thức Các phương thức Yêu cầu: - Xây dựng các lớp theo sơ đồ kề thừa ở trên, mỗi lớp có các hàm thành phần để nhập, xuất dữ liệu, hàm kiểm tra khen thưởng. - Hãy viết hàm main() cho phép nhập vào dữ liệu của không quá 100 sinh viên và không quá 30 giáo viên. In ra danh sách sinh viên và giáo viên được khen thưởng. 11. Giả sử ta có sơ đồ kế thừa của các lớp như sau: HINHHOC HAICHIEU BACHIEU TRON CHUNHAT LAPPHUONG Yêu cầu: - Thiết kế các lớp để có thể in ra các thông tin của các hình (tròn, chữ nhật, lập phương) bao gồm: diện tích, chu vi, thể tích. - Viết chương trình minh họa. 12. Viết chương trình quản lý việc mượn và trả sách ở một thư viện theo phương pháp lập trình hướng đối tượng. Chương trình cho phép: - Đăng ký bạn đọc mới với thông tin là mã và tên bạn đọc, số điện thoại - Nhập sách mới với thông tin là mã sách, tên sách, số lượng, nhà xuất bản. - Mượn và trả sách. - Thống kê bạn đọc. - Thống kê sách. BÀI 8 HÀM, LỚP TEMPLATE MÃ BÀI: ITPRG02.8 Giới thiệu: Mặt dù trong C++ cho phép chúng ta định nghĩa các hàm và lớp theo ý thích của người lập trình một cách dễ dàng và nhanh chóng, tuy nhiên đối với các lập trình viên chuyên nghiệp vấn đề còn có những yêu cầu cao hơn đó là hạnh chế việc phải 100
  33. hạn chế việc định nghĩa lại các hàm chồng cũng như các lớp gần giống nhau. C++ giải quyết vấn đề này bằng cách đưa ra khái niệm khuôn hình hàm (function template) và khuôn hình lớp (class template) mà ta sẽ tìm hiểu dưới đây. Mục tiêu thực hiện: Học xong bài này học viên sẽ có khả năng: - Định nghĩa và sử dụng được khuôn hình hàm (function template) - Định nghĩa và sử dụng được khuôn hình lớp (class template) Nội dung: 8.1. Khuôn hình hàm 8.1.1. Khái niệm 8.1.2. Tạo một khuôn hình hàm 8.1.3. Sử dụng khuôn hình hàm 8.1.4. Các tham số kiểu của khuôn hình hàm 8.1.5. Định nghĩa chồng các khuôn hình hàm 8.2. Khuôn hình lớp 8.2.1. Khái niệm 8.2.2. Tạo một khuôn hình lớp 8.2.3. Sử dụng khuôn hình lớp 8.1. Khuôn hình hàm 8.1.1. Khái niệm Ta đã biết hàm quá tải cho phép dùng một tên duy nhất cho nhiều hàm để thực hiện các công việc khác nhau. Khái niệm khuôn hình hàm cũng cho phép sử dụng cùng một tên duy nhất để thực hiện các công việc khác nhau, tuy nhiên so với định nghĩa hàm quá tải, nó có phần mạnh hơn và chặt chẽ hơn. Mạnh hơn vì chỉ cần viết định nghĩa khuôn hình hàm một lần, rồi sau đó chương trình biên dịch làm cho nó thích ứng với các kiểu dữ liệu khác nhau. Chặt chẽ hơn bởi vì dựa theo khuôn hình hàm, tất cả các hàm thể hiện được sinh ra bởi chương trình dịch sẽ tương ứng với cùng một định nghĩa và như vậy sẽ có cùng một giải thuật. 8.1.2. Tạo một khuôn hình hàm Giả thiết rằng chúng ta cần viết một hàm min đưa ra giá trị nhỏ nhất trong hai giá trị có cùng kiểu. Ta có thể viết một định nghĩa như thế với kiểu int như sau: int min (int a, int b) { if (a<b) return a; else return b; 101
  34. } Nếu ta muốn sử dụng hàm min cho kiểu double, float, char, ta lại phải viết lại định nghĩa hàm min, ví dụ: float min (float a, float b) { if (a tên hàm(khai báo tham số) { // định nghĩa hàm } trong đó là các kiểu dữ liệu được khai báo với từ khoá class, cách nhau bởi dấu phẩy. Kiểu dữ liệu là một kiểu bất kỳ, kể cả kiểu class. Ví dụ Xây dựng khuôn hình cho hàm tìm giá trị nhỏ nhất của hai số: template Kieuso min(Kieuso a, Kieuso b) { if (a<b) return a; else return b; } 8.1.3. Sử dụng khuôn hình hàm Để sử dụng khuôn hình hàm min vừa tạo ra, chỉ cần sử dụng hàm min trong những điều kiện phù hợp, trong trường hợp này là hai tham số của hàm phải cùng kiểu dữ liệu. Như vậy, nếu trong một chương trình có hai tham số nguyên n và m (kiểu int) với lời gọi min(n,m) thì chương trình dịch tự động sản sinh ra hàm min(), gọi là một hàm thể hiện, tương ứng với hai tham số kiểu int. Nếu chúng ta gọi min() với hai tham số kiểu float, chương trình biên dịch cũng tự động sản sinh một hàm thể 102
  35. hiện min khác tương ứng với các tham số kiểu float và cứ thế với các kiểu dữ liệu khác. Chú ý: - Các biến truyền cho danh sách tham số của hàm phải chính xác với kiểu khai báo. - Muốn áp dụng được với kiểu lớp thì trong lớp phải định nghĩa các toán tử tải bội tương ứng. 8.1.4. Các tham số kiểu của khuôn hình hàm Khuôn hình hàm có thể có một hay nhiều tham số kiểu, mỗi tham số đi liền sau từ khoá class. Các tham số này có thể ở bất kỳ đâu trong định nghĩa của khuôn hình hàm, nghĩa là : - Trong dòng tiêu đề (ở dòng đầu khai báo template). - Trong các khai báo biến cục bộ. - Trong các chỉ thị thực hiện. Trong mọi trường hợp, mỗi tham số kiểu phải xuất hiện ít nhất một lần trong khai báo danh sách tham số hình thức của khuôn hình hàm. Điều đó hoàn toàn logic, bởi vì nhờ các tham số này, chương trình dịch mới có thể sản sinh ra hàm thể hiện cần thiết. ở khuôn hình hàm min trên, mới chỉ cho phép tìm min của hai số cùng kiểu, nếu muốn tìm min hai số khác kiểu thì khuôn hình hàm trên chưa đáp ứng được. Ví dụ sau sẽ khắc phục được điều này. Ví dụ #include template kieuso1 min(kieuso1 a,kieuso2 b) { return a<b ? a : b; } void main(){ float a =2.5; int b = 8; cout << "so nho nhat la :" << min(a,b); } Ví dụ Giả sử trong lớp SO các số int đã xây dựng, ta có xây dựng các toán tử tải bội < , << cho các đối tượng của class SO. Nội dung file ttclsso.h như sau: class SO { private: 103
  36. int giatri; public: SO(int x=0) { giatri = x; } SO (SO &tso) { giatri = tso.giatri; } SO (){}; //Giong nhu ham thiet lap ngam dinh ~SO() { } int operator >(istream&,SO&); friend ostream& operator #include template kieuso1 min(kieuso1 a,kieuso2 b) { if (a<b) return a; else return b; } void main(){ float a =2.5; int b = 8; cout << "so nho nhat la :" << min(a,b)<<endl; SO so1(15),so2(20); cout << "so nho nhat la :" << min(so2,so1); } 104
  37. 8.1.5. Định nghĩa chồng các khuôn hình hàm Tương tự việc định nghĩa các hàm quá tải, C++ cho phép định nghĩa chồng các khuôn hình hàm, tức là có thể định nghĩa một hay nhiều khuôn hình hàm có cùng tên nhưng với các tham số khác nhau. Điều đó sẽ tạo ra nhiều họ các hàm (mỗi khuôn hình hàm tương ứng với họ các hàm). Ví dụ có ba họ hàm min : - Một họ gồm các hàm tìm giá trị nhỏ nhất trong hai giá trị - Một họ gồm các hàm tìm giá trị nhỏ nhất trong ba giá trị - Một họ gồm các hàm tìm giá trị nhỏ nhất trong một mảng giá trị. Một cách tổng quát, ta có thể định nghĩa một hay nhiều khuôn hình cùng tên, mỗi khuôn hình có các tham số kiểu cũng như là các tham số biểu thức riêng. Hơn nữa, có thể cung cấp các hàm thông thường với cùng tên với cùng một khuôn hình hàm, trong trường hợp này ta nói đó là sự cụ thể hoá một hàm thể hiện. Trong trường hợp tổng quát khi có đồng thời cả hàm quá tải và khuôn hình hàm, chương trình dịch lựa chọn hàm tương ứng với một lời gọi hàm dựa trên các nguyên tắc sau: Đầu tiên, kiểm tra tất cả các hàm thông thường cùng tên và chú ý đến sự tương ứng chính xác; nếu chỉ có một hàm phù hợp, hàm đó được chọn; Còn nếu có nhiều hàm cùng thỏa mãn sẽ tạo ra một lỗi biên dịch và quá trình tìm kiếm bị gián đọan. Nếu không có hàm thông thường nào tương ứng chính xác với lời gọi, khi đó ta kiểm tra tất cả các khuôn hình hàm có trùng tên với lời gọi, khi đó ta kiểm tra tất cả các khuôn hình hàm có trùng tên với lời gọi; nếu chỉ có một tương ứng chính xác được tìm thấy, hàm thể hiện tương ứng được sản sinh và vấn đề được giải quyết; còn nếu có nhiều hơn một khuôn hình hàm điều đó sẽ gây ra lỗi biên dịch và quá trình dừng. Cuối cùng, nếu không có khuôn hình hàm phù hợp, ta kiểm tra một lần nữa tất cả các hàm thông thường cùng tên với lời gọi. Trong trường hợp này chúng ta phải tìm kiếm sự tương ứng dựa vào cả các chuyển kiểu cho phép trong C/C++. 8.2. Khuôn hình lớp 8.2.1. Khái niệm Bên cạnh khái niệm khuôn hình hàm, C++ còn cho phép định nghĩa khuôn hình lớp. Cũng giống như khuôn hình hàm, ở đây ta chỉ cần viết định nghĩa các khuôn hình lớp một lần rồi sau đó có thể áp dụng chúng với các kiểu dữ liệu khác nhau để được các lớp thể hiện khác nhau. 8.2.2. Tạo một khuôn hình lớp Trong chương trước ta đã định nghĩa cho lớp SO, giá trị các số là kiểu int. Nếu ta muốn làm việc với các số kiểi float, double, thì ta phải định nghĩa lại một lớp 105
  38. khác tương tự, trong đó kiểu dữ liệu int cho dữ liệu giatri sẽ được thay bằng float,double, Để tránh sự trùng lặp trong các tình huống như trên, chương trình dịch C++ cho phép định nghĩa một khuôn hình lớp và sau đó, áp dụng khuôn hình lớp này với các kiểu dữ liệu khác nhau để thu được các lớp thể hiện như mong muốn. Ví dụ: template class SO { kieuso giatri; public : SO (kieuso x =0); void Hienthi(); }; Cũng giống như các khuôn hình hàm, template xác định rằng đó là một khuôn hình trong đó có một tham số kiểu kieuso . C++ sử dụng từ khoá class chỉ để nói rằng kieuso đại diện cho một kiểu dữ liệu nào đó. Việc định nghĩa các hàm thành phần của khuôn hình lớp, người ta phân biệt hai trường hợp: Khi hàm thành phần được định nghĩa bên trong định nghĩa lớp thì không có gì thay đổi. Khi hàm thành phần được định nghĩa bên ngoài lớp, khi đó cần phải nhắc lại cho chương trình biết các tham số kiểu của khuôn hình lớp, có nghĩa là phải nhắc lại template chẳng hạn, trước định nghĩa hàm. Ví dụ hàm Hienthi() được định nghĩa ngoài lớp: template void SO ::Hienthi() { cout Tên_đối_tượng; Ví dụ câu lệnh khai báo SO so1; sẽ khai báo một đối tượng so1 có thành phần dữ liệu giatri có kiểu nguyên int. 106
  39. SO có vai trò như một kiểu dữ liệu lớp; người ta gọi nó là một lớp thể hiện của khuôn hình lớp SO. Một cách tổng quát, khi áp dụng một kiểu dữ liệu nào đó với khuôn hình lớp SO ta sẽ có được một lớp thể hiện tương ứng với kiểu dữ liệu. Tương tự với các khai báo SO so2; cho phép khai báo một đối tượng so2 mà thành phần dữ liệu giatri có kiểu float. Ví dụ #include #include template class SO { kieuso giatri; public : SO (kieuso x =0); void Hienthi(){ cout soint(10); soint.Hienthi(); SO sofl(25.4); sofl.Hienthi(); getch(); } Kết quả trên màn hình là: Gia tri cua so : 10 Gia tri cua so : 25.4 8.2.4. Các tham số trong khuôn hình lớp Hoàn toàn giống như khuôn hình hàm, các khuôn hình lớp có thể có các tham số kiểu và tham số biểu thức. Ví dụ một lớp mà các thành phần có các kiểu dữ liệu khác nhau được khai báo theo dạng: template 107
  40. class { T x; U y; Z fct1 (int); }; Một lớp thể hiện được khai báo bằng cách liệt kê đằng sau tên khuôn hình lớp các tham số thực, là tên kiểu dữ liệu, với số lượng bằng các tham số trong danh sách của khuôn hình lớp (template ) 8.2.5. Tóm tắt Khuôn hình lớp/hàm là phương tiện mô tả ý nghĩa của một lớp/hàm tổng quát, còn lớp/hàm thể hiện là một bản sao của khuôn hình tổng quát với các kiểu dữ liệu cụ thể. Các khuôn hình lớp/hàm thường được tham số hoá. Tuy nhiên vẫn có thể sử dụng các kiểu dữ liệu cụ thể trong các khuôn hình lớp/hàm nếu cần. Bài tập 1. Viết khuôn hình hàm để tìm số lớn nhất của hai số bất kỳ 2. Viết khuôn hình hàm để trả về giá trị trung bình của một mảng, các tham số hình thức của hàm này là tên mảng, kích thước mảng. 3. Cài đặt hàng đợi templete. 4. Viết khuôn hình hàm để sắp xếp kiểu dữ liệu bất kỳ. 5. Xây dựng khuôn hình lớp cho các tọa độ điểm trong mặt phẳng, các thành phần dữ liệu của lớp là toadox, toadoy. 6. Xây dựng khuôn hình lớp cho vector để quản lý các vector có thành phần có kiểu tùy ý. THUẬT NGỮ CHUYÊN MÔN Encapsulation: Tính đóng gói Inheritance: Tính kế thừa Polymorpharism:Tính tương ứng bội Object Oriented Programming:Lập trình hướng đối tượng bottom-up :dưới –lên int:số nguyên float:số thực complex:số phức string:xâu ký tự method:phương thức member function 108
  41. destructor :Hàm hủy in-line :hàm nội tuyến pointer :con trỏ copy constructor:hàm tạo sao chép overloading constructor:hàm định nghĩa chồng inserters:chồng toán tử xuất Extractor:chồng toán tử nhập function template:khuôn hình hàm class template:khuôn hình lớp static: tĩnhdestructor: hủy bỏ destructor: hủy bỏ constructor: thiết lập TÀI LIỆU THAM KHẢO 1. Lê Đăng Hưng – Tại Tuấn Anh – Nguyễn Hữu Đức – Nguyễn Thanh Thủy. Lập trình hướng đối tượng với C++, Nhà xuất bản Khoa Học – Kỹ Thuật. 2. Giáo trình Lập trình hướng đối tượng – Trường Đại học Khoa học Huế. 109