0
点赞
收藏
分享

微信扫一扫

(P70)模板三:缺省模板参数,成员模板,关键字typename,派生类和模板,面向对象与泛型


文章目录

  • ​​1.缺省模板参数​​
  • ​​2.成员模板​​
  • ​​3.关键字typename​​
  • ​​4.派生类和模板​​
  • ​​5.面向对象与泛型​​

1.缺省模板参数

  • (P69)模板二:类模板,非类型模板参数中Stack类模板内部所用数据结构的实现是T*,可以看成是动态数组,具有数组的特征,空间是连续的,且可以通过参数传递来控制空间大小,Stack里面的数据结构还可以是链表,队列

typename CONT = std::vector<T>表示可以将数据结构类型传递进来
template <typename T, typename CONT = std::vector<T>>
class Stack2
{
。。。
}

  • eg:Stack由deque来实现,默认参数(缺省值)是deque
    P70\Stack.h

#ifndef _STACK_H
#define _STACK_H

#include <exception>
#include <deque>
using namespace std;

//deque是与向量一样的容器
//deque<T>表示容器内部的类型是T,deque<T>是一个模板类
template <typename T, typename CONT = deque<T> >//注意这里必要要有一个空格
class Stack
{
public:
//分配一个T类型的连续内存空间
Stack() :c_()
{

}
~Stack()
{

}
void Push(const T& elem)
{
c_.push_back(elem);
}
void Pop()
{
//取出容器最后一个元素
c_.pop_back();
}
T& Top()
{
return c_.top();
}
bool Empty() const
{
return c_.empty();
}
private:
CONT c_;//数据结构是所传递进来的容器
int top_;//当前的栈顶位置
};

#endif //_STACK_H

P70\01.cpp

#include "Stack.h"
#include <iostream>
#include <vector>
using namespace std;

int main(void)
{

//Stack内部所采用的数据结构是双端队列
//默认参数(缺省值)是deque<T>
Stack<int> s;

//传递一个vector容器(只要有push_back等接口就行),vector<int>本质是一个模板类
//我们没必要去继承vector<int>来使用它的接口,把它当作类型参数来传递。从而使用它的接口c_.push_back(elem),从而适配出新接口Push(const T& elem)
//此时class Stack就是一个适配器,这是STL6大组件之一
//这种代码复用,不通过继承,而是通过适配,当现有的类当作参数来传递,使得构造出的适配器Stack(新类)也具有现有类的功能,能使用其接口
Stack< int, vector<int> > s;//将模板类vector<int>当作参数传递给一个类模板Stack

s.Push(1);
s.Push(2);
s.Push(3);

while (!s.Empty())
{
cout<<s.Top()<<endl;
s.Pop();
}

return 0;
}

  • 测试:

2.成员模板

  • eg:P70\02.cpp

#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
//因为i 和 d 的类型不同,故会编译出错。可以用成员模板(成员函数定义成模板)的方法解决
//虽然是同一个类模板实例化出来的模板类,但是还是不一样
//一个T是double,一个T是int
return 0;
}

  • 测试:错误的情况
  • (P70)模板三:缺省模板参数,成员模板,关键字typename,派生类和模板,面向对象与泛型_Stack


  • (P70)模板三:缺省模板参数,成员模板,关键字typename,派生类和模板,面向对象与泛型_c++_02

  • 成员模板,P70\03.cpp

#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())
{

}
//成员模板,X是一种类型
template <class X>
void Assign( const MyClass<X> &x)
{
value = x.GetValue();//不能直接访问:value = x.value;
//因为对象d和对象i的类型不一样,这样就不能访问不同类型的私有成员,所以需要定义一个Assign接口出来
}
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;
}

  • eg:P70\04.cpp

#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);//这里不能转换构造,除非提供一个构造函数的成员模板

//d2 = i;若支持这种操作,等号运算符也应该是一个成员模板

return 0;
}

/*
auto_ptr<X> x;

auto_ptr<Y> y;

x = y;//说明等号运算符是一个成员模板,因为x和y时不同的类

auto_ptr<x> a(y);//说明拷贝构造是一个成员模板
*/

等号运算符是一个成员模板

(P70)模板三:缺省模板参数,成员模板,关键字typename,派生类和模板,面向对象与泛型_#include_03


拷贝构造函数也是一个成员模板

(P70)模板三:缺省模板参数,成员模板,关键字typename,派生类和模板,面向对象与泛型_#include_04

3.关键字typename

  • eg:P70\05.cpp

#include <iostream>
using namespace std;

template < typename T>
class MyClass
{
private:
/*
typename T::SubType *ptr_; 如果前面没有typename 修饰,则SubType会被认为是T类型内部的静态数据成员,推导下去,* 就不再认 为是指针,而被
认为是乘号,编译的时候就出错了。加上修饰,就知道SubType 是T 内部的自定义类型,ptr是指向这种类型的指 针,编译通过。
*/
typename T::SubType *ptr_;//typename代表SubType是一个类型
};

class Test
{
public:
typedef int SubType;
};
int main( void)
{
MyClass<Test> mc;
return 0;
}

  • 测试:出错的情况

    编译出错,模板被编译2次,第一次编译模板本身是否编译成功,第二次是实例化模板类后的代码是否规范,实例化后,发现Test里面并没有子类型SubType,所以在实例化的过程中就会失败。类模板要实例化成模板类才能使用,这是在编译期完成的。

4.派生类和模板

  • 为了运行的效率,类模板是相互独立的,即独立设计,没有使用继承的思想。对类模板的扩展是采用适配器(adapter)来完成的。通用性是模板库的设计出发点之一,这是由泛型算法(algorithm)和函数对象(functor)等手段达到的。
    eg:实现一个Stack,可以使用不同的数据结构,比如可以采用链表list,vector,双端队列deque,将这些模板类传递进去构造出一个新的适配器,为了复用这些代码,非继承, 而是使用适配的方式来复用。
    面向对象的复用是派生,模板的复用是将类型当作参数来传递,从而适配出新的类型
  • 派生的目标之一也是代码的复用和程序的通用性,最典型的就是MFC,派生类的优点是可以由简到繁,逐步深入,程序编制过程中可以充分利用前面的工作,一步步完成一个复杂的任务。
  • 模板追求的是运行效率,而派生追求的是编程的效率。

5.面向对象与泛型

  • 面向对象与泛型都依赖于某个形式的多态
  • 面向对象,依赖的是虚函数的动态多态,通过虚函数来动态绑定,函数入口地址是运行时决定的,所以其效率不如静态多态来的高。
    动态多态(虚函数)
  • 泛型编程,依赖的是静态多态,也就是模板,模板是一种静态的多态,在编译时实例化模板类,模板函数,函数的入口地址在编译期就决定了
  • 面向对象中的多态在运行时应用存在继承关系。我们编写使用这些类的代码,忽略基类与派生类之间的类型差异。只要使用基类指针或者引用,基类类型对象、派生类类型对象就可以共享相同的代码。
  • 在泛型编程中,我们所编写的类和函数能够多态地用于编译时不相关的类型。一个类或一个函数可以用来操纵多种类型的对象。
  • 参考:从零开始学C++之模板(三):缺省模板参数(借助标准模板容器实现Stack模板)、成员模板、关键字typename


举报

相关推荐

0 条评论