赞
踩
本小节回顾的知识点分别是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的路上~
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。