=>> Nếu bạn muốn học qua video thì có thể truy cập 2 link sau và ĐĂNG KÝ NGAY HÔM NAY ĐỂ NHẬN ƯU ĐÃI SIÊU HẤP DẪN <==

=> Nơi đăng ký 1

=> Nơi đăng ký 2

1. Sự cần thiết của các hàm templates

Trong các chương trước, bạn đã học cách viết các hàm và các lớp giúp làm cho các chương trình dễ viết hơn, an toàn hơn và dễ bảo trì hơn. Mặc dù các hàm và lớp là các công cụ mạnh mẽ và linh hoạt để lập trình hiệu quả, nhưng trong một số trường hợp nhất định, chúng cũng có thể bị hạn chế phần nào do yêu cầu của C ++ là bạn chỉ định loại của tất cả các tham số.

Ví dụ: Giả sử bạn muốn viết một hàm để tìm lớn nhất giữa hai số. Bạn có thể làm như vậy:

int max(int x, int y)
{
    return (x > y) ? x : y;
}

Hàm này sẽ hoạt động rất tốt – cho số nguyên. Điều gì xảy ra sau này khi bạn nhận ra hàm max() của mình cần hoạt động với kiểu double? Theo truyền thống, câu trả lời sẽ là quá tải(overload) hàm max() và tạo một hàm với phiên bản mới hoạt động với kiểu double:

double max(double x, double y)
{
    return (x > y) ? x : y;
}

Lưu ý rằng code để thực hiện tìm giá trị max với kiểu double của hàm max() hoàn toàn giống với phiên bản với kiểu int của max ()! Trong thực tế, việc triển khai này sẽ hoạt động cho tất cả các loại khác nhau: ký tự, số nguyên, số double và nếu bạn đã quá tải toán tử >, thậm chí các lớp! Tuy nhiên, vì C ++ yêu cầu bạn tạo các kiểu cụ thể cho biến của mình, bạn bị mắc kẹt khi viết một hàm cho mỗi kiểu bạn muốn sử dụng.

Phải chỉ định các kiểu dữ liệu khác nhau, các hàm khác nhau của cùng một chức năng trong đó điều duy nhất thay đổi là kiểu tham số có thể trở thành vấn đề bảo trì và thời gian xử lý, và nó cũng vi phạm nguyên tắc lập trình chung rằng code trùng lặp nên được giảm thiểu tối đa nhất có thể. Sẽ không hay nếu chúng ta có thể viết một phiên bản hàm max() có thể hoạt động với các tham số thuộc kiểu bật kỳ ??

Chào mừng đến với thế giới của các template.

2. Một hàm template là gì?

Nếu bạn tìm từ “template” trong từ điển, bạn sẽ tìm thấy một định nghĩa tương tự như sau: Một mẫu(template) là mô hình phục vụ như một mô hình để tạo các đối tượng tương tự. Một kiểu mẫu(template) rất dễ hiểu là một khuôn mẫu. Một khuôn mẫu là một đối tượng (ví dụ như một miếng bìa cứng) với hình dạng được cắt ra từ nó (ví dụ: chữ J). Bằng cách đặt tấm giấy nến lên trên một vật thể khác, sau đó phun sơn qua lỗ, bạn có thể nhanh chóng tạo ra các mẫu mới bằng nhiều màu khác nhau! Lưu ý rằng bạn chỉ cần tạo một khuôn mẫu nhất định một lần – sau đó bạn có thể sử dụng nó bao nhiêu lần tùy thích, để tạo các mẫu đối tưởng theo bất kỳ màu nào bạn thích. Thậm chí tốt hơn, bạn không phải quyết định màu sắc của khuôn mẫu mà bạn muốn tạo cho đến khi bạn quyết định thực sự sử dụng mẫu đó với màu gì.

Trong C ++, các hàm mẫu(function template) là các hàm đóng vai trò là mẫu để tạo các hàm tương tự khác. Ý tưởng cơ bản đằng sau các mẫu hàm là tạo ra một hàm mà không phải chỉ định (các) loại, kiểu chính xác của một số hoặc tất cả các biến. Thay vào đó, chúng ta xác định hàm bằng cách sử dụng các kiểu giữ chỗ, được gọi là tham số kiểu mẫu. Khi chúng ta đã tạo một hàm bằng cách sử dụng các kiểu giữ chỗ này, chúng ta đã tạo ra một hàm mẫu hiệu quả.

Khi bạn gọi một hàm mẫu(function template), trình biên dịch sẽ tạo ra một bản sao của mẫu, thay thế các kiểu giữ chỗ bằng các kiểu biến thực tế từ các tham số trong lệnh gọi hàm của bạn! Sử dụng phương pháp này, trình biên dịch có thể tạo ra nhiều hình thái khác nhau của một hàm từ một mẫu! Chúng ta sẽ xem xét quá trình này chi tiết hơn trong bài học tiếp theo.

3. Tạo các hàm mẫu trong C ++

Tại thời điểm này, có lẽ bạn đang tự hỏi làm thế nào để thực sự tạo các hàm mẫu trong C ++. Hóa ra, nó không quá khó khăn.

Chúng ta hãy xem lại phiên bản int của max ():

int max(int x, int y)
{
    return (x > y) ? x : y;
}

Lưu ý rằng có 3 vị trí sử dụng các kiểu cụ thể: tham số x, y và giá trị trả về đều xác định rằng chúng phải là số nguyên. Để tạo hàm mẫu, chúng ta sẽ thay thế các kiểu cụ thể này bằng các kiểu giữ chỗ. Trong trường hợp này, vì chúng ta chỉ có một kiểu cần thay thế (int), chúng ta chỉ cần một tham số kiểu mẫu.

Bạn có thể đặt tên cho kiểu giữ chỗ của mình gần như mọi thứ bạn muốn, miễn là nó không phải là một từ dành riêng. Tuy nhiên, trong C ++, thông thường đặt tên cho mẫu của bạn là chữ T (viết tắt của Kiểu).

Đây là một hàm template mới của chúng ta với kiểu giữ chỗ:

T max(T x, T y)
{
    return (x > y) ? x : y;
}

Đây là một khởi đầu tốt – tuy nhiên, nó sẽ không được biên dịch bởi vì trình biên dịch không biết rõ T là gì!

Để thực hiện công việc này, chúng ta cần nói với trình biên dịch hai điều: Thứ nhất, đây là định nghĩa mẫu và thứ hai, T là kiểu giữ chỗ. Chúng ta có thể thực hiện cả hai điều đó trong một dòng, sử dụng cái được gọi là khai báo tham số mẫu :

template <typename T> // this is the template parameter declaration
T max(T x, T y)
{
    return (x > y) ? x : y;
}

Tin hay không, đó là tất cả những gì chúng ta cần. Điều này sẽ biên dịch!

Bây giờ, hãy xem xét kỹ hơn về khai báo tham số mẫu. Chúng ta bắt đầu với từ khóa template – điều này cho trình biên dịch biết rằng phần tiếp theo sẽ là danh sách các tham số mẫu. Chúng ta đặt tất cả các tham số của chúng ta trong dấu ngoặc nhọn (<>). Để tạo một tham số kiểu mẫu, sử dụng một trong hai từ khóa typename hoặc class. Không có sự khác biệt giữa hai từ khóa trong ngữ cảnh này, vì vậy bạn sử dụng tùy thuộc vào bạn. Lưu ý rằng nếu bạn sử dụng từ khóa class, kiểu được truyền vào không thực sự phải là một lớp (nó có thể là một biến cơ bản, con trỏ hoặc bất cứ thứ gì phù hợp). Sau đó, bạn đặt tên cho kiểu của bạn (thường là T).

Nếu hàm mẫu sử dụng nhiều tham số kiểu mẫu, chúng có thể được phân tách bằng dấu phẩy:

template <typename T1, typename T2>
// template function here

Đối với các lớp sử dụng nhiều hơn một kiểu, người ta thường thấy chúng có tên là T1, T2, hoặc các tên chữ cái viết hoa đơn lẻ khác, chẳng hạn như S.

Một lưu ý cuối cùng: Bởi vì đối số hàm được truyền cho kiểu T có thể là một kiểu lớp nói chung, không nên chuyển các lớp theo giá trị, sẽ tốt hơn là tạo các tham số và trả về các kiểu tham chiếu hàm templated của chúng ta:

template <typename T>
const T& max(const T& x, const T& y)
{
    return (x > y) ? x : y;
}

4. Sử dụng các hàm mẫu

Sử dụng một hàm mẫu là cực kỳ đơn giản – bạn có thể sử dụng nó giống như bất kỳ hàm nào khác. Đây là một chương trình đầy đủ sử dụng hàm mẫu của chúng ta:

/**
* Cafedev.vn - Kênh thông tin IT hàng đầu Việt Nam
*
* @author cafedevn
* Contact: cafedevn@gmail.com
* Fanpage: https://www.facebook.com/cafedevn
* Instagram: https://instagram.com/cafedevn
* Twitter: https://twitter.com/CafedeVn
* Linkedin: https://www.linkedin.com/in/cafe-dev-407054199/
*/

#include <iostream>
 
template <typename T>
const T& max(const T& x, const T& y)
{
    return (x > y) ? x : y;
}
 
int main()
{
    int i = max(3, 7); // returns 7
    std::cout << i << '\n';
 
    double d = max(6.34, 18.523); // returns 18.523
    std::cout << d << '\n';
 
    char ch = max('a', '6'); // returns 'a'
    std::cout << ch << '\n';
 
    return 0;
}

Điều này sẽ in:

7
18,523
a

Lưu ý rằng cả ba lệnh gọi tới max() này đều có các tham số thuộc các kiểu khác nhau! Vì chúng ta đã gọi hàm với 3 kiểu khác nhau, trình biên dịch sẽ sử dụng định nghĩa mẫu để tạo 3 phiên bản khác nhau của hàm này: một phiên bản có tham số int (được đặt tên là max <int>), một loại có tham số double (được đặt tên là max <double> ) và một với tham số char (được đặt tên là max <char>).

Lưu ý rằng bạn không cần chỉ định rõ ràng kiểu mẫu trong tên hàm (ví dụ: phần <int> của max <int>) miễn là trình biên dịch có thể suy ra nó từ các kiểu tham số.

5. Tóm lược

Như bạn có thể thấy, các hàm mẫu có thể tiết kiệm rất nhiều thời gian, bởi vì bạn chỉ cần viết một hàm và nó sẽ hoạt động với nhiều kiểu khác nhau. Khi bạn đã quen với việc viết các hàm mẫu, bạn sẽ thấy chúng thực sự không mất nhiều thời gian để viết hơn các hàm với các kiểu thực tế. Các hàm mẫu giảm code để bảo trì, vì code trùng lặp được giảm đáng kể. Và cuối cùng, các hàm mẫu có thể an toàn hơn, vì không cần phải sao chép các hàm và thay đổi các kiểu bằng tay bất cứ khi nào bạn cần hàm để làm việc với một kiểu mới!

Các hàm mẫu có một vài nhược điểm và chúng ta sẽ không nhắc đến chúng.

Đầu tiên, một số trình biên dịch cũ hơn không có hỗ trợ mẫu. Tuy nhiên, nhược điểm này không còn là vấn đề như trước đây.

Thứ hai, các hàm mẫu thường tạo ra các thông báo lỗi trông có vẻ khó giải mã hơn nhiều so với các hàm thông thường (chúng ta sẽ thấy một ví dụ về điều này trong bài học tiếp theo).

Thứ ba, các hàm khuôn mẫu có thể tăng thời gian biên dịch và kích thước code của bạn, vì một mẫu duy nhất có thể được nhận ra và được biên dịch lại trong nhiều file (có nhiều cách để xử lý xung quanh file này).

Tuy nhiên, những nhược điểm này khá nhỏ so với sức mạnh của mẫu và tính linh hoạt mang đến cho bộ công cụ lập trình của bạn!

Lưu ý: Thư viện chuẩn đã đi kèm với hàm max () templated, vì vậy bạn không phải tự viết (trừ khi bạn muốn). Nếu bạn tự viết, hãy lưu ý việc đặt tên tránh xung đột, vì trình biên dịch sẽ không thể biết bạn muốn phiên bản max () hay std :: max () của bạn.

Trong phần còn lại của chương này, chúng ta sẽ tiếp tục khám phá chủ đề của các mẫu(template).