Skip to content

Latest commit

 

History

History
380 lines (324 loc) · 8.26 KB

l_16.md

File metadata and controls

380 lines (324 loc) · 8.26 KB

Lesson15 模板初级



1. 模板的基本概念

函数模板

函数模板是通用的函数描述,使用任意类型(泛型)来描述函数。

编译的时候,编译器推导实参的数据类型,根据实参的数据类型和函数模板,生成该类型的函数定义。
生成函数定义的过程被称为 实例化

创建交换两个变量的函数模板:

template <typename T>
void Swap(T& a, T& b) {
	T tmp = a;
	a = b;
	b = tmp;
}

在C++98添加关键字 typename 之前,C++使用关键字 class 来创建模板。
如果考虑向后兼容,函数模板应使用 typename ,而不是 class

函数模板实例化可以让编译器自动推导,也可以在调用的代码中显式指定。

int a, b;
double c, d;
swap(a,b);           // 自动类型推导
swap<double>(c,d);   // 显式指定类型

注意

  1. 可以为类的成员函数创建模板,但不能是虚函数和析构函数。
  2. 自动类型推导时,必须推导出一致的数据类型T才可以使用。
  3. 使用函数模板时,必须明确T的数据类型,确保实参与函数模板能匹配上。
  4. 使用函数模板时,推导的数据类型必须适应函数模板中的代码。
  5. 使用函数模板时,如果是自动类型推导,不会发生隐式类型转换,如果显式指定了函数模板的数据类型,可以发生隐式类型转换。
  6. 函数模板支持多个通用数据类型的参数。
  7. 函数模板支持重载,可以有非通用数据类型的参数。

example

notice 2

template <typename T>
void swap(T& a, T& b) {
   T tmp = a;
   a = b;
   b = tmp;
}

void test() {
   int a = 0;
   char c = 'c';
   swap(a, c);       // error
}

notice 3

template <typename T>
void func() {
   std::cout << "func" << std::endl;
}

void test1() {
   func();           // error 不能确定T的类型
}

void test2() {
   func<int>();      // ok 显式指定T类型
}

类模板

类模板是通用类的描述,使用任意类型(泛型)来描述类的定义。
使用类模板的时候,指定具体的数据类型,让编译器生成该类型的类定义。

语法

template <class T>
class Name
{
	...
};

函数模板建议用 typename 描述通用数据类型,类模板建议用 class

注意

  1. 在创建对象的时候,必须指明具体的数据类型,不可以使用自动类型推导
  2. 使用类模板时,数据类型必须适应类模板中的代码。
  3. 模板类的成员函数可以在类外实现。
  4. 可以用new创建模板类对象。
  5. 在程序中,模板类的成员函数使用了才会创建。

示例

template <class T1, class T2>
class A {
 public:
	T1 m_a;
	T2 m_b;
          
	A() {}
	AA(T1 a, T2 b) 
	  : m_a(a), m_b(b) {}

	T1 a() {           
	  T1 a = 2;        
	  return m_a + a;
	}

	T2 b();           
};

template <class T1, class T2>
T2 A<T1,T2>::b() {
  return m_b;
}
            
int main()
{
	A<int, std::string>* a = new AA<int, std::string>(3, "hello"); 
	
	std::cout << "a->a()=" << a->a() << std::endl;
	std::cout << "a->b()=" << a->b() << std::endl;
         
	delete a;
}

2. 模板参数

模板参数允许是抽象数据类型

此时实例化模板时 必须指明具体的数据类型

class A {
  public:
   A() {}
   A(int a)
       : _a(a) {}
   int _a;
};

template <typename T>
void func() {
   T a(1);
   std::cout << a._a << std::endl;
}

template <class T>
class B {
  public:
   T obj;
   B() {
      obj._a = 1;
      std::cout << obj._a << std::endl;
   }
};

int main() {
   func<A>();
   B<A> b;
}

output

1
1

默认参数

一般在类模板中使用,C++11起支持函数模板使用默认参数,且 不需要遵循 从右向左赋默认值的规则

template <typename T = int>
class A {
 public:
	T a;
};

以下用法只是先告诉你它存在,但是具体怎么用可能以后不会告诉你,等你自己去探索咯

匿名参数

template<typename, typename T>

约束参数

template<typename T, T i, int j>

3. 模板特化

函数模板特化

可以提供一个特化的函数定义,当编译器找到与函数调用匹配的特化定义时,将使用该定义,不再寻找模板。

模板特化时,必须先写 主模板 (primary template) ,再写特化模板

语法

// 主模板
template<> 
void function<Type>(param)
// 模板特化
template<> 
void function(param) {
	...
}

对于给定的函数名,可以有普通函数、函数模板和具体化的函数模板,以及它们的重载版本。

编译器使用各种函数的规则:

  1. 普通函数 > 特化模板 > 常规模板
  2. 如果希望使用函数模板,可以用空模板参数强制使用函数模板
  3. 如果函数模板能产生更好的匹配,将优先于普通函数

示例

template <typename T>
void Swap(T& a, T& b) {
   T tmp = a;
   a = b;
   b = tmp;
   std::cout << "Swap(T& a, T& b)" << std::endl;
}

template <>
void Swap<int>(int& a, int& b) {    // <int>可以省略
   int tmp = a;
   a = b;
   b = tmp;
   std::cout << "Swap<int>(T& a, T& b)" << std::endl;
}

int main() {
   double a = 10, b = 20;
   Swap(a, b);
   std::cout << "a=" << a << ",b=" << b << std::endl;

   int n1 = 10, n2 = 20;
   Swap(n1, n2);
   std::cout << "n1=" << n1 << ",n2=" << n2 << std::endl;
}

output

Swap(T& a, T& b)
a=20,b=10
Swap<int>(T& a, T& b)
n1=20,n2=10
void Swap(int a, int b) {
   std::cout << "普通函数" << std::endl;
}

template <typename T>
void Swap(T a, T b) {
   std::cout << "模板函数" << std::endl;
}

template <>
void Swap<int>(int a, int b) {
   std::cout << "特化函数模板" << std::endl;
}

int main() {
   Swap('c', 'd');
   Swap(1, 2);
   Swap<>(1, 2);
}

output

模板函数
普通函数
特化函数模板

类模板特化

模板类特化分为 完全特化部分特化

调用顺序
特化程度高的类优先于特化程度低的类,特化的类优先于没有特化的类。

// 主模板
template <class T1, class T2>
class AA {
  public:
   T1 m_x;
   T2 m_y;

   AA(const T1 x, const T2 y)
       : m_x(x), m_y(y) {
      std::cout << "primary template constructor" << std::endl;
   }
   void show() const;
};

template <class T1, class T2>
void AA<T1, T2>::show() const {
   std::cout << "primary template\nx = " << m_x << ", y = " << m_y << std::endl;
}

// 完全特化
template <>
class AA<int, double> {
  public:
   int m_x;
   double m_y;

   AA(const int x, const double y)
       : m_x(x), m_y(y) {
      std::cout << "complete specialization constructor" << std::endl;
   }
   void show() const;
};

void AA<int, double>::show() const {
   std::cout << "complete specialization\nx = " << m_x << ", y = " << m_y << std::endl;
}

// 部分特化
template <class T1>
class AA<T1, double> {
  public:
   T1 m_x;
   double m_y;

   AA(const T1 x, const double y)
       : m_x(x), m_y(y) {
      std::cout << "partial specialization constructor" << std::endl;
   }
   void show() const;
};

template <class T1>
void AA<T1, double>::show() const {
   std::cout << "partial specialization\nx = " << m_x << ", y = " << m_y << std::endl;
}

int main() {
   // 特化程度高的类优先于特化程度低的类,特化的类优先于没有特化的类
   AA<int, double> aa1(8, 1.1);
   AA<char, double> aa2(8, 1.1);
   AA<int, double> aa3(8, 'a');
}

output

complete specialization constructor
partial specialization constructor
complete specialization constructor

偏特化

template<typename T> class XXX<T[]>{};

暂时想不到应用场景,先告诉你存在这样的用法吧


edit Serein