Chương Kỹ thuật lập trình Chương 6: Lớp ₫ối tượng II 0101010101010101100001 0101010101010101100001 StateController 0101010100101010100101 0101010100101010100101 1010011000110010010010 1010011000110010010010 start() 1100101100100010000010 1100101100100010000010 stop() 0101010101010101100001 0101010101010101100001 0101010100101010100101 0101010100101010100101 1010011000110010010010 1010011000110010010010 y = A*x + B*u; 1100101100100010000010 1100101100100010000010 x = C*x + d*u; LQGController 0101010101010101100001 0101010101010101100001 0101010100101010100101 0101010100101010100101 start() 1010011000110010010010 stop() 1010011000110010010010 1100101100100010000010 1100101100100010000010 10/30/2007 Nội dung chương 6.1 6.2 6.3 6.4 6.5 Tạo hủy ₫ối tượng Xây dựng hàm tạo hàm hủy Nạp chồng toán tử Khai báo friend Thành viên static (tự ₫ọc) Chương 6: Lớp đối tượng II © 2007 AC - HUT 6.1 Tạo hủy ₫ối tượng Có cách ₫ể tạo/hủy ₫ối tượng? Tạo/hủy tự ₫ộng: Định nghĩa biến thuộc lớp — Bộ nhớ ₫ối tượng (chứa liệu biến thành viên) ₫ược tự ₫ộng cấp phát giống với biến thông thương — Bộ nhớ ₫ối tượng ₫ược giải phóng khỏi phạm vi ₫ịnh nghĩa class X { int a, b; }; void f( X x1) { if ( ) { Đối tượng ₫ược tạo ngăn xếp X x2; Thời ₫iểm nhớ cho x2 ₫ược giải phóng } Thời ₫iểm nhớ cho x1 ₫ược giải phóng } X x; Đối tượng ₫ược tạo vùng liệu chương trình Chương 6: Lớp đối tượng II © 2007 AC - HUT Tạo/hủy ₫ối tượng ₫ộng toán tử new delete: X* pX = 0; void f( ) { if ( ) { pX = new X; } } void g( ) { if (pX != 0) { delete pX; } } Chương 6: Lớp đối tượng II Đối tượng ₫ược tạo vùng nhớ tự Bộ nhớ ₫ối tượng heap ₫ược giải phóng © 2007 AC - HUT Vấn ₫ề 1: Khởi tạo trạng thái ₫ối tượng Sau ₫ược tạo ra, trạng thái ₫ối tượng (bao gồm liệu bên mối quan hệ) thường bất ₫ịnh => sử dụng an toàn, tin cậy, thuận tiện X x; // x.a = ?, x.b = ? X *px = new X; // px->a = ?, px->b = ?; class Vector { int n; double *data; }; Vector v; // v.n = ?, v.data = ? Làm ₫ể sau ₫ược tạo ra, ₫ối tượng có trạng thái ban ₫ầu theo ý muốn chương trình? X x = {1, 2}; // Error! cannot access private members Làm ₫ể tạo ₫ối tượng ₫ối tượng có kiểu khác? class Y { int c, d; }; Y y = x; // Error, X and Y are not the same type, // they are not compatible Chương 6: Lớp đối tượng II © 2007 AC - HUT Vấn ₫ề 2: Quản lý tài nguyên Đối với ₫ối tượng sử dụng nhớ ₫ộng, việc cấp phát giải phóng nhớ ₫ộng nên thực cho an toàn? class Vector { int nelem; double *data; public: void create(int n) { data = new double[nelem=n];} void destroy() { delete[] data; nlem = 0; } void putElem(int i, double d) { data[i] = d; } }; Vector v1, v2; v1.create(5); // forget to call create for v2 v2.putElem(1,2.5); // BIG problem! // forget to call destroy for v1, also a BIG problem Vấn ₫ề tương tự xảy sử dụng tệp tin, cổng truyền thông, tài nguyên khác máy tính Chương 6: Lớp đối tượng II © 2007 AC - HUT Giải pháp chung: Hàm tạo hàm hủy Một hàm tạo ₫ược tự ₫ộng gọi ₫ối tượng ₫ược tạo, hàm hủy ₫ược gọi ₫ối tượng bị hủy: class X { int a,b; public: X() { a = b = 0; } // constructor (1) X(int s, int t) { a = s; b = t;} // constructor (2) ~X() {} // destructor }; Gọi hàm tạo (1) không tham void f(X x1) { số (hàm tạo mặc ₫ịnh) if ( ) { X x2(1,2); Gọi hàm tạo (2) X x3(x2); Gọi hàm hủy cho x1 } Gọi hàm hủy cho x2, x3 Gọi hàm tạo } X *px1 = new X(1,2), *px2 = new X; delete px1; delete px2; Gọi hàm hủy cho *px1 *px2 Chương 6: Lớp đối tượng II © 2007 AC - HUT 6.2 Xây dựng hàm tạo hàm hủy Hàm tạo hội ₫ể khởi tạo cấp phát tài nguyên Hàm hủy hội ₫ể giải phóng tài nguyên ₫ã cấp phát Một lớp có nhiều hàm tạo (khác số lượng tham số kiểu tham số) Mặc ₫ịnh, compiler tự ₫ộng sinh hàm tạo không tham số hàm tạo — Thông thường, mã thực thi hàm tạo mặc ₫ịnh compiler sinh rỗng — Thông thường, mã thực thi hàm tạo compiler sinh chép liệu ₫ối tượng theo bit — Khi xây dựng lớp, cần bổ sung hàm tạo mặc ₫ịnh, hàm tạo hàm tạo khác theo ý muốn Mỗi lớp có xác hàm hủy, hàm hủy khơng ₫ược ₫ịnh nghĩa compiler tự sinh hàm hủy: — Thông thường, mã hàm hủy compiler tạo rỗng — Khi cần ₫ịnh nghĩa hàm hủy ₫ể thực thi mã theo ý muốn Chương 6: Lớp đối tượng II © 2007 AC - HUT Ví dụ: Lớp Time cải tiến class Time { int hour, min, sec; public: Time() : hour(0), min(0), sec(0) {} Time(int h, int m=0, int s=0) { setTime(h,m,s); } Time(const Time& t) : hour(t.hour),min(t.min),sec(t.sec) {} }; void main() { Time t1; // 0, 0, Time t2(1,1,1); // 1, 1, Time t3(1,1); // 1, 1, Time t4(1); // 1, 0, Time t5(t1); // 0, 0, Time t6=t2; // 1, 1, Time* pt1 = new Time(1,1); delete pt1; } Chương 6: Lớp đối tượng II 0 // 1, 1, Hàm tạo hàm hủy thực không cần ₫ịnh nghĩa cho lớp này! © 2007 AC - HUT Ví dụ: Lớp Vector cải tiến Yêu cầu từ người sử dụng: — Khai báo ₫ơn giản với kiểu — An toàn, người sử dụng gọi hàm cấp phát giải phóng nhớ Ví dụ mã sử dụng: Vector v1; // v1 has elements Vector v2(5,0); // v2 has elements init with Vector v3=v2; // v3 is a copy of v2 Vector v4(v3); // the same as above Vector f(Vector b) { double a[] = {1, 2, 3, 4}; Vector v(4, a); return v; } // Do not care about memory management Chương 6: Lớp đối tượng II © 2007 AC - HUT 10 Trường hợp ₫ặc biệt: Hàm tạo Hàm tạo ₫ược gọi chép ₫ối tượng: — Khi khai báo biến x2-x4 sau: X x1; X x2(x1); X x3 = x1; X x4 = X(x1); — Khi truyền tham số qua giá trị cho hàm, hàm trả ₫ối tượng void f(X x) { } X g( ) { X x1; f(x1); return x1; } Chương 6: Lớp đối tượng II © 2007 AC - HUT 13 Cú pháp chuẩn cho hàm tạo sao? class X { int a, b; public: X() : a(0), b(0) {} X(X x); // (1) X(const X x); // (2) X(X& x); // (3) X(const X& x); // (4) }; void main() { X x1; X x2(x1); } Chương 6: Lớp đối tượng II (1) Truyền tham số qua giá trị yêu cầu chép x1 sang x!!! (2) Như (1) ? (3) Không chép tham số, x bị vơ tình thay ₫ổi hàm (4) Khơng chép tham số, an tồn cho => cú pháp chuẩn! © 2007 AC - HUT 14 Khi cần ₫ịnh nghĩa hàm tạo sao? Khi hàm tạo mặc ₫ịnh khơng ₫áp ứng ₫ược u cầu Ví dụ, hàm tạo không ₫ược ₫ịnh nghĩa, mã compiler tự ₫ộng tạo cho lớp Vector có dạng: Vector::Vector(const Vector& b) : nelem(b.nelem), data(b.data) {} Vấn ₫ề: Sao chép trỏ túy, hai ₫ối tượng sử dụng chung nhớ phần tử Vector a(5); Vector b(a); a.nelem : a.data b.nelem : b.data 0 0 Trường hợp này, phải ₫ịnh nghĩa lại sau: Vector::Vector(const Vector& a) { create(a.nelem); for (int i=0; i < nelem; ++i) data[i] = a.data[i]; } Chương 6: Lớp đối tượng II © 2007 AC - HUT 15 Một số ₫iểm cần lưu ý Nhiều hàm tạo có hàm hủy => hàm hủy phải quán với tất hàm tạo — Trong ví dụ lớp Vector, có hàm tạo cấp phát nhớ, hàm tạo mặc ₫ịnh khơng => hàm hủy cần phân biệt rõ trường hợp Khi hàm tạo có cấp phát chiếm dụng tài nguyên cần ₫ịnh nghĩa lại hàm hủy Trong lớp mà có ₫ịnh nghĩa hàm hủy gần chắn phải ₫ịnh nghĩa hàm tạo (nếu cho phép chép) Một lớp cấm chép cách khai báo hàm tạo phần private, ví dụ: class Y { int a, b; Y(const&); }; void main() { Y y1; Y y2=y1; // error! } Chương 6: Lớp đối tượng II © 2007 AC - HUT 16 6.3 Nạp chồng toán tử Một kỹ thuật lập trình hay C++ Cho phép áp dụng phép toán với số phức với vector sử dụng toán tử +, -, *, / tương tự với số thực Ví dụ: class Complex { double re, im; public: Complex(double r = 0, double i =0): re(r),im(i) {} }; Complex z1(1,1), z2(2,2); Complex z = z1 + z2; // ??? Bản chất vấn ₫ề? Dòng mã cuối thực viết: Complex z = z1.operator+(z2); Complex z = operator+(z1,z2); Chương 6: Lớp đối tượng II Hàm tốn tử thực hàm thành viên hàm phi thành viên © 2007 AC - HUT 17 Ví dụ: bổ sung phép toán số phức class Complex { double re, im; public: Complex(double r = 0, double i =0): re(r),im(i) {} double real() const { return re; } double imag() const { return im; } Complex operator+(const Complex& b) const { Complex z(re+b.re, im+b.im); return z; } Complex operator-(const Complex& b) const { return Complex(re-b.re,im-b.im); } Complex operator*(const Complex&) const; Complex operator/(const Complex&) const; Complex& operator +=(const Complex&); Complex& operator -=(const Complex&); }; Chương 6: Lớp đối tượng II © 2007 AC - HUT 18 #include “mycomplex.h” Complex Complex::operator*(const Complex& b) const { // left for exercise! } Complex Complex::operator/(const Complex& b) const { // left for exercise! } Complex& Complex::operator +=(const Complex& b) { re += b.re; im += b.im; return *this; } Complex& operator -=(const Complex&) { } bool operator==(const Complex& a, const Complex& b) { return a.real() == b.real() && a.imag() == b.imag(); } void main() { Complex a(1,1), b(1,2); Complex c = a+b; a = c += b; // a.operator=(c.operator+=(b)); if (c == a) { } } return ? Chương 6: Lớp đối tượng II © 2007 AC - HUT 19 Các tốn tử nạp chồng? Hầu hết tốn tử có C++, ví dụ — — — — — Các toán tử số học: Các toán tử logic, logic bit: Các toán tử so sánh: Các toán tử thao tác bit: Các toán tử khác: ++ && == < >= > >>= ? : © 2007 AC - HUT 20 Một số qui ₫ịnh Có thể thay ₫ổi ngữ nghĩa tốn tử cho kiểu mới, khơng thay ₫ổi ₫ược cú pháp (ví dụ số ngơi, trình tự ưu tiên thực hiện, ) Trong phép tốn ₫ịnh nghĩa lại, phải có tốn hạng có kiểu (struct, union class) => khơng ₫ịnh nghĩa lại cho kiểu liệu kiểu dẫn xuất trực tiếp ₫ược! — Ví dụ khơng thể ₫ịnh nghĩa lại tốn tử ^ phép tính lũy thừa cho kiểu số học (int, float, double, ) Chỉ nạp chồng ₫ược tốn tử có sẵn, khơng ₫ưa thêm ₫ược tốn tử — Ví dụ khơng thể bổ sung ký hiệu toán tử ** cho phép toán lũy thừa Nạp chồng toán tử thực chất nạp chồng tên hàm => cần lưu ý qui ₫ịnh nạp chồng tên hàm Đa số hàm tốn tử nạp chồng dạng hàm thành viên, dạng hàm phi thành viên Một số toán tử nạp chồng hàm thành viên Một số toán tử nên nạp chồng hàm phi thành viên Chương 6: Lớp đối tượng II © 2007 AC - HUT 21 Nạp chồng tốn tử [] Yêu cầu: truy nhập phần tử ₫ối tượng thuộc lớp Vector với toán tử [] giống ₫ối với mảng Vector v(5,1.0); double d = v[0]; // double d = v.operator[](0); v[1] = d + 2.0; // v.operator[](1) = d + 2.0; const Vector vc(5,1.0); d = vc[1];// d = operator[](1); Giải pháp class Vector { public: double operator[](int i) const { return data[i]; } double& operator[](int i) { return data[i]; } }; Chương 6: Lớp đối tượng II © 2007 AC - HUT 22 Nạp chồng toán tử gán (=) Giống hàm tạo sao, hàm toán tử gán ₫ược compiler tự ₫ộng bổ sung vào lớp ₫ối tượng => mã hàm thực gán bit liệu Cú pháp chuẩn hàm toán tử gán cho lớp X tương tự cú pháp phép tính gán: X& operator=(const X&); Khi cần ₫ịnh nghĩa lại hàm tạo cần (và nên) ₫ịnh nghĩa lại hàm tốn tử gán Ví dụ, hàm tốn tử gán không ₫ược ₫ịnh nghĩa, mã compiler tự ₫ộng tạo cho lớp Vector có dạng: Vector& Vector::operator=(const Vector& b) { nelem = b.nelem; data = b.data return *this; } Chương 6: Lớp đối tượng II © 2007 AC - HUT 23 Vấn ₫ề tương tự hàm tạo mặc ₫ịnh, chí tồi tệ { Vector a(5), b(3), c; b = a; c = a; } // calling destructor for a, b and c causes // times calling of delete[] operator for the // same memory space a.nelem : a.data 0 b.nelem : b.data 0 Chương 6: Lớp đối tượng II 0 c.nelem : c.data © 2007 AC - HUT 24 Nạp chồng toán tử gán cho lớp Vector Vector& Vector::operator=(const Vector& b) { if (nelem != b.nelem) { destroy(); create(b.nelem); } for (int i=0; i < nelem; ++i) data[i] = b.data[i]; return *this; } Chương 6: Lớp đối tượng II © 2007 AC - HUT 25 6.4 Khai báo friend Vấn ₫ề: Một số hàm phi thành viên thực bên ngoài, hàm thành viên lớp khác không truy nhập ₫ược trực tiếp vào biến riêng ₫ối tượng => thực thi hiệu Giải pháp: Cho phép lớp khai báo friend, hàm phi thành viên, hàm thành viên lớp khác, lớp khác Ví dụ class Complex { friend bool operator==(const Complex&,const Complex&); friend class ComplexVector; friend ComplexVector Matrix::eigenvalues(); } bool operator==(const Complex& a, const Complex& b) { return a.re == b.re && a.im == b.im; } Chương 6: Lớp đối tượng II © 2007 AC - HUT 26 Bài tập nhà Hoàn chỉnh lớp Vector với phép toán cộng, trừ, nhân/chia với số vô hướng, nhân vô hướng so sánh Dựa cấu trúc List hàm liên quan ₫ã thực chương 4, xây dựng lớp ₫ối tượng List với hàm thành viên cần thiết Chương 6: Lớp đối tượng II © 2007 AC - HUT 27