Ngôn ngữ lập trình - Bài 6: Nạp chồng toán tử và kế thừa

pdf 50 trang vanle 4050
Bạn đang xem 20 trang mẫu của tài liệu "Ngôn ngữ lập trình - Bài 6: Nạp chồng toán tử và kế thừa", để 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:

  • pdfngon_ngu_lap_trinh_bai_6_nap_chong_toan_tu_va_ke_thua.pdf

Nội dung text: Ngôn ngữ lập trình - Bài 6: Nạp chồng toán tử và kế thừa

  1. 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
  2. NỘI DUNG Nạp chồng toán tử (Operator Overloading) và Hàm bạn (Friend Functions) Kế thừa (Inheritance) 2 Bài giảng có sử dụng hình vẽ trong cuốn sách “Practical Debugging in C++, A. Ford and T. Teorey, Prentice Hall, 2002”
  3. 1. NẠP CHỒNG TOÁN TỬ VÀ HÀM BẠN Operator Overloading and Friend Functions
  4. MỤC TIÊU 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 4
  5. LỚP MONEY 5
  6. GIỚI THIỆU NẠP CHỒNG TOÁN TỬ 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 6  Hàm “+” trả lại giá trị là tổng của 2 đối số
  7. TẠI SAO DÙNG NẠP CHỒNG TOÁN TỬ? Những toán tử được xây dựng sẵn (built-in operators)  Ví dụ, +, -, = , %, ==, /, *  Đã thao tác được với các kiểu dựng sẵn của C++ (built-in types) Nhưng nếu chúng ta muốn thực hiện phép + với 2 đối tượng của lớp SinhVien ?, giống như: sinh_vien1 + sinh_vien2; Chúng ta có thể nạp chồng những toán tử này!  Để thao tác với kiểu của chúng ta! 7
  8. CƠ BẢN VỀ NẠP CHỒNG 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 8
  9. NẠP CHỒNG “+” 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, ) 9
  10. VÍ DỤ ĐỊNH NGHĨA NẠP CHỒNG TOÁN TỬ “+” CHO LỚP MONEY 10
  11. NẠP CHỒNG “==” 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 11
  12. HÀM TẠO TRẢ VỀ ĐỐI TƯỢNG Nhớ lại: hàm tạo là một hàm “void” (không có giá trị trả lại)  Là hàm đặc biệt với những thuộc tính đặc biệt Tại sao trong cài đặt nạp chồng toán tử “+” phía trên lại có phần trả về giá trị?: return Money (finalDollars, finalCents)?  Trả về một “lời gọi” (invocation) của lớp Money  Vì thế hàm tạo có thể “trả về” một đối tượng!  Được gọi là “đối tượng vô danh” (anonymous object) 12
  13. SỬ DỤNG CONST TRONG NẠP CHỒNG TOÁN TỬ Nhìn lại nạp chồng toán tử “+”: const Money operator +(const Money& amount1, const Money&amount2);  Tại sao lại trả về một “đối tượng Money constant” ? Ảnh hưởng của việc trả về một đối tượng “non-const”, ví dụ: Money operator +(const Money& amount1, const Money& amount2);  Xem xét biểu thức m1 + m2, với m1, m2 là 2 đối tượng của lớp Money  Kết quả trả về là một đối tượng của lớp Money => có thể thực hiện các thao tác như gọi hàm thành viên  (m1+m2).output(); //Hợp lệ và Không có vấn đề gì do không thay đổi dữ liệu  (m1+m2).input(); // Hợp lệ nhưng phát sinh VẤN ĐỀ do thay đổi dữ liệu  Cho phép thay đổi trên đối tượng vô danh => Không mong muốn 13 Vì thế nên định nghĩa đối tượng trả về với const
  14. NẠP CHỒNG TOÁN TỬ MỘT NGÔI C++ sử dụng một số toán tử một ngôi (unary operators) – 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 14
  15. NẠP CHỒNG TOÁN TỬ “-” CHO LỚP MONEY 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 (toán tử một ngôi) Đị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 (binary operator), với 2 toán hạng/đối số  Khi nó là toán tử 1 ngôi (unary operator), với 1 toán hạng/đối số 15
  16. SỬ DỤNG NẠP CHỒNG TOÁN TỬ “-” 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.output(); amount3 = -amount1;  => Gọi hàm nạp chồng toán tử 1 ngôi “-” 16
  17. NẠP CHỒNG TOÁN TỬ NHƯ HÀM THÀNH VIÊN (1/2) 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 17
  18. NẠP CHỒNG TOÁN TỬ NHƯ HÀM THÀNH VIÊN (2/2) 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  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); 18 Chú ý CHỈ CÓ MỘT đối số
  19. NẠP CHỒNG MỘT SỐ TOÁN TỬ KHÁC 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: ++, (increment and decrement operators)  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ử >>, << 19
  20. NẠP CHỒNG TOÁN TỬ >> VÀ > myObject;  Thay vì phải viết: myObject.output(); myObject.input(); 20
  21. TOÁN TỬ CHÈN << (INSERTION OPERATOR) (1/2) Đượ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: 21 cout << "I have "; amount.output()
  22. TOÁN TỬ CHÈN << (INSERTION OPERATOR) (2/2) 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; 22
  23. VÍ DỤ CHƯƠNG TRÌNH NẠP CHỒNG TOÁN TỬ > (1/5) 23
  24. VÍ DỤ CHƯƠNG TRÌNH NẠP CHỒNG TOÁN TỬ > (2/5) 24
  25. VÍ DỤ CHƯƠNG TRÌNH NẠP CHỒNG TOÁN TỬ > (3/5) 25
  26. VÍ DỤ CHƯƠNG TRÌNH NẠP CHỒNG TOÁN TỬ > (4/5) 26
  27. VÍ DỤ CHƯƠNG TRÌNH NẠP CHỒNG TOÁN TỬ > (5/5) 27
  28. HÀM BẠN (FRIEND FUNCTIONS) Không phải hàm thành viên của lớp  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 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 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 28 Sử dụng từ khóa friend ở trước khai báo hàm
  29. LỚP BẠN (FRIEND CLASSES) 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 29
  30. TÓM TẮT NẠP CHỒNG TOÁN TỬ VÀ HÀM BẠN 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 30
  31. 2. KẾ THỪA Inheritance
  32. MỤC TIÊU 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) 32
  33. GIỚI THIỆU VỀ KẾ THỪA 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 đó 33  Lớp con có thể mở rộng hay thay đổi chức năng cho phù hợp
  34. CƠ BẢN VỀ KẾ THỪA 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) 34  Lớp con cháu (Descendant class)
  35. LỚP THỪA KẾ (DERIVED CLASSES) 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 35
  36. ĐỊNH NGHĨA LẠI HÀM THÀNH VIÊN 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 (check) khác nhau 36
  37. VÍ DỤ GIAO DIỆN CHO LỚP THỪA KẾ HOURLYEMPLOYEE (1/2) 37
  38. VÍ DỤ GIAO DIỆN CHO LỚP THỪA KẾ HOURLYEMPLOYEE (2/2) 38
  39. GIAO DIỆN LỚP HOURLYEMPLOYEE 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 39  Hàm thành viên: setRate(), getRate(), setHours(), getHours()
  40. ĐỊNH NGHĨA LẠI HÀM THÀNH VIÊN TRONG LỚP HOURLYEMPLOYEE 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 40 Định nghĩa một hàm mới với tham số khác
  41. TRUY XUẤT HÀM ĐỊNH NGHĨA LẠI 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! 41
  42. HÀM TẠO TRONG LỚP THỪA KẾ (1/2) 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) {} 42
  43. HÀM TẠO TRONG LỚP THỪA KẾ (2/2) 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) { } 43
  44. LƯU Ý: DỮ LIỆU PRIVATE CỦA LỚP CƠ SỞ 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! 44
  45. LƯU Ý: HÀM THÀNH VIÊN PRIVATE CỦA LỚP CƠ SỞ 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 45
  46. KHU VỰC PROTECTED Một phân loại (classification) / 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 46
  47. ĐA KẾ THỪA (MULTIPLE INHERITANCE) 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 { } 47
  48. BÀI TẬP Đị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 (SalariedEmployees)  Nhân viên bán thời gian, theo giờ (HourlyEmployees)  Định nghĩa lại hàm printCheck() riêng của hai lớp con 48
  49. TÓM TẮT VỀ KẾ THỪA 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 49
  50. GIÁO TRÌNH THAM KHẢO 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 50