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

pdf 68 trang Gia Huy 17/05/2022 3040
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 - Nghề: Lập trình máy tính - Trình độ: Cao đẳng nghề", để tải tài liệu gốc về máy bạn click vào nút DOWNLOAD ở trên

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

  • pdfgiao_trinh_lap_trinh_huong_doi_tuong_nghe_lap_trinh_may_tinh.pdf

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

  1. BỘ LAO ĐỘNG - THƯƠNG BINH VÀ XÃ HỘI TỔNG CỤC DẠY NGHỀ GIÁO TRÌNH Mô đun: LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG Mã số: ITPRG-02 NGHỀ: LẬP TRÌNH MÁY TÍNH Trình độ : Cao đẳng nghề NĂM 2012
  2. Tuyên bố bản quyền : Tài liệu này thuộc loại sách giáo trình Cho 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 có ý đồ 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. Tổng Cục Dạy nghề sẽ làm mọi cách để bảo vệ bản quyền của mình. Tổng Cục Dạy Nghề cám ơn và hoan nghênh các thông tin giúp cho việc tu sửa và hoàn thiện tốt hơn tàI liệu này. Địa chỉ liên hệ: Dự án giáo dục kỹ thuật và nghề nghiệp Tiểu Ban Phát triển Chương trình Học liệu 2
  3. LỜI TỰA Đây là tài liệu được xây dựng theo chương trình của dự án giáo dục kỹ thuật và dạy nghề, để có đươc giáo trình này dự án đã tiến hành theo hai giai đoạn. Giai đoạn 1 : Xây dựng chương trình theo phương pháp DACUM, kết quả của gian đoạn này là bộ khung chương trình gồm 230 trang cấp độ 2 và 170 trang cấp độ 3. Giai đoạn 2 : 29 giáo trình và 29 tài liệu hướng dẫn giáo viên cho nghề lập trình máy tính 2 cấp độ. Để có được khung chương trình chúng tôi đã mời các giáo viên, các chuyên gia đang làm việc trong lĩnh vực công nghệ thông tin cùng xây dựng chương trình. Trong giai đoạn viết giáo trình chúng tôi cũng đã có những sự điều chỉnh để giáo trình có tính thiết thực và phù hợp hơn với sự phát triển của lĩnh vực công nghệ thông tin. Hiện nay để tiết kiệm được nhiều thời gian và công sức, xu thế lập trình hướng đối tượng ngày càng trở nên vô cùng hiệu quả và phổ biến. Các môi trường phát triển ứng dụng luôn lấy tư tưởng lập trình hướng đối tượng làm nền tảng, vì vậy môn học lập trình hướng đối tượng này nhằm cung cấp một phương pháp hướng đối tượng vững chắc cho sinh viên dễ dàng tiếp cận với các ngôn ngữ lập trình hiện đại. Trong quá trình biên soạn, mặc dù đã cố gắng tham khảo nhiều tài liệu và giáo trình khác nhưng tác giả không khỏi tránh được những thiếu sót và hạn chế. Tác giả chân thành mong đợi những nhận xét, đánh giá và góp ý để cuốn giáo trình ngày một hoàn thiện hơn. Tài liệu này được thiết kế theo từng mô đun/ môn học thuộc hệ thống mô đun/môn học của một chương trình, để đào tạo hoàn chỉnh nghề Lập trình máy tính ở cấp trình độ bậc cao và được dùng làm Giáo trình cho học viên trong các khoá đào tạo, cũng có thể được sử dụng cho đào tạo ngắn hạn hoặc cho các công nhân kỹ thuật, các nhà quản lý và người sử dụng nhân lực tham khảo. Đây là tài liệu thử nghiệm sẽ được hoàn chỉnh để trở thành giáo trình chính thức trong hệ thống dạy nghề. 3
  4. MỤC LỤC ĐỀ MỤC TRANG 1. LỜI TỰA 3 2. MỤC LỤC 4 3. GIỚI THIỆU VỀ MÔN HỌC 7 4. CÁC HÌNH THỨC HỌC TẬP CHÍNH TRONG MÔN HỌC 10 Bài 1: PHƯƠNG PHÁP HƯỚNG ĐỐI TƯỢNG 11 1.1 Các phương pháp lập trình 12 1.1.1. Tiếp cận hướng đối tượng 12 1.1.2. Những nhược điểm của lập trình hướng thủ tục 12 1.1.3. Lập trình hướng đối tượng 13 1.2 Các đặc điểm lập trình hướng đối tượng 13 1.2.1 Các khái niệm cơ bản của lập trình hướng đối tượng 13 1.2.2 Trừu tượng hóa dữ liệu và bao gói thông tin 14 1.2.3 Kế thừa 15 1.2.4 Tương ứng bội 15 1.2.5 Liên kết động 15 1.2.6 Truyền thông báo 15 1.3. Xây dựng lớp đối tượng 16 BÀI 2: CÁC THÀNH PHẦN CỦA LỚP 17 2.1 Khai báo một lớp cơ sở 18 2.2 Hàm constructor và destructor 18 2.2.1 Hàm tạo sao chép (constructor) 18 2.2.2 Hàm hủy (destructor) 19 2.3 Hàm tạo sao chép có tham số 21 2.4 Hàm in-line (hàm nội tuyến) 26 2.5 Thành phần của lớp là static 28 2.5.1 Dữ liệu thành phần tĩnh 28 2.5.2 Hàm thành phần tĩnh 30 2.6 Hàm friend (hàm bạn) 32 BÀI 3 ĐỐI TƯỢNG 39 3.1 Biến con trỏ 40 3.1.1 Khái niệm con trỏ ( pointer ) và địa chỉ 40 3.1.2 Tính toán trên biến con trỏ ( pointer ) 40 3.2. Đối tượng là một con trỏ 41 3.3 Phép gán một đối tượng 42 3.3.1 Truy nhập tới các thành phần của lớp 42 4
  5. 3.3.2 Phép gán một đối tượng 48 3.4 Truyền tham số là đối tượng cho hàm 48 3.5 Giá trị trả về của hàm là một đối tượng 49 3.6. Tham chiếu 49 3.7. Mảng của các đối tượng 50 3.8 Con trỏ this 50 3.9 Hàm new và delete 51 3.9.1 Toán tử new để cấp phát bộ nhớ 52 3.9.2 Toán tử delete 53 BÀI 4 HÀM ĐỊNH NGHĨA CHỒNG 54 4.1 Hàm constructor định nghĩa chồng(overloading constructor) 55 4.2. Cách tạo và sử dụng hàm copy constructor 57 4.3. Tham số mặc định của hàm constructor 59 4.4. Hàm định nghĩa chồng 60 4.5. Lấy địa chỉ hàm định nghĩa chồng 62 4.5.1 Trường hợp các hàm có một tham số 62 4.5.2 Trường hợp hàm có nhiều tham số 62 BÀI 5: TOÁN TỬ ĐỊNH NGHĨA CHỒNG 63 5.1 Những khái niệm cơ bản toán tử chồng 64 5.2. Định nghĩa chồng toán tử hai ngôi 64 5.3. Định nghĩa chồng các toán tử ++ , 66 5.4. Định nghĩa chồng toán tử một ngôi 68 5.5. Hàm toán tử là friend 69 5.6. Toán tử gán ( = ) 70 5.7 Một số định nghĩa toán tử chồng 70 5.7.1 Hàm chuyển kiểu 70 5.7.2 Định nghĩa chồng toán tử nhập và xuất 72 BÀI 6: SỰ KẾ THỪA 74 6.1. Các loại kế thừa 75 6.2. Đơn kế thừa 75 6.2.1. Định nghĩa lớp dẫn xuất từ một lớp cơ sở 75 6.2.2. Truy nhập các thành phần trong lớp dẫn xuất 76 6.2.3. Định nghĩa lại các hàm thành phần của lớp cơ sở trong lớp dẫn xuất 77 6.2.4. Hàm tạo đối với tính kế thừa 80 6.2.5. Hàm hủy đối với tính kế thừa 82 6.2.6. Khai báo protected 83 6.2.7. Dẫn xuất protected 83 5
  6. 6.3. Đa kế thừa 84 6.3.1. Định nghĩa lớp dẫn xuất từ nhiều lớp cơ sở 84 6.3.2. Một số ví dụ về đa kế thừa 84 BÀI 7: HÀM ẢO VÀ TÍNH TƯƠNG ỨNG BỘI 91 7.1. Hàm ảo 92 7.1.1 Đặt vấn đề 92 7.1.2. Định nghĩa hàm ảo 93 7.1.3. Quy tắc gọi hàm ảo 95 7.1.4. Quy tắc gán địa chỉ đối tượng cho con trỏ lớp cơ sở 95 7.2 Lớp cơ sở ảo 98 7.2.1. Khai báo lớp cơ sở ảo 98 7.2.2. Hàm tạo và hàm hủy đối với lớp cơ sở 100 BÀI 8: HÀM, LỚP TEMPLATE 106 8.1. Khuôn hình hàm 107 8.1.1. Khái niệm 107 8.1.2. Tạo một khuôn hình hàm 107 8.1.3. Sử dụng khuôn hình hàm 108 8.1.4. Các tham số kiểu của khuôn hình hàm 108 8.1.5. Định nghĩa chồng các khuôn hình hàm 110 8.2. Khuôn hình lớp 111 8.2.1. Khái niệm 111 8.2.2. Tạo một khuôn hình lớp 111 8.2.3. Sử dụng khuôn hình lớp 112 8.2.4. Các tham số trong khuôn hình lớp 113 8.2.5. Tóm tắt 113 6
  7. GIỚI THIỆU VỀ MÔN HỌC Vị trí, ý nghĩa, vai trò môn học : Lập trình hướng đối tượng là phương pháp lập trình mới trên bước đường tiến hóa của việc lập trình máy tính, nhằm giúp chương trình trở nên linh hoạt, tin cậy và dễ phát triển. 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ữ lập trình chạy trên môi trường Windows như Visual Basic, Java, Visual C Vì vậy việc nghiên cứu phương pháp lập trình mới này là thật sự cần thiết đối với những người làm Tin học. Giáo trình này là một thành phần của hệ thống giáo trình của Tổng cục dạy nghề. Giáo trình được biên soạn theo chương trình chính quy công nhân lành nghề ngành Công nghệ thông tin. Mục tiêu của môn học: Sau khi học xong môn học này học viên có khả năng: Nắm vững 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. Mục tiêu thực hiện của môn học: Học xong môn học này học viên có khả năng: - Lập trình được theo phương pháp hướng đối tượng. - Cài đặt được lớp đối tượng trên ngôn ngữ lập trình C++. - Xây dựng được các phương thức, toán tử trong lớp đối tượng. - Cài đặt được lớp đối tượng kế thừa từ lớp đối tượng đã có sẵn. - Sử dụng và cài đặt được lớp đối tượng có tính tương ứng bội. - Tự thiết kế và xây dựng được các chương trình theo phương pháp hướng đối tượng. - Xây dựng được một đối tượng trong từng bài tập cụ thể. Nội dung chính của môn học: BÀI 1. PHƯƠNG PHÁP HƯỚNG ĐỐI TƯỢNG BÀI 2. CÁC THÀNH PHẦN CỦA LỚP BÀI 3. ĐỐI TƯỢNG BÀI 4. HÀM ĐỊNH NGHĨA CHỒNG (OVERLOAD) BÀI 5. TOÁN TỬ ĐỊNH NGHĨA CHỒNG BÀI 6. SỰ KẾ THỪA BÀI 7. HÀM ẢO VÀ TÍNH TƯƠNG ỨNG BỘI BÀI 8. HÀM, LỚP TEMPLATE 7
  8. SƠ ĐỒ MỐI LIÊN HỆ GIỮA CÁC MÔ ĐUN VÀ MÔN HỌC TRONG CHƯƠNG TRÌNH Học kỳ I Học kỳ II Học kỳ III Học kỳ IV Hệ thống Giao diện Lập trình Lập trình máy tính người máy nâng cao Web Phân tích thiết Lập trình căn bản Lập trình hướng kế hệ thống đối tượng Mạng căn bản Thiết kế hướng đối tượng Kỹ năng tin học Cấu trúc dữ liệu và thuật giải văn phòng Kỹ năng Ứng dụng CNTT Giao tiếp trong doanh nghiệp Cơ sở dữ liệu Kỹ năng Công nghệ Internet & WWW phần mềm Cơ sở toán học Công nghệ Đa Thiết kế Web Quản lý dự án phương tiện phần mềm Lập trình Visual Basic Hệ cơ sở dữ Hướng dẫn đồ Môi trường PT liệu án tốt nghiệp Phần mềm Anh văn Phần cứng An toàn Thi cho tin học máy tính lao động tốt nghiệp Lập trình hướng đối tượng là một môn học cơ bản của công nhân lành nghề. Để học tốt mon học này, học viên cần phải học qua môn lập trình căn bản. Những học viên qua kiểm tra và thi mà không đạt phải thu xếp cho học lại những phần chưa đạt ngay và phải đạt điểm chuẩn mới được phép học tiếp các mô đun/ môn học tiếp theo. Học viên, khi chuyển trường, chuyển ngành.nếu đã học ở một cơ sở đào tạo khác rồi thì phải xuất trình giấy chứng nhận; Trong một số trường hợp có thể vẫn phải qua sát hạch lại. Trang 8
  9. CÁC HÌNH THỨC HỌC TẬP CHÍNH TRONG MÔN HỌC 1 - Học trên lớp về : - Tư tưởng lập trình theo hướng đối tượng trên ngôn ngữ lập trình C++. - Cú pháp của ngôn ngữ C++ trong lập trình xây dựng hướng đối tượng. - Phương pháp thiết kế và xây dựng lớp trong phương pháp lập trình hướng đối tượng. - Phương pháp kế thừa. 2 - Học tại phòng học thực hành trường về: - Sử dụng phần mềm Turbo C++. - Sử dụng ngôn ngữ C++ xây dựng lớp cơ sở và các lớp dẫn xuất. - Sử dụng công cụ Rational Rose thiết kế đối tượng và sinh mã cho đối tượng. - Xây dựng một ứng dụng thực tiễn trong phương pháp lập trình hướng đối tượng. YÊU CẦU VỀ ĐÁNH GIÁ HOÀN THÀNH MÔN HỌC Kỹ năng thực hành: - Sử dụng thành thạo phần mềm hỗ trợ thiết kế - Lập tài liệu phân tích thiết kế - Kết hợp thành thạo công cụ Rational Rose trong lập trình hướng đối tượng. Thái độ học viên: - Cẩn thận lắng nghe ý kiến và thảo luận trong nhóm thiết kế - Học viên cần tuân thủ các bài tập thực hành theo thứ tự các chương, từ dễ đến khó. Đánh giá thông qua kiểm tra trắc nghiệm: - Dùng phần mềm thi trắc nghiệm. - Kiểm tra trắc nghiệm có thể trên giấy hoặc trên máy tính. - Xây dựng ngân hàng câu hỏi, học viên sẽ nhận được một bộ để phát sinh ngẫu nhiên và chất lượng các đề như nhau (trung bình, khá, giỏi, xuất sắc). - Thời gian làm bài tuỳ theo số lượng các câu trong đề. - Thang điểm 10 chia đều cho các câu. - Kết quả đánh giá dựa vào bài làm theo điểm đạt được. Thực hành: Đánh giá thông qua khả năng giải hoàn thành chương trình (đề kiểm tra) đề ra. Thang điểm: (đánh giá câu hỏi trắc nghiệm) 0-49 : Không đạt 50-69 : Đạt trung bình 70-85 : Đạt khá 86-100 : Đạt Giỏi BÀI 1 PHƯƠNG PHÁP HƯỚNG ĐỐI TƯỢNG 9
  10. Mã bài : ITPRG02.1 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 bao gồm: tính đóng gói, tính đa hình và sự đa kế thừa mà chúng ta sẽ tìm hiểu trong chương này. Mục tiêu thực hiện: Học xong bài này học viên sẽ có khả năng: - Diễn đạt được phương pháp hướng đối tượng - Cài đặt được chương trình theo phương pháp lập trình hướng đối tượng - Phân tích để xây dựng được một đối tượng và lớp đối tượng - Xử lý được các tình huống thừa kế. Nội dung chính: 1.1 Các phương pháp lập trình 1.2 Các đặc điểm lập trình hướng đối tượng Tính đóng gói (encapsulation ) Tính kế thừa (inheritance) Tính tương ứng bội (polymorpharism ) 1.3 Xây dựng lớp đối tượng 1.1 Các phương pháp lập trình Hiện nay hai phương pháp lập trình được sử dụng rộng rãi nhất là phương pháp lập trình thủ tục và phương pháp lập trình hướng đối tượng. + Phương pháp lập trình thủ tục là phương pháp truyền thống, thể hiện tính cấu trúc của chương trình. + Phương pháp lập trình hướng đối tượng là phương pháp lập trình mới, dựa trên các phương thức và sự kiện của từng đối tượng mà chúng ta sẽ tìm hiểu dưới đây. 1.1.1. Tiếp cận hướng đối tượng Trong thế giới thực, chung quanh chúng ta là những đối tượng, đó là các thực thể có mối quan hệ với nhau. Ví dụ các phòng trong một công ty kinh doanh được xem như những đối tượng. Các phòng ở đây có thể là: phòng quản lý, phòng bán hàng, phòng kế toán, phòng tiếp thị, Mỗi phòng ngoài những cán bộ đảm nhiệm những công việc cụ thể, còn có những dữ liệu riêng như thông tin về nhân viên, doanh số bán hàng, hoặc các dữ liệu khác có liên quan đến bộ phận đó. Việc phân chia các phòng chức năng trong công ty sẽ tạo điều kiện dễ dàng cho việc quản lý 10
  11. các hoạt động. Mỗi nhân viên trong phòng sẽ điều khiển và xử lý dữ liệu của phòng đó. Ví dụ phòng kế toán phụ trách về lương bổng nhân viên trong công ty. Nếu bạn đang ở bộ phận tiếp thị và cần tìm thông tin chi tiết về lương của đơn vị mình thì sẽ gởi yêu cầu về phòng kế toán. Với cách làm này bạn được đảm bảo là chỉ có nhân viên của bộ phận kế toán được quyền truy cập dữ liệu và cung cấp thông tin cho bạn. Điều này cũng cho thấy rằng, không có người nào thuộc bộ phận khác có thể truy cập và thay đổi dữ liệu của bộ phận kế toán. Khái niệm như thế về đối tượng hầu như có thể được mở rộng đối với mọi lĩnh vực trong đời sống xã hội và hơn nữa - đối với việc tổ chức chương trình. Mọi ứng dụng có thể được định nghĩa như một tập các thực thể - hoặc các đối tượng, sao cho quá trình tái tạo những suy nghĩa của chúng ta là gần sát nhất về thế giới thực. Trong phần tiếp theo chúng ta sẽ xem xét phương pháp lập trình truyền thống để từ đó thấy rằng vì sao chúng ta cần chuyển sang phương pháp lập trình hướng đối tượng. 1.1.2. Những nhược điểm của lập trình hướng thủ tục Cách tiếp cận lập trình truyền thống là lập trình hướng thủ tục (LTHTT). Theo cách tiếp cận này thì một hệ thống phần mềm được xem như là dãy các công việc cần thực hiện như đọc dữ liệu, tính toán, xử lý, lập báo cáo và in ấn kết quả v.v Mỗi công việc đó sẽ được thực hiện bởi một số hàm nhất định. Như vậy trọng tâm của cách tiếp cận này là các hàm chức năng. LTHTT sử dụng kỹ thuật phân rã hàm chức năng theo cách tiếp cận trên xuống (top-down) để tạo ra cấu trúc phân cấp. Các ngôn ngữ lập trình bậc cao như COBOL, FORTRAN, PASCAL, C, v.v , là những ngôn ngữ lập trình hướng thủ tục. Những nhược điểm chính của LTHTT là:  Chương trình khó kiểm soát và khó khăn trong việc bổ sung, nâng cấp chương trình. Chương trình được xây dựng theo cách TCHTT thực chất là danh sách các câu lệnh mà theo đó máy tính cần thực hiện. Danh sách các lệnh đó được tổ chức thành từng nhóm theo đơn vị cấu trúc của ngôn ngữ lập trình và được gọi là hàm/thủ tục. Trong chương trình có nhiều hàm/thủ tục, thường thì có nhiều thành phần dữ liệu quan trọng sẽ được khai báo tổng thể (global) để các hàm/thủ tục có thể truy nhập, đọc và làm thay đổi giá trị của biến tổng thể. Điều này sẽ làm cho chương trình rất khó kiểm soát, nhất là đối với các chương trình lớn, phức tạp thì vấn đề càng trở nên khó khăn hơn. Khi ta muốn thay đổi, bổ sung cấu trúc dữ liệu dùng chung cho một số hàm/thủ tục thì phải thay đổi hầu như tất cả các hàm/thủ tục liên quan đến dữ liệu đó.  Mô hình được xây dựng theo cách tiếp cận hướng thủ tục không mô tả được đầy đủ, trong thực hệ thống trong thực tế.  Phương pháp TCHTT đặt trọng tâm vào hàm là hướng tới hoạt động sẽ không thực sự tương ứng với các thực thể trong hệ thống của thế giới thực. 11
  12. 1.1.3. Lập trình hướng đối tượng Lập trình hướng đối tượng (Object Oriented Programming - LTHĐT) là phương pháp lập trình lấy đối tượng làm nền tảng để xây dựng thuật giải, xây dựng chương trình. Đối tượng được xây dựng trên cơ sở gắn cấu trúc dữ liệu với các phương thức (các hàm/thủ tục) sẽ thể hiện được đúng cách mà chúng ta suy nghĩ, bao quát về thế giới thực. LTHĐT cho phép ta kết hợp những tri thức bao quát về các quá trình với những khái niệm trừu tượng được sử dụng trong máy tính. Điểm căn bản của phương pháp LTHĐT là thiết kế chương trình xoay quanh dữ liệu của hệ thống. Nghĩa là các thao tác xử lý của hệ thống được gắn liền với dữ liệu và như vậy khi có sự thay đổi của cấu trúc dữ liệu thì chỉ ảnh hưởng đến một số ít các phương thức xử lý liên quan. LTHĐT không cho phép dữ liệu chuyển động tự do trong hệ thống. Dữ liệu được gắn chặt với từng phương thức thành các vùng riêng mà các phương thức đó tác động lên và nó được bảo vệ để cấm việc truy nhập tùy tiện từ bên ngoài. LTHĐT cho phép phân tích bài toán thành tập các thực thể được gọi là các đối tượng và sau đó xây dựng các dữ liệu cùng với các phương thức xung quanh các đối tượng đó. Tóm lại LTHĐT có những đặc tính chủ yếu như sau: 1. Tập trung vào dữ liệu thay cho các phương thức. 2. Chương trình được chia thành các lớp đối tượng. 3. Các cấu trúc dữ liệu được thiết kế sao cho đặc tả được các đối tượng. 4. Các phương thức xác định trên các vùng dữ liệu của đối tượng được gắn với nhau trên cấu trúc dữ liệu đó. 5. Dữ liệu được bao bọc, che dấu và không cho phép các thành phần bên ngoài truy nhập tự do. 6. Các đối tượng trao đổi với nhau thông qua các phương thức. 7. Dữ liệu và các phương thức mới có thể dễ dàng bổ sung vào đối tượng nào đó khi cần thiết. 8. Chương trình được thiết kế theo cách tiếp cận bottom-up (dưới -lên). 1.2 Các đặc điểm lập trình hướng đối tượng 1.2.1 Các khái niệm cơ bản của lập trình hướng đối tượng Những khái niệm cơ bản trong LTHĐT bao gồm: Đối tượng; Lớp; Trừu tượng hóa dữ liệu, bao gói thông tin; Kế thừa; Tương ứng bội; Liên kết động; Truyền thông báo Đối tượng Trong thế giới thực, khái niệm đối tượng được hiểu như là một thực thể, nó có thể là người, vật hoặc một bảng dữ liệu cần xử lý trong chương trình, Trong LTHĐT thì đối tượng là biến thể hiện của lớp. 12
  13. Lớp Lớp là một khái niệm mới trong LTHĐT so với kỹ thuật LTHTT. Nó là một bản mẫu mô tả các thông tin cấu trúc dữ liệu và các thao tác hợp lệ của các phần tử dữ liệu. Khi một phần tử dữ liệu được khai báo là phần tử của một lớp thì nó được gọi là đối tượng. Các hàm được định nghĩa hợp lệ trong một lớp được gọi là các phương thức (method) và chúng là các hàm duy nhất có thể xử lý dữ liệu của các đối tượng của lớp đó. Mỗi đối tượng có riêng cho mình một bản sao các phần tử dữ liệu của lớp. Mỗi lớp bao gồm: danh sách các thuộc tính (attribute) và danh sách các phương thức để xử lý các thuộc tính đó. Công thức phản ánh bản chất của kỹ thuật LTHĐT là: Đối tượng = Dữ liệu + Phương thức Chẳng hạn, chúng ta xét lớp HINH_CN bao gồm các thuộc tính: (x1,y1) toạ độ góc trên bên trái, d,r là chiều dài và chiều rộng của HCN. Các phương thức nhập số liệu cho HCN, hàm tính diện tích, chu vi và hàm hiển thị. Lớp HINH_CN có thể được mô tả như sau: HINH_CN Thuộc tính : x1,y1 d,r Phương thức : Nhập_sl Diện tích Chu vi Hiển thị Chú ý: Trong LTHĐT thì lớp là khái niệm tĩnh, có thể nhận biết ngay từ văn bản chương trình, ngược lại đối tượng là khái niệm động, nó được xác định trong bộ nhớ của máy tính, nơi đối tượng chiếm một vùng bộ nhớ lúc thực hiện chương trình. Đối tượng được tạo ra để xử lý thông tin, thực hiện nhiệm vụ được thiết kế, sau đó bị hủy bỏ khi đối tượng đó hết vai trò. 1.2.2 Trừu tượng hóa dữ liệu và bao gói thông tin Trừu tượng hóa là cách biểu diễn những đặc tính chính và bỏ qua những chi tiết vụn vặt hoặc những giải thích. Khi xây dựng các lớp, ta phải sử dụng khái niệm trừu tượng hóa. Ví dụ ta có thể định nghĩa một lớp để mô tả các đối tượng trong không gian hình học bao gồm các thuộc tính trừu tượng như là kích thước, hình dáng, màu sắc và các phương thức xác định trên các thuộc tính này. Việc đóng gói dữ liệu và các phương thức vào một đơn vị cấu trúc lớp được xem như một nguyên tắc bao gói thông tin. Dữ liệu được tổ chức sao cho thế giới bên ngoài (các đối tượng ở lớp khác) không truy nhập vào, mà chỉ cho phép các 13
  14. phương thức trong cùng lớp hoặc trong những lớp có quan hệ kế thừa với nhau mới được quyền truy nhập. Chính các phương thức của lớp sẽ đóng vai trò như là giao diện giữa dữ liệu của đối tượng và phần còn lại của chương trình. Nguyên tắc bao gói dữ liệu để ngăn cấm sự truy nhập trực tiếp trong lập trình được gọi là sự che giấu thông tin. 1.2.3 Kế thừa Kế thừa là quá trình mà các đối tượng của lớp này được quyền sử dụng một số tính chất của các đối tượng của lớp khác. Sự kế thừa cho phép ta định nghĩa một lớp mới trên cơ sở các lớp đã tồn tại. Lớp mới này, ngoài những thành phần được kế thừa, sẽ có thêm những thuộc tính và các hàm mới. Nguyên lý kế thừa hỗ trợ cho việc tạo ra cấu trúc phân cấp các lớp. 1.2.4 Tương ứng bội Tương ứng bội là khả năng của một khái niệm (chẳng hạn các phép toán) có thể sử dụng với nhiều chức năng khác nhau. Ví dụ, phép + có thể biểu diễn cho phép “cộng” các số nguyên (int), số thực (float), số phức (complex) hoặc xâu ký tự (string) v.v Hành vi của phép toán tương ứng bội phụ thuộc vào kiểu dữ liệu mà nó sử dụng để xử lý. Tương ứng bội đóng vai quan trọng trong việc tạo ra các đối tượng có cấu trúc bên trong khác nhau nhưng cùng dùng chung một giao diện bên ngoài (như tên gọi). 1.2.5 Liên kết động Liên kết động là dạng liên kết các thủ tục và hàm khi chương trình thực hiện lời gọi tới các hàm, thủ tục đó. Như vậy trong liên kết động, nội dung của đoạn chương trình ứng với thủ tục, hàm sẽ không được biết cho đến khi thực hiện lời gọi tới thủ tục, hàm đó. 1.2.6 Truyền thông báo Các đối tượng gửi và nhận thông tin với nhau giống như con người trao đổi với nhau. Chính nguyên lý trao đổi thông tin bằng cách truyền thông báo cho phép ta dễ dàng xây dựng được hệ thống mô phỏng gần hơn những hệ thống trong thế giới thực. Truyền thông báo cho một đối tượng là yêu cầu đối tượng thực hiện một việc gì đó. Cách ứng xử của đối tượng được mô tả bên trong lớp thông qua các phương thức. Trong chương trình, thông báo gửi đến cho một đối tượng chính là yêu cầu thực hiện một công việc cụ thể, nghĩa là sử dụng những hàm tương ứng để xử lý dữ liệu được khai báo trong đối tượng đó. Vì vậy, trong thông báo phải chỉ ra được hàm cần thực hiện trong đối tượng nhận thông báo. Thông báo truyền đi cũng phải xác định tên đối tượng và thông tin truyền đi. Ví dụ, lớp CONGNHAN có thể hiện là đối tượng cụ thể được đại diện bởi Hoten nhận được thông báo cần tính lương thông 14
  15. qua hàm TINHLUONG được xác định trong lớp CONGNHAN. Thông báo đó sẽ được xử lý như sau: CONGNHAN.TINHLUONG (Hoten) §èi t•îng Th«ng b¸o Th«ng tin Trong chương trình hướng đối tượng, mỗi đối tượng chỉ tồn tại trong thời gian nhất định. Đối tượng được tạo ra khi nó được khai báo và sẽ bị hủy bỏ khi chương trình ra khỏi miền xác định của đối tượng đó. Sự trao đổi thông tin chỉ có thể thực hiện trong thời gian đối tượng tồn tại. 1.3. Xây dựng lớp đối tượng Chương trình theo hướng đối tượng bao gồm một tập các đối tượng và mối quan hệ giữa các đối tượng với nhau. Vì vậy, lập trình trong ngôn ngữ hướng đối tượng bao gồm các bước sau: 1. Xác định các dạng đối tượng (lớp) của bài toán. 2. Tìm kiếm các đặc tính chung (dữ liệu chung) trong các dạng đối tượng này, những gì chúng cùng nhau chia xẻ. 3. Xác định lớp cơ sở dựa trên cơ sở các đặc tính chung của các dạng đối tượng. 4. Từ lớp cơ sở, xây dựng các lớp dẫn xuất chứa các thành phần, những đặc tính không chung còn lại của các dạng đối tượng. Ngoài ra, ta còn đưa ra các lớp có quan hệ với các lớp cơ sở và lớp dẫn xuất. Bài tập: Bài 1: Sử dụng công cụ Rational Rose xây dựng một lớp ConNguoi (con người) và các lớp dẫn xuất CaSi (ca sĩ), CauThu (cầu thủ) kế thừa lớp ConNguoi. Bài 2: Xây dựng lớp Diem (điểm) gồm 2 thuộc tính X và Y, xây dựng lớp dẫn xuất Diem3D với các thuộc tính X, Y, Z và lớp dẫn xuất Diem3Dmau (điểm 3D màu) với các thuộc tính X, Y, Z và Mau (màu sắc). BÀI 2 CÁC THÀNH PHẦN CỦA LỚP Mã bài : ITPRG02.2 Giới thiệu : 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. Ngoài các thành phần dữ liệu, lớp còn chứa các thành phần hàm, còn gọi là phương thức (method) hoặc hàm thành viên (member function). Lớp có thể xem như một kiểu dữ liệu các biến, mảng đối tượng. Từ một lớp đã định nghĩa, có thể tạo ra nhiều đối tượng khác nhau, mỗi đối tượng có vùng nhớ riêng. 15
  16. Bài này sẽ trình bày cách định nghĩa lớp, cách xây dựng 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 phương thức. Mục tiêu thực hiện: Học xong bài này học viên sẽ có khả năng: - Định nghĩa được một lớp cơ bản - Cài đặt được hàm constructor và destrutor - Truyền được tham số cho hàm constructor - Cài đặt được hàm in-line - Vận dụng được với thành phần Static của lớp - Khai báo và sử dụng được hàm friend Nội dung chính: 2.1 Khai báo một lớp cơ sở 2.2 Hàm constructor và destructor 2.3 Hàm constructor có tham số 2.4 Hàm in-line 2.5 Thành phần của lớp là static 2.6 Hàm friend 2.1 Khai báo một lớp cơ sở Lớp thường được khai báo ở đầu chương trình (trước hàm main) trước khi sử dụng chúng. Cú pháp để định nghĩa một lớp được trình bày như sau: class { private: [các trường, các hàm dùng riêng cho lớp này] protected: [các trường, các hàm dùng cho lớp này và các lớp con cháu] public: [các trường, các hàm dùng chung cho mọi lớp] }; Ví dụ: lớp con người sau được định nghĩa với một trường số chứng minh nhân dân kiểu private và một trường họ tên kiểu public. class CONNGUOI { private: string soCMND; public: string hoten; 16
  17. }; 2.2 Hàm constructor và destructor 2.2.1 Hàm tạo sao chép (constructor) Giả sử đã định nghĩa một lớp ABC nào đó. Khi đó: - Ta có thể dùng câu lệnh khai báo hoặc cấp phát bộ nhớ để tạo các đối tượng mới, ví dụ: ABC p1, p2; ABC *p = new ABC; - Ta cũng có thể dùng lệnh khai báo để tạo một đối tượng mới từ một đối tượng đã tồn tại, ví dụ: ABC u; ABC v(u); // Tạo v theo u Câu lệnh này có ý nghĩa như sau: - Nếu trong lớp ABC chưa xây dựng hàm tạo sao chép, thì câu lệnh này sẽ gọi tới một hàm tạo sao chép mặc định của C++. Hàm này sẽ sao chép nội dung từng bit của u vào các bit tương ứng của v. Như vậy các vùng nhớ của u và v sẽ có nội dung như nhau. - Nếu trong lớp ABC đã có hàm tạo sao chép thì câu lệnh: PS v(u); sẽ tạo ra đối tượng mới v, sau đó gọi tới hàm tạo sao chép để khởi gán v theo u. Ví dụ sau minh họa cách dùng hàm tạo sao chép mặc định: Trong chương trình đưa vào lớp PS (phân số): + Các thuộc tính gồm: t (tử số) và m (mẫu). + Trong lớp mà không có phương thức nào cả mà chỉ có hai hàm bạn là các hàm toán tử nhập (>>) và xuất ( #include class PS { private: int t,m; public: friend ostream& operator<< (ostream& os,const PS &p) { os<<" = "<<p.t<<"/"<<p.m; return os; } 17
  18. friend istream& operator>> (istream& is, PS &p) { cout >p.t>>p.m; return is; } }; void main() { PS d; cout >d; cout<<"\n PS d "<<d; PS u(d); cout<<"\n PS u "<<u; getch(); } 2.2.2 Hàm hủy (destructor) Hàm huỷ là một hàm thành phần của lớp, có chức năng ngược với hàm tạo. Hàm huỷ được gọi trước khi giải phóng một đối tượng để thực hiện một số công việc có tính “dọn dẹp” trước khi đối tượng được hủy bỏ, ví dụ giải phóng một vùng nhớ mà đối tượng đang quản lý, xoá đối tượng khỏi màn hình nếu như nó đang hiển thị Việc hủy bỏ đối tượng thường xảy ra trong 2 trường hợp sau: - Trong các toán tử và hàm giải phóng bộ nhớ như delete, free - Giải phóng các biến, mảng cục bộ khi thoát khỏi hàm, phương thức. 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à đủ, không cần đưa vào một hàm huỷ mới. Trường hợp cần xây dựng hàm hủy thì tuân theo quy tắc sau: - Mỗi lớp chỉ có một hàm hủy. - Hàm hủy không có kiểu, không có giá trị trả về và không có tham số. - Tên hàm hàm huỷ có một dấu ngã ngay trước tên lớp. Ví dụ: class A { private: int n; double *a; public: ~A() { n = 0; 18
  19. delete a; } }; Ví dụ 2.2 #include class Count{ private: static int counter; int obj_id; public: Count(); static void display_total(); void display(); ~Count(); }; int Count::counter; Count::Count() { counter++; obj_id = counter; } Count::~Count() { counter ; cout<<"Doi tuong "<<obj_id<<" duoc huy bo\n"; } void Count::display_total() { cout <<"So cac doi tuong duoc tao ra la = "<< counter << endl; } void Count::display() { cout << "Object ID la "<<obj_id<<endl; } void main() { Count a1; Count::display_total(); Count a2, a3; Count::display_total(); 19
  20. a1.display(); a2.display(); a3.display(); } Kết quả chương trình như sau: So cac doi tuong duoc tao ra la = 1 So cac doi tuong duoc tao ra la = 3 Object ID la 1 Object ID la 2 Object ID la 3 Doi tuong 3 duoc huy bo Doi tuong 2 duoc huy bo Doi tuong 1 duoc huy bo 2.3 Hàm tạo sao chép có tham số Hàm tạo sao chép sử dụng một đối kiểu tham chiếu đối tượng để khởi gán cho đối tượng mới và được viết theo mẫu sau: Tên_lớp (const Tên_lớp &ob) { // Các câu lệnh dùng các thuộc tính của đối tượng ob để khởi gán // cho các thuộc tính của đối tượng mới } Ví dụ: class PS { private: int t, m; public: PS(const PS &p) { t= p.t; m= p.m; } }; Hàm tạo sao chép trong ví dụ trên không khác gì hàm tạo sao chép mặc định. Chú ý: - Nếu lớp không có các thuộc tính kiểu con trỏ hoặc tham chiếu thì dùng hàm tạo sao chép mặc định là đủ. 20
  21. - Nếu lớp có các thuộc tính con trỏ hoặc tham chiếu, thì hàm tạo sao chép mặc định chưa đáp ứng được yêu cầu. class DT { private: int n; // Bac da thuc double *a; // Tro toi vung nho chua cac he so da thuc a0, a1, public: DT() { n = 0; a = NULL; } DT(int n1) { n = n1; a = new double[n1+1]; } friend ostream& operator > (istream& is,DT &d); }; Bây giờ chúng ta hãy theo dõi xem việc dùng hàm tạo mặc định trong đoạn chương trình sau sẽ dẫn đến sai lầm như thế nào: DT d; cin >> d; /* Nhập đối tượng d gồm: nhập một số nguyên dương và gán cho d.n, cấp phát vùng nhớ cho d.n, nhập các hệ số của đa thức và chứa vào vùng nhớ được cấp phát */ DT u(d); /* Dùng hàm tạo mặc định để xây dựng đối tượng u theo d. Kết quả: u.n = d.n và u.a = d.a. Như vậy hai con trỏ u.a và d.a cùng trỏ đến một vùng nhớ. */ Nhận xét: Mục đích của ta là tạo ra một đối tượng u giống như d, nhưng độc lập với d. Nghĩa là khi d thay đổi thì u không bị ảnh hưởng gì. Thế nhưng mục tiêu này không đạt được, vì u và d có chung một vùng nhớ chứa hệ số của đa thức, nên khi sửa đổi các hệ số của đa thức trong d thì các hệ số của đa thức trong u cũng thay đổi theo. Còn một trường hợp nữa cũng dẫn đến lỗi là khi một trong hai đối tượng u và d bị giải phóng (thu hồi vùng nhớ chứa đa thức) thì đối tượng còn lại cũng sẽ không còn vùng nhớ nữa. 21
  22. Ví dụ sau sẽ minh họa nhận xét trên: Khi d thay đổi thì u cũng thay đổi và ngược lại khi u thay đổi thì d cũng thay đổi theo. Ví dụ 2.3 #include #include #include class DT { private: int n; // Bac da thuc double *a; // Tro toi vung nho chua cac he //so da thuc a0,a1, public: DT() { this->n=0; this->a=NULL; } DT(int n1) { this->n=n1; this->a= new double[n1+1]; } friend ostream& operator > (istream& is,DT &d); }; ostream& operator > (istream& is,DT &d) { if (d.a != NULL) delete d.a; cout >d.n; d.a = new double[d.n+1]; cout<<"Nhap cac he so da thuc:\n"; for (int i=0 ; i<=d.n ; ++i) 22
  23. { cout > d.a[i]; } return is; } void main() { DT d; clrscr(); cout > d; DT u(d); cout > d; cout > u; cout #include #include class DT { private: int n; // Bac da thuc double *a; // Tro toi vung nho chua cac da thuc // a0, a1, public: DT() { this->n=0; this->a=NULL; } DT(int n1) { this->n=n1; 23
  24. this->a= new double[n1+1]; } DT(const DT &d); friend ostream& operator > (istream& is,DT &d); }; DT::DT(const DT &d) { this->n = d.n; this->a = new double[d.n+1]; for (int i=0;i a[i] = d.a[i]; } ostream& operator > (istream& is,DT &d) { if (d.a != NULL) delete d.a; cout > d.n; d.a = new double[d.n+1]; cout > d.a[i]; } return is; } void main() { DT d; clrscr(); cout > d; DT u(d); 24
  25. cout > d; cout > u; cout #include void main() { int s ; s=f(5,6); cout<<s; getch(); } inline int f(int a,int b) { return a*b; } 25
  26. Chú ý:  Chương trình dịch các hàm inline 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 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 trình dịch chứ không phải là một mệnh lệnh bắt buộc. Ví dụ 2.6 Chương trình sau sử dụng hàm inline để tính chu vi và diện tích hình chữ nhật. #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=0;i >a[i]>>b[i]; dtcvhcn(a[i],b[i],dt[i],cv[i]); } clrscr(); for(i=0;i<n;++i) { cout<<"\n Hinh chu nhat thu "<<i+1<<":"; cout<<"\n Do dai hai canh "<<a[i]<<"va"<<b[i]; cout<<"\n dien tich "<<dt[i]; cout<<"\n chu vi "<<cv[i]; } getch(); } 26
  27. Ví dụ 2.7 Một cách viết khác của chương trình trong ví dụ 2.6 #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=0;i >a[i]>>b[i]; dtcvhcn(a[i],b[i],dt[i],cv[i]); } clrscr(); for(i=0;i<n;++i) { cout<<"\n Hinh chu nhat thu "<<i+1<<":"; cout<<"\n Do dai hai canh "<<a[i]<<"va"<<b[i]; cout<<"\n dien tich "<<dt[i]; cout<<"\n chu vi "<<cv[i]; } getch(); } void dtcvhcn(int a,int b,int &dt ,int &cv) { dt=a*b; cv=2*(a+b); } 2.5 Thành phần của lớp là static 2.5.1. Dữ liệu thành phần tĩnh Dữ liệu thành phần tĩnh được khai báo bằng từ khoá static và được cấp phát một vùng nhớ cố định, nó tồn tại ngay cả khi lớp chưa có một đối tượng nào cả. Dữ liệu thành phần tĩnh là chung cho cả lớp, nó không phải là riêng của mỗi đối tượng, ví dụ: class A { private: 27
  28. static int ts; // Thành phần tĩnh int x; }; A u, v; // Khai báo 2 đối tượng 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, trong khi 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ụ: A::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ị đầu bằng một câu lệnh khai báo đặt sau định nghĩa lớp theo mẫu như sau: 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. Hãy xem chương trình sau: Ví dụ 2.8 #include #include class HDBH { private: char *tenhang; double tienban; static int tshd; static double tstienban; public: static void in() { cout <<”\n” << tshd; cout <<”\n” << tstienban; } }; void main () { HDBH::in(); getch(); } Các thành phần tĩnh tshd và tstienban chưa khai báo, nên chưa tồn tại. Vì vậy các câu lệnh in giá trị các thành phần này trong hàm in() là không thể được. Khi dịch 28
  29. chương trình, sẽ nhận được các thông báo lỗi. Có thể sửa chương trình trên bằng cách đưa vào các lệnh khai báo các thành phần tĩnh tshd và tstienban như sau: Ví dụ 2.9 #include #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(); } 2.5.2 Hàm thành phần tĩnh Hàm thành phần tĩnh được viết theo một trong hai cách: - Dùng từ khoá static đặt trước định nghĩa hàm thành phần viết bên trong định nghĩa lớp. - Nếu hàm thành phần 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 hàm thành phần bên trong định nghĩa lớp. Không cho phép dùng từ khoá static đặt trước định nghĩa hàm thành phần viết bên ngoài định nghĩa lớp. Các đặc tính của hàm thành phần tĩnh: - Hàm thành phần 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 hàm thành phần tĩnh như sau: Tên lớp :: Tên hàm thành phần tĩnh(các tham số thực sự) 29
  30. - Vì hàm thành phần 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. Nói cách khác 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. Đoạn chương trình sau minh họa điều này: 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; 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 30
  31. 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) Trong thực tế thường xãy ra trường hợp có một số lớp cần sử dụng chung một hàm. C++ giải quyết vấn đề này bằng cách dùng 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 { 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 ( ) 31
  32. { } 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 void f1 ( ) { } double f2 ( ) { } } ; Hàm bạn có những tính chất sau: - 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 B ; // Khai báo trước lớp B class C ; // Khai báo trước lớp C // Định nghĩa lớp A class A { // Khai báo f là bạn của A 32
  33. friend void f( ) } ; // Định nghĩa lớp B 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ụ 2.11 #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(); 33
  34. sophuc d1 (2.1,3.4); sophuc d2 (1.2,2.3) ; 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) { int t = x.v1; 34
  35. x.v1 = y.v2; y.v2 = t; } void main() { clrscr(); LOP1 ob1; LOP2 ob2; ob1.nhap(150); ob2.nhap(200); cout << "Gia tri ban dau :" << "\n"; ob1.hienthi(); ob2.hienthi(); traodoi(ob1, ob2); //Thuc hien hoan doi cout << "Gia tri sau khi thay doi:" << "\n"; ob1.hienthi(); ob2.hienthi(); getch(); } Chương trình cho kết quả như sau: Gia tri ban dau : 150 200 Gia tri sau khi thay doi: 200 150 Bài tập: 1. 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. 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 khong 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. 2. 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. 35
  36. 3. Thực hiện một 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. 4. 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, cộng, trừ, nhân hai ma trận. 5. Xây dựng lớp ma trận có tên là Matrix cho các ma trận vuông, 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, tính định thức và tính ma trận nghịch đảo. 6. Xây dựng lớp Stack cho ngăn xếp kiểu int. Các hàm thành phần bao gồm: Hàm tạo mặc định, hàm hủy, hàm isEmpty() kiểm tra stack có rỗng không, hàm isFull() kiểm tra stack có đầy không, hàm push() , pop(), hàm in nội dung ngăn xếp. Sử dụng một mảng để thực hiện. 7. Xây dựng lớp hàng đợi Queue chứa phần tử kiểu int. Các hàm thành phần bao gồm: hàm tạo, hàm hủy và những toán tử hàng đợi thông thường: hàm insert() để thêm phần tử vào hàng đợi, hàm remove() để loại bỏ phần tử, hàm isEmpty() kiểm tra hàng đợi có rỗng không, hàm isFull() kiểm tra hàng đợi có đầy không. Sử dụng một mảng để thực hiện. 4. Xây dựng lớp đa thức và các phương thức cộng, trừ hai đa thức. 8. 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ố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. 9. Xây dựng lớp vector để lưu trữ các vectơ gồm các số thực. Các thành phần dữ liệu bao gồm: - Kích thước vectơ. - Một mảng động chứa các thành phần của vectơ. Các hàm thành phần bao gồm hàm tạo, hàm hủy, hàm tính tích vô hướng hai vectơ, tính chuẩn của vectơ (theo chuẩn bất kỳ nào đó). 10. Xây dựng lớp Phanso với dữ liệu thành phần là tử và mẫu số. Các hàm thành phần bao gồm: - 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ả phải được tối giản - Chia hai phân số, kết quả phải được tối giản 36
  37. BÀI 3 ĐỐI TƯỢNG Mã bài : ITPRG02.3 Giới thiệu : Trong thế giới thực, khái niệm đối tượng được hiểu như là một thực thể, nó có thể là người, vật hoặc một bảng dữ liệu cần xử lý trong chương trình, Trong LTHĐT thì đối tượng là biến thể hiện của lớp. Mục tiêu thực hiện: Học xong bài này học viên sẽ có khả năng: - Khai báo và sử dụng được kiểu dữ liệu con trỏ - Truyền được tham số là đối tượng cho hàm - Định nghĩa được hàm có kiểu dữ liệu trả về là một đối tượng - Khai báo được mảng của các đối tượng - Vận dụng được con trỏ This - Sử dụng hiệu quả hàm new và delete Nội dung: 3.1 Kiểu dữ liệu con trỏ. 3.2. Đối tượng là một con trỏ 3.3. Phép gán một đối tượng 3.4. Truyền tham số là đối tượng cho hàm 3.5. Giá trị trả về của hàm là một đối tượng 3.6. Tham chiếu 3.7. Mảng của các đối tượng 3.8. Con trỏ This 3.9. Hàm new và delete 3.1 Biến con trỏ : 3.1.1 Khái niệm con trỏ ( pointer ) và địa chỉ : - Mỗi biến trong ngôn ngữ C++ đều có 1 tên và tương ứng với nó là một vùng nhớ dùng để chứa giá trị của nó. Tuỳ theo biến mà vùng nhớ dành cho biến có độ dài khác nhau. Ðịa chỉ của biến là sô thứ tự của byte đầu tiên tương ứng với biến đó. 37
  38. Ðịa chỉ của biến có kiểu khác nhau là khác nhau. Ðịa chỉ và biển kiểu int liên tiếp cách nhau 2 byte , biến kiểu float là 4 byte. - Con trỏ là biến dùng để chứa địa chỉ của biến khác hoặc có thể là một hàm. Do có nhiều loại địa chỉ nên cũng có nhiều loại biến con trỏ. Con trỏ kiểu int dùng để chứa địa chỉ của kiểu int. Con trỏ kiểu float dùng để chứa địa chỉ kiểu float. - Muốn sử dụng được pointer, trước tiên phải có được địa chỉ của biến mà ta cần quan tâm bằng phép toán lấy địa chỉ & . Kết quả của phép lấy địa chỉ & sẽ là 1 phần tử hằng. * Ví dụ : int num ; => &num là địa chỉ của num. int pnum ; // pnum là 1 pointer chỉ đến một int pnum = & num ; // pnum chứa địa chỉ biến int num giả sử : num = 5 ; => * pnum = 5 // do * là toán tử nội dung Hai câu lệnh sau đây là tương đương Num = 100 ; ( * pnum ) = 100 ; - Quy tắc khai báo biến con trỏ : * *Ví dụ 2 : int a, *p ; a = 5 ; // giả sử địa chỉ của a là p = & a ; // p = p = a ; // phép gán sai * p = a ; // phép gán đúng scanf ( " %d " , &a ) ; tương đương scanf ( " %d , p ) ; 3.1.2 Tính toán trên biến con trỏ ( pointer ) a. Hai biến con trỏ cùng kiểu có thể gán cho nhau : Ví dụ 1 : int a, * p, *a ; float * f; a = 5 ; p = &a ; q = p ; /* đúng */ f = p ; /* sai do khác kiểu */ f = ( float * )p ; /* đúng nhờ ép kiểu con trỏ nguyên về kiểu float */ Ví dụ 2 : int a ; char *c ; c = &a ; /* sai vì khác kiểu */ c = ( char*) /* đúng */ b. Một biến pointer có thể được cộng, trừ với một số nguyên ( int , long ) để cho kết quả là một pointer. * Ví dụ : int a , *p , * p10 ; a = 5 ; p = &a ; p10 = p + 10 ; Ví dụ : int V[10] ;/* mãng 10 phần tử */ int *p ; 38
  39. p = & V[0]; for ( i = 0 ; i thay bằng các lệnh : p = &a và scanf ( "%d", p ) ( đúng) 3.2. Đối tượng là một con trỏ Con trỏ đối tượng dùng để chứa địa chỉ của biến đối tượng, đượ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 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 Để 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ự) Nếu con trỏ chứa đầu địa chỉ của mảng, có thể dùng con trỏ như tên mảng. Ví dụ 3.1 #include #include class mhang { int maso; float gia; public: void getdata(int a, float b) {maso= a; gia= b;} 39
  40. void show() { cout >x>>y; p -> getdata(x,y); p++;} for (i = 0; i show(); d++; } getch(); } 3.3 Phép gán một đối tượng 3.3.1 Truy nhập tới các thành phần của lớp * Để truy nhập đến dữ liệu thành phần của lớp, ta dùng cú pháp: Tên_đối_tượng. Tên_thuộc_tính Cần chú ý rằng dữ liệu thành phần riêng chỉ có thể được truy nhập bởi những hàm thành phần của cùng một lớp, đối tượng của lớp cũng không thể truy nhập. * Để sử dụng các hàm thành phần của lớp, ta dùng cú pháp: Tên_đối_tượng. Tên_hàm (Các_khai_báo_tham_số_thực_sự) Ví dụ #include #include class DIEM { private : 40
  41. int x,y ; public : void nhapsl( ) { cout >x>>y ; } void hienthi( ) { cout #include class A { int m,n; public : void nhap( ) { cout >m>>n ; } int max() { return m>n?m:n; } void hienthi( ) { cout<<"\n Thanh phan du lieu lon nhat x = " <<max()<<endl;} }; void main () { clrscr(); A ob; ob.nhap(); 41
  42. ob.hienthi(); getch(); } Chú ý: Các hàm tự do có thể có các đối là đối tượng nhưng trong thân hàm không thể truy nhập đến các thuộc tính của lớp. Ví dụ gi sử đã định nghĩa lớp : class DIEM { private : double x,y ; // toa do cua diem public : void nhapsl() { cout > x>>y ; } void in() { cout #include class Box { private: int dai; int rong; int cao; public: 42
  43. int get_thetich(int lth,int wdth = 2,int ht = 3); }; int Box::get_thetich(int l, int w, int h) { dai = l; rong = w; cao = h; cout #include #include class phrase { private: char dongtu[10]; char danhtu[10]; char cumtu[25]; public: 43
  44. phrase(); inline void set_danhtu(char* in_danhtu); inline void set_dongtu(char* in_dongtu); inline char* get_phrase(void); }; void phrase::phrase() { strcpy(danhtu,""); strcpy(dongtu,""); strcpy(cumtu,""); } inline void phrase::set_danhtu(char* in_danhtu) { strcpy(danhtu, in_danhtu); } inline void phrase::set_dongtu(char* in_dongtu) { strcpy(dongtu, in_dongtu); } inline char* phrase::get_phrase(void) { strcpy(cumtu,dongtu); strcat(cumtu," the "); strcat(cumtu,danhtu); return cumtu; } void main() { phrase text; cout " " " " << 44
  45. text.get_phrase() the Cum tu la : -> the file Cum tu la : -> Save the file Cum tu la : -> Save the program Ví dụ 3.6 Ví dụ sau minh họa việc sử dụng từ khóa const trong lớp: #include #include class constants { private: int number; public: void print_it(const int data_value); }; void constants::print_it(const int data_value) { number = data_value; cout << number << "\n"; } void main() { constants num; const int START = 3; const int STOP = 6; int index; for (index=START; index<=STOP; index++) { cout<< "index = " ; num.print_it(index); cout<< "START = " ; num.print_it(START); } getch(); } Kết quả chương trình như sau: 45
  46. index = 3 START = 3 index = 4 START = 3 index = 5 START = 3 index = 6 START = 3 3.3.2 Phép gán một đối tượng Có thể thực hiện phép gán trên hai đối tượng cùng kiểu. Chẳng hạn với lớp DIEM có khai báo như sau: class DIEM { public: int x,y; }; 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 Có thể thực hiện các câu lệnh gán : d1 = d2 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 3.4 Truyền tham số là đối tượng cho hàm Các biến đối tượng có thể được truyền cho hàm như các biến thông thường, có nghĩa là chúng ta có thể truyền theo tham trị, tham biến cũng như chúng ta có thể truyền tham số là đối tượng cho hàm dưới dạng địa chỉ. Ví dụ: Truyền theo giá trị: void hienthi(DIEM d) { cout x y; } 46
  47. 3.5 Giá trị trả về của hàm là một đối tượng Khi đã định nghĩa một đối tượng, chúng ta có thể coi chúng như một kiểu dữ liệu. Vì vậy chúng ta có thể trả về kết quả cho hàm là một đối tượng. Ví dụ: class DIEM { public: int x,y; }; DIEM d_traidau(DIEM d) { DIEM d2; d2.x = -d.x; d2.y = -d.y; return d2; } 3.6. Tham chiếu Trong C++, các tham số và giá trị trả về của một hàm được truyền bằng giá trị. Để giả lập cơ chế truyền tham biến ta phải sử dụng con trỏ. Việc dùng khái niệm tham chiếu trong khai báo tham số hình thức của hàm sẽ yêu cầu chương trình biên dịch truyền địa chỉ của biến cho hàm và hàm sẽ thao tác trực tiếp trên các biến đó. Chúng ta sẽ xét ba cách viết khác nhau của hàm thực hiện việc hoán đổi nội dung của hai biến. void hoandoi1(int x, int y){ int tam = x; x = y; y = tam; } void hoandoi2(int *x, int *y){ int tam = *x; *x = *y; *y = tam; } void hoandoi3(int &x, int &y){ int tam = x; x = y; y = tam; } Trong hàm hoandoi1(), các tham số x, y được truyền theo tham trị nên giá trị của chúng không thay đổi trước và sau lời gọi hàm. Giải pháp đưa ra trong hàm hoandoi2() là thay vì truyền trực tiếp giá trị hai biến x và y người ta truyền địa chỉ của chúng rồi thông qua địa chỉ này để xác định giá trị của biến. Bằng cách đó giá trị của hai biến x và y sẽ hoán đổi cho nhau sau lời gọi hàm. Khác với hoandoi2(), hàm hoandoi3() đưa ra giải pháp sử dụng tham chiếu. Các tham số hình thức của hoandoi3() bây giờ là các tham chiếu đến các tham số thực được truyền cho hàm, nhờ vậy mà giá trị của hai tham số thực x và y có thể hoán đổi được cho nhau. 3.7. Mảng của các đối tượng 47
  48. Một mảng các đối tượng là một dãy các phần tử đối tượng được xếp cạnh nhau trong bộ nhớ và được khai báo với cú pháp như sau: [kích thước]; Ví dụ: class DIEM { public: int x,y; }; Dùng lớp DIEM, ta có thể khai báo: DIEM d [20] ; // Khai báo mảng đối tượng DIEM d2; Có thể thực hiện các câu lệnh gán : d[0].x = 100; d[0].y = 200; d[5] = d[0]; d2 = d[5]; //gán d2 bằng phần tử thứ 6 của mảng d 3.8 Con trỏ this Mỗi hàm thành phần của lớp có một tham số ẩn, đó là con trỏ this. Con trỏ this trỏ đến từng đối tượng cụ thể. Ta hãy xem lại hàm nhapsl() của lớp DIEM trong ví dụ ở trên: void nhapsl( ) { cout >x>>y; } Trong hàm này ta sử dụng tên các thuộc tính x,y một cách đơn độc. Điều này dường như mâu thuẩn với quy tắc sử dụng thuộc tính. Tuy nhiên nó được lý giải như sau: C++ sử dụng một con trỏ đặc biệt trong các hàm thành phần. Các thuộc tính viết trong hàm thành phần được hiểu là thuộc một đối tượng do con trỏ this trỏ tới. Như vậy hàm nhapsl() có thể viết một cách tường minh như sau: void nhapsl( ) { cout >this->x>>this->y ; } Con trỏ this là đối thứ nhất của hàm thành phần. Khi một lời gọi hàm thành phần được phát ra bởi một đối tượng thì tham số truyền cho con trỏ this chính là địa chỉ của đối tượng đó. 48
  49. Ví dụ: Xét một lời gọi tới hàm nhapsl() : DIEM d1 ; d1.nhapsl(); Trong trường hợp này của d1 thì this =&d1. Do đó this -> x chính là d1.x và this-> y chính là d1.y Chú ý: Ngoài tham số đặc biệt this không xuất hiện một cách tường minh, hàm thành phần lớp có thể có các tham số khác được khai báo như trong các hàm thông thường. Ví dụ: #include #include class time { int h,m; public : void nhap(int h1, int m1) { h= h1; m = m1;} void hienthi(void) { cout m = t1.m+ t2.m; h= m/60; //this->h = this->m/60; m= m%60; //this->m = this->m%60; h = h+t1.h+t2.h; //this->h = this->h + t1.h+t2.h; } void main() { clrscr(); time ob1, ob2,ob3; ob1.nhap(2,45); ob2.nhap(5,40); ob3.tong(ob1,ob2); cout <<"object 1 = "; ob1.hienthi(); cout <<"object 2 = "; ob2. hienthi(); cout <<"object 3 = "; ob3. hienthi(); getch(); } Chương trình cho kết quả như sau: 49
  50. object 1 = 2 gio 45 phut object 2 = 5 gio 40 phut object 3 = 8 gio 25 phut 3.9 Hàm new và delete Trong C có thể sử dụng các hàm cấp phát bộ nhớ như malloc(), calloc() và hàm free() để giải phóng bộ nhớ được cấp phát. C++ đưa thêm một cách thức mới để thực hiện việc cấp phát và giải phóng bộ nhớ bằng cách dùng hai toán tử new và delete. 3.9.1 Toán tử new để cấp phát bộ nhớ Toán tử new thay cho hàm malloc() và calloc() của C có cú pháp như sau: new Tên kiểu ; hoặc new (Tên kiểu); Trong đó Tên kiểu là kiểu dữ liệu của biến con trỏ, nó có thể là: các kiểu dữ liệu chuẩn như int, float, double, char, hoặc các kiểu do người lập trình định nghĩa như mảng, cấu trúc, lớp, Chú ý: Để cấp phát bộ nhớ cho mảng một chiều, dùng cú pháp như sau: Biến con trỏ = new kiểu[n]; Trong đó n là số nguyên dương xác định số phần tử của mảng. Ví dụ: float *p = new float; //cấp phát bộ nhớ cho biến con trỏ p có kiểu int int *a = new int[100]; //cấp phát bộ nhớ để lưu trữ mảng một chiều a // gồm 100 phần tử Khi sử dụng toán tử new để cấp phát bộ nhớ, nếu không đủ bộ nhớ để cấp phát, new sẽ trả lại giá trị NULL cho con trỏ. Đoạn chương trình sau minh họa cách kiểm tra lỗi cấp phát bộ nhớ: double *p; int n; cout >n; p = new double[n] if (p == NULL) { cout << “Loi cap phat bo nho”; exit(0); } 3.9.2 Toán tử delete Toán tử delete thay cho hàm free() của C, nó có cú pháp như sau: delete con trỏ ; 50
  51. Ví dụ: Chương trình sau minh hoạ cách dùng new để cấp phát bộ nhớ chứa n thí sinh. Mỗi thí sinh là một cấu trúc gồm các trường ht(họ tên), sobd(số báo danh), và td(tổng điểm). Chương trình sẽ nhập n, cấp phát bộ nhớ chứa n thí sinh, kiểm tra lỗi cấp phát bộ nhớ, nhập n thí sinh, sắp xếp thí sinh theo thứ tự giảm của tổng điểm, in danh sách thí sinh sau khi sắp xếp, giải phóng bộ nhớ đã cấp phát. #include #include #include #include #include struct TS { char ht[20]; long sobd; float td; }; void main(void) { TS *ts; int n; cout >n; ts = new TS[n+1]; if (ts == NULL) { cout > ts[i].sobd; cout >ts[i].td; } 51
  52. for (i=0;i<n-1;++i) for (int j=i+1;j<n;++j) if (ts[i].td<ts[j].td) { TS tg=ts[i]; ts[i]=ts[j]; ts[j]=tg; } cout << setiosflags(ios::showpoint)<<setprecision(1); for (i=0;i<n;++i) cout << "\n" << setw(20)<<ts[i].ht<<setw(6)<<ts[i].td; delete ts; getch(); } Bài tập: Bài 1: Xây dựng chương trình cho phép nhập vào một danh sách nhân viên, thông tin về nhân viên gồm mã nhân viên, họ và tên, ngày sinh, lương và phụ cấp. Bài 2: Viết chương trình nhập vào một danh sách sinh viên, thông tin về sinh viên gồm mã sinh viên, họ tên, ngày sinh, điểm lý thuyết, điểm thực hành. Sắp xếp danh sách sinh viên theo thứ tự giảm dần tổng điểm và xuất ra màn hình. BÀI 4 HÀM ĐỊNH NGHĨA CHỒNG MÃ BÀI ITPRG02.4 Giới thiệu: Việc định nghĩa nhiều hàm có tên giống nhau có thể được thực hiện trong ngôn ngữ C++ với các tham số khác nhau. Trong bài học này chúng ta sẽ tìm hiểu về cách khai báo và sử dụng hàm chồng cũng như việc định nghĩa chồng hàm khởi tạo và hủy bỏ. Mục tiêu thực hiện: Học xong bài này học viên sẽ có khả năng: - Sử dụng được hàm overloading construtor - Tạo và sử dụng một hàm tạo sao chép (copy constructor) - Cài đặt được tham số mặc định cho hàm - Khai báo được hàm định nghĩa chồng - Lấy được địa chỉ hàm định nghĩa chồng Nội dung: 4.1 Hàm constructor định nghĩa chồng(overloading constructor) 4.2. Cách tạo và sử dụng hàm copy constructor 52
  53. 4.3. Tham số mặc định của hàm constructor 4.4. Hàm định nghĩa chồng 4.5. Lấy địa chỉ hàm định nghĩa chồng 4.1 Hàm constructor định nghĩa chồng(overloading constructor) Hàm tạo sao chép sử dụng một đối kiểu tham chiếu đối tượng để khởi gán cho đối tượng mới và được viết theo mẫu sau: Tên_lớp (const Tên_lớp &ob) { // Các câu lệnh dùng các thuộc tính của đối tượng ob để khởi gán // cho các thuộc tính của đối tượng mới } Ví dụ: class PS { private: int t, m; public: PS(const PS &p) { t= p.t; m= p.m; } }; Chú ý: - Nếu lớp không có các thuộc tính kiểu con trỏ hoặc tham chiếu thì dùng hàm tạo sao chép mặc định là đủ. - Nếu lớp có các thuộc tính con trỏ hoặc tham chiếu, thì hàm tạo sao chép mặc định chưa đáp ứng được yêu cầu. class DT { private: int n; // Bac da thuc double *a; // Tro toi vung nho chua cac he so da thuc a0, a1 public: DT() { n = 0; a = NULL; } DT(int n1) { 53
  54. n = n1; a = new double[n1+1]; } friend ostream& operator > (istream& is,DT &d); }; 4.2. Cách tạo và sử dụng hàm copy constructor Bây giờ chúng ta hãy theo dõi xem việc dùng hàm tạo mặc định trong đoạn chương trình sau sẽ dẫn đến sai lầm như thế nào: DT d; cin >> d; /* Nhập đối tượng d gồm: nhập một số nguyên dương và gán cho d.n, cấp phát vùng nhớ cho d.n, nhập các hệ số của đa thức và chứa vào vùng nhớ được cấp phát */ DT u(d); /* Dùng hàm tạo mặc định để xây dựng đối tượng u theo d. Kết quả: u.n = d.n và u.a = d.a. Như vậy hai con trỏ u.a và d.a cùng trỏ đến một vùng nhớ. */ Nhận xét: Mục đích của ta là tạo ra một đối tượng u giống như d, nhưng độc lập với d. Nghĩa là khi d thay đổi thì u không bị ảnh hưởng gì. Thế nhưng mục tiêu này không đạt được, vì u và d có chung một vùng nhớ chứa hệ số của đa thức, nên khi sửa đổi các hệ số của đa thức trong d thì các hệ số của đa thức trong u cũng thay đổi theo. Còn một trường hợp nữa cũng dẫn đến lỗi là khi một trong hai đối tượng u và d bị giải phóng (thu hồi vùng nhớ chứa đa thức) thì đối tượng còn lại cũng sẽ không còn vùng nhớ nữa. Ví dụ sau sẽ minh họa nhận xét trên: Khi d thay đổi thì u cũng thay đổi và ngược lại khi u thay đổi thì d cũng thay đổi theo. Ví dụ : Ví dụ sau minh họa về hàm tạo sao chép: #include #include #include class DT { private: int n; // Bac da thuc double *a; // Tro toi vung nho chua cac da thuc // a0, a1, public: DT() 54
  55. { this->n=0; this->a=NULL; } DT(int n1) { this->n=n1; this->a= new double[n1+1]; } DT(const DT &d); friend ostream& operator > (istream& is,DT &d); }; DT::DT(const DT &d) { this->n = d.n; this->a = new double[d.n+1]; for (int i=0;i a[i] = d.a[i]; } ostream& operator > (istream& is,DT &d) { if (d.a != NULL) delete d.a; cout > d.n; d.a = new double[d.n+1]; cout > d.a[i]; } return is; } 55
  56. void main() { DT d; clrscr(); cout > d; DT u(d); cout > d; cout > u; cout << "\nDa thuc d " << d; cout << "\nDa thuc u " << u; getch(); } 4.3. Tham số mặc định của hàm constructor Gi sử đã định nghĩa một lớp ABC nào đó. Khi đó: - Ta có thể dùng câu lệnh khai báo hoặc cấp phát bộ nhớ để tạo các đối tượng mới, ví dụ: ABC p1, p2; ABC *p = new ABC; - Ta cũng có thể dùng lệnh khai báo để tạo một đối tượng mới từ một đối tượng đã tồn tại, ví dụ: ABC u; ABC v(u); // Tạo v theo u Câu lệnh này có ý nghĩa như sau: - Nếu trong lớp ABC chưa xây dựng hàm tạo sao chép, thì câu lệnh này sẽ gọi tới một hàm tạo sao chép mặc định của C++. Hàm này sẽ sao chép nội dung từng bit của u vào các bit tương ứng của v. Như vậy các vùng nhớ của u và v sẽ có nội dung như nhau. - Nếu trong lớp ABC đã có hàm tạo sao chép thì câu lệnh: PS v(u); sẽ tạo ra đối tượng mới v, sau đó gọi tới hàm tạo sao chép để khởi gán v theo u. Ví dụ sau minh họa cách dùng hàm tạo sao chép mặc định: Trong chương trình đưa vào lớp PS (phân số): 56
  57. + Các thuộc tính gồm: t (tử số) và m (mẫu). + Trong lớp mà không có phương thức nào cả mà chỉ có hai hàm bạn là các hàm toán tử nhập (>>) và xuất ( #include class PS { private: int t,m; public: friend ostream& operator > (istream& is, PS &p) { cout >p.t>>p.m; return is; } }; void main() { PS d; cout >d; cout 57
  58. #include class point{ int x,y; public: void init(); void init(int); void init(int, int); void display(); void display(char *); }; void point::init(){ x = 0; y = 0; } void point::init(int abs){ x = abs; y = 0; } void point::int(int abs, int ord){ x = abs; y = ord; } void point::display(){ cout<<"Toa do:"<<x<<","<<y<<endl; } void point::display(char *msg){ cout<<msg; display(); } void main(){ clrscr(); point a; a.init(); 58
  59. a.display(); point b; b.init(); b.display("point b "); point c; c.init(3,12); c.display("Hello "); getch(); } 4.5. Lấy địa chỉ hàm định nghĩa chồng 4.5.1 Trường hợp các hàm có một tham số: Chương trình dịch tìm kiếm "sự tương ứng nhiều nhất” có thể được; có các mức độ tương ứng sau: a) Tương ứng thật sự: ta phân biệt các kiểu dữ liệu khác nhau đồng thời lưu ý đến cả dấu. b) Tương ứng dữ liệu số nhưng có sự chuyển đổi kiểu dữ liệu tự động: char và short int; float int; c) Các chuyển đổi kiểu chuẩn được C++ chấp nhận. d) Các chuyển đổi kiểu do người sử dụng định nghĩa. Quá trình tìm kiếm bắt đầu từ mức cao nhất và dừng lại ở mức đầu tiên cho phép tìm thấy sự phù hợp. Nếu có nhiều hàm phù hợp ở cùng một mức, chương trình dịch đưa ra thông báo lỗi do không biết chọn hàm hàm nào giữa các hàm phù hợp. 4.5.2 Trường hợp hàm có nhiều tham số: Ý tưởng chung là phải tìm một hàm phù hợp nhất so với tất cả những hàm còn lại. Để đạt mục đích này, chương trình dịch chọn cho mỗi tham số các hàm phù hợp (ở tất cả các mức độ). Trong số các hàm được lựa chọn, chương trình dịch chọn ra hàm sao cho đối số nó đạt được sự phù hợp hơn cả so với các hàm khác. Bài tập: Bài 1: Định nghĩa lớp NhanVien gồm có các thuộc tính mã nhân viên, họ tên, ngày sinh, lương, phụ cấp, một hàm hủy bỏ, một hàm thiết lập 1 tham số, hai hàm thiết lập 2 tham số và một hàm thiết lập 3 tham số. Bài 2: Viết chương trình có khai báo một lớp Cha có các hàm khởi tạo và hàm hủy, có phương thức Chao (chào). Định nghĩa lớp Con kế thừa lớp Cha có hàm khởi tạo, hàm hủy và phương thức Chao. 59
  60. BÀI 5 TOÁN TỬ ĐỊNH NGHĨA CHỒNG MÃ BÀI: ITPRG02.5 Giới thiệu: C++ cho phép chúng ta định nghĩa các toán tử số học như cộng, trừ, nhân, chia cũng như các phép toán logic, phép toán gán, trên các đối tượng. Dựa trên các toán tử định nghĩa chồng, chúng ta có thể thực hiện các phép toán trên các đối tượng như các kiểu dữ liệu cơ bản. Mục tiêu thực hiện: Học xong bài này học viên sẽ có khả năng: - Khai báo được toán tử chồng, toán tử chồng hai ngôi, toán tử chồng logic, toán tử chồng đơn tác - Sử dụng được các toán tử friend, toán tử gán Nội dung: 5.1 Những khái niệm cơ bản toán tử chồng 5.2. Định nghĩa chồng toán tử hai ngôi 5.3. Định nghĩa chồng toán tử logic 5.4. Định nghĩa chồng toán tử một ngôi 5.5. Hàm toán tử là friend 5.6. Toán tử gán ( = ) 5.7. Một số định nghĩa toán tử chồng . Hàm chuyển kiểu . Định nghĩa chồng toán tử xuất (inserters) . Định nghĩa chồng toán tử nhập (Extractors) 5.1 Những khái niệm cơ bản toán tử chồng Các toán tử cùng tên thực hiện nhiều chức năng khác nhau được gọi là toán tử tải bội. Dạng định nghĩa tổng quát của toán tử tải bội như sau: Kiểu_tr_về operator op(danh sách tham số) {//thân 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ử tải bội operator op(danh sách tham số) gọi là hàm toán tử tải bội, 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. Danh sách tham 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: 60
  61. - Nếu toán tử tải bội là hàm thành phần thì: không có tham số cho toán tử một ngôi và một tham 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ử tải bội là hàm bạn thì: có một tham số cho toán tử một ngôi và hai tham số cho toán tử hai ngôi. Quá trình xây dựng toán tử tải bội đượ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ử tải bội - Khai báo hàm toán tử tải bội trong vùng public của lớp - Định nghĩa nội dung cần thực hiện. 5.2. Định nghĩa chồng toán tử hai ngôi Trong các hàm toán tử thành phần hai ngôi (có hai toán hạng) thì con trỏ this ứng với toán hạng thứ nhất, vì vậy trong tham số của toán tử chỉ cần dùng một tham số tường minh để biểu thị toán hạng thứ hai . Ví dụ 1:Toán tử tải bội hai ngôi #include #include class sophuc {float a,b; public : sophuc() {} sophuc(float x, float y) {a=x; b=y;} sophuc operator +(sophuc c2) { sophuc c3; c3.a= a + c2.a ; c3.b= 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) ; sophuc d3 ; d3 = d1+d2; //d3=d1.operator +(d2); cout<<"d1= ";d1.hienthi(d1); cout<<"d2= ";d2.hienthi(d2); 61
  62. cout #include class sophuc {float a,b; public : sophuc() {} sophuc(float x, float y) {a=x; b=y;} sophuc operator +(sophuc c2) { a= a + c2.a ; b= b + c2.b ; return (*this); } void hienthi(sophuc c) { cout #include class Diem { private: 62
  63. 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; } }; 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() 63
  64. { clrscr(); Diem d1(5,10),d2(20,25),d3(30,40),d4(50,60); cout 64
  65. #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; d1.x = -d.x; d1.y = -d.y;d1.z=-d.z; return d1; } void hienthi() { cout #include class Diem { private: float x,y,z; public: Diem() {} Diem(float x1,float y1,float z1) 65
  66. { x = x1; y = y1; z=z1;} friend Diem operator +(Diem d1, Diem d2) { Diem tam; tam.x = d1.x + d2.x; tam.y = d1.y + d2.y; tam.z = d1.z + d2.z; return tam; } void hienthi() { cout<<x<<" "<<y <<" " <<z<<endl;} }; void main() { clrscr(); Diem d1(3,-6,8),d2(4,3,7),d3; d3=d1+d2; d1.hienthi(); d2.hienthi(); cout<<"\n Tong hai diem co toa do la :"; d3.hienthi(); getch(); } 5.6. Toán tử gán ( = ) Việc định nghĩa chồng phép gán chỉ cần khi các đối tượng có các thành phần dữ liệu động. Chúng ta xét vấn đề này qua phân tích định nghĩa chồng phép gán “=” áp dụng cho lớp vector. Giả sử a và b là hai đối tượng thuộc lớp vector, khi đó: a = b; được hiểu là a.operator=b(); Do đó b được truyền cho hàm dưới dạng tham trị hoặc tham chiếu. Việc truyền bằng tham trị đòi hỏi sự có mặt của hàm thiết lập sao chép, hơn thế nữa sẽ làm cho chương trình chạy chậm vì mất thời gian sao chép một lượng lớn dữ liệu. Vì vậy d sẽ được truyền cho hàm ảoperator= dưới dạng tham chiếu. Ví dụ: class vector{ int n; float *v; public: 66
  67. vector &operator=(vector &b); }; vector & vector::operator=(vector &b){ if (this !=&b){ cout #include #include class complex{ float real, image; public: complex(){ real = 0; image = 0; } operator float(){ //ham chuyen doi kieu ep buoc ruturn real; } }; b. Hàm toán tử chuyển đổi kiểu cơ sở sang kiểu lớp Chúng ta có thể thực hiện các chỉ thị kiểu: complex e = 10; hoặc a = 1; 67
  68. Chỉ thị thứ nhất nhằm tạo một đối tượng tạm thời có kiểu complex tương ứng với phần thực bằng 10, phần ảo bằng 0 rồi sao chép sang đối tượng e mới được khai báo. Trong chỉ thị thứ hai, cũng có một đối tượng tạm thời kiểu comlpex được tạo ra và nội dung của nó (phần thực 1, phần ảo 0) được gán cho a. Như vậy, trong cả hai trường hợp đều phải gọi tới hàm thiết lập một tham số của lớp complex. Tương tự, nếu có hàm fct() với khai báo: ftc (complex) thì lời gọi ftc(4) sẽ đòi hỏi phải chuyển đổi từ giá trị nguyên 4 thành một đối tượng tạm thời có kiểu complex, để truyền cho fct(). Ví dụ: class complex{ float real, image; public: complex(float r){ real = r; image =0; } }; 5.7.2 Định nghĩa chồng toán tử nhập và xuất Ta có thể định nghĩa chồng cho hai toán tử vào/ra > kết hợp với cout và cin (cout >), cho phép các đối tượng đứng bên phải chúng. Lúc đó ta có thể thực hiện các thao tác vào ra như nhập dữ liệu từ bàn phím cho các đối tượng, hiển thị giá trị thành phần dữ liệu của các đối tượng ra màn hình. Hai hàm toán tử > phải là hàm tự do và khai báo là hàm bạn của lớp. Ví dụ #include #include class SO { private: int giatri; public: SO(int x=0) { giatri = x; } SO (SO &tso) { giatri = tso.giatri; } 68