STDIO
Tìm kiếm gần đây

    Nội dung

    template Trong C++

    21/09/2014
    10/12/2017
    template Trong C++
    Template là từ khóa trong C++, chúng ta có thể hiểu rằng là nó một kiểu dữ liệu trừu tượng, đặc trưng cho các kiểu dữ liệu cơ bản. Khi học về lập trình hướng đối tượng (OOP) trong ngôn ngữ C++, trong quá trình lập trình, chắc ai cũng gặp trường hợp phải overload lại các hàm để dùng cho mỗi kiểu dữ liệu tương ứng, thì trong C++ có hỗ trợ cho chúng ta giải quyết vấn đề trên, qua bài viết này tôi xin chia sẻ mọi người kiến thức của mình về "template".

    Giới thiệu

    Khi học về lập trình hướng đối tượng (OOP) trong ngôn ngữ C++, trong quá trình lập trình, chắc ai cũng gặp trường hợp phải overload lại các hàm để dùng cho mỗi kiểu dữ liệu tương ứng, thì trong C++ có hỗ trợ cho chúng ta giải quyết vấn đề trên, qua bài viết này tôi xin chia sẻ mọi người kiến thức của mình về “template”.

    Tiền đề bài viết

    Trong quá trình tìm hiểu, học tập tôi luôn gặp những trường hợp phải định nghĩa một hàm nhiều lần cho nhiều kiểu dữ liệu khác nhau, nó làm tốn khá nhiều thời gian, nhưng sau một lúc tìm kiếm thì từ khóa “template” đã giúp tôi khắc phục vấn đề trên. Tôi muốn chia sẻ kiến thức mà tôi đã tìm hiểu cho mọi người.

    Đối tượng hướng đến

    Bài viết hướng đến các bạn lập trình viên về ngôn ngữ C++

    Template là gì?

    “Template” là từ khóa trong C++, chúng ta có thể hiểu rằng là nó một kiểu dữ liệu trừu tượng, đặc trưng cho các kiểu dữ liệu cơ bản. “Template” là từ khóa báo cho trình biên dịch rằng đoạn mã sau đây định nghĩa cho nhiều kiểu dữ liệu và mã nguồn của nó sẽ được compile sinh ra tương ứng cho từng kiểu dữ liệu trong quá trình biên dịch.

    Có 2 loại “template” cơ bản:

    • Function template: là một khuôn mẫu hàm, cho phép định nghĩa các hàm tổng quát thao tác cho nhiều kiểu dữ liệu.
    • Class template: là một khuôn mẫu lớp, cho phép định nghĩa các lớp tổng quát cho nhiều kiểu dữ liệu.

    Function template

    void Swap( int &x, int &y)
    {
        int Temp = x;
        x = y; 
        y = Temp;
    }

    Với đoạn code trên dùng để hoán vị giữa 2 số nguyên, nếu ta cần hoán vị giữa 2 số kiểu float hoặc double,… thì ta lại phải định nghĩa các hàm cho từng loại kiểu dữ liệu như thế. Tôi tự hỏi có cách nào mà có thể định nghĩa một hàm duy nhất mà có thể dùng cho nhiều kiểu dữ liệu hay không, sau một hồi tìm hiểu thì tôi đã có câu trả lời đó là “template”, với từ khóa “template” ta chỉ cần định nghĩa một hàm duy nhất cho các kiểu dữ liệu: int, float, double, …

    template <class Type> 
    void Swap( Type &x, Type &y)
    {
        Type Temp = x;
        x = y; 
        y = Temp;
    }
     
    void main()
    {
        int x = 5, y = 10;
        float a = 5.5f, b = 3.0f;
     
        Swap(x, y);
        Swap(a, b);
    }

    Vậy cách thức nó hoạt động ra sao, ta hãy xét hoạt động của trình biên dịch khị gặp lời gọi hàm Swap(x, y) với tham số truyền vào là kiểu int, trước hết khi gặp lời gọi hàm Swap(x, y) thì trình biên dịch tìm xem có hàm Swap() nào đã được khai báo với tham số kiểu int hay chưa, nếu có thì sẽ liên kết với hàm đó, nếu chưa nhưng lại tìm thấy từ khóa “template” với hàm Swap() được truyền vào 2 tham số cùng kiểu với nhau (lúc này là kiểu Type), trình biên dịch chỉ cần kiểm tra xem lời gọi hàm Swap(x, y) có 2 tham số có cùng kiểu dữ liệu với nhau hay không( trong ví dụ trên là 2 tham số kiểu int -> cùng kiểu dữ liệu), nếu cùng kiểu thì trình biên dịch lại kiểm tra xem hàm Swap() với 2 tham số kiểu int đã được sinh ra trước đó hay chưa, nếu có thì lời gọi hàm sẽ liên kết với hàm Swap() đã được sinh ra, nếu chưa thì khi đó trình biên dịch sẽ sinh ra hàm Swap(int &x, int &y), tương tự với Swap(a, b) kiểu float cũng tương tự như vậy. Vì vậy trong quá trình biên dịch sẽ sinh ra 2 hàm Swap() cho 2 loại kiểu dữ liệu trên.

    Trong ví dụ trên tôi đã chỉ ra cách khai báo hàm mẫu, trong đó: 

    • “Type” chỉ là một tên riêng thể hiện cho một kiểu dữ liệu tổng quát. 

    • “class” ta có thể thay thế bằng “typename”, ở đây nó không có sự khác biệt.

    Ngoài ra ta có thể dùng prototype cho nguyên mẫu hàm giống như ta làm cho các hàm thông thường

    template <class T> void Swap( T &x, T &y);
     
    void main()
    {
        int x = 3, y = 4;
        float a = 1.2f, b = 2.2f;
     
        Swap(x, y);
        Swap(a, b);
    }
     
    template <class T> void Swap( T &x, T &y)
    {
        T z = x;
        x = y;
        y = z;
    }

    Trong ví dụ trên ta chỉ hoán ví giữa 2 tham số cùng kiểu, vậy với khác kiểu thì sau? Ta chỉ cần khai báo thêm một kiểu dữ liệu tổng quát

    template <class T, class X> void Swap( T &x, X &y);

    Overloading Function Templates

    Nguyên mẫu hàm (function template) đều có tính chất của một hàm thông thường, nó cho phép chúng ta nạp chồng (overload function)

    template <class T, class X>
    T Sum( T x, X y)
    {
        T sum = x + y;
        return sum;
    }
      
    template <class T, class X>
    X Sum( T x, X y, T z)
    {
       X sum = x + y + z;
       return sum;
    }

    Lưu ý: muốn nạp chồng hàm thì các tham số truyền vào ở các hàm phải khác nhau

    Class template

    Ta xét ví dụ sau

    class Point
    {
        int x;
        int y;
    };

    Nếu muốn khai báo khuôn mẫu lớp với kiểu tùy ý ta làm như sau

    template <class Type>
    class Point
    {
        Type x;
        Type y;
    };

    Lúc này nếu như muốn khai báo một thể hiện template Point

    Point<int> P1;

    Trong ví dụ trên là khai báo Point với 2 thuộc tính cùng kiểu với nhau, chúng ta có thể định nghĩa với 2 thuộc tính có kiểu dữ liệu khác nhau​

    template <class TypeOne, class TypeTwo>
    class Point
    {
        TypeOne x;
        TypeTwo y;
    public:
        Point();
    };
    
    template <class TypeOne, class TypeTwo>
    Point<TypeOne, TypeTwo>::Point()
    {
        x = 0;
        y = 0;
    }

    Trong ví dụ trên, khi muốn định nghĩa hàm của một lớp mẫu ở file khác thì ta phải khai báo template với các tham số kiểu trước mỗi hàm ta muốn định nghĩa None-type template parameters. None-type template parameters là tham số mẫu mặc định, được xác định và trình biên dịch không sinh ra kiểu khác trong suốt thời gian biên dịch.

    template <class Type, int N>
    class Stack
    {
       //do something
    };

    Trong ví dụ trên N là kiểu int đã được xác định rõ từ trước và không thay đổi trong thời gian biên dịch.

    Default type

    Xét ví dụ sau

    template <class Type = int> //defaults to type int
    class Point
    {
        Type x;
        Type y;
    };
    
    Point<> point;

    Trong ví dụ trên khi khai báo thể hiện của khuôn mẫu lớp là Point<> point; do không có kiểu truyền vào, lúc này trình biên dịch sẽ mặc định là kiểu đó là int do lúc đầu đã gán tham số kiểu mặc định là  int khi khai báo kiểu trong template.

    Lưu ý: tham số kiểu mặc định phải nằm ngoài cùng bên phải trong danh sách các tham số kiểu mẫu

    Template và Friend

    Như chúng ta thường thấy các lớp và các hàm có thể được khai báo là friend của nhau. Đối với khuôn mẫu lớp cũng vậy, chúng ta có thể khai báo friend giữa khuôn mẫu lớp và hàm toàn cục, hoặc hàm của các lớp khác,..

    template <class Type>
    class Point
    {
    public:
        friend void Sum(Type X);
        friend void Sum();
    };
    
    template <class Type>
    void Sum(Type X)
    {
         printf("a friend of only a class template with same type\n");
    }
    
    void Sum()
    {
         printf("a friend of every class template\n");
    }

    Trong ví dụ trên thì hàm Sum() sẽ là hàm bạn với Point<int>, Point<float>,… còn nếu Type là int thì lúc này Sum(int X) là hàm bạn với Point<int> nhưng sẽ không là bạn với Point<float>

    Template và thành viên tĩnh

    Tương tự như nontemplate class, chúng ta có thể định nghĩa thành viên tĩnh trong khuôn mẫu lớp, và thành viên tĩnh phải được khởi tạo ở phạm vi toàn cục. Nhưng thành viên tĩnh trong khuôn mẫu lớp khác với nontemplate class là thời điểm khởi tạo, đối với nontemplate class thì sẽ được khởi tạo khi bắt đầu chương trình, nhưng còn đối với khuôn mẫu lớp thì lại khởi tạo trong thời gian biên dịch.

    template <class Type>
    class Point
    {
    public:
        static int N;
    };
    
    Template <class Type> int Point<Type>::N = 0;
    void main()
    {
       Point<int> t1;
       t1.N++;
        Point<float> t2;
        t2.N++;
        Point<int> t3;
        t3.N++;
        printf("T1: %d\n", t1.N);
        printf("T2: %d\n", t2.N);
        printf("T3: %d\n", t3.N);
    }

    Kết quả in ra sẽ là 2 1 2. Nguyên nhân là khi ta khai báo Point <int> t1; lúc này trình biên dịch tìm xem Point<int> đã được khởi tạo chưa, lúc này chưa thì trình biên dịch sẽ khởi tạo ra đối tượng Point với kiểu cụ thể là int, thành viên tĩnh N được khởi tạo từ đây, sau khi thực thi dòng t1.N++; lúc này N = 1.

    Đối với Point<float> t2 cũng tương tự như với Point<int> t1, kết quả khi thực thi dòng t2.N++; lúc này N = 1. Đối với  Point<int> t3, lúc này lớp Point với kiểu int đã được khởi tạo rồi, thành viên tĩnh N của t3 được sử dụng chung với t1, lúc này khi t3.N++; thì N = 2. Vì vậy khi in kết quả lên màn hình sẽ ra kết quả 2 1 2

    Như tôi nói ở phần khuôn mẫu hàm thì “typename” và “class” là như nhau, nhưng trong trường hợp này thì lại phải dùng “typename”

    template <typename Type>
    class Point
    {
        Type p;
    };

    Ở đây A là một kiểu của Type vì thế mà p được hiểu là một con trỏ kiểu Type::A*, khi đó nếu muốn khai báo thể hiện của template Point thì làm như sau 

    class Q
    {
    public:
        typedef int A;
    };
     
    Point<Q> z;

     Còn nếu như ta bỏ “typename”

    template <class Type>
    class Point
    {
    	Type::A * p;
    };

    Lúc này Type::A * p được hiểu là tích giữa biến static A của lớp Type với một biến p nào đó. Ngoài ra đối với khuôn mẫu lớp ta có thể kế thừa giống như những các lớp thông thường

    template <class Type>
    class Point
    {
    protected:
        Type x;
        Type y;
    };
    template <class Type>
    class Point3D: public Point<Type>
    {
        Type z;
    };

    Template cho phép chúng ta tổng quát hóa kiểu dữ liệu, nhưng đồng thời cũng hỗ trợ chúng ta thao tác, xử lý chuyên môn trên trên một kiểu hay nhiều nhất định

    template <class Type>
    class Point
    {
    public:
        Point()
        {
            printf("Not explicit specialization\n");
        }
    };
    
    template<>
    class Point<int>
    {
    public:
        Point()
        {
            printf(“Explicit specialization\n”);
        }
    };

    Lợi ích của việc dùng template để tổng quát hóa kiểu dữ liệu

    Như các bạn đã thấy trong từng ví dụ, việc sử dụng từ khóa “template” làm giảm rất nhiều thời gian và công sức phải code lại một hàm mà dùng cho nhiều kiểu dữ liệu, dễ dàng bảo trì, phát triển hay thay đổi  mã nguồn.
    Tuy nhiên khi biên dịch, các hàm và lớp sẽ được phát sinh đầy đủ theo nhu cầu của chương trình trước khi chúng được gọi thực thi nên tốc độ của chương trình sẽ giống như ta cài đặt nhiều hàm hay lớp độc lập cho mỗi kiểu dữ liệu tương ứng.

    Thảo luận

    In order to comment you must be a STDIO Insider. Please sign up or log in to continue.

    Đăng nhập

    Bài viết liên quan

    9 Tính Năng Quan Trọng Trong C++11

    9 Tính Năng Quan Trọng Trong C++11

    C++11 là một phiên bản cải tiến và nâng cấp từ C++98 (hay các bạn vẫn gọi là C++), với những tính năng mới tối ưu hơn, dễ sử dụng hơn, dễ quản lý bộ nhớ hơn, và khắc phục ...

    Lê Minh Tài

    13/08/2015

    Tư Duy Tối Ưu Hóa Trong Lập Trình Games - Phần 1: Codes Trong C/C++

    Tư Duy Tối Ưu Hóa Trong Lập Trình Games - Phần 1: Codes Trong C/C++

    Bài viết hướng tối ưu hóa trong lập trình với C++, tối ưu hóa lập trình C++ với games, bài viết hướng games bởi vì games đòi hỏi hiệu năng rất cao, và các games lớn thông ...

    La Kiến Vinh

    18/09/2014

    STL - List Trong C++

    STL - List Trong C++

    STL là viết tắt của cụm từ Standard Template Library, là bộ thư viện chuẩn của C++, STL cung cấp các lớp cài đặt sẵn, cho phép thao tác với các kiểu dữ liệu cơ bản cũng ...

    Trung Nguyễn

    21/09/2014

    Thao Tác Với Chuỗi Trong C/C++

    Thao Tác Với Chuỗi Trong C/C++

    Khái niệm chuỗi ký tự do con người đặt ra để thuận tiện trong việc sử dụng. Có thể hiểu đơn giản, chuỗi là tập hợp các ký tự được lưu trữ liên tiếp trong vùng nhớ máy ...

    Rye Nguyen

    28/07/2015

    Bản Chất Của Biến Trong C/C++

    Bản Chất Của Biến Trong C/C++

    Những ngày đầu được học và làm việc với các kiểu biến như int, float, char….Tôi luôn có những thắc mắc về:”Điều gì đang xảy ra bên trong biến int, char… khi ta cấp phát ...

    Trần Hữu Danh

    16/01/2015

    Type-casting Trong C++

    Type-casting Trong C++

    Chuyển đổi kiểu dữ liệu (type-casting) là một kỹ năng quan trọng trong lập trình C/C++. Một số bài toán phức tạp không thể giải được do dữ liệu không tương thích, qua quá ...

    Rye Nguyen

    30/07/2015

    Interface Trong C#

    Interface Trong C#

    Giới thiệu về interface trong C#, ý nghĩa sử dụng, tính ứng dụng và cách thức sử dụng interface trong C#.

    Huỳnh Minh Tân

    03/10/2017

    Đọc Ghi File Cơ Bản Trong C#

    Đọc Ghi File Cơ Bản Trong C#

    Hướng dẫn đọc và ghi file cơ bản sử dụng đối tượng File trong C#. Nhờ có các hàm hỗ trợ nên các thao tác đọc ghi dữ liệu trên file trong C# được thực hiện một cách nhanh ...

    Bùi Trung Hiếu

    07/04/2016

    Nhập Xuất Cơ Bản Trong C/C++

    Nhập Xuất Cơ Bản Trong C/C++

    Bài viết là tiền đề giúp cho người đọc làm quen trong việc kiểm soát dữ liệu nhập xuất cũng như các thao tác cơ bản trên C/C++.

    Nguyễn Hồng Sơn

    18/03/2016

    While Và Do...While Trong C++

    While Và Do...While Trong C++

    Cấu trúc lặp là kiến thức nền tảng quan trong trong bất cứ ngôn ngữ lập trình nào. Ngoài cấu trúc for đã được giới thiệu và hướng dẫn ở bài viết trước, bài viết này sẽ ...

    Rye Nguyen

    29/07/2015

    STDIO
    Trang chính
    Công ty TNHH STDIO

    30, Trịnh Đình Thảo, Hòa Thạnh, Tân Phú, Hồ Chí Minh
    +84 28.36205514 - +84 942.111912
    developer@stdio.vn

    383/1 Quang Trung, Phường 10, Quận Gò Vấp, Hồ Chí Minh
    Số giấy phép ĐKKD: 0311563559 do sở Kế hoạch và Đầu Tư TPHCM cấp ngày 23/02/2012

    ©STDIO, 2013 - 2020