Lập Trình Hướng Đối Tượng Lập Trình Hướng Đối Tượng Lập Trình Hướng Đối Tượng Lập Trình Hướng Đối Tượng
Ôn tập tốt nghiệp
Chương 1 Chương 1
Đối tượng và lớp Đối tượng và lớp
Giới thiệu Giới thiệu
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
Rumbaugh: Mô hình hoá và thiết kế theo hướng đối tượng.
Phương pháp phân tích bằng mô hình Phương pháp phân tích bằng mô hình
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 hoá và thiết kế hướng đối tượng giúp hiểu rõ hơn yêu cầu của vấn đề, hướng đối tượng giúp hiểu rõ hơn yêu cầu của vấn đề, thiết kế trong sáng hơn, và kết quả là những hệ thống dễ dàng bảo trì hơn.
Phương pháp phân tích bằng mô hình Phương pháp phân tích bằng mô hình
Các khái niệm trong thế giới thực được mô hình hoá
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 cuối của qui trình phát triển hệ thống.
Phương pháp phân tích bằng mô hình Phương pháp phân tích bằng mô hình
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
chuyển, linh động và có được tự do quyết định trong giai đoạn phân tích và thiết kế.
Phương pháp luận hướng đối tượng Phương pháp luận hướng đối tượng
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 trưng là cấu trúc dữ liệu (các thuộc tính) và hoạt động (các thủ tục xử lý dữ liệu).
Phương pháp luận hướng đối tượng Phương pháp luận hướng đối tượng
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 kế hệ thống.
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ô hình (OMT: Object Modeling Technique).
Phương pháp luận hướng đối tượng Phương pháp luận hướng đối tượng
Kỹ thuật mô hình hoá OMT bao gồm các bước:
Phân tích Thiết kế hệ thống
Thiết kế
đối tượng Cài đặt
Các khái niệm hướng đối tượng Các khái niệm hướng đối tượng
Trừu tượng hoá :
• 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 hoá 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
(polymorphism) mang lại hiệu quả cao hơn.
Các khái niệm hướng đối tượng Các khái niệm hướng đối tượng
Tính đóng gói :
• Tách rời các khía cạnh giao diện với bên ngoà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 toà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 một thực thể duy nhất giúp cho tính đóng gói rõ ràng hơn và hiệu quả hơn.
Các khái niệm hướng đối tượng Các khái niệm hướng đối tượng
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
cách kết hợp dữ liệu và phần thủ tục xử lý dữ liệu vào trong một thực thể duy nhất, hệ thống trở thành một sơ đồ phân cấp duy nhất các lớp đối tượng.
Các khái niệm hướng đối tượng Các khái niệm hướng đối tượng
Sơ đồ phân cấp dữ liệu
Kết hợp dữ liệu và hành vi
Sơ đồ phân cấp lớp Sơ đồ phân cấp dữ liệu
Được thay bằng
Đối tượng và lớp Đố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 các đối tượng được phân biệt với nhau do sự tồn tại vốn 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 Đố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.
tính của Người.
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.
Sơ đồ đối tượng 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 đó.
đối tượng trong lớp đó.
Sơ đồ lớp và sơ đồ thể hiện 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
Điểm TB Đi học Đi thi
Phân loại
9.2
Thao tác
Đối tượng và 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
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
58kg, là một thể hiện của lớp người.
Cài đặt lớp trong C++
Cài đặt lớp trong C++
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à class.
Ví dụ so sánh: Xây dựng kiểu dữ liệu stack.
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 <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;
};
Ví dụ so sánh (tt) 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;
delete [] ps->st;
}
bool StackFull(Stack *ps) {
return (ps->top - ps->st >= ps->size);
}
Ví dụ so sánh (tt) Ví dụ so sánh (tt)
bool StackEmpty(Stack *ps) {
return (ps->top <= ps->st);
}
bool StackPush(Stack *ps, Item x) {
{
if (StackFull(ps)) return false;
*ps->top++ = x;
return true;
}
Ví dụ so sánh (tt) Ví dụ so sánh (tt)
bool StackPop(Stack *ps, Item *px) {
if (StackEmpty(ps)) return false;
*px = *--ps->top;
return true;
}
Ví dụ so sánh (tt) 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);
StackPush(&s, n%16);
n /= 16;
} while(n);
while(StackPop(&s,&x)) cout << hTab[x];
StackCleanUp(&s);
}
Ví dụ so sánh (tt) 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 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 sự, dọn dẹp.
Ví dụ so sánh (tt) 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];}
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);
};
Ví dụ so sánh (tt) Ví dụ so sánh (tt)
bool Stack::Push(Item x) {
if (Full()) return false;
*top++ = x;
return true;
}
bool Stack::Pop(Item *px) {
if (Empty()) return false;
*px = *--top;
return true;
Ví dụ so sánh (tt) Ví dụ so sánh (tt)
void XuatHe16(long n) {
static char hTab[] = “0123456789ABCDEF”;
Stack s;
s.Init(8);
int x;
do {
s.Push(n%16);
n /= 16;
} while(n);
while(s.Pop(&x)) cout << hTab[x];
s.CleanUp();
}
Ví dụ so sánh (tt) 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 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 sự, dọn dẹp.
Các hàm thành phần.
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 (->).
dấu mũi tên (->).
Stack s, *ps = &s;
s.Init(10);
for (int i = 0; i < 20; i++) ps->Push(i);
Lớp 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++) for (int i = 0; i < 20; i++)
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 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;
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);}
bool Push(Item x);
bool Pop(Item *px);
Lớp 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.
Thuộc tính truy xuất 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
• Thuộc tính public
Thuộc tính truy xuất 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.
// 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);
bool Pop(Item *px);
};
Ví dụ về lớp và thuộc tính truy xuất 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;
} }
bool Stack::Pop(Item *px) {
if (Empty()) return false;
*px = *--top;
Ví dụ về lớp và thuộc tính truy xuất 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 { do {
s.Push(n%16);
n /= 16;
} while(n);
while(s.Pop(&x))
cout << hTab[x];
}
Sử dụng phạm vi truy xuất 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.
Tự tham chiếu 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.
đị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à hủy bỏ.
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 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 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ỏ.
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 bởi tên lớp để phân biệt nó với các hàm thành phần khác.
Chỉ có thể có tối đa một phương thức hb.
Phương thức thiết lập và hủy bỏ.
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);}
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);
bool Pop(Item *px);
};
Hàm bạn (friends) 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 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 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.
Hàm thành phần hằng 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.
đổi các thành phần dữ liệu của đối tượng.
Hàm thành phần hằng 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;
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 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();
s.Output();
Truong.Output();
Truong.ToLower(); // Bao loi }
Thành phần tĩnh (static members) 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.
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ó 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 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:
public:
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 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];
return dayTab[LeapYear(y)][m];
}
bool betw(int x, int a, int b) {
return x >= a && x <= b;
}
Ví dụ về thành phần tĩnh : CDate 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;
int d,m,y;
cin >> d >> m >> y;
while (!ValidDate(d,m,y)) {
cout << "Please enter a valid date: ";
cin >> d >> m >> y;
}
2.3 Thiết lập và huỷ bỏ đối tượng 2.3 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ỷ
• 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:
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 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 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 được cấp phát động
Đối tượng tự động Đố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 đó.
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
nguyên với đối tượng nguồn.
Đối tượng là biến địa phương Đối tượng là biến địa phương
#include <iostream.h>
#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);}
~string() {cout << "delete "<< (void *)p <<
"\n"; delete [] p;}
void main() {
string a("Nguyen Van A");
string b = a; // String b(a) a.Output(); cout << "\n";
b.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 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ỷ, A”). Khi kết thúc hàm main() hai đối tượng này bị huỷ, 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ố 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;
char *p;
public:
String(char *s = "") {p = strdup(s);}
~String() {cout << "delete "<< (void *)p <<
"\n"; delete [] p;}
void Output() const {cout << p;}
bool Compare(String s) const;
};
Đối tượng là tham số truyền bằng giá trị Đối tượng là tham số truyền bằng giá trị
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);
cout << (c > 0 ? "a > b" : c == 0 ? "a = b" : "a < b") << "\n";
Đối tượng là tham số truyền bằng giá trị Đố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 delete 0x0f22
Đối tượng là giá trị trả về Đố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;}
delete [] p;}
void Output() const {cout << p;}
bool Compare(String s) const;
String UpCase() const;
};
Đối tượng là giá trị trả về Đối tượng là giá trị trả về
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();
cout << "a = "; a.Output(); cout << "\n";
cout << "A = "; a.Output(); cout << "\n";
}
Đối tượng là giá trị trả về Đố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 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ề Đố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();
String A = a.UpCase();
Đối tượng là giá trị trả về Đố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";
} }
Đối tượng là giá trị trả về Đố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 delete 0x0d32
Null pointer assignment
Phương thức thiết lập sao chép Phương thức thiết lập sao chép
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 có tham số là đối tượng cùng kiểu, dùng để sao chép đối có tham số là đối tượng cùng kiểu, dùng để sao chép đối tượng.
Phương thức thiết lập sao chép Phương thức thiết lập sao ché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 soát sử huỷ bỏ đối
tượng.
tượng.
Phương thức thiết lập sao chép Phương thức thiết lập sao chép
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);}
strdup(s.p);}
~String() {cout << "delete "<< (void *)p
<< "\n"; delete [] p;}
void Output() const {cout << p;}
bool Compare(String s) const;
String UpCase() const;
Phương thức thiết lập sao chép Phương thức thiết lập sao chép
bool String::Compare(String s) const {
return strcmp(p,s.p);
}
String String::UpCase() const {
String r = *this;
String r = *this;
strupr(r.p);
return r;
}
Phương thức thiết lập sao chép Phương thức thiết lập sao chép
void main() {
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();
cout << "a = "; a.Output(); cout << "\n";
Phương thức thiết lập sao chép Phương thức thiết lập sao chép
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 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 sao chép Phương thức thiết lập sao chép
Tham số của phương thức thiết lập sao chép bắt buộc là tham chiế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 soát.
Lưu ý:
Nếu đối tượng không có tài nguyên riêng thì không cần Nếu đối tượng không có tài nguyên 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 thức thiết lập sao chép, 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 Sao chép nông và sao chép sâu
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 ra khỏi các đối tượng dùng chung tài nguyên, nói cách ra khỏi các đối tượng dùng chung tài nguyên, nói cách khác, nó phải có tài nguyên riêng (như sao chép sâu).
Ví duï veà sao cheùp noâng Ví duï veà sao cheùp noâ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 <<
~StringRep() {cout << "delete ” << (void *)p <<
"\n";}
};
Ví duï veà sao cheùp noâng Ví duï veà sao cheùp noâng
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();
};
Ví duï veà sao cheùp noâng Ví duï veà sao cheùp noâng
String::~String() {
if (--rep->n <= 0) delete rep;
}
bool String::Compare(String s) const {
return strcmp(rep->p,s.rep->p);
return strcmp(rep->p,s.rep->p);
}
String String::UpCase() const {
String r(rep->p);
strupr(r.rep->p);
return r;
Ví duï veà sao cheùp noâng Ví duï veà sao cheùp noâng
void main() {
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
< 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 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 delete 0x0d72 delete 0x0d58
Ví dụ về sao chép nông 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.
delete 0x0d84
a = Nguyen Van A A = NGUYEN VAN A delete 0x0d96
delete 0x0d72 delete 0x0d62 delete 0x0d50
A = NGUYEN VAN A delete 0x0d8a
delete 0x0d72 delete 0x0d58
Đối tượng tĩnh Đố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 Đối tượng tĩnh địa phương được tạo ra khi chương trình
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 Đố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
Ví dụ về đối tượng toàn cục Ví dụ về đố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…
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 Ví dụ về đối tượng toàn cục
Đ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...";}
};
Dummy d;
Đối tượng là thành phần của lớp Đố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 khi thiết lập thì đối tượng kết hợp (đối tượng lớn) phải 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 Đố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.
phương thức huỷ bỏ của đối tượng kết hợp được gọi.
Đối tượng là thành phần của lớp Đố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 {
{
Diem A,B,C;
public:
void Ve() const;
// ...
};
TamGiac t; // Bao sai
Đối tượng là thành phần của lớp Đố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;
//...
};
};
class SinhVien {
String MaSo;
String HoTen;
int NamSinh;
public:
};
SinhVien s; // Bao sai
Đối tượng thành phần - thiết lập Đố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;}
// ...
};
Khởi động đối tượng thành phần 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;
void Ve() const;
// ...
};
TamGiac t(100,100,200,400,300,300);
Đối tượng là thành phần của lớp Đố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;
"\n"; delete [] p;
//...
};
Khởi động đối tượng thành phần 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;}
MaSo(ms){NamSinh = ns;}
//...
};
SinhVien s(“Nguyen Van Beo”, “19920005”, 1985);
//Ok
Khởi động đối tượng thành phần 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){}
// ...
};
};
class SinhVien {
String MaSo, HoTen;
int NamSinh;
public:
SinhVien(char *ht, char *ms, int ns):HoTen(ht),
Đối tượng thành phần - Huỷ bỏ Đố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:
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) {
memcpy(Diem = new double[SoMon], d, SoMon*sizeof(double));
}
Đối tượng là thành phần của mảng Đố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ố.
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.
• Lớp có phương thức thiết lập không tham số.
Đối tượng là thành phần của mảng Đố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 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;}
// ...
Đối tượng là thành phần của mảng Đố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){}
MaSo(ms), NamSinh(ns){}
//...
};
String as[3]; // Bao sai Diem ad[5]; // Bao sai SinhVien asv[7]; // Bao sai
Phương thức thiết lập với tham số có giá trị mặc nhiên
Phương thức thiết lậ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 {
char *p;
public:
String(char *s = “”) {p = strdup(s);}
String(const String &s) {p = strdup(s.p);}
~String() {cout << "delete "<< (void *)p <<
Dùng phương thức thiết lập với tham số có giá trị mặc nhiên
Dùng phương thức thiết lậ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 =
“19920014”, int ns =
1982):HoTen(ht), MaSo(ms), NamSinh(ns){}
//...
};
String as[3]; // Ok: Ca ba phan tu deu la chuoi
Dùng phương thức thiết lập không tham số Dùng phương thức thiết lậ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 {
char *p;
public:
String(char *s) {p = strdup(s);}
String() {p = strdup(“”);}
~String() {cout << "delete "<< (void *)p <<
"\n"; delete [] p;}
Dùng phương thức thiết lập không tham số
Dùng phương thức thiết lập không tham số
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 Đố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 để 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à huỷ nhiều đối tượng.
Đối tượng được cấp phá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;}
//...
};
};
class Diem {
double x,y;
public:
Diem(double xx, double yy):x(xx),y(yy){};
Cấp và huỷ một đối tượng 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;
delete pj;
delete pi;
Cấp và huỷ nhiều đối tượng 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:
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’
Lỗi trên được khắc phục bằng cách cung cấp phương
Cấp và huỷ nhiều đối tượ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;}
//...
};
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 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];