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



