C++之函数模板与类模板

Java、C/C++ 专栏收录该内容
14 篇文章 0 订阅

目录

函数模板的引入

其隐式转换

函数模板重载

类模板

引入

定做


函数模板的引入

重载:C++入门之构造函数,通过参数的不同来调用不同的函数体

int& max(int& a, int& b)
{
	return (a < b)? b : a;
}

double& max(double& a, double& b)
{
	return (a < b)? b : a;
}

float& max(float& a, float& b)
{
	return (a < b)? b : a;
}

我们可以通过一个函数模板来实现上面不同的函数,由于头文件有max函数的定义,这里命名为mymax,其中"template<typename T>"同T来表示这个类型,"__PRETTY_FUNCTION__"在std标准中可以打印出函数的具体名称

template<typename T> 
T& mymax(T& a, T& b)
{
	cout<<__PRETTY_FUNCTION__<<endl;
	return (a < b)? b : a;
}

定义int类型的变量

int ia = 1, ib = 2;
mymax(ia, ib);

执行结果如下

T& mymax(T&, T&) [with T = int]

现来多定义float和double型变量,也可以const int变量等

float fa = 1, fb = 2;
double da = 1, db = 2;
mymax(fa, fb);
mymax(da, db);

执行结果如下

T& mymax(T&, T&) [with T = float]
T& mymax(T&, T&) [with T = double]

两个程序相差百来字节,对于函数模板只是编译指令,一般写在头文件中;编译程序时,编译器根据函数的参数来"推导"模板的参数;然后生成具体的函数,例如"mymax(ia, ib)"会生成"int& mymax(int& a, int& b)"函数,参数类型必须完全匹配;如果不能直接匹配,则可以进行“有限的类型转换”;有限的类型转换看下面隐式转换,如果还不匹配,推导失败

 

其隐式转换

对于普通函数,支持隐式类型转换,其中add函数double变量在传参的时候就转换为int类型变量,而模板函数对隐式转换有局限,其中mymax会提示no matching function for call to 'mymax(int&, double&)',表示模板函数不匹配

template<typename T>
T& mymax(T& a, T& b)
{
	cout<<__PRETTY_FUNCTION__<<endl;
	return (a < b)? b : a;
}

void add(int a, int b)
{
	cout<<"add(int a, int b) ="<<(a+b)<<endl;
}


int main(int argc, char **argv)
{

	int a = 1;
	double b = 2.1;
	add(a, b);
	mymax(a, b);

	return 0;
}

函数模板只支持两种隐式类型转换,第一种是const转换:函数参数为非const引用/指针, 它可以隐式转换为const引用/指针,第二种是数组或函数指针转换:数组可以隐式转换为“指向第1个元素的指针”,参数为“函数的名字” 时,它隐式转换为“函数指针”,对于其他隐式转换都不支持,比如:算术转换、派生类对象向上转换

如下程序,在模板函数中可读可写的变量可以转换为隐式转换为可读的变量,而可读变量不能转换为可读可写

template<typename T>
const T& mymax(const T& a, const T& b)
{
	cout<<__PRETTY_FUNCTION__<<endl;
	return (a < b)? b : a;
}


int main(int argc, char **argv)
{
	int ia = 1;
	int ib = 2;	
	mymax(ia, ib);

	return 0;
}

执行结果如下

const T& mymax(const T&, const T&) [with T = int]

现在多写一个函数模板,类型为指针,将并数组代入

template<typename T>
const T& mymax(const T& a, const T& b)
{
	cout<<__PRETTY_FUNCTION__<<endl;
	return (a < b)? b : a;
}


template<typename T>
const T* mymax2(const T* a, const T* b)
{
	cout<<__PRETTY_FUNCTION__<<endl;
	return (a < b)? b : a;
}

int main(int argc, char **argv)
{
	char a[]="ab";
	char b[]="cd";
	
	mymax(a, b); 
	mymax2(a, b);

	return 0;
}

执行结果如下,对于模板一数组被推导为"char [3]"类型,而模板二被推导为指针char型

const T& mymax(const T&, const T&) [with T = char [3]]
const T* mymax2(const T*, const T*) [with T = char]

若字符串个数不同,对于模板一来说无法推导出"mymax(char& [4], char& [3])"函数,因为两个参数类型不一样;而对于模板二可以隐式转化为指针,因此对于两个字符串长度不一样只能转换为指针

	char a2[]="abc";
	char b2[]="cd";

    mymax(a2, b2);
    mymax2(a2, b2);

对于函数的情况,代入模板函数会隐式转换为函数指针,传入的是函数名或者取址结果都一样

template<typename T>
void test_func(T f)
{
	cout<<__PRETTY_FUNCTION__<<endl;
}

int f1(int a, int b)
{
	return 0;
}

int main(int argc, char **argv)
{

	test_func(f1);
	test_func(&f1);

	return 0;
}

执行结果如下

void test_func(T) [with T = int (*)(int, int)]
void test_func(T) [with T = int (*)(int, int)]

 

函数模板重载

函数模板也可以重载,现在对mymax函数写出二种不同参数的模板,同时加一个普通函数,并代入int类型

template<typename T>
const T& mymax(const T& a, const T& b)
{
	cout<<"1: "<<__PRETTY_FUNCTION__<<endl;
	return (a < b)? b : a;
}

template<typename T>
const T& mymax(T& a, T& b)
{
	cout<<"2: "<<__PRETTY_FUNCTION__<<endl;
	return (a < b)? b : a;
}


const int& mymax(int& a, int& b)
{
	cout<<"3: "<<__PRETTY_FUNCTION__<<endl;
	return (a < b)? b : a;
}

int main(int argc, char **argv)
{
	int ia = 1;
	int ib = 2;

	cout<<mymax(ia, ib)<<endl;
	return 0;
}

执行结果如下,调用的是普通函数

3: const int& mymax(int&, int&)
2

函数选择规则如下

  • 先列出候选函数,包括普通函数、参数推导成功的模板函数

  • 这些候选函数,根据“类型转换”来排序(注意:模板函数只支持有限的类型转换)

  • 如果某个候选函数的参数,跟调用时传入的参数更匹配,则选择此候选函数

  • 如果这些候选函数的参数匹配度相同(如果只有一个非模板函数,则选择它;如果只有模板函数,则选择“更特化”的模板函数;否则,最后导致“二义性”错误(ambiguous))

对于上述程序函数选择过程

  1. 第一个模板函数:mymax(const int &, const int &);第二个模板函数:mymax(int &, int &);第三个普通函数:mymax(int &, int &);
  2. 根据参数转换排序,调用时第一个模板函数需要转换排第二,第二模板函数和第三普通函数并列第一
  3. 选择"更匹配"的函数
  4. 匹配度相同,首先选择普通函数,对于多个模板函数,选择"更特化"的,否则出现二义性错误,因此选择的是普通函数

先加上第四个模板函数,执行程序,依然是选择普通函数

template<typename T> 
const T mymax(T a, T b)
{
	cout<<"4: "<<__PRETTY_FUNCTION__<<endl;
	return (a < b)? b : a;
}

现将第三个普通函数注释掉

#if 0
const int& mymax(int& a, int& b)
{
	cout<<"3: "<<__PRETTY_FUNCTION__<<endl;
	return (a < b)? b : a;
}
#endif

编译提示"error: call of overloaded 'mymax(int&, int&)' is ambiguous",出现二义性

函数选择过程

  1. 第一个模板函数:mymax(const int &, const int &);第二个模板函数:mymax(int &, int &);第四个模板函数:mymax(int , int);
  2. 根据参数转换排序,调用时第一个模板函数需要转换排第二,第二和第四个模板函数并列第一
  3. 选择"更匹配"的函数
  4. 匹配度相同,首先选择普通函数,对于多个模板函数,选择"更特化"的,否则出现二义性错误,因此对于二和四模板出现了二义性错误

先将第四个模板函数去掉加上第三个模板函数,然后用指针代入

template<typename T>
const T& mymax(const T& a, const T& b)
{
	cout<<"1: "<<__PRETTY_FUNCTION__<<endl;
	return (a < b)? b : a;
}

template<typename T>
const T& mymax(T& a, T& b)
{
	cout<<"2: "<<__PRETTY_FUNCTION__<<endl;
	return (a < b)? b : a;
}


const int& mymax(int& a, int& b)
{
	cout<<"3: "<<__PRETTY_FUNCTION__<<endl;
	return (a < b)? b : a;
}

int main(int argc, char **argv)
{
	int ia = 1;
	int ib = 2;
	int *p1=&ia;
	int *p2=&ib;

	cout<<mymax(p1, p2)<<endl;
	return 0;
}

执行结果如下

2: const T& mymax(T&, T&) [with T = int*]
0x7ffc9a8d6d94

函数选择过程

  1. 第一个模板函数:mymax(const int *&, const int *&);第二个模板函数:mymax(int *&, int *&);第三个普通函数参数不匹配
  2. 根据参数转换排序,调用时第二个模板函数不需要转换,而第一个模板需要转换,因此选择第二个

对于指针来说,函数应该比较的是指针所值变量的值,写出第四个模板函数

template<typename T>
const T mymax(T * a, T* b)
{
	cout<<"4: "<<__PRETTY_FUNCTION__<<endl;
	return (*a < *b)? *b : *a;
}

执行结果如下

4: const T mymax(T*, T*) [with T = int]
2

函数选择过程

  1. 第一个模板函数:mymax(const int *&, const int *&);第二个模板函数:mymax(int *&, int *&);第三个普通函数参数不匹配;第四个模板函数:mymax(int *, int *)
  2. 根据参数转换排序,调用时第二和第四个模板函数不需要转换,而第一个模板需要转换,第二和第四个模板函数并列第一
  3. 第二个和第四个都匹配
  4. 它们都是模板函数,选择出"更特化"的(更特殊、更具体和更细化),对于第二个模板函数mymax(T&, T&),T可以是int或者int*,而对于第四个模板函数参数只能是指针,因此T只能int,所以第四个更细化,因此选择第四个模板函数

修改第四个模板函数

template<typename T>
const T mymax(const T * a, const T* b)//这里加上了const
{
	cout<<"4: "<<__PRETTY_FUNCTION__<<endl;
	return (*a < *b)? *b : *a;
}

执行结果如下

2: const T& mymax(T&, T&) [with T = int*]
0x7ffc58a121c4

函数选择过程

  1. 第一个模板函数:mymax(const int *&, const int *&);第二个模板函数:mymax(int *&, int *&);第三个普通函数参数不匹配;第四个模板函数:mymax(const int *, const int *)
  2. 根据参数转换排序,调用时第一和第四个模板函数需要转换,因此选择第二函数模板

现将模板二注释掉

template<typename T>
const T& mymax(const T& a, const T& b) 
{
	cout<<"1: "<<__PRETTY_FUNCTION__<<endl;
	return (a < b)? b : a;
}

#if 0
template<typename T>
const T& mymax(T& a, T& b)
{
	cout<<"2: "<<__PRETTY_FUNCTION__<<endl;
	return (a < b)? b : a;
}
#endif

const int& mymax(int& a, int& b)
{
	cout<<"3: "<<__PRETTY_FUNCTION__<<endl;
	return (a < b)? b : a;
}

template<typename T>
const T mymax(const T * a, const T* b)
{
	cout<<"4: "<<__PRETTY_FUNCTION__<<endl;
	return (*a < *b)? *b : *a;
}

执行结果如下

1: const T& mymax(const T&, const T&) [with T = int*]
0x7fff9132c1f4

函数选择过程

  1. 第一个模板函数:mymax(const int *&, const int *&);第三个普通函数参数不匹配;第四个模板函数:mymax(const int *, const int *)
  2. 根据参数转换排序,调用时第一和第四个模板函数需要转换并列第一
  3. 对于模板一mymax(const T&, const T&),const修饰的是T,若T为int *,则表示const (int *)&,举例'int *q = &a;const (int *)p = q;",无法通过p修改q,但是p所指向内容可修改,对于模板四来说,若T为int,则指针所指向的内容不可修改,因此选择第一个模板函数

若传入的是字符串,选择过程也是类似的

template<typename T>
const T& mymax(const T& a, const T& b)
{
	cout<<"1: "<<__PRETTY_FUNCTION__<<endl;
	return (a < b)? b : a;
}

char* mymax(char* a, char* b)
{
	cout<<"3: "<<__PRETTY_FUNCTION__<<endl;
	return strcmp(a, b) < 0? b : a;
}

template<typename T>
const T mymax(T * a, T* b)
{
	cout<<"4: "<<__PRETTY_FUNCTION__<<endl;
	return (*a < *b)? *b : *a;
}


int main(int argc, char **argv)
{
	char *str1="hello"; 
	char *str2="Hellod";

	cout<<mymax(str1, str2)<<endl;
	return 0;
}

执行结果如下

3: char* mymax(char*, char*)
hello

 

类模板

与函数模板相似,类也可以有相应模板

1. 声明
template<typeclass T>
class AAA{
/* 使用T表示某种类型,比如: */
private:
    T obj;
public:
    void test_func(T& t);
    .....
};

2. 定义
template<typeclass T>
void AAA<T>::test_func(T& t)  .... 

引入

对于T来说,也可以多加几个类型例如"template<typename T1, typename T2>",对于成员函数在外实现也需要定义"template<typename T>",为了表示后面的T都是typename,怎么使用,用到的时候再实例化例如"AAA<int> a",或者事先实例化:"template AAA<int>;"再使用"AAA<int> a"

template<typename T>
class AAA {
private:
	T t;
public:
	void test_func(const T &t);
	void print(void);
};

template<typename T> void AAA<T>::test_func(const T &t)
{
	this->t = t;
}

template<typename T>
void AAA<T>::print(void)
{
	cout<<t<<endl;
}

int main(int argc, char **argv)
{
	AAA<int> a;

	a.test_func(1);
	a.print();

	AAA<double> b;

	b.test_func(1.23);
	b.print();

	return 0;
}

执行结果如下

1
1.23

定做

类似于重载

1. 声明
template<>
class AAA<int>{
......
public:
    void test(void);
};

2. 定义
void AAA<int>::test(void) ...

在函数"void AAA<T>::print(void)"后面加上以下程序

template<>   //这样写法:订制    类似重载
class AAA<int> {
public:
	void test_func_int(const int & t)
	{
		cout<<t<<endl;
	}
	void print_int(void);
};

void AAA<int>::print_int(void)
{
	cout<<"for test"<<endl;
}

执行结果如下

1
for test
1.23

 

  • 1
    点赞
  • 3
    评论
  • 1
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

相关推荐
©️2020 CSDN 皮肤主题: 黑客帝国 设计师:白松林 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值