Bài giảng Lập trình hướng đối tượng

pdf 92 trang hapham 1390
Bạn đang xem 20 trang mẫu của tài liệu "Bài giảng Lập trình hướng đối tượng", để 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:

  • pdfbai_giang_lap_trinh_huong_doi_tuong.pdf

Nội dung text: Bài giảng Lập trình hướng đối tượng

  1. Simpo PDF Merge and Split Unregistered Version - Mởđầu Lập trình hướng đối tượng Giới thiệu môn học n Phần C++ đãhọc chỉđủđể viết những chương trình C++ nhỏ. n Sinh viên gặp nhiều khó khăn khi viết một chương trình lớn hoặc làm việc trong một nhóm cùng các lập trình viên khác? Làm thế nào đểđảm bảo tính thống nhất trong chương trình lớn hoặc giữa các lập trình viên khác nhau? n Trong môn học này, sinh viên sẽ học những kiến thức cơ bản về lập trình hướng đối tượng (Object-Oriented Programming-OOP). Mục đích là để học cách thiết kế vàviết những dự án phần mềm lớn. @ 2004 Trần Minh Châu. FOTECH. VNU 2
  2. Simpo PDF Merge and Split Unregistered Version - Lịch sử ngắn gọn Các ngôn ngữ lập trình thời kỳđầu như Basic, Fortran không cócấu trúc và cho phép viết những đoạn mã rối rắm (spaghetti code). Lập trình viên sử dụng các lệnh goto”và“gosub” để nhảy đến mọi nơi trong chương trình. 10k=1 20 gosub 100 30if y > 120 goto 60 lệnh nhảy đến vị tríbất kỳ 40k = k + 1 trong chương trình 50 goto 20 60print k, y 70stop 100y = 3*k*k + 7*k-3 110return Đoạn trình trên khótheo dõi, khóhiểu, dễ gây lỗi, khósửađổi. @ 2004 Trần Minh Châu. FOTECH. VNU 3 Lịch sử ngắn gọn Kiểu lập trình rối rắm trên dẫn int func(int j) { tới phong cách lập trình mới: return (3*j*j + 7*j-3; lập trình cấu trúc, với các } ngôn ngữ Algol, Pascal, C int main() { int k = 1 while (func(k) < 120) k++; printf("%d\t%d\n", k, func(k)); return(0); } Đặc điểm của lập trình cấu trúc hay lập trình thủ tục (Procedural Programming -PP) là: n Sử dụng các cấu trúc vòng lặp:for, while, repeat, do-while n Chương trình làmột chuỗi các hàm/ thủ tục n Mãchương trình tập trung thể hiện thuật toán: làm như thế nào. @ 2004 Trần Minh Châu. FOTECH. VNU 4
  3. Simpo PDF Merge and Split Unregistered Version - Hạn chế của lập trình thủ tục n dữ liệu vàphần xử lý tách biệt n dữ liệu thụđộng, xử lý chủđộng n không đảm bảo được tính nhất quán vàcác ràng buộc của dữ liệu ¨ khócấm mã ứng dụng sửa dữ liệu của thư viện struct Date { n khóbảo trìcode int day; ¨ int month; phần xử lý cóthể nằm rải rác vàphải hiểu rõ cấu int year; trúc dữ liệu }; void setDate(Date& date, int newDay, int newMonth, int newYear) { date.day = newDay; Chuyện gìxảy ra nếu các đối số newDay, newMonth, newYear } tạo thành ngày tháng năm không hợp lệ? @ 2004 Trần Minh Châu. FOTECH. VNU 5 Lập trình hướng đối tượng n Lập trình hướng đối tượng cho phép class Date { khắc phục các hạn chế nói trên public: void setDate(int newDay, int newMonth, int newYear); int getDay() { return day; } private: int day; int month; int year; }; void Date::setDate(int newDay, int newMonth, int newYear) { //check validity of newDay, newMonth, newYear //set new values } @ 2004 Trần Minh Châu. FOTECH. VNU 6
  4. Simpo PDF Merge and Split Unregistered Version - Lập trình hướng đối tượng Chúý: Lập trình hướng đối tượng không tự dưng cho ta thiết kế chương trình tốt. n Vídụ: hai đoạn trình dưới đây không cógìkhác nhau. Thậm chí đoạn chương trình hướng đối tượng còn tồi hơn. class Date { public: struct Date int getDay { return day;} { int day; int setDay(int newDay) { day = newDay; } int month; int year; private: }; int day; int month; int year; }; @ 2004 Trần Minh Châu. FOTECH. VNU 7 Lịch sử OOP n Các ngôn ngữ lập trình hướng đối tượng không mới ¨ Simula (1967) làngôn ngữđầu tiên, cólớp, thừa kế, liên kết động (hay còn gọi làhàm ảo) n Nhưng các ngôn ngữ hướng đối tượng chậm hơn các ngôn ngữ thời kỳđầu ¨ nên chúng chỉđược dùng rộng rãi khi máy tính bắt đầu chạy nhanh (khoảng thời gian chiếc máy Pentium đầu tiên ra đời) ¨ Lưu ý rằng biên dịch các chương trình hướng đối tượng cũng chậm @ 2004 Trần Minh Châu. FOTECH. VNU 8
  5. Simpo PDF Merge and Split Unregistered Version - Hướng đối tượng làgì? n Một số hệ thống“hướng đối tượng”thời kỳđầu không cócác lớp ¨ chỉ cócác“đối tượng”vàcác“thông điệp”(v.d. Hypertalk) n Hiện giờ, đãcósựthống nhất rằng hướng đối tượng là: ¨ lớp-class ¨ thừa kế -inheritance vàliên kết động-dynamic binding n Một sốđặc tính của lập trình hướng đối tượng cóthểđược thực hiện bằng C hoặc các ngôn ngữ lập trình thủ tục khác. n Điểm khác biệt sự hỗ trợ vàép buộc ba khái niệm trên được cài hẳn vào trong ngôn ngữ. n Mức độ hướng đối tượng của các ngôn ngữ không giống nhau ¨ Eiffel (tuyệt đối), Java (rất cao), C++ (nửa nọ nửa kia) @ 2004 Trần Minh Châu. FOTECH. VNU 9 Các đặc điểm quan trọng của OO encapsulation Tam giác n Các lớp đối tượng-Classes "P.I.E" ¨ Khả năng lưu trạng thái-State retention abstraction ¨ Định danh đối tượng-Object identity ¨ Các thông điệp-Messages inheritancepolymorphism n Đóng gói–Encapsulation ¨ Che dấu thông tin-Information/implementation hiding n Thừa kế -Inheritance n Đa hình-Polymorphism n Lập trình tổng quát-Genericity @ 2004 Trần Minh Châu. FOTECH. VNU 10
  6. Simpo PDF Merge and Split Unregistered Version - Đóng gói Che dấu thông tin n Đóng gói: Nhóm những gìcóliên quan với nhau vào làm một, để sau này cóthể dùng một cái tên để gọi đến ¨ Các hàm/ thủ tục đóng gói các câu lệnh ¨ Các đối tượng đóng gói dữ liệu của chúng vàcác thủ tục có liên quan n Che dấu thông tin: đóng gói để che một số thông tin và chi tiết cài đặt nội bộđể bên ngoài không nhìn thấy ¨ mục tiêu là để khách hàng của ta (thường làcác lập trình viên khác) coi các đối tượng của ta làcác hộp đen @ 2004 Trần Minh Châu. FOTECH. VNU 11 Đối tượng n Lưu giữ trạng thái: mỗi đối tượng cótrạng thái (dữ liệu của nó) vàcác thao tác ¨ cóthể nói đối tượng cómột dạng“ký ức”vềquákhứ ¨ các thao tác của đối tượng cóthể sửa trạng thái của đối tượng đó. n Định danh: Mỗi đối tượng bất kểđang ở trạng thái nào đều có định danh và được đối xử như một thực thể riêng biệt. ¨ mỗi đối tượng cómộthandle (trong C++ là địa chỉ) ¨ hai đối tượng cóthể cógiátrị giống nhau nhưng handle khác nhau ¨ ngôn ngữ Lisp phân biệt hai phép so sánh eq và equal @ 2004 Trần Minh Châu. FOTECH. VNU 12
  7. Simpo PDF Merge and Split Unregistered Version - Đối tượng n Thông điệp: là phương tiện để một đối tượng A chuyển tới đối tượng B yêu cầu B thực hiện một trong số các thao tác của B. n một thông điệp gồm 3 phần: ¨ handle của đối tượng đích(đối tượng chủ) ¨ tên thao tác cần thực hiện ¨ các thông tin cần thiết khác (các đối số) n thực ra làmột lời gọi hàm với một đối sốẩn là đối tượng chủ n tuy nhiên, khái niệm thông điệp cóý nghĩa lớn đối với OOP ¨ dữ liệu trở nên chủđộng ¨ đối lập với quan điểm cũ về lập trình rằng n mọi hành động được điều khiển tập trung n dữ liệu luôn luôn bịđộng, các thủ tục thao tác dữ liệu @ 2004 Trần Minh Châu. FOTECH. VNU 13 Lớp đối tượng-class n Lớp: làkhuôn mẫu để tạo các đối tượng (tạo các thể hiện). Mỗi đối tượng cócấu trúc vàhành vi giống như lớp đối tượng mànó được tạo từ đó ¨ VD. Lớp VánCờ, các ván cờ cụ thể làcác đối tượng VánCờ n Lớp làcái ta thiết kế vàlập trình n Đối tượng làcái ta tạo (từ một lớp) tại thời gian chạy @ 2004 Trần Minh Châu. FOTECH. VNU 14
  8. Simpo PDF Merge and Split Unregistered Version - SHAPE Thừa kế -color + draw() n là cơ chế cho phép CIRCLE RECTANGLE -radius -width một lớp D có được -length các thuộc tính vàthao tác của lớp C, như thể các thuộc tính vàthao tác đó đã được định nghĩa lại lớp D. n cho phép các phần mềm sử dụng quan hệ “là” n giúp ta thiết kế các dịch vụ tổng quát rồi chuyên môn hóa chúng @ 2004 Trần Minh Châu. FOTECH. VNU 15 Đa hình n Đa hình hàm-Functional polymorphism ¨ cơ chế cho phép một tên thao tác hoặc thuộc tính cóthểđược định nghĩa tại nhiều lớp vàcóthể cónhiều cài đặt khác nhau tại mỗi lớp trong các lớp đó n v.d. lớp Date cài 2phương thức setDate(),một nhận tham số làmột đối tượng Date, phương thức kia nhận 3 tham số day, month, year. n Đa hình đối tượng-Object polymorphism ¨ các đối tượng thuộc các lớp khác nhau cókhả năng hiểu cùng một thông điệp theo các cách khác nhau n vd. khi nhận được cùng một thông điệp draw(), các đối tượng Rectangle vàTriangle hiểu vàthực hiện các thao tác khác nhau @ 2004 Trần Minh Châu. FOTECH. VNU 16
  9. Simpo PDF Merge and Split Unregistered Version - Lập trình tổng quát–genericity n khả năng xây dựng một lớp C sao cho một hoặc nhiều lớp được sử dụng bên trong C sẽđược cung cấp tại thời gian chạy ¨ vd. kiểu ngăn xếp tổng quát, cóthể dùng để chứa các đối tượng Date, cóthể dùng cho các string n Thực tế với C++: ¨ cài đặt bằng khuôn mẫu–template hay các kiểu cótham số ¨ Lựa chọn khuôn mẫu được quyết định tại thời điểm biên dịch của chương trình khách hàng-user n không phải tại thời điểm biên dịch của chương trình của người thiết kế -thư viện @ 2004 Trần Minh Châu. FOTECH. VNU 17
  10. Simpo PDF Merge and Split Unregistered Version - Biên dịch Biên dịch riêng rẽ Lập trình hướng đối tượng Biên dịch n Chỉ hướng dẫn biên dịch trong môitrường Unix, sinh viên tự tìm hiểu đối với các môi trường lập trình khác. n Ta sẽ sử dụng g++ để dịch các chương trình C++. g++ foo.cpp n biên dịch foo.cpp cho kết quả làfile chạy được a.out g++-o foo foo.cpp n biên dịch foo.cpp cho kết quả làfile chạy được foo @ 2004 Trần Minh Châu. FOTECH. VNU 2
  11. Simpo PDF Merge and Split Unregistered Version - Biên dịch riêng rẽ n VD: biên dịch chương trình program.cpp trong đósửdụng một lớp cótên Picture để thao tác các hình vẽ n Nênlưu phần cài đặt của lớp Picture trong một file riêng, chẳng hạn picture.cpp, để: ¨ tạo thuận lợi cho việc sử dụng lớp này trong một ứng dụng khác ¨ hai lập trình viên cóthể dễ dàng cùng làm việc: một người cài đặt lớp Picture, người kia viết chương trình chính program.cpp ¨ khi chương trình thay đổi, chỉ cần dịch lại file program.cpp, như vậy, quátrình biên dịch nhanh hơn. Đối với các chương trình lớn, điều này tạo sự khác biệt rất lớn. n Chúý: Theo thông lệ, các file chương trình C++ thường cókiểu mở rộng ".cpp", ".cc", ".C", hoặc ".cxx". @ 2004 Trần Minh Châu. FOTECH. VNU 3 File header của lớp: ".h" n Nếu ta không muốn người viết program.cpp biết chi tiết của lớp Picture (vì đócóthể làbímật thương mại), ta cần tách giao diện của lớp (phần khai báo) ra khỏi cài đặt của lớp. n Mặt khác, để cóthể biên dịch được, chương trình chính program.cpp cũng cần biết vềđịnh nghĩa của lớp Picture vàcác phương thức của lớp đó. n Giải pháp làmô tả lớp Picture tại hai file ¨ picture.hcác định nghĩa vàkhai báo (giao diện) ¨ picture.cppcài đặt @ 2004 Trần Minh Châu. FOTECH. VNU 4
  12. Simpo PDF Merge and Split Unregistered Version - File header của lớp: ".h" File chứa cài đặt /* picture.h */ /* picture.cpp */ class Picture #include "picture.h" { Picture* Picture::frame(constPicture& x) // { Picture* frame(const Picture&); //mã đểđóng khung một hình ảnh } } File header chứa giao diện /* program.cpp */ #include "picture.h" int main() { //thao tác các hình ảnh } Client/user @ 2004 Trần Minh Châu. FOTECH. VNU 5 File header của lớp: ".h" Như vậy, ta cóthể viết nhiều chương trình sử dụng lớp Picture cósẵn một cách tiện lợi @ 2004 Trần Minh Châu. FOTECH. VNU 6
  13. Simpo PDF Merge and Split Unregistered Version - Biên dịch riêng rẽ n biên dịch chương trình như sau: 1> g++-c picture.cpp 2> g++-c program.cpp 3> g++-o program program.o picture.o ¨ khóa chuyển –c tại dòng 1 và2 tạo các object file program.o và picture.o. Dòng 3 tạo file chạy được cótên program với khóa chuyển –o bằng cách liên kết các object file với nhau. n Hoặc 1> g++-c picture.cpp 2> g++-o program program.cpp picture.o n Nếu program.cpp bị thay đổi nhưng Picture vẫn giữ nguyên, thìkhi biên dịch lại, dòng 1 làkhông cần thiết. @ 2004 Trần Minh Châu. FOTECH. VNU 7 Liên kết object file File thực thi File mã nguồn File object @ 2004 Trần Minh Châu. FOTECH. VNU 8
  14. Simpo PDF Merge and Split Unregistered Version - Các định hướng tiền xử lý n Các định hướng tiền xử lý làcác lệnh cótính năng đặc biệt n Được thực hiện bởi trình tiền xử lý trước khi mã nguồn được biên dịch. n Trong C++, các định hướng tiền xử lý bắt đầu bằng một dấu # n #include n #define, #ifndef, #endif @ 2004 Trần Minh Châu. FOTECH. VNU 9 Định hướng tiền xử lý #include n Định hướng #include đọc nội dung của file được nêu tên vào nơi đặt định hướng #include #include "my_file.h" n Cặp ngoặc nhọn dùng cho các file header chuẩn được tìm kiếm trong các thư mục thư viện chuẩn. n Cặp dấu nháy““dùng cho các file header của người dùng, sẽđược tìm kiếm trước hết trong thư mục hiện tại. ¨ Cóthể dùng khoáchuyển –I (g++-I) để thay đổi đường dẫn tìm kiếm. Vídụ: g++ program.cpp–I/home/tmct/my_include/ trong đó, /home/tmct/my_include/ là đường dẫn đầy đủđến các thư mục chứa các file .h cần tìm @ 2004 Trần Minh Châu. FOTECH. VNU 10
  15. Simpo PDF Merge and Split Unregistered Version - Các thư viện n Để tạo một file thực thi (executable file), trình liên kết (linker) cần kết nối mã của các hàm được khai báo trong các file header chuẩn C++ (iostream.h, string.h, v.v ) Các đoạn mã tương ứng cóthểđược tìm thấy trong các thư viện chuẩn C++ n Một thư viện làmột tập hợp các object file. n Trình liên kết lựa chọn mã object từ các thư viện chứa định nghĩa các hàm được sử dụng trong các file chương trình vàkết nối chúng vào file thực thi (executable file). n Một số thư viện được trình liên kết C++ tựđộng sử dụng, chẳng hạn thư viện chuẩn C++. Các thư viện khác phải được chỉ rõ trong quátrình liên kết bằng khoáchuyển –l. Vídụ, trong một số môitrường lập trình, cần lệnh sau để liên kết với thư viện toán học chuẩn libm.a g++-o myprog myprog.o–lm @ 2004 Trần Minh Châu. FOTECH. VNU 11 #define, #ifdef, #ifndef, #endif n #define định nghĩa một định danh ¨ #define MAX 100// từđây, MAX sẽ cógiátrị 100 ¨ #define DEBUG// định nghĩa DEBUG n #ifdef định hướng điều kiện "nếu đã định nghĩa" (if defined) ¨ #ifdef DEBUG// nếu DEBUG đã được định nghĩa n #ifndef định hướng điều kiện "nếu chưa định nghĩa" (if not defined) ¨ #ifndef DEBUG// nếu DEBUG chưa được định nghĩa n #endif kết thúc khối mởđầu bằng #ifndef hoặc #ifdef gần nhất ¨ nếu điều kiện tại định hướng mởđầu khối thỏa mãn thìbiên dịch đoạn lệnh nằm trong khối @ 2004 Trần Minh Châu. FOTECH. VNU 12
  16. Simpo PDF Merge and Split Unregistered Version - #define, #ifdef, #ifndef, #endif n Vídụsửdụng #define DEBUG DEBUG được định nghĩa, #ifdef DEBUG đoạn trình được biên dịch std::cerr << "Debug info: "; #endif //#define DEBUG #ifdef DEBUG DEBUG không được định nghĩa, std::cerr << "Debug info: "; đo n trình b b qua ạ ị ỏ #endif @ 2004 Trần Minh Châu. FOTECH. VNU 13 #define, #ifdef, #ifndef, #endif /* program.h */ /* b.h */ /* c.h */ #include "b.h" #include "a.h" #include "a.h" #include "c.h" #include "d.h" #include "e.h" n Do các định hướng #include cóthể lồng nhau, một file header cóthểđược kết nối hai lần. Hậu quả là ¨ file đó được xử lý nhiều lần à tốn thời gian, ¨ các hằng, macro, kiểu dữ liệu, nguyên mẫu hàm được khai báo nhiều lần à lỗi biên dịch. n Do vậy, ta cần các định hướng điều kiện (conditional directive) trong mọi file header #ifndef PICTURE_H #define PICTURE_H // các khai báo đối tượng, định nghĩa lớp, hàm #endif//PICTURE_H @ 2004 Trần Minh Châu. FOTECH. VNU 14
  17. Simpo PDF Merge and Split Unregistered Version - Operator Overloading Lập trình hướng đối tượng Tài liệu đọc n Eckel, Bruce. Thinking in C++, 2nd Ed. Vol. 1. ¨ Chapter 8: Operator Overloading n Dietel. C++ How to Program, 4th Ed. ¨ Chapter 8: Operator Overloading @ 2004 Trần Minh Châu. FOTECH. VNU 2
  18. Simpo PDF Merge and Split Unregistered Version - Operator Overloading n Giới thiệu n Làm việc với tính đóng gói n Các toán tử của C++ n friend n Lý thuyết về operator n Tại sao sử dụng toán tử toàn overloading cục n Phép chèn ("<<") n Cúpháp operator overloading n Phép tăng ("++") n Định nghĩa các toán tử thành n Các tham số vàkiểu trả về viên n Thành viên hay hàm toàn cục? n Phép gán n Chuyển đổi kiểu tựđộng n Định nghĩa các toán tử toàn cục @ 2004 Trần Minh Châu. FOTECH. VNU 3 Giới thiệu n Các toán tử cho phép ta sử dụng cúpháp toán học đối với các kiểu dữ liệu của C++ thay vìgọi hàm (tuy bản chất vẫn làgọi hàm). ¨ Vídụthay a.set(b.add(c)); bằng a = b + c; ¨ gần với kiểu trình bày mà con người quen dùng ¨ đơn giản hóa mãchương trình n C/C++ đãlàm sẵn cho các kiểu cài sẵn (int, float ) n Đối với các kiểu dữ liệu người dùng: C++ cho phép định nghĩa các toán tử cho các thao tác đối với các kiểu dữ liệu người dùng. n Đólàoperator overload ¨ một toán tử cóthể dùng cho nhiều kiểu dữ liệu n Như vậy, ta cóthể tạo các kiểu dữ liệu đóng gói hoàn chỉnh (fully- encapsulated) để kết hợp với ngôn ngữ như các kiểu dữ liệu cài sẵn @ 2004 Trần Minh Châu. FOTECH. VNU 4
  19. Simpo PDF Merge and Split Unregistered Version - Các toán tử của C++ n Các toán tửđược chia thành hai loại theo số toán hạng nóchấp nhận ¨ Toán tửđơn nhận một toán hạng ¨ Toán tửđôi nhận hai toán hạng n Các toán tửđơn lại được chia thành hai loại ¨ Toán tử trước đặt trước toán hạng ¨ Toán tử sau đặt sau toán hạng operators Unary operatorBinary operator Prefix operator Postfix operator (!, &, ~, ++, , ) (++, , ) @ 2004 Trần Minh Châu. FOTECH. VNU 5 Các toán tử của C++. n Một số toán tửđơn cóthểđược dùng làm cả toán tử trước vàtoán tử sau ¨ Vídụphép tăng ("++") vàphép giảm (" ") n Một số toán tử cóthểđược dùng làm cả toán tửđơn vàtoán tửđôi ¨ Vídụ, "*" làtoán tửđơn trong phép truy nhập con trỏ, làtoán tửđôi trong phép nhân n Toán tử chỉ mục ("[ ]") làtoán tửđôi, mặc dùmột trong hai toán hạng nằm trong ngoặc ¨ Phép lấy chỉ mục códạng "arg1[arg2]" n Các từ khoá"new" và"delete" cũng được coi làtoán tử vàcóthể được định nghĩa lại (overload) @ 2004 Trần Minh Châu. FOTECH. VNU 6
  20. Simpo PDF Merge and Split Unregistered Version - Các toán tử overload được n Phần lớn các toán tử của C++ đều cóthể overload được, bao gồm: +-*/% ^&|!= +=-=*=/= ~=%=^=&=|= >>= =&&||++ ,->->*()[] newdeletenew[]delete[] @ 2004 Trần Minh Châu. FOTECH. VNU 7 Các toán tử khôngoverload được n Các toán tử C++ không cho phép overload * :::? typeidsizeof const_castdynamic_cast reinterpret_caststatic_cast @ 2004 Trần Minh Châu. FOTECH. VNU 8
  21. Simpo PDF Merge and Split Unregistered Version - Các hạn chế đối với việc overload toán tử n Không thể tạo toán tử mới hoặc kết hợp các toán tử cósẵn theo kiểu mà trước đóchưa được định nghĩa n Không thể thay đổi thứ tựưu tiên của các toán tử n Không thể tạo cúpháp mới cho toán tử n Không thểđịnh nghĩa lại một định nghĩa cósẵn của một toán tử ¨ Vídụ: không thể thay đổi định nghĩa cósẵn của phép ("+") đối với hai số kiểu int ¨ Như vậy, khi tạo định nghĩa mới cho một toán tử,ít nhất một trong số các tham số (toán hạng) của toán tửđóphải làmột kiểu dữ liệu người dùng @ 2004 Trần Minh Châu. FOTECH. VNU 9 Lưu ýkhi định nghĩa lại toán tử n Tôn trọng ý nghĩa của toán tử gốc, cung cấp chức năng màngười dùng mong đợi/chấp nhận ¨ không ai nghĩ rằng phép "+" sẽ in kết quả ra màn hình hoặc thực hiện phép chia ¨ Sử dụng "+" để nối hai xâu cóthể hơi lạđối với người chỉ quen dùng "+" cho các số, nhưng nóvẫn tuân theo khái niệm chung của phép cộng n Nên cho kiểu trả về của toán tử khớp với định nghĩa cho các kiểu cài sẵn ¨ không nên trả về giátrị int từ phép so sánh == của mảng số, nên trả về bool n Cố gắng tái sử dụng mã nguồn một cách tối đa ¨ Ta sẽ thường xuyên định nghĩa các toán tử sử dụng các định nghĩa có sẵn @ 2004 Trần Minh Châu. FOTECH. VNU 10
  22. Simpo PDF Merge and Split Unregistered Version - Cúpháp của Operator Overloading class MyNumber { public: MyNumber(int value = 0); ~MyNumber(); private: int value; }; n Ta sẽ sử dụng vídụtrên: ¨ Đây làlớp bọc ngoài (wrapper class) cho kiểu int n Ta sẽ overload các toán tửđể cho phép cộng, trừ, so sánh, các đối tượng của lớp @ 2004 Trần Minh Châu. FOTECH. VNU 11 Cúpháp của Operator Overloading n Khai báo và định nghĩa toán tử thực chất không khác với việc khai báo và định nghĩa nghĩa một loại hàm bất kỳ nào khác n sử dụng tên hàm là"operator@" cho toán tử "@" ¨ để overload phép "+", ta dùng tên hàm "operator+" n Số lượng tham số tại khai báo phụ thuộc hai yếu tố: ¨ Toán tử làtoán tửđơn hay đôi ¨ Toán tửđược khai báo làhàm toàn cục hay phương thức của lớp aa@bb è aa.operator@(bb) hoặc operator@(aa,bb) @aa è aa.operator@( ) hoặc operator@(aa) aa@ è aa.operator@(int) hoặc operator@(aa,int) làphương thức của lớp làhàm toàn cục @ 2004 Trần Minh Châu. FOTECH. VNU 12
  23. Simpo PDF Merge and Split Unregistered Version - Cúpháp của Operator Overloading MyNumber x(5); n Vídụ: Sử dụng toán tử "+" để cộng hai đối tượng MyNumber y(10); MyNumber vàtrả về kết quả làmột MyNumber z = x + y; n Ta cóthể khai báo hàm toàn cục sau const MyNumber operator+(const MyNumber& num1, const MyNumber& num2); ¨ "x+y" sẽđược hiểu là"operator+(x,y)" ¨ dùng từ khoáconst đểđảm bảo các toán hạng gốc không bị thay đổi n Hoặc khai báo toán tử dưới dạng thành viên của MyNumber: const MyNumber operator+(const MyNumber& num); ¨ đối tượng chủ của phương thức được hiểu làtoán hạng thứ nhất của toán tử. ¨ "x+y" sẽđược hiểu là"x.operator+(y)" @ 2004 Trần Minh Châu. FOTECH. VNU 13 Cúpháp của Operator Overloading n Sau khi đãkhai báo toán tử bị overload (làphương thức hay hàm toàn cục), cúpháp định nghĩa không cógìkhó ¨ Định nghĩa toán tử dạng phương thức không khác với định nghĩa phương thức bất kỳ khác ¨ Định nghĩa toán tử dạng hàm toàn cục không khác với định nghĩa hàm toàn cục bất kỳ khác n Tuy nhiên, cómột số vấn đề liênquan đến hướng đối tượng(đặc biệt làtính đóng gói) màta cần xem xét @ 2004 Trần Minh Châu. FOTECH. VNU 14
  24. Simpo PDF Merge and Split Unregistered Version - Toán tử làhàm thành viên n Để bắt đầu, xét định nghĩa toán tử bên trong giới hạn lớp n Vídụ:định nghĩa phép cộng: const MyNumber MyNumber::operator+(const MyNumber& num) { MyNumber result(this->value + num.value); return result; } ¨ Constructor cho MyNumber và định nghĩa cósẵn của phép cộng cho int được tái sử dụng ¨ Tạo một đối tượng MyNumber sử dụng constructor với giátrị làtổng hai giátrị thành viên của các tham số tính bằng phép cộng đãcósẵn cho kiểu int. n Ta sẽ lấy một vídụthường gặp khác làphép gán @ 2004 Trần Minh Châu. FOTECH. VNU 15 Phép gán "=" n Một trong những toán tử hay được overload nhất ¨ Cho phép gán cho đối tượng này một giátrị dựa trên một đối tượng khác ¨ Copy constructor cũng thực hiện việc tương tự, cho nên, định nghĩa toán tử gán gần như giống hệt định nghĩa của copy constructor n Ta cóthể khai báo phép gán cho lớp MyNumber như sau: const MyNumber& operator=(const MyNumber& num); ¨ Phép gán nên luôn luôn trả về một tham chiếu tới đối tượng đích(đối tượng được gán trị cho) ¨ Tham chiếu được trả về phải là const để tránh trường hợp a bị thay đổi bằng lệnh "(a = b) = c;" (lệnh đókhôngtương thích với định nghĩa gốc của phép gán) @ 2004 Trần Minh Châu. FOTECH. VNU 16
  25. Simpo PDF Merge and Split Unregistered Version - Phép gán "=" const MyNumber& MyNumber::operator=(const MyNumber& num) { if (this != &num) { this->value = num.value; } return *this; } n Định nghĩa trên cóthể dùng cho phép gán ¨ Lệnh if dùng để ngăn chặn các vấn để cóthể nảy sinh khi một đối tượng được gán cho chính nó(thídụkhi sử dụng bộ nhớđộng để lưu trữ các thành viên) ¨ Ngay cả khi gán một đối tượng cho chính nólàan toàn, lệnh if trên đảm bảo không thực hiện các công việc thừa khi gán @ 2004 Trần Minh Châu. FOTECH. VNU 17 Phép gán "=" n Khi nói về copy constructor, ta đãbiết rằng C++ luôn cung cấp một copy constructor mặc định, nhưng nóchỉ thực hiện sao chép đơn giản (sao chép nông) n Đối với phép gán cũng vậy n Vậy, ta chỉ cần định nghĩa lại phép gán nếu: ¨ Ta cần thực hiện phép gán giữa các đối tượng ¨ Phép gán nông (memberwise assignment) không đủ dùng vì n ta cần sao chép sâu-chẳng hạn sử dụng bộ nhớđộng n Khi sao chép đòi hỏi cả tính toán-chẳng hạn gán một số hiệu có giátrị duy nhất hoặc tăng sốđếm @ 2004 Trần Minh Châu. FOTECH. VNU 18
  26. Simpo PDF Merge and Split Unregistered Version - Toán tử làhàm toàn cục n Quay lại với vídụvềphép cộng cho MyNumber, ta cóthể khai báo hàm định nghĩa phép cộng tại mức toàn cục: const MyNumber operator+(const MyNumber& num1, const MyNumber& num2); n Khi đó, ta cóthểđịnh nghĩa toán tửđónhư sau: const MyNumber operator+(const MyNumber& num1, const MyNumber& num2) { MyNumber result(num1.value + num2.value); return result; } ¨ Ởđây cóvấnđề . truy nhập các thành viên private value @ 2004 Trần Minh Châu. FOTECH. VNU 19 Làm việc với tính đóng gói n Rắc rối: hàm toàn cục muốn truy nhập thành viên private của lớp ¨ thông thường: không được quyền ¨ đôi khi bắt buộc phải overload bằng hàm toàn cục ¨ không thể hy sinh tính đóng gói của hướng đối tượng để chuyển các thành viên private thành public n Giải pháp làdạng cuối cùng của quyền truy nhập: friend @ 2004 Trần Minh Châu. FOTECH. VNU 20
  27. Simpo PDF Merge and Split Unregistered Version - friend n Khái niệm friend cho phép một lớp cấp quyền truy nhập tới các phần nội bộ của lớp đócho một số cấu trúc được chọn n C++ có3 kiểu friend ¨ Hàm friend (trong đócócác toán tửđược overload) ¨ Lớp friend ¨ Phương thức friend (hàm thành viên) n Các tính chất của quan hệ friend ¨ Phải được cho, không tự nhận ¨ Không đối xứng ¨ Không bắc cầu @ 2004 Trần Minh Châu. FOTECH. VNU 21 Hàm friend n Khi khai báo một hàm bên ngoài là friend của một lớp, hàm đó được cấp quyền truy nhập tương đương quyền của các phương thức của lớp đó ¨ Như vậy, một hàm friend cóthể truy nhập cả các thành viên private và protected của lớp đó n Để khai báo một hàm là friend của một lớp, ta phải khai báo hàm đóbên trong khai báo lớp và đặt từ khoá friend lên đầu khai báo. Vídụ: class MyNumber { public: MyNumber(int value = 0); ~MyNumber(); friend const MyNumber operator+(const MyNumber& num1, const MyNumber& num2); }; @ 2004 Trần Minh Châu. FOTECH. VNU 22
  28. Simpo PDF Merge and Split Unregistered Version - Hàm friend n Lưu ý: tuy khai báo của hàm friend được đặt trong khai báo lớp vàhàm đócóquyền truy nhập ngang với các phương thức của lớp, hàm đó không phải phương thức của lớp n Không cần thêm sửa đổi gìcho định nghĩa của hàm đã được khai báo là friend. ¨ Định nghĩa trước của phép cộng vẫn giữ nguyên const MyNumber operator+(const MyNumber& num1, const MyNumber& num2) { MyNumber result(num1.value + num2.value); return result; } @ 2004 Trần Minh Châu. FOTECH. VNU 23 Tại sao dùng toán tử toàn cục? n Đối với toán tửđược khai báo làphương thức của lớp, đối tượng chủ (xác định bởi con trỏ this) luôn được hiểu làtoán hạng đầu tiên (trái nhất) của phép toán. ¨ Nếu muốn dùng cách này, ta phải được quyền bổ sung phương thức vào định nghĩa của lớp/kiểu của toán hạng trái n Không phải lúc nào cũng cóthể overload toán tử bằng phương thức ¨ phép cộng giữa MyNumber và int cần cả hai cách MyNumber + intvà int + MyNumber ¨ cout << obj; ¨ không thể sửa định nghĩa kiểu int hay kiểu của cout ¨ lựa chọn duy nhất: overload toán tử bằng hàm toàn cục @ 2004 Trần Minh Châu. FOTECH. VNU 24
  29. Simpo PDF Merge and Split Unregistered Version - Toán tử chèn (‘<<‘) n prototype như thế nào? xét vídụ: ¨ cout << num; // num là đối tượng thuộc lớp MyNumber n Toán hạng trái cout thuộc lớp ostream, không thể sửa định nghĩa lớp này nên ta overload bằng hàm toàn cục n Tham số thứ nhất : tham chiếu tới ostream n Tham số thứ hai : kiểu MyNumber, ¨ const (do không cólý do gì để sửa đối tượng được in ra) n giátrị trả về: tham chiếu tới ostream (để thực hiện được cout << num1 << num2;) n Kết luận: ostream& operator<<(ostream& os, const MyNumber& num) @ 2004 Trần Minh Châu. FOTECH. VNU 25 Toán tử chèn ("<<") n Khai báo toán tửđược overload là friend của lớp MyNumber class MyNumber { public: MyNumber(int value = 0); ~MyNumber(); friend ostream& operator<<( ostream& os, const MyNumber& num); }; @ 2004 Trần Minh Châu. FOTECH. VNU 26
  30. Simpo PDF Merge and Split Unregistered Version - Toán tử chèn (" >") được overload tương tự, tuy nhiên, định nghĩa thường phức tạp hơn ¨ do cóthể phải xử lý input để kiểm tra tính hợp lệ tuỳ theo cách ta quy định như thế nào khi in một đối tượng ra thành một chuỗi ký tự @ 2004 Trần Minh Châu. FOTECH. VNU 27 Phép tăng ("++") @aa è aa.operator@( ) hoặc operator@(aa) aa@ è aa.operator@(int) hoặc operator@(aa,int) n Khi gặp phép tăng trong một lệnh, trình biên dịch sẽ sinh một trong 4 lời gọi hàm trên, tuỳ theo toán tử làtoán tử trước (prefix) hay toán tử sau (postfix), làphương thức hay hàm toàn cục. n Giả sử ta overload phép tăng dưới dạng phương thức của MyNumber vàoverload cả hai dạng đặt trước và đặt sau ¨ Nếu gặp biểu thức dạng "x++", trình biên dịch sẽ sinh lời gọi MyNumber::operator++() ¨ Nếu gặp biểu thức dạng"++x", trình biên dịch sẽ sinh lời gọi MyNumber::operator++(int) ¨ Tham số int chỉ dành để phân biệt danh sách tham số của hai dạng prefix vàpostfix @ 2004 Trần Minh Châu. FOTECH. VNU 28
  31. Simpo PDF Merge and Split Unregistered Version - Phép tăng ("++") n giátrị trả về ¨ tăng trước ++num n trả về tham chiếu (MyNumber &) n giátrị trái-lvalue (cóthểđược gán trị) ¨ tăng sau num++ n trả về giátrị (giátrị cũ trước khi tăng) n trả vềđối tượng tạm thời chứa giátrị cũ. n giátrị phải-rvalue (không thể làm đích của phép gán) n prototype ¨ tăng trước: MyNumber& MyNumber::operator++() ¨ tăng sau: const MyNumber MyNumber::operator++(int) @ 2004 Trần Minh Châu. FOTECH. VNU 29 Phép tăng ("++") n Nhớ lại rằng phép tăng trước tăng giátrị trước khi trả kết quả, trong khi phép tăng sau trả lại giátrị trước khi tăng n Ta định nghĩa từng phiên bản của phép tăng như sau: MyNumber& MyNumber::operator++() { // Prefix this->value++;// Increment value return *this; // Return current MyNumber } const MyNumber MyNumber::operator++(int) { // Postfix MyNumber before(this->value); // Create temporary MyNumber // with current value this->value++;// Increment value return before;// Return MyNumber before increment } before làmộtđối tượng địa phương của phương thức vàsẽchấm dứt tồn tại khi lời gọi hàm kết thúc Không thể trả về tham chiếu Khi đó, tham chiếu tới nótrở thành bất hợp lệ @ 2004 Trần Minh Châu. FOTECH. VNU 30
  32. Simpo PDF Merge and Split Unregistered Version - Tham số vàkiểu trả về n Cũng như khi overload các hàm khác, khi overload một toán tử, ta cũng cónhiều lựa chọn về việc truyền tham số vàkiểu trả về ¨ chỉ cóhạn chế rằngít nhất một trong các tham số phải thuộc kiểu người dùng tựđịnh nghĩa n Ởđây, ta cómột số lời khuyên về các lựa chọn @ 2004 Trần Minh Châu. FOTECH. VNU 31 Tham số vàkiểu trả về n Các toán hạng: ¨ Nên sử dụng tham chiếu mỗi khi cóthể (đặc biệt làkhi làm việc với các đối tượng lớn) ¨ Luôn luôn sử dụng tham số làhằng tham chiếu khi đối số sẽ không bị sửa đổi bool String::operator==(const String &right) const n Đối với các toán tử làphương thức, điều đócónghĩa ta nên khai báo toán tử làhằng thành viên nếu toán hạng đầu tiên sẽ không bị sửa đổi n Phần lớn các toán tử (tính toán vàso sánh) không sửa đổi các toán hạng của nó, do đóta sẽ rất hay dùng đến hằng tham chiếu @ 2004 Trần Minh Châu. FOTECH. VNU 32
  33. Simpo PDF Merge and Split Unregistered Version - Tham số vàkiểu trả về n Giátrị trả về ¨ không cóhạn chế về kiểu trả vềđối với toán tửđược overload, nhưng nên cố gắng tuân theo tinh thần của các cài đặt cósẵn của toán tử n Vídụ, các phép so sánh (==, != ) thường trả về giátrị kiểu bool, nên các phiên bản overload cũng nên trả về bool ¨ làtham chiếu (tới đối tượng kết quả hoặc một trong các toán hạng) hay một vùng lưu trữ mới ¨ Hằng hay không phải hằng @ 2004 Trần Minh Châu. FOTECH. VNU 33 Tham số vàkiểu trả về n Giátrị trả về ¨ Các toán tử sinh một giátrị mới cần cókết quả trả về làmột giá trị (thay vìtham chiếu), vàlà const(đểđảm bảo kết quảđó không thể bị sửa đổi như một l-value) n Hầu hết các phép toán số học đều sinh giátrị mới n ta đãthấy, các phép tăng sau, giảm sau tuân theo hướng dẫn trên ¨ Các toán tử trả về một tham chiếu tới đối tượng ban đầu(đãbị sửađổi), chẳng hạn phép gán vàphép tăng trước, nên trả về tham chiếu không phải làhằng n để kết quả cóthểđược tiếp tục sửa đổi tại các thao tác tiếp theo const MyNumber MyNumber::operator+(const MyNumber& right) const MyNumber& MyNumber::operator+=(const MyNumber& right) @ 2004 Trần Minh Châu. FOTECH. VNU 34
  34. Simpo PDF Merge and Split Unregistered Version - Tham số vàkiểu trả về n Lời khuyên cuối cùng: ¨ Xem lại cách ta đãdùng để trả về kết quả của toán tử: const MyNumber MyNumber::operator+(const MyNumber& num) { MyNumber result(this->value + num.value); return result; } ¨ Cách trên khôngsai, nhưng C++ cung cấp một cách hiệu quả hơn @ 2004 Trần Minh Châu. FOTECH. VNU 35 Tham số vàkiểu trả về 1. Gọi constructor để tạo n Trình tự thực hiện cách cũ: đối tượng result const MyNumber MyNumber::operator+(const MyNumber& num) { 2. Gọi copy-constructor MyNumber result(this->value + num.value); để tạo bản sao dành return result; cho giátrị trả về khi hàm } thoát 3. Gọi destructor để huỷ đối tượng result n Cách tốt hơn: const MyNumber MyNumber::operator+(const MyNumber& num) { return MyNumber(this->value + num.value); } @ 2004 Trần Minh Châu. FOTECH. VNU 36
  35. Simpo PDF Merge and Split Unregistered Version - Tham số vàkiểu trả về return MyNumber(this->value + num.value); n Cúpháp của vídụtrước tạo một đối tượng tạm thời (temporary object) n Khi trình biên dịch gặp đoạn mã này, nóhiểu đối tượng được tạo chỉ nhằm mục đích làm giátrị trả về, nên nótạo thẳng một đối tượng bên ngoài(để trả về)-bỏqua việc tạo vàhuỷđối tượng bên trong lời gọi hàm n Vậy, chỉ cómột lời gọi duy nhất đến constructor của MyNumber (không phải copy-constructor) thay vìdãy lời gọi trước n Quátrình này được gọi làtốiưu hoágiátrị trả về n Ghi nhớ rằng quátrình này không chỉ áp dụng được đối với các toán tử. Ta nên sử dụng mỗi khi tạo một đối tượng chỉđể trả về @ 2004 Trần Minh Châu. FOTECH. VNU 37 Phương thức hay hàm toàn cục? Khi lựa chọn overload toán tử tại lớp hoặc tại mức toàn cục, trường hợp nào nên chọn kiểu nào? n Một số toán tử phải làthành viên: ¨ "=", "[]", "()", và"->", "->*" phải làthành viên n Các toán tửđơn nên làthành viên(đểđảm bảo tính đóng gói) n Khi toán hạng trái cóthểđược gán trị, toán tử nên là thành viên ("+=", "-=", "/=", ) n Mọi toán tửđôi khác không nên làthành viên ¨ Trừ khi ta muốn các toán tử này làhàm ảo trong cây thừa kế @ 2004 Trần Minh Châu. FOTECH. VNU 38
  36. Simpo PDF Merge and Split Unregistered Version - Phương thức hay hàm toàn cục? n Các toán tử làthành viên nên là hằng hàm mỗi khi có thể ¨ Điều này cho phép tính mềm dẻo khi làm việc với hằng n Nếu ta cảm thấy không nên cho phép sử dụng một toán tử nào đóvới lớp của ta (vàkhông muốn các nhàthiết kế khác định nghĩa nó), ta khai báo toán tửđódạng private (vàkhông cài đặt toán tửđó) @ 2004 Trần Minh Châu. FOTECH. VNU 39 Vídu:Kiểu Date n Date class ¨ Overload phép tăng n thay đổi ngày, tháng, năm ¨ Overloaded += ¨ hàm kiểm tra năm nhuận ¨ hàm kiểm tra xem một ngày cóphải cuối tháng @ 2004 Trần Minh Châu. FOTECH. VNU 40
  37. 1 //Fig. 8.10: date1.h Simpo2 /PDF/Date Merge class and definition. Split Unregistered Version - 3 #ifndef DATE1_H 4 #define DATE1_H 5 #include 6 7 using std::ostream; 8 9 class Date { 10 friend ostream&operatordate1.h<<(ostream&, const (1Date of& ); 2) 11 12 public: 13 Date( int m = 1, int d = 1, int y = 1900 ); // constructor 14 void setDate( int, int, int ); // set the date 15 Lưu ý sự khác nhau giữa tăng 16 Date&operator++(); //preincrementrước vàttăoperatorng sau. 17 Date operator++( int ); //postincrementoperator 18 19 const Date &operator+=( int ); // add days, modify object 20 21 bool leapYear( int ) const; // is this a leap year? 22 bool endOfMonth( int ) const; // is this end of month? 23 24 private: 25 int month; 26 int day; 27 int year; 28 29 staticconstint days[]; // array of days per month 30 void helpIncrement(); // utility function 31 32 }; // end class Date date1.h (2 of 2) 33 34 #endif
  38. 1 //Fig. 8.11: date1.cpp Simpo2 // PDF Date Merge class and member Split Unregisteredfunction definitions. Version - 3 #include 4 #include "date1.h" 5 6 // initialize static member at file scope; 7 // one class-wide copy 8 constint Date::days[] = 9 { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; 10 11 //Date constructor 12 Date::Date( int m, int d, int y ) 13 { 14 setDate( m, d, y ); 15 16 } // end Date constructor 17 18 // set month, day and year 19 void Date::setDate( int mm, int dd, int yy) 20 { 21 month = ( mm >= 1 && mm = 1900 &&yy = 1 &&dd = 1 &&dd<= days[ month ] ) ?dd:1; 29 30 } // end functionsetDate 31 32 //overloadedpreincrementoperator 33 Date&Date::operator++() 34 { 35 helpIncrement(); 36 37 return *this; // reference return to create an lvalue 38 Lưu ý: biến int không cótên. 39 } // end function operator++ 40 41 // overloaded postincrement operator;P hnoteép tă nthatg sau stheửa g idummyátrị của đối tượng vàtrả về một bản 42 // integer parameter does not have a saoparameter của đối t ượnameng ban đầ u.Không trả về tham số tới biến 43 Date Date::operator++( int ) t ạ m vì đ ó làm ộ t bi ế n đị a phương vàsẽbịhủy. 44 { 45 Date temp = *this; // hold current state of object 46 helpIncrement(); 47 48 // return unincremented, saved, temporary object 49 return temp; // value return; not a reference return 50 51 } // end function operator++
  39. 53 // add specified number of days to date Simpo54 const PDF MergeDate & Dandate:: Splitoperator Unregistered+=( int VersionadditionalDays - ) 55 { 56 for ( int i = 0; i < additionalDays; i++ ) 57 helpIncrement(); 58 59 return *this; // enables cascading 60 61 } // end function operator+= 62 63 // if the year is a leap year, return true; 64 // otherwise, return false 65 bool Date::leapYear( int testYear)const 66 { 67 if (testYear%400 == 0 || 68 (testYear%100 != 0 &&testYear%4== 0 ) ) 69 returntrue; // a leap year 70 else 71 returnfalse; // not a leap year 72 73 } // end functionleapYear 74 75 // determine whether the day is the last day of the month 76 bool Date::endOfMonth( int testDay ) const 77 { 78 if ( month == 2 && leapYear( year ) ) 79 return testDay == 29; // last day of Feb. in leap year 80 else 81 return testDay == days[ month ]; 82 83 } // end function endOfMonth 84
  40. 85 // function to help increment the date Simpo86 PDF void MergeDate::helpIncrement() and Split Unregistered Version - 87 { 88 // day is not end of month 89 if ( !endOfMonth( day ) ) 90 ++day; 91 92 else 93 94 // day is end ofdate1.cp month and month < 12 p(4 of 5) 95 if ( month < 12 ) { 96 ++month; 97 day = 1; 98 } 99 100 // last day of year 101 else { 102 ++year; 103 month = 1; 104 day = 1; 105 } 106 107 } // end function helpIncrement 108 109 // overloaded output operator 110 ostream&operator<<(ostream&output, const Date &d ) 111 { 112 static char *monthName[ 13 ] = { "", "January", 113 "February", "March", "April", "May", "June", 114 "July", "August", "September", "October", 115 "November", "December" }; 116 117 output <<monthName[ d.month ] << ' ' 118 << d.day << ", " << d.year; 119 120 return output; // enables cascading 121 122 } // end function operator<<
  41. 1 //Fig. 8.12: fig08_12.cpp Simpo2 PDF // MergeDate class and Splittest Unregistered program. Version - 3 #include 4 5 using std::cout; 6 using std::endl; 7 8 #include "date1.h" // Date class definition 9 10 int main() 11 { 12 Date d1; // defaults to January 1, 1900 13 Date d2( 12, 27, 1992 ); 14 Date d3( 0, 99, 8045 ); // invalid date 15 16 cout<< "d1 is " << d1 << "\nd2 is " << d2 17 << "\nd3 is " << d3; 18 19 cout<< "\n\nd2 += 7 is " << ( d2 += 7 ); 20 21 d3.setDate( 2, 28, 1992 ); 22 cout<< "\n\n d3 is " << d3; 23 cout<< "\n++d3 is " << ++d3; 24 25 Date d4( 7, 13, 2002 ); 26 27 cout<< "\n\nTestingthepreincrementoperator:\n" 28 << " d4 is " << d4 << '\n'; 29 cout<< "++d4 is " << ++d4 << '\n'; 30 cout<< " d4 is " << d4; 31 32 cout<< "\n\nTestingthepostincrementoperator:\n" 33 << " d4 is " fig0<< d4 << '\n'8_12.cpp; 34 cout<< "d4++ is " << d4++ << '\n'; 35 cout<< " d4 is " << d4 <<endl; 36 (2 of 2) 37 return 0; 38 39 } // end main
  42. d1 is January 1, 1900 Simpod2 isPDF Decem Mergeber and27, Split1992 Unregistered Version - d3 is January 1, 1900 d2 += 7 is January 3, 1993 d3 is February 28, 1992 ++d3 is February 29, 1992 Testing thepreincrementoperator: d4 is July 13, 2002 ++d4 is July 14, 2002 d4 is July 14, 2002 Testing thepostincrementoperator: d4 is July 14, 2002 d4++ is July 14, 2002 d4 is July 15, 2002 Đổi kiểu tựđộng Automatic type conversion n Ta đãnói về nhiều công việc mà đôi khi C++ ngầm thực hiện n Một trong những việc đólàthực hiện đổi kiểu tựđộng ¨ Vídụ, nếu ta gọi một hàm đòi hỏi một kiểu dữ liệu cósẵn nhưng ta cho hàm một kiểu hơi khác, C++ đôi khi sẽ tựđổi kiểu tham số truyền vào void foo(double x); foo(2); n Đối với các lớp dữ liệu người dùng, C++ cũng cung cấp khả năng định nghĩa các phép chuyển đổi tựđộng @ 2004 Trần Minh Châu. FOTECH. VNU 52
  43. Simpo PDF Merge and Split Unregistered Version - Đổi kiểu tựđộng n Cóhai cách định nghĩa phép đổi kiểu tựđộng ¨ Khai báo và định nghĩa một constructor lấy một tham số duy nhất class MyNumber { public: MyNumber(int value = 0); Đổi từ int sang MyNumber ¨ Khai báo và định nghĩa một toán tửđặc biệt (special overloaded operator) class MyOtherNumber { public: MyOtherNumber( ); Đổi từ MyOtherNumber sang MyNumber operator MyNumber() const; @ 2004 Trần Minh Châu. FOTECH. VNU 53 Đổi kiểu tựđộng-constructor n Cách 1: định nghĩa một constructor lấy một tham số duy nhất (giátrị hoặc tham chiếu) thuộc một kiểu nào đó, constructor đósẽđược ngầm gọi khi cần đổi kiểu. class MyNumber { void foo(MyNumber num) { public: MyNumber(int value = 0); } int x = 5; foo(x); // Automatically converts the argument @ 2004 Trần Minh Châu. FOTECH. VNU 54
  44. Simpo PDF Merge and Split Unregistered Version - Đổi kiểu tựđộng-constructor ¨ Nếu ta muốn ngăn chặn đổi kiểu tựđộng kiểu này, ta cóthể dùng từ khoá explicit khi khai báo constructor class MyNumber { public: explicit MyNumber(int value = 0); ¨ Kết quả làviệc đổi kiểu chỉ xảy ra khi được gọi một cách tường minh int x = 5; Foo(x); // This will generate an error Foo(MyNumber(x)); // Explicit conversion-ok @ 2004 Trần Minh Châu. FOTECH. VNU 55 Đổi kiểu tựđộng–toán tử n Cách 2: định nghĩa phép đổi kiểu sử dụng một toán tửđặc biệt ¨ Tạo một toán tử làthành viên của lớp cần đổi, toán tử này sẽ chuyển thành viên của lớp này sang kiểu mong muốn ¨ Toán tử này hơi đặc biệt: không cókiểu trả về n Thực ra, tên của toán tử chính làkiểu trả về @ 2004 Trần Minh Châu. FOTECH. VNU 56
  45. Simpo PDF Merge and Split Unregistered Version - Đổi kiểu tựđộng–toán tử n Giả sử cần một phép chuyển đổi tựđộng từ kiểu MyOtherNumber sang kiểu MyNumber ¨ khai báo một toán tử cótên MyNumber : class MyOtherNumber { public: MyOtherNumber( ); operator MyNumber() const; ¨ Thân toán tử chỉ cần tạo vàtrả về một thể hiện của lớp đích n Nên làhàm inline operator MyNumber() const { return MyNumber( ); } Các tham số cần thiết để tạo thể hiện mới @ 2004 Trần Minh Châu. FOTECH. VNU 57 Đổi kiểu tựđộng-đổi kiểu ẩn class Fee { public: 3. Không có copy constructor để Fee(int) {} tạo Fee từ Fee, }; nhưng lại cócopy constructor mặc định do trình biên dịch tự sinh class Fo { (phép gán mặc định) int i; public: Fo(int x = 0) : i(x) {} operator Fee() const { return Fee(i); } }; int main() { Fo fo; Fee fee = fo; 2. Tuy nhiên, cóphép } ///:~ chuyển đổi tựđộng từ Fo sang Fee 1. Không cóconstructor tạo Fee từ Fo @ 2004 Trần Minh Châu. FOTECH. VNU 58
  46. Simpo PDF Merge and Split Unregistered Version - Đổi kiểu tựđộng n Nói chung, nên tránh đổi kiểu tựđộng, do chúng cóthể dẫn đến các kết quả người dùng không mong đợi ¨ Nếu cóconstructor lấy đúng một tham số khác kiểu lớp chủ, hiện tượng đổi kiểu tựđộng sẽ xảy ra ¨ Chính sách an toàn nhất làkhai báo các constructor là explicit, trừ khi kết quả của chúng đúng làcái màngười dùng mong đợi @ 2004 Trần Minh Châu. FOTECH. VNU 59
  47. Simpo PDF Merge and Split Unregistered Version - Template Lập trình hướng đối tượng Tài liệu đọc n Eckel, Bruce. Thinking in C++, 2nd Ed. Vol. 1. ¨ Chapter 16: Introduction to Templates n Dietel. C++ How to Program, 4th Ed. ¨ Chapter 11: Templates @ 2004 Trần Minh Châu. FOTECH. VNU 2
  48. Simpo PDF Merge and Split Unregistered Version - Giới thiệu về khuôn mẫu n Giới thiệu n Lập trình tổng quát (generic programming) n Lập trình tổng quát trong C n C++ template n Khuôn mẫu hàm n Khuôn mẫu lớp n Các tham số template khác n Template sử dụng template @ 2004 Trần Minh Châu. FOTECH. VNU 3 Giới thiệu n Trong suốt khoáhọc, ta đãnói về phương pháp hướng đối tượng như làmột cơ chế cho việc trừu tượng hoá ¨ Nhóm các đối tượng cócùng tập hành vi vàthuộc tính lại với nhau, vàtương tác với chúng theo cùng kiểu n Với đa hình vàthừa kế, ta cóthể biểu diễn mối quan hệ giữa các lớp đối tượng tương tự vàtương tác với chúng một cách thống nhất ¨ Ta cóthể lệnh cho một chiếc xe chạy bằng máy thực hiện việc "drive", vàhành vi thích hợp sẽđược gọi tuỳ theo loại xe đang nói đến n Để kết thúc, ta sẽ giới thiệu một kiểu trừu tượng hoákhác @ 2004 Trần Minh Châu. FOTECH. VNU 4
  49. Simpo PDF Merge and Split Unregistered Version - Lập trình tổng quát n Ta đãtập trung vào việc trừu tượng hoácác chi tiết để tạo được các lớp đối tượng-gồm những thứ cócùng tập thuộc tính vàhành vi n Mọi ý tưởng được bàn đến đều xoay quanh đối tượng- biểu diễn khái niệm hoặc biểu diễn trong C++ n Bây giờ, ta nói về một phương pháp lập trình màtrừu tượng hoáchính các lớp đối tượng. n Đây làmức tiếp theo trong quátrình logic phát triển của trừu tượng hoá. @ 2004 Trần Minh Châu. FOTECH. VNU 5 Lập trình tổng quát n Lập trình tổng quát làphương pháp lập trình độc lập với chi tiết biểu diễn dữ liệu ¨ Tư tưởng làta định nghĩa một khái niệm không phụ thuộc một biểu diễn cụ thể nào, vàsau đómới chỉ ra kiểu dữ liệu thích hợp làm tham số n Qua các vídụ, ta sẽ thấy đây làmột phương pháp tự nhiên tuân theo khuôn mẫu hướng đối tượng theo nhiều kiểu @ 2004 Trần Minh Châu. FOTECH. VNU 6
  50. Simpo PDF Merge and Split Unregistered Version - Lập trình tổng quát n Ta đãquen với ý tưởng cómột phương thức được định nghĩa sao cho khi sử dụng với các lớp khác nhau, nósẽ đáp ứng một cách thích hợp ¨ Khi nói vềđa hình, nếu phương thức "draw" được gọi cho một đối tượng bất kỳ trong cây thừa kế Shape, định nghĩa tương ứng sẽđược gọi đểđối tượng được vẽđúng ¨ Trong trường hợp này, mỗi hình đòi hỏi một định nghĩa phương thức hơi khác nhau đểđảm bảo sẽ vẽ ra hình đúng n Nhưng nếu định nghĩa hàm cho các kiểu dữ liệu khác nhau nhưng không cần phải khác nhau thìsao? @ 2004 Trần Minh Châu. FOTECH. VNU 7 Lập trình tổng quát n Vídụ, xét hàm sau: void swap(int& a, int& b) { int temp; temp = a; a = b; b = temp; ¨ Hàm trên} chỉ cần hoán đổi giátrị chứa trong hai biến int. ¨ Nếu ta muốn thực hiện việc tương tự cho một kiểu dữ liệu khác, chẳng hạn float? void swap(float& a, float& b) { float temp; temp = a; a = b; b = temp; } ¨ Cóthực sự cần đến cả hai phiên bản không? @ 2004 Trần Minh Châu. FOTECH. VNU 8
  51. Simpo PDF Merge and Split Unregistered Version - Lập trình tổng quát class Stack { public: n Vídụkhác:ta định nghĩa một lớp Stack(); biểu diễn cấu trúc ngăn xếp cho kiểu int ~Stack(); void push(const int& i); void pop(int& i); bool isEmpty() const; }; n Ta thấy khai báo và định nghĩa của Stack phụ thuộc tại một mức độ nào đó vào kiểu dữ liệu int ¨ Một số phương thức lấy tham số vàtrả về kiểu int ¨ Nếu ta muốn tạo ngăn xếp cho một kiểu dữ liệu khác thìsao? ¨ Ta cónên định nghĩa lại hoàn toàn lớp Stack (kết quả sẽ tạo ra nhiều lớp chẳng hạn IntStack, FloatStack, ) hay không? @ 2004 Trần Minh Châu. FOTECH. VNU 9 Lập trình tổng quát n Ta thấy, trong một số trường hợp, đưa chi tiết về kiểu dữ liệu vào trong định nghĩa hàm hoặc lớp là điều không cólợi ¨ Trong khi ta cần các định nghĩa khác nhau cho "draw" của Point hay Circle, vấn đề khác hẳn với trường hợp một hàm chỉ cónhiệm vụ hoán đổi hai giátrị n Thực ra, khái niệm lập trình tổng quát học theo sự sử dụng một phương pháp của lớp cơ sở cho các thể hiện của các lớp dẫn xuất ¨ Vídụ, trong cây thừa kế khỉ, ta muốn cùng một phương thức eatBanana() được thực thi, bất kể con trỏ/tham chiếu đang chỉ tới một Monkey hay LazyMonkey n Với lập trình tổng quát, ta tìm cách mở rộng sự trừu tượng hoára ngoài địa hạt của các cây thừa kế @ 2004 Trần Minh Châu. FOTECH. VNU 10
  52. Simpo PDF Merge and Split Unregistered Version - Lập trình tổng quát trong C Sử dụng trình tiền xử lý của C ¨ Trình tiền xử lý thực hiện thay thế text trước khi dịch ¨ Do đó, ta cóthể dùng #define để chỉ ra kiểu dữ liệu vàthay đổi tại chỗ khi cần #define TYPE int void swap(TYPE & a, TYPE & b) { TYPE temp; temp = a; a = b; b = temp; Trình tiền xử lý sẽ thay } mọi "TYPE" bằng "int" trước khi thực hiện biên dịch Hai hạn chế: ¨ nhàm chán vàdễlỗi ¨ chỉ cho phép đúng một định nghĩa trong một chương trình @ 2004 Trần Minh Châu. FOTECH. VNU 11 C++ template n Template (khuôn mẫu) làmột cơ chế thay thế mã cho phép tạo các cấu trúc màkhông phải chỉ rõ kiểu dữ liệu n Từ khoátemplate được dùng trong C++ để báo cho trình biên dịch rằng đoạn mã theo sau sẽ thao tác một hoặc nhiều kiểu dữ liệu chưa xác định ¨ Từ khoátemplate được theo sau bởi một cặp ngoặc nhọn chứa tên của các kiểu dữ liệu tuỳ ý được cung cấp template Chúý: // Declaration that makes reference to a data type "T" Một lệnh template chỉ cóhiệu quảđối template với khai báo // Declaration that makes reference to a data type "T" ngay sau nó // and a datatype "U" @ 2004 Trần Minh Châu. FOTECH. VNU 12
  53. Simpo PDF Merge and Split Unregistered Version - C++ template n Hai loại khuôn mẫu cơ bản: ¨ Function template–khuôn mẫu hàm cho phép định nghĩa các hàm tổng quát dùng đến các kiểu dữ liệu tuỳ ý ¨ Class template–khuôn mẫu lớp cho phép định nghĩa các lớp tổng quát dùng đến các kiểu dữ liệu tuỳ ý n Ta sẽ mô tả từng loại trước khi đi bàn đến những phức tạp của lập trình khuôn mẫu @ 2004 Trần Minh Châu. FOTECH. VNU 13 Khuôn mẫu hàm n Khuôn mẫu hàm làdạng khuôn mẫu đơn giản nhất cho phép ta định nghĩa các hàm dùng đến các kiểu dữ liệu tuỳ ý n Định nghĩa hàm swap() bằng khuôn mẫu: template void swap(T & a, T & b) { T temp; temp = a; a = b; b = temp; } n Phiên bản trên trông khágiống với phiên bản swap() bằng C sử dụng #define, nhưng nómạnh hơn nhiều @ 2004 Trần Minh Châu. FOTECH. VNU 14
  54. Simpo PDF Merge and Split Unregistered Version - Khuôn mẫu hàm n Thực chất, khi sử dụng template, ta đã định nghĩa một tậpvô hạn các hàm chồng nhau với tên swap() n Để gọi một trong các phiên bản này, ta chỉ cần gọi nó với kiểu dữ liệu tương ứng int x = 1, y = 2; float a = 1.1, b = 2.2; swap(x, y); // Invokes int version of swap() swap(a, b); // Invokes float version of swap() @ 2004 Trần Minh Châu. FOTECH. VNU 15 Khuôn mẫu hàm n Chuyện gìxảy ra khi ta biên dịch mã? ¨ Trước hết, sự thay thế "T" trong khai báo/định nghĩa hàm swap() không phải thay thế text đơn giản và cũng không được thực hiện bởi trình tiền xử lý ¨ Việc chuyển phiên bản mẫu của swap() thành các cài đặt cụ thể cho int và float được thực hiện bởi trình biên dịch @ 2004 Trần Minh Châu. FOTECH. VNU 16
  55. Simpo PDF Merge and Split Unregistered Version - Khuôn mẫu hàm n Hãy xem xét hoạt động của trình biên dịch khi gặp lời gọi swap() thứ nhất (với hai tham số int) ¨ Trước hết, trình biên dịch tìm xem cómột hàm swap() được khai báo với 2 tham số kiểu int hay không n Nókhông tìm thấy một hàm thích hợp, nhưng tìm thấy một template cóthể dùng được ¨ Tiếp theo, nóxem xét khai báo của template swap() để xem có thể khớp được với lời gọi hàm hay không n Lời gọi hàm cung cấp hai tham số thuộc cùng một kiểu (int) n Trình biên dịch thấy template chỉ ra hai tham số thuộc cùng kiểu T, nên nókết luận rằng T phải làkiểu int n Do đó, trình biên dịch kết luận rằng template khớp với lời gọi hàm @ 2004 Trần Minh Châu. FOTECH. VNU 17 Khuôn mẫu hàm n Khi đãxác định được template khớp với lời gọi hàm, trình biên dịch kiểm tra xem đãcómột phiên bản của swap() với hai tham số kiểu int được sinh ra từ template hay chưa ¨ Nếu đãcó, lời gọi được liên kết (bind) với phiên bản đã được sinh (lưu ý: khái niệm liên kết này giống với khái niệm ta đãnói đến trong đa hình tĩnh) ¨ Nếu không, trình biên dịch sẽ sinh một cài đặt của swap() lấy hai tham số kiểu int (thực ra làviết đoạn mã màta sẽ tạo nếu ta tự mình viết)–vàliên kết lời gọi hàm với phiên bản vừa sinh. @ 2004 Trần Minh Châu. FOTECH. VNU 18
  56. Simpo PDF Merge and Split Unregistered Version - Khuôn mẫu hàm n Vậy, đến cuối quy trình biên dịch đoạn mã trong vídụ, sẽcóhai phiên bản của swap() được tạo (một cho hai tham số kiểu int, một cho hai tham số kiểu float) với các lời gọi hàm của ta được liên kết với phiên bản thích hợp ¨ Vậy, ta cóthểđoán rằng cóchi phíphụ về thời gian biên dịch đối với việc sử dụng template ¨ Ngoài ra còn cóchi phíphụ về khônggian liên quan đến mỗi cài đặt của swap() được tạo trong khi biên dịch ¨ Tuy nhiên, tính hiệu quả của các cài đặt đócũng không khác với khi ta tự cài đặt chúng. @ 2004 Trần Minh Châu. FOTECH. VNU 19 Khuôn mẫu hàm n Cần ghi nhớ rằng tuy trình biên dịch đãtạo các phiên bản của swap() cho các tham số int vàfloat, không tồn tại các hàm swap(int,int) hay swap(float, float) ¨ Thay vào đó, cómột hàm swap () vàswap () n Khi được dùng với một cấu trúc template, cặp ngoặc nhọn được dùng để chỉ rõ kiểu dữ liệu cần đến n Thực tế, ta cóthể sửa đoạn mãtrước để gọi các hàm trên một cách tường minh: int x = 1, y = 2; float a = 1.1, b = 2.2; swap (x, y); // Invokes int version of Swap() swap (a, b); // Invokes float version of Swap() @ 2004 Trần Minh Châu. FOTECH. VNU 20
  57. Simpo PDF Merge and Split Unregistered Version - Khuôn mẫu lớp n Tương tự với khuôn mẫu hàm với tham số thuộc các kiểu tuỳ ý, ta cũng cóthểđịnh nghĩa khuôn mẫu lớp (class template) sử dụng các thể hiện của một hoặc nhiều kiểu dữ liệu tuỳ ý ¨ Ta cũng cóthểđịnh nghĩa template cho struct và union n Khai báo một khuôn mẫu lớp cũng tương tự với khuôn mẫu hàm @ 2004 Trần Minh Châu. FOTECH. VNU 21 Khuôn mẫu lớp Vídụ, ta sẽ tạo một cấu trúc cặp đôi giữ một cặp giátrị thuộc kiểu tuỳ ý struct Pair { n Trước hết, xét khai báo Pair int first; int second; cho một cặp giátrị kiểu int: }; n Ta cóthể sửa khai báo trên thành template một khuôn mẫu lấy kiểu tuỳ ý: struct Pair { T first; Tuy nhiên hai thành viên first vàsecond T second; phải thuộc cùng kiểu }; n Hoặc ta cóthể cho phép hai template struct Pair { thành viên nhận các kiểu dữ T first; liệu khác nhau: U second; }; @ 2004 Trần Minh Châu. FOTECH. VNU 22
  58. Simpo PDF Merge and Split Unregistered Version - Khuôn mẫu lớp n Để tạo các thể hiện của template Pair, ta phải dùng ký hiệu cặp ngoặc nhọn ¨ Khác với khuôn mẫu hàm khi ta cóthể bỏ qua kiểu dữ liệu cho các tham số, đối với khuôn mẫu class/struct/union, chúng phải được cung cấp tường minh Pair p;// Not permitted Pair q;// Creates a pair of ints Pair r; // Creates a pair with an int and a float n Tại sao đòi hỏi kiểu tường minh? ¨ Các lệnh trên làm gì?-cấp phát bộ nhớ cho đối tượng ¨ Nếu không biết các kiểu dữ liệu được sử dụng, trình biên dịch làm thế nào để biết cần đến bao nhiêu bộ nhớ? @ 2004 Trần Minh Châu. FOTECH. VNU 23 Khuôn mẫu lớp n Cũng như khuôn mẫu hàm, không có struct Pair màchỉ cócác struct cótên Pair , Pair , Pair , n Quy trình tạo các phiên bản struct Pair từ khuôn mẫu cũng giống như đối với khuôn mẫu hàm n Khi trình biên dịch lần đầu gặp khai báo dùng Pair , nókiểm tra xem struct đó đãtồn tại chưa, nếu chưa, nósinh một khai báo tương ứng. ¨ Đối với các khuôn mẫu cho class, trình biên dịch sẽ sinh cả các định nghĩa phương thức cần thiết để khớp với khai báo class. @ 2004 Trần Minh Châu. FOTECH. VNU 24
  59. Simpo PDF Merge and Split Unregistered Version - Khuôn mẫu lớp n Một khi đãtạođược một thể hiện của một khuôn mẫu class/struct/union, ta cóthể tương tác với nónhư thể nó làthể hiện của một class/struct/union thôngthường. Pair q; Pair r; q.first = 5; q.second = 10; r.first = 15; r.second = 2.5; n Tiếp theo, ta sẽ tạo một template cho lớp Stack đã được mô tả trong các slice trước @ 2004 Trần Minh Châu. FOTECH. VNU 25 Khuôn mẫu lớp n Khi thiết kế khuôn mẫu (cho lớp hoặc hàm),thông thường, ta nên tạo một phiên bản cụ thể trước, sau đó mới chuyển nóthành một template ¨ Vídụ, ta sẽ bắt đầu bằng việc cài đặt hoàn chỉnh Stack cho số nguyên n Điều đócho phép phát hiện các vấn đề về khái niệm trước khi chuyển thành phiên bản cho sử dụng tổng quát ¨ khi đó, ta cóthể test tương đối đầy đủ lớp Stack cho số nguyên để tìm các lỗi tổng quát màkhông phải quan tâm đến các vấn đề liên quan đến template @ 2004 Trần Minh Châu. FOTECH. VNU 26
  60. Simpo PDF Merge and Split Unregistered Version - Stack cho số nguyên n Khai báo và định nghĩa lớp Stack cho kiểu int ¨ Bắt đầu bằng một ngăn xếp đơn giản class Stack { public: Stack(); ~Stack(); void push(const int& i) throw (logic_error); void pop(int& i) throw (logic_error); bool isEmpty() const; bool isFull() const; private: static const int max = 10; int contents[max]; int current; }; @ 2004 Trần Minh Châu. FOTECH. VNU 27 Stack::Stack() { this->current = 0; } Stack::~Stack() {} void Stack::push(const int& i) throw(logic_error) { if (this->current max) { this->contents[this->current++] = i; } else { throw logic_error(“Stack is full.”); } } void Stack::pop(int& i)throw(logic_error){ if (this->current > 0) { i = this->contents[ this->current]; } else { throw logic_error(“Stack is empty.”); } } bool Stack::isEmpty() const { return (this->current == 0;) } bool Stack::isFull() const { return (this->current == this->max); } @ 2004 Trần Minh Châu. FOTECH. VNU 28
  61. Simpo PDF Merge and Split Unregistered Version - Template Stack n Chuyển khai báo và định nghĩa trước thành một phiên bản tổng quát: template class Stack { public: Thêm lệnh template để Stack(); nói rằng một phần của kiểu ~Stack(); sẽđược chỉ rõ sau void push(const T& i) throw (logic_error); void pop(T& i) throw (logic_error); bool isEmpty() const; bool isFull() const; private: static const int max = 10; T contents[max]; int current; }; @ 2004 Trần Minh Châu. FOTECH. VNU 29 template Stack ::Stack() { this->current = 0; } Mỗi phương thức cần một template lệnh template đặt trước Stack ::~Stack() {} template void Stack ::push(const T& i) { Mỗi khi dùng toán tử phạm vi, if (this->current max) { cần một ký hiệu ngoặc nhọn kèm this->contents[this->current++] = i; theo tên kiểu } else { Ta đang định nghĩa một lớp throw logic_error(“Stack is full.”); Stack , chứ không định } nghĩa lớp Stack } template void Stack ::pop(T& i) { if (this->current > 0) { i = this->contents[ this->current]; Thay thế kiểu của đối tượng } else { được lưu trong ngăn xếp throw logic_error(“Stack is empty.”); (trước là int) bằng kiểu tuỳ } ý T } template bool Stack ::isEmpty() const { return (this->current == 0;) } template bool Stack ::isFull() const { return (this->current == this->max); } @ 2004 Trần Minh Châu. FOTECH. VNU 30
  62. Simpo PDF Merge and Split Unregistered Version - Template Stack n Sau đó, ta cóthể tạo vàsửdụng các thể hiện của các lớp được định nghĩa bởi template của ta: int x = 5, y; char c = 'a', d; Stack s; Stack t; s.push(x); t.push(c); s.pop(y); t.pop(d); @ 2004 Trần Minh Châu. FOTECH. VNU 31 Các tham số khuôn mẫu khác n Ta mới nói đến các lệnh template với tham số thuộc "kiểu" typename n Tuy nhiên, còn cóhai "kiểu" tham số khác ¨ Kiểu thực sự (vídụ:int) ¨ Các template @ 2004 Trần Minh Châu. FOTECH. VNU 32
  63. Simpo PDF Merge and Split Unregistered Version - Các tham số khuôn mẫu khác n Nhớ lại rằng trong cài đặt Stack, ta cómột hằng max quy định số lượng tối đa các đối tượng màngăn xếp cóthể chứa ¨ Như vậy, mỗi thể hiện sẽ cócùng kích thước đối với mọi kiểu của đối tượng được chứa n Nếu ta không muốn đòi hỏi mọi Stack đều cókích thước tối đa như nhau? n Ta cóthể thêm một tham số vào lệnh template chỉ ra một số int (giátrị này sẽđược dùng để xác định giátrị cho max) template // Specifies that one arbitrary type T and one int I // will be parameters in the following statement ¨ Lưu ý: ta khai báo tham số int giống như trong các khai báo khác @ 2004 Trần Minh Châu. FOTECH. VNU 33 Các tham số khuôn mẫu khác n Sửa khai báo và định nghĩa trước để sử dụng tham số mới: template class Stack { public: Khai báo tham số mới Stack(); ~Stack(); void push(const T& i) throw (logic_error); void pop(T& i) throw (logic_error); bool isEmpty() const; Sử dụng tham số mới để bool isFull() const; xác định giátrị max của private: một lớp thuộc một kiểu static const int max = I; nào đó T contents[max]; int current; }; @ 2004 Trần Minh Châu. FOTECH. VNU 34
  64. Simpo PDF Merge and Split Unregistered Version - Các tham số khuôn mẫu khác template Stack ::Stack() { this->current = 0; } Sửa các lệnh template template Sửa tên lớp Stack ::~Stack() {} dùng cho các toán tử phạm vi template void Stack ::push(const T& i) { if (this->current max) { this->contents[this->current++] = i; } else { throw logic_error(“Stack is full.”); } } @ 2004 Trần Minh Châu. FOTECH. VNU 35 Các tham số khuôn mẫu khác n Giờ ta cóthể tạo các thể hiện của các lớp Stack với các kiểu dữ liệu vàkích thước đa dạng Stack s; // Creates an instance of a Stack // class of ints with max = 5 Stack t; // Creates an instance of a Stack // class of ints with max = 10 Stack u; // Creates an instance of a Stack // class of chars with max = 5 ¨ Lưu ý rằng các lệnh trên tạo thể hiện của 3 lớp khác nhau @ 2004 Trần Minh Châu. FOTECH. VNU 36
  65. Simpo PDF Merge and Split Unregistered Version - Các tham số khuôn mẫu khác n Các ràng buộc khi sử dụng các kiểu thực sự làm tham số cho lệnh template: ¨ Chỉ cóthể dùng các kiểu số nguyên, con trỏ, hoặc tham chiếu ¨ Không được gán trị cho tham số hoặc lấy địa chỉ của tham số @ 2004 Trần Minh Châu. FOTECH. VNU 37 Các tham số khuôn mẫu khác n Loại tham số thứ ba cho lệnh template chính làmột template n Vídụ, xét thiết kế khuôn mẫu cho một lớp Map (ánh xạ) ánh xạ các khoátới các giátrị ¨ Lớp này cần lưu cácánh xạ từ khoátới giátrị, nhưng ta không muốn chỉ ra kiểu của các đối tượng được lưu trữ ngay từđầu ¨ Ta sẽ tạo Map làmột khuôn mẫu sao cho cóthể sử dụng các kiểu khác nhau cho khoávàgiátrị ¨ Tuy nhiên, ta cần chỉ ra lớp chứa (container) làmột template, để nócóthể lưu trữ các khoávàgiátrị làcác kiểu tuỳ ý @ 2004 Trần Minh Châu. FOTECH. VNU 38
  66. Simpo PDF Merge and Split Unregistered Version - Các tham số khuôn mẫu khác n Ta cóthể khai báo lớp Map: template Container> class Map { private: Container keys; Container values; }; n Sau đócóthể tạo các thể hiện của Map như sau: Map wordcount; ¨ Lệnh trên tạo một thể hiện của lớp Map chứa các thành viên làmột tập các string vàmột tập các int (giả sử còn cócác đoạn mã thực hiệnánh xạ mỗi từ tới một số int biểu diễn số lần xuất hiện của từđó) ¨ Ta đãdùng template Stack để làm container lưu trữ các thông tin trên @ 2004 Trần Minh Châu. FOTECH. VNU 39 Các tham số khuôn mẫu khác ¨ Như vậy, khi trình biên dịch sinh các khai báo và định nghĩa thực sự cho các lớp Map, nósẽđọc các tham số mô tả các thành viên dữ liệu ¨ Khi đó, nósẽsửdụng khuôn mẫu Stack để sinh mã cho hai lớp Stack và Stack ¨ Đến đây, ta phải hiểu rõ tại sao container phải làmột khuôn mẫu, nếu không, làm thế nào để cóthể dùng nó để tạo các loại stack khác nhau? @ 2004 Trần Minh Châu. FOTECH. VNU 40
  67. Simpo PDF Merge and Split Unregistered Version - Từ C đến C++ Lập trình hướng đối tượng Tài liệu đọc n Eckel, Bruce. Thinking in C++, 2nd Ed. Volume 1. ¨ Chapter 3: The C in C++ ¨ Chapter 8: Constants n Up to p. 352: Classes ¨ Chapter 10: Name Control n Up to p. 423: Static members in C++ ¨ Chapter 13: Dynamic Object Creation n Up to p. 566: Overloading new & delete ¨ Chapter 11: References and the Copy-Constructor n Up to p. 452: References in Functions @ 2004 Trần Minh Châu. FOTECH. VNU 2
  68. Simpo PDF Merge and Split Unregistered Version - Khác biệt đối với C n Các khác biệt đối với C (ngoài các đặc điểm hướng đối tượng) ¨ Chúthích ¨ Các kiểu dữ liệu ¨ Kiểm tra kiểu, đổi kiểu ¨ Cảnh báo của trình biên dịch ¨ Phạm vi vàkhai báo ¨ Không gian tên ¨ Hằng ¨ Quản lý bộ nhớ ¨ Tham chiếu @ 2004 Trần Minh Châu. FOTECH. VNU 3 Chúthích n Bên cạnh chúthích kiểu C (nhiều dòng), C++ cho phép kiểu chúthích dòng đơn C C++ /* This is a variable */ // This is a variable int x; int x; /* This is the variable // This is the variable * being given a value */ // being given a value x = 5; x = 5; @ 2004 Trần Minh Châu. FOTECH. VNU 4
  69. Simpo PDF Merge and Split Unregistered Version - Chúthích n C++ cho phép kiểu chúthích /* */ bao ngoài các chúthích dòng đơn. C C++ /* /* /* This is a variable */ // This is a variable int x; int x; /* This is the variable // This is the variable * being given a value */ // being given a value x = 5; x = 5; */ */ Chúthích cólỗi @ 2004 Trần Minh Châu. FOTECH. VNU 5 Kiểu dữ liệu n Kiểu giátrị Boolean: bool ¨ Hai giátrị: true hoặc false ¨ Các toán tử logic (!, &&, ) lấy/tạo một giátrị bool ¨ Các phép toán quan hệ (==, <, ) tạo một giátrị bool ¨ Các lệnh điều kiệu (if, while, ) đòi hỏi một giátrị bool n Để tương thích ngược với C, C++ ngầm chuyển từ int sang bool khi cần ¨ Giátrị 0 → false ¨ Giátrị khác 0 → true @ 2004 Trần Minh Châu. FOTECH. VNU 6
  70. Simpo PDF Merge and Split Unregistered Version - Kiểm tra kiểu dữ liệu n C++ kiểm soát kiểu dữ liệu chặt chẽ hơn C n C++ đòi hỏi hàm phải được khai báo trước khi sử dụng (mọi lời gọi hàm được kiểm tra khi biên dịch) n C++ không cho phép gán giátrị nguyên cho các biến kiểu enum enum Temperature {hot, cold}; enum Temperature t = 1; // Error in C++ n C++ không cho phép các con trỏ không kiểu (void*) sử dụng trực tiếp tại bên phải lệnh gán hoặc một lệnh khởi tạo void * vp; int * ip = vp; // Error: Invalid conversion @ 2004 Trần Minh Châu. FOTECH. VNU 7 Đổi vàép kiểu dữ liệu n C++ cho phép người dùng đổi kiểu dữ liệu một cách khá rộng rãi n Trình biên dịch tựđộng thực hiện nhiều chuyển đổi dễ thấy: ¨ Gán một giátrị thuộc kiểu số học này cho một biến thuộc kiểu khác ¨ Các kiểu số học khác nhau cùng cótrong các biểu thức ¨ Truyền đối số cho các hàm n Nếu hiểu rõ khi nào các chuyển đổi này xảy ra vàtrình biên dịch đang làm gì,ta cóthể giải thích được các kết quả khôngmong đợi @ 2004 Trần Minh Châu. FOTECH. VNU 8
  71. Simpo PDF Merge and Split Unregistered Version - Đổi vàép kiểu dữ liệu n Tựđộng chuyển đổi từ các đối tượng nhỏ thành các đối tượng lớn thìkhông cóvấnđề gì, chiều ngược lại cóthể cóvấnđề ¨ short → long (~16 bits → ~32 bits) không cóvấnđề ¨ long → short (~32 bits → ~16 bits) cóthể mất dữ liệu ¨ Khi chuyển từ các kiểu chấm động sang các kiểu nguyên cóthể làm giảm độ chính xác của dữ liệu n Trình biên dịch sẽ sinh cảnh báo (warning) đối với các chuyển đổi tựđộng cóthể gây mất dữ liệu. @ 2004 Trần Minh Châu. FOTECH. VNU 9 Đổi vàép kiểu dữ liệu n C++ cho phép người dùngép kiểu một cách tường minh bằng nhiều cách ¨ Ép kiểu kiểu C: myInt = (int) myFloat; ¨ Ép kiểu kiểu hàm C++: myInt = int(myFloat); n Để hạn chế ép kiểu quámức vàloại trừ các lỗi doép kiểu, C++ cung cấp một cách mới sử dụng 4 loạiép kiểu tường minh ¨ static_cast ¨ const_cast ¨ reinterpret_cast ¨ dynamic_cast n Cúpháp myInt = static_cast (myFloat) @ 2004 Trần Minh Châu. FOTECH. VNU 10
  72. Simpo PDF Merge and Split Unregistered Version - Phạm vi vàcác Khai báo n Trong C, các biến phải được định nghĩa tại đầu file hoặc tại bắt đầu của một khối { } n C++ cho phép khai báo sau vàphạm vi của các biến được giới hạn chính xác hơn ¨ Các khai báo cóthểđặt tại các câu lệnh lặp for vàcác câu lệnh điều kiện ¨ Phạm vi giới hạn bên trong vòng lặp hoặc khối điều kiện n C++ còn bổ sung hai phạm vi mới: ¨ Phạm vi không gian tên-Namespace scope ¨ Phạm vi lớp-Class scope @ 2004 Trần Minh Châu. FOTECH. VNU 11 Namespace-Không gian tên n Không gian tên được bổ sung vào C++ để biểu diễn cấu trúc logic vàcung cấp khả năng quản lý phạm vi tốt hơn n Không gian tên cung cấp một cơ chế tường minh để tạo các vùng khai báo n Các tên khai báo trong một không gian tên ¨ không xung đột với các tên được namespace Frog { khai báo trong các không gian tên khác double weight; double jump() { } ¨ Tránh xung đột tên biến, tên hàm } ¨ Nghiễm nhiên cóthểđược namespace Kangaroo { liên kết ra ngoài (external linkage) int weight; void jump() { } } @ 2004 Trần Minh Châu. FOTECH. VNU 12
  73. Simpo PDF Merge and Split Unregistered Version - Namespace n Vídụ weight trong namespace namespace Frog { Frog và weight trong double weight; namespace độc double jump() { } Kangaroo } lập vàkhông bị xung đột namespace Kangaroo { int weight; void jump() { } } int main() Khi sử dụng định danh, { dùng tên namespace và Frog::weight = 5; toán tử phạm vi Kangaroo::jump(); } @ 2004 Trần Minh Châu. FOTECH. VNU 13 Namespace n C++ cung cấp hai cơ chếđểđơn giản hóa việc sử dụng các namespaces: các khai báo using vàcác định hướng using. n khai báo using (using-declaration) cho phép truy nhập một định danh cụ thể trong vùng khai báo tạm thời using :: ; ¨ Từđây, ta cóthể sử dụng tên màkhông cần mỗi lần đều phải chỉ rõ namespace chứa nó. @ 2004 Trần Minh Châu. FOTECH. VNU 14
  74. Simpo PDF Merge and Split Unregistered Version - Namespace n Vídụsửdụng khai báo using namespace Frog { double weight; Khai báo using cho định double jump() { } danh weight trong } namespace Frog. int main() { using Frog::weight; int weight; // Error (already declared locally) weight = 5; // Sets Frog::weight to 5 } Từđây, weight được hiểu là Frog::weight @ 2004 Trần Minh Châu. FOTECH. VNU 15 Namespace n Khai báo using dành cho 1tên, định hướng using cho phép truy nhập mọi định danh trong namespace using namespace ; n Định hướng using thường được đặt tại mức toàn cục. #include using namespace std; @ 2004 Trần Minh Châu. FOTECH. VNU 16
  75. Simpo PDF Merge and Split Unregistered Version - Quản lý bộ nhớ n Cấp phát bộ nhớđộng trong C trông rối rắm vàdễlỗi ¨myObj* obj = (myObj*)malloc(sizeof(myObj)); n Các nhàthiết kế C++ thấy rằng: ¨ Một ngôn ngữ sử dụng class sẽ hay phải sử dụng bộ nhớđộng. ¨ Không cólý do gì để tách cấp phát bộ nhớđộng ra khỏi việc khởi tạo đối tượng (hay tách thu hồi bộ nhớđộng ra khỏi việc hủy đối tượng) n malloc và free đã được thay bằng new và delete @ 2004 Trần Minh Châu. FOTECH. VNU 17 Quản lý bộ nhớ n Lợi thế của new so với malloc: ¨ Không cần chỉ ra lượng bộ nhớ cần cấp phát ¨ Không cần đổi kiểu ¨ Không cần dùng lệnh if để kiểm tra xem bộ nhớđãhết chưa ¨ Nếu bộ nhớđang được cấp cho một đối tượng, hàm khởi tạo (constructor) của đối tượng sẽđược gọi tựđộng (tương tự, delete sẽ tựđộng gọi hàm hủy (destructor) của đối tượng) n Vídụ: myObj* obj = new myObj;//một đối tượng delete obj; myObj* obj = new myObj[10];// mảng đối tượng delete[] obj; @ 2004 Trần Minh Châu. FOTECH. VNU 18
  76. Simpo PDF Merge and Split Unregistered Version - Tham chiếu–Reference n Tham chiếu tới một đối tượng làmột biệt danh tới đối tượng đó n Cóthể coi mọi thao tác trên tham chiếu đều được thực hiện trên chính đối tượng nguồn. int x = 5; int& y = x; cout <<“x =“<< x <<“y =“<< y <<“.\n”; // x = 5 y = 5 x = x + 1; cout <<“x =“<< x <<“y =“<< y <<“.\n”; // x = 6 y = 6 y = y + 1; cout <<“x =“<< x <<“y =“<< y <<“.\n”; // x = 7 y = 7 int *p = &y; *p = 9; cout <<“x =“<< x <<“y =“<< y <<“.\n”; // x = 9 y = 9 @ 2004 Trần Minh Châu. FOTECH. VNU 19 Tham chiếu–Reference n Tham chiếu cóthểđược dùng độc lập nhưng thường hay được dùng làm tham số cho hàm n C truyền mọi đối số cho hàm bằng giátrị (truyền trị -call by value) ¨ Khi cần, ta cóthể truyền một con trỏ tới đối tượng (chính nócũng được truyền bằng giátrị) ¨ void myFunction(myObj* obj) { } n C++ cho phép các đối số hàm được truyền bằng tham chiếu (call by reference) ¨ void myFunction(myObj& obj) { } ¨ myObj& cónghĩa“tham chiếu tới myObj” ¨ đối với các đối số làcác đối tượng lớn, truyền bằng tham chiếu đỡ tốn kém hơn truyền bằng giátrị (do chỉ truyền địa chỉ bộ nhớ) @ 2004 Trần Minh Châu. FOTECH. VNU 20
  77. Simpo PDF Merge and Split Unregistered Version - Tham chiếu–Reference n Vídụ: tham chiếu làm đối số cho hàm int f(int &i) { ++i; return i; } int main() { int j = 7; cout << f(j) << endl; cout << j << endl; } n Biến i làmột biến địa phương của hàm f. i thuộc kiểu tham chiếu int và được tạo khi f được gọi. n Trong lời gọi f(j), i được tạo tương tự như trong lệnh int &i = j; n Do đótrong hàm f, i sẽ làmột tên khác của biến j vàsẽluônnhư vậy trong suốt thời gian tồn tại của i @ 2004 Trần Minh Châu. FOTECH. VNU 21 Tham chiếu–Reference n Tham chiếu phải được khởi tạo ¨ int& x; // Error n Giátrị của tham chiếu không được thay đổi sau khi đãkhởi tạo ¨ không thể “chiếu”lại một tham chiếu tới đối tượng khác ¨ chúý phân biệt giữa khởi tạo tham chiếu vàgán trị cho tham chiếu n Truy nhập tới tham chiếu chính làtruy nhập tới đối tượng nguồn ¨ áp dụng cho cả toán tử & vàphép gán n cout <<“The address of x is:“<< &x <<“.\n”; n cout <<“The address of y is:“<< &y <<“.\n”; n Output: n The address of x is 0x0056dc13. n The address of y is 0x0056dc13. @ 2004 Trần Minh Châu. FOTECH. VNU 22
  78. Simpo PDF Merge and Split Unregistered Version - Tham chiếu–Reference n Vậy tại sao dùng tham chiếu thay cho con trỏ? n Tham chiếu sạch hơn, không dễ gây lỗi như con trỏ, đặc biệt khi dùng trong hàm ¨ tham chiếu đảm bảo không chiếu tới null n Sử dụng tham chiếu trong nguyên mẫu hàm giúp cho việc gọi hàm dễ hiểu hơn ¨ không cần dùng toán tửđịa chỉ void func1(int *pi) { (*pi)++; } void func2(int &ri) { ri++; } int main() { int i = 1; func1(&i); // call using address of i func2(i); // call using i } @ 2004 Trần Minh Châu. FOTECH. VNU 23 Const n Trong C, hằng được định nghĩa bằng định hướng tiền xử lý #define #define PI 3.14 ¨ Biên dịch chậm hơn (trình tiền xử lý tìm vàthay thế) ¨ Trình debug không biết đến các tên hằng ¨ Sử dụng #define không gắn được kiểu dữ liệu với giátrị hằng (v.d.‘15’là int hay float?) n Const của ANSI-Cít dùng hơn vàcónghĩa hơi khác: ¨ ANSI-C const không được trình biên dịch chấp nhận làhằng n Không dùng để khai báo mảng được n In C++, const values can be used when declaring arrays: @ 2004 Trần Minh Châu. FOTECH. VNU 24
  79. Simpo PDF Merge and Split Unregistered Version - Const Hằng của ANSI-C vàC++ cócác quy tắc phạm vi khác nhau n Các giátrị #define cóphạm vi file (do trình tiền xử lý chỉ thực hiện tìm vàthay thế tại file đó) n const của ANSI-C được coi làcác biến cógiátrị không đổi vàcóphạm vi chương trình ¨ Do đóta không thể cócác biến trùng tên tại hai file khác nhau n Trong C++, các định danh const tuân theo các quy tắc phạm vi như các biến khác ¨ Do đó, chúng cóthể cóphạm vi toàn cục, phạm vi không gian tên, hoặc phạm vi khối @ 2004 Trần Minh Châu. FOTECH. VNU 25 const vàcon trỏ n Ba loại: 1. const int * pi;// con trỏ tới hằng 2. int * const ri = &i; // hằng con trỏ 3. const int * const ri = &i; //hằng con trỏ tới hằng n Các khái niệm cần ghi nhớ: n Nếu một đối tượng làhằng ¨ Không thể sửa đối tượng đó ¨ Chỉ cócon trỏ tới hằng mới được dùng để trỏ tới hằng, con trỏ thường không dùng được n Nếu PI được khai báo làcon trỏ tới hằng: ¨ Cóthể thay đổi PI, nhưng *PI không thể bị thay đổi. ¨ PI cóthể trỏđến hằng hoặc biến thường @ 2004 Trần Minh Châu. FOTECH. VNU 26
  80. Simpo PDF Merge and Split Unregistered Version - const vàcon trỏ n Khi sử dụng một con trỏ, có hai đối tượng cóliên quan: chính con trỏđóvà đối tượng nótrỏ tới n Cúpháp cho con trỏ tới hằng vàhằng con trỏ rất dễ nhầm lẫn n Quy tắc: trong lệnh khai báo, từ khoáconst bên trái dấu * có nghĩa đối tượng được trỏ tới làhằng, từ khoáconst bên phải dấu * cónghĩa con trỏ làhằng n Cách dễ hơn: đọc các khai báo từ phải sang trái ¨ char c = 'Y'; // c làchar ¨ char *const cpc = &c; //cpc làhằng con trỏ tới char ¨ const char *pcc; // pcc làcon trỏ tới hằng char ¨ const char *const cpcc = &c; // cpcc làhằngcon trỏ tới hằng char @ 2004 Trần Minh Châu. FOTECH. VNU 27 hằng tham chiếu làm tham số hàm Cóhai lý do để truyền tham biến, nhưng nếu hàm được truyền không cần sửa đối tượng được truyền thìta nên khai báo tham biến là const. Cóhaiích lợi: 1. Nếu trong hàm, ta lỡ sửa đổi tham số thìtrình biên dịch sẽ bắt lỗi. n ngăn chặn được một số lỗi lập trình viên cóthể phạm 2. Ta cóthể truyền các đối số làhằng hoặc không phải hằng cho hàm cóhằng tham biến n Ngược lại, đối với các hàm cótham biến không phải làhằng, ta không thể truyền hằng làm đối số. @ 2004 Trần Minh Châu. FOTECH. VNU 28
  81. Simpo PDF Merge and Split Unregistered Version - hằng tham chiếu làm tham số hàm Lỗi: Lo làhằng n Vídụ nênkhông được sửa đổi void non_constRef(LargeObj &Lo) { Lo.height +=10; } // Fine void constRef(const LargeObj &Lo) { Lo.height +=10; } // Error !!! void non_constRef(LargeObj &Lo) { cout Lo.height; } void constRef(const LargeObj &Lo) { cout Lo.height; } int main { LargeObj dinosaur; const LargeObj rocket; non_constRef(dinosaur); constRef(dinosaur); non_constRef(rocket);// Error constRef(rocket); Lỗi: rocket làhằng, } nên không thể làm đối số không phải hằng @ 2004 Trần Minh Châu. FOTECH. VNU 29 const: Hàm thành viên Đối với hàm thành viên không sửa dữ liệu của đối tượng chủ, ta nên khai báo hàm đólàhằng hàm ¨ Đối với các đối tượng được khai báo làhằng, C++ chỉ cho phép gọi các hàm thành viên làhằng màkhông cho phép gọi các hàm thành viên không phải làhằng của đối tượng đó. class Date { int year, month, day; public: int getDay() const { return day; } int getMonth() const { return month; } void addYear(int y) // Non-const function }; @ 2004 Trần Minh Châu. FOTECH. VNU 30
  82. Simpo PDF Merge and Split Unregistered Version - Const-Tóm tắt Nên khai báo hằng đối với: n Các đối tượng mà ta không định sửa đổi const double PI = 3.14; const Date openDate(18,8,2003); n Các tham số của hàm màta không định cho hàm đósửađổi void printHeight(const LargeObj &LO) { cout << LO.height; } n Các hàm thành viênkhôngthay đổi đối tượng chủ int Date::getDay() const { return day; } Lưu ý: các yêu cầu trênáp dụng cho tất cả các bài tập, bài thi của môn học. @ 2004 Trần Minh Châu. FOTECH. VNU 31
  83. Simpo PDF Merge and Split Unregistered Version - Ôn tập về Con trỏ Lập trình hướng đối tượng Ôn tập con trỏ n Cấp phát động (Dynamic Allocation) new, delete n Con trỏ lạc (Dangling pointers) n Rò rỉ bộ nhớ (Memory leakage) n Con trỏ mảng (Array Pointer) n Các phép tính trên con trỏ (Pointer Arithmetic) n Con trỏ tới bản ghi n Cấp phát động mảng @ 2004 Trần Minh Châu. FOTECH. VNU 2
  84. Simpo PDF Merge and Split Unregistered Version - int x = 361; Con trỏ int *y = &x; Một con trỏ hay một biến con trỏ là: ¨ một biến chiếu đến một ô nhớ. ¨ nó lưu vị trí/địa chỉ của ô nhớđó. n Hai ứng dụng chính: ¨ Truy nhập gián tiếp ¨ Bộ nhớđộng n Vấn đề kỹ thuật: Nếu P làmột biến con trỏ ¨ Làm thế nào để trỏ P đến một ô nhớ nào đó? ¨ Làm thế nào để truy nhập đến ô nhớ P trỏđến? @ 2004 Trần Minh Châu. FOTECH. VNU 3 Thao tác con trỏ n Các ký hiệu, từ khóa: &, *, new, delete int X, Y; int* P;// P is an integer pointer variable n Lệnh thứ hai khai báo một biến con trỏ P cógiátrị chưa xác định nhưng khác Null. Biến con trỏ này cóthể chỉ trỏ tới một ô nhớ chứa một số nguyên P = &Y;// trỏ P tới Y (P lưu địa chỉ của Y) *P = X; // ghi giátrị của biến X vào vùng bộ nhớ trỏ bởi P n Vídụ Y = 5;// variable Y stores value P = &X; // P points to memory location of X *P = Y;// same as writing X = Y Sau vídụtrên, X = 5, Y = 5, vàP trỏ tới X @ 2004 Trần Minh Châu. FOTECH. VNU 4
  85. Simpo PDF Merge and Split Unregistered Version - Vídụ #include int main() { int x = 10; int y = 20; int *p1, *p2; x = 10 y = 20 p1 = &x; *p1 = 10 p2 = &y; *p2 = 20 cout << "x = " << x << endl; cout << "y = " << y << endl; x = 50 cout << "*p1 = " << *p1 << endl; y = 90 cout << "*p2 = " << *p2 << endl << endl; *p1 = 50 *p2 = 90 *p1 = 50; *p2 = 90; x = 50 cout << "x = " << x << endl; y = 90 cout << "y = " << y << endl; *p1 = 90 cout << "*p1 = " << *p1 << endl; *p2 = 90 cout << "*p2 = " << *p2 << endl << endl; p1 = p2; cout << "x = " << x << endl; cout << "y = " << y << endl; cout << "*p1 = " << *p1 << endl; cout << "*p2 = " << *p2 << endl << endl; } @ 2004 Trần Minh Châu. FOTECH. VNU 5 Ký hiệu n Đọc *P là biến màP trỏ tới n Đọc &X là địa chỉ của X n & là toán tửđịa chỉ (address of operator) n * là toán tử thâm nhập (dereferencing operator) n Giả sử P1 = &X và P2 = &Y, thì P1 trỏ tới X và P2 trỏ tới Y P1 = P2 Khôngtương đương với *P1 = *P2 n P1 = P2 cóhiệu quả trỏ P1 tới Y,lệnh đó khôngthay đổi X n Lệnh *P1 = *P2; tương đương với X = Y; @ 2004 Trần Minh Châu. FOTECH. VNU 6
  86. Simpo PDF Merge and Split Unregistered Version - Sử dụng typedef n Lỗi hay gặp khi sử dụng con trỏ. Phân biệt hai dòng sau: int* P, Q;// P is a pointer and Q an int int *P, *Q;// P and Q are both pointers n Một cách tránh lỗi làsửdụng lệnh typedef đểđặt tên kiểu mới. Vídụ: typedef double distance; //distance is a new name for double distance miles; Giống như double miles; Cónghĩa rằng, thay vìviết int *P, *Q; Ta cóthể viết typedef int* IntPtr;// new name for pointers to ints IntPtr P, Q; //P and Q are both pointers @ 2004 Trần Minh Châu. FOTECH. VNU 7 Cấp phát bộ nhớ tĩnh và động (Static and Dynamic Allocation Of Memory) Đoạn trình int X,Y;// X and Y are integers int *P;// P is an integer pointer variable Cấp phát bộ nhớ cho X, Y vàP tại thời điểm biên dịch Đólà cấp phát tĩnh (static allocation) n Bộ nhớ cũng cóthểđược cấp phát tại thời gian chạy. Đógọi là Cấp phát động (dynamic allocation). Vídụ: P = new int; ¨ Cấp phát một ô nhớ mới cóthể chứa một số nguyên, vàtrỏ P tới ô nhớ đó @ 2004 Trần Minh Châu. FOTECH. VNU 8
  87. Simpo PDF Merge and Split Unregistered Version - Vídụ //Program to demonstrate pointers //and dynamic variables *p1 = 10 #include *p2 = 10 int main() { *p1 = 30 int *p1, *p2; *p2 = 30 p1 = new int; *p1 = 10; *p1 = 40 p2 = p1; *p2 = 30 cout << "*p1 = " << *p1 << endl; cout << "*p2 = " << *p2 << endl << endl; *p2 = 30; cout << "*p1 = " << *p1 << endl; cout << "*p2 = " << *p2 << endl << endl; p1 = new int; *p1 = 40; cout << "*p1 = " << *p1 << endl; cout << "*p2 = " << *p2 << endl << endl; } @ 2004 Trần Minh Châu. FOTECH. VNU 9 Cấp phát-thu hồi bộ nhớđộng n heap: vùng bộ nhớđặc biệt dành riêng cho các biến động. Để tạo một biến động mới, hệ thống cấp phát không gian từ heap. Nếu không còn bộ nhớ, new không thể cấp phát bộ nhớ thìnótrả về gia trị Null n Trong lập trình thực thụ, ta nên luôn luôn kiểm tra lỗi này int *p; p = new int; if (p == NULL) { cout << "Memory Allocation Error\n"; exit; } n Thực ra, NULL làgiátrị 0, nhưng ta coi nólàmột giátrịđặc biệt vìcòn sử dụng cho trường hợp đặc biệt: con trỏ "rỗng". @ 2004 Trần Minh Châu. FOTECH. VNU 10
  88. Simpo PDF Merge and Split Unregistered Version - Cấp phát-thu hồi bộ nhớđộng n Hệ thống chỉ cómột lượng bộ nhớ giới hạn, ¨ cần trả lại cho heap phần bộ nhớđộng không còn sử dụng. n Lệnh delete P; ¨ trả lại vùng bộ nhớ trỏ bởi P, nhưng không sửa giátrị của P. ¨ Sau khi thực thi delete P, giátrị của P không xác định. @ 2004 Trần Minh Châu. FOTECH. VNU 11 Con trỏ lạc–Dangling Pointer n khi delete P, ta cần chúý không xoávùng bộ nhớ mà một con trỏ Q khác đang trỏ tới. int *P; P int *Q; tạo P = new int; Q Q = P; sau đó delete P; làm Q bị lạc?P P = Null; Q @ 2004 Trần Minh Châu. FOTECH. VNU 12
  89. Simpo PDF Merge and Split Unregistered Version - Rò rỉ bộ nhớ n Một vấn đề liên quan: mất mọi con trỏđến một vùng bộ nhớđược cấp phát.Khi đó, vùng bộ nhớđóbịmất dấu, không thể trả lại cho heap được. int *P; P int *Q; tạo P = new int; Q Q = new int; sau đó làm mất vùng P Q = P; nhớđãtừng được Q trỏ tới Q @ 2004 Trần Minh Châu. FOTECH. VNU 13 Mảng vàcon trỏ Tên mảng được coi như một con trỏ tới phần tửđầu tiên của mảng. int A[6] = {2,4,6,8,10,12};// defines an array of inegers int *P; P = A; // P points to A A[0] A[1] A[2] A[3] A[4] A[5] A 2 4 6 8 10 12 P Do tên mảng vàcon trỏ là tương đương, ta cóthể dùng P như tên mảng. Vídụ: P[3] = 7; tương đương với A[3] = 7; @ 2004 Trần Minh Châu. FOTECH. VNU 14
  90. Simpo PDF Merge and Split Unregistered Version - Vídụ A[0] A[1] A[2] A[3] A[4] A[5] Bắt đầu A 2 4 6 8 10 12 P A[0] A[1] A[2] A[3] A[4] A[5] Thực hiện P = &A[2] A 2 4 6 8 10 12 Bây giờ, P[0] làA[2], P[0]P[1]P[2] P[3] p[1] làA[3], P @ 2004 Trần Minh Châu. FOTECH. VNU 15 Các phép tính trên con trỏ P = A; P = A + 2; A[0] A[1] A[2] A[3] A[4] A[5] A[0] A[1] A[2] A[3] A[4] A[5] A 2 4 6 8 10 12 A 2 4 6 8 10 12 P P P++; *P = *(P+1) + 2; A[0] A[1] A[2] A[3] A[4] A[5] A[0] A[1] A[2] A[3] A[4] A[5] A 2 4 6 8 10 12 A 2 4 10 8 10 12 P P P = P + 2; *(P-1) = *(P+2) A[0] A[1] A[2] A[3] A[4] A[5] A[0] A[1] A[2] A[3] A[4] A[5] A 2 4 6 8 10 12 A 2 10 10 8 10 12 P P @ 2004 Trần Minh Châu. FOTECH. VNU 16
  91. Simpo PDF Merge and Split Unregistered Version - Con trỏ tới bản ghi: bộ nhớđộng #ifndef IQ1_H #include #define IQ1_H #include "iq1.h" #include int main() { class IQ IQ *x = new IQ("Newton",200); { IQ *y = new IQ("Einstein",250); private: char name[20]; x->print(); int score; y->print(); public: IQ (const char s, int k) { return; strcpy(name, s); } score = k; } void smarter(int k) { score += k;} x Newton void print() const { cout int main () { int size; cin << size; int* A = new int[size]; // dynamically allocate array A[0] = 0; A[1] = 1; A[2] = 2; cout << "A[1] = " << A[1] << endl; delete [] A; // delete the array } @ 2004 Trần Minh Châu. FOTECH. VNU 18
  92. Simpo PDF Merge and Split Unregistered Version - Huỷ mảng động bất hợp lệ #include int main () { int* A = new int[6]; // dynamically allocate array P không trỏ tới A[0] = 0; A[1] = 1; A[2] = 2; đầu mảng A A[3] = 3; A[4] = 4; A[5] = 5; int *p = A + 2; cout << "A[1] = " << A[1] << endl; Huỷ không hợp lệ delete [] p; // illegal!!! // results depend on particular compiler cout << "A[1] = " << A[1] << endl; } Kết quả phụ thuộc trình biên dịch @ 2004 Trần Minh Châu. FOTECH. VNU 19 Cấp phát động mảng đa chiều n Cấp phát động mảng hai chiều (N+1)(M+1) gồm các đối tượng IQ: IQ a = new (IQ*) [N+1]; for (int i=0; i<N+1; i++) a[i] = new IQ[M+1]; @ 2004 Trần Minh Châu. FOTECH. VNU 20