Bài giảng Ngôn ngữ lập trình - Bài 6: Nạp chồng toán tử và kế thừa - Lê Nguyễn Tuấn Thành
Giới thiệu nạp chồng toán tử
6
Những toán tử như +,-, %, == etc. thực ra là những hàm!
Các hàm đặc biệt này được gọi với cú pháp khác so với
cách gọi hàm thông thường
Gọi hàm thông thường:
Tên_Hàm (Danh_Sách_Đối_Số)
Với toán tử: ví dụ, x + 7, “+” là một toán tử 2 ngôi (binary
operator) với x, 7 là 2 toán hạng (operands)
Thử viết theo cách gọi hàm thông thường:+(x,7)
“+” là tên hàm
x, 7 là tham số của hàm
Hàm “+” trả lại giá trị là tổng của 2 đối sốTại sao dùng nạp chồng toán tử?
7
Những toán tử được xây dựng sẵn
Ví dụ, +, -, = , %, ==, /, *
Đã thao tác được với các kiểu dựng sẵn của C++
Nhưng liệu chúng ta có thể thực hiện phép + với 2
đối tượng của lớp Money?, giống như:
money1 + money2;
Để làm được điều này, chúng ta phải nạp chồng
những toán tử này cho lớp Money!
Tóm tắt nội dung tài liệu: Bài giảng Ngôn ngữ lập trình - Bài 6: Nạp chồng toán tử và kế thừa - Lê Nguyễn Tuấn Thành
Ngôn ngữ lập trình Bài 6: Nạp Chồng Toán Tử và Kế Thừa Giảng viên: Lê Nguyễn Tuấn Thành Email: thanhlnt@tlu.edu.vn Bộ Môn Công Nghệ Phần Mềm – Khoa CNTT Trường Đại Học Thủy Lợi Nội dung 2 Nạp chồng toán tử (Operator Overloading) và Hàm bạn (Friend Functions) Kế thừa (Inheritance) Bài giảng có sử dụng hình vẽ trong cuốn sách “Absolute C++. W. Savitch, Addison Wesley, 2002” 1. Nạp chồng toán tử và Hàm bạn Operator Overloading and Friend Functions Mục tiêu 4 Nạp chồng toán tử cơ bản Toán tử hai ngôi (binary operators) Toán tử một ngôi (unary operators) Nạp chồng bằng hàm thành viên Hàm bạn và Lớp bạn L ớ p M o n e y 5 Giới thiệu nạp chồng toán tử 6 Những toán tử như +,-, %, == etc. thực ra là những hàm! Các hàm đặc biệt này được gọi với cú pháp khác so với cách gọi hàm thông thường Gọi hàm thông thường: Tên_Hàm (Danh_Sách_Đối_Số) Với toán tử: ví dụ, x + 7, “+” là một toán tử 2 ngôi (binary operator) với x, 7 là 2 toán hạng (operands) Thử viết theo cách gọi hàm thông thường: +(x,7) “+” là tên hàm x, 7 là tham số của hàm Hàm “+” trả lại giá trị là tổng của 2 đối số Tại sao dùng nạp chồng toán tử? 7 Những toán tử được xây dựng sẵn Ví dụ, +, -, = , %, ==, /, * Đã thao tác được với các kiểu dựng sẵn của C++ Nhưng liệu chúng ta có thể thực hiện phép + với 2 đối tượng của lớp Money?, giống như: money1 + money2; Để làm được điều này, chúng ta phải nạp chồng những toán tử này cho lớp Money! Cơ bản về nạp chồng 8 Nạp chồng toán tử Tương tự như với nạp chồng hàm Toán tử bản thân nó là tên của hàm Ví dụ khai báo const Money operator + (const Money& amount1, const Money& amount2); Nạp chồng toán tử + với toán hạng là đối tượng kiểu Money Giá trị trả lại là một kiểu Money Mục đích: cho phép thực hiện phép + trên hai đối tượng của lớp Money Nạp chồng toán tử “+” 9 const Money operator + (const Money& amount1, const Money& amount2); Chú ý: hàm nạp chồng toán tử “+” này không phải hàm thành viên của lớp Money Định nghĩa, cài đặt của hàm này phức tạp hơn so với phép cộng thông thường (phải tính đến biến thành viên, kiểm tra giá trị âm/dương, ) Định nghĩa nạp chồng toán tử “+” cho lớp Money 10 Nạp chồng toán tử “==” 11 Toán tử so sánh bằng “==” Cho phép so sánh các đối tượng của lớp Money Khai báo: bool operator ==(const Money& amount1, const Money& amount2); Hàm này cũng không phải là hàm thành viên của lớp Money Nạp chồng toán tử một ngôi (Unary operators) 12 Toán tử một ngôi: chỉ có một toán hạng Toán tử phủ định (negation) “-” X = -Y // đặt X bằng giá trị phủ định của Y Toán tử tăng ++ Toán tử giảm -- Nạp chồng toán tử “-” cho lớp Money 13 Khai báo hàm nạp chồng toán tử “-” cho lớp Money const Money operator –(const Money& amount); Không phải hàm thành viên của lớp Chú ý: chỉ có một đối số, do toán tử này chỉ có một toán hạng Định nghĩa hàm nạp chồng toán tử một ngôi “-” const Money operator –(const Money& amount) { return Money(-amount.getDollars(), -amount.getCents()); } Trả lại một đối tượng vô danh (anonymous object) Lưu ý: nạp chồng toán tử “-” có hai trường hợp! Khi nó là toán tử 2 ngôi, với 2 toán hạng/đối số Khi nó là toán tử 1 ngôi, với 1 toán hạng/đối số Sử dụng nạp chồng toán tử “-” 14 Xét ví dụ sau: Money amount1(10), amount2(6), amount3; amount3 = amount1 – amount2; => Gọi nạp chồng toán tử 2 ngôi “-” amount3 = -amount1; => Gọi hàm nạp chồng toán tử 1 ngôi “-” Nạp chồng toán tử như hàm thành viên (1/2) 15 Những ví dụ ở trước: các hàm đứng độc lập không phải thành viên của lớp Có thể nạp chồng như “toán tử thành viên”, được xem như hàm thành viên Khi toán tử là hàm thành viên Chỉ có MỘT tham số, không phải có 2 tham số! Được tượng được gọi (phía sau toán tử) được xem là tham số duy nhất Nạp chồng toán tử như hàm thành viên (2/2) 16 Ví dụ: Money cost(1, 50), tax(0, 15), total; total = cost + tax; Nếu toán tử “+” được nạp chồng như toán tử thành viên thì: Biến/ đối tượng cost là đối tượng gọi hàm nạp chồng Đối tượng tax là tham số duy nhất của hàm nạp chồng Tưởng tượng giống như cách viết sau total = cost.+(tax); Khai báo của toán tử “+” trong định nghĩa lớp const Money operator +(const Money& amount); Chú ý CHỈ CÓ MỘT đối số Nạp chồng một số toán tử khác 17 Toán tử gọi hàm: () Toán tử &, ||, dấu phẩy Toán tử gán = (assignment operator), phải được nạp chồng như hàm thành viên! Toán tử tăng, giảm: ++, -- Mỗi toán tử có 2 phiên bản: Tiền tố (prefix notation): ++x; Hậu tố (postfix notation): x++; Toán tử mảng [ ], nạp chồng như hàm thành viên! Toán tử >>, << Nạp chồng toán tử >> và << 18 Cho phép nhập và xuất dữ liệu cho đối tượng Tăng tính dễ đọc cho chương trình Ví dụ chúng ta sẽ viết: cout << myObject; cin >> myObject; Thay vì phải viết: myObject.output(); myObject.input(); Toán tử chèn << (1/2) (Insertion operator) 19 Được sử dụng với cout, ví dụ: cout << "Hello"; Là toán tử hai ngôi: Toán hạng đầu tiên là đối tượng được định nghĩa sẵn cout, từ thư viện iostream Toán hạng thứ hai là dữ liệu/đối tượng cần in ra màn hình Giả sử khai báo: Money amount(100); Nếu chúng ta đã nạp chồng toán tử << với lớp Money, chúng ta có thể viết: cout << "I have " << amount << endl; thay vì sử dụng hàm thành viên output() và viết: cout << "I have "; amount.output() Toán tử chèn << (2/2) (Insertion operator) 20 Nạp chồng << nên trả về giá trị Giá trị nào được trả về? Đối tượng cout ! Trả về kiểu của đối số đầu tiên, ostream Hai cách viết sau là tương đương: cout << "I have " << amount; (cout << "I have ") << amount; Chương trình nạp chồng toán tử > (1/5) 21 Chương trình nạp chồng toán tử > (2/5) 22 Chương trình nạp chồng toán tử > (3/5) 23 Chương trình nạp chồng toán tử > (4/5) 24 Chương trình nạp chồng toán tử > (5/5) 25 Hàm bạn (Friend functions) 26 Nhớ lại: Nạp chồng toán tử có thể không phải hàm thành viên Khi đó, truy xuất dữ liệu phải thông qua các hàm accessor và mutator Cách làm này không hiệu quả (tăng phụ phí khi gọi các hàm này!) Hàm bạn Không phải hàm thành viên của lớp nhưng có thể truy xuất trực tiếp đến các dữ liệu trong khu vực private của lớp Không có phụ phí khi gọi hàm => hiệu quả hơn Vì vậy: cách tốt nhất là cài đặt nạp chồng toán tử là khai báo chúng như các hàm bạn Sử dụng từ khóa friend ở trước khai báo hàm Lớp bạn (Friend classes) 27 Toàn bộ một lớp có thể là bạn của một lớp khác Tương tự như một hàm là bạn trong một lớp Nếu lớp F là bạn của lớp C => tất cả hàm thành viên của lớp F đều là bạn của lớp C Điều ngược lại không đúng Cú pháp: friend class F Tóm tắt nạp chồng toán tử và hàm bạn 28 Những toán tử dựng sẵn (built-in) trong C++ có thể được nạp chồng để thao tác với đối tượng của lớp mà bạn định nghĩa Toán tử thực ra là những hàm! Toán tử có thể được nạp chồng như hàm ngoài (không phải thành viên) hoặc hàm thành viên của lớp Toán hạng đầu tiên là đối tượng gọi Hàm bạn truy xuất trực tiếp được các thành viên trong khu vực private 2. Kế thừa Inheritance Mục tiêu 30 Cơ bản về kế thừa (inheritance) Lớp thừa kế (derived classes), với hàm tạo Khu vực Protected Định nghĩa lại hàm thành viên Hàm không kế thừa Chương trình với kế thừa Toán tử gán và hàm tạo Đa kế thừa (multiple inheritance) Giới thiệu về kế thừa 31 Thế nào là kế thừa? Định nghĩa? Một kỹ thuật lập trình mạnh, khái niệm trừu tượng Cấu trúc tổng quát về một khái niệm được định nghĩa trong một lớp (lớp cha / lớp cơ sở) Những phiên bản chuyên biệt (lớp con) sau đó kế thừa thuộc tính của lớp tổng quát đó Lớp con có thể mở rộng hay thay đổi chức năng cho phù hợp Cơ bản về kế thừa 32 Một lớp mới được kế thừa từ một lớp khác Lớp cơ sở (lớp cha) Lớp tổng quát mà từ đó các lớp khác sẽ kế thừa Lớp thừa kế (lớp con) Một lớp mới Tự động có những hàm/biến thành viên của lớp cơ sở Sau đó có thể thêm những hàm/biến thành viên mới Thuật ngữ (terminology) về kế thừa giống như quan hệ gia đình Lớp cha (Parent class) ~ Lớp cơ sở (Base class) Lớp con (Child class) ~ Lớp thừa kế (Derived class) Lớp tổ tiên (Ancestor class) Lớp con cháu (Descendant class) Lớp thừa kế (Derived classes) 33 Xét ví dụ về lớp Nhân_Viên (Employee) Có thể bao gồm nhiều loại nhỏ: Nhân viên được trả lương (Salaried employees) Nhân viên bán thời gian, theo giờ (Hourly employees) Khái niệm tổng quát về Nhân_Viên là hữu ích! Được định nghĩa trước như một khung chung: Tất cả nhân viên đều có những thông tin chung như: Tên, Tuổi, Giới Tính, Quốc Tịch, CMTND Các hàm thành viên liên quan đến những dữ liệu này là giống nhau (cơ sở) cho tất cả nhân viên Ví dụ: các hàm accessor, mutator Định nghĩa lại hàm thành viên 34 Xét hàm printCheck() của lớp cơ sở Nhân_Viên Được định nghĩa lại trong các lớp thừa kế Do các loại nhân viên khác nhau có thể có các kiểm tra khác nhau Giao diện cho lớp thừa kế HourlyEmployee (1/2) 35 Giao diện cho lớp thừa kế HourlyEmployee (2/2) 36 Giao diện lớp HourlyEmployee 37 Lưu ý phần đầu chương trình Cấu trúc #ifndef Khai báo bao gồm (include) các thư viện liên quan Khai báo bao gồm lớp cơ sở employee.h! Khai báo cấu trúc kế thừa class HourlyEmployee : public Employee Giao diện (interface) của lớp thừa kế chỉ liệt kê những thành viên mới hoặc sẽ được định nghĩa lại Bởi vì tất cả những thành viên khác kế thừa từ lớp cơ sở đã được định nghĩa trước đó! Lớp HourlyEmployee thêm các thành viên sau: Hàm khởi tạo Biến thành viên: wageRate, hours Hàm thành viên: setRate(), getRate(), setHours(), getHours() Định nghĩa lại hàm thành viên trong lớp HourlyEmployee 38 Lớp HourlyEmployee định nghĩa lại: Hàm thành viên printCheck() của lớp cơ sở Phiên bản mới của hàm printCheck() sẽ “ghi đè” (overrides) phiên bản cũ đã được cài đặt trong lớp cơ sở Employee Cài đặt của hàm thành viên này phải được thực hiện trong lớp HourlyEmployee Định nghĩa lại hàm khác nạp chồng hàm thế nào? Rất khác nhau Định nghĩa lại hàm trong lớp thừa kế CÙNG danh sách tham số Thực chất là viết lại cùng một hàm Nạp chồng hàm Danh sách tham số khác nhau Định nghĩa một hàm mới với tham số khác Truy xuất hàm định nghĩa lại 39 Khi được định nghĩa lại một hàm trong lớp con, định nghĩa của hàm này trong lớp cơ sở không bị mất đi! Employee JaneE; HourlyEmployee SallyH; JaneE.printCheck(); //gọi hàm printCheck của lớp Employee SallyH.printCheck(); //gọi hàm printCheck của lớp HourlyEmployee SallyH.Employee::printCheck(); //gọi hàm printCheck của lớp Employee! Hàm tạo trong lớp thừa kế (1/2) 40 Hàm tạo của lớp cơ sở không được kế thừa tự động trong lớp con ! Nhưng chúng có thể được gọi bên trong hàm tạo của của lớp con! Hàm tạo của lớp cơ sở nên khởi tạo tất cả các biến thành viên Xét ví dụ hàm tạo của lớp HourlyEmployee HourlyEmployee::HourlyEmployee(string theName, string theNumber, double theWageRate, double theHours) : Employee(theName, theNumber), wageRate(theWageRate), hours(theHours) {} Hàm tạo trong lớp thừa kế (2/2) 41 Nếu lớp con không gọi hàm tạo nào của lớp cơ sở: Hàm tạo mặc định của lớp cơ sở tự động được gọi Ví dụ: HourlyEmployee::HourlyEmployee() : wageRate(0), hours(0) { } Lưu ý: dữ liệu private của lớp cơ sở 42 Lớp con kế thừa biến thành viên trong khu vực private Nhưng vẫn không thể truy xuất trực tiếp “theo tên” (by- name) đến những biến thành viên này Ngay cả truy xuất biến private thông qua các hàm thành viên của lớp con! Biến thành viên private có thể CHỈ được truy xuất “theo tên” trong các hàm thành viên của lớp cơ sở mà chúng được định nghĩa! Lưu ý: hàm thành viên private của lớp cơ sở 43 Không thể được truy xuất bên ngoài giao diện và cài đặt của lớp cơ sở Ngay cả trong định nghĩa hàm thành viên của lớp con Khu vực protected 44 Một khu vực mới cho thành viên của lớp Cho phép truy xuất “theo tên” thành viên trong lớp thừa kế Nhưng không cho phép truy xuất trong các lớp không kế thừa! Trong lớp mà những thành viên protected này được định nghĩa, hoạt động giống như các thành viên private Đa kế thừa (Multiple inheritance) 45 Lớp con có thể kế thừa nhiều hơn một lớp cơ sở! Cú pháp: các lớp cơ sở được phân tách bằng dấu phẩy Ví dụ: class derivedMulti : public base1, base2 {} Bài tập 46 Định nghĩa lớp Nhân viên (Employee) Private: Tên, Tuổi, Giới Tính, Quốc Tịch Public: void printCheck() Protected: CMTND Định nghĩa hai lớp con kế thừa từ lớp Nhân_Viên Nhân viên được trả lương (SalariedEmployee) Nhân viên bán thời gian, theo giờ (HourlyEmployee) Định nghĩa lại hàm printCheck() riêng của hai lớp con Tóm tắt về kế thừa 47 Kế thừa cho phép sử dụng lại code Cho phép một lớp kế thừa từ lớp khác và thêm các chức năng mới Lớp con kế thừa những thành viên của lớp cơ sở và có thể thêm thành viên mới Biến thành viên private trong lớp cơ sở không thể được truy xuất “theo tên” trong lớp con Hàm thành viên private không được kế thừa, chỉ được sử dụng riêng ở lớp cơ sở Có thể định nghĩa lại hàm thành viên của lớp cơ sở trong lớp con Các lớp con khác nhau có thể có những định nghĩa khác nhau Thành viên trong khu vực protected của lớp cơ sở có thể được truy xuất “theo tên” trong lớp con Giáo trình Tham khảo 48 Giáo trình chính: W. Savitch, Absolute C++, Addison Wesley, 2002 Tham khảo: A. Ford and T. Teorey, Practical Debugging in C++, Prentice Hall, 2002 Nguyễn Thanh Thủy, Kĩ thuật lập trình C++, NXB Khoa học và Kĩ Thuật, 2006
File đính kèm:
- bai_giang_ngon_ngu_lap_trinh_bai_6_nap_chong_toan_tu_va_ke_t.pdf