Templates
Templates (mẫu) là một tính năng mới được giới thiệu bởi chuẩn ANSI-C++.
Nếu bạn sử dụng một trình biên dịch C++ chưa tương thích với chuẩn này thì
bạn không thể sử dụng mẫu.
Các mẫu hàm
Các mẫu cho phép tạo các hàm có thể chấp nhận bất kì kiểu dữ liệu nào làm tham
số và trả về giá trị mà không phải làm quá tải hàm với tất cả các kiểu dữ liệu có
thể. Khai báo mẫu của nó có thể là một trong hai kiểu sau:
template <class indetifier> function_declaration;
template <typename indetifier> function_declaration;
sự khác biệt duy nhất giữa hai kiểu khai báo mẫu này là việc sử dụng từ khoá
class hay typename, sự khác nhau giữa chúng là không rõ ràng vì cả hai đều
có cùng một ý nghĩa và đều cho một kết quả như nhau.
Ví dụ, để tạo một hàm mẫu trả về giá trị lớn hơn của hai đối tượng chúng ta có thể
sử dụng:
template <class GenericType>
GenericType GetMax (GenericType a, GenericType b) {
return (a>b?a:b);
}
Ở dòng đầu tiên, chúng ta đã tạo một mẫu cho một kiểu dữ liệu tổng quát với tên
GenericType. Vì vậy trong hàm sau đó, GenericType trở thành một kiểu dữ
liệu hợp lệ và nó được sử dụng như là một kiểu dữ liệu cho hai tham số a, b và giá
trị trả về của hàm GetMax.
GenericType thực sự không biểu diễn một kiểu dữ liệu cụ thể nào, chúng ta có
thể gọi hàm với bất kì kiểu dữ liệu hợp lệ nào. Kiểu dữ liệu này sẽ đáp ứng như là
pattern(mẫu) và sẽ thay thế GenericType bên trong hàm. Cách thức để gọi một
lớp mẫu với một kiểu dữ liệu mẫu như sau:
function <pattern> (parameters);
Ví dụ, để gọi hàm GetMax và so sánh hai giá trị nguyên kiểu int chúng ta có thể
viết:
int x,y;
GetMax <int> (x,y);
Ok, dưới đây là ví dụ đầy đủ:
// mẫu hàm
#include <iostream.h>
template <class T>
T GetMax (T a, T b) {
T result;
result = (a>b)? a : b;
return (result);
}
int main () {
int i=5, j=6, k;
long l=10, m=5, n;
k=GetMax<int>(i,j);
n=GetMax<long>(l,m);
cout << k << endl;
cout << n << endl;
return 0;
}
6
10
(Trong trường hợp này chúng ta gọi kiểu dữ liệu tổng quát là T thay vì
GenericType vì nó ngắn hơn, thêm vào đó nó là một trong những tên phổ biến
nhất được dùng cho mẫu mặc dù chúng ta có thể sử dụng bất cứ tên hợp lệ nào).
Ở ví dụ trên chúng ta đã sử dụng cùng một hàm GetMax() với các tham số kiểu
int và long chỉ với một phiên bản duy nhất của hàm. Như vậy có thể nói chúng
ta đã viết một mẫu hàm và gọi nó bằng hai kiểu khác nhau.
Như bạn có thể thấy, bên trong mẫu hàm GetMax() kiểu T có thể được dùng để
khai báo các đối tượng mới:
T result;
Trong trường hợp cụ thể này kiểu dữ liệu tổng quát T được sử dụng như là tham số
chao hàm GetMax, trình biên dịch có thể tự động tìm thấy kiểu dữ liệu nào phải
truyền cho nó mà không cần bạn phải chỉ định <int> hay <long>. Bởi vậy
chúng ta có thể viết:
int i,j;
GetMax (i,j);
Cách này được sử dụng phổ biến hơn và cũng cho kết quả như vậy:
// mẫu hàm II
#include <iostream.h>
template <class T>
T GetMax (T a, T b) {
return (a>b?a:b);
}
int main () {
int i=5, j=6, k;
long l=10, m=5, n;
k=GetMax(i,j);
n=GetMax(l,m);
cout << z << endl;
cout << n << endl;
return 0;
}
6
10
Chú ý trong trường hợp này, bên trong hàm main() chúng ta đã gọi hàm mẫu
GetMax() mà không chỉ định rõ kiểu dữ liệu ở giữa hai ngoặc nhọn <>. Trình
biên dịch sẽ tự động xác định kiểu dữ liệu nào là cần thiết chô mỗi lời gọi.
Vì hàm mẫu của chúng ta chỉ dùng một kiểu dữ liệu (class T) và cả hai tham số
của nó đều có cùng một kiểu, chúng ta không thể gọi hàm mẫu với tham số là hai
đối tượng có kiểu khác nhau:
int i;
long l;
k = GetMax (i,l);
Chúng ta cũng có thể khiến cho hàm cho hàm mẫu chấp nhận nhiều hơn một kiểu
dữ liệu tổng quát. Ví dụ:
template <class T, class U>
T GetMin (T a, U b) {
return (a<b?a:b);
}
Trong trường hợp này, hàm mẫu GetMin() chấp nhận hai tham số có kiểu khác
nhau và trả về một đối tượng có cùng kiểu với tham số đầu tiên (T). Ví dụ, sau khi
khai báo như trên chúng ta có thể gọi hàm như sau:
int i,j;
long l;
i = GetMin<int,long> (j,l);
hoặc thậm chí:
i = GetMin (j,l);
Các lớp mẫu
Chúng ta cũng có thể viết các lớp mẫu, một lớp mà có các thành viên dựa trên các
kiểu dữ liệu tổng quát không cần phải được định nghĩa ở thời điểm tạo lớp. Ví dụ
template <class T>
class pair {
T values [2];
public:
pair (T first, T second)
{
values[0]=first; values[1]=second;
}
};
Lớp mà chúng ta vừa định nghĩa dùng để lưu trữ hai phần tử có kiểu bất kì. Ví dụ,
nếu chúng ta muốn khai báo một đối tượng thuộc lớp này để lưu trữ hai giá trị
nguyên kiểu int có giá trị là 115 and 36 chúng ta có thể viết:
pair<int> myobject (115, 36);
lớp này cũng có thể được dùng để lưu trữ bất kì kiểu dữ liệu nào khác:
pair<float> myfloats (3.0, 2.18);
Hàm thành viên duy nhất được định nghĩa inline bên trong định nghĩa của lớp, tuy
nhiên nếu chúng ta định nghĩa một hàm thành viên bên ngoài lớp chúng ta luôn
phải đặt trước dòng định nghĩa tiền tố template <... >.
// mẫu lớp
#include <iostream.h>
100
template <class T>
class pair {
T value1, value2;
public:
pair (T first, T
second)
{value1=first;
value2=second;}
T getmax ();
};
template <class T>
T pair<T>::getmax ()
{
T retval;
retval = value1>value2?
value1 : value2;
return retval;
}
int main () {
pair <int> myobject
(100, 75);
cout <<
myobject.getmax();
return 0;
}
chú ý cách bắt đầu định nghĩa hàm getmax :
template <class T>
T pair<T>::getmax ()
Tất cả các T xuất hiện ở đó đều là cần thiết. Mỗi khi bạn khai báo một hàm thành
viên bạn sẽ phải theo một khuôn mẫu tương tự như thế.
Chuyên môn hoá mẫu
Việc chuyên môn hoá mẫu cho phép một mẫu tạo ra những bản thực thi đặc biệt
khi làm việc với một loại dữ liệu xác định nào đó. Ví dụ, giả sử rằng lớp mẫu
pair có một hàm dùng để trả về phần dư trong phép chia giữa hai trường ở trong,