Giáo trình Lập trình hướng đối tượng - Trình độ: Cao đẳng, Trung cấp - Trường Cao đẳng cơ giới Ninh Bình

doc 171 trang Gia Huy 16/05/2022 2080
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 - Trình độ: Cao đẳng, Trung cấp - Trường Cao đẳng cơ giới Ninh Bình", để 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:

  • docgiao_trinh_lap_trinh_huong_doi_tuong_trinh_do_cao_dang_trung.doc

Nội dung text: Giáo trình Lập trình hướng đối tượng - Trình độ: Cao đẳng, Trung cấp - Trường Cao đẳng cơ giới Ninh Bình

  1. BỘ NÔNG NGHIỆP VÀ PHÁT TRIỂN NÔNG THÔN TRƯỜNG CAO ĐẲNG CƠ GIỚI NINH BÌNH GIÁO TRÌNH MÔN HỌC: MH 13_LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG NGHỀ: LẬP TRÌNH MÁY TÍNH TRÌNH ĐỘ: Cao đẳng/ trung cấp Ban hành kèm theo Quyết định số: /QĐ- TCGNB ngày .tháng .năm của Hiệu trưởng Trường Cao Đẳng Cơ giới Ninh Bình Ninh Bình, năm 2018 1
  2. TUYÊN BỐ BẢN QUYỀN Tài liệu này thuộc loại sách giáo trình nên các nguồn thông tin có thể được phép dùng nguyên bản hoặc trích dùng cho các mục đích về đào tạo và tham khảo. Mọi mục đích khác mang tính lệch lạc hoặc sử dụng với mục đích kinh doanh thiếu lành mạnh sẽ bị nghiêm cấm. 2
  3. MỤC LỤC Lời nói đầu 1 Chương 1 PHƯƠNG PHÁP HƯỚNG ĐỐI TƯỢNG 3 1.1 Các phương pháp lập trình. 3 1.2 Đặc điểm của lập trình hướng đối tượng. 10 1.3 Xây dựng lớp đối tượng. 10 BÀI TẬP CHƯƠNG 1 11 Chương 2 CÁC THÀNH PHẦN CỦA LỚP 12 2.1 Khai báo một lớp cơ sở. 2.2 Hàm CONSTRUCTOR (Hàm tạo). 13 2.3 Hàm DESTRUCTOR (Hàm hủy). 19 2.4 Hàm INLINE (Hàm nội tuyến). 22 2.5 Thành phần tĩnh của lớp 25 2.6 Hàm FRIEND (hàm bạn) 29 BÀI TẬP CHƯƠNG 2 39 Chương 3 LỚP 3.1 Định nghĩa lớp. 41 3.2 Tạo lập đối tượng. 44 3.3 Truy nhập tới các thành phần của lớp 45 3.4 Con trỏ đối tượng. 47 BÀI TẬP CHƯƠNG 3 59 Chương 4. TOÁN TỬ ĐỊNH NGHĨA CHỒNG 5.1 Cách định nghĩa chồng các toán tử 63 5.2 Cách dùng hàm toán tử 76 BÀI TẬP CHƯƠNG 4 83 Chương 5 THỪA KẾ 6.1 Giới thiệu 84 6.2 Đơn thừa kế 86 6.3 Đa kế thừa 99 BÀI TẬP CHƯƠNG 5 106 Chương 6: HÀM ẢO VÀ TÍNH TƯƠNG ỨNG BỘI 111 7.1 Hàm ảo. 111 7.2 Lớp cơ sở ảo. 117 Chương 7: HÀM, LỚP TEMPLATE 8.1 Khuôn hình hàm 125 8.2 Khuôn hình lớp 130 BÀI TẬP CHƯƠNG 7 133 Phụ lục 1 135 Phụ lục 2 137 TÀI LIỆU THAM KHẢO 143 3
  4. LỜI GIỚI THIỆU Phương pháp lập trình hướng đối tượng trở nên phổ biến, ngày càng được quan tâm nghiên cứu nhiều. Vì lập trình hướng đối tượng dựa trên việc tổ chức chương trình thành các lớp. Khác với hàm và thủ tục, lớp là một đơn vị bao gồm cả dữ liệu và các phương thức xử lý vì vậy lớp có thể mô tả các thực thể một cách chân thực, đầy đủ cả phần dữ liệu và yêu cầu quản lý. Tư tưởng lập trình hướng đối tượng được áp dụng cho hầu hết các ngôn ngữ mới chạy trên môi trường Windows như Microsoft Access, C++, Visual Basic, Visual C++, Java, Vì vậy việc nghiên cứu phương pháp lập trình mới này là rất cần thiết đối với tất cả những người quan tâm tới lập trình. C ra đời năm 1973 với mục đích ban đầu là để viết hệ điều hành Unix trên máy tính mini PDP. Sau đó C đã được sử dụng rộng rãi trên nhiều loại máy tính khác nhau và đã trở thành một ngôn ngữ lập trình cấu trúc rất được ưa chuộng. Để đưa C vào thế giới hướng hướng đối tượng, năm 1980 nhà khoa học người Mỹ B. Stroustrup đã cho ra đời một ngôn ngữ C mới có tên ban đầu là “C có lớp”, sau đó đến năm 1983 thì gọi là C++. Ngôn ngữ C++ là một sự phát triển mạnh mẽ của C. Trong C++ chẳng những đưa vào tất cả các khái niệm, công cụ của lập trình hướng đối tượng mà còn đưa vào nhiều khả năng mới mẻ cho hàm. Như vậy C++ là một ngôn ngữ lai cho phép tổ chức chương trình theo các lớp và các hàm. Có thể nói C++ đã thúc đẩy ngôn ngữ C vốn đã rất thuyết phục đi vào thế giới lập trình hướng đối tượng và C++ đã trở thành ngôn ngữ hướng đối tượng nổi bật trong những năm 90. Bài giảng này sẽ trình bầy một cách hệ thống các khái niệm của lập trình hướng đối tượng được cài đặt trong C++ như lớp, đối tượng, sự thừa kế, tính tương ứng bội và các khả năng mới trong xây dựng, sử dụng hàm như: đối tham chiếu, đối mặc định, hàm trùng tên, hàm toán tử. Các chương từ 1 đến 7 với cách giải thích tỉ mỉ và với nhiều chương trình minh hoạ sẽ cung cấp cho người học các khái niệm, phương pháp và kinh nghiệm lập trình hướng đối tượng trên C++. Bài giảng gồm 7 chương và 2 phụ lục Chương 1: Phương pháp hướng đối tượng. Chương 2: Các thành phần của lớp. Chương 3: Lớp. Chương 4: Toán tử định nghĩa chồng. Chương 5: Thừa kế. Chương 6: Hảm ảo và tính tương ứng bội. Chương 7:Hàm, lớp template. Phụ lục 1 trình bầy các phép toán trong C++ và thứ tự ưu của chúng. Phụ lục 2 trình bầy một vấn đề quan trọng nhưng còn ít được nói đến trong 4
  5. các tài liệu, đó là cách sử dụng con trỏ void để xây dựng các hàm với số đối không cố định giống như các hàm printf và scanf của C. Khi viết chúng tôi đã hết sức cố gắng để cuốn sách được hoàn chỉnh, song chắc chắn không tránh khỏi thiếu sót, vì vậy rất mong nhận được sự góp ý của các bạn. Xin chân thành cám ơn! Ninh Bình, ngày tháng năm 2018 Tham gia biên soạn 1. Chủ biên - Đoàn Xuân Luận 2. Phạm Thị Thoa 3. Nguyễn Anh Văn 5
  6. GIÁO TRÌNH MÔN HỌC Tên Môn học: Lập trình hướng đối tượng Mã môn học: MH13 Vị trí, tính chất của mô đun: - Vị trí: Môn học được bố trí sau khi học xong các môn học chung. - Tính chất: Môn học này là môn học cơ sở. - Ý nghĩa, vai trò của môn học: Đây là môn học cơ sở ngành của các ngành liên quan đến công nghệ thông tin, cung cấp cho sinh viên các kiến thức cơ bản về lập trình. Mục tiêu của môn học: - Về kiến thức: + Trình bày được các đặc trưng cơ bản: tính đóng gói, tính kế thừa, tính tương ứng bội của phương pháp lập trình hướng đối tượng; + Tiếp cận được phương pháp lập trình hướng đối tượng. - Về kỹ năng: + Phân tích, cài đặt và xây dựng được chương trình theo phương pháp hướng đối tượng trên một ngôn ngữ lập trình cụ thể; + Viết chương trình và thực hiện chương trình trong máy tính. - Về năng lực tự chủ và trách nhiệm: + Bố trí làm việc khoa học đảm bảo an toàn cho người và phương tiện học tập. Nội dung của môn học: Chương 1: Phương pháp hướng đối tượng. Chương 2: Các thành phần của lớp. Chương 3: Lớp. Chương 4: Toán tử định nghĩa chồng. Chương 5: Thừa kế. Chương 6: Hảm ảo và tính tương ứng bội. Chương 7:Hàm, lớp template. 6
  7. CHƯƠNG 1 PHƯƠNG PHÁP HƯỚNG ĐỐI TƯỢNG Mã chương: MH13_CH01 Giới thiệu: Phương pháp hướng đối tượng hiện đang được sử dụng rộng rãi trên thế giới do có những đặt điểm quan trọng giúp người lập trình có thể giảm thiểu đáng kể thời gian lập trình, một số công nghệ hiện đại hiện nay như C++ Builder, Visual Studio .NET, cho phép người lập trình kế thừa một kho tàng đối tượng phong phú để có thể xây dựng giao diện ứng dụng đồ họa một cách nhanh chóng. Những đặt tính cơ bản của phương pháp này sẽ được trình bày trong môn học này. Mục tiêu: - Trình bày được các phương pháp của lập trình hướng đối tượng, các đặt điểm nỗi bật của phương pháp lập trình hướng đối tượng. - Trình bày được các thành phần của một lớp đối tượng - Thực hiện các thao tác an toàn với máy tính. Nội dung: Chương 1: PHƯƠNG PHÁP HƯỚNG ĐỐI TƯỢNG 1.1 Các phương pháp lập trình. 1.1.1 Lập trình cấu trúc. Tư tưởng chính của lập trình cấu trúc là tổ chức chương trình thành các chương trình con. Mỗi chương trình con đảm nhận xử lý một công việc nhỏ trong toàn bộ hệ thống. Mỗi chương trình con này lại có thể chia nhỏ thành các chương trình con nhỏ hơn. Quá trình phân chia như vậy tiếp tục diễn ra cho đến các chương trình con nhỏ nhận được đủ đơn giản, đó là quá trình làm mịn dần. Các chương trình con tương đối độc lập với nhau. Ngôn ngữ lập trình thể hiện rõ nét nhất phương pháp lập trình cấu trúc là ngôn ngữ lập trình Pascal. Trong ngôn ngữ lập trình C chỉ có một loại chương trình con là hàm. Hàm là một đơn vị chương trình độc lập dùng để thực hiện một phần việc nào đó như: nhập số liệu, in kết quả hay thực hiện một số tính toán. Hàm cần có đối và các biến, mảng cục bộ dùng riêng cho hàm. Việc trao đổi dữ liệu giữa các hàm thực hiện thông qua các đối và các biến toàn bộ Các ngôn ngữ như C, PASCAL, FOXPRO là các ngôn ngữ cho phép triển khai. phương pháp lập trình cấu trúc. Một chương trình cấu trúc gồm các cấu trúc dữ liệu (như biến, mảng, bản ghi) và các hàm, thủ tục. Nhiệm vụ chính của việc tổ chức thiết kế chương trình hướng cấu trúc là tổ chức chương trình thành các hàm, thủ tục: Chương trình sẽ bao gồm các hàm, thủ tục nào. 7
  8. Ví dụ : Xét yêu cầu sau: Viết chương trình nhập toạ độ (x,y) của một dãy điểm, sau đó tìm một cặp điểm cách xa nhau nhất. Trên tư tưởng của lập trình hướng cấu trúc có thể tổ chức chương trình như sau: + Sử dụng 2 mảng thực toàn bộ x và y để chứa toạ độ dãy điểm + Xây dựng 2 hàm: Hàm nhapsl dùng để nhập toạ độ n điểm, hàm này có một đối là biến nguyên n và được khai báo như sau: void nhapsl(int n); Hàm do_dai dùng để tính độ dài đoạn thẳng đi qua 2 điểm có chỉ số là i và j, nó được khai báo như sau: float do_dai(int i, int j); Chương trình C cho bài toán trên được viết như sau: #include #include #include float x[100],y[100]; float do_dai(int i, int j) { return sqrt(pow(x[i]-x[j],2)+pow(y[i]-y[j],2)); } void nhapsl(int n) { int i; for (i=1;i<=n;++i) { printf("\nNhap toa do x, y cua diem thu %d : ",i); scanf("%f%f",&x[i],&y[i]); } } void main() { int n,i,j,imax,jmax; float d,dmax; printf("\nSo diem N= "); scanf("%d",&n); nhapsl(n); dmax=do_dai(1,2); imax=1;jmax=2; for (i=1;i<=n-1;++i) for (j=i+1;j<=n;++j) 8
  9. { d=do_dai(i,j); if (d>dmax) { dmax=d; imax=i; jmax=j; } } printf("\nDoan thang lon nhat co do dai bang: %0.2f",dmax); printf("\n Di qua 2 diem co chi so la %d va %d",imax,jmax); getch(); } Tuy nhiên khi sử dụng phương pháp lập trình cấu trúc thì Chương trình=Cấu trúc dữ liệu+Giải thuật. Điều này đòi hỏi người lập trình phải có kiến thức vững về cấu trúc dữ liệu. Khó khăn gặp phải trong lập trình cấu trúc là giải thuật của chương trình phụ thuộc rất chặt chẽ vào cấu trúc dữ liệu, do đó chỉ cần có một thay đổi nhỏ ở cấu trúc dữ liệu cũng có thể làm thay đổi giải thuật và như vậy phải viết lại chương trình. Điều này không thể thích hợp khi phải xây dựng một dự án phần mềm lớn. Phương pháp lập trình hướng đối tượng ra đời khắc phục được nhược điểm này của lập trình cấu trúc. 1.1.2 Lập trình hướng đối tượng. Khái niệm trọng tâm của lập trình hướng đối tượng là lớp (class). Có thể xem lớp là sự kết hợp các thành phần dữ liệu và các hàm. Cũng có thể xem lớp là sự mở rộng của cấu trúc trong C (struct) bằng cách đưa thêm vào các phương thức (method) hay còn gọi là hàm thành viên (member function). Một lớp được định nghĩa như sau: class Tên_Lớp { // Khai báo các thành phần dữ liệu // Khai báo các phương thức }; Các phương thức có thể được viết (xây dựng) bên trong hoặc bên ngoài (phía dưới) phần định nghĩa lớp. Cấu trúc (cách viết) phương thức tương tự như hàm ngoại trừ quy tắc sau: khi xây dựng một phương thức bên ngoài định nghĩa lớp thì trước tên phương thức cần có tên lớp và 2 dấu : để chỉ rõ phương thức thuộc lớp nào (xem ví dụ bên dưới). - Sử dụng các thành phần dữ liệu trong phương thức: Vì phương thức và các thành phần dữ liệu thuộc cùng một lớp nên trong thân của phương thức có quyền truy 9
  10. nhập đến các thành phần dữ liệu (của cùng lớp). - Biến lớp: Sau khi định nghĩa một lớp, có thể dùng tên lớp để khai báo các biến kiểu lớp hay còn gọi là đối tượng. Mỗi đối tượng sẽ có các thành phần dữ liệu và các phương thức. Lời gọi một phương thức cần chứa tên đối tượng để xác định phương thức thực hiện từ đối tượng nào. - Một chương trình hướng đối tượng sẽ bao gồm các lớp có quan hệ với nhau. - Việc phân tích, thiết kế chương trình theo phương pháp hướng đối tượng nhằm thiết kế, xây dựng các lớp. - Từ khái niệm lớp nảy sinh hàng loạt khái niệm khác như: thành phần dữ liệu, phương thức, phạm vi, sự đóng gói, hàm tạo, hàm huỷ, sự thừa kế, lớp cơ sở, lớp dẫn xuất, tương ứng bội, phương thức ảo, +Đối tượng (object) là sự biểu diễn một thực thể phần mềm gồm các thuộc tính và các phương thức liên quan. Một đối tượng cụ thể được gọi là một thể hiện (instance) Một đối tượng là sự đóng gói 2 thành phần: Trạng thái (state) hay dữ liệu Các ứng xử (behavior) hay hành vi, thao tác +Lớp (class) là tập hợp các đối tượng có cùng thuộc tính và hành vi Lớp là bản thiết kế hoặc bản mẫu mô tả một cấu trúc dữ liệu gồm: Các thành phần dữ liệu Các phương thức Lớp được sử dụng như kiểu dữ liệu người dùng định nghĩa +Thuộc tính (attribute) là dữ liệu trình bày các đặc điểm về một đối tượng. Thuộc tính bao gồm: Hằng, biến Tham số nội tại Thuộc tính được xác định kiểu, gồm: Kiểu cơ bản Kiểu do người dùng định nghĩa +Phương thức (method) có liên quan tới những thứ mà đối tượng có thể làm. Một phương thức đáp ứng một chức năng tác động lên dữ liệu của đối tượng (thuộc tính). Phương thức là: 10
  11. Các hàm nội tại của đối tượng Có kiểu trả về Tên gọi khác: hàm thành viên +Thông điệp (message) là một lời yêu cầu một hoạt động. Một thông điệp được truyền khi một đối tượng triệu gọi một hay nhiều phương thức của đối tượng khác để yêu cầu thông tin. Một thông điệp bao gồm: Đối tượng nhận thông điệp Tên phương thức cần thực hiện Các tham số mà phương thức cần Hệ thống yêu cầu đối tượng thực hiện phương thức như sau: Gửi thông báo và tham số cho đối tượng Kiểm tra tính hợp lệ của thông báo Gọi thực hiện hàm tương ứng phương thức +Sự trừu tượng hoá dữ liệu: là biểu thị những đặc tả thiết yếu của đối tượng để phân biệt ranh giới rõ ràng giữa các đối tượng và những tính chất đặc thù của chúng Các loại trừu tượng hoá: Trừu tượng hoá dữ liệu: không quan tâm các chi tiết không quan trọng bên trong Trừu tượng hoá chức năng: không quan tâm làm thế nào để thực hiện công việc Ví dụ: Mô tả hoạt động của một ngăn xếp trong cấu trúc dữ liệu bằng mảng Các dữ liệu cần thiết cho ngăn xếp: - Kích thước của phần tử dữ liệu - Số lượng các dữ liệu - Chỉ số phần tử tiếp theo Các hoạt động với các ngăn xếp - Khởi tạo - Thêm một phần tử vào ngăn xếp - Đếm số phần tử của ngăn xếp - Xóa phần tử trong ngăn xếp Lập trình cấu trúc Khai báo cấu trúc thích hợp Khai báo các thao tác thích hợp 11
  12. typedef struct CStashTag void initialize(CStash* s, int size); { void cleanup(CStash* s); int m_nSize; int add(CStash* s, const void* element); int m_nQuantity; void* fetch(CStash* s,int index); int m_nNext; int count(CStash* s); } CStash; void inflate(CStash* s, int increase); Lập trình hướng đối tượng struct CStash { //dữ liệu int m_nSize; Int m_nQuantity; int m_nNext; // Hàm void initialize(int size); void cleanup(); int add(const void* element); void *fetch(int index); int count(); void inflate(int increase); }; +Bao gói thông tin: Là cơ chế ràng buộc dữ liệu và các thao tác trên dữ liệu thành thể thống nhất. Bao gói gồm: Bao gói: người dùng giao tiếp với hệ thống qua giao diện Che dấu: ngăn chặn các thao tác không được phép từ bên ngoài Có ưu điểm: Quản lý sự thay đổi Bảo vệ dữ liệu +Kế thừa (inheritance): Khả năng cho phép xây dựng lớp mới được thừa hưởng các thuộc tính và phương thức của lớp đã có Đặc điểm: Lớp nhận được có thể bổ sung các thành phần Hoặc định nghĩa lại các thuộc tính của lớp cha Có các loại kế thừa: Đơn kế thừa và đa kế thừa +Tính đa hình(polymorphime): Khả năng đưa một phương thức có cùng tên trong các lớp con 12
  13. Đa hình thực hiện bởi: Định nghĩa lại Nạp chồng Cơ chế dựa trên sự kết gán: Kết gán sớm Kết gán muộn - Ưu điểm của việc thiết kế hướng đối tượng là: +Tập trung xác định các lớp để mô tả các thực thể được gọi là các đối tượng của bài toán và sau đó xây dựng các dữ liệu cùng các hàm xung quanh các đối tượng đó. Các thực thể tác động, trao đổi thông tin với nhau qua cơ chế thông báo (message). Như vậy việc thiết kế chương trình xuất phát từ các nội dung, các vấn đề của bài toán. +Thông qua nguyên lý kế thừa, có thể loại bỏ được những đoạn chương trình lặp lại trong quá trình mô tả các lớp và có thể mở rộng khả năng sử dụng của các lớp đã xây dựng mà không cần phải viết lại. +Chương trình được xây dựng từ những đơn thể (đối tượng) trao đổi với nhau nên việc thiết kế và lập trình sẽ được thực hiện theo quy trình nhất định chứ không phải dựa vào kinh nghiệm và kỹ thuật như trước nữa. Điều này đảm bảo rút ngắn được thời gian xây dựng hệ thống và tăng năng suất lao động. +Nguyên lý đóng gói hay che giấu thông tin giúp người lập trình tạo ra được những chương trình an toàn không bị thay đổi bởi những đoạn chương trình khác. +Có thể xây dựng được ánh xạ các đối tượng của bài toán vào đối tượng chương trình. +Cách tiếp cận thiết kế đặt trọng tâm vào dữ liệu, giúp chúng ta xây dựng được mô hình chi tiết và dễ dàng cài đặt hơn. +Các hệ thống hướng đối tượng dễ mở rộng, nâng cấp thành những hệ lớn hơn. +Kỹ thuật truyền thông báo trong việc trao đổi thông tin giữa các đối tượng làm cho việc mô tả giao diện với các hệ thống bên ngoài trở nên đơn giản hơn. +Có thể quản lý được độ phức tạp của những sản phẩm phần mềm. - Các ngôn ngữ thuần tuý hướng đối tượng (như Smalltalk, Java, VB.net, Visual C++, C# ) chỉ hỗ trợ các khái niệm về lớp, không có các khái niệm hàm. - C++ là ngôn ngữ hỗ trợ hướng đối tượng, nó cho phép sử dụng cả các công cụ của lớp và hàm. Ví dụ: xét bài toán tìm độ dài lớn nhất đi qua 2 điểm. Trong bài toán này ta gặp một thực thể là dãy điểm. Các thành phần dữ liệu của lớp dãy điểm gồm: - Biến nguyên n là số điểm của dãy - Con trỏ x kiểu thực trỏ đến vùng nhớ chứa dãy hoành độ - Con trỏ y kiểu thực trỏ đến vùng nhớ chứa dãy tung độ Các phương thức cần đưa vào theo yêu cầu bài toán gồm: 13
  14. - Nhập toạ độ một điểm - Tính độ dài đoạn thẳng đi qua 2 điểm Dưới đây là chương trình viết theo thiết kế hướng đối tượng. Để thực hiện chương trình này đặt tên tệp có đuôi CPP. Một điều mới trong C++ là các khai báo biến, mảng có thể viết bất kỳ chỗ nào trong chương trình (nhưng lưu ý phải trước khi sử dụng biến, mảng). #include #include #include #include class daydiem { public: int n; float *x,*y; float do_dai(int i, int j) { return sqrt(pow(x[i]-x[j],2)+pow(y[i]-y[j],2)); } void nhapsl(void); }; void daydiem::nhapsl(void) { int i; printf("\nSo diem N= "); scanf("%d",&n); x=(float*)malloc((n+1)*sizeof(float)); y=(float*)malloc((n+1)*sizeof(float)); for (i=1;i<=n;++i) { printf("\nNhap toa do x, y cua diem thu %d : ",i); scanf("%f%f",&x[i],&y[i]); } } void main() { daydiem p; p.nhapsl(); int n,i,j,imax,jmax; float d,dmax; n=p.n; 14
  15. dmax=p.do_dai(1,2); imax=1;jmax=2; for (i=1;i dmax) { dmax=d; imax=i; jmax=j; } } printf("\nDoan thang lon nhat co do dai bang: %0.2f",dmax); printf("\n Di qua 2 diem co chi so la %d va %d",imax,jmax); getch(); } 1.2 Đặc điểm của lập trình hướng đối tượng. - Tập trung vào dữ liệu thay cho các hàm - Chương trình được chia thành các đối tượng. - Các cấu trúc dữ liệu được thiết kế sao cho đặc tả được đối tượng. - Các hàm thao tác trên các vùng dữ liệu của đối tượng được gắn với cấu trúc dữ liệu đó. - Dữ liệu được đóng gói lại, được che giấu và không cho phép các hàm ngoại lai truy nhập tự do. - Các đối tượng tác động và trao đổi thông tin với nhau qua các hàm - Có thể dễ dàng bổ sung dữ liệu và các hầm mới vào đối tượng nào đó khi cần thiết - Chương trình được thiết kế theo cách tiếp cận từ dưới lên (bottom-up). 1.3 Xây dựng lớp đối tượng. Các bước chính để xây dựng lớp đối tượng - Xác định các dạng đối tượng (lớp) của bài toán. - Tìm kiếm các đặc tính chung. - Xác định lớp cơ sở dựa trên cơ sở các đặc tính chung. - Xây dựng lớp dẫn xuất từ lớp cơ sở. 15
  16. BÀI TẬP CHƯƠNG 1 1. Viết chương trình in ra màn hình 2 chữ cái đầu của tên bạn. Ví dụ tên Thủy In ra 2 chữ TH: TTTTTTTTT H H T H H T HHHHHHHHH T H H T H H 2. Viết chương trình nhập hai số nguyên và hiển thị tổng, hiệu, tích và thương của số nguyên vừa nhập. 3. Viết chương trình nhập tháng và năm từ bàn phím (chú ý có kiểm tra điều kiện cho tháng và năm) . Sau đó đưa ra số ngày tương ứng của tháng thuộc năm đã nhập. 4. Viết chương trình nhập chiều cao h từ bàn phím, sau đó hiển thị các tam giác hình sao có chiều cao h như dưới đây (trên DOS). Chú ý có kiểm tra điều kiện của h: 2<=h<=24. Nếu h nằm ngoài đoạn trên, yêu cầu người dùng nhập lại. * * h 5. Viết chương trình nhập n số thực từ bàn phím. Sau đó hỏi người sử dụng muốn sắp xếp theo chiều tăng dần hay giảm dần rồi đưa ra danh sách số thực của mảng đã sắp xếp theo yêu cầu của người sử dụng. 16
  17. CHƯƠNG 2 CÁC THÀNH PHẦN CỦA LỚP Mã chương: MH13_CH02 Giới thiệu: Bài này giúp học sinh tìm hiểu về các thành phần của lớp. Mục tiêu: - Trình bày được các thành phần có trong một lớp; - Cài đặt được một lớp đối tượng trên ngôn ngữ trình hướng đối tượng C++; - Cài đặt được các hàm khởi tạo và hàm hủy bỏ; - Khai báo được hàm friend ; - Thực hiện các thao tác an toàn với máy tính. Nội dung: Chương 2 CÁC THÀNH PHẦN CỦA LỚP 2.1 Khai báo một lớp cơ sở. Một lớp cơ sở được khai báo như sau: class { private: public: }; Ví dụ: /*point.cpp*/ #include #include class point { /*khai báo các thành phần dữ liệu riêng*/ private: int x,y; /*khai báo các hàm thành phần công cộng*/ public: void init(int ox, int oy); 17
  18. void move(int dx, int dy); void display(); }; /*định nghĩa các hàm thành phần bên ngoài khai báo lớp*/ void point::init(int ox, int oy) { cout :: ( ) { } 2.2 Hàm CONSTRUCTOR (Hàm tạo). 2.2.1 Công dụng của hàm Constructor. - Hàm tạo là một hàm thành phần đặc biệt không thể thiếu được trong một lớp. Nó được gọi tự động mỗi khi có một đối tượng được khai báo. Chức năng của hàm tạo là khởi tạo các giá trị thành phần dữ liệu của đối tượng, xin cấp phát bộ nhớ cho các thành phần dữ liệu động. 18
  19. - Hàm tạo tạo định nghĩa và khởi tạo các đối tượng của một lớp. - Một lớp có thể có nhiều hàm tạo. Ví dụ 1: class Point { int a, b; public: Point (int x,int y) {a = x; b = y;} // constructor void OffsetPt (int,int); }; Ví dụ 2: /*point.cpp*/ #include #include class point { /*khai báo các thành phần dữ liệu riêng*/ int x; int y; /*khai báo các hàm thành phần chung*/ public: point(int ox,int oy) {x=ox;y=oy;} /*hàm tạo*/ void move(int dx, int dy); void display(); }; /*Định nghĩa các hàm thành phần bên ngoài khai báo lớp*/ void point::move(int dx, int dy) { x += dx; y += dy; } void point::display() { cout<<"Toa do: "<<x<<" "<<y<<"\n"; } void main() { clrscr(); point a(5,2); /*Sử dụng hàm tạo*/ a.display(); a.move(-2,4); a.display(); point b.init(1,-1);b.display(); clrscr(); } 19
  20. Kết quả thực hiện chương trình như sau: Toa do : 5 2 Toa do : 3 6 Toa do : 1 -1 2.2.2 Quy tắc viết hàm Constructor. - Hàm tạo có cùng tên với tên của lớp. - Hàm tạo phải có thuộc tính public. - Hàm tạo không có giá trị trả về và không cần khai báo void. - Có thể có nhiều hàm tạo trong cùng lớp (chồng các hàm tạo). - Khi một lớp có nhiều hàm tạo, việc tạo các đối tượng phải kèm theo các tham số phù hợp với một trong các hàm tạo đã khai báo. Ví dụ 3 : /*Định nghĩa lại lớp point*/ class point { int x,y; public: point() {x=0;y=0;} point(int ox, int oy) {x=ox;y=oy;} /*hàm tạo có hai tham số*/ void move(int,int); void display(); }; point a(1); /* Lỗi vì tham số không phù hợp với hàm tạo */ point b; /*Đúng, tham số phù hợp với hàm tạo không tham số*/ point c(2,3); /*Đúng, tham số phù hợp với hàm thiếtlập thứ hai, có hai tham số*/ - Định nghĩa hàm tạo không tham số Ví dụ 4: /*point4.cpp*/ #include #include class point { /*khai báo các thành phần dữ liệu*/ int x; int y; public: /*khai báo các hàm thành phần*/ point(int ox,int oy) {x=ox;y=oy;} /*định nghĩa thêm hàm tạo không tham số*/ point() {x = 0; y = 0;} void move(int dx,int dy) ; 20
  21. void display(); }; /*phân biệt các thành phần hàm với các hàm thông thường nhờ tên lớp và toán tử ::*/ void point::move(int dx,int dy) { x+=dx; y+=dy; } void point::display() { cout<<“Toa do : “<<x<<" "<<y<<"\n"; } void main() { clrscr(); point a(5,2); //OK a.display(); a.move(-2,4); a.display(); point b[10]; /*Hết lỗi vì hàm tạo không tham số được gọi để tạo các đối tượng thành phần của b*/ getch(); } - Hàm tạo có thể được khai báo với các tham số có giá trị ngầm định Ví dụ 5: /*Định nghĩa lại lớp point*/ class point { int x,y; public: point(int ox, int oy = 0) {x=ox;y=oy;} /*hàm tạo có hai tham số*/ void move(int,int); void display(); }; point a; /*Lỗi: không có hàm tạo ngầm định hoặc hàm tạo với các tham số có giá trị ngầm định*/ point b(1); //Đối số thứ hai nhận giá trị 0 point c(2,3); //Đúng Nhận xét: + Trong ví dụ 5, chỉ thị point b(1); có thể được thay thế bằng cách viết khác như sau: point b=1; 21
  22. + Cách viết thứ hai hàm ý rằng đã có chuyển kiểu ngầm định từ số nguyên 1 thành đối tượng kiểu point - Như là các hàm toàn cục, một hàm thành viên của một lớp có thể có các đối số mặc định, đối số có thể là một biểu thức gồm nhiều đối tượng được định nghĩa bên trong phạm vi mà lớp xuất hiện. Ví dụ 6: một hàm tạo dựng cho lớp Point có thể sử dụng các đối số mặc định để cung cấp nhiều cách thức khác nhau cho việc định nghĩa một đối tượng Point : class Point { int xVal, yVal; public: Point (int x = 0, int y = 0); // }; với hàm tạo dựng đã có này thì các định nghĩa sau là hoàn toàn hợp lệ: Point p1; // như là: p1(0, 0) Point p2(10); // như là: p2(10, 0) Point p3(10, 20); - Việc sử dụng không đúng các đối số mặc định có thể dẫn đến sự tối nghĩa không mong muốn. Ví dụ 7: với lớp đã cho class Point { int xVal, yVal; public: Point (int x = 0, int y = 0); Point (float x = 0, float y = 0); // tọa độ cực // }; thì định nghĩa sau được xem như là tối nghĩa bởi vì nó so khớp với cả hai hàm tạo dựng: Point p; // tối nghĩa hay không rõ ràng 2.2.3 Dùng hàm tạo trong khai báo. - Khi đã xây dựng các hàm tạo, ta có thể dùng chúng trong khai báo để tạo ra một đối tượng đồng thời khởi gán cho các thuộc tính của đối tượng được tạo. Dựa vào các tham số trong khai báo mà trình biên dịch sẽ biết cần gọi đến hàm tạo nào. - Khi khai báo một biến đối tượng có thể sử dụng các tham số để khởi gán cho các thuộc tính của biến đối tượng. - Khi khai báo mảng đối tượng không cho phép dùng các tham số để khởi gán. - Câu lệnh khai báo một biến đối tượng sẽ gọi tới hàm tạo 1 lần - Câu lệnh khai báo một mảng n đối tượng sẽ gọi tới hàm tạo n lần. Ví dụ: 22
  23. point d; // Gọi tới hàm tạo không đối. // Kết quả d.x=0, d.y=0 point u(200,100,4); // Gọi tới hàm tạo có đối. // Kết quả u.x=200, u.y=100 point v(300,250); // Gọi tới hàm tạo có đối. // Kết quả v.x=300, v.y=250 point p[10] ; // Gọi tới hàm tạo không đối 10 lần Chú ý: Với các hàm có đối kiểu lớp, thì đối chỉ xem là các tham số hình thức, vì vậy khai báo đối (trong dòng đầu của hàm) sẽ không tạo ra đối tượng mới và do đó không gọi tới các hàm tạo. - Khi đối tượng là nội dung một biến có kiểu lớp, ta có thể gán cho nó các “bí danh”; nghĩa là có thể khai báo các tham chiếu đến chúng. Một tham chiếu đối tượng chỉ có ý nghĩa khi tham chiếu tới một đối tượng nào đó đã được khai báo trước đó. Ví dụ 8: /*point8.cpp*/ #include #include class point { /*khai báo các thành phần dữ liệu*/ int x; int y; public: /*khai báo các hàm thành phần */ point(int ox = 1,int oy =0) {x=ox;y=oy;} void move(int dx,int dy) ; void display(); }; void point::move(int dx,int dy){ x+=dx; y+=dy; } void point::display() { cout<<“Toa do : “<<x<<" "<<y<<"\n"; } void main() { clrscr(); point a(2,5); //OK 23
  24. point &ra=a; a.display(); ra.display(); ra.move(2,3); a.display(); point b[10];/*Trong trường hợp này các đối tượng thành phần của b được tạo ra nhờ hàm thiết lập được gọi với hai tham số có giá trị ngầm định là 1 và 0.*/ getch(); } Kết quả in ra : Toa do : 2 5 Toa do : 2 5 Toa do : 4 8 2.2.4 Dùng hàm Constructor trong cấp phát bộ nhớ. - Con trỏ đối tượng được khai báo như sau: *ptr; - Con trỏ đối tượng có thể nhận giá trị là địa chỉ của các đối tượng có cùng kiểu lớp: ptr = &a; - Khi đó có thể gọi các hàm thành phần của lớp point thông qua con trỏ như sau: ptr->display(); ptr->move(-2,3); - Khi dùng toán tử new cấp phát một đối tượng động, hàm tạo cũng được gọi, do vậy cần cung cấp danh sách các tham số Cú pháp: =new ( ); Ví dụ: giả sử trong lớp point có một hàm tạo hai tham số, khi đó câu lệnh sau: ptr = new point(3,2); sẽ xin cấp phát một đối tượng động với hai thành phần x và y nhận giá trị tương ứng là 2 và 3. Kết quả này được minh chứng qua lời gọi hàm: ptr->display(); Toa do : 3 2 Ví dụ 9: Chương trình sau đây minh hoạ cách xây dựng hàm tạo và cách sử dùng hàm tạo trong khai báo, trong cấp phát bộ nhớ và trong việc biểu diễn các hằng đối tượng. #include #include #include 24
  25. class DIEM_DH { private: int x,y,m; public: // Hàm bạn dùng để in đối tượng DIEM_DH friend void in(DIEM_DH d) { cout <<"\n " << d.x << " "<< d.y<<" " << d.m ; } // Phương thức dùng để in đối tượng DIEM_DH void in() { cout <<"\n " << x << " "<< y<<" " << m ; ; } //Hàm tạo không đối DIEM_DH() { x=y=0; m=1//Hàm tạo có đối, đối m1 có giá trị mặc định là 15 (mầu trắng) DIEM_DH(int x1,int y1,int m1=15); }; //Xây dựng hàm tạo DIEM_DH::DIEM_DH(int x1,int y1,int m1) { x=x1; y=y1; m=m1; } void main() { DIEM_DH d1; // Gọi tới hàm tạo không đối DIEM_DH d2(200,200,10); // Gọi tới hàm tạo có đối DIEM_DH *d; d= new DIEM_DH(300,300); // Gọi tới hàm tạo có đối clrscr(); in(d1); //Gọi hàm bạn in() d2.in();//Gọi phương thức in() in(*d); //Gọi hàm bạn in() DIEM_DH(2,2,2).in();//Gọi phương thức in() DIEM_DH t[3]; // 3 lần gọi hàm tạo không đối DIEM_DH *q; // Gọi hàm tạo không 25
  26. đối int n; cout > n; q=new DIEM_DH[n+1]; // (n+1) lần gọi hàm tạo không đối for (int i=0;i #include int line=1; class test { public: int num; test(int); ~test(); }; test::test(int n) { num = n; cout<<line++<<”.”; cout<<"++ Goi ham thiet lap voi num ="<<num<<"\n"; } test::~test() { 26
  27. cout<<line++<<”.”; cout<<" Goi ham huy bo voi num ="<<num<<"\n"; } void main() { clrscr(); void fct(int); test a(1); for(int i=1; i<= 2; i++) fct(i); } void fct(int p) { test x(2*p); } . Trong chương trình chính, dòng thứ nhất tạo ra đối tượng a có kiểu lớp test, do đó có dòng thông báo số 1. . Vòng lặp for hai lần gọi tới hàm fct(). Mỗi lời gọi hàm fct() kéo theo việc khai báo một đối tượng cục bộ x trong hàm. Vì lỡ đối tượng cục bộ bên trong hàm fct() nên x bị xoá khỏi vùng bộ nhớ ngăn xếp (dùng để cấp phát cho các biến cục bộ khi gọi hàm) khi kết thúc thực hiện hàm. Do đó, mỗi lời gọi tới fct() sinh ra một cặp dòng thông báo, tương ứng với lời gọi hàm tạo, hàm huỷ bỏ (các dòng thông báo 2, 3, 4, 5 tương ứng). . Cuối cùng, khi hàm main() kết thúc thực hiện, đối tượng a được giải phóng, hàm huỷ bỏ đối với a sẽ cho ra dòng thông báo thứ 6. Kết quả thực hiện chương trình như sau: 1. ++ Goi ham thiet lap voi num = 1 2. ++ Goi ham thiet lap voi num = 2 3. Goi ham huy bo voi num = 2 4. ++ Goi ham thiet lap num = 4 5. Goi ham huy bo voi num = 4 6. Goi ham huy bo voi num = 1 2.3.2 Hàm huỷ mặc định. Nếu trong lớp không định nghĩa hàm huỷ, thì một hàm huỷ mặc định không làm gì cả được phát sinh. Đối với nhiều lớp thì hàm huỷ mặc định là đủ, và không cần đưa vào một hàm huỷ mới. 2.3.3 Quy tắc viết hàm huỷ. - Tên của hàm huỷ bỏ bắt đầu bằng dấu ~ theo sau là tên của lớp tương ứng. Ví dụ lớp test thì sẽ hàm huỷ bỏ tên là ~test. - Hàm huỷ bỏ phải có thuộc tính public - Nói chung hàm huỷ bỏ không có tham số, mỗi lớp chỉ có một hàm huỷ bỏ (Trong khi đó có thể có nhiều các hàm tạo). 27
  28. - Khi không định nghĩa hàm huỷ bỏ, chương trình dịch tự động sản sinh một hàm như vậy (hàm huỷ bỏ ngầm định), hàm này không làm gì ngòai việc “lấp chỗ trống”. Đối với các lớp không có khai báo các thành phần bộ nhớ động, có thể dùng hàm huỷ bỏ ngầm định. Trái lại, phải khai báo hàm huỷ bỏ tường minh để đảm bảo quản lý tốt việc giải phóng bộ nhớ động do các đối tượng chiếm giữ chiếm giữ khi chúng hết thời gian làm việc. - Giống như hàm tạo, hàm huỷ bỏ không có giá trị trả về. Ví dụ 1: class Set { public: Set (const int size); ~Set (void) {delete elems;} // destructor // private: int *elems; // cac phan tu tap hop int maxCard; // so phan tu toi da int card; // so phan tu cua tap hop }; Ví dụ 2: xây dựng hàm huỷ cho lớp DT (đa thức) class DT { private: int n; // Bac da thuc double *a; // Tro toi vung nho chua cac he so da thuc // a0, a1, public: ~DT() { this->n=0; delete this->a; } } ; 2.4 Hàm INLINE (Hàm nội tuyến). 2.4.1 Ưu nhược điểm của hàm Inline. Việc tổ chức chương trình thành các hàm có 2 ưu điểm rõ rệt : Thứ nhất là chia chương trình thành các đơn vị độc lập, làm cho chương trình được tổ chức một cách khoa học dễ kiểm soát dễ phát hiện lỗi, dễ phát triển, mở rộng. Thứ hai là giảm được kích thước chương trình, vì mỗi đoạn chương trình t hực 28
  29. hiện nhiệm vụ của hàm được thay bằng một lời gọi hàm. Tuy nhiên hàm cũng có nhược điểm là làm chậm tốc độ chương trình do phải thực hiện một số thao tác có tính thủ tục mỗi khi gọi hàm như: Cấp phát vùng nhớ cho các đối và biến cục bộ, truyền dữ liệu của các tham số cho các đối, giải phóng vùng nhớ trước khi thoát khỏi hàm. Các hàm trực tuyến trong C++ cho khả năng khắc phục được nhược điểm nói trên. 2.4.2 Quy tắc viết hàm Inline. C++ cho khả năng khắc phục nhược điểm này bằng cách dùng hàm nội tuyến. Một hàm thành viên được định nghĩa là nội tuyến bằng cách chèn từ khóa inline vào trước định nghĩa của hàm. Chú ý: Trong mọi trường hợp, từ khoá inline phải viết trước các lời gọi hàm thì trình biên dịch mới biết cần xử lý hàm theo kiểu inline. inline :: ( ){ ; } Có thể định nghĩa các hàm thành viên là nội tuyến bằng cách chèn định nghĩa của các hàm này vào bên trong lớp. Ví dụ 1: // Lop PS // Inline #include #include class PS { private: int t,m ; public: PS() { t=0;m=1; } PS(int t1, int m1); void nhap(); void in(); PS operator*=(PS p2) { t*=p2.t; m*=p2.m; 29
  30. return *this; } }; inline PS::PS(int t1, int m1) { t=t1; m=m1; } inline void PS::nhap() { cout > t >> m; } inline void PS::in() { cout << "\nPS = " << t << "/" << m ; } void main() { PS q,p,s(3,5); cout << "\n Nhap PS p"; p.nhap(); s.in(); p.in(); q = p*=s; p.in(); q.in(); getch(); } Chú ý: - Thân hàm được chèn vào trong lớp nên không cần dấu chấm phẩy sau khai báo hàm. - Các tham số của hàm phải được đặt tên. +Chương trình dịch các hàm inline như tương tự như các macro, nghĩa là nó sẽ thay đổi lời gọi hàm bằng một đoạn chương trình thực hiện nhiệm vụ hàm. Cách làm này sẽ tăng tốc độ chương trình do không phải thực hiện các thao tác có tính thủ tục khi gọi hàm nhưng lại làm tăng khối lượng bộ nhớ chương trình (nhất là đối với các hàm nội tuyến có nhiều câu lệnh). Vì vậy chỉ nên dùng hàm inline đối với các hàm có nội dung đơn giản không chứa các câu lệnh phức tạp (như chu trình, goto, switch, đệ quy) . +Không phải khi gặp từ khoá inline là chương trình dịch nhất thiết phải xử lý hàm theo kiểu nội tuyến. Từ khoá inline chỉ là một từ khoá gợi ý cho chương 30
  31. trình dịch chứ không phải là một mệnh lệnh bắt buộc. Ví dụ 2: Chương trình sau sử dụng hàm inline tính chu vi và diện tích của hình chữ nhật: Phương án 1: Không khai báo nguyên mẫu. Khi đó hàm dtcvhcn phải đặt trên hàm main. #include #include inline void dtcvhcn(int a, int b, int &dt, int &cv) { dt=a*b; cv=2*(a+b); } void main() { int a[20],b[20],cv[20],dt[20],n; cout > n; for (int i=1;i > a[i] >> b[i] ; dtcvhcn(a[i],b[i],dt[i],cv[i]); } clrscr(); for (i=1;i<=n;++i) { cout << "\n Hinh chu nhat thu " << i << " : "; cout << "\nDo dai 2 canh= " << a[i] << " va " << b[i] ; cout << "\nDien tich= " << dt[i] ; cout << "\nChu vi= " << cv[i] ; } getch(); } 31
  32. Phương án 2: Sử dụng khai báo nguyên mẫu. Khi đó từ khoá inline đặt trước nguyên mẫu. #include #include inline void dtcvhcn(int a, int b, int &dt, int &cv) ; void main() { int a[20],b[20],cv[20],dt[20],n; cout > n; for (int i=1;i > a[i] >> b[i] ; dtcvhcn(a[i],b[i],dt[i],cv[i]); } clrscr(); for (i=1;i<=n;++i) { cout << "\n Hinh chu nhat thu " << i << " : "; cout << "\nDo dai 2 canh= " << a[i] << " va " << b[i] ; cout << "\nDien tich= " << dt[i] ; cout << "\nChu vi= " << cv[i] ; } getch(); } void dtcvhcn(int a, int b, int &dt, int &cv) { dt=a*b; cv=2*(a+b); } Chú ý: Không được đặt inline trước định nghĩa hàm. Trong chương trình trên đây nếu đặt inline trước định nghĩa hàm thì hậu quả như sau: Chương trình vẫn dịch thông, nhưng khi chạy thì chương trình bị quẩn, không thoát được. 32
  33. 2.5 Thành phần tĩnh của lớp. 2.5.1 Thành phần dữ liệu tĩnh. Thành phần tĩnh dữ liệu được khai báo bằng từ khoá static gọi là tĩnh, được cấp phát một vùng nhớ cố định và tồn tại ngay cả khi lớp chưa có một đối tượng nào cả. Thành phần dữ liệu tĩnh là chung cho cả lớp, không phải là riêng của mỗi đối tượng nào Ví dụ: class A { private: static int ts ; // Thành phần tĩnh int x; } ; xét 2 đối tượng: A u,v ; // Khai báo 2 đối tượng thì giữa các thành phần x và ts có sự khác nhau như sau: u.x và v.x có 2 vùng nhớ khác nhau u.ts và v.ts chỉ là một, chúng cùng biểu thị một vùng nhớ thành phần ts tồn tại ngay khi u và v chưa khai báo - Để biểu thị thành phần tĩnh, ta có thể dùng tên lớp ví dụ: Đối với ts thì 3 cách viết sau là tương đương: A::ts u.ts v.ts - Khai báo và khởi gán giá trị cho thành phần tĩnh: thành phần tĩnh sẽ được cấp phát bộ nhớ và khởi gán giá trị ban đầu bằng một câu lệnh khai báo đặt sau định nghĩa lớp (bên ngoài các hàm, kể cả hàm main), theo các mẫu: int A::ts ; // Khởi gán cho ts giá trị 0 int A::ts = 1234; // Khởi gán cho ts giá trị 1234 Chú ý: Khi chưa khai báo thì thành phần tĩnh chưa tồn tại. Ví dụ 1: #include #include class HDBH { private: char *tenhang; double tienban; static int tshd; static double tstienban; 33
  34. public: static void in() { cout #include class HDBH { private: int shd; char *tenhang; double tienban; static int tshd; static double tstienban; public: static void in() { cout <<”\n” <<tshd; cout <<”\n” <<tstienban; } }; int HDBH::tshd=5 double HDBH::tstienban=20000.0; void main() { HDBH::in(); getch();} 34
  35. 2.5.2 Phương thức tĩnh. Phương thức tĩnh được viết theo một trong hai cách: - Dùng từ khoá static đặt trước định nghĩa phương thức viết bên trong định nghĩa lớp. - Nếu phương thức xây dựng bên ngoài định nghĩa lớp, thì dùng từ khoá static đặt trước khai báo phương thức bên trong định nghĩa lớp. Không cho phép dùng từ khoá static đặt trước định nghĩa phương thức viết bên ngoài định nghĩa lớp. Các đặc tính của phương thức tĩnh: - Phương thức tĩnh là chung cho toàn bộ lớp và không lệ thuộc vào một đối tượng cụ thể, nó tồn tại ngay khi lớp chưa có đối tượng nào. - Lời gọi phương thức tĩnh như sau: Tên lớp :: Tên phương thức tĩnh(các tham số thực sự) - Phương thức tĩnh là độc lập với các đối tượng, nên không thể dùng hàm thành phần tĩnh để xử lý dữ liệu của các đối tượng trong lời gọi phương thức tĩnh. Không cho phép truy nhập các thuộc tính (trừ thuộc tính tĩnh) trong thân hàm thành phần tĩnh. Ví dụ 2: class HDBH { private: int shd; char *tenhang; double tienban; static int tshd; static double tstienban; public: static void in() { cout #include class A { int m; 35
  36. static int n; //n la bien tinh public: void set_m(void) { m= ++n;} void show_m(void) { cout << "\n Doi tuong thu:" << m << endl; } static void show_n(void) { cout << " m = " << n << endl; } }; int A::n=1; //khoi gan gia tri ban dau 1 cho bien tinh n void main() { clrscr(); A t1, t2; t1.set_m(); t2.set_m(); A::show_n(); A t3; t3.set_m(); A::show_n(); t1.show_m(); t2.show_m(); t3.show_m(); getch(); } Kết quả chương trình trên là: m = 3 m = 4 Doi tuong thu : 2 Doi tuong thu : 3 Doi tuong thu : 4 2.6 Hàm FRIEND (hàm bạn). 2.6.1 Quy tắc viết hàm bạn. Để một hàm trở thành bạn của một lớp, có 2 cách viết: Cách 1: Dùng từ khóa friend để khai báo hàm trong lớp và xây dựng hàm bên ngoài như các hàm thông thường (không dùng từ khóa friend). Mẫu viết như sau: class A 36
  37. { private : // Khai báo các thuộc tính public : // Khai báo các hàm bạn của lớp A friend void f1 ( ) ; friend double f2 ( ) ; } ; // Xây dựng các hàm f1,f2,f3 void f1 ( ) { } double f2 ( ) { } Cách 2: Dùng từ khóa friend để xây dựng hàm trong định nghĩa lớp . Mẫu viết như sau : class A { private : // Khai báo các thuộc tính public : // Khai báo các hàm bạn của lớp A friend void f1 ( ) { } friend double f2 ( ) { } } ; Có nhiều kiểu bạn bè: - Hàm tự do là bạn của một lớp. - Hàm thành phần của một lớp là bạn của một lớp khác. 37
  38. - Hàm bạn của nhiều lớp. - Tất cả các hàm thành phần của một lớp là bạn của một lớp khác. a. Hàm tự do là bạn của một lớp. Ví dụ: class point { int x,y; public: int coincide (point p); }; Có thể định nghĩa hàm coincide như một hàm tự do bạn của lớp point như sau: friend int coincide(point , point); Ví dụ 1: /*friend1.cpp*/ #include class point { int x, y; public: point(int abs =0, int ord =0) { x = abs;y = ord; } friend int coincide (point,point); }; int coincide (point p, point q) { if ((p.x == q.x) && (p.y == q.y)) return 1; else return 0; } void main() { point a(1,0),b(1),c; if (coincide (a,b)) cout <<"a trung voi b\n"; else cout<<"a va b khac nhau\n"; if (coincide(a,c)) cout<<"a trung voi c\n"; else cout<<"a va c khac nhau\n"; } . Trong hàm bạn, không còn tham số ngầm định this như trong hàm thành phần. . Giống như các hàm thành phần khác danh sách “tham số ” của hàm bạn gắn với định nghĩa chồng các toán tử. Hàm bạn của một lớp có thể có một hay nhiều tham số, hoặc có giá trị trả về thuộc kiểu lớp đó. Tuy rằng điều này không bắt buộc. 38
  39. . Có thể có các hàm truy xuất đến các thành phần riêng của các đối tượng cục bộ trong hàm. Khi hàm bạn của một lớp trả giá trị thuộc lớp này, thường đó là giá trị dữ liệu của đối tượng cục bộ bên trong hàm, việc truyền tham số phải thực hiện bằng tham trị, bởi truyền bằng tham chiếu (hoặc bằng địa chỉ) hàm gọi sẽ nhận địa chỉ của một vùng nhớ bị giải phóng khi hàm kết thúc. b. Hàm thành phần của lớp là bạn của một lớp khác. Đây là một trường hợp đặc biệt của hàm tự do là bạn của một lớp, chỉ khác ở cách mô tả hàm. Sử dụng tên đầy đủ của hàm thành phần bao gồm tên lớp, toán tử phạm vi và tên hàm thành phần bạn bè. Ví dụ: giả sử có hai lớp A và B, trong đó B có một hàm thành phần f được khai báo như sau: int f(char , A); Nếu f có nhu cầu truy xuất vào các thành phần riêng của A thì f cần phải được khai báo là bạn của A ở trong lớp A bằng câu lệnh: friend int B::f(char , A); Sơ đồ để khai báo và định nghĩa như sau: class A; class B { int f(char, A); }; class A { friend int B::f(char, A); }; int B::f(char ,A ) { } Chú ý: . Để biên dịch được các khai báo của lớp A có chứa khai báo bạn bè friend int B::f(char, A); 39
  40. chương trình dịch cần phải biết được nội dung của lớp B; nghĩa là khai báo của B (không nhất thiết định nghĩa của các hàm thành phần) phải được biên dịch trước khai báo của A. . Ngược lại, khi biên dịch khai báo: int f(char, A) ; bên trong lớp B, chương trình dịch không nhất thiết phải biết chi tiết nội dung của A, nó chỉ cần biết là một lớp. Để có được ta dùng chỉ thị sau: class A; trước khai báo lớp B. . Việc biên dịch định nghĩa hàm f cần các thông tin đầy đủ về các thành phần của A và B; Như vậy các khai báo của A và B phải có trước định nghĩa đầy đủ của f. c. Hàm bạn của nhiều lớp. Về nguyên tắc, mọi hàm (hàm tự do hay hàm thành phần) đều có thể là bạn của nhiều lớp khác nhau. Ví dụ: một hàm là bạn của hai lớp A và B class A { friend void f(A, B); }; class B { friend void f(A,B); }; void f(A ,B ) { //truy nhập vào các thành phần riêng của hai lớp bất kỳ A và B } Chú ý: . khai báo của A có thể được biên dịch không cần khai báo của B nếu có chỉ thị class B; đứng trước. . Tương tự, khai báo của lớp B cũng được biên dịch mà không cần đến A nếu có chỉ thị class A; đứng trước. . Nếu ta muốn biên dịch cả hai khai báo của A và của B, thì cần phải sử dụng một trong hai chỉ thị đã chỉ ra ở trên. . Còn định nghĩa của hàm f cần phải có đầy đủ cả hai khai báo của A và của B đứng trước Ví dụ: class B; class A { 40
  41. friend void f(A, B); }; class B { friend void f(A,B); }; void f(A ,B ) { //truy nhập vào các thành phần riêng của hai lớp bất kỳ A và B } d. Tất cả các hàm của lớp là bạn của lớp khác. Đây là trường hợp tổng quát trong đó có thể khai báo lớp bạn bè với các hàm. Đưa ra một khai báo tổng thể để nói rằng tất cả các hàm thành phần của lớp B là bạn của lớp A bằng cách đặt trong khai báo lớp A chỉ thị: friend class B; Chú ý: . Trong trường hợp này, để biên dịch khai báo của lớp A, chỉ cần đặt trước nó chỉ thị: class B; . kiểu khai báo lớp bạn cho phép không phải khai báo tiêu đề của các hàm có liên quan. 2.6.2 Tính chất của hàm bạn. - Hàm bạn không phải là hàm thành phần của lớp. - Việc truy nhập tới hàm bạn được thực hiện như hàm thông thường. - 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 đố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. - Một hàm có thể là bạn của nhiều lớp. Lúc đó nó có quyền 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 class C ; // Khai báo trước lớp A // Định nghĩa lớp A class A { // Khai báo f là bạn của A friend void f( ) } ; // Định nghĩa lớp B 41
  42. class B { // Khai báo f là bạn của B friend void f( ) } ; // Định nghĩa lớp C class C { // Khai báo f là bạn của C friend void f( ) } ; // Xây dựng hàm f void f( ) { } Ví dụ 1: #include #include class sophuc {float a,b; public : sophuc() {} sophuc(float x, float y) {a=x; b=y;} friend sophuc tong(sophuc,sophuc); friend void hienthi(sophuc); }; sophuc tong(sophuc c1,sophuc c2) {sophuc c3; c3.a=c1.a + c2.a ; c3.b=c1.b + c2.b ; return (c3); } void hienthi(sophuc c) {cout<<c.a<<" + "<<c.b<<"i"<<endl; } void main() { clrscr(); sophuc d1 (2.1,3.4); sophuc d2 (1.2,2.3) ; 42
  43. sophuc d3 ; d3 = tong(d1,d2); cout #include class LOP1; class LOP2 { int v2; public: void nhap(int a) { v2=a;} void hienthi(void) { cout<<v2<<"\n";} friend void traodoi(LOP1 &, LOP2 &); }; class LOP1 { int v1; public: void nhap(int a) { v1=a;} void hienthi(void) { cout<<v1<<"\n";} friend void traodoi(LOP1 &, LOP2 &); }; void traodoi(LOP1 &x, LOP2 &y) { 43
  44. int t = x.v1; x.v1 = y.v2; y.v2 = t; } void main() { clrscr(); LOP1 ob1; LOP2 ob2; ob1.nhap(150); ob2.nhap(200); cout 44
  45. #include #include class VT; class MT ; class VT { private: int n; double x[20]; // Toa do cua diem public: void nhapsl(); friend void in(const VT &x); friend VT tich(const MT &a,const VT &x) ; } ; class MT { private: int n; double a[20][20]; public: friend VT tich(const MT &a,const VT &x); friend void in(const MT &a); void nhapsl(); } ; void VT::nhapsl() { cout > n ; for (int i=1; i > x[i]; } } void MT::nhapsl() { cout > n ; for (int i=1; i<=n ; ++i) for (int j=1; j<=n; ++j) { 45
  46. cout > a[i][j]; } } VT tich(const MT &a,const VT &x) { VT y; int n=a.n; if (n!=x.n) return x; y.n = n; for (int i=1; i<=n; ++i) { y.x[i]=0; for (int j=1; j<=n; ++j) y.x[i] += a.a[i][j]*x.x[j]; } return y; } void in(const VT &x) { cout << "\n"; for (int i=1; i<=x.n; ++i) cout << x.x[i] << " "; } void in(const MT &a) { for (int i=1; i<=a.n; ++i) { cout << "\n" ; for (int j=1; j<=a.n; ++j) cout << a.a[i][j] << " "; } } void main() { MT a; VT x,y; clrscr(); a.nhapsl(); x.nhapsl(); y=tich(a,x); 46
  47. clrscr(); cout << "\nMa tran A:"; in(a); cout << "\n\nVec to x: " ; in(x); cout << "\n\nVec y = Ax: " ; in(y); getch(); } BÀI TẬP CHƯƠNG 2 1. Chỉ ra các cách khai báo đối tượng có thể cho các lớp đối tượng dưới đây class A { }; class B { B (int, int); public: B (int = 0); }; class C { C ( C& ); public: C (); }; class D { public: D ( D& ); }; 3. Xây dựng lớp mô tả các đối tượng bạn đọc trong một hệ thống thông tin thư viện. Mỗi bạn đọc cần có thông tin về mã và tên của bạn đọc. Trong lớp bạn đọc có thành phần dữ liệu tĩnh là tập các đối tượng bạn đọc có trong thư viện. Viết các hàm thành phần tĩnh để nhập và hiển thị dữ liệu về các đối tượng bạn đọc 4. Xây dựng lớp thời gian Time. - Dữ liệu thành phần bao gồm giờ, phút giây. 47
  48. - Các hàm thành phần bao gồm: hàm tạo, hàm truy cập dữ liệu, hàm normalize() để chuẩn hóa dữ liệu nằm trong khoảng quy định của giờ (0 giờ < 24) , phút (0 phút <60), giây (0 giây <60), hàm advance(int h, int m, int s) để tăng thời gian hiện hành của đối tượng đang tồn tại, hàm reset(int h, int m, int s) để chỉnh lại thời gian hiện hành của một đối tượng đang tồn tại và một hàm print() để hiển thị dữ liệu. 5. Xây dựng lớp Date. - Dữ liệu thành phần bao gồm ngày, tháng, năm. - Các hàm thành phần bao gồm: hàm tạo, hàm truy cập dữ liệu, hàm normalize() để chuẩn hóa dữ liệu nằm trong khoảng quy định của ngày (1 ngày <daysIn(tháng)), tháng (1 tháng < 12), năm (năm 1), hàm daysIn(int) trả về số ngày trong tháng, hàm advance(int y, int m, int d) để tăng ngày hiện lên các năm y, tháng m, ngày d của đối tượng đang tồn tại, hàm reset(int y, int m, int d) để đặt lại ngày cho một đối tượng đang tồn tại và một hàm print() để hiển thị dữ liệu. 6. Xây dựng lớp String. - Mỗi đối tượng của lớp sẽ đại diện một chuỗi ký tự. - Những thành phần dữ liệu là chiều dài chuỗi, và chuỗi ký tự. - Các hàm thành phần bao gồm: hàm tạo, hàm truy cập, hàm hiển thị, hàm character(int i) trả về một ký tự trong chuỗi được chỉ định bằng tham số i. 7. Xây dựng lớp ma trận có tên là Matrix cho các ma trận, các hàm thành phần bao gồm: - Hàm tạo mặc định - Hàm nhập xuất ma trận - Hàm cộng, trừ, nhân hai ma trận. 8. Xây dựng lớp biểu diễn hình chữ nhật có các thuộc tính là độ dài hai cạnh (chiều dài và chiều rộng) và có các phương thức sau: - Nhập dữ liệu cho hai cạnh của hình chữ nhật - Tính chu vi và diện tích của hình chữ nhật - In thông tin của hình chữ nhật ra màn hình (bao gồm độ dài hai cạnh, chu vi và diện tích) 48
  49. CHƯƠNG 3 LỚP Mã chương: MH13_CH03 Giới thiệu: Trong bài này giúp cho học sinh cài đặt lớp đối tượng. Mục tiêu: - Khai báo và sử dụng được lớp, đối tượng; - Sử dụng đối tượng làm tham số cho hàm; - Khai báo và sử dụng được con trỏ đối tượng, mảng đối tượng; - Quản lý được đối tượng thông qua con trỏ this.; - Thực hiện các thao tác an toàn với máy tính. Nội dung: Chương 3 LỚP 3.1 Định nghĩa lớp. 3.1.1 Cú pháp. Lớp được định nghĩa theo mẫu sau: class { private: public: } ; 49
  50. Hoặc có thể khai báo như sau: class { public: } ; - Thuộc tính của lớp được gọi là dữ liệu thành phần và hàm đợc gọi là phương thức hoặc hàm thành viên. Thuộc tính và hàm đợc gọi chung là các thành phần của lớp. - Các thành phần của lớp đợc tổ chức thành hai vùng: vùng sở hữu riêng (private) và vùng dùng chung (public) để quy định phạm vi sử dụng của các thành phần. Nếu không quy định cụ thể (không dùng các từ khóa private và public) thì C++ hiểu đó là private. - Các thành phần private chỉ đợc sử dụng bên trong lớp (trong thân của các hàm thành phần). - Các thành phần public được phép sử dụng ở cả bên trong và bên ngoài lớp. Các hàm không phải là hàm thành phần của lớp thì không đợc phép sử dụng các thành phần này. 3.1.2 Khai báo các thuộc tính của lớp. Các thuộc tính của lớp được thực hiện như khai báo biến. Thuộc tính của lớp không thể có kiểu chính của lớp đó, nhưng có thể là kiểu con trỏ của lớp này. Ví dụ: class A { A x; //Không cho phép, vì x có kiểu lớp A A *p ; // Cho phép, vì p là con trỏ kiểu lớp A } ; Chú ý: - 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 trước (cấu trúc, hợp, lớp, ). 50
  51. 3.1.3 Định nghĩa các hàm thành phần. Các hàm thành phần 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 hàm thành phần đơn giản, có ít dòng lệnh sẽ đợc viết bên trong định nghĩa lớp, còn các hàm thành phần dài thì viết bên ngoài định nghĩa lớp. Các hàm thành phần viết bên trong định nghĩa lớp được viết như hàm thông thường. Khi định nghĩa hàm thành phần ở bên ngoài lớp, ta dùng cú pháp sau đây: Kiểu_trả_về_của_hàm Tên_lớp::Tên_hàm(khai báo các tham số){ [nội dung hàm] ; } + Toán tử :: được gọi là toán tử phân giải miền xác định, được dùng để chỉ ra lớp mà hàm đó thuộc vào. + Trong thân hàm thành phần, có thể sử dụng các thuộc tính của lớp, các hàm thành phần khác và các hàm tự do trong chơng trình. Chú ý : - Các thành phần dữ liệu khai báo là private nhằm bảo đảm nguyên lý che dấu thông tin, bảo vệ an toàn dữ liệu của 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 lớp . - Các hàm thành phần khai báo là public có thể đợc gọi tới từ các hàm thành phần public khác trong chơng trình. - Có thể có nhiều public và private trong lớp cơ sở. - Khi một lớp được định nghĩa, thì tên lớp bao hàm một kiểu dữ liệu mới cho phép sử dụng để khai báo kiểu dữ liệu cho các biến. Ví dụ 1: định nghĩa lớp để mô tả và xử lý các điểm trên màn hình đồ hoạ. Lớp được đăt tên là DIEM. + Các thuộc tính của lớp gồm: int x ; // hoành độ (cột) int y ; // tung độ (hàng) int m ; // mầu + Các phương thức: Nhập dữ liệu một điểm Hiển thị một điểm ẩn một điểm Lớp điểm được xây dựng như sau: class DIEM 51
  52. { private: int x, y, m ; public: void nhapsl() ; void hien() ; void an() { putpixel(x, y, getbkcolor()); } } ; void DIEM::nhap() { cout > x >> y ; cout > m ; } void DIEM::hien() { int mau_ht ; mau_ht = getcolor(); putpixel(x, y, m); setcolor(mau_ht); } Qua ví dụ trên có thể rút ra một số điều cần nhớ: + Trong cả 3 phương thức (dù viết trong hay viết ngoài định 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. + Các phương thức viết bên trong định nghĩa lớp (như phương thức an() ) được viết như một hàm thông thường. + 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 định rõ đây là phương thức của lớp 52
  53. nào. Ví dụ 2: #include "stdio.h" #include "conio.h" #include "iostream.h" typedef struct date { int ngay,thang,nam; }; class sv { private: char msv[3],ht[30],qq[30]; date ns; public: void nhap(void); void in(void); } ; void sv::nhap(void) { cout >msv; cout >ht; cout >qq; cout >ns.ngay>>ns.thang>>ns.nam; } void sv::in(void) { cout<<"\n MSV: "<<msv; cout<<"\n Ten: "<<ht; cout<<"\n Que: "<<qq; cout<<"\n Ngay sinh: "<<ns.ngay<<"/"<<ns.thang<<"/"<<ns.nam; cout<<"\n"; } void main() { int i,j,n; 53
  54. clrscr(); sv a[30]; //Khai bao mang chua 30 SV cout >n; for(i=1;i ; • 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. • Không có vùng nhớ riêng để chứa các hàm thành phần cho mỗi đối tượng. • Các hàm thành phần sẽ được sử dụng chung cho tất cả các đối tượng cùng lớp. Ví dụ: Mô tả đối tượng điểm { //dữ liệu int x,y; //phương thức void init(int ox,int oy); void move(int dx,int dy); void display(); }; Một lớp (sau khi định nghĩa) có thể xem như 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: Tên_lớp danh sách đối ; Tên_lớp danh sách mảng ; Ví dụ : sử dụng lớp DIEM có thể khai báo các biến, mảng 54
  55. DIEM như sau: DIEM d1, d2, d3 ; // Khai báo 3 biến đối tượng d1, d2, d3 DIEM d[20] ; // Khai báo mảng đối tượng d gồm 20 phần tử Thuộc tính của đối tượng: Trong ví dụ trên, mỗi đối tượng d1, d2, d3 và mỗi phần tử d[i] đều có 3 thuộc tính là x, y, m. Chú ý là mỗi thuộc đều thuộc về một đố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 tính của đối tượng như sau: tên_đối_tượng.Tên_thuộc_tính Với các đối tượng d1, d2, d3 và mảng d, có thể viết như sau: d1.x // Thuộc tính x của đối tượng d1 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 d[2].m // Thuộc tính m của phần tử d[2] d1.x = 100 ; // Gán 100 cho d1.x d2.y = d1.x; // Gán d1.x cho d2.y 3.3 Truy nhập tới các thành phần của lớp. Phạm vi truy xuất các thành phần của lớp quy định bởi các từ khoá private và public - Các thành phần (thuộc tính và phương thức) của lớp có thể khai báo là private hoặc public theo mẫu: private: // Khai báo các thành phần riêng của lớp public: // Khai báo các thành phần chung (công cộng) Chú ý: Các thành phần khai báo mặc định (không dùng các từ khoá private và public) được xem là các thành phần private. 3.3.1 Các thành phần riêng của lớp. Chỉ được sử dụng trong phạm vi 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. + Một thuộc tính private: Thuộc tính này (của một đối tượng nào đó) chỉ có thể được sử dụng trong thân của các phương thức cùng lớp. + 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. Ví dụ: Xét lớp PS (phân số) với 2 thuộc tính nguyên là t (tử) và m (mẫu). Giả sử cần xây dựng các phương thức để thực hiện các phép toán cộng trừ, nhân, chia phân số. Do 55
  56. 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 báo là public. Để thực hiện các phép tính trên phân số cần dùng đến 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. 3.3.2 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. Ví dụ: phương án dùng một hàm (tự do) để thực hiện phép cộng 2 số phức như sau là sai: Phương án 3: Dùng hàm tự do class SP { private: double a; // Phần thực double b; // Phần ảo public: } ; SP cong(SP u1, SP u2) { SP u: u.a = u1.a + u2.a ; u.b = u1.b + u2.b ; return u; } 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 tính dấu kín, an toàn dữ liệu của lớp. 3.4 Con trỏ đối tượng. 3.4.1 Khai báo con trỏ đối tượng. 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 * Tên_con_ trỏ ; Ví dụ : Dùng lớp DIEM, ta có thể khai báo: DIEM *p1, *p2, *p3 ; // Khai báo 3 con trỏ p1, p2, p3 DIEM d1, d2 ; //Khai báo hai đối tượng d1, d2 56
  57. DIEM d [20] ; // Khai báo mảng đối tượng Có thể thực hiện câu lệnh : p1 = &d2 ; //p1 chứa địa chỉ của d2, p1 trỏ tới d2 p2 =d ; // p2 trỏ tới đầu mảng d p3 =new DIEM //tạo một đối tượng và chứa địa chỉ của nó vào p3 Để sử dụng thuộc tính của đối tượng thông qua con trỏ, ta viết như sau: Tên_con_trỏ->Tên_thuộc_tính 3.4.2 Cách truy xuất các thành phần của lớp từ con trỏ đối tượng. Để truy xuất các thành phần của lớp từ con trỏ đối tượng, ta viết nh sau : Tên_con_trỏ -> Tên_thuộc_tính Tên_con_trỏ -> Tên_hàm(các tham số thực sự) Giả sử con trỏ q trỏ tới địa chỉ đầu vùng nhớ của một dẫy đối tượng. Khi đó: - Để biểu thị một thành phần (thuộc tính hoặc phương thức) của đối tượng thứ i, ta dùng một trong các mẫu viết sau: q[i].tên_thành_phần (q+i)-> tên_thành_phần - Để biểu thị đối tượng thứ i, ta dùng một trong các mẫu viết sau: q[i] *(q+i) Chú ý: Nếu con trỏ chứa địa chỉ đầu của mảng, có thể dùng con trỏ như tên mảng. Như vậy sau khi thực hiện các câu lệnh trên thì: p1->x và d2.x là như nhau p2[i].y và d[i].y là như nhau Ví dụ: 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 bằng cách dùng con trỏ và cấp phát bộ nhớ cho các đối tượng. Chương trình hàm main() như sau: void main() { TS *ts; int n, i, j; clrscr(); cout > n; ts = new TS[n+1]; for (i=1; i<= n; ++i) ts[i].nhap(i); cout <<"\n Danh sach nhap vao:"; 57
  58. for (i=1; i #include class mhang { int maso; float gia; public: void getdata(int a, float b) {maso= a; gia= b;} void show() { cout >x>>y; p -> getdata(x,y); p++;} for (i = 0; i <k; i++) 58
  59. { cout show(); d++; } getch(); } Ví dụ 2: Chương trình dưới đây sử dụng lớp DIEM để 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 đối tượng. #include #include #include class DIEM { private: int x, y, m ; public: void nhapsl(); void an() { putpixel(x,y,getbkcolor()); } void hien(); }; void DIEM::nhapsl() { cout > x >> y ; cout > m ; } void DIEM::hien() { int mau_ht; mau_ht = getcolor() ; putpixel(x,y,m); setcolor(mau_ht); } void kd_do_hoa() 59
  60. { int mh, mode ; mh=mode=0; initgraph(&mh, &mode, ""); } void main() { DIEM *p; int i, n; cout > n; p = new DIEM[n+1]; for (i=1; i<=n; ++i) p[i].nhapsl(); kd_do_hoa(); for (i=1; i<=n; ++i) p[i].hien(); getch(); for (i=1; i<=n; ++i) p[i].an(); getch(); closegraph(); } Ví dụ 3: 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. Ví dụ minh họa + Thuộc tính (thành phần dữ liệu) của lớp có thể là đối tượng của lớp khác đã định nghĩa bên trên. + Phương thức có giá trị trả về kiểu đối tượng và con trỏ đối tượng. hình chữ nhật có max diện tích và hình chữ nhật có max chu vi. Chương trình được tổ chức thành 2 lớp: + Lớp HINH_CN gồm: - Các thuộc tính: d và r (chiều dài và chiều rộng) - Các phương thức void nhapsl() ; // Nhập chiều dài, rộng int dien_tich(); // Tính diện tích int chu_vi() ; // Tính chu vi + Lớp DAY_HINH_CN gồm - Các thuộc tính: int n ; //số hình chữ nhật của dãy HINH_CN *h; //Con trỏ tới dãy đối tượng của lớp HINH_CN 60
  61. - Các phương thức 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ó diện tích max HINH_CN *hinh_cv_max() ; // Trả về con trỏ tới HCN có chu vi max #include #include class HINH_CN { private: int d, r; // chieu dai va chieu rong public: void nhapsl() { cout > d >> r ; } void in() { cout << "\nchieu dai = " << d ; cout << " chieu rong= " << r; } int dien_tich() { return d*r; } int chu_vi() { return 2*(d+r); } } ; class DAY_HINH_CN { private: int n; // So hinh ch nhat HINH_CN *h; public: void nhapsl(); HINH_CN hinh_dt_max() ; HINH_CN *hinh_cv_max() ; 61
  62. } ; void DAY_HINH_CN::nhapsl() { cout > n; h = new HINH_CN[n+1]; for (int i=1;i hdtmax.dien_tich() ) hdtmax = h[i]; return hdtmax; } HINH_CN *DAY_HINH_CN::hinh_cv_max() { int imax = 1; for (int i=2; i h[imax].chu_vi() ) imax = i ; return (h+imax); } void main() { DAY_HINH_CN d; HINH_CN hdtmax; d.nhapsl(); hdtmax = d.hinh_dt_max(); hdtmax.in() ; HINH_CN *hcvmax=d.hinh_cv_max(); hcvmax->in() ; getch(); } Ví dụ 4: chương trình là nhập một dãy các điểm, sau đó tìm tam giác lớn nhất (về diện tích) có đỉnh là các điểm vừa nhập. Ví dụ minh họa 62
  63. + Thuộc tính (thành phần dữ liệu) của lớp có thể là đối tượng của lớp khác đã định nghĩa bên trên. + Phương thức có giá trị trả về kiểu đối tượng + Vai trò của con trỏ this (xem phương thức maxdt của lớp TAM_GIAC) + Phương thức tĩnh (xem phương thức tao_tg của lớp TAM_GIAC) Chương trình được tổ chức thành 2 lớp: + Lớp DIEM gồm: - Các thuộc tính: x và y (toạ độ của điểm) - Các phương thức void nhapsl() ; // Nhập x, y void in() ; // In toạ độ double do_dai(DIEM d2) ; // Tính độ dài đoạn thẳng qua 2 điểm (điểm ẩn xác // định bởi this và điểm d2) + Lớp TAM_GIAC gồm: - Các thuộc tính: DIEM d1,d2,d3; // 3 đỉnh của tam giác - Các phương thức: void nhapsl(); // Nhập toạ độ 3 đỉnh 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) 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à t2 TAM_GIAC maxdt(TAM_GIAC t2); + Các vấn đề đáng chú ý trong chương trình là: - Phương thức tĩnh tao_tg - Phương thức maxdt + Thuật toán là: - Duyệt qua các tổ hợp 3 điểm. - Dùng phương thức tao_tg để lập tam giác từ 3 điểm - Dùng phương thức maxdt để chọn tam giác có diện tích lớn hơn trong 2 tam giác: tam giác vừa tạo và tam giác có diện tích max (trong số các tam giác đã tạo) #include #include #include class DIEM { private: double x,y; // Toa do cua diem 63
  64. public: void nhapsl() { cout > x >> y ; } void in() { cout << " x = " << x << " y = " << y; } double do_dai(DIEM d2) { return sqrt(pow(x-d2.x,2) + pow(y-d2.y,2) ); } } ; class TAM_GIAC { private: DIEM d1,d2,d3; // 3 dinh tam giac public: void nhapsl(); void in(); static TAM_GIAC tao_tg(DIEM e1, DIEM e2, DIEM e3) { TAM_GIAC t; t.d1=e1; t.d2 = e2; t.d3=e3; return t; } double dien_tich() ; TAM_GIAC maxdt(TAM_GIAC t2); } ; void TAM_GIAC::nhapsl() { cout << "\nDinh 1 - " ; d1.nhapsl(); cout << "\nDinh 2 - " ; d2.nhapsl(); cout << "\nDinh 3 - " ; d3.nhapsl(); } 64
  65. void TAM_GIAC::in() { cout dien_tich() > t2.dien_tich()) return *this ; else return t2; } void main() { DIEM d[50]; int n, i ; clrscr(); cout > n; for (i=1; i<=n; ++i) { cout << "\nNhap diem " << i << " - " ; d[i].nhapsl(); } int j, k ; TAM_GIAC tmax, t; tmax = TAM_GIAC::tao_tg(d[1],d[2],d[3]); for (i=1;i<=n-2;++i) for (j=i+1;j<=n-1;++j) for (k=j+1;k<=n;++k) 65
  66. { t=TAM_GIAC::tao_tg(d[i],d[j],d[k]); tmax = tmax.maxdt(t); } cout #include 66
  67. #include class DIEM { private: double x,y; // Toa do cua diem public: void nhapsl() { cout > x >> y ; } void in() { cout << " x = " << x << " y = " << y; } double do_dai(DIEM d2) { return sqrt(pow(x-d2.x,2) + pow(y-d2.y,2) ); } } ; class TAM_GIAC { private: DIEM d1,d2,d3; // 3 dinh tam giac public: void nhapsl(); void in(); friend TAM_GIAC tao_tg(DIEM e1, DIEM e2, DIEM e3) { TAM_GIAC t; t.d1=e1; t.d2 = e2; t.d3=e3; return t; } double dien_tich() ; TAM_GIAC maxdt(TAM_GIAC t2); } ; void TAM_GIAC::nhapsl() { cout << "\nDinh 1 - " ; d1.nhapsl(); 67
  68. cout dien_tich() > t2.dien_tich()) return *this ; else return t2; } void main() { DIEM d[50]; int n, i ; clrscr(); cout > n; for (i=1; i<=n; ++i) { cout << "\nNhap diem " << i << " - " ; d[i].nhapsl(); } int j, k ; 68
  69. TAM_GIAC tmax, t; tmax = tao_tg(d[1],d[2],d[3]); for (i=1;i<=n-2;++i) for (j=i+1;j<=n-1;++j) for (k=j+1;k<=n;++k) { t=tao_tg(d[i],d[j],d[k]); tmax = tmax.maxdt(t); } cout << "\n\nTam giac co dien tich lon nhat: " ; tmax.in(); cout << "\nDien tich = " << tmax.dien_tich(); getch(); } Chú ý: Hàm bạn có thể xây dựng bên trong định nghĩa lớp (như 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: class TAM_GIAC { private: DIEM d1,d2,d3; // 3 dinh tam giac public: void nhapsl(); void in(); friend TAM_GIAC tao_tg(DIEM e1,DIEM e2,DIEM e3); double dien_tich() ; TAM_GIAC maxdt(TAM_GIAC t2); } ; TAM_GIAC tao_tg(DIEM e1, DIEM e2, DIEM e3) { TAM_GIAC t; t.d1=e1; t.d2 = e2; t.d3=e3; return 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) 3.4.3 Con trỏ This. - Con trỏ this là đối thứ nhất của phương thức. Xem lại phương thức nhapsl của lớp DIEM void DIEM::nhapsl() { 69
  70. cout > x >> y ; cout > m ; } Trong phương thức này 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 quy tắc sử dụng thuộc tính nêu trong mục trước. C++ sử dụng con trỏ đặc biệt this trong các phương thức. Các thuộc tính viết trong phương thức được hiểu là thuộc một đối tượng do con trỏ this trỏ tới. Như vậy phương thức nhapsl() có thể viết một cách tường minh như sau: void DIEM::nhapsl() { cout > this->x >> this->y ; cout > this->m ; } 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. - Tham số ứng với đối con trỏ this. Xét một lời gọi tới phương thức nhapsl() : DIEM d1; d1.nhapsl() ; Trong trường hợp này tham số truyền cho con trỏ this chính là địa chỉ của d1: this = &d1 Do đó: this->x chính là d1.x this->y chính là d1.y this->m chính là d1.m Như vậy câu lệnh d1.nhapsl() ; sẽ nhập dữ liệu cho các thuộc tính của đối tượng d1. - Tham số truyền cho đối con trỏ this chính là địa chỉ của đối tượng đi kèm với phương thức trong lời gọi phương thức. BÀI TẬP CHƯƠNG 3 1. Viết chương trình nhập vào số nguyên và cho kết quả là dãy các chữ số 0,1 biểu diễn nhị phân của số đó. 2. Hãy xây dựng lớp SinhVien có các thuộc tính riêng: Số báo danh, Học và tên sinh 70
  71. viên, điạ chỉ, môn học, điểm thi học kỳ I, II. a. Viết các hảmtuy nhập tới các thành phần dữ liệu và hàm tạo cho lớp sinhvien. b. Viết chương trình chính để tạo ra danh sách SinhVien và hiển thị menu: 1. Nhập vào thông tin về sinh viên. 2. Xem thông tin về sinh viên. 3. Tìm sinh viên theo điểm. 4. Kết thúc chương trình. c. Viết các hàm thành phần của một lớp để thực hiện các yêu cầu trên. 3. Xây dựng lớp Sinhvien để quản lý hộ tên sinh viên, năm sinh, điểm thi 9 môn học của các sinh viên. Cho biết sinh viên nào được làm khóa luận tốt nghiệp, bao nhiêu sinh viên thi tốt nghiệp, bao nhiêu sinh viên thi lại, tên môn thi lại. Tiêu chuẩn để xét như sau: - Sinh viên làm khóa luận phải có điểm trung bình từ 7 trở lên, trong đó không có môn nào dưới 5. - Sinh viên thi tố nghiệp khi điểm trung bình nhỏ hơn 7 và điểm các môn không dưới 5. - Sinh viên thi lại môn dưới 5. Xây dựng lớp Sinhvien để quản lý hộ tên sinh viên, năm sinh, điểm thi 9 môn học của các sinh viên. Cho biết sinh viên nào được làm khóa luận tốt nghiệp, bao nhiêu sinh viên thi tốt nghiệp, bao nhiêu sinh viên thi lại, tên môn thi lại. Tiêu chuẩn để xét như sau: - Sinh viên làm khóa luận phải có điểm trung bình từ 7 trở lên, trong đó không có môn nào dưới 5. - Sinh viên thi tố nghiệp khi điểm trung bình nhỏ hơn 7 và điểm các môn không dưới 5. - Sinh viên thi lại môn dưới 5. 4. Một khách sạn 5 sao có 3 loại phòng: - Phòng loại A, đơn giá 120 USD/ngày - Phòng loại B, đơn giá 80 USD/ngày - Phòng loại C, đơn giá 50 USD/ngày Tiền thuê phòng = đơn giá x số ngày thuê Khách ở phòng loại A được sử dụng một số dịch vụ của khách sạn. Tiền sử dụng dịch vụ cộng vào tiền thuê phòng của khách. Đặc biệt khách sạn có chính sách giảm giá 10% cho những khách thuê phòng loại A và B kể từ ngày thứ 5 trở đi. Viết chương trình: - Nhập vào thông tin thuê phòng của khách - Tính tiền thuê phòng cho khách 71
  72. 5.Tạo một lớp MyDate gồm 3 thuộc tính là số nguyên gồm: Ngày, tháng, năm (date, month, year) theo kỹ thuật đóng gói và che giấu thông tin của lập trình hướng đối tượng. a. Viết các phương thức set/get cho các thuộc tính của lớp, nếu giá trị thiết lập không đảm bảo ràng buộc của các thuộc tính này (đều phải là số nguyên, 1<=month<=12; tùy thuộc vào giá trị month và year nhập vào mà day có đoạn tương ứng là [1,28] hay [1,29] hay [1,30] hay [1,31]). b. Viết phương thức nhap() để yêu cầu người dùng nhập các thuộc tính của lớp MyDate (year month day) từ bàn phím (sử dụng các phương thức của lớp JOptionPane, nếu nhập sai thì thông báo lỗi – chú ý các hằng số messageType hoặc optionType – và yêu cầu nhập lại cho tới khi nhập đúng). c. Viết phương thức hienThi() để hiển thị các thuộc tính ngày, tháng, năm cho lớp MyDate theo quy cách: ngày/tháng/năm (Ví dụ: 17/01/2008). d. Viết hàm main thực hiện các lệnh để kiểm tra các phương thức của lớp đã xây dựng bằng cách: - Tạo ra đối tượng của lớp bằng toán tử “new”: MyDate d = new MyDate(); - Gọi các phương thức của lớp bằng toán tử “.”: d.ten_phuong_thuc(ds_tham_so); //neu co tham so 6. Tạo một lớp biểu diễn điểm trong mặt phẳng (Point) gồm các thuộc tính: Tên điểm (Chuỗi ký tự ví dụ: “A”, “B”, “C” ), Hoành độ (số thực), Tung độ (số thực). a. Viết các phương thức get/set cho lớp Point b. Viết các phương thức khởi tạo sau: i. Phương thức khởi tạo không có tham số nào: Gán cho hoành độ và tung độ = 0, tên điểm mặc định là “A”. ii. Phương thức khởi tạo nhận hai số thực làm tham số iii. Phương thức khởi tạo nhận 1 xâu ký tự và 2 số thực làm tham số iv. Phương thức khởi tạo nhận 1 đối tượng của lớp Point làm tham số c. Viết phương thức nhập vào một điểm từ bàn phím: Nhập tung độ và hoành độ từ bàn phím (sử dụng JOptionPane.showInputDialog( )). d. Viết phương thức in thông tin một điểm ra màn hình dưới dạng: Tên_điểm(Tung_độ, Hoành_độ). Ví dụ: A(2, 4). e. Viết phương thức main() để tạo ra các đối tượng của lớp Point và kiểm tra các phương thức đã cài đặt cho lớp. 7. Tạo một lớp gọi là PS để thực hiện các thao tác số học với phân số. Viết chương trình để kiểm tra lớp vừa tạo ra. Sử dụng các biến nguyên để biểu diễn các thành phần dữ liệu của lớp tử số và mẫu số. Viết định nghĩa hàm thiết lập để tạo đối tượng sao cho phần ảo phải là số nguyên dương. Ngoài ra còn có các hàm thành phần khác thực hiện 72
  73. các công việc cụ thể: + Cộng hai phân số. Kết quả phải được tối giản. + Trừ hai phân số. Kết quả phải được tối giản. + Nhận hai phân số. Kết quả dưới dạng tối giản. + Chia hai phân số. Kết quả dưới dạng tối giản. + In ra màn hình phân số dưới dạng a/b trong đó a là tử số, còn b là mẫu số. + In phân số dưới dạng số thập phân. 8. Xây dựng lớp Stack và lớp Queue mô tả hoạt động của ngăn xếp và hàng đợi các số nguyên. 9. Viết chương trình mô phỏng quá trình bóng nảy trong một ô hình chữ nhật. Tạo lớp đối tượng mô tả chuyển động quả bóng. Xây dựng chương trình bắt đầu từ đơn giản có một quả bóng rồi nhiều quả bóng có thể va vào nhau, thêm các chướng ngại vật trên bàn, quả bóng lăn có ma sát và có lực tương tác giữa các quả bóng khi có va chạm. 10. Xây dựng một lớp mô tả các bảng thi đấu bóng đá gọi là BangThiDau. Giả thiết mỗi bảng có 4 đội và thi đấu chéo từng cặp. Có lịch các trận thi đấu của bảng. Tạo các phương thức nhập kết quả thi đấu và tính điểm cho từng đội. Yêu cầu việc nhập kết quả thi đấu phải theo thứ tự thời gian. Thêm các phương thức hiển thị thông tin thi đấu của từng đội từng đội và của cả bảng. Viết chương trình để kiểm nghiệm lớp xây dựng được. 73
  74. CHƯƠNG 4 TOÁN TỬ ĐỊNH NGHĨA CHỒNG Mã chương: MH13_CH04 Giới thiệu: - Trong bài này sẽ giúp cho học sinh định nghĩa chồng các hàm khởi tạo không tham số và có tham số và sử dụng được các hàm định nghĩa chồng. Mục tiêu: - Định nghĩa chồng được các toán tử hai như toán tử +, -, *, / trên các lớp đối tượng; - Định nghĩa chồng toán tử logic; - Định nghĩa chồng toán tử một ngôi; - Định nghĩa chồng toán tử gán (=); - Thực hiện các thao tác an toàn với máy tính. Nội dung chính: Chương 4. TOÁN TỬ ĐỊNH NGHĨA CHỒNG 4.1 Cách định nghĩa chồng các toán tử 4.1.1 Tên hàm toán tử. - Trong C++, có thể định nghĩa chồng đối với hầu hết các phép toán (một ngôi hoặc hai ngôi) trên các lớp, nghĩa là một trong số các toán hạng tham gia phép toán là các đối tượng. - Đây là một khả năng mạnh vì nó cho phép xây dựng trên các lớp các toán tử cần thiết, làm cho chương trình được viết ngắn gọn dễ đọc hơn và có ý nghĩa hơn. - Hàm toán tử có tên được ghép bởi từ khoá operator và ký hiệu của phép toán tương ứng Ví dụ: Tên hàm Ý nghĩa operator + định nghĩa phép + operator * định nghĩa phép * operator / định nghĩa phép / operator += định nghĩa phép tự cộng+= operator != định nghĩa phép so sánh khác nhau 74
  75. Quá trình xây dựng toán tử được thực hiện như sau: - Định nghĩa lớp để xác định kiểu dữ liệu sẽ được sử dụng trong các toán tử - Khai báo hàm toán tử trong vùng public của lớp - Định nghĩa nội dung cần thực hiện Cú pháp xây dựng hàm toán tử: Kiểu_trả_về operator op(danh sách đối số) { //thân của hàm toán tử } Trong đó: • Kiểu_trả_về là kiểu kết quả thực hiện của toán tử. • op là tên toán tử • operator op(danh sách đối số) gọi là hàm toán tử, nó có thể là hàm thành phần hoặc là hàm bạn, nhưng không thể là hàm tĩnh. • Thân của hàm toán tử: Viết như thân của hàm thông thường. Ví dụ : hàm đổi dấu ma trận có thể được định nghĩa như sau struct MT { double a[20][20] ; // Mảng chứa các phần tử ma trận int m ; // Số hàng ma trận int n ; // Số cột ma trân } ; MT operator-(MT x) { MT y; for (int i=1; i<= m ;++i) for (int j=1; j<= n ;++j) y[i][j] = - x[i][j] ; return y; } 4.1.2 Các đối của hàm toán tử. Danh sách đối số được khai báo tương tự khai báo biến nhưng phải tuân theo những quy định sau: - Nếu toán tử là hàm thành phần thì: không có đối số cho toán tử một ngôi và một đối số cho toán tử hai ngôi. Cũng giống như hàm thành phần thông thường, hàm thành phần toán tử có đối đầu tiên (không tường minh) là con trỏ this . - Nếu toán tử là hàm bạn thì: có một đối số cho toán tử một ngôi và hai đối số cho toán tử hai ngôi. 75
  76. - Hàm toán tử thành phần có một tham số ngầm định là đối tượng gọi hàm nên chỉ có một tham số tường minh. + Các phép toán một ngôi là: *, &, ~, !, ++, , sizeof (kiểu) Ví dụ: hàm toán tử đổi dấu ma trận (đổi dấu tất cả các phần tử của ma trận) được khai báo như sau struct MT { double a[20][20] ; // Mảng chứa các phần tử ma trận int m ; // Số hàng ma trận int n ; // Số cột ma trân } ; MT operator-(MT x) ; + Các phép toán hai ngôi là: *, /, %, +, - , >, , =, ==, !=, &, |, ^, &&, || Ví dụ: các hàm toán tử cộng , trừ phân số được khai báo như sau struct PS { int a; // Tử số int b; // Mẫu số } ; PS operator+(PS p1, PS p2); // p1 + p2 PS operator-(PS p1, PS p2); // p1 - p2 PS operator*(PS p1, PS p2); // p1 * p2 PS operator/(PS p1, PS p2); // p1 / p2 4.1.3 Ví dụ. Ví dụ 1: toán tử một ngôi dùng hàm bạn #include class Diem { private: float x,y,z; public: Diem() {} Diem(float x1,float y1,float z1) { x = x1; y = y1; z=z1;} friend Diem operator -(Diem d) { Diem d1; 76
  77. d1.x = -d.x; d1.y = -d.y;d1.z=-d.z; return d1; } void hienthi() { 77
  78. cout #include class Diem { private: float x,y,z; public: Diem() {} Diem(float x1,float y1,float z1) { x = x1; y = y1; z=z1;} friend Diem operator +(Diem d1, Diem d2) { Diem tam; tam.x = d1.x + d2.x; } void main() {tam.y = d1.y + d2.y; tam.z = d1.z + d2.z; return tam; 78
  79. clrscr(); Diem d1(3,-6,8),d2(4,3,7),d3; d3=d1+d2; d1.hienthi(); d2.hienthi(); cout #include class Diem { private: float x,y,z; public: Diem() {} Diem(float x1,float y1,float z1) { x = x1; y = y1; z=z1;} Diem operator +(Diem d2) { void hienthi() x = x + d2.x; y = y + d2.y; z = z + d2.z; return (*this); } { cout<<"\n x="<<x<<" y= "<<y<<" z = " << z <<endl;} }; void main() { clrscr(); Diem d1(3,-6,8),d2(4,3,7),d3; d1.hienthi(); d2.hienthi(); d3=d1+d2; cout<<"\n Tong hai diem co toa do la :"; d3.hienthi(); getch(); 79
  80. } Ví dụ 4: sử dụng các hàm toán tử để thực hiện 4 phép tính trên phân số và định nghĩa chồng các phép toán > để xuất và nhập phân số #include #include #include typedef struct { int a,b; } PS; ostream& operator > (istream& is,PS &p); int uscln(int x, int y); PS rutgon(PS p); PS operator+(PS p1, PS p2); PS operator-(PS p1, PS p2); PS operator*(PS p1, PS p2); PS operator/(PS p1, PS p2); ostream& operator > (istream& is,PS &p) { cout > p.a >> p.b ; return is; } int uscln(int x, int y) { x=abs(x); y=abs(y); if (x*y==0) return 1; while (x!=y) if (x>y) x-=y; else y-=x; return x; } PS rutgon(PS p) { 80
  81. PS q; int x; x=uscln(p.a,p.b); q.a = p.a / x ; q.b = p.b / x ; return q; } PS operator+(PS p1, PS p2) { PS q; q.a = p1.a*p2.b + p2.a*p1.b; q.b = p1.b * p2.b ; return rutgon(q); } PS operator-(PS p1, PS p2) { PS q; q.a = p1.a*p2.b - p2.a*p1.b; q.b = p1.b * p2.b ; return rutgon(q); } PS operator*(PS p1, PS p2) { PS q; q.a = p1.a * p2.a ; q.b = p1.b * p2.b ; return rutgon(q); } PS operator/(PS p1, PS p2) { PS q; q.a = p1.a * p2.b ; q.b = p1.b * p2.a ; return rutgon(q); } void main() { PS p, q, z, u, v ; PS s; cout > p >> q >> z >> u >> v ; s = (p - q*z) / (u + v) ; 81
  82. cout > có 2 đối dùng để nhập đa thức Chương trình nhập vào 4 đa thức: p, q, r, s. Sau đó tính đa thức: f = -(p+q)*(r-s) Tính giá trị f(x), với x là một số thực nhập từ bàn phím. #include #include #include struct DT { double a[20]; // Mang chua cac he so da thuc a0, a1, int n ; // Bac da thuc } ; ostream& operator > (istream& is,DT &d); DT operator-(const DT& d); DT operator+(DT d1, DT d2); DT operator-(DT d1, DT d2); DT operator*(DT d1, DT d2); double operator^(DT d, double x); // Tinh gia tri da thuc ostream& operator > (istream& is, DT &d) { cout > d.n; 82
  83. cout > d.a[i] ; } return is; } DT operator-(const DT& d) { DT p; p.n = d.n; for (int i=0 ; i d2.n ? d1.n : d2.n ; for (i=0; i 0 && d.a[i]==0.0) i; d.n = i; return d ; } DT operator-(DT d1, DT d2) { return (d1 + (-d2)); } DT operator*(DT d1, DT d2) { DT d; int k, i, j; 83
  84. k = d.n = d1.n + d2.n ; for (i=0; i > p; cout > q; cout > r; cout > s; cout > x; f = -(p+q)*(r-s); g = f^x; cout << "\nDa thuc f " << f ; cout << "\n x = " << x; cout << "\nf(x) = " << g; getch(); } Ví dụ 6: Chương trình xét các ma trận thực vuông cấp n và các véc tơ thực cấp n. Nội dung chương trình là nhập 4 ma trận X, Y, R, S và véc tơ u. Sau đó tính véc tơ v theo công thức: v = ((X + Y)*(R - S))-1u struct MT { 84
  85. double a[20][20] ; // Mang a chứa các phần tử ma trận int n ; // Cấp ma trận } ; struct VT { double b[20]; // Mang chua cac phan tu cua vec to int n ; // Cap vec to } ; Để xử lý ma trận và véc tơ, chúng ta xây dựng 9 hàm toán tử: ostream& operator > (istream& is,MT& x); // Nhập ma trận istream& operator>> (istream& is, VT &v); // Nhập véc tơ MT operator+(const MT& x1, const MT& x2); // Cộng 2 ma trận MT operator-(const MT& x1, const MT& x2); // Trừ 2 ma trận MT operator*(const MT& x1, const MT& x2); // Nhân 2 ma trận VT operator*(const MT& x, const VT& v); // Nhân ma trận véc tơ MT operator!(MT x); // Nghịch đảo ma trận Để nghịch đảo ma trận có nhiều cách, ở đây chúng ta dùng phương pháp Jordance như sau. Giả sử cần nghịch đảo ma trận x cấp n. Ta dùng thêm ma trận đơn vị y. Sau đó thực hiện đồng thời các phép tính trên cả x và y sao cho x trở thành đơn vị. Kết quả y chính là nghịch đảo của x. Thuật toán được tiến hành trên n bước. Nội dung của bước k (k = 1, ,n) như sau: Tìm chỉ số r ( k #include #include 85
  86. #include struct MT { double a[20][20]; // Mang chua cac phan tu ma tran int n ; // Cap ma tran } ; struct VT { double b[20]; // Mang chua cac phan tu cua vec to int n ; // Cap vec to } ; ostream& operator > (istream& is,MT& x); istream& operator>> (istream& is, VT &v); MT operator+(const MT& x1, const MT& x2); MT operator-(const MT& x1, const MT& x2); MT operator*(const MT& x1, const MT& x2); VT operator*(const MT& x, const VT& v); MT operator!(MT x); // Tinh ma tran nghich dao ostream& operator > (istream& is, MT& x) 86
  87. { cout > x.n; cout > x.a[i][j] ; } return is; } istream& operator>> (istream& is, VT& v) { cout > v.n; cout > v.b[i] ; } return is; } MT operator+(const MT& x1, const MT& x2) { if (x1.n!=x2.n) { cout << "\nKhong thuc hien duoc phep cong vi 2 MT khong cung cap"; getch(); return x1; } else { MT x; int i, j, n; n = x.n = x1.n ; for (i=1; i<=n; ++i) for (j=1; j<=n ;++j) x.a[i][j] = x1.a[i][j] + x2.a[i][j] ; return x; 87
  88. } } MT operator-(const MT& x1, const MT& x2) { if (x1.n!=x2.n) { cout << "\nKhong thuc hien duoc phep tru vi 2 MT khong cung cap"; getch(); return x1; } else { MT x; int i, j, n; n = x.n = x1.n; for (i=1; i<=n; ++i) for (j=1; j<=n ;++j) x.a[i][j] = x1.a[i][j] - x2.a[i][j] ; return x; } } MT operator*(const MT& x1, const MT& x2) { if (x1.n!=x2.n) { cout << "\nKhong thuc hien duoc phep nhan vi 2 MT khong cung cap"; getch(); return x1; } else { MT x; int n, i, j,k; n = x.n = x1.n; for (i=1; i<=n; ++i) for (j=1; j<=n ;++j) { x.a[i][j] = 0.0 ; for (k=1 ; k<=n; ++k) x.a[i][j] += x1.a[i][k]*x2.a[k][j] ; } 88
  89. return x; } } VT operator*(const MT& x, const VT& v) { if (x.n != v.n) { cout abs(x.a[r][k]) ) r = i; if (abs(x.a[r][k]) < 1.0E-8) { 89
  90. cout > x; 90
  91. cout > y; cout > r; cout > s; cout > u; v = !((x+y)*(r-s))*u ; cout yêu cầu hàm toán tử phải là hàm thành phần của lớp, không thể dùng hàm bạn để định nghĩa toán tử. - Về nguyên tắc, định nghĩa chồng một phép toán là đơn giản, nhưng việc sử dụng phép toán định nghĩa chồng lại không dễ dàng và đòi hỏi phải cân nhắc nếu bị lạm dụng sẽ làm cho chương trình khó hiểu. - Phải làm sao để các phép toán vẫn giữ được ý nghĩa trực quan nguyên thuỷ của chúng. - Phải xác định trước ý nghĩa các phép toán trước khi viết định nghĩa của các hàm toán tử tương ứng. 4.2.1 Dùng như hàm thông thường. Dùng như một hàm thông thường bằng cách viết lời gọi Ví dụ: PS p, q, u, v ; u = operator+(p, q) ; // u = p + q v = operator-(p, q) ; // v = p - q 4.2.2 Dùng như phép toán của C++. Ví dụ: 91
  92. PS p, q, u, v ; u = p + q ; // u = p + q v = p - q ; // v = p - q Chú ý: Khi dùng các hàm toán tử như phép toán của C++ ta có thể kết hợp nhiều phép toán để viết các công thức phức tạp. Cũng cho phép dùng dấu ngoặc tròn để quy định thứ tự thực hiện các phép tính. Thứ tự ưu tiên của các phép tính vẫn tuân theo các quy tắc ban đầu của C++ . Chẳng hạn các phép * và / có thứ ưu tiên cao hơn so với các phép + và - Ví dụ: PS p, q, u, v, s1, s2 ; s1 = p*q - u/v ; // s1 = (p*q) s2 = (p - q)/(u + v) ; // s2 = (p - q)/(u + v) a. Định nghĩa các toán tử ++, - - - Ta có thể định nghĩa chồng cho các toán tử ++/ theo quy định sau: + Toán tử ++/ dạng tiền tố trả về một tham chiếu đến đối tượng thuộc lớp. + Toán tử ++/ dạng hậu tố trả về một đối tượng thuộc lớp. Ví dụ: #include #include class Diem { private: int x,y; public: Diem() {x = y = 0;} Diem(int x1, int y1) {x = x1; y = y1;} Diem & operator ++(); //qua tai toan tu ++ tien to Diem operator ++(int); //qua tai toan tu ++ hau to Diem & operator (); //qua tai toan tu tien to Diem operator (int); //qua tai toan tu hau to void hienthi() { cout<<" x = "<<x<<" y = "<<y; } }; 92
  93. Diem & Diem::operator ++() { x++; y++; return (*this); } Diem Diem::operator ++(int) { Diem temp = *this; ++*this; return temp; } Diem & Diem::operator () { x ; y ; return (*this); } Diem Diem::operator (int) { Diem temp = *this; *this; return temp; } void main() { clrscr(); Diem d1(5,10),d2(20,25),d3(30,40),d4(50,60); cout<<"\nd1 : ";d1.hienthi(); ++d1; cout<<"\n Sau khi tac dong cac toan tu tang truoc :"; cout<<"\nd1 : ";d1.hienthi(); cout<<"\nd2 : ";d2.hienthi(); d2++; cout<<" \n Sau khi tac dong cac toan tu tang sau"; cout<<"\nd2 : ";d2.hienthi(); cout<<"\nd3 : ";d3.hienthi(); d3; cout<<"\n Sau khi tac dong cac toan tu giam truoc :"; cout<<"\nd3 : ";d3.hienthi(); cout<<"\nd4 : ";d4.hienthi(); 93