Th4.3:typename使用场合、默认模板参数、趣味写法分析之详述

本小节回顾的知识点分别是typename使用场合、默认模板参数、趣味写法分析

 

今天总结的知识分为以下4个点:
(1)typename的使用场合
(2)函数指针做其他函数的参数
(3)函数模板的趣味用法举例(这是需要我们学习的函数模板的写法)
(4)默认模板参数

(1)typename的使用场合:

(先总结2个重要且较为常用的typename的使用场合,后续若遇到其他场合,再进行补充~)

        场合①在模板的定义中,使用typename表明其后的模板参数是类型参数。(此时的typename关键字是可以class关键字替换掉的!)

template<typename T1,typename T2,...typename Tn>
//此时的typename可以用class关键字来代替
//==> template<classT1,class T2,...class Tn>
retName funcName(Params) {
    /*...*/
}
//or
template<typename T1, typename T2, ...typename Tn>
//此时的typename可以用class关键字来代替
//==> template<classT1,class T2,...class Tn>
class ClassName {
public:
	/*...*/
};

        场合②用于访问(类/函数)模板中的类型成员(所谓类型成员:在类中用typedef定义的类型即为类的类型成员)

请看以下代码:(类模板例子)

#ifndef __MYVECTOR_H__
#define __MYVECTOR_H__
#include<iostream>
using namespace std;
template<typename T>
class myVector {
private:
	int m_Size;//当前数组元素个数
	int m_Capacity;//数组总容量大小
	T* my_Arr;
public:
	typedef T* myiterator;//迭代器 vector iterator
	myiterator mybegin();//迭代器的起始位置my_Arr[0]
	myiterator myend();//迭代器的最后一个元素my_Arr[n-1]的下一个位置

	/*...*/
};
#endif __MYVECTOR_H__

        当我们在模板类的定义{}之外区定义以下2个函数时,会遇到以下的问题:

template<typename T>
myVector<T>::myiterator myVector<T>::mybegin(){//迭代器的起始位置my_Arr[0]
	return &my_Arr[0];
}
template<typename T>
myVector<T>::myiterator myVector<T>::myend() {//迭代器的最后一个元素my_Arr[n-1]的下一个位置
	return &my_Arr[this->m_Size];
}

        这看起来似乎没啥毛病,但是当我们创建对象时:

#include<iostream>
#include"myVector.h"
using namespace std;
int main(void) {
	myVector<int> vec;
	return 0;
}

运行结果:

        此时,就必须要加上typename关键字才能访问到该模板类中的类型成员myiterator:

template<typename T>
typename myVector<T>::myiterator myVector<T>::mybegin(){//迭代器的起始位置my_Arr[0]
	return &my_Arr[0];
}
template<typename T>
typename myVector<T>::myiterator myVector<T>::myend() {//迭代器的最后一个元素my_Arr[n-1]的下一个位置
	return &my_Arr[this->m_Size];
}

        这样就能成功运行了!用typename就可以让那个编译器知道后面返回的myiterator是一个类型成员呢?而不是一个静态成员变量,因为编译器看到myVector<T>::这里时,默认是会认为::作用域符号后面是跟着static静态成员变量的(static静态成员变量可用类名::静态成员变量名的方式调用)。而当我们使用了typename之后,编译器就知道myiteratorr是一个类型成员了,这就达到了我们想要编译器做到的目标。(此时typename关键字是不可以class关键字替换的!)

再举个函数模板的例子:

#include<iostream>
#include<string>
using namespace std;
//这是一个返回string字符串长度的函数模板
template<typename T>
T::size_type getLength(const T& c) {
	if (c.empty()) return 0;
	return c.size();
}
int main(void) {
	string str =  "I Love CHina!";
    //string容器类中内置了size_type <==> unsigned int (是一种类型!)
    //用以防止你的代码在不同的操作系统上更通用!
	string::size_type size = getLength<string>(str);
	cout << "size = " << size << endl;
	return 0;
}

运行结果:

        此时,就必须要加上typename关键字才能访问到该模板函数中的类型成员size_type;

template<typename T>
typename T::size_type getLength(const T&c) {
	if (c.empty()) return 0;
	return c.size();
}

        运行结果:(成功运行!)

string str =  "I Love CHina!";
string::size_type size = getLength<string>(str);
cout << "size = " << size << endl;//size = 13

(2)函数指针做其他函数的参数:

        将函数指针作为其他函数的形参,如何do呢?

答:先定义了该类型的函数指针类型进行传参

        定义某类型的函数指针的格式(有2种):

①
typedef retName (*funcPointerName)(Params); 
②
using funcPointerName = retName(*)(Params);

请看以下代码:

#include<iostream>
using namespace std;

//若想定义一种类型的话,就必须要使用typedef or using来do
//先定义一个函数指针类型
typedef int(*FunType)(int, int);//or using FunType = int(*)(int, int);

//一种函数指针类型:int(*)(int,int);这种类型叫做FunType
//指向要给返回值类型为int,且函数参数为int,int的这种函数

int myadd(int a, int b) {
	return a + b;
}
void testFunc(int a, int b, FunType funcpoint) {
	//此时,我就可以通过传入函数名赋值给函数指针funcpoint
	//进而可以调用所传入的函数了!
	int res1 = (*funcpoint)(a, b);
	int res2 = funcpoint(a, b);
	cout << "res1 = " << res1 << endl;
	cout << "res2 = " << res2 << endl;
}
int main(void) {
	testFunc(12, 18, myadd);
	return 0;
}

运行结果:

 (3)函数模板的趣味用法举例(这是需要我们学习的函数模板的写法):

废话不多说,直接看代码:

#include<iostream>
using namespace std;

//(我们日后coding时,若想定义一种类型的话,就必须要使用typedef or using来do!)
//先定义一个函数指针类型
typedef int(*FunType)(int, int);
using FunType = int(*)(int, int);
//一种函数指针类型:int(*)(int,int);这种类型叫做FunType
//指向要给返回值类型为int,且函数参数为int,int的这种函数

int myadd(int a, int b) {
	return a + b;
}
template<typename T,typename F>
void testFunc(const T& a, const T& b, F funcpoint) {
	//此时,我就可以通过传入函数名赋值给函数指针funcpoint
	//进而可以调用所传入的函数了!
	int res1 = (*funcpoint)(a, b);
	int res2 = funcpoint(a, b);
	cout << "res1 = " << res1 << endl;
	cout << "res2 = " << res2 << endl;
}
int main(void) {
	testFunc(8, 12, myadd);
	testFunc<int, FunType>(12, 18, myadd);
	return 0;
}

运行结果:

 下面引入一个知识点:可调用对象(仿函数)

        可调用对象(仿函数):其实就是一个重载了函数调用符号()运算符函数的类的对象可以像一个函数那样被调用!这即称之为可调用对象!

请看以下代码:

#include<iostream>
using namespace std;
class tc {
public:
	tc() { cout << "tc的构造函数执行了!" << endl; }
	tc(const tc& t) { cout << "tc的拷贝构造函数执行了!" << endl; }
	//重载函数调用的运算符()符号
	int operator()(int v1, int v2)const {
		return v1 + v2;
	}
	~tc() {	cout << "tc的析构函数执行了!" << endl; }
};
template<typename T,typename F>
void testFunc(const T& a, const T& b, F funcpoint) {
	int res = funcpoint(a, b);//此时因为传入一个对象,so ==> tobj(7,3); ==> res = 7 + 3;
	cout << "res = " << res << endl;
}
int main(void) {
	tc tobj;//创建一个tc类的对象
	testFunc(7, 3, tobj);//直接调用该对象!像调用函数一样!
	return 0;
}

运行结果:

 

当然,你也可以这样:

testFunc(8, 9, tc());

        直接用tc类的一个匿名对象tc()作为函数模板的参数,这样编译器直接就用该临时的匿名对象作为funcpoint了,此时就不需要调用tc类的拷贝构造函数,也节省了一个析构函数都调用类。这是好的代码!

运行结果: 

 

        相信大家都看出来了,我们在不改变函数模板的情况下,函数指针以及可调用的对象居然都可以使用它!这是不是很有趣呢?所以,在设计模板时,这样的通用型强大的函数模板就非常值得我们学习了!

(4)默认模板参数:

        As we all know,函数参数可以有默认值。同理,函数模板的参数也可以具有默认值,类模板的参数也是可以具有默认值的!

        注意:模板语句中,带有默认值模板形参必须位于模板参数列表的结尾

        解释:在模板声明语句 template<typename T1,typename T2,...,typename Tn>中,如果从某个模板参数T开始是具有默认值的话,那么从此具有默认值的模板参数T开始,一直到last一个模板参数Tn为止,都必须要有默认值,否则就无法实例化出具体版本的该(类/函数)模板了,这是不符合语法规则的!(这和函数默认值的注意事项是一模一样的哈~)

例如:

a)给类模板提供默认值:

myarr.h:

#ifndef __MYARR_H__
#define __MYARR_H__
#include<iostream>
#include<string>
using  namespace std;
//这里的Size为非类型模板参数
#include<string>
template<typename T=string,int Size=10>
class myArr {
private:
	T arr[Size];
public:
	//...
	void myfunc();
};
template<typename T,int Size>
void myArr<T,Size>::myfunc() {
	cout << Size << endl;
}
#endif __MYARR_H__

main.cpp:

#include<iostream>
#include"myarr.h"
using namespace std;
int main(void) {
	myArr<> Arr1;//√成功运行!完全缺省(默认case下) ==> myArr<string,10> Arr;
	myArr<int> Arr2;//√成功运行! ==> myArr<int,10> Arr;
	myArr<double,8> Arr3;//√成功运行! ==> myArr<double,8> Arr;
	return 0;
}

b)给函数模板提供默认值:

        注意:要给函数模板提供默认值,就必须同时给模板参数函数参数提供缺省值

        解释:对于函数模板来说,不止是要在模板声明的语句中指定模板参数的默认值,还需要在模板函数形参表中指定对应的类型的参数的默认值。(这两者的默认指定是缺一不可的!)

#include<iostream>
using namespace std;
class tc {
public:
	tc() {
		cout << "tc的构造函数执行了!" << endl;
	}
	tc(const tc& t) {
		cout << "tc的拷贝构造函数执行了!" << endl;
	}
	//重载函数调用的运算符()符号
	int operator()(int v1, int v2)const {
		return v1 + v2;
	}
	~tc() {
		cout << "tc的析构函数执行了!" << endl;
	}
};
template<typename T,typename F=tc>//给定F是tc类的类型
void testFunc(const T& a, const T& b, F funcpoint=F()) {
//F funcpoint=F() <==> F funcpoint=tc() 
//然后给定一个匿名对象的默认值给funcpoint
	int res = funcpoint(a, b);
	cout << "res = " << res << endl;
}
int main(void) {
	testFunc(7, 3);
	testFunc(8, 9);
	return 0;
}

运行结果:

         注意到,上述代码中的F funcpoint=F() 是非常妙的一种写法。<==> F funcpoint = tc();

再比如:

#include<iostream>
using namespace std;
typedef int(*FunType)(int, int);//or using FunType = int(*)(int,int);
int myadd(int a, int b) {
	return a + b;
}
template<typename T,typename F= FunType>//让函数模板默认调用FunType这种函数指针类型所指向的函数
void testFunc(const T& a, const T& b, F funcpoint= myadd) {
	int res = funcpoint(a, b);
	cout << "add of " << a << " and " << b << " is " << res << endl;
}
int main(void) {
	testFunc(7, 3);
	testFunc(8, 9);
	return 0;
}

运行结果:

         以上就是我总结的关于typename使用场合、默认模板参数、趣味写法分析的笔记。希望你能读懂并且消化完,也希望自己能牢记这些小小的细节知识点,加油吧,我们都在coding的路上~

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Fanfan21ya

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值