Bài giảng môn học Kỹ thuật lập trình
Tóm tắt Bài giảng môn học Kỹ thuật lập trình: ... OR True] 5. True Thứ tự ưu tiên giữa các kiểu toán tử khác nhau Khi một biểu thức có nhiều hơn một kiểu toán tử thì thứ tự ưu tiên phải được thiết lập giữa các kiểu toán tử với nhau. Bảng dưới đây cho biết thứ tự ưu tiên giữa các kiểu toán tử khác nhau. Thứ tự Kiểu toán tử 1 Số học ...rị được sinh ra từ một cấp số cộng. Một vòng lặp for cũng có thể được sử dụng để khởi tạo một mảng các ký tự như sau: Ví dụ 9.3: #include void main() { char alpha[26]; int i, j; for(i = 65, j = 0; i < 91; i++, j++) { alpha[j] = i; printf(“The character now assign...rỏ: int ary[10] = {1,2,3,4,5,6,7,8,9,10}; hoặc int ary[] = {1,2,3,4,5,6,7,8,9,10}; Ví dụ sau đây tạo một mảng một chiều và sắp xếp mảng theo thứ tự tăng dần. Chương trình sử dụng con trỏ và hàm malloc() để gán bộ nhớ. #include #include void main() { int *p, n, i, j, temp; pr...
urn true; return false; } void Set::AddElem (const int elem) { if (Member(elem)) return; if (card < maxCard) elems[card++] = elem; else cout << "Set overflow\n"; } void Set::RmvElem (const int elem) { for (register i = 0; i < card; ++i) if (elems[i] == elem) { for (; i < card-1; ++i) // dich cac phan tu sang trai elems[i] = elems[i+1]; --card; } } void Set::Copy (Set &set) { 342 for (register i = 0; i < card; ++i) set.elems[i] = elems[i]; set.card = card; } Bool Set::Equal (Set &set) { if (card != set.card) return false; for (register i = 0; i < card; ++i) if (!set.Member(elems[i])) return false; return true; } void Set::Intersect (Set &set, Set &res) { res.card = 0; for (register i = 0; i < card; ++i) if (set.Member(elems[i])) res.elems[res.card++] = elems[i]; } void Set::Union (Set &set, Set &res) { set.Copy(res); for (register i = 0; i < card; ++i) res.AddElem(elems[i]); } void Set::Print (void) { cout << "{"; for (int i = 0; i < card-1; ++i) 343 cout << elems[i] << ","; if (card > 0) // khong co dau , sau phan tu cuoi cung cout << elems[card-1]; cout << "}\n"; } Hàm main sau đây tạo ra ba tập đối tượng Set và thực thi một vài hàm thành viên của nó. int main (void) { Set s1, s2, s3; s1.EmptySet(); s2.EmptySet(); s3.EmptySet(); s1.AddElem(10); s1.AddElem(20); s1.AddElem(30); s1.AddElem(40); s2.AddElem(30); s2.AddElem(50); s2.AddElem(10); s2.AddElem(60); cout << "s1 = "; s1.Print(); cout << "s2 = "; s2.Print(); s2.RmvElem(50); cout << "s2 - {50} = "; s2.Print(); if (s1.Member(20)) cout << "20 is in s1\n"; s1.Intersect(s2,s3); cout << "s1 intsec s2 = "; s3.Print(); s1.Union(s2,s3); cout << "s1 union s2 = "; s3.Print(); if (!s1.Equal(s2)) cout s2\n"; return 0; } Khi chạy chương trình sẽ cho kết quả như sau: 344 s1 = {10,20,30,40} s2 = {30,50,10,60} s2 - {50} = {30,10,60} 20 is in s1 s1 intsec s2 = {10,30} s1 union s2 = {30,10,60,20,40} s1 s2 345 Bài 23. Hàm xây dựng (Constructor) và Hàm hủy (Destructor) 23.1. Hàm xây dựng (Constructor) Hoàn toàn có thể định nghĩa và khởi tạo các đối tượng của một lớp ở cùng một thời điểm. Điều này được hỗ trợ bởi các hàm đặc biệt gọi là hàm xây dựng (constructor). Một hàm xây dựng luôn có cùng tên với tên lớp của nó. Nó không bao giờ có một kiểu trả về rõ ràng. Ví dụ, class Point { int xVal, yVal; public: Point (int x,int y) {xVal = x; yVal = y;} // constructor void OffsetPt (int,int); }; là một định nghĩa có thể của lớp Point, trong đó SetPt đã được thay thế bởi một hàm xây dựng được định nghĩa nội tuyến. Bây giờ chúng ta có thể định nghĩa các đối tượng kiểu Point và khởi tạo chúng một lượt. Điều này quả thật là ép buộc đối với những lớp chứa các hàm xây dựng đòi hỏi các đối số: Point pt1 = Point(10,20); Point pt2; // trái luật Hàng thứ nhất có thể được đặc tả trong một hình thức ngắn gọn. Point pt1(10,20); Một lớp có thể có nhiều hơn một hàm xây dựng. Tuy nhiên, để tránh mơ hồ thì mỗi hàm xây dựng phải có một dấu hiệu duy nhất. Ví dụ, class Point { int xVal, yVal; public: Point (int x, int y) { xVal = x; yVal = y; } Point (float, float); // các tọa độ cực Point (void) { xVal = yVal = 0; } // gốc void OffsetPt (int, int); 346 }; Point::Point (float len, float angle) // các tọa độ cực { xVal = (int) (len * cos(angle)); yVal = (int) (len * sin(angle)); } có ba hàm xây dựng khác nhau. Một đối tượng có kiểu Point có thể được định nghĩa sử dụng bất kỳ hàm nào trong các hàm này: Point pt1(10,20); // tọa độ Đê-cát-tơ Point pt2(60.3,3.14); // tọa độ cực Point pt3; // gốc Lớp Set có thể được cải tiến bằng cách sử dụng một hàm xây dựng thay vì EmptySet: class Set { public: Set (void) { card = 0; } //... }; Điều này tạo thuận lợi cho các lập trình viên không cần phải nhớ gọi EmptySet nữa. Hàm xây dựng đảm bảo rằng mọi tập hợp là rỗng vào lúc ban đầu. Lớp Set có thể được cải tiến hơn nữa bằng cách cho phép người dùng điều khiển kích thước tối đa của tập hợp. Để làm điều này chúng ta định nghĩa elems như một con trỏ số nguyên hơn là mảng số nguyên. Hàm xây dựng sau đó có thể được cung cấp một đối số đặc tả kích thước tối đa mong muốn. Nghĩa là maxCard sẽ không còn là hằng được dùng cho tất cả các đối tượng Set nữa mà chính nó trở thành một thành viên dữ liệu: class Set { public: Set (const int size); //... private: 347 int *elems; // cac phan tu tap hop int maxCard; // so phan tu toi da int card; // so phan tu }; Hàm xây dựng dễ dàng cấp phát một mảng động với kích thước mong muốn và khởi tạo giá trị phù hợp cho maxCard và card: Set::Set (const int size) { elems = new int[size]; maxCard = size; card = 0; } Bây giờ có thể định nghĩa các tập hợp có các kích thước tối đa khác nhau: Set ages(10), heights(20), primes(100); Chúng ta cần lưu ý rằng một hàm xây dựng của đối tượng được ứng dụng khi đối tượng được tạo ra. Điều này phụ thuộc vào phạm vi của đối tượng. Ví dụ, một đối tượng toàn cục được tạo ra ngay khi sự thực thi chương trình bắt đầu; một đối tượng tự động được tạo ra khi phạm vi của nó được đăng ký; và một đối tượng động được tạo ra khi toán tử new được áp dụng tới nó. 23.2. Hàm hủy (Destructor) Như là một hàm xây dựng được dùng để khởi tạo một đối tượng khi nó được tạo ra, một hàm hủy được dùng để dọn dẹp một đối tượng ngay trước khi nó được thu hồi. Hàm hủy luôn luôn có cùng tên với chính tên lớp của nó nhưng được đi đầu với ký tự ~. Không giống các hàm xây dựng, mỗi lớp chỉ có nhiều nhất một hàm hủy. Hàm hủy không nhận bất kỳ đối số nào và không có một kiểu trả về rõ ràng. Thông thường các hàm hủy thường hữu ích và cần thiết cho các lớp chứa dữ liệu thành viên con trỏ. Các dữ liệu thành viên con trỏ trỏ tới các khối bộ nhớ được cấp phát từ lớp. Trong các trường hợp như thế thì việc giải phóng bộ nhớ đã được cấp phát cho các con trỏ thành viên là cực kỳ quan trọng trước khi đối tượng được thu hồi. Hàm hủy có thể làm công việc như thế. 348 Ví dụ, phiên bản sửa lại của lớp Set sử dụng một mảng được cấp phát động cho các thành viên elems. Vùng nhớ này nên được giải phóng bởi một hàm hủy: class Set { public: Set (const int size); ~Set (void) {delete elems;} // destructor //... private: int *elems; // cac phan tu tap hop int maxCard; // so phan tu toi da int card; // so phan tu cua tap hop }; Bây giờ hãy xem xét cái gì xảy ra khi một Set được định nghĩa và sử dụng trong hàm: void Foo (void) { Set s(10); //... } Khi hàm Foo được gọi, hàm xây dựng cho s được triệu tập, cấp phát lưu trữ cho s.elems và khởi tạo các thành viên dữ liệu của nó. Kế tiếp, phần còn lại của thân hàm Foo được thực thi. Cuối cùng, trước khi Foo trả về, hàm hủy cho cho s được triệu tập, xóa đi vùng lưu trữ bị chiếm bởi s.elems. Kể từ đây cho đến khi cấp phát lưu trữ được kể đến thì s ứng xử giống như là biến tự động của một kiểu có sẳn được tạo ra khi phạm vi của nó được biết đến và được hủy đi khi phạm vi của nó được rời khỏi. Nói chung, hàm xây dựng của đối tượng được áp dụng trước khi đối tượng được thu hồi. Điều này phụ thuộc vào phạm vi của đối tượng. Ví dụ, một đối tượng toàn cục được thu hồi khi sự thực hiện của chương trình hoàn tất; một đối tượng tự động được thu hồi khi toán tử delete được áp dụng tới nó. 349 Bài 24. Kỹ thuật lập trình Thừa kế Trong thực tế hầu hết các lớp có thể kế thừa từ các lớp có trước mà không cần định nghĩa lại mới hoàn toàn. Ví dụ xem xét một lớp được đặt tên là RecFile đại diện cho một tập tin gồm nhiều mẫu tin và một lớp khác được đặt tên là SortedRecFile đại diện cho một tập tin gồm nhiều mẫu tin được sắp xếp. Hai lớp này có thể có nhiều điểm chung. Ví dụ, chúng có thể có các thành viên hàm giống nhau như là Insert, Delete, và Find, cũng như là thành viên dữ liệu giống nhau. SortedRecFile là một phiên bản đặc biệt của RecFile với thuộc tính các mẫu tin của nó được tổ chức theo thứ tự được thêm vào. Vì thế hầu hết các hàm thành viên trong cả hai lớp là giống nhau trong khi một vài hàm mà phụ thuộc vào yếu tố tập tin được sắp xếp thì có thể khác nhau. Ví dụ, hàm Find có thể là khác trong lớp SortedRecFile bởi vì nó có thể nhờ vào yếu tố thuận lợi là tập tin được sắp để thực hiện tìm kiếm nhị phân thay vì tìm tuyến tính như hàm Find của lớp RecFile. Với các thuộc tính được chia sẻ của hai lớp này thì việc định nghĩa chúng một cách độc lập là rất dài dòng. Rõ ràng điều này dẫn tới việc phải sao chép lại mã đáng kể. Mã không chỉ mất thời gian lâu hơn để viết nó mà còn khó có thể được bảo trì hơn: một thay đổi tới bất kỳ thuộc tính chia sẻ nào có thể phải được sửa đổi tới cả hai lớp. Lập trình hướng đối tượng cung cấp một kỹ thuật thuận lợi gọi là thừa kế để giải quyết vấn đề này. Với thừa kế thì một lớp có thể thừa kế những thuộc tính của một lớp đã có trước. Chúng ta có thể sử dụng thừa kế để định nghĩa những thay đổi của một lớp mà không cần định nghĩa lại lớp mới từ đầu. Các thuộc tính chia sẻ chỉ được định nghĩa một lần và được sử dụng lại khi cần. Trong C++ thừa kế được hỗ trợ bởi các lớp dẫn xuất (derived class). Lớp dẫn xuất thì giống như lớp gốc ngoại trừ định nghĩa của nó dựa trên một hay nhiều lớp có sẵn được gọi là lớp cơ sở (base class). Lớp dẫn xuất có thể chia sẻ những thuộc tính đã chọn (các thành viên hàm hay các thành viên dữ liệu) của các lớp cơ sở của nó nhưng không làm chuyển đổi định nghĩa của bất kỳ lớp cơ sở nào. Lớp dẫn xuất chính nó có thể là lớp cơ sở của một lớp dẫn xuất khác. Quan hệ thừa kế giữa các lớp của một chương trình được gọi là quan hệ cấp bậc lớp (class hierarchy). 350 Lớp dẫn xuất cũng được gọi là lớp con (subclass) bởi vì nó trở thành cấp thấp hơn của lớp cơ sở trong quan hệ cấp bậc. Tương tự một lớp cơ sở có thể được gọi là lớp cha (superclass) bởi vì từ nó có nhiều lớp khác có thể được dẫn xuất. 24.1. Ví dụ minh họa Chúng ta sẽ định nghĩa hai lớp nhằm mục đích minh họa một số khái niệm lập trình trong các phần sau của chương này. Hai lớp được định nghĩa trong Danh sách 22.1 và hỗ trợ việc tạo ra một thư mục các đối tác cá nhân. 1 #include 2 #include 3 class Contact { 4 public: 5 Contact(const char *name, const char *address, const char *tel); 6 ~Contact (void); 7 const char* Name (void) const {return name;} 8 const char* Address(void) const {return address;} 9 const char* Tel(void) const {return tel;} 10 friend ostream& operator << (ostream&, Contact&); 11 private: 12 char *name; // ten doi tac 13 char *address; // dia chi doi tac 14 char *tel; // so dien thoai 15 }; 16//------------------------------------------------------------------- 17 class ContactDir { 18 public: 19 ContactDir(const int maxSize); 10 ~ContactDir(void); 21 void Insert(const Contact&); 22 void Delete(const char *name); 23 Contact* Find(const char *name); 24 friend ostream& operator <<(ostream&, ContactDir&); 351 25 private: 26 int Lookup(const char *name); 27 Contact **contacts; // danh sach cac doi tac int dirSize; // kich thuoc thu muc hien tai 28 int maxSize; // kich thuoc thu muc toi da 29 }; Chú giải 3 Lớp Contact lưu giữ các chi tiết của một đối tác (nghĩa là, tên, địa chỉ, và số điện thoại). 18 Lớp ContactDir cho phép chúng ta thêm, xóa, và tìm kiếm một danh sách các đối tác. 22 Hàm Insert xen một đối tác mới vào thư mục. Điều này sẽ viết chồng lên một đối tác tồn tại (nếu có) với tên giống nhau. 23 Hàm Delete xóa một đối tác (nếu có) mà tên của đối tác trùng với tên đã cho. 24 Hàm Find trả về một con trỏ tới một đối tác (nếu có) mà tên của đối tác khớp với tên đã cho. 27 Hàm Lookup trả về chỉ số vị trí của một đối tác mà tên của đối tác khớp với tên đã cho. Nếu không tồn tại thì sau đó hàm Lookup trả về chỉ số của vị trí mà tại đó mà một đầu vào như thế sẽ được thêm vào. Hàm Lookup được định nghĩa như là riêng (private) bởi vì nó là một hàm phụ được sử dụng bởi các hàm Insert, Delete, và Find. Cài đặt của hàm thành viên và hàm bạn như sau: Contact::Contact (const char *name, const char *address, const char *tel) { Contact ::name = new char[strlen(name) + 1]; Contact::address = new char[strlen(address) + 1]; Contact :: tel = new char[strlen(tel) + 1]; strcpy(Contact::name, name); strcpy(Contact::address, address); strcpy(Contact: :tel, tel); } 352 Contact ::~Contact (void) { delete name; delete address; delete tel ; } ostream& operator << (ostream &os, Contact &c) { os << "(" << c.name << " , " << c.address << " , " << c.tel << ")"; return os; } ContactDir::ContactDir (const int max) { typedef Contact *ContactPtr; dirSize = 0; maxSize = max; contacts = new ContactPtr[maxSize]; }; ContactDir::~ContactDir (void) { for (register i = 0; i < dirSize; ++i) delete contacts[i]; delete [] contacts; } void ContactDir::Insert (const Contact& c) { if (dirSize < maxSize) { int idx = Lookup(c.Name()); if (idx > 0 && strcmp(c.Name(), contacts[idx]->Name()) == 0) { 353 delete contacts[idx]; } else { for (register i = dirSize; i > idx; --i) // dich phai contacts[i] = contacts[i-1]; ++dirSize; } contacts[idx] = new Contact(c.Name(), c.Address(), c.Tel()); } } void ContactDir::Delete (const char *name) { int idx = Lookup(name); if (idx < dirSize) { delete contacts[idx]; --dirSize; for (register i = idx; i < dirSize; ++i) // dich trai contacts[i] = contacts[i+1]; } } Contact *ContactDir: :Find (const char *name) { int idx = Lookup(name); return (idx < dirSize && strcmp(contacts[idx]->Name(), name) == 0) ? contacts[idx] : 0; } int ContactDir::Lookup (const char *name) { for (register i = 0; i < dirSize; ++i) if (strcmp(contacts[i]->Name(), name) == 0) 354 return i; return dirSize; } ostream &operator << (ostream &os, ContactDir &c) { for (register i = 0; i < c.dirSize; ++i) os << *(c.contacts[i]) << '\n' ; return os; } Hàm main sau thực thi lớp ContactDir bằng cách tạo ra một thư mục nhỏ và gọi các hàm thành viên: int main (void) { ContactDir dir(10); dir.Insert(Contact("Mary", "11 South Rd", "282 1324")); dir.Insert(Contact("Peter", "9 Port Rd", "678 9862")); dir.Insert(Contact("Jane", "321 Yara Ln", "982 6252")); dir.Insert(Contact("Jack", "42 Wayne St", "663 2989")); dir.Insert(Contact("Fred", "2 High St", "458 2324")); cout << dir; cout << "Find Jane: " << *dir.Find("Jane") << '\n' ; dir.Delete("Jack"); cout << "Deleted Jack\n"; cout << dir; return 0; }; Khi chạy nó sẽ cho kết quả sau: (Mary , 11 South Rd , 282 1324) (Peter , 9 Port Rd , 678 9862) (Jane , 321 Yara Ln , 982 6252) (Jack , 42 Wayne St , 663 2989) 355 (Fred , 2 High St , 458 2324) Find Jane: (Jane , 321 Yara Ln , 982 6252) Deleted Jack (Mary , 11 South Rd , 282 1324) (Peter , 9 Port Rd , 678 9862) (Jane , 321 Yara Ln , 982 6252) (Fred , 2 High St , 458 2324) 24.2. Lớp dẫn xuất đơn giản Chúng ta muốn định nghĩa một lớp gọi là SmartDir ứng xử giống như là lớp ContactDir và theo dõi tên của đối tác mới vừa được tìm kiếm gần nhất. Lớp SmartDir được định nghĩa tốt nhất như là một dẫn xuất của lớp ContactDir như được minh họa bởi Danh sách 22.2. 1 class SmartDir : public ContactDir { 2 public: 3 SmartDir(const int max) : ContactDir(max) {recent = 0;} 4 Contact* Recent (void); 5 Contact* Find (const char *name); 6 private: 7 char * recent; // ten duoc tim gan nhat 8 }; Chú giải 1 Phần đầu của lớp dẫn xuất chèn vào các lớp cơ sở mà nó thừa kế. Một dấu hai chấm (:) phân biệt giữa hai phần. Ở đây, lớp ContactDir được đặc tả là lớp cơ sở mà lớp SmartDir được dẫn xuất. Từ khóa public phía trước lớp ContactDir chỉ định rằng lớp ContactDir được sử dụng như một lớp cơ sở chung. 3 Lớp SmartDir có hàm xây dựng của nó, hàm xây dựng này triệu gọi hàm xây dựng của lớp cơ sở trong danh sách khởi tạo thành viên của nó. 4 Hàm Recent trả về một con trỏ tới đối tác được tìm kiếm sau cùng (hoặc 0 nếu không có). 5 Hàm Find được định nghĩa lại sao cho nó có thể ghi nhận đầu vào được tìm kiếm sau cùng. 356 7 Con trỏ recent được đặt tới tên của đầu vào đã được tìm sau cùng. Các hàm thành viên được định nghĩa như sau: Contact* SmartDir::Recent (void) { return recent == 0 ? 0 : ContactDir::Find(recent); } Contact* SmartDir::Find (const char *name) { Contact *c = ContactDir::Find(name); if (c != 0) recent = (char*) c->Name(); return c; } Bởi vì lớp ContactDir là một lớp cơ sở chung của lớp SmartDir nên tất cả thành viên chung của lớp ContactDir trở thành các thành viên chung của lớp martDir. Điều này nghĩa là chúng ta có thể triệu gọi một hàm thành viên như là Insert trên một đối tượng SmartDir và đây là một lời gọi tới ContactDir::Insert. Tương tự, tất cả các thành viên riêng của lớp ContactDir trở thành các thành viên riêng của lớp SmartDir. Phù hợp với các nguyên lý ẩn thông tin, các thành viên riêng của lớp ContactDir sẽ không thể được truy xuất bởi SmartDir. Vì thế, lớp SmartDir sẽ không thể truy xuất tới bất kỳ thành viên dữ liệu nào của lớp ContactDir cũng như là hàm thành viên riêng Lookup. Lớp SmartDir định nghĩa lại hàm thành viên Find. Điều này không nên nhầm lẫn với tái định nghĩa. Có hai định nghĩa phân biệt của hàm này: ContactDir::Find và SmartDir::Find (cả hai định nghĩa có cùng dấu hiệu dẫu cho chúng có thể có các dấu hiệu khác nhau nếu được yêu cầu). Triệu gọi hàm Find trên đối tượng SmartDir thứ hai sẽ được gọi. Như được minh họa bởi định nghĩa của hàm Find trong lớp SmartDir,hàm thứ nhất có thể vẫn còn được triệu gọi bằng cách sử dụng tên đầy đủ của nó. Đoạn mã sau minh họa lớp SmartDir cư xử như là lớp ContactDir nhưng cũng theo dõi đầu vào được tìm kiếm được gần nhất: SmartDir dir(10); 357 dir.Insert(Contact("Mary", "11 South Rd", "282 1324")); dir.Insert(Contact("Peter", "9 Port Rd", "678 9862")); dir.Insert(Contact("Jane", "321 Yara Ln", "982 6252")); dir.Insert(Contact("Fred", "2 High St", "458 2324")); dir.Find("Jane"); dir.Find("Peter"); cout << "Recent: " << *dir.Recent() << '\n'; Điều này sẽ cho ra kết quả sau: Recent: (Peter , 9 Port Rd , 678 9862) Một đối tượng kiểu SmartDir chứa đựng tất cả dữ liệu thành viên của ContactDir cũng như là bất kỳ dữ liệu thành viên thêm vào được giới thiệu bởi SmartDir. Hình 22.1 minh họa việc tạo ra một đối tượng ContactDir và một đối tượng SmartDir. Hình 22.1. Các đối tượng lớp cơ sở và lớp dẫn xuất. 24.3. Ký hiệu thứ bậc lớp Thứ bậc lớp thường được minh họa bằng cách sử dụng ký hiệu đồ họa đơn giản. Hình 9.2 minh họa ký hiệu của ngôn ngữ UML mà chúng ta sẽ đang sử dụng trong giáo trình này. Mỗi lớp được biểu diễn bằng một hộp được gán nhãn là tên lớp. Thừa kế giữa hai lớp được minh họa bằng một mũi tên có hướng vẽ từ lớp dẫn xuất đến lớp cơ sở. Một đường thẳng với hình kim cương ở một đầu miêu tả composition (tạm dịch là quan hệ bộ phận, nghĩa là một đối tượng của lớp được bao gồm một hay nhiều đối tượng của lớp khác). Số đối tượng chứa bởi đối tượng khác được miêu tả bởi một nhãn (ví dụ, n). 358 Hình trên được thông dịch như sau. Contact, ContactDir, và SmartDir là các lớp. Lớp ContactDir gồm có không hay nhiều đối tượng Contact. Lớp SmartDir được dẫn xuất từ lớp ContactDir.
File đính kèm:
- bai_giang_mon_hoc_ky_thuat_lap_trinh.pdf