当前位置:   article > 正文

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

typename 默认值

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

 

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

(1)typename的使用场合:

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

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

  1. template<typename T1,typename T2,...typename Tn>
  2. //此时的typename可以用class关键字来代替
  3. //==> template<classT1,class T2,...class Tn>
  4. retName funcName(Params) {
  5. /*...*/
  6. }
  7. //or
  8. template<typename T1, typename T2, ...typename Tn>
  9. //此时的typename可以用class关键字来代替
  10. //==> template<classT1,class T2,...class Tn>
  11. class ClassName {
  12. public:
  13. /*...*/
  14. };

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

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

  1. #ifndef __MYVECTOR_H__
  2. #define __MYVECTOR_H__
  3. #include<iostream>
  4. using namespace std;
  5. template<typename T>
  6. class myVector {
  7. private:
  8. int m_Size;//当前数组元素个数
  9. int m_Capacity;//数组总容量大小
  10. T* my_Arr;
  11. public:
  12. typedef T* myiterator;//迭代器 vector iterator
  13. myiterator mybegin();//迭代器的起始位置my_Arr[0]
  14. myiterator myend();//迭代器的最后一个元素my_Arr[n-1]的下一个位置
  15. /*...*/
  16. };
  17. #endif __MYVECTOR_H__

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

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

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

  1. #include<iostream>
  2. #include"myVector.h"
  3. using namespace std;
  4. int main(void) {
  5. myVector<int> vec;
  6. return 0;
  7. }

运行结果:

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

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

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

再举个函数模板的例子:

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

运行结果:

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

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

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

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

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

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

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

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

  1. typedef retName (*funcPointerName)(Params);
  2. using funcPointerName = retName(*)(Params);

请看以下代码:

  1. #include<iostream>
  2. using namespace std;
  3. //若想定义一种类型的话,就必须要使用typedef or using来do
  4. //先定义一个函数指针类型
  5. typedef int(*FunType)(int, int);//or using FunType = int(*)(int, int);
  6. //一种函数指针类型:int(*)(int,int);这种类型叫做FunType
  7. //指向要给返回值类型为int,且函数参数为int,int的这种函数
  8. int myadd(int a, int b) {
  9. return a + b;
  10. }
  11. void testFunc(int a, int b, FunType funcpoint) {
  12. //此时,我就可以通过传入函数名赋值给函数指针funcpoint
  13. //进而可以调用所传入的函数了!
  14. int res1 = (*funcpoint)(a, b);
  15. int res2 = funcpoint(a, b);
  16. cout << "res1 = " << res1 << endl;
  17. cout << "res2 = " << res2 << endl;
  18. }
  19. int main(void) {
  20. testFunc(12, 18, myadd);
  21. return 0;
  22. }

运行结果:

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

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

  1. #include<iostream>
  2. using namespace std;
  3. //(我们日后coding时,若想定义一种类型的话,就必须要使用typedef or using来do!)
  4. //先定义一个函数指针类型
  5. typedef int(*FunType)(int, int);
  6. using FunType = int(*)(int, int);
  7. //一种函数指针类型:int(*)(int,int);这种类型叫做FunType
  8. //指向要给返回值类型为int,且函数参数为int,int的这种函数
  9. int myadd(int a, int b) {
  10. return a + b;
  11. }
  12. template<typename T,typename F>
  13. void testFunc(const T& a, const T& b, F funcpoint) {
  14. //此时,我就可以通过传入函数名赋值给函数指针funcpoint
  15. //进而可以调用所传入的函数了!
  16. int res1 = (*funcpoint)(a, b);
  17. int res2 = funcpoint(a, b);
  18. cout << "res1 = " << res1 << endl;
  19. cout << "res2 = " << res2 << endl;
  20. }
  21. int main(void) {
  22. testFunc(8, 12, myadd);
  23. testFunc<int, FunType>(12, 18, myadd);
  24. return 0;
  25. }

运行结果:

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

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

请看以下代码:

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

运行结果:

 

当然,你也可以这样:

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:

  1. #ifndef __MYARR_H__
  2. #define __MYARR_H__
  3. #include<iostream>
  4. #include<string>
  5. using namespace std;
  6. //这里的Size为非类型模板参数
  7. #include<string>
  8. template<typename T=string,int Size=10>
  9. class myArr {
  10. private:
  11. T arr[Size];
  12. public:
  13. //...
  14. void myfunc();
  15. };
  16. template<typename T,int Size>
  17. void myArr<T,Size>::myfunc() {
  18. cout << Size << endl;
  19. }
  20. #endif __MYARR_H__

main.cpp:

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

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

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

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

  1. #include<iostream>
  2. using namespace std;
  3. class tc {
  4. public:
  5. tc() {
  6. cout << "tc的构造函数执行了!" << endl;
  7. }
  8. tc(const tc& t) {
  9. cout << "tc的拷贝构造函数执行了!" << endl;
  10. }
  11. //重载函数调用的运算符()符号
  12. int operator()(int v1, int v2)const {
  13. return v1 + v2;
  14. }
  15. ~tc() {
  16. cout << "tc的析构函数执行了!" << endl;
  17. }
  18. };
  19. template<typename T,typename F=tc>//给定F是tc类的类型
  20. void testFunc(const T& a, const T& b, F funcpoint=F()) {
  21. //F funcpoint=F() <==> F funcpoint=tc()
  22. //然后给定一个匿名对象的默认值给funcpoint
  23. int res = funcpoint(a, b);
  24. cout << "res = " << res << endl;
  25. }
  26. int main(void) {
  27. testFunc(7, 3);
  28. testFunc(8, 9);
  29. return 0;
  30. }

运行结果:

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

再比如:

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

运行结果:

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

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/很楠不爱3/article/detail/576884
推荐阅读
  

闽ICP备14008679号