1. 简介

我们对传递数值或变量给函数都很熟悉,除了传递变量,我们还能传递类型给模板。传递类型就是大家所熟知的泛型编程,因为 我们可以用泛型编写程序,而用特定的类型调用。

泛型编程的目的是为了编写的程序不依赖于数据类型。在 C 语言中,所有的代码都需要绑定到确定的数据类型,这样写的代码只能对特定的数据类型起作用。 而模板可以让我们实现泛型编程。你可以将类型作为参数来构建模板函数和类模板。当你的算法需要作用于多种数据类型的时候,模板就显得及其有用了。

C++的标准模板库(STL)提供了一些常用的容器类模板的实现,例如vector,可以用来存放所有类型的元素。

2. 示例:STL 中的 vector 类模板

C/C++中的内置数组有一些缺点:

  1. 它的大小是固定的,需要在声明的时候确定大小,不支持动态声明。你不能在执行期给数组扩容;
  2. 数组不提供下标边界校验,你可以使用超出边界的下标
  3. 你需要自己实现数组比较,和赋值操作

C++提供了一个vector类模板,作为标准模板库(STL)的一部分。vector被定义在<vector>头文件中,属于std命名空间。vector 是最常用的 STL 类,它能够取代数组,并且支持动态分配空间和一些其它操作(例如比较和赋值)。

vector 是一个类模板,它可以被特定类型的实例化,形如:vector<int>, vector<double>, vector<string>。同一个模板能够用于多种类型,而不必为每种类型都写一套实现。

#include <iostream>
#include <vector>
#include <string>

using namespace std;

void print(const vector<int> &v);

int main(int argc, char *argv[]) {
	vector<int> v1(5); // Create a vector with 5 elements.

	// Assign values into v1, using array-like index []
	// You can retrieve the size of vector via size()
	for (int i = 0; i < v1.size(); i++) {
		v1[i] = (i + 1) * 2;
	}

	// Print vector content, using at()
	for (int i = 0; i < v1.size(); i++) {
		cout << v1.at(i) << " ";
	}
	cout << endl;

	vector<int> v2;
	// Assign v1 to v2 memberwise
	v2 = v1;
	for (int i = 0; i < v2.size(); i++) {
		cout << v2[i] << " ";
	}
	cout << endl;

	// Compare 2 vectors memberwise
	cout << boolalpha << (v1 == v2) << endl;

	// Append more elements - synamically allocate memory
	v1.push_back(80);
	v1.push_back(81);
	for (int i = 0; i < v1.size(); i++) {
		cout << v1[i] << " ";
	}
	cout << endl;

	vector<string> v3;
	v3.push_back("a for apple");
	v3.push_back("b for boy");
	for (int i = 0; i < v3.size(); i++) {
		cout << v3[i] << " ";
	}

	cout << endl;

	return 0;
}

说明:

  • 你可以通过声明vector<int> v1(n)来初始化一个int类型的vector,其中n表示初始化的元素个数
  • 可以使用v1.size()来获取元素个数
  • 可以使用v1[i]v1.at(i)来访问元素,但是[]操作符不会做边界检查,而at()
  • 使用push_back()pop_back()添加和删除元素。vector会自动调整内存分配。

3. 函数模板

把处理不同类型的公共逻辑抽象成函数,就得到了函数模板。

定义函数模板的定义语法如下:

template <typename T> OR template <class T>
return-type function-name(function-parameter-list) { ...... }

Example 1

#include <iostream>

using namespace std;

template <typename T>
void mySwap(T &a, T &b);

int main(int argc, char *argv[]) {
	int i1 = 1, i2 = 2;
	mySwap(i1, i2);
	cout << "i1 is " << i1 << ", i2 is " << i2 << endl;

	char c1 = 'a', c2 = 'b';
	mySwap(c1, c2);
	cout << "c1 is " << c1 << ", c2 is " << c2 << endl;

	double d1 = 1.1, d2 = 2.2;
	mySwap(d1, d2);
	cout << "d1 is " << d1 << ", d2 is " << d2 << endl;

	return 0;
}

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

C++编译器会为每种使用的类型都生成一个对应的函数,例如int型:

void mySwap(int &a, int &b) {
	int temp;
	temp = a;
	a = b;
	b = temp;
}

这样并不能为代码的执行效率和内存使用率带来提升,但是能够大大提高开发效率。

Example 2

#include <iostream>

using namespace std;

template<typename T>
T abs(T value) {
	T result;
	result = (value >= 0) ? value : -value;
	return result;
}

int main(int argc, char *argv[]) {
	int i = -5;
	cout << abs(i) << endl;

	double d = - 55.5;
	cout << abs(d) << endl;

	float f = -555.5f;
	cout << abs(f) << endl;

	return 0;
}

函数模板重载

#include <iostream>

using namespace std;

template<typename T>
void mySwap(T &a, T &b);

template<typename T>
void mySwap(T a[], T b[], int size);

template<typename T>
void print(const T *const array, int size);

int main(int argc, char *argv[]) {
	int i1 = 1, i2 = 2;
	mySwap(i1, i2);
	cout << "i1 is " << i1 << ", i2 is " << i2 << endl;

	const int SIZE = 3;
	int arr1[] = {1, 2, 3}, arr2[] = {4, 5, 6};
	mySwap(arr1, arr2, SIZE);

	print(arr1, SIZE);
	print(arr2, SIZE);
	return 0;
}


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

template<typename T>
void mySwap(T a[], T b[], int size) {
	T temp;
	for (int i = 0; i < size; i++) {
		temp = a[i];
		a[i] = b[i];
		b[i] = temp;
	}
}

template<typename T>
void print(const T *const array, int size) {
	cout << "(";
	for (int i = 0; i < size; i++) {
		cout << array[i];
		if (i < size - 1) cout << ",";
	}
	cout << ")" << endl;
}

显式特化

#include <iostream>

using namespace std;

template<typename T>
void mySwap(T &a, T &b);

template<>
void mySwap<int>(int &a, int &b);

int main(int argc, char *argv[]) {
	double d1 = 1, d2 = 2;
	mySwap(d1, d2);

	int i1 = 1, i2 = 2;
	mySwap(i1, i2);

	return 0;
}


template<typename T>
void mySwap(T &a, T &b) {
	cout << "template" << endl;
	T temp;
	temp = a;
	a = b;
	b = temp;
}

template<>
void mySwap<int>(int &a, int &b) {
	cout << "specilization" << endl;
	int temp;
	temp = a;
	a = b;
	b = temp;
}

4. 类模板

定义一个类模板的语法如下:

template<class T>
class ClassName {
	......
}

关键字’class’和’typename’都是用来定义模板的。使用定义好的模板的语法是:ClassName<actual-type>

例如:

#include <iostream>
using namespace std;

template <typename T>
class Number {
private:
	T value;
public:
	Number(T value) { this->value = value; }
	T getValue() const { return this->value; }
	void setValue(T value) { this->value = value; }
};

int main(int argc, char *argv[]) {
	Number<int> i(55);
	cout << i.getValue() << endl;

	Number<double> d(55.66);
	cout <<d.getValue() << endl;

	Number<string> s("hello");
	cout << s.getValue() << endl;
	return 0;
}

将模板声明和定义分开

如果将函数实现和声明分开,就需要在每个函数实现上都使用"template"关键字,例如:

template<typename T>
T Number<T>::getValue() {
	return value;
}

将所有模板代码都放在头文件中

多参数类型

template<typename T1, typename T2, ...>
class ClassName { ...... }

默认类型

template<typename T = int>
class ClassName { ...... }

特化

// General Template
template<typename T>
class Complex { ...... }

// Specialization for type double
template<>
class Complex<double> { ...... }

// Specialization for type int
template<>
class Complex<int> { ...... }

5. 示例:MyComplex Template Class

MyComplex.h

/*
 * The MyComplex template class header (MyComplex.h)
 * All template codes are kept in the header, to be included in program
 * (Follow, modified and simplified from GNU GCC complex template class.)
 */
#ifndef MY_COMPLEX_H
#define MY_COMPLEX_H

#include <iostream>

// Forward declaration
template <typename T> class MyComplex;

template <typename T>
std::ostream & operator<< (std::ostream & out, const MyComplex<T> & c);
template <typename T>
std::istream & operator>> (std::istream & in, MyComplex<T> & c);

// MyComplex template class declaration
template <typename T>
class MyComplex {
private:
   T real, imag;

public:
   // Constructor
   explicit MyComplex<T> (T real = 0, T imag = 0)
         : real(real), imag(imag) { }

   // Overload += operator for c1 += c2
   MyComplex<T> & operator+= (const MyComplex<T> & rhs) {
      real += rhs.real;
      imag += rhs.imag;
      return *this;
   }

   // Overload += operator for c1 += value
   MyComplex<T> & operator+= (T value) {
      real += value;
      return *this;
   }

   // Overload comparison == operator for c1 == c2
   bool operator== (const MyComplex<T> & rhs) const {
      return (real == rhs.real && imag == rhs.imag);
   }

   // Overload comparison != operator for c1 != c2
   bool operator!= (const MyComplex<T> & rhs) const {
      return !(*this == rhs);
   }

   // Overload prefix increment operator ++c
   // (Separate implementation for illustration)
   MyComplex<T> & operator++ ();

   // Overload postfix increment operator c++
   const MyComplex<T> operator++ (int dummy);

   /* friends */

   // (Separate implementation for illustration)
   friend std::ostream & operator<< <>(std::ostream & out, const MyComplex<T> & c); // out << c
   friend std::istream & operator>> <>(std::istream & in, MyComplex<T> & c);        // in >> c

   // Overloading + operator for c1 + c2
   // (inline implementation for illustration)
   friend const MyComplex<T> operator+ (const MyComplex<T> & lhs, const MyComplex<T> & rhs) {
      MyComplex<T> result(lhs);
      result += rhs;  // uses overload +=
      return result;
   }

   // Overloading + operator for c + double
   friend const MyComplex<T> operator+ (const MyComplex<T> & lhs, T value) {
      MyComplex<T> result(lhs);
      result += value;  // uses overload +=
      return result;
   }

   // Overloading + operator for double + c
   friend const MyComplex<T> operator+ (T value, const MyComplex<T> & rhs) {
      return rhs + value;   // swap and use above function
   }
};

// Overload prefix increment operator ++c
template <typename T>
MyComplex<T> & MyComplex<T>::operator++ () {
  ++real;   // increment real part only
  return *this;
}

// Overload postfix increment operator c++
template <typename T>
const MyComplex<T> MyComplex<T>::operator++ (int dummy) {
   MyComplex<T> saved(*this);
   ++real;  // increment real part only
   return saved;
}

/* Definition of friend functions */

// Overload stream insertion operator out << c (friend)
template <typename T>
std::ostream & operator<< (std::ostream & out, const MyComplex<T> & c) {
   out << '(' << c.real << ',' << c.imag << ')';
   return out;
}

// Overload stream extraction operator in >> c (friend)
template <typename T>
std::istream & operator>> (std::istream & in, MyComplex<T> & c) {
   T inReal, inImag;
   char inChar;
   bool validInput = false;
   // Input shall be in the format "(real,imag)"
   in >> inChar;
   if (inChar == '(') {
      in >> inReal >> inChar;
      if (inChar == ',') {
         in >> inImag >> inChar;
         if (inChar == ')') {
            c = MyComplex<T>(inReal, inImag);
            validInput = true;
         }
      }
   }
   if (!validInput) in.setstate(std::ios_base::failbit);
   return in;
}

#endif

TestMyComplex.cpp

/* Test Driver for MyComplex template class (TestMyComplex.cpp) */
#include <iostream>
#include <iomanip>
#include "MyComplex.h"

int main() {
   std::cout << std::fixed << std::setprecision(2);

   MyComplex<double> c1(3.1, 4.2);
   std::cout << c1 << std::endl;  // (3.10,4.20)
   MyComplex<double> c2(3.1);
   std::cout << c2 << std::endl;  // (3.10,0.00)

   MyComplex<double> c3 = c1 + c2;
   std::cout << c3 << std::endl;  // (6.20,4.20)
   c3 = c1 + 2.1;
   std::cout << c3 << std::endl;  // (5.20,4.20)
   c3 = 2.2 + c1;
   std::cout << c3 << std::endl;  // (5.30,4.20)

   c3 += c1;
   std::cout << c3 << std::endl;  // (8.40,8.40)
   c3 += 2.3;
   std::cout << c3 << std::endl;  // (10.70,8.40)

   std::cout << ++c3 << std::endl; // (11.70,8.40)
   std::cout << c3++ << std::endl; // (11.70,8.40)
   std::cout << c3   << std::endl; // (12.70,8.40)

// c1+c2 = c3;  // error: c1+c2 returns a const
// c1++++;      // error: c1++ returns a const

// MyComplex<int> c4 = 5;  // error: implicit conversion disabled
   MyComplex<int> c4 = (MyComplex<int>)5;  // explicit type casting allowed
   std::cout << c4 << std::endl; // (5,0)

   MyComplex<int> c5;
   std::cout << "Enter a complex number in (real,imag): ";
   std::cin >> c5;
   if (std::cin.good()) {
      std::cout << c5 << std::endl;
   } else {
      std::cerr << "Invalid input" << std::endl;
   }
   return 0;
}