C++——模板,template

函数模板

我们经常会遇到一种情况:用相同的方法处理不同的数据。对于是函数,我们可以用函数重载来解决。虽然重载可以解决这种情况,但还是很繁琐。如果函数重载10次,有一天你突然发现有新的需求,函数需要修改,那你只能把这10个函数依次修改了,麻烦的要死!所以函数模板他来了。

函数模板代表了一类函数,函数模板与参数类型无关,在使用时被实例化,根据参数类型产生特定类型的函数版本。


函数模板格式

template<typename T1, ... ,typename Tn>

函数返回值 函数名 (参数列表)   

{

        函数体;

}

注:typename可以用class替换,二者基本没有区别

        T1 ... Tn表示参数名,可以自己随便命名

Swap函数模板:

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

函数模板原理

函数模板不是函数,在编译器编译阶段,编译器会根据传入的实参类型推演生成对应的函数以供调用。所以我们调用的不是模板函数而是由模板函数生成的函数。


函数模板实例化

当我们使用函数模板时,生成函数供我们使用,这个过程就是函数的实例化。函数实例化分两种方式:隐式实例化和显式实例化。

隐式实例化

编译器自己推演模板参数类型

template<typename T>
T Add(T& a, T& b)
{
    return a + b;
}

int main()
{
    int a = 5;
    int b = 10;
    cout << Add(a, b) << endl;//15
}

显式实例化

如果加法函数的两个参数是不同的类型,隐式实例化会报错,因为模板参数T只能是一个类型。

解决这种情况有三种办法:

方法一:强制类型转换

template<typename T>
T Add(T& a, T& b)
{
    return a + b;
}

int main()
{
    int a = 5;
    double b = 10;
    cout << Add((double)a, b) << endl;
    cout << Add(a, (int)b) << endl;
}

方法二:使用两个模板参数

template<typename T1, typename T2>
T2 Add(T1 a, T2 b)
{
    return a + b;
}

int main()
{
    int a = 5;
    double b = 10;
    cout << Add(a, b) << endl;
}

 方法三:显式实例化

在函数名后<>指定模板参数的类型

template<typename T>
T Add(T a, T b)
{
    return a + b;
}

int main()
{
    int a = 5;
    double b = 10;
    cout << Add<int>(a, b) << endl;
    cout << Add<double>(a, b) << endl;
}

如果指定类型与实际类型不匹配,编译器会自动进行类型转换,无法转换就会报错


注意:

  1.  一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函数
  2. 对于非模板函数和同名函数模板,如果其他条件都相同,在调动时会优先调用非模板函数而不会从该模板产生出一个实例。如果模板可以产生一个具有更好匹配的函数, 那么将选择模板

类模板

类模板和函数模板类似

对于C++中用来存储数据的container,例如vector,string,list,stack等等,都是类模板。container可以存储各种不同的数据,不管是自定义类型还是内置类型。我们使用这些container存储具体的数据就是一种类模板的实例化。

//类模板定义
template<class T1,...,Tn>
class classname
{
    ...
}

//类模板实例化

void test()
{
    //type是任意的数据类型
    classname<type> c;
    ...
}

typename和class的区别

二者在大部分情况下没有区别,但是这种情况下会不同:

当模板参数是容器内部定义的类型参数,并且在后面也使用了该参数。那么在使用该参数时必须加typename前缀

template<class T, class container=vector<T>>
//在后面的类中使用了 容器中的类型 的模板参数
class it
{
private:
	container c;
public:
	void print()
	{
        //必须使用typename前缀
		typename container::iterator _it = c.begin();
		while (_it != c.end())
		{
			cout << *_it << ' ';
			_it++;
		}
	}
	void pushback(const T& x)
	{
		c.push_back(x);
	}
};
在模板中使用了 容器中的类型 的模板参数

 原因:

当我们用::使用容器中的类型时,编译器无法判断它是不是一个类型,它也可能是容器中的一个静态成员,typename就是告诉编译器这是一个类型,编译才可以过。


非类型模板参数

在模板中有两种参数,类型形参和非类型形参。模板中的非类型参数可以当作常量使用,这种参数多用于静态容器,即不需要扩容,使用时指定容器大小的情况。

但是有几点要注意:

1.非类型参数必须是整形,浮点数和类对象以及字符串不允许

2.非类型参数必须在编译器就能确定结果

下面是用非类型模板参数模拟实现静态数组的代码:

template<class T, size_t num = 10>
class arr
{
private:
	T _a[num];
	size_t _size;
public:
	T& operator[](size_t index)
	{
		return _a[index];
	}
	const T& operator[](size_t index)const
	{
		return _a[index];
	}
	size_t size()const
	{
		return _size;
	}
	bool empty()const
	{
		return _size == 0;
	}
};

模板的特化

有的时候模板实例化后在大部分情况下是正确的,但是个别情况下无法正常使用或者需求不同,这就需要我们对个别情况下的模板进行特化,即特殊实例化。

函数模板特化(不支持偏特化)

template<class T1, class T2>
T1 add(const T1 x1, const T2 x2)
{
	return x1 + x2;
};
//特化
template<>
int add<int, double>(const int x1, const double x2)
{
	return x1 - x2;
};
void test3()
{
	cout << add(4, 2.1) << endl;
	cout << add(4, 1) << endl;
}

类模板特化 

template <class T1, class T2>
class Data
{
private:
	T1 d1;
	T2 d2;
public:
	void print()
	{
		cout << "Data(T1, T2)" << endl;
	}
};
//全特化
template<>
class Data<int, int>
{
private:
	int d1;
	int d2;
public:
	void print()
	{
		cout << "Data(int, int)" << endl;
	}
};
//偏特化,对部分模板参数特化
template<class T2>
class Data<int, T2>
{
private:
	int d1;
	T2 d2;
public:
	void print()
	{
		cout << "Data(int, T2)" << endl;
	}
};
//偏特化,对模板参数进行进一步限制
template<class T1, class T2>
class Data<T1*, T2>
{
private:
	T1* d1;
	T2 d2;
public:
	void print()
	{
		cout << "Data(T1*, T2)" << endl;
	}
};
void test4()
{
	Data<double, double> d1;
	d1.print();
	Data<int, double> d2;
	d2.print();
	Data<int, int> d3;
	d3.print();
	Data<int*, double> d4;
	d4.print();
}

模板的分离编译

一个程序由许多源文件共同构成,每个源文件分别编译成目标文件,最后链接到一起形成一个可执行程序,这是分离编译模式。

下面的模板分离编译会有什么问题?

com.h
//声明
template <class T>
bool compare(const T& left, const T& right);

com.cpp
//定义
template <class T>
bool compare(const T& left, const T& right)
{
    return left < right;
}

test.cpp
#include"com.h"
int main()
{
	compare(1, 2);
	compare(3, 2);
    return 0;
}

com.cpp编译时,函数未实例化,不会生成compare函数,也就没有函数地址;因为调用了compare<int>函数,test.cpp在链接时要寻址,但是找不到compare函数的地址,就会报错。

解决方法:

将函数声明和定义放到一个xxx.hpp或xxx.h文件中即可。

猜你喜欢

转载自blog.csdn.net/weixin_74269833/article/details/131043315