C++/C学习笔记--(8)

终于到模板了,不过,模板这个心里真没底。这个东西应该是c++区别其他c系列语言的杀手锏,c++11更是引进了外部模板来简化了其自身在使用过程中代码扩张,添加了对匿名类和对象的支持,添加变长模板的支持,使其已完全形成了一种新的语言,可以说现在他就是寄生于c++中的另一种语言。而其关于多态的提供了一种继承的多动态所没有的优势。另,c++动多态,是指继承这种多态,其是在运行期来绑定的,而静多态是指通过模板在编译期便绑定的

多态的实质,便是我们可以根据需要对实例化出多种需要。

  • c++ 的动多态具有优点
    • 能够优雅地处理异类集合
    • 可执行代码的大小通常比较小(相比静态多态,因其针对不同类型会生成不同的代码,代码量会增大)
    • 可对对代码进行完全编译;因此并不需要发布实现源码。(模板库通常都需要同时发布模板实现的源代码的,难以做到定义和实现的分离)
  • c++r 静多态具有的优点
    • 可以很容易地实现内建类型的集合。更广义的说,并不需要通过公共类来表达接口的共同性。
    • 所生成的代码效率通常都比较高(相比动多态,没有指针的间接调用,且有更多机会使非虚函数内联)
    • 对于只提供部分接口的具体类型,如果在应用程序中只是使用到这一部分接口,那么也可以使用该具体类型;而不必在乎该类型是否提供其他部分的接口。
  • 所以一般的认为,静多态具有更好的类型安全性,因为静多态在编译期会多所有的绑定操作会进行检查。但在实际应用中,对于看起来相同的接口,如果在它们背后隐藏着一些语文假设的话,那么模板实例化体有时也会导致一些问题。即若 我们用一个类型实例化一个模板,在这模板中用到此类型+运算,但此类型并无此运算方法,此时便出现不匹配的问题,虽这种问题在编译过程中往往就可以解决,但,这种语义不匹配另人很不爽,相反在动态多态中这是很少见的。
  • 所以在具体的设计过程中,应该结合二者的优点,以桥模式为主设计需要。

开始吧:

  1. 函数模板
    1. inline 和 constexpr 的位置放在模板参数列表之后,返回类型之前。
    2. 函数模板在c++11中可以有默认值。
      template < typename t =int>
      void func(t a){
      	cout <<typeid(t).name();
      }
      int main() {
      	func(1); //输出 int
      
      }
      
    3. 在一些情况下,编译器无法推断出模板函数的参数类型,此时需要使用显示实参。即若我们显示指定了类型,编译器将试图把传入的参数进行指定类型的转换,若转不成功,则编译错误。关于模板函数的参数实例化中,有一点需要注意,便是其也可将模板用于返回类型,但这种方法的确显得笨拙。我们可以通过 auto和尾置返回的方法来解决这个问题。当然在尾置转换中我们可以结合type_traits库中的众多方法,得到我们想要的类型、
    4. 引用折叠的问题
      1. 在模板中可能存在引用的折叠问题,即 x& & 、x&& &以及 x& &&均将折叠成 X&,而只有x&& &&折叠为x&&
      2. 这个问题有两层意思, 一是当一个函数参数是模板类型的一个普通左值引用时,只能传递给他一个左值。二是当一个函数参数是模板类型的一个右值引用时,当我们将一个左值传递给函数的右值引用函数,且此右值引用指向模板类型类型参数时,编译器推断为左值为引用,
      3. 还需明确指明的是参数是&&的,其虽可既传入左值、也可传入一个右值,但传入的又值,其时便是以值的方式存在的。
        #include <iostream>
        #include <typeinfo>
        #include <type_traits>
        using namespace std;
        
        template < typename t =int>
        void func(t a){
        	cout <<typeid(t).name();
        }
        template <class m>
        void cun(m&& a){
        	cout << is_rvalue_reference<m>::value;
        	cout << is_lvalue_reference<m>::value;
        	cout << is_literal_type<m>::value;
        
        }
        int main() {
        
        	cun(4);//输出001
        }
        
      4. 我们可以从此推断出标准库中move的函数的可能实现方法可能是如下所示,其核心即是将一个左值转去除掉引用性,同时对传入右值不予处理。
        template<class T>
        typename remove_reference<T>::type 
        move(T&& a){
        return static_cast<typename remove_reference<T>::typename>(a);}
      5. 由此还引出一个性质便是转发的问题,我们在即在若我们把一个类型传入到一个函数中,如何保持类型不会变化,前面3中只解决了此问题的一半,若参数为&&,传入一个左值,会保留,但传入一个右值,依旧不会保留,此时我们可以利用标准库中std::forward<T>()来进一步保留其中的信息。
    5. 模板的重载
      1. 模板函数的重载与前相同,符合函数名相同,参数不同即可
      2. 若多个函数完全匹配,则按照非模板,特化模板,模板的顺序匹配执行
      3. 一条经验,对于定义任何函数之前,记得声明所有重载的函数版本。这样便不必担心编译器会出现你预想不到的事情。
  2. 模板类
    1. 编译器不能为类推断出类模板的类型,故类模板需要显示实例化。
    2. 在模板类中定义成员函数,可少写模板参数这个标记,在类外,只个必须,因为类需要。而若成员函数是一个模板函数,此时便需要两个模板参数,前一个是类的,后一个是模板的。这个不好看。
      #include <iostream>
      #include <typeinfo>
      #include <type_traits>
      using namespace std;
      
      template <class T>
      class t{
      public:
      	template<class A>
      	void func(A b);
      };
      
      template<class T>
      template <class A>
      void t<T>::func(A b){
      	cout << "this is in template class template function;";
      }
      int main() {
      	t<int>().func<int>(3);//this is in template class template function;当然在调用函数时,我们亦可不以显示的方式调用,可通过推断即可,但在对于类则必须以显示的方式调用,即使其类模板中含有默认参数,我们依然需要显示,此果可以不指定具体类型,但<〉不可缺
      }
      
    3. 关于模板类的友元一言以蔽之,若友元是一个模板类A,若一个类,或一个模板类B以A的一个特例为友元,请前置声明A是一个模板类,否则无此需要。
    4. 关于类的偏特实例化或叫部分实例化,有两个概念,一是针对某种类型进行明确的实例化,另一个针对某种修饰符进行明确实例化,如引用,或指针之类。还可以针对成员函数进行特例化。
  3. 模板类型别名,在原可以通过typedef的方式指定具体的模板类型,现可以通过using指定一个模板类型别名。
    #include <iostream>
    #include <typeinfo>
    #include <type_traits>
    using namespace std;
    template <class t ,class u>
    struct s{};
    template <typename t> 	using p = s<t,t>; //  这个声明只能在全局空间中,不能在函数中。!!
    int main() {
    	typedef s<int,int> ints; //typdef 可以明确类型,可以在函数中直接声明。
    	ints a;
    	p<double> b;
    }
    

  4. 范围限定符在编译器中默认其为值,所以如果使用::域限定符时,声明一个类型,在前需要添加typename来标记域解析出的为一个类型,而非数值。
  5. 模板类中的模板成员函数不能是虚函数,对于类中的静态型,其访问,定义也需要以模板的形式来处理,这个看起来不好看,但意义是一样的。
    #include <iostream>
    #include <typeinfo>
    #include <type_traits>
    using namespace std;
    template <class t ,class u>
    struct s{
    	 static const int fd ;
    };
    template<class t,class u>
    const int s<t,u>::fd =435;
    
    template <>
    const int s<int,float>::fd = 2233;//定义的形式式,在定义特例时,可以省略其中模板参数。但定义形式还是一样的。在函数中定义中特例化亦是如此
    int main() {
    	cout << s<int,float>::fd<<endl;
    	s<int,double> d;
    	cout << d.fd;
    }

  6. 可变参数模板的问题
    1. 例子
      #include <iostream>
      #include <typeinfo>
      #include <type_traits>
      using namespace std;
      template<class t>
      void func(t a){
      	cout << a;
      }
      template <class t,class ... Args> //定义形式
      void func(t&& a,Args&& ... rest ){ 
      	cout<<a<<endl;
      	func(rest...); // 调用形式
      }
      int main() {
      	func("nihao",12,.99,5678906545L);
      }
    2. 需要注意的是可变形参的使用形式
    3. 在可变参数模板中的一般的定义要按照递归定义。或者不用到所有具体的参数时,也可直接定义,而勿需递归处理。
    4. 我们也可将可变参数模板与std::forward<T>来结合将参数不变地传递给其他函数。
    5. 可变参可用sizeof...运算符来求模板参数个数。
      #include <iostream>
      #include <typeinfo>
      #include <type_traits>
      using namespace std;
      template<class t>
      void func(t a){
      	cout << a;
      }
      template <class t,class ... Args>
      void func(t&& a,Args&& ... rest ){
      	cout<<a<<endl;
      	func(rest...);
      }
      template <class ... T>
      void g (T ... t){
      	cout << sizeof...(T);
      }
      int main() {
      	func("nihao",12,.99,5678906545L);
      	cout << endl;
      	g(1,2,3,4,5,6,7,8);//8
      }




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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值