DẪN NHẬP
Phương pháp lập trình Hướng đối tượng
1.1 Phương pháp lập trình hướng thủ tục
Trong lập trình hướng thủ tục, chương trình được cấu trúc thành các chương trình con độc lập, hay còn gọi là module hoặc hàm Mỗi chương trình con thực hiện một nhiệm vụ nhỏ hoặc một nhóm nhiệm vụ trong toàn bộ hệ thống, và chúng có thể được phân chia thành các chương trình con nhỏ hơn để tối ưu hóa quá trình xử lý.
Trong lập trình hướng thủ tục, trước khi bắt tay vào giải quyết bài toán, cần xác định rõ cấu trúc dữ liệu cần thiết cho bài toán Sau đó, các thao tác xử lý, hay còn gọi là các hàm, cũng cần được xác định Thông tin giữa các hàm được truyền tải thông qua bộ tham số đầu vào.
Như vậy Chương trình = cấu trúc dữ liệu + giải thuật
Tài liệu giảng dạy Lập Trình Hướng Đối Tượng 9
- Ưu điểm của phương pháp lập trình hướng thủ tục:
Triển khai các phần mềm dễ dàng hơn đối với lập trình viên
Chính vì được tổ chức dưới dạng module hóa nên chương trình dễ hiểu và dễ bảo trì hơn
- Nhược điểm của phương pháp lập trình hướng thủ tục:
Việc xử lý các kiểu dữ liệu trong nhiều hàm khác nhau trong một chương trình yêu cầu phải thực hiện thay đổi ở tất cả các hàm liên quan khi có sự thay đổi trong dữ liệu Điều này có thể gây tốn thời gian và giảm hiệu quả, đặc biệt đối với các chương trình lớn với hàng ngàn dòng lệnh và hàng trăm hàm.
Một điểm yếu của lập trình có cấu trúc là khi nhiều lập trình viên làm việc cùng một ứng dụng, việc phân công viết các hàm và kiểu dữ liệu cho từng người có thể gây ra sự phụ thuộc lẫn nhau Những thay đổi của một lập trình viên đối với các phần tử dữ liệu có thể ảnh hưởng đến toàn bộ nhóm, dẫn đến việc sửa chữa mất nhiều thời gian do sai sót trong việc trao đổi thông tin Mặc dù lập trình có cấu trúc có thể dễ dàng hơn trong môi trường nhóm, nhưng sự giao tiếp kém giữa các thành viên có thể gây ra những hậu quả nghiêm trọng.
Để giải quyết những nhược điểm trong việc mô tả các quan hệ phức tạp của tự nhiên, lập trình hướng đối tượng (OOP) đã được phát triển như một phương pháp lập trình hiệu quả.
1.2 Lập trình hương đối tượng và các khái niệm căn bản
Lập trình hướng đối tượng (OOP) là một phương pháp lập trình mới, mang đến cách nhìn và tư duy hiện đại, giúp tổ chức và quản lý các đối tượng trong quá trình phát triển phần mềm hiệu quả hơn.
Lập trình hướng đối tượng (OOP) là phương pháp xây dựng chương trình dựa trên cấu trúc lớp (class), giúp tạo ra các giải thuật hiệu quả Khác với lập trình hướng thủ tục, nơi chúng ta thường áp dụng nguyên lý lập trình từ trên xuống (top-down) để giải quyết công việc, OOP khuyến khích tư duy sáng tạo hơn Thay vì chỉ tìm cách giải quyết công việc từ đầu, chúng ta có thể sử dụng các đối tượng đã có sẵn và phát triển chúng để đáp ứng các yêu cầu cụ thể, từ đó mở rộng khả năng giải quyết vấn đề một cách linh hoạt và sáng tạo hơn.
Nguyên lý lập trình từ dưới lên (bottom-up) là phương pháp phân tích mà trong đó đối tượng được đặt ở vị trí trung tâm của quá trình lập trình.
Tài liệu giảng dạy Lập Trình Hướng Đối Tượng 10 Đối tƣợng (object):
Mỗi phần tử trong thế giới thực xung quanh chúng ta đều được coi là một đối tượng, bao gồm các thực thể như con người, giáo viên, học sinh, máy tính, xe cộ, phân số, hỗn số, chuỗi, hình vuông, chữ nhật và các điểm trong mặt phẳng hai chiều.
Đối tượng là sự kết hợp giữa thông tin mô tả (dữ liệu) và các hành vi thao tác (phương thức) trên dữ liệu đó.
Tòm lại: Đối tượng = Dữ liệu + Phương thức
Đối tượng Sinh viên có các thuộc tính như mã sinh viên, họ tên, quê quán, và điểm trung bình, cùng với các phương thức như ăn, học, và tính điểm trung bình Trong khi đó, đối tượng Phân Số có hai thuộc tính là tử số và mẫu số, với các phương thức như rút gọn phân số và cộng phân số.
Lớp đối tƣợng (gọi tắt là lớp - class)
Trong thực tế, chúng ta thường gặp nhiều đối tượng có đặc tính tương đồng, chẳng hạn như học sinh Nguyễn Thị Mỹ Loan và Trần Văn Tuấn đều thuộc lớp học sinh Tương tự, các phân số như 3/5, 1/3, 9/4 cũng chia sẻ đặc tính chung của một phân số Như vậy, học sinh và phân số đại diện cho các lớp đối tượng khác nhau, tạo thành tập hợp các đối tượng cùng loại.
Một đối tượng trong lập trình được xác định bởi hai thành phần chính: thuộc tính và phương thức Tương tự, một lớp đối tượng cũng được đặc trưng bởi các thuộc tính chung của những đối tượng cùng loại và các phương thức chung mà chúng chia sẻ.
Tài liệu giảng dạy Lập Trình Hướng Đối Tượng 11
Đối tượng chính là một thể hiện cụ thể của lớp đối tượng, trong khi lớp đối tượng là khung hình chung cho các đối tượng cùng loại.
Hình 1-3 Sơ đồ lớp và thể hiện
1.2.1 Sự trừu tượng hóa dữ liệu (data abstraction)
Trong quá trình phân tích bài toán, chúng ta cần đưa vào lập trình một số đối tượng để xử lý, nhưng không nhất thiết phải liệt kê toàn bộ thuộc tính và phương thức của chúng Việc này sẽ làm cho chương trình trở nên phức tạp và cồng kềnh Thay vào đó, chúng ta nên tập trung vào những chi tiết cần thiết và loại bỏ những thông tin không quan trọng.
Ngôn ngữ C++
2.1 Lịch sử của ngôn ngữ lập trình C++
Vào đầu thập niên 1980, C++ được biết đến với tên gọi "C with Classes", được mô tả trong hai bài báo của Bjarne Stroustrup từ AT&T Bell Laboratories Các bài báo có tiêu đề "Classes: An Abstract Data Type Facility for the C Language" và "Adding Classes to C: An Exercise in Language Evolution" đã giới thiệu khái niệm lớp, kiểm tra kiểu tham số của hàm, chuyển đổi kiểu và một số mở rộng khác cho ngôn ngữ C Mục tiêu của Bjarne Stroustrup là mở rộng ngôn ngữ C để phát triển một ngôn ngữ mô phỏng với các tính năng hướng đối tượng.
Vào những năm 1983-1984, ngôn ngữ "C with Classes" đã được thiết kế lại và mở rộng, dẫn đến sự ra đời của trình biên dịch mới và tên gọi "C++" Bjarne Stroustrup lần đầu tiên mô tả ngôn ngữ C++ trong bài báo "Data Abstraction in C" Sau một số điều chỉnh, C++ đã được công bố rộng rãi trong một quyển sách.
Cuốn sách "The C++ Programming Language" của Bjarne Stroustrup đánh dấu sự ra đời quan trọng của C++, cung cấp cho các lập trình viên một ngôn ngữ mạnh mẽ để thực hiện các dự án thực tiễn.
C++ là một ngôn ngữ lập trình hướng đối tượng, phát triển dựa trên ngôn ngữ C với các tính năng mở rộng quan trọng Điểm nổi bật của C++ là khả năng hỗ trợ lập trình đối tượng, giúp lập trình viên tổ chức và quản lý mã nguồn hiệu quả hơn.
Tài liệu giảng dạy Lập Trình Hướng Đối Tượng 17 giới thiệu về sự phát triển của lập trình hướng đối tượng, bắt nguồn từ những ý tưởng cấu trúc trong C++ vào những năm 1970 với sự ảnh hưởng của Simula 70 và Algol 68 Các ngôn ngữ này đã giới thiệu khái niệm về lớp và đối tượng Mặc dù Ada cũng phát triển từ những khái niệm này, nhưng C++ đã khẳng định vai trò quan trọng của mình trong lĩnh vực lập trình hướng đối tượng.
2.2 Cú pháp của C++ (Mở rộng của C++ so với C)
Trong lập trình C, hàm printf() và scanf() thường được sử dụng để nhập và xuất dữ liệu Trong C++, chúng ta có thể sử dụng dòng nhập/xuất chuẩn thông qua hai đối tượng là cout cho xuất dữ liệu và cin cho nhập dữ liệu, kết hợp với toán tử trích >.
Xuất chuỗi: Để chuỗi cần xuất ở trong cặp nháy đôi “chuỗi xuất”
Xuất biến: Sau toán tử =7) coutô”XL khỏ”; else coutô”XL trung bỡnh”;
Thuộc tính truy xuất trong lập trình bao gồm private, public và protected, mỗi loại thể hiện quyền truy cập khác nhau Chúng xác định khả năng cho phép hoặc hạn chế các đối tượng bên ngoài truy cập vào các thành phần của lớp, từ đó kiểm soát mức độ bảo mật và tính toàn vẹn của dữ liệu trong ứng dụng.
Thuộc tính truy xuất private :
Thành phần của lớp đặt sau từ khóa này thì có nghĩa là những thành phần này là
Trong lập trình hướng đối tượng, thuộc tính của lớp thường được khai báo là private để chỉ cho phép truy cập trong lớp đó Điều này có nghĩa là chỉ có các phương thức của lớp mới có quyền truy xuất các thành phần đã được đánh dấu là private, và việc truy cập từ bên ngoài lớp là hoàn toàn bị cấm.
Thuộc tính truy xuất public :
Public có nghĩa là "công cộng", cho phép các thành phần trong lớp được khai báo sau từ khóa này có thể được truy cập từ bên ngoài lớp, ở bất kỳ đâu Thông thường, các phương thức trong lớp thường được khai báo là public để đảm bảo tính khả dụng cao.
Tài liệu giảng dạy Lập Trình Hướng Đối Tượng 36
Trong ví dụ này, thuộc tính masv, hoten và dtb được khai báo là private, do đó chỉ có các thành phần trong lớp mới có quyền truy xuất, cụ thể là phương thức XepLoai() Các phạm vi khác, như trong hàm main(), không thể truy cập vào thuộc tính dtb của đối tượng lớp SinhVien, dẫn đến lỗi biên dịch Ngược lại, phương thức XepLoai() được khai báo là public, cho phép truy xuất từ bất kỳ đâu, bao gồm cả hàm main(), nơi mà đối tượng x có thể gọi hành động XepLoai() mà không gặp lỗi.
Thuộc tính truy xuất protected : class SinhVien
{ private: char masv[10]; char hoten[20]; float dtb; public: void XepLoai() { if(dtb>=8) coutô”XL giỏi”; else if (dtb >=7) coutô”XL khỏ”; else coutô”XL trung bỡnh”;
SinhVien x; cinằx.dtb; // lỗi truy xuất x.XepLoai(); // đƣợc phép truy xuất }
Tài liệu giảng dạy Lập Trình Hướng Đối Tượng 37
Các thành phần được khai báo là protected trong lớp cho phép truy cập không chỉ từ các thành phần trong lớp đó mà còn từ các lớp dẫn xuất Thuộc tính này chỉ có ý nghĩa trong bối cảnh có mối quan hệ kế thừa trong chương trình.
2.1.4 Vấn đề che dấu thông tin
Trong lập trình hướng đối tượng, tính đóng gói dữ liệu được thể hiện qua thuộc tính truy xuất private, giúp che giấu thông tin bên trong lớp Dữ liệu này không thể được truy cập từ bên ngoài, mà chỉ có thể tương tác thông qua các phương thức public Điều này tạo ra một lớp bảo vệ, cho phép người dùng sử dụng các hành động mà không cần biết chi tiết cài đặt bên trong, giống như một cái hộp đen.
2.2 Giao diện và chi tiết cài đặt Để cài đặt chúng ta sử dụng môi trường Visual studio C++ 6.0, 2005, 2008,
Sau đây là minh họa giao diện cài đặt Visual studio C++ 2010
Sau đó xuất hiện cửa sổ:
Tạo project (nơi chứa mã chương trình):
Tài liệu giảng dạy Lập Trình Hướng Đối Tượng 38
Hình 2-3 Tạo mới một project
Hình 2-4 Cửa sổ đặt tên project
Thực hiện 4 bước như hình trên bấm OK
Sau đó xuất hiện một cửa sổ, rồi bấm next để tiếp tục
4: Đặt tên project 3:Chọn vị trí lưu project
Tài liệu giảng dạy Lập Trình Hướng Đối Tượng 39
Cửa sổ kế tiếp: chọn Empty project rồi bấm finish
Tài liệu giảng dạy Lập Trình Hướng Đối Tượng 40
Hình 2-5 Cửa sổ chọn Empty project
Tiếp theo chúng ta sẽ có hai sự lựa chọn để viết code:
Lựa chọn 1 : Tất cả chương trình chỉ chứa trong một file có phần mở rộng là cpp
Kế tiếp thêm một file source vào project đã tạo ở trên:
Tài liệu giảng dạy Lập Trình Hướng Đối Tượng 41
kế tiếp làm theo hướng dẫn hình sau rồi nhấn nút Add
Hình 2-6.Thêm file source vào project
Kết quả tạo ra cửa sổ giao diện là nơi chúng ta có thể viết code thực thi:
Tài liệu giảng dạy Lập Trình Hướng Đối Tượng 42
Hình 2-7 Giao diện soạn thảo
Lựa chọn 2 : là cách cài đặt dưới dạng thư viện
Sau khi tạo project xong chúng ta thực hiện theo những hình sau:
Tài liệu giảng dạy Lập Trình Hướng Đối Tượng 43
Cửa sổ mới hiện ra:
Tài liệu giảng dạy Lập Trình Hướng Đối Tượng 44
Hình 2-8 Cửa sổ tạo tên lớp
Bấm finish sẽ xuất hiện cửa sổ:
Tài liệu giảng dạy Lập Trình Hướng Đối Tượng 45
Click phải vào tên lớp để tiến hành khai báo thuộc tính và phương thức
Hình 2-9 Thêm thuộc tính vào lớp
Chọn thuộc tính truy xuất
Tài liệu giảng dạy Lập Trình Hướng Đối Tượng 46
Nhấn finish để kết thúc việc thêm một thuộc tính của lớp Các thuộc tính khác làm tương tự
Hình 2-10 Cửa sổ khai báo thuộc tính
Hình 2-11 Thêm phương thức vào lớp
Hình 2-12 Khai báo phương thức
Kiểu trả về của hàm
Chọn thuộc tính truy xuất
Kiểu tham số của hàm
Tài liệu giảng dạy Lập Trình Hướng Đối Tượng 47
Nhấn ok rồi lặp lại cho nhiều phương thức khác của lớp
Sau khi hoàn tất việc tạo tất cả các thuộc tính và phương thức của lớp, chúng ta sẽ nhận được hai file: một file “.cpp” dùng để định nghĩa các phương thức của lớp và một file “.h” để khai báo lớp.
Tài liệu giảng dạy Lập Trình Hướng Đối Tượng 48
Hình 2-14 Minh họa file cpp
Để hoàn tất dự án, bạn cần thêm một tệp cpp chứa hàm main(), nơi thực thi các đối tượng Để sử dụng lớp đối tượng, hãy chắc chắn rằng bạn đã include của lớp đó ở đầu chương trình.
Nhƣ vậy là đã hoàn tất việc tạo giao diện cài đặt Nhấn Ctrl+F7 để biên dịch và Ctrl+F5 để chạy chương trình
2.3 Sử dụng đối tƣợng Để sử dụng đối tƣợng thì đầu tiên chúng ta tạo ra đối tƣợng bằng câu lệnh khai báo biến, sau đó có thể cho đối tƣợng đó hoạt động tức gọi các hành động của đối tượng đó Hãy nhớ lại rằng ch ng tr nh trong lập trình hướng đối tượng chính là sự hoạt động của các đối tƣợng
Khai báo như SinhVien x ; khi đó để gọi các phương thức của x chúng ta sử dụng toán tử chấm “.”, chẳng hạn nhƣ x.XepLoai()
2.3.1 Chu trình sống của đối tượng
Tài liệu giảng dạy Lập Trình Hướng Đối Tượng 49 nêu rõ rằng đối tượng được tạo ra thông qua câu lệnh khai báo biến Đối tượng sẽ được giải phóng khi chương trình kết thúc hoặc khi có lệnh hủy đối tượng.
2.3.2 Thiết lập và hủy đối tượng
Phương thức thiết lập (còn gọi là phương thức khởi tạo)
Khi một đối tượng mới được tạo ra, nó chưa có dữ liệu ban đầu, dẫn đến việc không thể kiểm soát các ô nhớ cho thuộc tính của đối tượng, có thể chứa giá trị rác hoặc ngẫu nhiên Do đó, việc khởi tạo giá trị ban đầu cho đối tượng là rất cần thiết Để thực hiện điều này, chúng ta sử dụng phương thức khởi tạo.
- Có tên trùng với tên lớp
- Có thể có nhiều phương thức khởi tạo (các phương thức này được phân biệt nhờ danh sách tham số truyền vào)
- Không cần có câu lệnh gọi mà đƣợc tự động thực thi khi đối tƣợng vừa đƣợc tạo ra
Ví dụ: hai phương thức như hình sau được gọi là các phương thức khởi tạo của lớp PhanSo
HÀM FRIEND, LỚP FRIEND
Thông thường thì một hàm không phải là thành phần của lớp thì nó không có quyền truy cập đến những thành phần private, protected của đối tƣợng
Tài liệu giảng dạy Lập Trình Hướng Đối Tượng 59
Để giải quyết vấn đề này, chúng ta có thể khai báo hàm thông thường là bạn (friend) của lớp, cho phép hàm này truy cập vào các thuộc tính đã được đóng gói của lớp.
Cú pháp khai báo hàm bạn trong C++ là: friend ; khai báo này cần được đặt trong lớp, và khi định nghĩa hàm, bạn không cần sử dụng từ khóa friend nữa.
Lưu ý: một hàm có thể làm bạn của nhiều lớp Khi đó phải khai báo mẫu trước các tên lớp đó
Nếu lớp B được khai báo là bạn của lớp A thì tất cả các phương thức của B có thể truy xuất đến thành phần riêng của lớp A
Một lớp có thể là bạn của nhiều lớp
Tài liệu giảng dạy Lập Trình Hướng Đối Tượng 60
Có thể khai báo A là bạn của B và ngƣợc lại
Phải khai báo trước các lớp
Cú pháp: friend class
ĐỊNH NGHĨA PHÉP TOÁN
Trong bài viết này, chúng ta sẽ khám phá cách đa năng hóa toán tử trong C++, cho phép các toán tử làm việc với các đối tượng của lớp Trước đó, chúng ta đã tìm hiểu về các lớp C++ và khái niệm kiểu dữ liệu trừu tượng Việc thực hiện các thao tác trên các đối tượng lớp thông qua việc gửi thông điệp (gọi hàm thành viên) có thể trở nên cồng kềnh, đặc biệt đối với các lớp toán học Để khắc phục vấn đề này, chúng ta sẽ định nghĩa lại các hàm toán tử, giúp việc sử dụng các đối tượng trở nên dễ dàng và trực quan hơn.
Khi làm việc với hai đối tượng x và y thuộc lớp PhanSo, việc tính tổng hai phân số này không thể thực hiện bằng phép tính x+y, vì toán tử + không hỗ trợ cho kiểu dữ liệu do người dùng định nghĩa.
4.2 Nguyên tắc định nghĩa phép toán
C++ không cho phép tạo ra các toán tử mới, nhưng cho phép đa năng hóa các toán tử đã có, giúp chúng hoạt động với các đối tượng của lớp Đây là một trong những điểm mạnh nổi bật của C++.
Các toán tử có thể được đa năng hóa thông qua việc định nghĩa một hàm, bao gồm phần đầu và thân hàm giống như cách viết một hàm thông thường, chỉ khác ở chỗ không có tên hàm.
Tài liệu giảng dạy Lập Trình Hướng Đối Tượng 61 cho biết rằng hàm hiện nay được gọi là từ khóa operator, theo sau bởi ký hiệu của toán tử được đa năng hóa Prototype của nó có dạng như sau:
operator ( Danh sách tham số);
Để sử dụng toán tử với các đối tượng của lớp, cần phải đa năng hóa ngoại trừ hai trường hợp Thứ nhất, toán tử gán có thể sử dụng với mọi lớp mà không cần đa năng hóa, với cách sử dụng mặc định là gán thành viên dữ liệu của lớp Tuy nhiên, việc sao chép thành viên mặc định có thể nguy hiểm đối với các lớp có thành viên được cấp phát động, do đó cần phải đa năng hóa toán tử gán một cách tường minh cho những lớp này Thứ hai, toán tử địa chỉ (&) cũng có thể được sử dụng với các đối tượng của bất kỳ lớp nào mà không cần đa năng hóa, vì nó trả về địa chỉ của đối tượng trong bộ nhớ.
Phần lớn các toán tử trong C++ đều có thể đƣợc đa năng hóa Bảng sau cho thấy các toán tử có thể đa năng hóa:
Và và bảng sau là các toán tử không thể đa năng hóa:
Thứ tự ƣu tiên của các toán tử không đƣợc thay đổi
Giữ đúng số ngôi của toán tử đƣợc đa năng hóa
Các hàm toán tử có thể được khai báo là hàm thành viên của lớp hoặc là hàm thông thường Khi là hàm thành viên, đối tượng bên trái của toán tử phải là một đối tượng của lớp, còn khi là hàm thông thường, phải khai báo hàm bạn để truy cập được thành phần private và protected của lớp Tuy nhiên, một số toán tử như >> và > và The prototype for the overloaded (istream & stream, ClassName Object);
Hàm toán tử >> trả về tham chiếu đến dòng nhập istream, với tham số đầu tiên là tham chiếu đến dòng nhập và tham số thứ hai là đối tượng của lớp cần tạo dựng từ dữ liệu dòng nhập Trong quá trình sử dụng, dòng nhập hoạt động như toán hạng bên trái, trong khi đối tượng nhận dữ liệu là toán hạng bên phải Tương tự như toán tử không phải là hàm thành viên của lớp, mà thường được định nghĩa là hàm friend.
Minh họa việc khai báo và sử dụng toán tử :
Cách 1 : hàm toán tử là thành viên của lớp
Ví dụ minh họa với lớp PhanSo
Tài liệu giảng dạy Lập Trình Hướng Đối Tượng 64 kq= a + b; // kq =a operator +(b)
Cách 2 : hàm toán tử là hàm bạn
Khai báo trong lớp PhanSo: Định nghĩa:
Các toán tử quan hệ thiết lập một quan hệ giữa các giá trị của toán hạng
Chúng sinh ra một giá trị có kiểu Boolean, là true nếu quan hệ đó là đúng và false nếu quan hệ đó là sai
Ví dụ về toán tử =Sử dụng:
Ví dụ về toán tử < lớp cơ sở 2>,…
THIẾT KẾ VÀ CÀI ĐẶT LỚP TRONG MỐI QUAN HỆ KẾ THỪA
2.1 Cơ chế tự động thừa hưởng đặc tính ở lớp cơ sở
2.1.1 Kế thừa thành phần dữ liệu
Lớp dẫn xuất thừa kế các thuộc tính từ lớp cơ sở, tạo thành một tập hợp thuộc tính bao gồm: các thuộc tính mới được khai báo trong lớp dẫn xuất và các thuộc tính đã được kế thừa từ lớp cơ sở.
- Tuy vậy trong lớp dẫn xuất không đƣợc truy xuất đến các thành phần private của lớp cơ sở
Ví dụ minh họa kế thừa thành phần dữ liệu:
Tài liệu giảng dạy Lập Trình Hướng Đối Tượng 76
Trong lớp C, có tổng cộng 4 thuộc tính, bao gồm hai thuộc tính x và y kế thừa từ lớp cơ sở A, cùng với hai thuộc tính riêng là z và t Tuy nhiên, lớp C không thể truy cập trực tiếp vào thành phần private x của lớp A Để lấy giá trị của x, lớp C cần sử dụng phương thức public của lớp A để nhận giá trị trả về.
2.1.2 Kế thừa thành phần phương thức
Lớp C bao gồm hai phương thức chính là show() và display(), cho phép bất kỳ đối tượng nào thuộc lớp C có thể thực hiện cả hai phương thức này.
2.2 Truy xuất thành phần của lớp cơ sở
Truy cập protected đóng vai trò như một lớp bảo vệ trung gian giữa truy cập công khai và riêng tư Các thành viên được định nghĩa là protected trong một lớp cơ sở có thể được truy cập bởi các lớp con, giúp tăng cường tính bảo mật và khả năng mở rộng của mã nguồn.
Tài liệu giảng dạy Lập Trình Hướng Đối Tượng 77 cho phép truy cập thông qua các hàm thành viên và hàm friend của lớp cơ sở, cũng như các hàm thành viên và hàm friend của lớp dẫn xuất.
2.2.2 Vấn đề truy xuất thành phần của lớp cơ sở
Lưu ý: Các phương thức của lớp cơ sở đều được kế thừa trừ phương thức khởi tạo, phương thức hủy và toán tử gán
Khi cả lớp cơ sở và lớp dẫn xuất đều có phương thức trùng tên như display(), nếu đối tượng thuộc lớp dẫn xuất yêu cầu gọi phương thức này, hệ thống sẽ tự động gọi phương thức trong lớp dẫn xuất Để gọi phương thức trùng tên từ lớp cơ sở, chúng ta sử dụng toán tử phạm vi ::, ví dụ như A::display().
2.3 Định nghĩa lại hành vi ở lớp dẫn xuất
Ví dụ: Giả sử chúng ta có lớp GiaoVien nhƣ sau: class GiaoVien
{ private: string hoTen; float mucLuong; int soNgayNghi; public:
GiaoVien(string Ten, float Luong, int snnghi); void giangDay(); float tinhLuong() { return mucLuong – soNgayNghi * 10000;
Để thiết kế lớp GVCN, chúng ta cần kế thừa từ lớp GiaoVien Tuy nhiên, vì cách tính lương của GVCN khác với GiaoVien, nên việc kế thừa một cách máy móc là không khả thi Do đó, cần phải định nghĩa lại phương thức tính lương cho GVCN.
L ng GV = Mức ng – S ng y nghỉ * 10000
L ng GV N = L ng GV + Phụ cấp 50000
Sau đây là ví dụ minh họa:
Tài liệu giảng dạy Lập Trình Hướng Đối Tượng 78 class GVCN: public GiaoVien
GVCN(string Ten, float Luong, int snnghi, string Lop); void sinhHoatCN(); float tinhLuong() { return GiaoVien::tinhLuong() +50000;
Khi đó nội dung của chương trình chính như sau: void main()
GiaoVien gv1(“Minh”,50000,5); gv1.giangDay(); float fLuong1= gv1.tinhLuong();
CVCN gv2(“Hanh”,70000,3, ”CD11CNTT2”); gv2.giangDay(); float fLuong2= gv2.tinhLuong();
2.4 Tầm vực trong kế thừa
Giả sử chúng ta có mối quan hệ kế thừa sau:
Tài liệu giảng dạy Lập Trình Hướng Đối Tượng 79
Khi lớp B kế thừa thuộc tính từ lớp cơ sở A, tầm vực của các thuộc tính này phụ thuộc vào loại kế thừa được sử dụng Có ba loại kế thừa chính: public, private và protected Việc xác định loại kế thừa sẽ giúp chúng ta hiểu rõ hơn về khả năng truy cập và sử dụng các thuộc tính trong lớp B.
Sau đây là các bảng minh họa tầm vực trong kế thừa
Phạm vi Kế thừa public Kế thừa private
2.5 Vấn đề thiết lập và hủy đối tƣợng
Khi một lớp dẫn xuất kế thừa từ lớp cơ sở, constructor của lớp cơ sở cần được gọi để khởi động các thành viên của nó khi đối tượng lớp dẫn xuất được khởi tạo Để thực hiện điều này, một bộ khởi tạo lớp cơ sở có thể được cung cấp trong constructor của lớp dẫn xuất, cho phép gọi tường minh constructor của lớp cơ sở Nếu không, constructor mặc định của lớp cơ sở sẽ được gọi tự động.
Các constructor và toán tử gán của lớp cơ sở không được kế thừa bởi lớp dẫn xuất Tuy nhiên, lớp dẫn xuất có khả năng gọi các constructor và toán tử gán từ lớp cơ sở.
Khi một lớp dẫn xuất được khởi tạo, constructor của lớp cơ sở luôn được gọi đầu tiên để thiết lập các thành viên của lớp cơ sở Nếu constructor của lớp dẫn xuất bị bỏ qua, thì constructor mặc định sẽ tự động gọi constructor của lớp cơ sở Ngược lại, các destructor được thực thi theo thứ tự ngược lại với các constructor, do đó, destructor của lớp dẫn xuất sẽ được gọi trước destructor của lớp cơ sở.
Ví dụ hàm tạo của lớp dẫn xuất (lớp GVCN trong phần 2.3) đƣợc viết nhƣ sau: Lớp cơ sở Lớp dẫn xuất
Tài liệu giảng dạy Lập Trình Hướng Đối Tượng 80
GVCN(string Ten, float Luong, int snnghi, string Lop):
2.6 Chuyển đổi ngầm định đối tƣợng lớp dẫn xuất sang lớp cơ sở
Mặc dù đối tượng lớp dẫn xuất là một loại đối tượng lớp cơ sở, nhưng kiểu lớp dẫn xuất và lớp cơ sở có sự khác biệt Đối tượng lớp dẫn xuất có thể được xử lý giống như đối tượng lớp cơ sở, điều này rất quan trọng vì lớp dẫn xuất có các thành viên tương ứng với lớp cơ sở Tuy nhiên, phép gán theo chiều ngược lại không được phép, vì việc gán một đối tượng lớp cơ sở cho đối tượng lớp dẫn xuất có thể dẫn đến việc thêm các thành viên không xác định từ lớp dẫn xuất.
Một con trỏ đến đối tượng lớp dẫn xuất có thể được chuyển đổi ngầm thành con trỏ đến đối tượng lớp cơ sở, vì đối tượng lớp dẫn xuất thực chất là một đối tượng lớp cơ sở.
Có bốn phương pháp để kết hợp và so sánh các con trỏ của lớp cơ sở và lớp dẫn xuất với các đối tượng tương ứng của chúng.
- Tham chiếu tới một đối tƣợng lớp cơ sở với một con trỏ lớp cơ sở thì không phức tạp
- Tham chiếu tới một đối tƣợng lớp dẫn xuất với một con trỏ lớp dẫn xuất thì không phức tạp
Tham chiếu đối tượng lớp dẫn xuất qua con trỏ lớp cơ sở là an toàn, vì đối tượng lớp dẫn xuất cũng thuộc lớp cơ sở của nó Do đó, mã chỉ có thể truy cập các thành viên của lớp cơ sở Nếu mã cố gắng truy cập các thành viên của lớp dẫn xuất thông qua con trỏ lớp cơ sở, trình biên dịch sẽ báo lỗi cú pháp.
XỬ LÝ ĐA HÌNH
BÀI TOÁN XỬ LÝ ĐA HÌNH
Tài liệu giảng dạy Lập Trình Hướng Đối Tượng 86
Trong ví dụ về kế thừa, lớp C kế thừa từ lớp B, trong khi lớp B lại kế thừa từ lớp A, và cả ba lớp đều có phương thức print() Khi gọi phương thức print() cho đối tượng c của lớp C trong hàm main(), trình biên dịch sẽ tự động gọi phương thức print() của lớp C Để gọi phương thức print() của lớp B hoặc lớp A, chúng ta cần sử dụng toán tử phạm vi :: Điều này cho thấy rằng, trong trường hợp có các phương thức trùng tên, đối tượng của lớp nào sẽ ưu tiên gọi phương thức của lớp đó.
Giả sử bây giờ lời gọi phương thức print() không phải là từ một đối tượng như trên mà là một con trỏ đối tƣợng nhƣ hình sau:
Trong hàm main(), chúng ta khởi tạo ba con trỏ đối tượng thuộc lớp A và ba đối tượng của các lớp A, B, C Đặc biệt, một con trỏ của lớp cơ sở có khả năng chứa địa chỉ của đối tượng thuộc lớp dẫn xuất, cho phép con trỏ lớp cơ sở trỏ tới đối tượng lớp dẫn xuất Điều này giúp chúng ta có thể sử dụng ba con trỏ p, q, r của lớp A để tương tác với các đối tượng khác nhau.
Trong tài liệu giảng dạy Lập Trình Hướng Đối Tượng, ba con trỏ a, b, c được khai báo cho các đối tượng thuộc ba lớp A, B, C Khi gọi phương thức print() thông qua các con trỏ này, mong muốn là phương thức tương ứng của lớp mà con trỏ đang quản lý sẽ được thực thi Tuy nhiên, kết quả cuối cùng lại cho thấy tất cả đều gọi đến phương thức print() của lớp A.
Phương thức print() trong hai ví dụ trên được coi là phương thức tĩnh Từ những ví dụ này, chúng ta có thể rút ra một số kết luận quan trọng.
Lời gọi đ n ph ng thức tĩnh
Nếu lời gọi phương thức xuất phát từ đối tượng của lớp nào thì phương thức của lớp đó sẽ đƣợc gọi
Khi một phương thức được gọi thông qua con trỏ đối tượng của một lớp cụ thể, phương thức của lớp đó sẽ được thực thi, ngay cả khi con trỏ đang tham chiếu đến một đối tượng thuộc lớp khác.
Để giải quyết vấn đề làm sao để ba lời gọi p->print(), q->print() và r->print() có thể truy cập vào các phương thức tương ứng trong ba lớp khác nhau, chúng ta cần đảm bảo rằng con trỏ đang quản lý đối tượng nào thì sẽ gọi phương thức của đối tượng đó Điều này yêu cầu sử dụng hai yếu tố quan trọng.
- Khả năng định nghĩa đè phương thức (phương thức ảo)
- Con trỏ lớp cơ sở có thể trỏ đến đối tƣợng lớp dẫn xuất
CƠ CHẾ XỬ LÝ ĐA HÌNH
2.1 Phương thức ảo Để đưa một phương thức trở thành một phương thức ảo thì chỉ cần đặt từ khóa virtual trước phương thức
Vậy sử dụng phương thức ảo chúng ta sẽ thu được kết quả gì? Chúng ta cùng xem xét ví dụ sau:
Tài liệu giảng dạy Lập Trình Hướng Đối Tượng 88
Kết quả thu đƣợc là: A-B-C
Bây giờ chúng ta viết lại hàm main ở ví dụ trên nhƣ sau: void main()
A*p; p=&a; // p tro vao a p->print(); //xuat A p= &b; // p tro vao b p->print(); // Xuat B
Tài liệu giảng dạy Lập Trình Hướng Đối Tượng 89
Kết quả vẫn là A-B-C, cho thấy phương thức print() là phương thức ảo Điều này đáp ứng mong muốn rằng con trỏ sẽ gọi đến phương thức của lớp mà nó đang trỏ tới, ngay cả khi con trỏ đó thuộc về lớp khác.
Vậy tính đa hình đƣợc thể hiện nhƣ thế nào trong ví dụ này?
Trong C++, hành vi của phương thức print() có thể thay đổi tùy thuộc vào đối tượng mà con trỏ p đang trỏ tới Nếu p trỏ vào đối tượng a, phương thức sẽ xuất ra “A”; nếu p trỏ vào b, nó sẽ xuất ra “B”; và nếu p trỏ vào c, kết quả sẽ là “C” Khả năng này được gọi là tính đa hình, cho phép cùng một phương thức hoạt động khác nhau trên các đối tượng khác nhau.
Như vậy phương thức ảo là gì?
- Là một phương thức của lớp
- Khai báo: virtual ;
Phương thức ảo cho phép chuyển lời gọi hàm đến đúng đối tượng mà con trỏ đang trỏ tới, được gọi là liên kết động Cơ chế này cho phép gửi một loại thông điệp đến nhiều đối tượng khác nhau, mỗi đối tượng sẽ xử lý theo cách riêng phù hợp với ngữ cảnh của chúng.
2.2 Các lưu ý khi sử dụng phương thức ảo
- Phương thức ảo chỉ có ý nghĩa khi gọi thông qua con trỏ
- Muốn một hàm trở thành phương thức ảo có hai cách: khai báo với từ khóa virtual hoặc hàm tương ứng ở lớp cơ sở đã là phương thức ảo
- Phương thức ảo chỉ hoạt động nếu các hàm ở lớp cơ sở và lớp con có nghi thức giao tiếp giống hệt nhau
- Nếu ở lớp con định nghĩa lại phương thức ảo (viết khác tên phương thức) thì sẽ gọi phương thức ở lớp cơ sở (gần nhất có định nghĩa)
2.3 Các thành viên ảo của một lớp
Có constructor và destructor ảo hay không? p=&c; p->print(); // xuat C
Tài liệu giảng dạy Lập Trình Hướng Đối Tượng 90
Khi một đối tượng thuộc lớp có phương thức ảo, trình biên dịch sẽ tạo thêm một con trỏ vptr để quản lý địa chỉ của phương thức ảo Mỗi lớp chỉ có một bảng phương thức ảo, trong khi có thể có nhiều đối tượng thuộc lớp đó Do đó, khi một đối tượng mới được tạo ra, con trỏ vptr đã tồn tại Vì lý do này, bảng phương thức ảo phải được tạo ra trước khi gọi constructor, dẫn đến việc constructor không thể là phương thức ảo.
Khi một đối tượng thuộc lớp bị hủy, bảng phương thức ảo và con trỏ vptr vẫn tồn tại, do lớp chỉ có một bảng phương thức ảo Destructor được gọi trước khi bộ nhớ cho đối tượng bị thu hồi, cho phép nó có thể là phương thức ảo Mặc dù vậy, constructor của lớp vẫn có khả năng gọi các phương thức ảo khác mà không mâu thuẫn với cơ chế kết nối động.
Tài liệu giảng dạy Lập Trình Hướng Đối Tượng 91
Nếu destructor không phải là phương thức ảo, khi giải phóng đối tượng B, chỉ có destructor của lớp cơ sở được gọi Ngược lại, nếu destructor là phương thức ảo, khi giải phóng đối tượng B, destructor của lớp dẫn xuất sẽ được gọi trước, sau đó là destructor của lớp cơ sở.
Phương thức hủy bỏ ảo:
Nếu phương thức hủy không phải là ảo, chỉ có phương thức hủy của lớp cơ sở được gọi, trong khi phương thức hủy của lớp dẫn xuất sẽ không được kích hoạt Để đảm bảo việc dọn dẹp tài nguyên diễn ra đầy đủ và chính xác, cần sử dụng phương thức hủy ảo.
Ví dụ xét quan hệ sau:
Tài liệu giảng dạy Lập Trình Hướng Đối Tượng 92
PHƯƠNG THỨC THUẦN ẢO VÀ LỚP TRỪU TƯỢNG
Trong thiết kế chương trình hướng đối tượng, việc xây dựng hệ thống phả hệ kế thừa đòi hỏi lập trình viên phải dự đoán sự phát triển của cấu trúc và lựa chọn thành viên phù hợp cho các lớp trên cùng Đây là một thách thức lớn, nhằm tránh lãng phí bộ nhớ khi tạo đối tượng, ngôn ngữ C++ cho phép thiết kế các lớp với phương thức ảo không thực hiện chức năng nào, gọi là phương thức thuần ảo Những lớp chứa phương thức thuần ảo này được gọi là lớp trừu tượng, và không thể tạo ra đối tượng thuộc lớp đó.
Ví dụ về các lớp Hình tròn, Tam giác, và Hình vuông kế thừa từ lớp Hình cho thấy rằng các hàm trong lớp Hình có nội dung nhưng không có ý nghĩa thực tiễn Trong khi đó, việc tạo đối tượng từ lớp Hình là khả thi, điều này mâu thuẫn với nguyên tắc của lập trình hướng đối tượng.
Ta có thể thay thế cho nội dung không có ý nghĩa bằng phương thức thuần ảo class Nguoi
Nguoi(char *ht, int ns):namSinh(ns) {hoTen = strdup(ht);} virtual ~Nguoi() {delete [] hoTen;}
Tài liệu giảng dạy Lập Trình Hướng Đối Tượng 93
Nhƣ vậy lớp cơ sở trừu tƣợng là lớp cơ sở không có đối tƣợng nào và chỉ sử dụng để cho các lớp khác kế thừa
Phương thức thuần ảo được khai báo trong lớp sẽ làm cho lớp đó trở thành lớp cơ sở trừu tƣợng
Cú pháp khai báo phương thức thuần ảo: virtual KieuTraVe TenHam(danh sach tham so)=0
Lưu ý: Tất cả các lớp dẫn xuất đều phải định nghĩa lại phương thức thuần ảo
Ví dụ về lớp cơ sở trừu tượng và phương thức thuần ảo: class Animal
{ public: void talk() {cout