Bài giảng Lập trình hướng đối tượng C++ - Chương 3: Khái niệm về lớp
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 3: Khái niệm về lớp", để 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:
- bai_giang_lap_trinh_huong_doi_tuong_c_chuong_3_khai_niem_ve.doc
Nội dung text: Bài giảng Lập trình hướng đối tượng C++ - Chương 3: Khái niệm về lớp
- Chương 3 trước (cấu trúc, hợp, lớp, ) . Thuộc tính của lớp không thể có kiểu Khái niệm về lớp của chính lớp đó, nhưng có thể là kiểu con trỏ lớp này, ví dụ: class A Như đã nói ở trên, lớp là khái niệm trung tâm của lập trình hướng { đối tượng, nó là sự mở rộng của các khái niệm cấu trúc (struct) của C và bản ghi (record) của PASCAL. Ngoài các thành phần dữ liệu (như A x ; // Không cho phép, vì x có kiểu lớp A cấu trúc), lớp còn chứa các thành phần hàm , còn gọi là phương thức A *p ; // Cho phép , vì p là con trỏ kiểu lớp A (method) hay hàm thành viên (member function). Cũng giống như cấu trúc, lớp có thể xem như một kiểu dữ liệu. Vì vậy lớp còn gọi là } ; kiểu đối tượng và lớp được dùng để khai báo các biến, mảng đối 2. Khi báo các thành phần của lớp (thuộc tính và phương thức) có tượng (như thể dùng kiểu int để khai báo các biến mảng nguyên). thể dùng các từ khoá private và public để quy định phạm vi sử dụng Như vậy từ một lớp có thể tạo ra (bằng cách khai báo) nhiều đối của các thành phần. Nếu không quy định cụ thể (không dùng các từ tượng (biến, mảng) khác nhau. Mỗi đối tượng có vùng nhớ riêng của khoá private và public) thì C++ hiểu đó là private. mình. Vì vậy cũng có thể quan niệm lớp là tập hợp các đối tượng cùng kiểu. Các thành phần private (riêng) chỉ được sử dụng bên trong lớp (trong thân của các phương thức của lớp). Các hàm không phải là Chương này sẽ trình bầy cách định nghĩa lớp, cách xây dựng phương thức của lớp không được phép sử dụng các thành phần này. phương thức, giải thích về phạm vi truy nhập, sư dụng các thành phần của lớp, cách khai báo biến, mảng cấu trúc, lời gọi tới các Các thành phần public (công cộng) được phép sử dụng ở cả bên phương thức. trong và bên ngoài lớp. 3. Các thành phần dữ liệu thường (nhưng không bắt buộc) khai báo là private để bảo đảm tính giấu kín, bảo vệ an toàn dữ liệu của § 1. Định nghĩa lớp lớp, không cho phép các hàm bên ngoài xâm nhập vào dữ liệu của 1. Lớp được định nghĩa theo mẫu: lớp. class tên_lớp 4. Các phương thức thường khai báo là public để chúng có thể { được gọi tới (sử dụng) từ các hàm khác trong chương trình. // Khai báo các thành phần dữ liệu (thuộc tính) 5. Các phương thức có thể được xây dựng bên ngoài hoặc bên trong định nghĩa lớp. Thông thường, các phương thức ngắn được viết // Khai báo các phương thức bên trong định nghĩa lớp, còn các phương thức dài thì viết bên ngoài } ; định nghĩa lớp. // Định nghĩa (xây dựng) các phương thức 6. Trong thân phương thức của một lớp (giả sử lớp A) có thể sử dụng: Chú ý: + Các thuộc tính của lớp A Thuộc tính của lớp có thể là các biến, mảng, con trỏ có kiểu chuẩn (int, float, char, char*, long, ) hoặc kiểu ngoài chuẩn đã định nghĩa + Các phương thức của lớp A 93 94
- + Các hàm tự lập trong chương trình. Vì phạm vi sử dụng của { hàm là toàn chương trình. cout > x >> y ; ngoài chuẩn) cout > m ; tên là DIEM. } + Các thuộc tính của lớp gồm: void DIEM::hien() int x ; // hoành độ (cột) { int y ; // tung độ (hàng) int mau_ht ; int m ; // mầu mau_ht = getcolor(); + Các phương thức: putpixel(x, y, m); Nhập dữ liệu một điểm setcolor(mau_ht); Hiển thị một điểm } ẩn một điểm Qua ví dụ trên có thể rút ra một số điều cần nhớ sau: Lớp điểm được xây dựng như sau: + Trong cả 3 phương thức (dù viết trong hay viết ngoài định class DIEM nghĩa lớp) đều được phép truy nhập đến các thuộc tính x, y và m của { lớp. private: + Các phương thức viết bên trong định nghĩa lớp (như phương int x, y, m ; thức an() ) được viết như một hàm thông thường. public: + Khi xây dựng các phương thức bên ngoài lớp, cần dùng thêm tên lớp và toán tử phạm vi :: đặt ngay trước tên phương phức để quy void nhapsl() ; định rõ đây là phương thức của lớp nào. void hien() ; void an() § 2. Biến, mảng đối tượng { Như đã nói ở trên, một lớp (sau khi định nghĩa) có thể xem như putpixel(x, y, getbkcolor()); một kiểu đối tượng và có thể dùng để khai báo các biến, mảng đối } tượng. Cách khai báo biến, mảng đối tượng cũng giống như khai báo } ; biến, mảng các kiểu khác (như int, float, cấu trúc, hợp, ), theo mẫu sau: void DIEM::nhap()
- Tên_lớp danh sách đối ; Cũng giống như hàm, một phương thức được sử dụng thông qua Tên_lớp danh sách mảng ; lời gọi. Tuy nhiên trong lời gọi phương thức bao giờ cũng phải có tên đối tượng để chỉ rõ phương thức thực hiện trên các thuộc tính § Ví dụ sử dụng lớp DIEM ở 1, có thể khai báo các biến, mảng của đối tượng nào. Ví dụ lời gọi: DIEM như sau: d1.nhapsl(); DIEM d1, d2, d3 ; // Khai báo 3 biến đối tượng d1, d2, d3 sẽ thực hiện nhập số liệu vào các thành phần d1.x, d1.y và d1.m DIEM d[20] ; // Khai báo mảng đối tượng d gồm 20 phần tử Câu lệnh 97 98 Mỗi đối tượng sau khi khai báo sẽ được cấp phát một vùng nhớ riêng để chứa các thuộc tính của chúng. Chú ý rằng sẽ không có d[3].nhapsl() ; vùng nhớ riêng để chứa các phương thức cho mỗi đối tượng. Các sẽ thực hiện nhập số liệu vào các thành phần d[3].x, d[3].y và d[3].m phương thức sẽ được sử dụng chung cho tất cả các đối tượng cùng Chúng ta sẽ minh hoạ các điều nói trên bằng một chương trình lớp. Như vậy về bộ nhớ được cấp phát thì đối tượng giống cấu trúc. đơn giản sử dụng lớp DIEM để nhập 3 điểm, hiện rồi ẩn các điểm Trong trương hợp này: vừa nhập. Trong chương trình đưa vào hàm kd_do_hoa() dùng để sizeof(d1) = sizeof(d2) = sizeof(d3) = 3*sizeof(int) = 6 khởi động hệ đồ hoạ. sizeof(d) = 20*6 = 120 #include Thuộc tính của đối tượng: #include Trong ví dụ trên, mỗi đối tượng d1, d2, d3 và mỗi phần tử d[i] #include đều có 3 thuộc tính là x, y, m. Chú ý là mỗi thuộc đều thuộc về một class DIEM đối tượng, vì vậy không thể viết tên thuộc một cách riêng rẽ mà bao { giờ cũng phải có tên đối tượng đi kèm, giống như cách viết trong cấu trúc của C hay bản ghi của PASCAL. Nói cách khác, cách viết thuộc private: tính của đối tượng như sau: int x, y, m ; tên_đối_tượng.Tên_thuộc_tính public: Với các đối tượng d1, d2, d3 và mảng d, có thể viết như sau: void nhapsl(); d1.x // Thuộc tính x của đối tượng d1 void an() d2.x // Thuộc tính x của đối tượng d2 { d3.y // Thuộc tính y của đối tượng d3 putpixel(x,y,getbkcolor()); d[2].m // Thuộc tính m của phần tử d[2] } d1.x = 100 ; // Gán 100 cho d1.x void hien(); d2.y = d1.x; // Gán d1.x cho d2.y }; void DIEM::nhapsl() Sử dụng các phương thức
- { setbkcolor(BLACK); cout > x >> y ; d2.hien(); cout > m ; getch(); } d1.an(); void DIEM::hien() d2.an(); { d3.an(); 99 100 int mau_ht; getch(); mau_ht = getcolor() ; closegraph(); putpixel(x,y,m); } setcolor(mau_ht); } § 3. Con trỏ đối tượng void kd_do_hoa() Con trỏ đối tượng dùng để chứa địa chỉ của biến, mảng đối tượng. Nó được khai báo như sau: { Tên_lớp *con trỏ ; int mh, mode ; Ví dụ dùng lớp DIEM có thể khai báo: mh=mode=0; DIEM *p1 , *p2, *p3 ; // khai báo 3 con trỏ p1, p2, p3 initgraph(&mh, &mode, ""); DIEM d1, d2 ; // Khai báo 2 đối tượng d1, d2 } DIEM d[20] ; // Khai báo mảng đối tượng void main() và có thể thực hiện các câu lệnh: { p1 = &d2 ; // p1 chứa địa chỉ của d2 , hay p1 trỏ tới d2 DIEM d1, d2, d3 ; p2 = d ; // p2 trỏ tới đầu mảng d d1.nhapsl(); p3 = new DIEM // Tạo một đối tượng và chứa địa chỉ của nó d2.nhapsl(); // vào p3 Để sử dụng thuộc tính của đối tượng thông qua con trỏ, ta viết d3.nhapsl(); như sau: kd_do_hoa(); Tên_con_trỏ->Tên_thuộc_tính
- Chú ý: Nếu con trỏ chứa địa chỉ đầu của mảng, có thể dùng con putpixel(x,y,getbkcolor()); trỏ như tên mảng. } Như vậy sau khi thực hiện các câu lệnh trên thì: void hien(); p1->x và d2.x là như nhau }; p2[i].y và d[i].y là như nhau void DIEM::nhapsl() { Tóm lại ta có quy tắc sau cout . Trong chương trình, không cin >> x >> y ; cho phép viết tên thuộc tính một cách đơn độc mà phải đi kèm tên cout > m ; 101 102 Tên_đối_tượng.Tên_thuộc_tính } Tên_con_trỏ->Tên_thuộc_tính void DIEM::hien() Tên_mảng_đối_tượng[chỉ_số].Tên_thuộc_tính { Tên_con_trỏ[chỉ_số].Tên_thuộc_tính int mau_ht; Chương trình dưới đây cũng sử dụng lớp DIEM (trong §1) để mau_ht = getcolor() ; nhập một dẫy điểm, hiển thị và ẩn các điểm vừa nhập. Chương trình dùng một con trỏ kiểu DIEM và dùng toán tử new để tạo ra một dẫy putpixel(x,y,m); đối tượng. setcolor(mau_ht); #include } #include void kd_do_hoa() #include { class DIEM int mh, mode ; { mh=mode=0; private: initgraph(&mh, &mode, ""); int x, y, m ; } public: void nhapsl(); void main() void an() { { DIEM *p; int i, n;
- cout > n; sau: p = new DIEM[n+1]; C++ sử dụng con trỏ đặc biệt this trong các phương thức. Các for (i=1; i > this->x >> this->y ; for (i=1; i > this->m ; getch(); } 103 104 closegraph(); Từ góc độ hàm số có thể kết luận rằng: Phương thức bao giờ cũng } có ít nhất một đối là con trỏ this và nó luôn luôn là đối đầu tiên của phương thức. § 4. Đối của phương thức, con trỏ this 4.2. Tham số ứng với đối con trỏ this 4.1. Con trỏ this là đối thứ nhất của phương thức Xét một lời gọi tới phương thức nhapsl() : Chúng ta hãy xem lại phương thức nhapsl của lớp DIEM DIEM d1; void DIEM::nhapsl() d1.nhapsl() ; { Trong trường hợp này tham số truyền cho con trỏ this chính là cout > x >> y ; this = &d1 cout > m ; this->x chính là d1.x } this->y chính là d1.y this->m chính là d1.m Rõ ràng trong phương thức này chúng ta sử dụng tên các thuộc tính x, y và m một cách đơn độc. Điều này có vẻ như mâu thuẫn với Như vậy câu lệnh
- d1.nhapsl() ; ve_tam_giac (Vẽ tam giác qua 3 điểm) sẽ nhập dữ liệu cho các thuộc tính của đối tượng d1. Từ đó có thể rút do_dai (Tính độ dài của đoạn thẳng qua 2 điểm) ra kết luận sau: chu_vi (Tính chu vi tam giác qua 3 điểm) Tham số truyền cho đối con trỏ this chính là địa chỉ của đối tượng Chương trình còn minh hoạ: đi kèm với phương thức trong lời gọi phương thức. + Việc phương thức này sử dụng phương thức khác (phương thức 4.3. Các đối khác của phương thức ve_tam_giac sử dụng phương thức ve_doan_thang, phương thức chu_vi sử dụng phương thức do_dai) Ngoài đối đặc biệt this (đối này không xuất hiện một cách tường minh), phương thức còn có các đối khác được khai báo như trong + Sử dụng con trỏ this trong thân các phương thức ve_tam_giac các hàm. Đối của phương thức có thể có kiểu bất kỳ (chuẩn và ngoài và chu_vi chuẩn). Nội dung chương trình là nhập 3 điểm, vẽ tam giác có đỉnh là 3 Ví dụ để xây dựng phương thức vẽ đường thẳng qua 2 điểm ta điểm vừa nhập sau đó tính chu vi tam giác. cần đưa vào 3 đối: Hai đối là 2 biến kiểu DIEM, đối thứ ba kiểu #include nguyên xác định mã mầu. Vì đã có đối ngầm định this là đối thứ #include nhất, nên chỉ cần khai báo thêm 2 đối. Phương thức có thể viết như sau: #include #include 105 106 void DIEM::doan_thang(DIEM d2, int mau) #include { class DIEM int mau_ht; { mau_ht = getcolor(); private: setcolor(mau); int x, y ; public: line(this->x,this->y,d2.x,d2.y); void nhapsl(); setcolor(mau_ht); void ve_doan_thang(DIEM d2, int mau) ; } void ve_tam_giac(DIEM d2, DIEM d3,int mau) ; Chương trình sau minh hoạ các phương thức có nhiều đối. Ta vẫn double do_dai(DIEM d2) dùng lớp DIEM nhưng có một số thay đổi: { + Bỏ thuộc tính m (mầu) DIEM d1 = *this ; return sqrt( pow(d1.x - d2.x,2) + + Bỏ các phương thức hien và an pow(d1.y - d2.y,2) ) ; +Đưa vào 4 phương thức mới: } ve_ doan_thang (Vẽ đoạn thẳng qua 2 điểm) double chu_vi(DIEM d2, DIEM d3);
- }; void main() void DIEM::nhapsl() { { DIEM d1, d2, d3; cout > x >> y ; d1.nhapsl(); } d2.nhapsl(); void kd_do_hoa() d3.nhapsl(); { kd_do_hoa(); int mh, mode ; d1.ve_tam_giac(d2,d3,15); mh=mode=0; double s = d1.chu_vi(d2,d3); initgraph(&mh, &mode, ""); sprintf(tb_cv,"Chu vi = %0.2f", s); } outtextxy(10,10,tb_cv); void DIEM::ve_doan_thang(DIEM d2, int mau) getch(); { closegraph(); } setcolor(mau); line(this->x,this->y,d2.x,d2.y); Một số nhận xét về đối của phương thức và lời gọi phương107 108 } thức + Quan sát nguyên mẫu phương thức: void DIEM::ve_tam_giac(DIEM d2, DIEM d3,int mau) void ve_doan_thang(DIEM d2, int mau) ; { sẽ thấy phương thức có 3 đối: (*this).ve_doan_thang(d2,mau); d2.ve_doan_thang(d3,mau); Đối thứ nhât là một đối tượng DIEM do this trỏ tới d3.ve_doan_thang(*this,mau); Đối thứ hai là đối tượng DIEM d2 } Đối thứ ba là biến nguyên mau double DIEM::chu_vi(DIEM d2, DIEM d3) Nội dung phương thức là vẽ một đoạn thẳng đi qua các điểm *this và d2 theo mã mầu mau. Xem thân của phương sẽ thấy được nội { dung này: double s; void DIEM::ve_doan_thang(DIEM d2, int mau) s= (*this).do_dai(d2) + d2.do_dai(d3) + d3.do_dai(*this) ; { return s; setcolor(mau); } line(this->x,this->y,d2.x,d2.y);
- } d3.ve_doan_thang(*this,mau); Tuy nhiên trong trương hợp này, vai trò của this không cao lắm, } vì nó được đưa vào chỉ cốt làm rõ đối thứ nhất. Trong thân phương Phương án không dùng this trong phương thức ve_tam_giac: thức có thể bỏ từ khoá this vẫn được. void DIEM::ve_tam_giac(DIEM d2, DIEM d3,int mau) + Vai trò của this trở nên quan trọng trong phương thức { ve_tam_giac: DIEM d1; void ve_tam_giac(DIEM d2, DIEM d3,int mau) ; d1.x = x; Phương thức này có 4 đối là: d1.y = y; this trỏ tới một đối tượng kiểu DIEM d1.ve_doan_thang(d2,mau); d2 một đối tượng kiểu DIEM d2.ve_doan_thang(d3,mau); d3 một đối tượng kiểu DIEM d3.ve_doan_thang(d1,mau); mau một biến nguyên } Nội dung phương thức là vẽ 3 cạnh: cạnh 1 đi qua *this và d2 § 5. Nói thêm về kiểu phương thức và kiểu đối của phương thức cạnh 2 đi qua d2 và d3 5.1. Kiểu phương thức cạnh 3 đi qua d3 và *this Phương thức có thể không có giá trị trả về (kiểu void) hoặc có thể109 110 Các cạnh trên được vẽ nhờ sử dụng phương thức ve_doan_thang: trả về một giá trị có kiểu bất kỳ, kể cả giá trị kiểu đối tượng, con trỏ Vẽ cạnh 1 dùng lệnh: (*this).ve_doan_thang(d2,mau) ; đối tượng, tham chiếu đối tượng. Vẽ cạnh 2 dùng lệnh: d2.ve_doan_thang(d3,mau); 5.2. Đối của phương thức Vẽ cạnh 3 dùng lệnh: d3.ve_doan_thang(*this,mau); Đối của phương thức (cũng giống như đối của hàm) có thể có Trong trường này rõ ràng vai trò của this rất quan trọng. Nếu kiểu bất kỳ: không dùng nó thì công việc trơ nên khó khăn, dài dòng và khó hiểu hơn. Chúng ta hãy so sánh 2 phương án: + Kiểu dữ liệu chuẩn như int, float, char, . Con trỏ hoặc tham chiếu đến kiểu dữ liệu chuẩn như int*, float*, char*, int&, float&, Phương án dùng this trong phương thức ve_tam_giac: char&, void DIEM::ve_tam_giac(DIEM d2, DIEM d3,int mau) + Các kiểu ngoài chuẩn đã định nghĩa trước như đối tượng, cấu { trúc, hợp, enum, . Con trỏ hoặc tham chiếu đến các kiểu ngoài (*this).ve_doan_thang(d2,mau); chuẩn này. d2.ve_doan_thang(d3,mau);
- + Kiểu đối tượng của chính phương thức, con trỏ hoặc tham #include chiếu đến kiểu đối tượng này. #include 5.3. Các ví dụ class HINH_CN Ví dụ 1 minh hoạ: { + Thuộc tính (thành phần dữ liệu) của lớp có thể là đối tượng private: của lớp khác đã định nghĩa bên trên. int d, r; // chieu dai va chieu rong + Phương thức có giá trị trả về kiểu đối tượng và con trỏ đối public: tượng. void nhapsl() Nội dung chương trình là nhập một dẫy hình chữ nhật, sau đó tìm { hình chữ nhật có max diện tích và hình chữ nhật có max chu vi. cout > d >> r ; + Lớp HINH_CN gồm: } - Các thuộc tính: d và r (chiều dài và chiều rộng) void in() - Các phương thức { void nhapsl() ; // Nhập chiều dài, rộng cout << "\nchieu dai = " << d ; int dien_tich(); // Tính diện tích cout << " chieu rong= " << r; int chu_vi() ; // Tính chu vi } + Lớp DAY_HINH_CN gồm int dien_tich() - Các thuộc tính: 111 112 { int n ; //số hình chữ nhật của dẫy return d*r; HINH_CN *h; //Con trỏ tới dẫy đối tượng của lớp HINH_CN } - Các phương thức int chu_vi() void nhapsl(); // Nhập một dẫy hình chữ nhật { HINH_CN hinh_dt_max() ; //Trả về hình chữ nhật có return 2*(d+r); // diện tích max } HINH_CN *hinh_cv_max() ; // Trả về con trỏ tới HCN có } ; // chu vi max class DAY_HINH_CN {
- private: for (int i=2; i h[imax].chu_vi() ) HINH_CN *h; imax = i ; public: return (h+imax); void nhapsl(); } HINH_CN hinh_dt_max() ; void main() HINH_CN *hinh_cv_max() ; } ; { void DAY_HINH_CN::nhapsl() DAY_HINH_CN d; { HINH_CN hdtmax; cout > n; hdtmax = d.hinh_dt_max(); h = new HINH_CN[n+1]; hdtmax.in() ; for (int i=1;i in() ; } getch(); HINH_CN DAY_HINH_CN::hinh_dt_max() } { Ví dụ 2 minh hoạ: HINH_CN hdtmax; + Thuộc tính (thành phần dữ liệu) của lớp có thể là đối tượng hdtmax = h[1]; của lớp khác đã định nghĩa bên trên. for (int i=2; i hdtmax.dien_tich() ) 113 114 + Vai trò của con trỏ this (xem phương thức maxdt của lớp hdtmax = h[i]; TAM_GIAC) return hdtmax; + Phương thức tĩnh (xem phương thức tao_tg của lớp } TAM_GIAC) Nội dung chương trình là nhập một dẫy các điểm, sau đó tìm tam HINH_CN *DAY_HINH_CN::hinh_cv_max() giác lớn nhất (về diện tích) có đỉnh là các điểm vừa nhập. { Chương trình được tổ chức thành 2 lớp: int imax = 1; + Lớp DIEM gồm:
- - Các thuộc tính: x và y (toạ độ của điểm) #include - Các phương thức #include void nhapsl() ; // Nhập x, y class DIEM void in() ; // In toạ độ { double do_dai(DIEM d2) ; // Tính độ dài đoạn thẳng qua private: // 2 điểm (điểm ẩn xác định bởi this và điểm d2) double x,y; // Toa do cua diem + Lớp TAM_GIAC gồm: public: - Các thuộc tính: void nhapsl() DIEM d1,d2,d3; // 3 đỉnh của tam giác { - Các phương thức: cout > x >> y ; void in(); // In toạ độ 3 đỉnh } // Tạo một đối tượng TAM_GIAC từ 3 đối tượng DIEM static TAM_GIAC tao_tg(DIEM e1, DIEM e2, DIEM e3) void in() double dien_tich() ; // Tính diện tích { // Tìm tam giác có diện tích max trong 2 tam giác *this và cout public: void nhapsl();
- void in(); double a,b,c,p,s; static TAM_GIAC tao_tg(DIEM e1, DIEM e2, DIEM e3) a=d1.do_dai(d2); { b=d2.do_dai(d3); TAM_GIAC t; c=d3.do_dai(d1); p=(a+b+c)/2; t.d1=e1; t.d2 = e2; t.d3=e3; return sqrt(p*(p-a)*(p-b)*(p-c)); return t; } } TAM_GIAC TAM_GIAC::maxdt(TAM_GIAC t2) double dien_tich() ; { TAM_GIAC maxdt(TAM_GIAC t2); if (this->dien_tich() > t2.dien_tich()) } ; return *this ; void TAM_GIAC::nhapsl() else { return t2; cout > n; { for (i=1; i<=n; ++i) cout << "\nDinh 1: " ; d1.in(); { cout << "\nDinh 2: " ; d2.in(); cout << "\nNhap diem " << i << " - " ; cout << "\nDinh 3: " ; d3.in(); d[i].nhapsl(); } } 117 118 double TAM_GIAC::dien_tich() int j, k ; { TAM_GIAC tmax, t;
- tmax = TAM_GIAC::tao_tg(d[1],d[2],d[3]); - Không có đối ngầm định xác định bởi con trỏ this (như for (i=1;i t.d1=e1; t.d2 = e2; t.d3=e3; #include return t; #include } class DIEM Phương thức tĩnh (sẽ nói thêm trong các mục bên dưới) có các { đặc điểm sau: private: double x,y; // Toa do cua diem + Nó giống phương thức thông thường ở chỗ: Trong thân của nó public: có thể truy nhập tới các thành phần của lớp (cụ thể là lớp TAM_GIAC). void nhapsl() { + Nó khác phương thức thông thường ở chỗ: cout > x >> y ; } 119 120
- void in() { { cout dien_tich() > t2.dien_tich()) void TAM_GIAC::nhapsl() return *this ;
- else getch(); return t2; } } void main() Chú ý: Hàm bạn có thể xây dựng bên trong định nghĩa lớp (như 121 122 { chương trình trên) hoặc có thể khai báo bên trong và xây dựng bên ngoài định nghĩa lớp như sau: DIEM d[50]; class TAM_GIAC int n, i ; { clrscr(); private: cout > n; public: for (i=1; i<=n; ++i) void nhapsl(); { void in(); cout << "\nNhap diem " << i << " - " ; friend TAM_GIAC tao_tg(DIEM e1,DIEM e2,DIEM e3); d[i].nhapsl(); double dien_tich() ; } TAM_GIAC maxdt(TAM_GIAC t2); int j, k ; } ; TAM_GIAC tmax, t; tmax = tao_tg(d[1],d[2],d[3]); TAM_GIAC tao_tg(DIEM e1, DIEM e2, DIEM e3) for (i=1;i<=n-2;++i) { for (j=i+1;j<=n-1;++j) TAM_GIAC t; for (k=j+1;k<=n;++k) t.d1=e1; t.d2 = e2; t.d3=e3; { return t; t=tao_tg(d[i],d[j],d[k]); } tmax = tmax.maxdt(t); Nhận xét: Không cho phép dùng từ khoá friend khi xây dựng } hàm (bên ngoài lớp) cout << "\n\nTam giac co dien tich lon nhat: " ; tmax.in(); § 6. Hàm, hàm bạn cout << "\nDien tich = " << tmax.dien_tich();
- 6.1. Hàm có các tính chất sau: Hàm này sẽ bị báo lỗi khi dịch, vì trong thân hàm không cho phép + Phạm vi của hàm là toàn bộ chương trình, vì vậy hàm có thể sử dụng các thuộc tính d1.x, d1.y, d2.x, d2.y của các đối tượng d1 và được gọi tới từ bất kỳ chỗ nào. Như vây trong các phương thức có d2 thuộc lớp DIEM. thể sử dụng hàm. + Phạm vi sử dụng của các phương thức (public) là toàn chương + Đối của hàm có thể là các đối tượng, tuy nhiên có một hạn chế trình, vì vậy trong thân hàm có thể gọi tới các phương thức. Ví dụ là trong thân hàm không cho phép truy nhập tới thuộc tính của các123 124giả sử đã định nghĩa lớp: đối này. Ví dụ giả sử đã định nghĩa lớp: class DIEM class DIEM { { private: private: double x,y; // Toa do cua diem double x,y; // Toa do cua diem public: public: void nhapsl() void nhapsl() { { cout > x >> y ; cin >> x >> y ; } } void in() void in() { { cout << " x = " << x << " y = " << y; cout << " x = " << x << " y = " << y; } } }; double do_dai(DIEM d2) Dùng lớp DIEM, ta xây dựng hàm tính độ dài của đoạn thẳng đi { qua 2 điểm như sau: return sqrt(pow(x-d2.x,2) + pow(y-d2.y,2) ); double do_dai(DIEM d1, DIEM d2) } { } ; return sqrt(pow(d1.x-d2.x,2) + pow(d1.y-d2.y,2)); Khi đó bằng cách dùng phương thức do_dai, ta có thể viết hàm } tính diện tích tam giác có đỉnh là các đối tượng d1, d2, d3 của lớp DIEM như sau:
- double dt_tg(DIEM d1, DIEM d2, DIEM d3) cout p=(a+b+c)/2; #include return sqrt(p*(p-a)*(p-b)*(p-c)); #include } class DIEM void main() { { private: DIEM d[50]; double x,y; // Toa do cua diem int n, i,j,k,imax,jmax,kmax ; public: clrscr(); void nhapsl() cout > n; cout > x >> y ; cout << "\nNhap diem " << i << " - " ; } d[i].nhapsl(); void in() } { imax=1; jmax=2; kmax=3;
- for (i=1;i dt_tg(d[imax],d[jmax],d[kmax])) private: { double x,y; // Toa do cua diem imax = i ; public: jmax = j; void nhapsl(); kmax = k; void in(); } double do_dai(DIEM d2); cout > x >> y ; Bây giờ nếu ta dùng mảng ngoài thì từ số thứ tự sẽ suy ra phần tử của mảng. Như vây hàm } double dt_tg(DIEM d1, DIEM d2, DIEM d3); void DIEM::in() có 3 đối kiểu DIEM có thể thay bằng hàm có 3 đối nguyên: { double dt_tg(int i, int j, int k); cout #include double dt_tg(int i, int j, int k) #include {
- double a,b,c,p,s; cout > n; class A for (i=1; i dt_tg(imax,jmax,kmax)) friend A f3( ) ; { imax = i ; } ; jmax = j; // Xây dựng các hàm f1, f2, f3 kmax = k; void f1( ) } { cout << "\n\nTam giac co dien tich lon nhat: " ;
- } { double f2( ) { } } } ; A f3( ) 6.2.2. Tính chất của hàm bạn { Trong thân hàm bạn của một lớp có thể truy nhập tới các thuộc tính của các đối tượng thuộc lớp này. Đây là sự khác nhau duy nhất giữa hàm bạn và hàm thông thường. Chú ý rằng hàm bạn không phải } là phương thức của lớp. Phương thức có một đối ẩn (ứng với con trỏ this) và lời gọi của phương thức phải gắn với một đối tượng nào đó Cách 2: Dùng từ khoá friend để xây dựng hàm trong định nghĩa (địa chỉ đối tượng này được truyền cho con trỏ this). Lời gọi của hàm lớp. Mẫu viết như sau: bạn giống như lời gọi của hàm thông thường. class A Ví dụ sau sẽ so sánh phương thức, hàm bạn và hàm tự do (hàm 131 132 { thông thường). Xét lớp SP (số phức). Hãy so sánh 3 phương án để private: thực hiện việc cộng 2 số phức: // Khai báo các thuộc tính Phương án 1: Dùng phương thức public: class SP { // Xây dựng các hàm bạn của lớp A private: void f1( ) double a; // Phần thực { double b; // Phần ảo public: } SP cong(SP u2) double f2( ) { { SP u: } u.a = this->a + u2.a ; A f3( ) u.b = this->b + u2.b ;
- return u; private: } double a; // Phần thực } ; double b; // Phần ảo public: Cách dùng SP u, u1, u2; } ; u = u1.cong(u2); SP cong(SP u1, SP u2) Phương án 2: Dùng hàm bạn { class SP SP u: { u.a = u1.a + u2.a ; private: u.b = u1.b + u2.b ; double a; // Phần thực return u; double b; // Phần ảo } public: friend SP cong(SP u1, SP u2) 133 134 Phương án này không được chấp nhận, Trình biên dịch sẽ báo lỗi { vì trong thân hàm không được quyền truy xuất đến các thuộc tính SP u: riêng (private) a, b của các đối tượng u, u1 và u2 thuộc lớp SP. u.a = u1.a + u2.a ; 6.2.3. Một hàm có thể là bạn của nhiều lớp được không? Câu u.b = u1.b + u2.b ; trả lời là được. Khi một hàm là bạn của nhiều lớp, thì nó có quyền return u; truy nhập tới tất cả các thuộc tính của các đối tượng trong các lớp này. Để làm cho hàm f trở thành bạn của các lớp A, B và C ta sử } dụng mẫu viết sau: }; class B; // Khai báo trước lớp A Cách dùng class B; // Khai báo trước lớp B SP u, u1, u2; class C; // Khai báo trước lớp C u = cong(u1, u2); // Định nghĩa lớp A Phương án 3: Dùng hàm tự do class A class SP { { // Khai báo f là bạn của A
- friend void f( ) ; Nội dung chương trình là nhập một ma trận vuông cấp n và một } ; véc tơ cấp n, sau đó thực hiện phép nhân ma trận với véc tơ vừa nhập. // Định nghĩa lớp B // Chương trình CT3_09.CPP class B #include { #include // Khai báo f là bạn của B #include friend void f( ) ; class VT; } ; class MT ; // Định nghĩa lớp C class VT class C { { private: // Khai báo f là bạn của C int n; friend void f( ) ; double x[20]; // Toa do cua diem } ; public: // Xây dụng hàm f 135 136 void nhapsl(); void f( ) friend void in(const VT &x); { friend VT tich(const MT &a,const VT &x) ; } ; } class MT Chương trình sau đây minh hoạ cách dùng hàm bạn (bạn của một { lớp và bạn của nhiều lớp). Chương trình đưa vào 2 lớp VT (véc tơ), private: MT (ma trận) và 3 hàm bạn để thực hiện các thao tác trên 2 lớp này: int n; // Hàm bạn với lớp VT dùng để in một véc tơ double a[20][20]; friend void in(const VT &x); public: // Hàm bạn với lớp MT dùng để in một ma trận friend VT tich(const MT &a,const VT &x); friend void in(const MT &a); friend void in(const MT &a); // Hàm bạn với cả 2 lớp MT và VT dùng để nhân ma trận với véc tơ void nhapsl(); friend VT tich(const MT &a,const VT &x);
- } ; return x; void VT::nhapsl() y.n = n; { for (int i=1; i > n ; y.x[i]=0; for (int i=1; i > x[i]; return y; } } } void in(const VT &x) void MT::nhapsl() { { cout > n ; cout > a[i][j]; for (int i=1; i<=a.n; ++i) } { } cout << "\n" ; VT tich(const MT &a,const VT &x) for (int j=1; j<=a.n; ++j) { cout << a.a[i][j] << " "; VT y; } int n=a.n; } if (n!=x.n) void main()
- { Chú ý: Các thành phần khai báo mặc định (không dùng các từ MT a; VT x,y; khoá private và public) được xem là các thành phần private. clrscr(); 7.2. Các thành phần riêng của lớp chỉ được sử dụng trong phạm vi a.nhapsl(); của lớp (trong thân các phương thức của lớp). Chúng không thể đem ra sử dụng bên ngoài lớp. x.nhapsl(); + Một thuộc tính private: Thuộc tính này (của một đối tượng nào y=tich(a,x); đó) chỉ có thể được sử dụng trong thân của các phương thức cùng clrscr(); lớp. cout << "\nMa tran A:"; + Một phương thức private: Chỉ được sử dụng trong thân của các phương thức cùng lớp. in(a); Ví dụ sau minh hoạ cách dùng phương thức private. Xét lớp PS cout << "\n\nVec to x: " ; (phân số) với 2 thuộc tính nguyên là t (tử) và m (mẫu). Giả sử cần in(x); xây dựng các phương thức để thực hiện các phép toán cộng trừ, cout << "\n\nVec y = Ax: " ; nhân, chia phân số. Do các phép toán này cần dùng trong toàn bộ chương trình, nên các phương thức thực hiện các phép toán cần khai in(y); báo là public. Để thực hiện các phép tính trên phân số cần dùng đến getch(); phép rút gọn phân số. Ta có thể dùng một phương thức private để } làm điều này vì việc rút gọn chỉ dùng trong nội bộ lớp. 7.3. Các thành phần công cộng của lớp có phạm vi sử dụng trong toàn chương trình. Như vậy nếu một thuộc tính được khai báo là public, thì nó có thể được truy nhập trong thân của bất kỳ hàm nào trong chương trình. § 7. Phạm vi truy xuất 139 140 Ví dụ trong §6 đã chỉ ra phương án dùng một hàm (tự do) để thực 7.1. Các từ khoá private và public hiện phép cộng 2 số phức như sau là sai: Các thành phần (thuộc tính và phương thức) của lớp có thể khai Phương án 3: Dùng hàm tự do báo là private hoặc public theo mẫu: class SP private: { // Khai báo các thành phần riêng của lớp private: public: double a; // Phần thực // Khai báo các thành phần chung (công cộng) double b; // Phần ảo public:
- có đối tường minh. Ví dụ phương thức toán tử - (đổi dấu) một đối } ; tượng kiểu SP (số phức) có thể viết như sau: SP cong(SP u1, SP u2) class SP { { SP u: private: u.a = u1.a + u2.a ; double a; // Phần thực u.b = u1.b + u2.b ; return u; double b; // Phần ảo } public: SP operator-(); Tuy nhiên nếu sửa chữa bằng cách khai báo các thuộc tính a và b là public thì lại được. } ; Nhận xét: Các thuộc tính thường khai báo là private để đảm bảo SP SP:: operator-() tính dấu kín, an toàn dữ liệu của lớp. { SP u ; § 8. Các phương thức toán tử u.a = - this->a ; 8.1. Cách đặt tên u.b = - this->b ; Các phương thức toán tử được xây dựng như các phương thức return u; thông thường, chỉ có khác cách đặt tên. Tên các phương thức toán tử } (cũng giống như hàm toán tử) được tạo bằng cách ghép từ khoá Cách dùng: operator với một phép toán, ví dụ: SP u, v; operator+ u = -v; operator > 141 1428.4. Toán tử hai toán hạng Các phương thức toán tử hai toán hạng: Con trỏ this ứng với toán 8.2. Con trỏ this hạng thứ nhất, nên trong phương thức chỉ cần dùng một đối tường Cũng giống như phương thức thông thường, phương thức toán tử minh để biểu thị toán hạng thứ hai. Ví dụ phương thức toán tử + có đối đầu tiên (đối không tường minh) là con trỏ this. (cộng) hai đối tượng kiểu SP (số phức) có thể viết như sau: 8.3. Toán tử một toán hạng class SP Các phương thức toán tử một toán hạng: Dùng ngay con trỏ this { để biểu thị toán hạng duy nhất này, nên trong phương thức sẽ không private:
- double a; // Phần thực operator[] dùng để cho biết bậc và hệ số của đa thức double b; // Phần ảo + Các hàm bạn: public: operator > dùng để nhập các hệ số đa thức } ; + Hàm (tự do) SP SP:: operator+(SP u2) double F(DT p, double x) dùng để tính p(x)-giá trị đa thức tại x { + Nói thêm về phương thức chỉ số và hàm tự do F SP u ; - Nếu p là đối tượng của lớp DT, thì hàm chỉ số cho biết: u.a = this->a + u2.a ; p[-1] = double(n) u.b = this->b + u2.b ; p[i] = a[i] , i=0, 1, , n return u; - Hàm tự do F sẽ dùng phương thức chỉ số để xác định n , các } hệ số đa thức và dùng chúng để tính giá trị đa thức. Cách dùng: + Trong chương trình sử dụng hàm new để cấp phát vùng nhớ SP p, p, r; chứa hệ số đa thức. r = p + q ; + Nội dung chương trình gồm: - Nhập, in các đa thức p, q, r, s 8.5. Lớp DT (Đa thức) - Tính đa thức: f = -(p + q)*(r - s) Chương trình sau sẽ định nghĩa lớp DT và đưa vào các phương - Nhập các số thực x1 và x2 thức, hàm: - Tính f(x1) (bằng cách dùng phương thức operator^) + Các thuộc tính: - Tính f(x2) (bằng cách dùng hàm F) int n ; // bậc đa thức // Chương trình CT3_10.CPP double *a ; // trỏ tới vùng nhớ chứa các hệ số đa thức #include + Các phương thức operator+, operator- dùng để đổi dấu các hệ số đa thức 143 144 #include operator+ dùng để cộng 2 đa thức #include operator- dùng để trừ 2 đa thức class DT operator* dùng để nhân 2 đa thức { operator^ dùng để tính giá trị đa thức private: int n; // Bac da thuc
- double *a; // Tro toi vung nho chua cac he so da thuc ostream& operator > (istream& is,DT &d); os > (istream& is,DT &d) double operator^(const double &x); // Tinh gia tri da thuc { double operator[](int i) cout > d.n; if(i > d.a[i] ; double F(DT d,double x) } { return is; double s=0.0 , t=1.0; } int n; DT DT::operator-() n = int(d[-1]); { for (int i=0; i<=n; ++i) DT p; { s += d[i]*t; p.n = n; t *= x; p.a = new double[n+1]; 145 146 } for (int i=0 ; i<=n ; ++i) return s; p.a[i] = -a[i]; } return p;
- } k = d.n = n + d2.n ; DT DT::operator+(const DT &d2) d.a = new double[k+1]; { for (i=0; i d2.n ? n : d2.n ; d.a[i+j] += a[i]*d2.a[j] ; d.a = new double[k+1]; return d; for (i=0; i 0 && d.a[i]==0.0) i; } d.n = i; return s; return d ; } } void main() DT DT::operator-(DT d2) { { DT p,q,r,s,f; return (*this + (-d2)); double x1,x2,g1,g2; } clrscr(); DT DT::operator*(const DT &d2) cout > p; { cout > q; int k, i, j; 147 148 cout << "\nDa thuc q " << q ;
- cout > r; cout > s; cout > x1; cout > x2; g1 = f^x1; g2 = F(f,x2); cout << "\nDa thuc f " << f ; cout << "\n f("<<x1<<") = " << g1; cout << "\n f("<<x2<<") = " << g2; getch(); } 149 147 148