C++模板与泛型编程

模板与泛型编程

  • OOP和泛型编程都能处理在编写程序时不知道类型的情况,不同之处在于:OOP能处理在程序运行之前都未知的情况;而在泛型编程中,在编译时就能知道类型。
  • 这里有个类模板的例子,实现了矩阵的基本运算:https://blog.csdn.net/u012526003/article/details/80116295

定义模板

  • 编译器可以推断出函数模板的类型,即根据传参类型进行推断或者进行隐式转换,但是无法推断出类模板的类型,因此需要在定义类的时候,在<>中提供额外的信息。
  • 模板类中有静态函数时,可以通过类类型对象来访问它,但是必须引用一个特定的实例,即需要给类对象的参数类型
  • 与函数参数相同,声明中的模板参数不必与定义中相同。
  • 可以指定默认模板,比如template <typename T = int>,表示默认T为int
  • code

    template <typename T>
    class A
    {
    public:
        A(T t) : val(t) {  }
        static void print() { cout << __FUNCTION__ << endl; }
    
    private:
        T val;
    };
    
    // 默认了类型为T
    template <typename T = int>
    class B
    {
    public:
        static void print() { cout << __FUNCTION__ << endl; }
    private:
        T val_;
    };
    
    void test()
    {
        A<int> a1(3);
        a1.print();
        // A::print(); //会报错,静态函数也需要提供类的参数类型
        A<double>::print();
    
        B<> b1;  // 仍然需要尖括号,但是不需要填写类型,因为已经有默认类型
        B<>::print();
        B<vector<int>>::print();
    }
    

控制实例化

  • 模板在使用时才会被实例化,因此相同的实例可能会出现在多个对象文件中,这种实例化相同模板的额外开销可能很大。因此可以通过显示实例化来避免这种开销。
  • 方法

    extern template class Name<int>;
    extern template int compare(cosnt int&, const int&);
    
  • 注意:声明的模板类型必须在程序其他位置进行实例化
  • 对于数组实参,如果两个数组大小不同,则它们是不同类型,但是如果被转换为指针,则他们的类型相同,因此主要是看是否可以被转换为指针。
  • code

    template <typename T>
    void  fun1(T a, T b)
    {
    }
    template <typename T>
    void  fun2(const T& a, const T& b)
    {
    }
    
    void test()
    {
        int a[10], b[42];
        fun1( a, b ); // 被转换为指针,因此运行没有问题
        // fun2(a, b); // 数组大小不同,又无法被拷贝转换为指针,因此无法编译通过
    }
    

函数模板显示实参

  • 如果没有实参的类型可以推断出参数模板的类型,则需要我们显示指定类型,注意:需要显示指定的参数类型必须写在template列表的最前面
  • code

    template <typename T1, typename T2, typename T3>
    T1 fun1(T2 a, T3 b)
    {
        return a + b;
    }
    template <typename T1, typename T2, typename T3>
    T3 fun2(T2 a, T1 b)
    {
        return a + b;
    }
    
    void test()
    {
        auto res = fun1<int>(1, 2);
        cout << typeid(res).name() << " : " << res << endl;
        // auto res2 = fun2<int>(1, 2); // 错误,无法推断出res2的类型,需要显式给出全部三个参数类型才可以
    }
    

非模板与模板重载

  • 如果模板有多个重载,则编译器会选择最匹配的那一个函数进行调用;如果非模板函数与模板重载的匹配度相同,则编译器会选择最特例化的那个版本,出于此,一个非函数模板函数比一个函数模板更好。
  • 在使用多个重载函数时,一定记得声明所有重载的函数版本,这样可以使得编译器找出最匹配、最特例化的函数版本。
  • code

    template <typename T>
    T fun(T a, T b)
    {
        return a + b;
    }
    int fun(int a, int b); // 如果没有这个声明,则下面的fun(1,2)只能调用函数模板了
    
    void test()
    {
        auto res1 = fun<int>(1, 2); // 只能执行函数模板
        auto res2 = fun(1, 2); // 匹配度相同,执行最特例化的版本,可以打断点看具体是调用了哪个函数
    }
    
    int fun(int a, int b)
    {
        return a + b;
    }
    

可变参数模板

  • 可变参数模板就是一个接受可变数目参数的模板函数或者模板类,可变数目的参数被称为参数包。有2种参数包:模板参数包,表示0互助哦和多个模板函数;函数参数包,表示0或者多个函数参数。可以用typnename...指出接下来的参数表示0或者多个类型的列表。sizeof...(Args)可以用来计算参数包中所含参数的个数。
  • code

    template<typename T, typename... Args>
    void foo(const T t, Args ...args)
    {
        cout << __FUNCTION__ << " : " << sizeof...(Args) << endl;
    }
    
    void test()
    {
        foo(1, 2, 3, 4);
        foo(1, "a", vector<int>());
        foo( 1 );
    }
    

输出参数包中的所有参数

  • code

    // 只有一个参数的时候会调用这个函数
    template<typename T>
    ostream& print(ostream& os, const T& t)
    {
        os << t;
        return os;
    }
    
    template<typename T, typename... Args>
    ostream& print(ostream& os, const T& t, const Args& ...args)
    {
        os << t << ", ";
        return print( os, args... );
    }
    
    void test()
    {
        int a = 0, b = 1;
        string s1("test1"), s2("test2");
        print(cout, a, b, s1, s2);
        cout << endl;
    }
    

函数模板特例化

  • 函数模板的一个特例化版本就是模板的一个独立的定义。
  • code

    template<typename T>
    int cmp(const T& t1, const T& t2)
    {
        cout << typeid(t1).name() << endl;
        if (t1 < t2)
            return -1;
        else if (t1 == t2)
            return 0;
        else
            return 1;
    }
    
    // 接受不同长度的字符串常量的比较
    // 如果定义不同长度的数组,没有定义这个函数的话,则无法对其进行比较
    // char[4]与char[5]是不同的变量类型
    template<size_t N, size_t M>
    int cmp(const char (&s1)[N], const char (&s2)[M])
    {
        cout << typeid(s1).name() << ", " << typeid(s2).name() << endl;
        return strcmp( s1, s2 );
    }
    
    void test()
    {
        string s1("aaa"), s2("bbb");
        cout << cmp( s1, s2 ) << endl;
        char str1[] = "123";
        char str2[] = "4567";
        cout << cmp(str1, str2) << endl;
    
        cout << cmp("123", "4567") << endl; // 默认会被识别为字符数组而非指针
    }
    

猜你喜欢

转载自blog.csdn.net/u012526003/article/details/80250387