Professional Documents
Culture Documents
Lap Trinh Huong Doi Tuong Chuong 07 Template, Exception (Cuuduongthancong - Com)
Lap Trinh Huong Doi Tuong Chuong 07 Template, Exception (Cuuduongthancong - Com)
.c
ng
CHƢƠNG 7.
co
an
TEMPLATE, EXCEPTION
th
o ng
du
u
cu
CuuDuongThanCong.com https://fb.com/tailieudientucntt
Template
om
1 Lập trình tổng quát
.c
ng
Lập trình tổng quát trong C++
co
2
an
th
3 C++ template
ng
o
du
om
Ví dụ xét hàm hoán vị như sau:
.c
void swap ( int& a, int& b){
ng
co
int temp;
an
temp = a; a = b; b = temp;
}
th
o ng
du
om
Ví dụ khác: Ta định nghĩa một lớp biểu diễn cấu
.c
trúc ngăn xếp cho kiểu int
ng
co
class Stack {
an
public:
th
Stack(); ng
~Stack();
o
du
om
Khai báo và định nghĩa của Stack phụ thuộc tại
.c
một mức độ nào đó vào kiểu dữ liệu int.
ng
co
Một số phương thức lấy tham số và trả về kiểu int
an
Nếu ta muốn tạo ngăn xếp cho một kiểu dữ liệu khác
thì sao?
th
o ng
Ta có nên định nghĩa lại hoàn toàn lớp Stack (kết quả
du
hay không?
om
Lập trình tổng quát là phương pháp lập trình độc
.c
lập với chi tiết biểu diễn dữ liệu.
ng
co
Tư tưởng là ta định nghĩa một khái niệm không phụ
an
thuộc một biểu diễn cụ thể nào, và sau đó mới chỉ ra
th
kiểu dữ liệu thích hợp làm tham số.
o ng
Như vậy trong một số trường hợp, đưa chi tiết về
du
om
Sử dụng trình tiền xử lý của C
.c
Trình tiền xử lý thực hiện thay thế text trước khi dịch
ng
co
Do đó, ta có thể dùng #define để chỉ ra kiểu dữ liệu và
an
thay đổi tại chỗ khi cần.
th
ng
#define TYPE int
o
void swap(TYPE & a, TYPE & b) {
du
TYPE temp;
u
cu
temp = a; a = b; b = temp;
}
om
#define TYPE int
.c
void swap(TYPE & a, TYPE & b) {
ng
TYPE temp;
co
temp = a; a = b; b = temp;
an
th
} ng
Sử dụng trình tiền xử lý của C
o
du
Chỉ cho phép đúng một định nghĩa trong một chương
trình.
om
Template (khuôn mẫu) là một cơ chế thay thế cho
.c
phép tạo các cấu trúc mà không phải chỉ rõ kiểu
ng
dữ liệu ngay từ đầu.
co
an
th
ng
Từ khóa template được dùng trong C++ để báo
o
du
om
Từ khóa template được theo sau bởi một cặp
.c
ngoặc nhọn chứa tên của các kiểu dữ liệu tùy ý
ng
được cung cấp.
co
an
template <typename T>
th
ng
template <typename T, typename U>
o
du
u
cu
om
Hai loại khuôn mẫu cơ bản:
.c
Function template – khuôn mẫu hàm cho phép
ng
co
định nghĩa các hàm tổng quát dùng đến các
an
kiểu dữ liệu tùy ý.
th
ng
Class template – khuôn mẫu lớp cho phép
o
du
om
Khuôn mẫu hàm là dạng khuôn mẫu đơn giản
.c
nhất cho phép ta định nghĩa các hàm dùng đến
ng
các kiểu dữ liệu tùy ý.
co
an
Ví dụ sau định nghĩa hàm swap() bằng khuôn
th
ng
mẫu:
template <typename T>
o
du
T temp;
temp = a; a = b; b = temp;
}
13/11/2020 CuuDuongThanCong.com Lập trình hướng đối tượng https://fb.com/tailieudientucntt 12
Khuôn mẫu hàm
om
Thực chất, khi sử dụng template, ta đã định
.c
nghĩa một tập “vô hạn” các hàm chồng nhau với
ng
co
tên swap()
an
Để gọi một trong các phiên bản này, ta chỉ cần
th
ng
gọi nó với kiểu dữ liệu tương ứng
o
du
int x = 1, y = 2;
u
cu
om
Chuyện gì xảy ra khi ta biên dịch mã?
.c
Trước hết, sự thay thế "T" trong khai báo/định
ng
co
nghĩa hàm swap() không phải thay thế text
an
đơn giản và cũng không được thực hiện bởi
th
ng
trình tiền xử lý.
o
du
om
Hãy xem xét hoạt động của trình biên dịch khi
.c
gặp lời gọi swap() thứ nhất (với hai tham số int)
ng
co
Trước hết, trình biên dịch tìm xem có một hàm
an
swap() được khai báo với 2 tham số kiểu int
th
ng
hay không? không tìm thấy nhưng tìm thấy
o
du
om
Tiếp theo, nó xem xét khai báo của template
.c
swap() để xem có thể khớp được với lời gọi hàm
ng
co
hay không?
an
Lời gọi hàm cung cấp hai tham số thuộc cùng một kiểu
(int)
th
o ng
Trình biên dịch thấy template chỉ ra hai tham số thuộc
du
Do đó, trình biên dịch kết luận rằng template khớp với
lời gọi hàm
om
Khi đã xác định được template khớp với lời gọi
.c
hàm, trình biên dịch kiểm tra xem đã có một
ng
phiên bản của swap() với hai tham số kiểu int
co
an
được sinh ra từ template hay chưa?
th
ng
Nếu đã có, lời gọi được liên kết (bind) với phiên bản
o
đã được sinh ra
du
u
Nếu không, trình biên dịch sẽ sinh một cài đặt của
cu
swap() lấy hai tham số kiểu int - và liên kết lời gọi hàm
với phiên bản vừa sinh.
om
Như vậy, đến cuối quy trình biên dịch đoạn mã
.c
trong ví dụ, sẽ có hai phiên bản của swap() được
ng
tạo với các lời gọi hàm của ta được liên kết với
co
an
phiên bản thích hợp.
th
ng
Chi phí về thời gian biên dịch đối với việc sử dụng
o
template?
du
u
Chi phí về không gian liên quan đến mỗi cài đặt của
cu
om
Tương tự với khuôn mẫu hàm với tham số thuộc
.c
các kiểu tùy ý, ta cũng có thể định nghĩa khuôn
ng
mẫu lớp (class template) sử dụng các thể hiện
co
an
của một hoặc nhiều kiểu dữ liệu tùy ý.
th
ng
Việc khai báo một khuôn mẫu lớp cũng tương tự
o
du
om
Ví dụ: ta sẽ tạo một cấu trúc cặp đôi giữ một cặp
.c
giá trị thuộc kiểu tùy ý.
ng
co
Trước hết, xét khai báo Pair cho một cặp giá trị
an
kiểu int như sau:
th
ng
struct Pair {
o
du
int first;
u
cu
int second;
};
om
Ta có thể sửa khai báo trên thành một khuôn
.c
mẫu lấy kiểu tùy ý:
ng
co
template <typename T>
an
struct Pair {
T first;
th
o ng
T second;
du
};
u
cu
om
Ta có thể cho phép hai thành viên nhận các kiểu
.c
dữ liệu khác nhau:
ng
co
template <typename T, typename U>
an
struct Pair {
th
ng
T first;
o
du
U second;
u
};
cu
om
Để tạo các thể hiện của template Pair, ta phải
.c
dùng ký hiệu cặp ngoặc nhọn (khác với khuôn
ng
mẫu hàm)
co
an
Pair p; // Không được
th
ng
Pair<int, int> q; // Creates a pair of ints
o
du
a float
om
Khi thiết kế khuôn mẫu (cho lớp hoặc hàm),
.c
thông thường, ta nên tạo một phiên bản cụ thể
ng
trước, sau đó mới chuyển nó thành một template.
co
an
Ví dụ, ta sẽ bắt đầu bằng việc cài đặt hoàn chỉnh
th
ng
Stack cho số nguyên.
o
du
om
Ví dụ: Xét lớp Stack với số nguyên
.c
class Stack {
ng
private:
co
static const int max = 10;
an
int contents[max], current;
public:
th
ng
Stack(); ~Stack();
o
du
om
Stack::Stack() { this->current = 0; }
.c
Stack::~Stack() {}
ng
void Stack::push(const int& i) {
co
if (this->current < this->max) this->contents[this->current++] = i;
an
}
th
void Stack::pop(int& i) { ng
if (this->current > 0) i = this->contents[--this->current];
o
du
}
bool Stack::isEmpty() const { return (this->current == 0;) }
u
cu
om
template <class T>
class Stack {
.c
private:
ng
static const int max = 10;
co
T contents[max];
an
int current;
th
public: ng
Stack(); ~Stack();
o
du
om
template <class T>
Mỗi phương thức cần một
.c
Stack<T>::Stack() { lệnh template đặt trước
ng
this->current = 0;
co
}
Mỗi khi dùng toán tử phạm
an
template <class T>
vi, cần một ký hiệu ngoặc
th
Stack<T>::~Stack() { }
nhọn kèm theo tên kiểu.
ng
template <class T>
Ta đang định nghĩa một lớp
o
du
om
template <class T>
.c
void Stack<T>::pop(T& i) {
if (this->current > 0)
ng
i = this->contents[--this->current];
co
}
an
template <class T>
th
bool Stack<T>::isEmpty() const {
ng
return (this->current == 0;)
o
}
được lưu trong ngăn xếp (trước
u
om
int x = 5,
.c
char c = 'a',
ng
co
Stack<int>
an
Stack<char>
th
ng
s.push(x);
o
du
t.push(c);
u
cu
s.pop(y);
t.pop(d);
om
Phần trước chúng ta chỉ mới nói đến các lệnh
.c
template với tham số thuộc "kiểu" class.
ng
co
Tuy nhiên, chúng ta có thể sử dụng các tham số
an
kiểu và tham số biểu thức trong khuôn mẫu lớp
th
ng
template <class T, int elements>
o
du
om
Trong cài đặt Stack, ta có một hằng max quy định
.c
số lượng tối đa các đối tượng mà ngăn xếp có
ng
thể chứa mỗi thể hiện sẽ có cùng kích thước
co
an
đối với mọi kiểu của đối tượng được chứa.
th
ng
Ta không muốn mọi Stack đều có kích thước tối
o
du
om
template <typename T, int M>
class Stack {
.c
public: Khai báo tham số mới
ng
Stack();
co
~Stack(); Sử dụng tham số mới
an
void push(const T& i); để xác định giá trị
void pop(T& i);
thmax của một lớp
ng
bool isEmpty() const; thuộc một kiểu nào đó
o
du
om
template <typename T, int I>
Sửa các lệnh
.c
Stack<T, I>::Stack() { this->current = 0; } template
ng
co
template <typename T, int I>
an
Stack<T, I>::~Stack() {}
th
ng
template <typename T, int I>
Sửa tên lớp
o
du
this->contents[this->current++] = i;
}
13/11/2020 CuuDuongThanCong.com Lập trình hướng đối tượng https://fb.com/tailieudientucntt 34
Các tham số khuôn mẫu khác
om
Giờ ta có thể tạo các thể hiện của các lớp Stack
.c
với các kiểu dữ liệu và kích thước đa dạng
ng
co
Stack<int, 5> s;
an
th
Stack<int, 10> t; ng
o
Stack<char, 5> u;
du
u
cu
om
1 Giới thiệu
.c
ng
Cách xử lý lỗi truyền thống
co
2
an
th
3 Ngoại lệ trong C++
ng
o
du
om
Mọi đoạn chương trình đều tiềm ẩn khả năng
.c
sinh lỗi
ng
co
Lỗi chủ quan: do lập trình sai
an
Lỗi khách quan: do dữ liệu, do trạng thái của hệ thống
th
ng
Lỗi có 2 loại?
o
du
om
Cài đặt mã xử lý tại nơi phát sinh ra lỗi
.c
Làm cho chương trình trở nên khó hiểu
ng
co
Không phải lúc nào cũng đầy đủ thông tin để xử lý
an
Không nhất thiết phải xử lý
th
ng
Truyền trạng thái lên mức trên
o
du
Thông qua tham số, giá trị trả lại hoặc biến tổng thể
u
(flag)
cu
Dễ nhầm
Khó hiểu
13/11/2020 CuuDuongThanCong.com Lập trình hướng đối tượng https://fb.com/tailieudientucntt 38
Cách xử lý lỗi truyền thống
om
int devide(int num, int denom, int& error){
.c
if (0 != denom){
ng
error = 0;
co
return num/denom;
an
} else {
th
ng
error = 1;
o
du
return 0;
u
cu
}
}
om
Khó kiểm soát được hết các trường hợp
.c
Lỗi số học
ng
co
Lỗi bộ nhớ
an
…
th
ng
Lập trình viên thường quên không xử lý lỗi
o
du
om
Exception – Ngoại lệ là cơ chế thông báo và xử lý
.c
lỗi giải quyết được các vấn đề gặp phải ở trên.
ng
co
Tách được phần xử lý lỗi ra khỏi phần thuật toán
an
chính.
th
ng
Cho phép một hàm có thể thông báo về nhiều
o
du
loại ngoại lệ
u
cu
om
Một ngoại lệ là một đối tượng chứa thông tin về
.c
một lỗi và được dùng để truyền thông tin đó tới
ng
cấp thực thi cao hơn.
co
an
Ngoại lệ có thể thuộc kiểu dữ liệu bất kỳ của C++
th
ng
Có sẵn, chẳng hạn int, char*, …
o
du
om
Quá trình truyền ngoại lệ từ ngữ cảnh thực thi
.c
hiện hành tới mức thực thi cao hơn gọi là ném
ng
một ngoại lệ (throw an exception).
co
an
Vị trí trong mã của hàm nơi ngoại lệ được ném được
gọi là điểm ném (throw point)
th
o ng
Khi một ngữ cảnh thực thi tiếp nhận và truy nhập
du
u
the exception)
om
Quy trình gọi hàm và trả về trong trường hợp
.c
bình thường:
ng
co
void main() {
an
int x, y;
cout << “Nhập 2 số: ”; th
o ng
cin >> x >> y;
du
om
Quy trình ném và bắt ngoại lệ:
.c
ng
co
an
th
ng catches
exeption
o
du
u
cu
throws
exception
om
Cơ chế xử lý ngoại lệ của C++ có 3 tính năng
.c
chính:
ng
co
Khả năng tạo và ném ngoại lệ (sử dụng từ khoá
an
throw)
th
ng
Khả năng bắt và giải quyết ngoại lệ (sử dụng từ
o
du
khoá catch)
u
ra khỏi phần còn lại của hàm (sử dụng từ khoá try)
om
Để ném một ngoại lệ, ta dùng từ khóa throw, kèm
.c
theo đối tượng mà ta định ném.
ng
Ta có thể dùng mọi thứ làm ngoại lệ, kể cả giá trị
co
thuộc kiểu có sẵn.
an
th
double MyDivide(double numerator, double denominator){
ng
if (denominator == 0.0) {
o
du
} else {
cu
om
Khối try – catch dùng để:
.c
Tách phần giải quyết lỗi ra khỏi phần có thể sinh lỗi
ng
co
Quy định các loại ngoại lệ được bắt tại mức thực thi
an
hiện hành
th
ng
try {
o
// Code that could generate an exception
du
}
u
cu
om
Có thể có nhiều khối catch, mỗi khối chứa mã để
.c
giải quyết một loại ngoại lệ cụ thể:
ng
try {
co
// Code that could generate an exception
an
}
th
catch (<Exception type1>) { ng
// Code that resolves a type1 exception
o
}
du
}
catch (<Exception typeN>) {
// Code that resolves a typeN exception
};
13/11/2020 CuuDuongThanCong.com Lập trình hướng đối tượng https://fb.com/tailieudientucntt 49
Kiểm soát ngoại lệ – Ví dụ
om
void main() {
int x, y;
.c
double result;
ng
cout << “Nhập 2 số: ”;
co
cin >> x >> y;
an
try {
th
result = MyDivide(x, y);
ng
cout << “Kết quả x/y = ”<< result << “\n”;
o
du
}
u
om
void main(){
int x, y;
.c
double result;
bool success;
ng
do {
co
success = true;
cout << "Nhập 2 số: ";
an
cin >> x >> y;
th
try { ng
result = Divide(x, y);
cout << "Kết quả x/y = "<< result << "\n";
o
du
}
catch (string& s) {
u
success = false;
};
} while (success == false);
}
om
Khi một ngoại lệ được ném từ trong một khối try,
.c
hệ thống xử lý ngoại lệ sẽ kiểm tra các kiểu được
ng
liệt kê trong khối catch theo thứ tự liệt kê:
co
an
Khi tìm thấy kiểu đã khớp, ngoại lệ được coi là
th
ng
được giải quyết, không cần tiếp tục tìm kiếm.
o
du
om
Khi tìm các kiểu dữ liệu khớp với ngoại lệ, trình
.c
biên dịch nói chung sẽ không thực hiện đổi kiểu
ng
tự động.
co
an
Nếu một ngoại lệ kiểu float được ném, nó sẽ không
th
khớp với một khối catch cho ngoại lệ kiểu int
o ng
Một đối tượng hoặc tham chiếu kiểu dẫn xuất sẽ
du
u
om
try {
//…
.c
ng
}
co
catch (MotorVehicle& mv) {…}
an
catch (Car& c) {…}
th
catch (Truck& t) {…};
o ng
Vấn đề gặp phải?
du
u
om
Nếu muốn bắt các ngoại lệ dẫn xuất tách khỏi
.c
ngoại lệ cơ sở, ta phải xếp lệnh catch cho lớp
ng
dẫn xuất lên trước:
co
an
try {
//… th
o ng
}
du
om
Nếu ta muốn bắt tất cả các ngoại lệ được ném
.c
(kể cả các ngoại lệ ta không thể giải quyết)?
ng
co
Để có một lệnh catch bắt được mọi ngoại lệ, ta
an
đặt dấu ba chấm bên trong lệnh catch.
th
ng
catch(…){
o
//…
du
u
};
cu
om
Để tích hợp hơn nữa các ngoại lệ vào ngôn ngữ
.c
C++, lớp exception đã được đưa vào thư viện
ng
chuẩn.
co
an
Sử dụng #include <exception> và namespace std
th
ng
Sử dụng thư viện này, ta có thể ném các thể hiện
o
du
om
Một số lớp ngoại lệ chuẩn khác được dẫn xuất từ
.c
lớp cơ sở exception.
ng
co
File header <stdexcept> (cũng thuộc thư viện
an
chuẩn C++) chứa một số lớp ngoại lệ dẫn xuất từ
th
ng
exception. Trong đó có hai lớp quan trọng được
o
du
runtime_error
logic_error
om
runtime_error: Các lỗi trong thời gian chạy (các
.c
lỗi là kết quả của các tình huống không mong
ng
đợi, chẳng hạn: hết bộ nhớ)
co
an
logic_error: Các lỗi trong logic chương trình
th
ng
(chẳng hạn truyền tham số không hợp lệ)
o
du
om
runtime_error có các lớp dẫn xuất sau:
.c
range_error điều kiện sau (post-condition) bị vi phạm
ng
overflow_error xảy ra tràn số học
co
bad_alloc không thể cấp phát bộ nhớ
an
th
logic_error có các lớp dẫn xuất sau:ng
domain_error điều kiện trước (pre-condition) bị vi
o
du
phạm
u
cho hàm
length_error tạo đối tượng lớn hơn độ dài cho phép
out_of_range tham số ngoài khoảng
13/11/2020 CuuDuongThanCong.com Lập trình hướng đối tượng https://fb.com/tailieudientucntt 60
Lớp exception
om
Ta có thể viết lại hàm MyDivide() để sử dụng các
.c
ngoại lệ chuẩn tương ứng như sau:
ng
co
double MyDivide(double numerator, double denominator)
an
{
th
if (denominator == 0.0) { ng
throw invalid_argument(“The denominator cannot
o
be 0.”);
du
} else {
u
cu
om
void main() {
int x, y;
.c
double result;
ng
do {
co
cout << “Nhập 2 số: ”; cin >> x >> y;
an
try {
th
result = MyDivide(x, y);
ng
cout << “x/y = ” << result << “\n”;
o
}
du
catch (invalid_argument& e) {
u
cu
om
Dễ sử dụng
.c
Dễ dàng chuyển điều khiển đến nơi có khả năng xử lý
ng
ngoại lệ
co
Có thể “ném” nhiều loại ngoại lệ
an
th
Tách xử lý ngoại lệ khỏi thuật toán ng
Tách mã xử lý
o
du
om
.c
ng
co
an
th
o ng
du
u
cu
CuuDuongThanCong.com https://fb.com/tailieudientucntt