C++ TGP 模板基础知识--02类模板、变量模板、别名模板

类模板的基本范例和模板参数的推断

类模板是生产类的模具,通过给定的模板参数生成具体的类,也就是实例化一个特定的类,引入类模板的目的是减少代码冗余.类模板中只只有被调用的时候才会实例化该函数

#include 
#include 
#include 

template <typename T>  // T是类型模板参数
class MyVector {  // MyVector可以称为类名或者类模板,MyVector可以称为类型名
  public:
    using myiterator = T*;

  public:
    MyVector();
    MyVector& operator=(const MyVector&);  // 赋值运算符重载

    void myfunc()
    {
        std::cout << "called myfunc" << std::endl;
    }

    static void myfuncs()
    {
        std::cout << "called myfuns" << std::endl;
    }

    myiterator mybegin();  // 迭代器的起始位置
    myiterator myend();  // 迭代器的结束位置
};

// 类模板实现
template <typename T>
MyVector<T>::MyVector()  // 类外构造函数的实现
{
    std::cout << "MyVector Constructed" << std::endl;
}

int main(int argc, char** argv)
{
    MyVector<int> vec;
    vec.myfunc();
    MyVector<std::string>::myfuncs(); 
    return 0;
}

模板参数推断

MyVector(T val)
{
    std::cout << "type t:" << typeid(val).name() << std::endl;
}

MyVector vec(10);// 无需指定模板参数
vec.myfunc();

模板参数推断指南

隐式推断指南

针对类模板的每个构造函数都有一个隐式的模板参数推断机制存在,称为隐式推断指南

// template
// A(T, T) -> A
// 表达式出现->左侧部分内容或者形式时,请推断成->右侧的类型。右侧类型也被称为“指南类型”
//->左侧部分:该推断指南所对应的构造函数的函数声明,多个参数之间用,分割
//->右侧部分:类模板名,接着一个尖括号,尖括号中时模板参数名
//整个推断指南的含义:当调用带2个参数的构造函数通过类模板A创建相关对象时,请用所提供的构造函数的实参来推断类模板A的模板参数类型

#include 
#include 
#include 

template <typename T>
class A {
  public:
    A(T val1)
    {
        std::cout << "A(T val1) construct called"
                  << " type t:" << typeid(val1).name() << std::endl;
    }

    A(T val1, T val2)
    {
        std::cout << "A(T val1, T val2) construct called"
                  << " type t:" << typeid(val1).name() << std::endl;
    }
};

int main(int argc, char** argv)
{
    A obj1(12, 15);  // A
    A obj2(12.2);  // A
    return 0;
}

类模板的各种特化

一般来讲所写的类模板都是泛化的类模板。而特化的类模板是通过泛化的类模板生成的,因此先要有泛化版本,才有特化版本

类模板的全特化

全特化就是把A泛化版本中的所有模板参数都用具体的类型来代替构成的一个特殊的版本(全特化版本)

#include 
#include 
#include 

template <typename T, typename U>
class A {
  public:
    A()
    {
        std::cout << "A 泛化版本构造函数执行了" << std::endl;
    }
    void func()
    {
        std::cout << "A 泛化版本func() 执行了" << std::endl;
    }
};

// 全特化
template <>  // 所有类型模板参数都由具体的类型代表,所以<>里空了
class A<int, int> {
  public:
    A()
    {
        std::cout << "A 全特化版本构造函数执行了" << std::endl;
    }
    // 类中实现
    void func()
    {
        std::cout << "A 全特化版本func()执行了" << std::endl;
    }
    void func2()
};

// 类外实现
void A<int, int>::func2()
{
    std::cout << "A 全特化版本func2()执行了" << std::endl;
}

int main(int argc, char** argv)
{
    // 泛化
    A<int, float> obj1;
    obj1.func();
    // 全特化
    A<int, int> obj2;
    obj2.func();
    obj2.func2();
    return 0;
}

普通成员函数的全特化

#include 
#include 
#include 

template <typename T, typename U>
class A {
  public:
    A()
    {
        std::cout << "A 泛化版本构造函数执行了" << std::endl;
    }
    void func()
    {
        std::cout << "A 泛化版本func() 执行了" << std::endl;
    }
};

// 类A成员函数void func()的全特化
template <>
void A<int, float>::func()
{
    std::cout << "A普通成员函数void func()的全特化版本 A::func() 执行了" << std::endl;
}

int main(int argc, char** argv)
{
    A<int, float> obj1;
    obj1.func();
    return 0;
}

静态成员变量的全特化

#include 
#include 
#include 

template <typename T, typename U>
class A {
  public:
    A()
    {
        std::cout << "A 泛化版本构造函数执行了" << std::endl;
    }
    void func()
    {
        std::cout << "A 泛化版本func() 执行了" << std::endl;
    }

    static int m_val;  // 声明一个静态成员变量
};
// 静态成员变量定义
template <typename T, typename U>
int A<T, U>::m_val = 50;

// 静态成员变量的全特化
template <>
int A<double, int>::m_val = 100;

int main(int argc, char** argv)
{
    A<int, double> obj1;
    std::cout << "m_val:" << obj1.m_val << std::endl;

    A<double, int> obj2;
    std::cout << "m_val:" << obj2.m_val << std::endl;

    return 0;
}

注意:如果进行了普通成员函数的全特化或者是静态成员变量的全特化,那么就无法使用这些全特化指定类型来对整个类模板进行全特化了。下例失败

// ERROR 类模板全特化
template<>
class A<double, int>
{
    A()
    {
        std::cout << "A 全特化版本构造函数执行了" << std::endl;
    }
}

类模板的偏特化(局部特化)

模板参数数量上的偏特化
#include 
#include 
#include 

template <typename T, typename U>
class A {
  public:
    A()
    {
        std::cout << "A 泛化版本构造函数执行了" << std::endl;
    }
    void func()
    {
        std::cout << "A 泛化版本func() 执行了" << std::endl;
    }
};

// 偏特化
template <typename U>
class A<double, U> {
  public:
    A()
    {
        std::cout << "A 偏特化版本构造函数执行了" << std::endl;
    }
    void func();
};

template <typename U>
void A<double, U>::func()
{
    std::cout << "A 偏特化版本func() 执行了" << std::endl;
}

int main(int argc, char** argv)
{
    // 泛化
    A<int, double> obj1;
    obj1.func();
    // 偏特化
    A<double, int> obj2;
    obj2.func();
    return 0;
}
模板参数范围上的偏特化

int -> const int, T -> T*, T -> T&, T -> T&&

#include 
#include 
#include 

template <typename T, typename U>
class A {
  public:
    A()
    {
        std::cout << "A 泛化版本构造函数执行了" << std::endl;
    }
    void func()
    {
        std::cout << "A 泛化版本func() 执行了" << std::endl;
    }
};

// 范围偏特化
template <typename T, typename U>
class A<const T, U*> {
  public:
    A()
    {
        std::cout << "A 偏特化版本构造函数执行了" << std::endl;
    }
    void func();
};

template <typename T, typename U>
void A<const T, U*>::func()
{
    std::cout << "A 偏特化版本func() 执行了" << std::endl;
}

int main(int argc, char** argv)
{
    // 泛化
    A<int, double> obj1;
    obj1.func();
    // 偏特化
    A<const double, int*> obj2;
    obj2.func();
    return 0;
}

缺省参数

常规缺省参数

类型模板参数缺省值的规则:如果某个模板参数有缺省值,那么从这个缺省值的模板参数开始, 后面的所有模板参数都得有缺省值。类模板偏特化版本中的类型模板参数不能有缺省值

#include 
#include 
#include 
// template 
template <typename T = int, typename U = int>  // 指定缺省类型
class A {
  public:
    A()
    {
        std::cout << "A 泛化版本构造函数执行了" << std::endl;
    }

    void func()
    {
        std::cout << "A 泛化版本func() 执行了" << std::endl;
    }
};

int main(int argc, char** argv)
{
    A obj1;
    obj1.func();

    A<>       obj2;  // 使用了缺省模板参数,所以<>中啥也不提供
    A<double> obj3;  //<>中第一个类型参数不使用缺省参数,第二个类型使用缺省参数
    return 0;
}

后面的模板参数依赖前面的模板参数

#include 
#include 
#include 
template <typename T, typename U = T*>  // 后面的模板参数U依赖前面的模板参数T
class A {
  public:
    A()
    {
        std::cout << "A 泛化版本构造函数执行了" << std::endl;
    }

    void func()
    {
        std::cout << "A 泛化版本func() 执行了" << std::endl;
    }
};

int main(int argc, char** argv)
{
    A<double> obj1;
    obj1.func();

    return 0;
}

在模板声明中指定缺省参数

#include 
#include 
#include 

// 声明1 指定V,W的缺省参数
template <typename T, typename U, typename V = int, typename W = char>
class A;

// 声明2 指定U缺省值,相当于U、V、W都指定了缺省参数
template <typename T, typename U = char, typename V, typename W>
class A;

// 定义泛化版本
template <typename T, typename U, typename V, typename W>
class A {
  public:
    A()
    {
        std::cout << "A 泛化版本构造函数执行了" << std::endl;
    }

    void func()
    {
        std::cout << "A 泛化版本func() 执行了" << std::endl;
    }
};

int main(int argc, char** argv)
{
    A<double> obj1;  // 第2、3、4个模板参数采用缺省值,所以<>里只提供了一个类型模板实参
    obj1.func();

    return 0;
}

类型别名

  • typedef
    • typedef A INT_D_A;
  • using
    • using INT_D_A = A

非类型模板参数

#include 
#include 

// ArraySize
template <typename T, typename U, size_t ArraySize = 8>
class A {
  public:
    A()
    {
        std::cout << "A 泛化版本构造函数执行了,array_size:" << ArraySize << std::endl;
    }
    void func();

  private:
    T m_arr[ArraySize];  // ArraySize在编译器编译期间确定
};
// 非类型模板参数,类的成员函数类外实现
template <typename T, typename U, size_t ArraySize>
void A<T, U, ArraySize>::func()
{
    std::cout << "A 泛化版本A::func执行了" << std::endl;
}

int main(int argc, char** argv)
{
    A<double, int> obj1;  // 缺省的第三个模板参数是8
    obj1.func();

    A<double, int, 10> obj2;
    obj2.func();
    return 0;
}

成员函数模板

基本概念、构造函数模板

  • 类模板中的成员函数,只有源程序代码中出现调用这些成员函数代码时,这些成员函数才会出现在一个实例化了的类模板中
  • 类模板中的成员函数模板,只有源程序代码中出现调用这些成员函数模板的代码时,这些成员函数模板的具体实例才会出现在一个实例化了的类中
  • 目前编译器并不支持虚成员函数模板,因为虚函数表vtbl的大小是固定的。如果允许虚函数模板,则每次使用新的模板参数类型调用该虚函数模板时,就必须给对应的虚函数表在增加一项,这意味着只有在链接程序的时候才能去构造虚函数表,并在表中设置相关函数,因此成员函数模板绝不能为虚的
    • 类模板中可以有普通的虚成员函数(虚函数),因为普通成员函数如果不被调用时是不会被实例化的。但是对于虚函数,不管是否调用,编译器都会把它实例化出来。因为编译器要创建虚函数表vtbl,该表中的每个具体表项都对应一个虚函数地址
#include 
#include 

template <typename T1>
class A {
  public:
    A()
    {
        std::cout << "A 泛化版本构造函数执行了" << std::endl;
    }

    template <typename T2>
    A(T2 v1, T2 v2);  // 构造函数模板,引入了自己的模板参数T2

    A(double v1, double v2)  // 构造函数
    {
        std::cout << " A(double, double)构造函数执行了" << std::endl;
    }

    A(T1 v1, T1 v2)
    {
        std::cout << " A(T1, T1)构造函数执行了" << std::endl;
    }

    template <typename T3>
    void func(T3 val)  // 普通成员函数模板
    {
        std::cout << "A func va;:" << val << std::endl;
    }

  private:
    T1                   m_val;
    static constexpr int m_static_val = 200;
};

// 类外实现类模板的构造函数模板
template <typename T1>  // 类模板
template <typename T2>  // 成员函数模板
A<T1>::A(T2 val1, T2 val2)
{
    std::cout << "A::A(T2, T2) 构造函数执行了, type T2:" << typeid(val2).name()
              << " type m_val:" << typeid(m_val).name() << std::endl;
}

int main(int argc, char** argv)
{
    A<float> obj1(1, 2);  // 实例化了A这样一个类型,并用int类型来实例化构造函数
    A<float> obj2(1.2, 2.3);  // A上面已经实例化了,并用double类型来实例化构造函数
    A<float> obj3(1.2f, 2.3f);  // 用float类型来实例化构造函数
    return 0;
}

拷贝构造函数模板与拷贝赋值运算符模板

拷贝构造函数模板不是拷贝构造函数,拷贝赋值运算符模板不是拷贝赋值运算符,构造函数模板也不是构造函数。拷贝构造函数或者拷贝赋值运算符要求拷贝的对象类型完全相同,而拷贝构造函数模板和拷贝赋值运算符模板就没有这种要求。拷贝构造函数模板永远不会成为拷贝构造函数。当**类型不同但 都是类模板实例化出来的两个对象时(如A和A)用一个拷贝构造另一个时,拷贝构造函数模板才会调用

#include 
#include 

template <typename T1>
class A {
  public:
    A()
    {
        std::cout << "A 泛化版本构造函数执行了" << std::endl;
    }
    // 拷贝构造函数模板
    template <typename U>
    A(const A<U>& other)
    {
        std::cout << "A::A(const A& other)构造函数执行了" << std::endl;
    }
    // 拷贝赋值运算符模板
    template <typename U>
    A<T1>& operator=(const A<U>& other)
    {
        std::cout << "A &operator=(const A &other)构造函数执行了" << std::endl;
        return *this;
    }

    template <typename T3>
    void func(T3 val)  // 普通成员函数模板
    {
        std::cout << "A func va;:" << val << std::endl;
    }

  private:
    T1 m_val;
};

int main(int argc, char** argv)
{
    A<double> obj1;
    A<double> obj2(obj1);  // 未执行拷贝构造函数模板的代码,因为obj1、obj2类型相同,
                          // 本该执行拷贝构造函数,但是因为类模板中没有拷贝构造函数,所以编译器内部实际是执行了按值拷贝的一个操作
                          // 拷贝构造函数模板永远不会称为拷贝构造函数,编译器不会调用拷贝构造函数模板来代替拷贝构造函数
    A<int>   obj3(obj1); // obj1:A obj3:A,两者类型不同

    A<int>   obj4 = obj1;
    return 0;
}

特化

#include 
#include 

template <typename T1>
class A {
  public:
    A()
    {
        std::cout << "A 泛化版本构造函数执行了" << std::endl;
    }
    // 拷贝构造函数模板
    template <typename U>
    A(const A<U>& other)
    {
        std::cout << "A::A(const A& other)构造函数执行了" << std::endl;
    }
    // 拷贝赋值运算符模板
    template <typename U>
    A<T1>& operator=(const A<U>& other)
    {
        std::cout << "A &operator=(const A &other)构造函数执行了" << std::endl;
        return *this;
    }

    template <typename T3, typename T4>
    void func(T3 val1, T4 val2)  // 普通成员函数模板
    {
        std::cout << "A func(T3, T4) 泛化版本"
                  << "type val1:" << typeid(val1).name() << "type val2:" << typeid(val1).name() << std::endl;
    }
    // 成员函数 偏特化
    template <typename T4>
    void func(int val1, T4 val2)
    {
        std::cout << "A func(int, T4) 偏特化版本"
                  << "type val1:" << typeid(val1).name() << "type val2:" << typeid(val1).name() << std::endl;
    }
    // 成员函数全特化
    // template <>
    // void func(int val1, double val2)
    // {
    //     std::cout << "A func(int, double)全特化版本"
    //               << "type val1:" << typeid(val1).name() << "type val2:" << typeid(val1).name() << std::endl;
    // }

  private:
    T1 m_val;
};

int main(int argc, char** argv)
{
    A<double> obj1;

    return 0;
}

变量模板与成员变量模板

变量模板

// 变量模板泛化
template <typename T>
T g_var{};

// g_var = 12.0;
// g_var = 10;

变量模板的特化

// 变量模板全特化
template <>
char g_var<double>{};  // 变量模板特化时不需要正在特化的类型(double)与这个变量模板的类型(char)保持一致
 g_var<double> = 'a';

// 变量模板偏特化
template <typename T>
T g_var<T*>{120}; // T*依赖T

std::cout << g_var<int *> << std::endl;

默认模板参数

template <typename T = int>
T g_var;

非类型模板参数

// 非类型模板参数 value
template <typename T, int value>
T g_var[value];

for (int i = 0; i < 10; ++i) {
    g_var<int, 10>[i] = i;
}

for (int i = 0; i < 10; ++i) {
    std::cout << g_var<int, 10>[i] << std::endl;
}

变量模板的另一种形式

template <typename T>
struct B
{
    constexpr static T value = 160;
};

template <typename T>
int g_val = B<T>::value;

std::cout << g_val<int> << std::endl; // 160
g_val<int> = 120;
std::cout << g_val<int> << std::endl; // 120

成员变量模板

template <typename T>
class D {
  public:
    template <typename W>
    static W m_tpi;  // 静态成员变量模板声明
};

template <typename T>
template <typename W>
W D<T>::m_tpi = 5;

int main(int argc, char** argv)
{
    std::cout << D<float>::m_tpi<int> << std::endl;
    D<float>::m_tpi<int> = 150;
    std::cout << D<float>::m_tpi<int> << std::endl;

    return 0;
}

别名模板与成员别名模板

// 别名模板
template <typename T>
using str_map_t = std::map<std::string, T>;

template <typename T>
class E {
    // 成员别名模板
    template <typename T>
    using str_map_t = std::map<std::string, T>;

};

你可能感兴趣的:(C/C++,c++,开发语言)