1. Trang chủ
  2. » Cao đẳng - Đại học

CNTT LT Huong doi tuong C

92 7 0

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

THÔNG TIN TÀI LIỆU

Cú pháp của ví dụ trước tạo một đối tượng tạm thời temporary object 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[r]

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

Ngày đăng: 02/10/2021, 01:44

Xem thêm:

TÀI LIỆU CÙNG NGƯỜI DÙNG

TÀI LIỆU LIÊN QUAN

w