Lập trình hướng đối tượng - Chương 2: Đối tượng và lớp

Lớp Diem biểu diễn khái niệm điểm trong mặt phẳng với hai thành phần toạ độ x và y. Khai báo lớp Diem được đặt trong tập tin diem.h và chi tiết cài đặt các hàm thành phần được đặt trong tập tin diem.cpp. Có thể xây dựng một số ứng dụng của lớp điểm như trong tập tin tdiem.cpp hoặc dongho.cpp với các ứng dụng tương ứng là tdiem.exe và dongho.exe (hoặc dongho2.exe, dongho3.exe). Chương 2. L?p 120  Lớp DaGiac biểu diễn khái niệm đa giác trong mặt phẳng có các thao tác tịnh tiến, vị tự, quay, vẽ Phần khai báo được đặt trong tập tin dagiac.h, phần cài đặt trong tập tin dagiac.cpp và ứng dụng tdg.cpp cho phép tạo, vẽ, tịnh tiến và quay đa giác. Tập tin thực thi là tdg.exe

pdf120 trang | Chia sẻ: nguyenlam99 | Lượt xem: 1195 | Lượt tải: 0download
Bạn đang xem trước 20 trang tài liệu Lập trình hướng đối tượng - Chương 2: Đối tượng và lớp, để xem tài liệu hoàn chỉnh bạn click vào nút DOWNLOAD ở trên
Lập Trình Hướng Đối Tượng 45 LT+30TH Chương 2 – Đối tượng và Lớp 1. Đối tượng và lớp 2. Cài đặt lớp trong C++ 3. Thiết lập và huỷ bỏ đối tượng 4. Giao diện và chi tiết cài đặt 5. Các nguyên tắc xây dựng lớp 6. Một số ví dụ về lớp 2.1. Đối tượng và lớp  Ta định nghĩa một đối tượng là một "cái gì đó" có ý nghĩa cho vấn đề ta quan tâm. Đối tượng phục vụ hai mục đích: Giúp hiểu rõ thế giới thực và cung cấp cơ sở cho việc cài đặt trong máy.  Mỗi đối tượng có một nét nhận dạng để phân biệt nó với các đối tượng khác. Nét nhận dạng mang ý nghĩa các đối tượng được phân biệt với nhau do sự tồn tại vốn Chương 2. Lớp 3 có của chúng chứ không phải các tính chất mà chúng có. Đối tượng và lớp  Các đối tượng có các đặc tính tương tự nhau được gom chung lại thành lớp đối tượng. Ví dụ Người là một lớp đối tượng. Một lớp đối tượng được đặc trưng bằng các thuộc tính, và các hoạt động (hành vi).  Một thuộc tính (attribute) là một giá trị dữ liệu cho mỗi đối tượng trong lớp. Tên, Tuổi, Cân nặng là các thuộc tính của Người. Chương 2. Lớp 4  Một thao tác (operation) là một hàm hay một phép biến đổi có thể áp dụng vào hay áp dụng bởi các đối tượng trong lớp. Đối tượng và lớp  Cùng một thao tác có thể được áp dụng cho nhiều lớp đối tượng khác nhau, một thao tác như vậy được gọi là có tính đa dạng (polymorphism).  Mỗi thao tác trên mỗi lớp đối tượng cụ thể tương ứng với một cài đặt cụ thể khác nhau. Một cài đặt như vậy được gọi là một phương thức (method).  Một đối tượng cụ thể thuộc một lớp được gọi là một thể hiện (instance) của lớp đó. Joe Smith, 25 tuổi, nặng Chương 2. Lớp 5 58kg, là một thể hiện của lớp người. Sơ đồ đối tượng  Ta dùng sơ đồ đối tượng để mô tả các lớp đối tượng. Sơ đồ đối tượng bao gồm sơ đồ lớp và sơ đồ thể hiện.  Sơ đồ lớp mô tả các lớp đối tượng trong hệ thống, một lớp đối tượng được diễn tả bằng một hình chữ nhật có 3 phần: phần đầu chỉ tên lớp, phần thứ hai mô tả các thuộc tính và phần thứ ba mô tả các thao tác của các đối tượng trong lớp đó. Chương 2. Lớp 6 Sơ đồ lớp và sơ đồ thể hiện Sinh viên Họ tên Năm sinh Mã số Điểm TB (Sinh viên) Nguyễn Văn A 1984 0610234T 9.2 Tên lớp Thuộc tính Chương 2. Lớp 7 Đi học Đi thi Phân loại Thao tác Sơ đồ lớp Sơ đồ thể hiện 2.2. Các thành phần của lớp  Một kiểu dữ liệu là một biểu diễn cụ thể một khái niệm trong thực tế. Ví dụ kiểu int là một biểu diễn cụ thể của khái niệm số nguyên trong toán học.  Trong C++, các kiểu dữ liệu có sẵn (built-in data types) :int, long, float, double, char... cho phép kiểm tra lúc biên dịch và phát sinh mã chương trình tối ưu. Các kiểu dữ liệu này cung cấp một giao diện tự nhiên độc lập với Chương 2. Lớp 8 phần cài đặt. 2.2. Các thành phần của lớp  Lớp trong C++ là cài đặt của kiểu dữ liệu trừu tượng do người sử dụng định nghĩa, cho phép kết hợp dữ liệu, các phép toán, các hàm liên quan để tạo ra một đơn vị chương trình duy nhất. Các lớp này có đầy đủ ưu điểm và tiện lợi như các kiểu dữ liệu nội tại. Lớp tách rời phần giao diện (chỉ liên quan với người sử dụng) và phần cài đặt lớp. Chương 2. Lớp 9  Lớp trong C++ được cài đặt sử dụng từ khoá struct và class. Ví dụ so sánh: Xây dựng kiểu dữ liệu stack. 1. Cách tiếp cận cổ điển: // Stack1.cpp : //Dung cau truc va ham toan cuc #include typedef int bool; typedef int Item; const bool false = 0, true = 1; Chương 2. Lớp 10 struct Stack { Item *st, *top; int size; }; Ví dụ so sánh (tt) void StackInit(Stack *ps, int sz) { ps->st = ps->top = new Item[ps->size=sz]; } void StackCleanUp(Stack *ps) { delete [] ps->st; Chương 2. Lớp 11 } bool StackFull(Stack *ps) { return (ps->top - ps->st >= ps->size); } Ví dụ so sánh (tt) bool StackEmpty(Stack *ps) { return (ps->top st); } bool StackPush(Stack *ps, Item x) { Chương 2. Lớp 12 if (StackFull(ps)) return false; *ps->top++ = x; return true; } Ví dụ so sánh (tt) bool StackPop(Stack *ps, Item *px) { if (StackEmpty(ps)) return false; *px = *--ps->top; return true; } Chương 2. Lớp 13 Ví dụ so sánh (tt) void XuatHe16(long n) { static char hTab[] = “0123456789ABCDEF”; Stack s; StackInit(&s,8); int x; do { StackPush(&s, n%16); Chương 2. Lớp 14 n /= 16; } while(n); while(StackPop(&s,&x)) cout << hTab[x]; StackCleanUp(&s); } Ví dụ so sánh (tt) Nhận xét:  Giải quyết được vấn đề.  Khai báo cấu trúc dữ liệu nằm riêng, các hàm xử lý dữ liệu nằm riêng ở một nơi khác. Do đó khó theo dõi quản lý khi hệ thống lớn. Vì vậy khó bảo trì.  Mọi thao tác đều có tham số đầu tiên là con trỏ đến đối tượng cần thao tác. Tư tưởng thể hiện ở đây là hàm hay Chương 2. Lớp 15 thủ tục đóng vai trò trọng tâm. Đối tượng được gởi đến cho hàm xử lý.  Trình tự sử dụng qua các bước: Khởi động, sử dụng thực sự, dọn dẹp. Ví dụ so sánh (tt) 2. Cách tiếp cận dùng hàm thành phần: //... struct Stack { Item *st, *top; int size; void Init(int sz) {st = top = new Item[size=sz];} Chương 2. Lớp 16 void CleanUp() {if (st) delete [] st;} bool Full() const {return (top - st >= size);} bool Empty() const {return (top <= st);} bool Push(Item x); bool Pop(Item *px); }; Ví dụ so sánh (tt) bool Stack::Push(Item x) { if (Full()) return false; *top++ = x; return true; } Chương 2. Lớp 17 bool Stack::Pop(Item *px) { if (Empty()) return false; *px = *--top; return true; } Ví dụ so sánh (tt) void XuatHe16(long n) { static char hTab[] = “0123456789ABCDEF”; Stack s; s.Init(8); int x; do { Chương 2. Lớp 18 s.Push(n%16); n /= 16; } while(n); while(s.Pop(&x)) cout << hTab[x]; s.CleanUp(); } Ví dụ so sánh (tt) Nhận xét:  Giải quyết được vấn đề.  Dữ liệu và các hàm xử lý dữ liệu được gom vào một chỗ bên trong cấu trúc. Do đó dễ theo dõi quản lý, dễ bảo trì nâng cấp.  Các thao tác đều bớt đi một tham số so với cách tiếp cận cổ điển. Vì vậy việc lập trình gọn hơn. Tư tưởng thể hiện Chương 2. Lớp 19 ở đây là đối tượng đóng vai trò trọng tâm. Đối tượng thực hiện thao tác trên chính nó.  Trình tự sử dụng qua các bước: Khởi động, sử dụng thực sự, dọn dẹp. 2.2.1 Các hàm thành phần.  Là hàm được khai báo trong lớp. Hàm thành phần có thể được định nghĩa bên trong hoặc bên ngoài lớp.  Hàm thành phần có nghi thức giao tiếp giống với các hàm bình thường khác: có tên, danh sách tham số, giá trị trả về.  Gọi hàm thành phần bằng phép toán dấu chấm (.) hoặc dấu mũi tên (->). Chương 2. Lớp 20 Stack s, *ps = &s; s.Init(10); for (int i = 0; i < 20; i++) ps->Push(i); 2.2.3 Lớp  Trong cách tiếp cận dùng struct và hàm thành phần, người sử dụng có toàn quyền truy xuất, thay đổi các thành phần dữ liệu của đối tượng thuộc cấu trúc. Ví dụ: Stack s; s.Init(10); s.size = 100; // Nguy hiem for (int i = 0; i < 20; i++) Chương 2. Lớp 21 s.Push(i);  Vì vậy, ta không có sự an toàn dữ liệu. Lớp là một phương tiện để khắc phục nhược điểm trên.  Lớp có được bằng cách thay từ khoá struct bằng từ khoá class. Lớp  Trong lớp mọi thành phần mặc nhiên đều là riêng tư (private) nghĩa là thế giới bên ngoài không được phép truy xuất. Do đó có sự an toàn dữ liệu. class Stack { Item *st, *top; int size; Chương 2. Lớp 22 void Init(int sz) {st = top = new Item[size=sz];} void CleanUp() {if (st) delete [] st;} bool Full() const {return (top - st >= size);} bool Empty() const {return (top <= st);} bool Push(Item x); bool Pop(Item *px); }; Lớp  Phát biểu: s.size = 100; // Bao sai Sẽ bị báo sai lúc biên dịch, nhờ đó tránh được những lỗi lúc chạy chương trình (run-time error) rất khó tìm khi thực hiện chương trình. Chương 2. Lớp 23 2.2.4 Thuộc tính truy xuất  Tuy nhiên lớp như trên trở thành vô dụng vì các hàm thành phần cũng trở thành private và không ai dùng được. Điều đó được khắc phục nhờ thuộc tính truy xuất.  Thuộc tính truy xuất của một thành phần của lớp chỉ rõ phần chương trình được phép truy xuất đến nó. • Thuộc tính private • Thuộc tính public Chương 2. Lớp 24 Thuộc tính truy xuất  Các thành phần là nội bộ của lớp, bao gồm dữ liệu và các hàm phục vụ nội bộ được đặt trong phần private. Các hàm nhằm mục đích cho người sử dụng dùng được đặt trong phần public.  Ví dụ sau minh hoạ thuộc tính truy xuất. Chương 2. Lớp 25 // Stack.h class Stack { Item *st, *top; int size; void Init(int sz) {st = top = new Item[size=sz];} void CleanUp() {delete [] st;} public: Stack(int sz = 20) {Init(sz);} Thuộc tính truy xuất Chương 2. Lớp 26 ~Stack() {delete [] st;} bool Full() const {return (top - st >= size);} bool Empty() const {return (top <= st);} bool Push(Item x); bool Pop(Item *px); }; Ví dụ về lớp và thuộc tính truy xuất // Stack.cpp #include "stack.h" bool Stack::Push(Item x) { if (Full()) return false; *top++ = x; return true; } Chương 2. Lớp 27 bool Stack::Pop(Item *px) { if (Empty()) return false; *px = *--top; return true; } Ví dụ về lớp và thuộc tính truy xuất // he16.cpp #include "stack.h" void XuatHe16(long n) { static char hTab[] = “0123456789ABCDEF”; Stack s(8); int x; do { Chương 2. Lớp 28 s.Push(n%16); n /= 16; } while(n); while(s.Pop(&x)) cout << hTab[x]; } Sử dụng phạm vi truy xuất  Phạm vi truy xuất được sử dụng đúng sẽ cho phép ta kết luận: Nhìn vào lớp thấy được mọi thao tác trên lớp.  Người dùng bình thường có thể khai thác hết các chức năng (public) của lớp.  Người dùng cao cấp có thể thay đổi chi tiết cài đặt, cải tiến giải thuật các hàm thành phần. Chương 2. Lớp 29 Tự tham chiếu  Là tham số ngầm định của hàm thành phần trỏ đến đối tượng. Nhờ đó hàm thành phần biết được nó đang thao tác trên đối tượng nào.  Khi một đối tượng gọi một thao tác, địa chỉ của đối tượng được gởi đi một cách ngầm định với tên this, tên các thành phần của đối tượng được hiểu là của đối tượng có địa chỉ this này. Chương 2. Lớp 30 bool Stack::Push(Item x) { if (Full()) // if (this->Full()) return false; *top++ = x; // *this->top++ = x; return true; } Phương thức thiết lập và hủy bỏ.  Phương thức thiết lập và huỷ bỏ được xây dựng nhằm mục đích khắc phục lỗi quên khởi động đối tượng hoặc khởi động dư. Việc quên khởi động đối tượng thường gây ra những lỗi rất khó tìm.  Phương thức thiết lập là hàm thành phần đặc biệt được tự động gọi đến mỗi khi một đối tượng thuộc lớp được tạo ra. Người ta thường lợi dụng đặc tính trên để khởi Chương 2. Lớp 31 động đối tượng.  Phương thức thiết lập có tên trùng với tên lớp để phân biệt nó với các hàm thành phần khác. Phương thức thiết lập và hủy bỏ.  Có thể có nhiều phiên bản khác nhau của phương thức thiết lập  Phương thức huỷ bỏ là hàm thành phần đặc biệt được tự động gọi đến mỗi khi một đối tượng bị huỷ đi. Người ta thường lợi dụng đặc tính trên để dọn dẹp đối tượng.  Phương thức huỷ bỏ bắt đầu bằng dấu ngã (~) theo sau bởi tên lớp để phân biệt nó với các hàm thành phần Chương 2. Lớp 32 khác.  Chỉ có thể có tối đa một phương thức huy bo. Phương thức thiết lập và hủy bỏ. typedef int Item; class Stack { Item *st, *top; int size; void Init(int sz); void CleanUp() {delete [] st;} public: Stack(int sz = 20) {Init(sz);} Chương 2. Lớp 33 ~Stack() {delete [] st;} bool Full() const {return (top - st >= size);} bool Empty() const {return (top <= st);} bool Push(Item x); bool Pop(Item *px); }; 2.3 Các đặc tính khác của lớp  Hàm bạn (Friends).  Hàm thành phần hằng (const member functions).  Thành phần tĩnh (static members)  Con trỏ tới hàm thành phần. Chương 2. Lớp 34 2.3.1 Hàm bạn (friends)  Nguyên tắc chung khi thao tác trên lớp là thông qua các hàm thành phần. Tuy nhiên có những trường hợp ngoại lệ, khi hàm phải thao tác trên hai lớp.  Hàm bạn của một lớp là hàm được khai báo ở bên ngoài nhưng được phép truy xuất các thành phần riêng tư của lớp.  Ta làm một hàm trở thành hàm bạn của lớp bằng cách Chương 2. Lớp 35 đưa khai báo của hàm đó vào trong lớp, thêm từ khoá friend ở đầu.  Ta dùng hàm bạn trong trường hợp hàm phải là hàm toàn cục nhưng có liên quan mật thiết với lớp, hoặc là hàm thành phần của một lớp khác. Ví dụ: Nhân ma trận, không dùng hàm bạn const int N = 4; class Vector { double a[N]; public: double Get(int i) const {return a[i];} void Set(int i, double x) {a[i] = x;} }; Chương 2. Lớp 36 Ví dụ: Nhân ma trận, không dùng hàm bạn class Matrix { double a[N][N]; public: double Get(int i, int j) const {return a[i][j];} void Set(int i, int j, double x) {a[i][j] = x;} //... }; Chương 2. Lớp 37 Ví dụ: Nhân ma trận, không dùng hàm bạn Vector Multiply(const Matrix &m, const Vector &v) { Vector r; for (int i = 0; i < N; i++) { r.Set(i, 0); Chương 2. Lớp 38 for (int j = 0; j < N; j++) r.Set(i, r.Get(i)+ m.Get(i,j)*v.Get(j)); } return r; } const int N = 4; class Matrix; class Vector { double a[N]; public: double Get(int i) const {return a[i];} void Set(int i, double x) {a[i] = x;} Ví dụ: Nhân ma trận, dùng hàm bạn Chương 2. Lớp 39 friend Vector Multiply(const Matrix &m, const Vector &v); }; class Matrix { double a[N][N]; public: double Get(int i, int j) const {return a[i][j];} void Set(int i, int j, double x) {a[i][j] = x;} friend Vector Multiply(const Matrix &m, const Vector &v); Ví dụ: Nhân ma trận, dùng hàm bạn Chương 2. Lớp 40 }; Ví dụ minh hoạ: Sử dụng hàm bạn Vector Multiply(const Matrix &m, const Vector &v) { Vector r; for (int i = 0; i < N; i++) { r.a[i] = 0; Chương 2. Lớp 41 for (int j = 0; j < N; j++) r.a[i] += m.a[i][j]*v.a[j]; } return r; } 2.3.2 Hàm thành phần hằng  Hàm thành phần hằng là hàm thành phần có thể áp dụng được cho các đối tượng hằng.  Ta qui định một hàm thành phần là hằng bằng cách thêm từ khoá const vào cuối khai báo của nó.  Ta khai báo hàm thành phần là hằng khi nó không thay đổi các thành phần dữ liệu của đối tượng. Chương 2. Lớp 42 Hàm thành phần hằng inline char *strdup(const char *s) { return strcpy(new char[strlen(s) + 1], s); } class string { char *p; Chương 2. Lớp 43 public: string(char *s = "") {p = strdup(s);} ~string() {delete [] p;} string(const string &s2) {p = strdup(s2.p);} void Output() const {cout << p;} void ToLower() {strlwr(p);} }; Hàm thành phần hằng void main() { const string Truong("DH BC TDT"); string s("ABCdef"); s.Output(); s.ToLower(); s.Output(); Chương 2. Lớp 44 Truong.Output(); Truong.ToLower(); // Bao loi } 2.3.3 Thành phần tĩnh (static members)  Thành phần dữ liệu tĩnh là thành phần dữ liệu dùng chung cho mọi đối tượng thuộc lớp.  Hàm thành phần tĩnh là hàm thành phần có thể hoạt động không cần dữ liệu của đối tượng, nói cách khác, nó không cần đối tượng.  Ta dùng hàm thành phần tĩnh thay vì hàm toàn cục vì nó có liên quan mật thiết với lớp. Chương 2. Lớp 45  Ta còn dùng hàm thành phần tĩnh để tạo đối tượng có giá trị trả về cho biết việc tạo đối tượng có thành công theo nghĩa luận lý hay không. Ví dụ về thành phần tĩnh: CDate typedef int bool; const bool false = 0, true = 1; class CDate { static int dayTab[][13]; int day, month, year; public: Chương 2. Lớp 46 static bool LeapYear(int y) {return y%400 == 0 || y%4==0 && y%100 != 0;} static int DayOfMonth(int m, int y); static bool ValidDate(int d, int m, int y); void Input(); }; Ví dụ về thành phần tĩnh : CDate int CDate::dayTab[][13] = { {0,31,28,31,30,31,30,31,31,30,31,30,31}, {0,31,29,31,30,31,30,31,31,30,31,30,31} }; int CDate::DayOfMonth(int m, int y) { return dayTab[LeapYear(y)][m]; Chương 2. Lớp 47 } bool betw(int x, int a, int b) { return x >= a && x <= b; } Ví dụ về thành phần tĩnh : CDate bool CDate::ValidDate(int d, int m, int y) { return betw(m,1,12) && betw(d,1,DayOfMonth(m,y)); } void CDate::Input() { int d,m,y; Chương 2. Lớp 48 cin >> d >> m >> y; while (!ValidDate(d,m,y)) { cout << "Please enter a valid date: "; cin >> d >> m >> y; } day = d; month = m; year = y; } Ví dụ về thành phần tĩnh: Stack class Stack { Item *st, *top; int size; void Init(int sz); void CleanUp() {if (st) delete [] st;} public: Stack(int sz = 20) {Init(sz);} Chương 2. Lớp 49 ~Stack() {CleanUp();} static Stack *Create(int sz); bool Full() const {return (top-st >= size);} bool Empty() const {return (top <= st);} bool Push(Item x); bool Pop(Item *px); }; Ví dụ về thành phần tĩnh: Stack // Stack.cpp #include #include "stack.h" bool Stack::Push(Item x) { if (Full()) return false; *top++ = x; return true; Chương 2. Lớp 50 } bool Stack::Pop(Item *px) { if (Empty()) return false; *px = *--top; return true; } Stack *Stack::Create(int sz) { Stack *ps = new Stack(sz); if (!ps->st) { delete ps; return NULL; } Ví dụ về thành phần tĩnh: Stack Chương 2. Lớp 51 return ps; } void main() { Stack *ps = new Stack(50000); // khong biet tao duoc stack khong Stack *pr = Stack::Create(50000); if (!pr) { cout << "Khong the tao stack"; return; } else Ví dụ về thành phần tĩnh: Stack Chương 2. Lớp 52 pr->Push(5); // ... } 2.4 Thiết lập và huỷ bỏ đối tượng Ta cần kiểm soát khi nào phương thức thiết lập được gọi, khi nào phương thức huỷ bỏ được gọi. • Khi nào đối tượng thiết lập được gọi? Khi đối tượng được tạo ra. • Khi nào phương thức huỷ bỏ được gọi? Khi đối tượng bị huỷ đi. Thời gian từ khi đối tượng được tạo ra đến khi nó bị huỷ Chương 2. Lớp 53 • đi được gọi là thời gian sống.  Vậy vấn đề xác định khi nào phương thức thiết lập và huỷ bỏ được gọi trở thành: Khi nào đối tượng được tạo ra ? Khi nào đối tượng bị huỷ đi ? Thiết lập và huỷ bỏ đối tượng  Thời gian sống của đối tượng khác nhau tuỳ thuộc đối tượng đó thuộc lớp lưu trữ (storage class) nào. Trong C++ có các lớp lưu trữ sau: auto global static free stored Chương 2. Lớp 54  Ta sẽ lần lượt xét các loại sau: Đối tượng tự động Đối tượng toàn cục Đối tượng tĩnh Đối tượng được cấp phát động Đối tượng thành phần Đối tượng tự động  Đối tượng tự động (automatic objects) là đối tượng được tự động sinh ra và tự động bị huỷ đi.  Đối tượng địa phương • Là các biến được khai báo, định nghĩa bên trong một khối. • Nó được tự động sinh ra khi chương trình thực hiện ngang dòng lệnh chứa định nghĩa và bị huỷ đi sau khi chương trình hoàn tất khối chứa định nghĩa đó. Chương 2. Lớp 55  Khi khởi động một đối tượng bằng một đối tượng cùng kiểu, cơ chế tạo đối tượng mặc nhiên là sao chép từng bit, do đó đối tượng được khởi động sẽ chia sẻ tài nguyên với đối tượng nguồn. Đối tượng là biến địa phương #include #include char *strdup(const char *s) { return strcpy(new char[strlen(s) + 1], s); } class string Chương 2. Lớp 56 { char *p; public: string(char *s = "") {p = strdup(s);} ~string() {cout << "delete "<< (void *)p << "\n"; delete [] p;} void Output() const {cout << p;} }; void main() { string a("Nguyen Van A"); string b = a; // String b(a) a.Output(); cout << "\n"; b.Output(); cout << "\n"; } Chương 2. Lớp 57  Xuất liệu khi thực hiện đoạn chương trình trên: Nguyen Van A Nguyen Van A delete 0x0f06 delete 0x0f06  Đối tượng b có nội dung vật lý giống với a, nghĩa là dùng chung phần tài nguyên (con trỏ đến chuỗi “Nguyen Van A”). Khi kết thúc hàm main() hai đối tượng này bị huỷ, Chương 2. Lớp 58 phần tài nguyên chung bị huỷ 2 lần (SAI). Đối tượng là tham số truyền bằng giá trị  Đối tượng là tham số hàm, truyền bằng giá trị thì tham số hình thức là bản sao của tham số thực sự, nên có nội dung vật lý giống tham số thực sự do cơ chế sao chép từng bit. extern char *strdup(const char *s); class String { char *p; Chương 2. Lớp 59 public: String(char *s = "") {p = strdup(s);} ~String() {cout << "delete "<< (void *)p << "\n"; delete [] p;} void Output() const {cout << p;} int Compare(String s) const; }; Đối tượng là tham số truyền bằng giá trị int String::Compare(String s) const { return strcmp(p,s.p); } void main() { Chương 2. Lớp 60 String a("Nguyen Van A"); String b("Le Van Beo"); int c = a.Compare(b); cout 0 ? "a > b" : c == 0 ? "a = b" : "a < b") << "\n"; } Đối tượng là tham số truyền bằng giá trị  Khi thực hiện đoạn chương trình trên, ta được xuất liệu (có thể thay đổi ở những lần thực hiện khác nhau hoặc ở máy khác): delete 0x0f34 a > b delete 0x0f34 delete 0x0f22 Chương 2. Lớp 61 Đối tượng là giá trị trả về extern char *strdup(const char *s); class String { char *p; public: String(char *s = "") {p = strdup(s);} ~String() {cout << "delete "<< (void *)p << "\n"; delete [] p;} Chương 2. Lớp 62 void Output() const {cout << p;} int Compare(String s) const; String UpCase() const; }; Đối tượng là giá trị trả về String String::UpCase() const { String r = *this; strupr(r.p); return r; } void main() { Chương 2. Lớp 63 clrscr(); String a("Nguyen Van A"); cout << "a = "; a.Output(); cout << "\n"; String A; A = a.UpCase(); cout << "a = "; a.Output(); cout << "\n"; cout << "A = "; a.Output(); cout << "\n"; } Đối tượng là giá trị trả về  Khi thực hiện đoạn chương trình trên, ta được xuât liệu a = Nguyen Van A delete 0x0f36 delete 0x0f36 a = 2¤2¤EN VAN A A = 2¤2¤EN VAN A delete 0x0f36 Chương 2. Lớp 64 delete 0x0f36 Null pointer assignment Đối tượng giá trị trả về là bản sao của biểu thức trả về. Do đó có sự chia sẻ tài nguyên (SAI). Đối tượng là giá trị trả về Các lỗi sai gây ra ở đoạn chương trình trên do sao chép đối tượng (phát biểu String r = *this), đối tượng giá trị trả về và phép gán (A = a.Upcase).  Ta có thể khắc phục lỗi gây ra do phép gán bằng cách thay hai phát biểu khai báo A và gán bằng phát biểu khởi động: String A = a.UpCase(); Chương 2. Lớp 65 Đối tượng là giá trị trả về void main() { clrscr(); String a("Nguyen Van A"); cout << "a = "; a.Output(); cout << "\n"; String A = a.UpCase(); cout << "a = "; a.Output(); cout << "\n"; cout << "A = "; a.Output(); cout << "\n"; } Chương 2. Lớp 66 Đối tượng là giá trị trả về  Xuất liệu trong trường hợp này là a = Nguyen Van A delete 0x0d32 a = NGUYEN VAN A A = NGUYEN VAN A delete 0x0d32 delete 0x0d32 Chương 2. Lớp 67 Null pointer assignment Phương thức thiết lập bản sao  Các lỗi sai nêu trên gây ra do sao chép đối tượng, ta có thể khắc phục bằng cách “dạy” trình biên dịch sao chép đối tượng một cách luận lý thay vì sao chép từng bit theo nghĩa vật lý. Điều đó được thực hiện nhờ phương thức thiết lập bản sao.  Phương thức thiết lập bản sao là phương thức thiết lập có tham số là đối tượng cùng kiểu, dùng để sao chép đối Chương 2. Lớp 68 tượng. Phương thức thiết lập bản sao  Phương thức thiết lập bản sao thực hiện sao chép theo nghĩa logic, thông thường là tạo nên tài nguyên mới (sao chép sâu).  Phương thức thiết lập bản sao cũng có thể chia sẻ tài nguyên cho các đối tượng (sao chép nông). Trong trường hợp này, cần có cơ chế để kiểm soát sử huỷ bỏ đối tượng. Chương 2. Lớp 69 Phương thức thiết lập bản sao extern char *strdup(const char *s); class String { char *p; public: String(char *s = "") {p = strdup(s);} String(const String &s) {p = strdup(s.p);} Chương 2. Lớp 70 ~String() {cout << "delete "<< (void *)p << "\n"; delete [] p;} void Output() const {cout << p;} int Compare(String s) const; String UpCase() const; }; Phương thức thiết lập bản sao int String::Compare(String s) const { return strcmp(p,s.p); } String String::UpCase() const { String r = *this; Chương 2. Lớp 71 strupr(r.p); return r; } Phương thức thiết lập bản sao void main() { clrscr(); String a("Nguyen Van A"); String b("Le Van Beo"); String aa = a; int c = a.Compare(b); Chương 2. Lớp 72 cout 0 ? "a > b" : c == 0 ? "a = b" : "a < b") << "\n"; cout << "a = "; a.Output(); cout << "\n"; String A = a.UpCase(); cout << "a = "; a.Output(); cout << "\n"; cout << "A = "; A.Output(); cout << "\n"; } Phương thức thiết lập bản sao  Xuất liệu trong trường hợp trên như sau: delete 0x0d84 a > b a = Nguyen Van A delete 0x0d84 a = Nguyen Van A A = NGUYEN VAN A Chương 2. Lớp 73 delete 0x0d96 delete 0x0d72 delete 0x0d62 delete 0x0d50  Mỗi đối tượng đều có tài nguyên riêng nên không xảy trường hợp một vùng tài nguyên bị giải phóng nhiều lần. Phương thức thiết lập bản sao  Tham số của phương thức thiết lập bản sao bắt buộc là tham chiếu.  Phương thức thiết lập bản sao có thể được dùng để sao chép nông, tài nguyên vẫn được chia sẻ nhưng có một biến đếm để kiểm soát. Lưu ý: Nếu đối tượng không có tài nguyên riêng thì không cần Chương 2. Lớp 74  phương thức thiết lập bản sao.  Khi truyền tham số là đối tượng thuộc lớp có phương thức thiết lập bản sao, ta nên truyền bằng tham chiếu và có thêm từ khoá const. Sao chép nông và sao chép sâu  Dùng phương thức thiết lập bản sao như trên, trong đó đối tượng mới có tài nguyên riêng là sao chép sâu.  Ta có thể sao chép nông bằng cách chia sẻ tài nguyên và dùng một biến đếm để kiểm soát số thể hiện các đối tượng có chia sẻ cùng tài nguyên.  Khi một đối tượng thay đổi nội dung, nó phải được tách ra khỏi các đối tượng dùng chung tài nguyên, nói cách Chương 2. Lớp 75 khác, nó phải có tài nguyên riêng (như sao chép sâu). Ví dụ về sao chép nông extern char *strdup(const char *s); class StringRep { friend class String; char *p; int n; StringRep(const char *s) {p = strdup(s); n = 1;} ~StringRep() {cout << "delete ” << (void *)p << Chương 2. Lớp 76 "\n"; delete [] p;} }; Ví dụ về sao chép nông class String { StringRep *rep; public: String(const char *s = "") {rep = new StringRep(s);} String(const String &s) {rep = s.rep; rep->n++;} Chương 2. Lớp 77 ~String(); void Output() const {cout p;} int Compare(String s) const; String UpCase() const; void ToUpper(); }; Ví dụ về sao chép nông String::~String() { if (--rep->n <= 0) delete rep; } int String::Compare(String s) const { return strcmp(rep->p,s.rep->p); Chương 2. Lớp 78 } String String::UpCase() const { String r(rep->p); strupr(r.rep->p); return r; } Ví dụ về sao chép nông void main() { clrscr(); String a("Nguyen Van A"); String b("Le Van Beo"); String aa = a; int c = a.Compare(b); cout 0 ? "a > b" : c == 0 ? "a = b" : "a Chương 2. Lớp 79 < b") << "\n"; cout << "a = "; a.Output(); cout << "\n"; String A = a.UpCase(); cout << "a = "; a.Output(); cout << "\n"; cout << "A = "; A.Output(); cout << "\n"; } Ví dụ về sao chép nông  Xuất liệu khi thực hiện đoạn chương trình trên như sau: a > b a = Nguyen Van A a = Nguyen Van A A = NGUYEN VAN A delete 0x0d8a delete 0x0d72 Chương 2. Lớp 80 delete 0x0d58 Ví dụ về sao chép nông delete 0x0d84 a > b a = Nguyen Van A delete 0x0d84 a > b a = Nguyen Van A a = Nguyen Van A A = NGUYEN VAN A So sánh với sao chép sâu: Bên trái, dùng sao chép sâu, bên phải dùng sao chép nông. Chương 2. Lớp 81 a = Nguyen Van A A = NGUYEN VAN A delete 0x0d96 delete 0x0d72 delete 0x0d62 delete 0x0d50 delete 0x0d8a delete 0x0d72 delete 0x0d58 Đối tượng tĩnh  Đối tượng tĩnh có thể là đối tượng được định nghĩa toàn cục, được định nghĩa có thêm từ khoá static (toàn cục hoặc địa phương).  Đối tượng toàn cục hoặc tĩnh toàn cục được tạo ra khi bắt đầu chương trình và bị huỷ đi khi kết thúc chương trình.  Đối tượng tĩnh địa phương được tạo ra khi chương trình Chương 2. Lớp 82 thực hiện đoạn mã chứa định nghĩa đối tượng lần đầu tiên và bị huỷ đi khi kết thúc chương trình. Đối tượng tĩnh  Trình tự thực hiện chương trình gồm: • Gọi phương thức thiết lập cho các đối tượng toàn cục • Thực hiện hàm main() • Gọi phương thức huỷ bỏ cho các đối tượng toàn cục  Ví dụ sau minh hoạ ý nghĩa của đối tượng toàn cục Chương 2. Lớp 83 Ví dụ về đối tượng toàn cục  Xét đoạn chương trình sau: #include void main() { cout << "Hello, world.\n"; } Hãy sửa lại đoạn chương trình trên để có xuất liệu: Chương 2. Lớp 84 Entering a C++ program saying... Hello, world. And then exitting Yêu cầu không thay đổi hàm main() dưới bất kỳ hình thức nào. Ví dụ về đối tượng toàn cục  Đoạn chương trình được sửa lại như sau: #include void main() { cout << "Hello, world.\n"; } class Dummy Chương 2. Lớp 85 { public: Dummy() {cout << "Entering a C++ program saying...\n";} ~Dummy() {cout << "And then exitting...";} }d; Đối tượng là thành phần của lớp  Đối tượng có thể là thành phần của đối tượng khác, khi một đối tượng thuộc lớp “lớn” được tạo ra, các thành phần của nó cũng được tạo ra. Phương thức thiết lập (nếu có) sẽ được tự động gọi cho các đối tượng thành phần.  Nếu đối tượng thành phần phải được cung cấp tham số khi thiết lập thì đối tượng kết hợp (đối tượng lớn) phải Chương 2. Lớp 86 có phương thức thiết lập để cung cấp tham số thiết lập cho các đối tượng thành phần. Đối tượng là thành phần của lớp  Cú pháp để khởi động đối tượng thành phần là dùng dấu hai chấm (:) theo sau bởi tên thành phần và tham số khởi động.  Khi đối tượng kết hợp bị huỷ đi thì các đối tượng thành phần của nó cũng bị huỷ đi, nghĩa là phương thức huỷ bỏ sẽ được gọi cho các đối tượng thành phần, sau khi phương thức huỷ bỏ của đối tượng kết hợp được gọi. Chương 2. Lớp 87 Đối tượng là thành phần của lớp class Diem { double x,y; public: Diem(double xx, double yy) {x = xx; y = yy;} // ... }; class TamGiac { Chương 2. Lớp 88 Diem A,B,C; public: void Ve() const; // ... }; TamGiac t; // Bao sai Đối tượng là thành phần của lớp class String { char *p; public: String(char *s) {p = strdup(s);} String(const String &s) {p = strdup(s.p);} ~String() {cout << "delete "<< (void *)p << "\n"; delete [] p; //... }; Chương 2. Lớp 89 class SinhVien { String MaSo; String HoTen; int NamSinh; public: }; SinhVien s; // Bao sai Đối tượng thành phần - thiết lập class Diem { double x,y; public: Diem(double xx, double yy) {x = xx; y = yy;} // ... }; Chương 2. Lớp 90 Khởi động đối tượng thành phần class TamGiac { Diem A,B,C; public: TamGiac(double xA, double yA, double xB, double yB, double xC, double yC):A(xA,yA), B(xB,yB),C(xC,yC){} void Ve() const; Chương 2. Lớp 91 // ... }; TamGiac t(100,100,200,400,300,300); Đối tượng là thành phần của lớp class String { char *p; public: String(char *s) {p = strdup(s);} String(const String &s) {p = strdup(s.p);} ~String() {cout << "delete "<< (void *)p << "\n"; delete [] p; Chương 2. Lớp 92 //... }; Khởi động đối tượng thành phần class SinhVien { String MaSo; String HoTen; int NamSinh; public: SinhVien(char *ht, char *ms, int ns):HoTen(ht), MaSo(ms){NamSinh = ns;} Chương 2. Lớp 93 //... }; SinhVien s(“Nguyen Van Beo”, “19920005”, 1985); //Ok Khởi động đối tượng thành phần  Cú pháp dùng dấu hai chấm cũng được dùng cho đối tượng thành phần thuộc kiểu cơ bản. class Diem { double x,y; public: Diem(double xx, double yy):x(xx), y(yy){} Chương 2. Lớp 94 // ... }; Khởi động đối tượng thành phần  Cú pháp dùng dấu hai chấm cũng được dùng cho đối tượng thành phần thuộc kiểu cơ bản. class SinhVien { String MaSo, HoTen; int NamSinh; public: Chương 2. Lớp 95 SinhVien(char *ht, char *ms, int ns):HoTen(ht), MaSo(ms), NamSinh(ns){} //... }; Đối tượng thành phần - Huỷ bỏ  Khi đối tượng kết hợp bị huỷ bỏ, các đối tượng thành phần của nó cũng bị huỷ bỏ. class SinhVien { String MaSo, HoTen; int NamSinh; int SoMon; double *Diem; public: Chương 2. Lớp 96 SinhVien(char *ht, char *ms, int ns, int sm, double *d); ~SinhVien() {delete [] Diem;} //... }; SinhVien::SinhVien(char *ht, char *ms, int ns, int sm, double *d):HoTen(ht), MaSo(ms), NamSinh(ns), SoMon(sm) { memcpy(Diem = new double[SoMon], d, SoMon*sizeof(double)); } Đối tượng là thành phần của mảng  Khi một mảng được tạo ra, các phần tử của nó cũng được tạo ra, do đó phương thức thiết lập sẽ được gọi cho từng phần tử một.  Vì không thể cung cấp tham số khởi động cho tất cả các phần tử của mảng nên khi khai báo mảng, mỗi đối tượng trong mảng phải có khả năng tự khởi động, nghĩa là có thể được thiết lập không cần tham số. Chương 2. Lớp 97  Đối tượng có khả năng tự khởi động trong các trường hợp sau: • Lớp không có phương thức thiết lập. • Lớp có phương thức thiết lập không tham số. • Lớp có phương thức thiết lập mà mọi tham số đều có giá trị mặc nhiên. Đối tượng là thành phần của mảng class Diem { double x,y; public: Diem(double xx, double yy):x(xx), y(yy) {} void Set(double xx, double yy) {x = xx, y = yy;} // ... }; class String Chương 2. Lớp 98 { char *p; public: String(char *s) {p = strdup(s);} String(const String &s) {p = strdup(s.p);} ~String() {cout << "delete "<< (void *)p << "\n"; delete [] p;} // ... Đối tượng là thành phần của mảng class SinhVien { String MaSo; String HoTen; int NamSinh; public: SinhVien(char *ht, char *ms, int ns):HoTen(ht), MaSo(ms), NamSinh(ns){} Chương 2. Lớp 99 //... }; String as[3]; // Bao sai Diem ad[5]; // Bao sai SinhVien asv[7]; // Bao sai PTTLập với tham số có giá trị mặc nhiên class Diem { double x,y; public: Diem(double xx = 0, double yy = 0):x(xx), y(yy){} void Set(double xx, double yy) {x = xx, y = yy;} // ... }; class String Chương 2. Lớp 100 { char *p; public: String(char *s = “”) {p = strdup(s);} String(const String &s) {p = strdup(s.p);} ~String() {cout << "delete "<< (void *)p << "\n"; delete [] p;} // ... PTTlập với tham số có giá trị mặc nhiên class SinhVien { String MaSo; String HoTen; int NamSinh; public: SinhVien(char *ht = “Nguyen Van A”, char *ms = “19920014”, int ns = 1982):HoTen(ht), MaSo(ms), Chương 2. Lớp 101 NamSinh(ns){} //... }; String as[3]; // Ok: Ca ba phan tu deu la chuoi rong Diem ad[5]; // Ok: ca 5 diem deu la (0,0) SinhVien asv[7]; // Ok: Het sai ca 7 sinh vien deu co cung hoten, maso, namsinh Dùng PTTlập không tham số class Diem { double x,y; public: Diem(double xx, double yy):x(xx), y(yy){} Diem():x(0), y(0){} // ... }; class String Chương 2. Lớp 102 { char *p; public: String(char *s) {p = strdup(s);} String() {p = strdup(“”);} ~String() {cout << "delete "<< (void *)p << "\n"; delete [] p;} // ... }; Dùng ptthiết lập không tham số class SinhVien { String MaSo; String HoTen; int NamSinh; public: SinhVien(char *ht, char *ms, int ns):HoTen(ht), MaSo(ms), NamSinh(ns){} Chương 2. Lớp 103 SinhVien():HoTen(“Nguyen Van A”), MaSo(“19920014”), NamSinh(1982){} //... }; String as[3]; // Ca ba phan tu deu la chuoi rong Diem ad[5]; // ca 5 diem deu la (0,0) SinhVien asv[7];// Ca 7 sinh vien deu co cung hoten, maso, namsinh Đối tượng được cấp phát động  Đối tượng được cấp phát động là các đối tượng được tạo ra bằng phép toán new và bị huỷ đi bằng phép toán delete  Phép toán new cấp đối tượng trong vùng heap (hay vùng free store) và gọi phương thức thiết lập cho đối tượng được cấp.  Dùng new có thể cấp một đối tượng và dùng delete để Chương 2. Lớp 104 huỷ một đối tượng.  Dùng new và delete cũng có thể cấp nhiều đối tượng và huỷ nhiều đối tượng. Đối tượng được cấp phát động class String { char *p; public: String(char *s) {p = strdup(s);} String(const String &s) {p = strdup(s.p);} ~String() {delete [] p;} //... Chương 2. Lớp 105 }; class Diem { double x,y; public: Diem(double xx, double yy):x(xx),y(yy){}; //... }; //... Cấp và huỷ một đối tượng int *pi = new int; int *pj = new int(15); Diem *pd = new Diem(20,40); String *pa = new String("Nguyen Van A"); //... delete pa; delete pd; delete pj; Chương 2. Lớp 106 delete pi; Cấp và huỷ nhiều đối tượng  Trong trường hợp cấp nhiều đối tượng, ta không thể cung cấp tham số cho từng phần tử được cấp: int *pai = new int[10]; Diem *pad = new Diem[5]; // Bao sai String *pas = new String[5]; // Bao sai //...  Thông báo lỗi cho đoạn chương trình trên như sau: Chương 2. Lớp 107 Cannot find default constructor to initialize array element of type 'Diem' Cannot find default constructor to initialize array element of type String’  Lỗi trên được khắc phục bằng cách cung cấp phương thức thiết lập để đối tượng có khả năng tự khởi động. Cấp và huỷ nhiều đối tượng class String { char *p; public: String(char *s = "Alibaba") {p = strdup(s);} String(const String &s) {p = strdup(s.p);} ~String() {delete [] p;} //... Chương 2. Lớp 108 }; class Diem { double x,y; public: Diem(double xx, double yy):x(xx),y(yy){}; Diem():x(0),y(0){}; //... }; Cấp và huỷ nhiều đối tượng  Khi đó mọi phần tử được cấp đều được khởi động với cùng giá trị int *pai = new int[10]; Diem *pad = new Diem[5]; // ca 5 diem co cung toa do (0,0) String *pas = new String[5]; // Ca 5 chuoi cung duoc khoi dong bang “Alibaba”  Việc huỷ nhiều đối tượng được thực hiện bằng cách dùng Chương 2. Lớp 109 delete và có thêm dấu [] ở trước. delete [] pas; delete [] pad; delete [] pai;  (?) Có thể thay ba phát biểu trên bằng một phát biểu duy nhất sau được không ? delete pas,pad,pai; // ?? 2.4 Giao diện và chi tiết cài đặt  Lớp có hai phần tách rời, một là phần giao diện khai báo trong phần public để người sử dụng “thấy” và sử dụng, và hai là chi tiết cài đặt bao gồm dữ liệu khai báo trong phần private của lớp và chi tiết mã hoá các hàm thành phần, vô hình đối với người dùng.  Ta có thể thay đổi uyển chuyển chi tiết cài đặt, nghĩa là có thể thay đổi tổ chức dữ liệu của lớp, cũng như có thể Chương 2. Lớp 110 thay đổi chi tiết thực hiện các hàm thành phần (do sự thay đổi tổ chức dữ liệu hoặc để cải tiến giải thuật). Nhưng nếu bảo đảm không thay đổi phần giao diện thì không ảnh hưởng đến người sử dụng, và do đó không làm đổ vỡ kiến trúc của hệ thống.  Lớp ThoiDiem có thể được cài đặt với các thành phần dữ liệu là giờ, phút, giây hoặc tổng số giây tính từ 0 giờ. Lớp ThoiDiem – Cách 1 class ThoiDiem { int gio, phut, giay; static bool HopLe(int g, int p, int gy); public: ThoiDiem(int g = 0, int p = 0, int gy = 0) {Set(g,p,gy);} void Set(int g, int p, int gy); Chương 2. Lớp 111 int LayGio() const {return gio;} int LayPhut() const {return phut;} int LayGiay() const {return giay;} void Nhap(); void Xuat() const; void Tang(); void Giam(); }; Lớp ThoiDiem – Cách 2 class ThoiDiem { long tsgiay; static bool HopLe(int g, int p, int gy); public: ThoiDiem(int g = 0, int p = 0, int gy = 0) {Set(g,p,gy);} void Set(int g, int p, int gy); Chương 2. Lớp 112 int LayGio() const {return tsgiay / 3600;} int LayPhut() const {return (tsgiay%3600)/60;} int LayGiay() const {return tsgiay % 60;} void Nhap(); void Xuat() const; void Tang(); void Giam(); }; Giao diện và chi tiết cài đặt  Có thể xem chi tiết đầy đủ lớp thời điểm cài đặt bằng giờ, phút, giây và cài đặt bằng tổng số giây trong tập tin nguồn thgian.cpp và thgian2.cpp,  Tương tự lớp Stack có thể được cài đặt dưới dạng mảng hoặc dưới dạng danh sách liên kết. Chương 2. Lớp 113 2.5 Các nguyên tắc xây dựng lớp  Khi ta có thể nghĩ đến “nó” như một khái niệm riêng rẽ, xây dựng lớp biểu diễn khái niệm đó. Ví dụ lớp SinhVien.  Khi ta nghĩ đến “nó” như một thực thể riêng rẽ, tạo đối tượng thuộc lớp. Ví dụ đối tượng Sinh viên “Nguyen Van A” (và các thuộc tính khác như mã số, năm sinh).  Lớp là biểu diễn cụ thể của một khái niệm, vì vậy lớp Chương 2. Lớp 114 luôn luôn là DANH TỪ.  Các thuộc tính của lớp là các thành phần dữ liệu, nên chúng luôn luôn là DANH TỪ.  Các hàm thành phần là các thao tác chỉ rõ hoạt động của lớp nên các hàm này là ĐỘNG TỪ. Các nguyên tắc xây dựng lớp // SAI class TamGiac // DUNG class TamGiac  Các thuộc tính dữ liệu phải vừa đủ để mô tả khái niệm, không dư, không thiếu.  Các thuộc tính có thể được suy diễn từ những thuộc tính khác thì dùng hàm thành phần để thực hiện tính toán. Chu vi, diện tích tam giác là thuộc tính suy diễn. Chương 2. Lớp 115 { Diem A,B,C; double ChuVi, DienTich; public: //... }; { Diem A,B,C; public: //... double ChuVi()const; double DienTich() const; }; Các nguyên tắc xây dựng lớp  Cá biệt có thể có một số thuộc tính suy diễn đòi hỏi nhiều tài nguyên hoặc thời gian để thực hiện tính toán, ta có thể khai báo là dữ liệu thành phần. Ví dụ tuổi trung bình của dân Việt Nam. class QuocGia { long DanSo; double DienTich; Chương 2. Lớp 116 double TuoiTrungBinh; //... public: double TinhTuoiTB() const; //... }; Các nguyên tắc xây dựng lớp  Chi tiết cài đặt, bao gồm dữ liệu và phần mã hoá các hàm thành phần có thể thay đổi uyển chuyển nhưng phần giao diện, nghĩa là phần khai báo các hàm thành phần cần phải cố định để không ảnh hưởng đến người sử dụng (xem phần 2.4). Tuy nhiên nên cố gắng cài đặt dữ liệu một cách tự nhiên theo đúng khái niệm. // NEN // KHONG NEN Chương 2. Lớp 117 class PhanSo { int tu, mau; public: //... }; class PhanSo { long tu_mau; public: //... }; Các nguyên tắc xây dựng lớp  Dữ liệu thành phần nên được kết hợp thay vì phân rã // NEN class TamGiac { Diem A,B,C; public: //... // KHONG NEN class TamGiac { double xA, yA, xB, yB, xC, yC; public: Chương 2. Lớp 118 }; class HinhTron { Diem Tam; double BanKinh; public: //... }; //... }; class HinhTron { double tx, ty, BanKinh; public: //... }; Các nguyên tắc xây dựng lớp  Trong mọi trường hợp, nên có phương thức thiết lập để khởi động đối tượng.  Nên có phương thức thiết lập có khả năng tự khởi động không cần tham số.  Nếu đối tượng có nhu cầu cấp phát tài nguyên thì phải có phương thức thiết lập, phương thức thiết lập bản sao để khởi động đối tượng bằng đối tượng cùng kiểu và có Chương 2. Lớp 119 phương thức huỷ bỏ để dọn dẹp. Ngoài ra còn phải có phép gán (chương tiếp theo).  Ngược lại, đối tượng đơn giản không cần tài nguyên riêng thì không cần phương thức thiết lập bản sao và cũng không cần phương thức huỷ bỏ. 2.6 Một số ví dụ về lớp  Lớp Diem biểu diễn khái niệm điểm trong mặt phẳng với hai thành phần toạ độ x và y. Khai báo lớp Diem được đặt trong tập tin diem.h và chi tiết cài đặt các hàm thành phần được đặt trong tập tin diem.cpp. Có thể xây dựng một số ứng dụng của lớp điểm như trong tập tin tdiem.cpp hoặc dongho.cpp với các ứng dụng tương ứng là tdiem.exe và dongho.exe (hoặc dongho2.exe, dongho3.exe). Chương 2. Lớp 120  Lớp DaGiac biểu diễn khái niệm đa giác trong mặt phẳng có các thao tác tịnh tiến, vị tự, quay, vẽ Phần khai báo được đặt trong tập tin dagiac.h, phần cài đặt trong tập tin dagiac.cpp và ứng dụng tdg.cpp cho phép tạo, vẽ, tịnh tiến và quay đa giác. Tập tin thực thi là tdg.exe

Các file đính kèm theo tài liệu này:

  • pdflthdt_ch02_0308.pdf
Tài liệu liên quan