Tải bản đầy đủ (.pdf) (39 trang)

Template và hướng đối tượng

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 (83.67 KB, 39 trang )




Chương 10


Template và điều khiển ngoại lệ









Hàm template


Lớp template


Điều khiển ngoại lệ


Biến tónh


Từ khoá extern và asm



Hàm chuyển kiểu


Những khác biệt giữa C và C++






Chửụng 10
Template vaứ ủieu khieồn ngoaùi leọ




298









































Chương 10
Template và điều khiển ngoại lệ





299
I/ Hàm template
Hàm template (hàm mẫu)
đònh nghiã một dãy tổng quát các tác vụ được dùng cho
nhiều kiểu dữ liệu khác nhau
. Trong đó,
kiểu dữ liệu được dùng sẽ được truyền đến
hàm dưới dạng một tham số
.

Với cơ chế này,
một thủ tục có thể được dùng cho nhiều kiểu dữ liệu khác nhau
.
Trong môn học Cấu trúc dữ liệu và thuật toán, nhiều giải thuật giống nhau về mặt
luận lý, bất kể nó làm việc với kiểu dữ liệu gì. Ví dụ giải thuật QuickSort là giống
nhau, không cần biết nó sẽ áp dụng cho dãy số nguyên hay dãy số thực. Vấn đề là ở
chỗ dữ liệu được xử lý khác nhau.

Bằng cách tạo ra hàm template, có thể đònh nghiã bản chất của giải thuật độc lập với
kiểu dữ liệu mà nó xử lý. Dựa vào hàm template, trình biên dòch sẽ tự động sinh ra
mã chương trình để dùng cho một kiểu dữ liệu cụ thể nào đó khi thực thi chương
trình. Thực chất là việc tạo ra một hàm template đồng nghiã với việc
tạo ra một hàm
mà nó có thể tự quá tải lên chính nó
.

Khai báo hàm template



template < class

Ttype
>

ret_type func_name
(parameter list)
{
// body of function
}

Từ khoá
template
dùng để tạo ra một dạng mẫu mô tả hoạt động của hàm và
nhường cho trình biên dòch điền vào một cách chi tiết khi cần.

Ttype
là tên hình thức cho kiểu dữ liệu được dùng bên trong hàm, nó sẽ được thay
thế bởi một kiểu dữ liệu cụ thể khi trình biên dòch sinh ra một phiên bản chi tiết của
hàm.

Quá trình tạo ra phiên bản chi tiết của hàm được gọi là quá trình sinh hàm. Việc tạo
ra
bản chi tiết của hàm template được gọi là
tạo ra ngay tức khắc
(instantiating) một
hàm. Nói một cách khác là
một hàm sinh ra phiên bản dùng trong chốc lát của hàm

template
.

Chương 10
Template và điều khiển ngoại lệ




300
Ví dụ 1.1
Hàm template trao đổi nội dung của hai biến

// Function template example.
#include <iostream.h>
// This is a function template.
template

<class
X
>
void
swapargs
(X &a, X &b)
{
X temp;

temp = a;
a = b;
b = temp;

}

int main()
{
int i=10, j=20;
float x=10.1, y=23.3;

cout << "Original i, j: " << i << ' ' << j << endl;
cout << "Original x, y: " << x << ' ' << y << endl;


swapargs
(
i, j
); // swap integers

swapargs
(
x, y
); // swap floats

cout << "Swapped i, j: " << i << ' ' << j << endl;
cout << "Swapped x, y: " << x << ' ' << y << endl;

return 0;
}

template

<class

X
>
void
swapargs
(X &a, X &b)
Dòng này thông báo với chương trình hai vấn đề :
- Đây là một hàm template.
- Đây là điểm bắt đầu của phần đònh nghiã một hàm template.

X có ý nghiã là kiểu dữ liệu của các biến cần trao đổi nội dung.
Chương 10
Template và điều khiển ngoại lệ




301
Hàm
swapargs
() được gọi hai lần trong hàm main() : một lần để trao đổi nội dung
hai biến kiểu số nguyên, và lần sau cho hai biến kiểu số thực. Do hàm
swapargs
() là
một hàm template, cho nên trình biên dòch sẽ tự động phát sinh ra hai phiên bản của
hàm
swapargs
() tương ứng với kiểu dữ liệu của đối số thực gởi vào.


Phần khai báo template của một hàm mẫu không bắt buộc phải viết trên cùng

một dòng với tên hàm.

Ví dụ 1.2

template
<class X>
void
swapargs
(X &a, X &b)
{
X temp;

temp = a;
a = b;
b = temp;
}



Chú ý rằng, không thể đặt bất kỳ mệnh đề nào giữa phần khai báo template và
phần thân hàm.

Ví dụ 1.3

// This will not compile.
template <class X>
int i
; // this is an error
void swapargs(X &a, X &b)
{

X temp;

temp = a;
a = b;
b = temp;
}
Chương 10
Template và điều khiển ngoại lệ




302

Trong hàm template, có thể
sử dụng nhiều kiểu dữ liệu khác nhau
bằng cách
đưa vào một danh sách các kiểu dữ liệu dùng trong hàm, trong đó tên hình thức
của các kiểu được phân cách nhau bằng dấu phẩy.

#include <iostream.h>
template
<class
type1
, class
type2
>
void myfunc(
type1
x,

type2
y)
{
cout << x << ' ' << y << endl;
}

int main()
{
myfunc(10, "hi");

myfunc(0.23, 10L);

return 0;
}

@ Khi phát sinh hàm, các kiểu hình thức type1 và type2 được thay thế hai lần : lần
đầu bằng hai kiểu int và char*, lần sau là double và long .



Hàm template tương tự như hàm được quá tải nhưng nó chòu nhiều hạn chế hơn.
Khi quá tải một hàm, các phiên bản có thể mang nhiều tác vụ rất khác nhau,
trong khi một
hàm template chỉ thực hiện được một công việc duy nhất ở tất cả
các phiên bản
.

Ví dụ không thể thay thế hàm được quá tải sau đây bằng một hàm template bởi vì
các hàm quá tải sau đây không cùng thực thi một công việc.


void
outdata(int i)

{
cout << i;
}

Chương 10
Template và điều khiển ngoại lệ




303
void
outdata(double d)

{
cout << setprecision(10) << setfill('#');
cout << d;
cout << setprecision(6) << setfill(' ');
}


Một
hàm template có thể tự quá tải
khi cần thiết, khi đó cần quá tải nó một cách
tường minh. Nếu
quá tải một hàm template thì hàm được quá tải sẽ che hàm
template đó

.

// Overriding a template function.
#include <iostream.h>

template <class X> void swapargs(X &a, X &b)
{
X temp;

temp = a;
a = b;
b = temp;
}

// This overrides the generic version of swapargs().
void swapargs(int a, int b)
{
cout << "this is inside swapargs(int,int)\n";
}

int main()
{
int i=10, j=20;
float x=10.1, y=23.3;

cout << "Original i, j: " << i << ' ' << j << endl;
cout << "Original x, y: " << x << ' ' << y << endl;

Chương 10
Template và điều khiển ngoại lệ





304

swapargs(i, j)
; // this calls the explicitly overloaded swapargs()
swapargs(x, y); // swap floats

cout << "Swapped i, j: " << i << ' ' << j << endl;
cout << "Swapped x, y: " << x << ' ' << y << endl;

return 0;
}

@ Việc quá tải lên một hàm template, cho phép lập trình viên
thay đổi giải thuật
của một phiên bản của hàm template để phù hợp với một tình huống đặc biệt
. Tuy
nhiên, nếu muốn sử dụng những phiên bản hoàn toàn khác nhau của một hàm trong
đó có nhiều kiểu dữ liệu, hãy dùng quá tải hàm thay vì hàm template.


Bài tập I

1. Viết một chương trình, trong đó có hàm template tên là min(), trả nó về giá trò đối
số nhỏ hơn trong hai giá trò đối số của hàm. Lấy ví dụ min(3,4) = 3, hoặc min('c', 'a')
= 'a' .


2. Viết một chương trình, trong đó có hàm template tên là bubblesort(), thực hiện
việc sắp xếp các dữ liệu kiểu số nguyên, số thực hoặc kiểu ký tự . Nếu thành công,
hàm trả về giá trò 1, ngược lại hàm sẽ trả về trò là -1. Hàm có dạng

int bubblesort (X a[], int n);
với n xác đònh số lượng các phần tử của dãy a.

3. Viết một chương trình, trong đó có hàm template tên là find(), thực hiện việc tìm
kiếm một đối tượng trên một dãy. Nếu tìm thấy, hàm trả về chỉ số của đối tượng tìm
được, ngược lại hàm sẽ trả về trò là -1. Hàm có dạng

int find(int object, int *list, int size);
với size xác đònh số lượng các phần tử của dãy.


Chương 10
Template và điều khiển ngoại lệ




305
II/ Lớp template

Lớp template
đònh nghiã tất cả các thuật toán được dùng bởi lớp đó, tuy nhiên kiểu
dữ liệu thực sự mà lớp này xử lý sẽ được đònh rõ dưới dạng tham số lúc tạo ra các đối
tượng thuộc lớp template đó.



Khai báo

template

< class

Ttype

>

class

class_name
{
// body of class
};

Ttype
là tên lớp hình thức và nó sẽ được xác đònh khi tạo ra một lớp. Khi cần thiết,
có thể đònh nghiã lớp template với nhiều kiểu dữ liệu template bằng cách dùng một
danh sách các tên kiểu hình thức được phân cách nhau bằng dấu phẩy.


Sau khi tạo ra lớp template, có thể sinh ra một lớp cụ thể từ lớp template


class_name
<

type


>

object_name
;

với
type
là tên thực của kiểu dữ liệu mà lớp đó xử lý.

Các
hàm thành phần của một lớp template cũng là các hàm template một cách tự
động
và không cần phải dùng từ khoá template để khai báo tường minh cho các hàm
đó.


Ví dụ 2.1
Tạo ra lớp template cho một danh sách liên kết, dùng để lưu các ký tự.
// A simple generic linked list.
#include <iostream.h>

template < class
data_t

> class
list {

data_t
data;

list *next;
public:
list(
data_t
d) ;
void add(list *node) {
node->next = this;
Chửụng 10
Template vaứ ủieu khieồn ngoaùi leọ




306
next = 0;
}
list *getnext() { return next; }

data_t
getdata() { return data; }
};

template <

class

data_t

>
list <

data_t
> :: list(
data_t
d)
{
data = d;
next = 0;
}

int main()
{
list<char> start('a')
;

list<char> *p, *last
;
int i;

// build a list
last = &start;
for(i=1; i<26; i++) {
p = new
list<char> ('a' + i)
;
p->add(last);
last = p;
}

// follow the list
p = &start;

while(p) {
cout << p->getdata();
p = p->getnext();
}

return 0;
}


Chương 10
Template và điều khiển ngoại lệ




307
@ Lưu ý dòng lệnh
list<char> start('a') ;
với
kiểu dữ liệu được truyền sang cho lớp xuất hiện bên trong cặp dấu
ngoặc
< >.

Kết quả chương trình ?

@ Có thể dùng danh sách list để lưu trữ các phần tử kiểu số nguyên hay không ?


Có thể sử dụng lớp template list ở trên cho một kiểu dữ liệu mới do người dùng
đònh nghiã. Ví dụ, về một cấu trúc lưu thông tin về danh bạ điện thoại


struct
addr
{
char name[40];
char street[40];
char city[30];
char state[3];
char phone[40];
char zip[12];
};

Dùng lớp template list để sinh ra một đối tượng mới obj là một danh sách liên kết
của các phần tử thuộc kiểu addr bằng khai báo như sau
list<
addr
>
obj(structvar);

với structvar là biến có kiểu dữ liệu là
addr
.


Các hàm template và lớp template là một công cụ mạnh làm gia tăng hiệu quả
lập trình, bởi vì nó cho phép
đònh nghiã một giải thuật ở dạng tổng quát và sử
dụng được cho nhiều loại dữ liệu có kiểu khác nhau
. Ngoài ra, còn
tiết kiệm được

thời gian gõ chương trình vào máy tính
, do không phải gõ lại nhiều đoạn mã
chương trình giống nhau về mặt giải thuật nhưng khác nhau về kiểu dữ liệu mà
nó xử lý.

Ví dụ 2.2
Tạo ra lớp template cho stack, dùng để lưu các kiểu dữ liệu ký tự và
kiểu double.
// This function demonstrates a generic stack.
Chửụng 10
Template vaứ ủieu khieồn ngoaùi leọ




308
#include <iostream.h>
#define SIZE 10

// Create a generic stack class
template <class
StackType

> class
stack {

StackType
stck[SIZE]; // holds the stack
int tos; // index of top-of-stack


public:
void init() { tos = 0; } // initialize stack
void push(
StackType
ch); // push object on stack

StackType
pop(); // pop object from stack
};

// Push an object.
template
<class
StackType
> void stack<
StackType
>::
push
(
StackType
ob)
{
if(tos==SIZE) {
cout << "Stack is full.\n";
return;
}
stck[tos] = ob;
tos++;
}


// Pop an object.
template
<class
StackType
>
StackType
stack<
StackType
>::
pop
()
{
if(tos==0) {
cout << "Stack is empty.\n";
return 0; // return null on empty stack
}
tos--;
return stck[tos];
}


Chửụng 10
Template vaứ ủieu khieồn ngoaùi leọ




309
int main()
{

// Demonstrate character stacks.

stack<char> s1, s2
; // create two stacks
int i;

// initialize the stacks
s1.init();
s2.init();

s1.push('a');
s2.push('x');
s1.push('b');
s2.push('y');
s1.push('c');
s2.push('z');

for(i=0; i<3; i++) cout << "Pop s1: " << s1.pop() << "\n";
for(i=0; i<3; i++) cout << "Pop s2: " << s2.pop() << "\n";

// demonstrate double stacks

stack<double> ds1, ds2
; // create two stacks


// initialize the stacks
ds1.init();
ds2.init();


ds1.push(1.1);
ds2.push(2.2);
ds1.push(3.3);
ds2.push(4.4);
ds1.push(5.5);
ds2.push(6.6);

for(i=0; i<3; i++) cout << "Pop ds1: " << ds1.pop() << "\n";
for(i=0; i<3; i++) cout << "Pop ds2: " << ds2.pop() << "\n";
Chương 10
Template và điều khiển ngoại lệ




310
return 0;
}

Kết quả chương trình ?



Một lớp template có thể chứa nhiều kiểu dữ liệu tổng quát (
generic data types
)
.
Khi đó các tên kiểu dữ liệu template được khai báo trong phần template dưới
dạng một danh sách và phân cách nhau bằng dấu phẩy.


Ví dụ 2.3
Một lớp có hai kiểu dữ liệu tổng quát

// This example uses two generic data types in a class definition.
#include <iostream.h>

template <class
Type1
,
class

Type2
>
class
myclass
{

Type1
i;

Type2
j;
public:
myclass(
Type1
a,
Type2
b) { i = a; j = b; }
void show() { cout << i << ' ' << j << '\n'; }
};


int main()
{

myclass<int, double>
ob1(10, 0.23); // a/

myclass<char, char *>
ob2('X', "This is a test"); // b/

ob1.show(); // show int, double
ob2.show(); // show char, char *

return 0;
}

Kết quả chương trình ?

Chương 10
Template và điều khiển ngoại lệ




311
@ Trong cả hai trường hợp ở trên (a/, b/) , trình biên dòch sẽ tự động sinh ra các dữ
liệu và hàm tương ứng với mệnh đề tạo ra lớp.


Bài tập II


1. Viết chương trình tạo và biểu diễn một lớp template lưu trữ queue.

2. Viết chương trình tạo một lớp template với tên input, khi hàm tạo của lớp này
được gọi nó sẽ thực hiện các công việc sau :
- thông báo nhắc người dùng nhập dữ liệu : prompt message
- nhận dữ liệu từ bàn phím.
- thông báo lỗi nếu dữ liệu không nằm trong vùng trò cho phép
giữa min_value và max_value.

Đối tượng thuộc kiểu input được khai báo như sau
input ob("prompt message", min_value, max_value) ;


III/ Điều khiển ngoại lệ (exception handling)

C++ (theo chuẩn ANSI C++, 1994) cung cấp sẵn một cơ chế xử lý lỗi gọi là điều
khiển ngoại lệ. Qua đó có thể
khống chế và đáp ứng lại các lỗi xuất hiện lúc thực thi
chương trình
.

1/
Điều khiển ngoại lệ
của C++ gồm ba từ khoá :
try
,
catch
,
throw

.

Một cách tổng quát, các mệnh đề dùng để giám sát các ngoại lệ được đặt bên trong
một khối try.
Khi một ngoại lệ (tức một lỗi) xuất hiện bên trong khối
try
, nó sẽ được
ném ra (bằng từ khoá
throw
). Tiếp theo ngoại lệ này sẽ bò bắt lại để xử lý (bằng từ
khoá
catch
).


Bất kỳ một mệnh đề nào thực hiện việc ném ra ngoài một ngoại lệ phải đặt ở bên
trong khối try (hoặc bên trong một hàm được gọi đến từ một mệnh đề bên trong khối
try).
Tất cả các ngoại lệ phải bò bắt lại bởi một mệnh đề catch đặt ngay sau khối try
mà từ đó nó được ném ra
.

×