从零开始学C++之模板(三):缺省模板参数(借助标准模板容器实现Stack模板)、成员模板、关键字typename

xiaoxiao2021-02-28  168

一、缺省模板参数

回顾前面的文章,都是自己管理stack的内存,无论是链栈还是数组栈,能否借助标准模板容器管理呢?答案是肯定的,只需要多传一个模板参数即可,而且模板参数还可以是缺省的,如下:

template <typename T, typename CONT = std::deque<T> > class Stack { …

private:

    CONT c_; };

如果没有传第二个参数,默认为deque 双端队列,当然我们也可以传递std::vector<T>

下面程序借助标准模板容器管理内存来实现stack模板类:

Stack.h:

 C++ Code  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 #ifndef _STACK_H_ #define _STACK_H_ #include <exception> #include <deque> using  namespace std; template < typename T,  typename CONT = deque<T> > class Stack { public:     Stack() : c_()     {     }     ~Stack()     {     }      void Push( const T &elem)     {         c_.push_back(elem);     }      void Pop()     {         c_.pop_back();     }     T &Top()     {          return c_.back();     }      const T &Top()  const     {          return c_.back();     }      bool Empty()  const     {          return c_.empty();     } private:     CONT c_; }; #endif  // _STACK_H_

main.cpp:

 C++ Code  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #include  "Stack.h" #include <iostream> #include <vector> using  namespace std; int main( void) {      /*Stack<int> s;*/     Stack< int, vector< int> > s;     s.Push( 1);     s.Push( 2);     s.Push( 3);      while (!s.Empty())     {         cout << s.Top() << endl;         s.Pop();     }      return  0; }

输出为 3 2 1

即如果没有传递第二个参数,堆栈和压栈等操作直接调用deque<int> 的成员函数,也由deque<int> 管理内存。

如程序中传递vector<int> ,则由vector<int> 成员函数处理。

二、成员模板

来看下面的例子:

 C++ Code  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 #include <iostream> using  namespace std; template < typename T> class MyClass { private:     T value; public:      void Assign( const MyClass<T> &x)     {         value = x.value;     } }; int main( void) {     MyClass< double> d;     MyClass< int> i;     d.Assign(d);         // OK     d.Assign(i);         // Error      return  0; }

因为i 和 d 的类型不同,故会编译出错。可以用成员模板的方法解决:  C++ Code  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 #include <iostream> using  namespace std; template < typename T> class MyClass { private:     T value; public:     MyClass() {}      template < class X>     MyClass( const MyClass<X> &x) : value(x.GetValue())     {     }      template < class X>      void Assign( const MyClass<X> &x)     {         value = x.GetValue();     }     T GetValue()  const     {          return value;     } }; int main( void) {     MyClass< double> d;     MyClass< int> i;     d.Assign(d);         // OK     d.Assign(i);         // OK     MyClass< double> d2(i);      return  0; } 为了支持  MyClass< double > d2(i); 故也要将拷贝构造函数实现为成员模板函数,同理,如果想支持 d = i ; 也要将赋值运算符实现为成员 模板。 实际上auto_ptr<class> 中的实现就使用了成员模板,因为要支持类似下面的运算: auto_ptr<X> x; auto_ptr<Y> y; x = y; 三、typename 关键字 看下面的例子:  C++ Code  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 #include <iostream> using  namespace std; template < typename T> class MyClass { private:      typename T::SubType *ptr_; }; class Test { public:      typedef  int SubType; }; int main( void) {     MyClass<Test> mc;      return  0; } typename T::SubType *ptr_; 如果前面没有typename 修饰,则SubType会被认为是T类型内部的静态数据成员,推导下去,* 就不再认 为是指针,而被 认为是乘号,编译的时候就出错了。加上修饰,就知道SubType 是T 内部的自定义类型,ptr是指向这种类型的指 针,编译通过。 在vector 的源码中也可以发现下面的一些片段:  C++ Code  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 template<>  class _CRTIMP2_PURE allocator< void> {      // generic allocator for type void public:      template< class _Other>      struct rebind     {          // convert an allocator<void> to an allocator <_Other>          typedef allocator<_Other> other;     };     .... } typedef  typename _Alloc:: template rebind<_Ty>::other _Alty; 最后一行是类型定义,由于要解释_Alloc 类型需要引用的代码片段比较多,就姑且认为是allocator<int> 类型,可以看到 rebind<_Ty>  是成员模板类,other是成员模板类中自定义类型,_Ty 可以认为是int , 那么other 类型也就是allocator<int>, 也就是说_Alty 是类型 allocator<int> 。 此外还可以看到 : template<class _Ty>  class allocator  {  template<> class _CRTIMP2_PURE allocator<void }; }; 也就是说allocator<void> 是allocator 模板类的特化。

四、派生类与模板、面向对象与泛型编程

(一)、派生类与模板

1、为了运行的效率,类模板是相互独立的,即独立设计,没有使用继承的思想。对类模板的扩展是采用适配器(adapter)来完成的。通用性是模板库的设计出发点之一,这是由泛型算法(algorithm)和函数对象(functor)等手段达到的。

2、派生的目标之一也是代码的复用和程序的通用性,最典型的就是MFC,派生类的优点是可以由简到繁,逐步深入,程序编制过程中可以充分利用前面的工作,一步步完成一个复杂的任务。

3、模板追求的是运行效率,而派生追求的是编程的效率。

(二)、面向对象与泛型编程

1、面向对象与泛型都依赖于某个形式的多态

面向对象

动态多态(虚函数)

泛型

静态多态(模板类,模板函数)

2、面向对象中的多态在运行时应用存在继承关系。我们编写使用这些类的代码,忽略基类与派生类之间的类型差异。只要使用基类指针或者引用,基类类型对象、派生类类型对象就可以共享相同的代码。

3、在泛型编程中,我们所编写的类和函数能够多态地用于编译时不相关的类型。一个类或一个函数可以用来操纵多种类型的对象。

参考:

C++ primer 第四版 Effective C++ 3rd C++编程规范

转载请注明原文地址: https://www.6miu.com/read-24361.html

最新回复(0)