Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (955.76 KB, 118 trang )
<span class='text_page_counter'>(1)</span><div class='page_container' data-page=1>
Phân tích thiết kế và lập trình theo hướng đối tượng tuy sinh sau đẻ
muộn nhưng đã chứng tỏ được những ưu điểm vượt trội so với cách
tiếp cận cổ điển.
Trong lãnh vực phân tích và thiết kế hệ thống, hướng tiếp cận mới
mẻ này đã thu hút nhiều nhà nghiên cứu tên tuổi. Nhiều kiểu mẫu,
phương pháp luận, mơ hình phân tích đã được đưa ra với những
mức độ thành công khác nhau.
Ta sẽ nghiên cứu phương hướng phân tích theo quan điểm của
Ta sẽ nghiên cứu phương hướng phân tích theo quan điểm của
Phân tích dựa trên cơ sở mơ hình hóa các đối tượng
trong thế giới thực.
Dùng mơ hình để xây dựng một thiết kế khơng phụ
thuộc ngôn ngữ được tổ chức xung quanh các đối tượng.
So với cách tổ chức cổ điển, mô hình hố và thiết kế
Các khái niệm trong thế giới thực được mơ hình hố
bằng các ký hiệu đồ hoạ mơ tả các đối tượng của chúng
(cấu trúc dữ liệu và hành vi) độc lập với ngôn ngữ.
Các khái niệm và ký hiệu này có thể được dùng thống
nhất suốt quá trình phát triển hệ thống từ phân tích,
thiết kế đến cài đặt mà khơng cần thay đổi qua các giai
đoạn như một số phương pháp luận khác.
đoạn như một số phương pháp luận khác.
Không quan tâm đến chi tiết cài đặt cho đến giai đoạn
Các khái niệm liên quan đến máy tính chỉ được đưa ra
ở bước mã hóa sau cùng, nhờ đó giữ được sự uyển
Mô hình hóa và thiết kế theo hướng đối tượng là một lối
suy nghĩ mới về vấn đề cần giải quyết dùng các mơ
hình được tổ chức xung quanh các khái niệm trong thế
giới thực.
Trong một hệ thống thông tin hướng đối tượng, mọi thứ,
hay hầu như mọi thứ, được quan điểm như các đối
tượng.
hay hầu như mọi thứ, được quan điểm như các đối
tượng.
Mỗi đối tượng là sự kết hợp của cả hai thành phần đặc
Phương pháp luận theo quan điểm của J.Rumbaugh bao
gồm xây dựng một mơ hình của hệ thống trong lãnh vực
ứng dụng và thêm chi tiết cài đặt trong quá trình thiết
Các ký hiệu đồ họa được sử dụng để biểu diễn các khái
niệm hướng đối tượng.
niệm hướng đối tượng.
Cách tiếp cận này được gọi là kỹ thuật thiết kế bằng mô
Kỹ thuật mơ hình hố OMT bao gồm các bước:
Phân tích Thiết kế
hệ thống
Thiết kế
Trừu tượng hố :
• Nhấn mạnh vào các khía cạnh cốt yếu vốn có của một thực thể
và bỏ qua những tính chất riêng biệt.
• Sử dụng trừu tượng hố trong phân tích có nghĩa là làm việc với
các khái niệm trong lãnh vực ứng dụng và bỏ qua chi tiết cài
đặt.
• Hầu hết các ngơn ngữ lập trình hiện đại đều hổ trợ trừu tượng
• Hầu hết các ngơn ngữ lập trình hiện đại đều hổ trợ trừu tượng
hoá. Nhưng sự trừu tượng hóa được tận dụng trong tiếp cận đối
tượng với tính kế thừa (inheritance) và tính đa dạng
Tính đóng gói :
• Tách rời các khía cạnh giao diện với bên ngồi của đối tượng
với chi tiết cài đặt bên trong.
• Tính đóng gói ngăn chặn khả năng một chương trình trở nên
quá phụ thuộc lẫn nhau dẫn tới hậu quả một sự thay đổi nhỏ
có thể ảnh hưởng lớn đến tồn bộ hệ thống.
• Trong tiếp cận O.O. khả năng kết hợp dữ liệu và hành vi trong
• Trong tiếp cận O.O. khả năng kết hợp dữ liệu và hành vi trong
Kết hợp dữ liệu và hành vi:
• Trong cách tiếp cận thủ tục cổ điển, hệ thống được xây dựng
trên hai sơ đồ phân cấp chằng chịt: sơ đồ phân cấp dữ liệu và
sơ đồ phân cấp thủ tục, trong đó sự liên hệ giữa một loại dữ liệu
và các thủ tục xử lý dữ liệu rất mờ nhạt, dẫn đến khó khăn
trong việc sửa chữa, nâng cấp trong tương lai.
• Cách tiếp cận O.O. loại bỏ những nhược điểm kể trên bằng
• Cách tiếp cận O.O. loại bỏ những nhược điểm kể trên bằng
Sơ đồ phân cấp dữ liệu
Sơ đồ phân cấp lớp
Sơ đồ phân cấp dữ liệu
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ó
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.
tính của Người.
Một thao tác (operation) là một hàm hay một phép biến
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 đó.
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
Điểm TB
Đi học
Phân loại
9.2
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
Một đối tượng cụ thể thuộc một lớp được gọi là một thể
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.
đặt lớp.
Lớp trong C++ được cài đặt sử dụng từ khoá struct và
1. Cách tiếp cận cổ điển:
<b>// Stack1.cpp : </b>
<b>//Dung cau truc va ham toan cuc</b>
#include <iostream.h>
typedef int bool;
typedef int Item;
const bool false = 0, true = 1;
const bool false = 0, true = 1;
struct Stack
{
Item *st, *top;
int size;
void StackInit(Stack *ps, int sz)
{
ps->st = ps->top = new Item[ps->size=sz];
}
void StackCleanUp(Stack *ps)
{
delete [] ps->st;
delete [] ps->st;
}
bool StackFull(Stack *ps)
{
bool StackEmpty(Stack *ps)
{
return (ps->top <= ps->st);
}
bool StackPush(Stack *ps, Item x)
{
{
if (StackFull(ps)) return false;
*ps->top++ = x;
bool StackPop(Stack *ps, Item *px)
{
if (StackEmpty(ps)) return false;
*px = *--ps->top;
void XuatHe16(long n)
{
static char hTab[] = “0123456789ABCDEF”;
Stack s;
StackInit(&s,8);
do {
StackPush(&s, n%16);
StackPush(&s, n%16);
n /= 16;
} while(n);
while(StackPop(&s,&x))
cout << hTab[x];
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
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
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
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];}
Item[size=sz];}
void CleanUp() {if (st) delete [] st;}
bool Full() const {return (top - st >=
size);}
bool Empty() const {return (top <= st);}
bool Stack::Push(Item x)
{
if (Full()) return false;
*top++ = x;
return true;
}
bool Stack::Pop(Item *px)
{
if (Empty()) return false;
*px = *--top;
void XuatHe16(long n)
{
static char hTab[] = “0123456789ABCDEF”;
Stack s;
s.Init(8);
do {
s.Push(n%16);
n /= 16;
} while(n);
while(s.Pop(&x))
cout << hTab[x];
s.CleanUp();
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
cổ điển. Vì vậy việc lập trình gọn hơn. Tư tưởng thể hiện
ở đâ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
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 ngồ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 tốn dấu chấm (.) hoặc
dấu mũi tên (->).
dấu mũi tên (->).
Stack s, *ps = &s;
s.Init(10);
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++)
for (int i = 0; i < 20; i++)
s.Push(i);
Vì vậy, ta khơng có sự an tồ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á
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 ngồi khơng được phép
truy xuất. Do đó có sự an tồn dữ liệu.
class Stack
{
Item *st, *top;
int size;
int size;
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);}
Phát biểu:
s.size = 100; // Bao sai
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ó.
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.
// 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);}
Stack(int sz = 20) {Init(sz);}
~Stack() {delete [] st;}
bool Full() const {return (top - st >= size);}
bool Empty() const {return (top <= st);}
bool Push(Item x);
// Stack.cpp
#include "stack.h"
bool Stack::Push(Item x)
{
if (Full()) return false;
*top++ = x;
return true;
}
}
bool Stack::Pop(Item *px)
{
// he16.cpp
#include "stack.h"
void XuatHe16(long n)
{
static char hTab[] = “0123456789ABCDEF”;
Stack s(8); int x;
do {
do {
s.Push(n%16);
n /= 16;
} while(n);
while(s.Pop(&x))
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
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.
địa chỉ this này.
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à 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
tạo ra. Người ta thường lợi dụng đặc tính trên để khởi
độ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
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
bởi tên lớp để phân biệt nó với các hàm thành phần
khác.
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);}
Stack(int sz = 20) {Init(sz);}
~Stack() {CleanUp();}
bool Full() const {return (top - st >= size);}
bool Empty() const {return (top <= st);}
bool Push(Item x);
Ngun 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
Ta làm một hàm trở thành hàm bạn của lớp bằng cách
đư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
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ừ khố 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
inline char *strdup(const char *s)
{
return strcpy(new char[strlen(s) + 1], s);
}
class string
{
char *p;
public:
string(char *s = "") {p = strdup(s);}
~string() {delete [] p;}
string(const string &s2) {p = strdup(s2.p);}
void Output() <b>const {cout << p;}</b>
void main()
{
const string Truong("DH BC TDT");
string s("ABCdef");
s.Output();
s.ToLower();
s.Output();
s.Output();
Truong.Output();
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 tồn cục vì
nó có liên quan mật thiết với lớp.
nó có liên quan mật thiết với lớp.
Ta cịn dùng hàm thành phần tĩnh để tạo đối tượng có
const bool false = 0, true = 1;
class CDate
{
static int dayTab[][13];
int day, month, year;
public:
public:
static bool LeapYear(int y) {return y%400
== 0 || y%4==0 && y%100 != 0;}
static int DayOfMonth(int m, int y);
{
{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];
bool betw(int x, int a, int b)
{
return betw(m,1,12) &&
betw(d,1,DayOfMonth(m,y));
}
void CDate::Input()
{
int d,m,y;
int d,m,y;
cin >> d >> m >> y;
while (!ValidDate(d,m,y))
{
cout << "Please enter a valid date: ";
cin >> d >> m >> y;
• 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ỷ
• Thời gian từ khi đối tượng được tạo ra đến khi nó bị huỷ
đ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:
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
free stored
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 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 hồn tất
khối chứa định nghĩa đó.
khối chứa định nghĩa đó.
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
#include <string.h>
char *strdup(const char *s)
{
return strcpy(new char[strlen(s) + 1], s);
}
class string
class string
{
char *p;
public:
string(char *s = "") {p = strdup(s);}
void main()
{
string a("Nguyen Van A");
string b = a; // String b(a)
a.Output(); cout << "\n";
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
Đố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;
String(char *s = "") {p = strdup(s);}
~String() {cout << "delete "<< (void *)p <<
"\n"; delete [] p;}
bool String::Compare(String s) const
{
return strcmp(p,s.p);
}
void main()
{
String a("Nguyen Van A");
String b("Le Van Beo");
int c = a.Compare(b);
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
delete 0x0f34
a > b
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;}
delete [] p;}
void Output() const {cout << p;}
bool Compare(String s) const;
String String::UpCase() const
String r = *this;
strupr(r.p);
return r;
}
void main()
{
{
clrscr();
String a("Nguyen Van A");
cout << "a = "; a.Output(); cout << "\n";
String A;
A = a.UpCase();
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
Null pointer assignment
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:
void main()
{
clrscr();
String a("Nguyen Van A");
cout << "a = "; a.Output(); cout << "\n";
cout << "a = "; a.Output(); cout << "\n";
cout << "A = "; a.Output(); cout << "\n";
}
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
delete 0x0d32
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 sao chép.
Phương thức thiết lập sao chép là phương thức thiết lập
Phương thức thiết lập sao chép 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 sao chép 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 sốt sử huỷ bỏ đối
class String
{
char *p;
public:
String(char *s = "") {p = strdup(s);}
String(const String &s) {p =
strdup(s.p);}
strdup(s.p);}
~String() {cout << "delete "<< (void *)p
<< "\n"; delete [] p;}
void Output() const {cout << p;}
bool Compare(String s) const;
bool String::Compare(String s) const
{
return strcmp(p,s.p);
}
String String::UpCase() const
{
String r = *this;
String r = *this;
strupr(r.p);
{
clrscr();
String a("Nguyen Van A");
String b("Le Van Beo");
String aa = a;
int c = a.Compare(b);
cout << (c > 0 ? "a > b" : c == 0 ? "a =
b" : "a < b") << "\n";
cout << "a = "; a.Output(); cout << "\n";
String A = a.UpCase();
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
A = NGUYEN VAN A
delete 0x0d96
delete 0x0d72
delete 0x0d62
delete 0x0d50
Mỗi đối tượng đều có tài ngun riêng nên khơng xảy
Tham số của phương thức thiết lập sao chép bắt buộc là
tham chieáu.
Phương thức thiết lập sao chép 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 sốt.
Lưu ý:
Nếu đối tượng khơng có tài ngun riêng thì khơng cần
Nếu đối tượng khơng có tài ngun riêng thì khơng cần
phương thức thiết lập sao chép.
Khi truyền tham số là đối tượng thuộc lớp có phương
Dùng phương thức thiết lập sao chép 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
class StringRep
{
friend class String;
char *p;
int n;
StringRep(const char *s) {p = strdup(s); n = 1;}
~StringRep() {cout << "delete ” << (void *)p <<
~StringRep() {cout << "delete ” << (void *)p <<
class String
{
StringRep *rep;
public:
String(const char *s = "") {rep = new
StringRep(s);}
String(const String &s) {rep = s.rep;
rep->n++;}
rep->n++;}
~String();
void Output() const {cout << rep->p;}
bool Compare(String s) const;
String UpCase() const;
void ToUpper();
String::~String()
{
if (--rep->n <= 0)
}
bool String::Compare(String s) const
{
return strcmp(rep->p,s.rep->p);
return strcmp(rep->p,s.rep->p);
}
String String::UpCase() const
{
{
clrscr();
String a("Nguyen Van A");
String b("Le Van Beo");
String aa = a;
int c = a.Compare(b);
cout << (c > 0 ? "a > b" : c == 0 ? "a = b" : "a
cout << (c > 0 ? "a > b" : c == 0 ? "a = b" : "a
cout << "a = "; a.Output(); cout << "\n";
String A = a.UpCase();
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 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.
delete 0x0d84
a = Nguyen Van A
A = NGUYEN VAN A
delete 0x0d96
delete 0x0d72
delete 0x0d62
delete 0x0d50
A = NGUYEN VAN A
delete 0x0d8a
Đố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ừ khố static (tồ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
Đối tượng tĩnh địa phương được tạo ra khi chương trì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 tồ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
Xét đoạn chương trình sau:
#include <iostream.h>
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:
Hãy sửa lại đoạn chương trình trên để có xuất liệu:
Entering a C++ program saying...
Hello, world.
And then exitting…
Đoạn chương trình được sửa lại như sau:
#include <iostream.h>
void main()
{
cout << "Hello, world.\n";
}
class Dummy
class Dummy
{
public:
Dummy() {cout << "Entering a C++ program
saying...\n";}
~Dummy() {cout << "And then exitting...";}
};
Đố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ố
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
class Diem
{
double x,y;
public:
Diem(double xx, double yy) {x = xx; y = yy;}
// ...
};
class TamGiac
{
{
Diem A,B,C;
public:
void Ve() const;
// ...
};
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";
//...
};
};
class SinhVien
{
String MaSo;
String HoTen;
int NamSinh;
public:
};
{
double x,y;
public:
Diem(double xx, double yy {x = xx; y = yy;}
// ...
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;
void Ve() const;
// ...
};
{
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;
"\n"; delete [] p;
//...
{
String MaSo;
String HoTen;
int NamSinh;
public:
SinhVien(char *ht, char *ms, int ns):HoTen(ht),
MaSo(ms){NamSinh = ns;}
MaSo(ms){NamSinh = ns;}
//...
};
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){}
// ...
};
};
class SinhVien
{
String MaSo, HoTen;
int NamSinh;
public:
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:
public:
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)
{
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ố.
là có thể được thiết lập khơng cần tham số.
Đố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.
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
class String
{
char *p;
public:
String(char *s) {p = strdup(s);}
String(const String &s) {p = strdup(s.p);}
~String() {cout << "delete "<< (void *)p <<
class SinhVien
{
String MaSo;
String HoTen;
int NamSinh;
public:
SinhVien(char *ht, char *ms, int ns):HoTen(ht),
MaSo(ms), NamSinh(ns){}
MaSo(ms), NamSinh(ns){}
//...
};
String as[3]; // Bao sai
Diem ad[5]; // Bao sai
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
{
char *p;
public:
String(char *s = “”) {p = strdup(s);}
class SinhVien
{
String MaSo;
String HoTen;
int NamSinh;
public:
SinhVien(char *ht = “Nguyen Van A”, char *ms =
“19920014”, int ns =
“19920014”, int ns =
1982):HoTen(ht), MaSo(ms),
NamSinh(ns){}
//...
};
class Diem
{
double x,y;
public:
Diem(double xx, double yy):x(xx), y(yy){}
Diem():x(0), y(0){}
// ...
};
};
class String
{
char *p;
public:
String(char *s) {p = strdup(s);}
String() {p = strdup(“”);}
class SinhVien
{
String MaSo;
String HoTen;
int NamSinh;
public:
SinhVien(char *ht, char *ms, int
SinhVien(char *ht, char *ms, int
ns):HoTen(ht), MaSo(ms),
NamSinh(ns){}
SinhVien():HoTen(“Nguyen Van A”),
MaSo(“19920014”), NamSinh(1982){}
//...
Đố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 để
Dùng new có thể cấp một đối tượng và dùng delete để
huỷ một đối tượng.
Dùng new và delete cũng có thể cấp nhiều đối tượng và
class String
{
char *p;
public:
String(char *s) {p = strdup(s);}
String(const String &s) {p = strdup(s.p);}
~String() {delete [] p;}
//...
};
};
class Diem
{
double x,y;
public:
int *pi = new int;
int *pj = new int(15);
Diem *pd = new Diem(20,40);
String *pa = new String("Nguyen Van A");
//...
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:
Thơng báo lỗi cho đoạn chương trình trên như sau:
Cannot find default constructor to initialize array element of type
'Diem'
Cannot find default constructor to initialize array element of type
String’
class String
{
char *p;
public:
String(char *s = "Alibaba") {p = strdup(s);}
String(const String &s) {p = strdup(s.p);}
~String() {delete [] p;}
//...
};
class Diem
{
double x,y;
Diem(double xx, double yy):x(xx),y(yy){};
Diem():x(0),y(0){};
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
// 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
delete và có thêm dấu [] ở trước.
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ã hố 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ể
có thể thay đổi tổ chức dữ liệu của lớp, cũng như có thể
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
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);}
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);
void Set(int g, int p, int gy);
int LayGio() const {return tsgiay / 3600;}
int LayPhut() const {return (tsgiay%3600)/60;}
int LayGiay() const {return tsgiay % 60;}
void Nhap();
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
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
Lớp là biểu diễn cụ thể của một khái niệm, vì vậy lớp
ln ln 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Ừ.
// SAI
class TamGiac
{
// DUNG
class TamGiac
{
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 tốn. Chu
vi, diện tích tam giác là thuộc tính suy diễn.
{
Diem A,B,C;
Cá biệt có thể có một số thuộc tính suy diễn địi hỏi
nhiều tài ngun hoặc thời gian để thực hiện tính tố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;
double DienTich;
double TuoiTrungBinh;
//...
public:
double TinhTuoiTB() const;
//...
Chi tiết cài đặt, bao gồm dữ liệu và phần mã hố 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
// NEN
class PhanSo
{
int tu, mau;
public:
// KHONG NEN
class PhanSo
{
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,
public:
//...
};
class HinhTron
{
Diem Tam;
double BanKinh;
public:
xB, yB, xC,
yC;
public:
//...
};
class HinhTron
{
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).
Dữ liệu thành phần nên được kết hợp thay vì phân rã
// NEN // KHONG NEN
// NEN
class TamGiac
{
Diem A,B,C;
public:
//...
// KHONG NEN
class TamGiac
{
double xA, yA,
Trong mọi trường hợp, cầ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 ngun thì phải
có phương thức thiết lập, phương thức thiết lập sao chép
để khởi động đối tượng bằng đối tượng cùng kiểu và có
để khởi động đối tượng bằng đối tượng cùng kiểu và có
phương thức huỷ bỏ để dọn dẹp. Ngồ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