Essential C++ Lippman 第6章 以template进行编程 摘录

Stroustrup拟好C++语言中关于template的原始设计后,将template称为被参数化的类型(parameterized type):称其参数化是因为,类型相关信息可自tempplate定义中剥离,称其类型则是因为,每一个class template或function template基本上随着它所作用或它所内含的类型而有性质上的变化。

Template定义扮演的乃是“处方”角色,能根据用户指定的特定值或特定类型,自动产生一个函数或类。

本章的任务之一,带你一览二叉树(binary tree) class template的所有实现过程。

所谓树(tree)乃是由节点(node)以及连接不同节点的链接(link)组成。所谓二叉树,维护着每个节点与下层另两个节点间的两条链接,一般将此下层二节点称为左子节点(left child)和右子节点(right child)。最上层第一个节点称为根节点(root)。无论是左子节点或右子节点,都可能扮演另一个'子树“的根节点。一个节点如果不在有任何子节点,便称为叶节点(leaf)。

我们的二叉树包含两个类,一个是BinaryTree,用来存放指针,指向根节点,另一个是BTnode,用来存放节点实值,以及连接至左、右两个子节点的链接。

6.1 被参数化的类型

以下是一个non-template BTnodes class,其实值存放在string对象之中。

class string_BTnode
{
public:
    //...
private:
    string _val;
    int _cnt;
    string_BTnodes* _lchild;
    string_BTnodes* _rchild;
};

template机制帮助我们将类定义中”与类型相关(type-dependent)“和"独立于类型之外"的两部分分离开。

在一个class template中,与类型相关的部分会被抽取出来,成为一个或多个参数。以BTnodes class为例,data member _val的类型可被参数化。

template <typename valType>
class BTnode;
{
public:
    //...
private:
    valType _val;
    int _cnt;
    BTnode* _lchild;
    BTnode* _rchild;
};

在此class template定义中,valType被拿来做一个占位符。其名称可以任意设定。在用户为它指定某个特定类型之前,它被视为可取代为任意类型的东西。

在我的实现中,BTnode class template必须和BinaryTree class template配合使用。针对每一种BTnode的实际类型,我们希望对于的BinaryTree实例能够成为其friend。声明方法如下:

template <typename valType>
class BinaryTree;

template <typename valType>
class BTnode;
{
    friend class BinaryTree<valType>;
};

为了通过class template实例化类,我们必须在class template名称之后,紧接一个尖括号,其内放一个实际类型(准备用来取代valType)。

BinaryTree仅声明一笔数据:一个BTnode指针,指向二叉树根节点。

template <typename elemType>
class BinaryTree
{
public:
    // ...
private:
    BTnode<elemType>* _root;
};

6.2 Class Template的定义

以下便是我们的BinaryTree class template的部分定义:

template <typename elemType>
class BinaryTree
{
public:
    BinaryTree();
    BinaryTree(const BinaryTree&);
    ~BinaryTree();
    BinaryTree& operator=(const BinaryTree&);

    bool empty() { return _root == 0;}
    void clear();

private:
    BTnode<elemType>* root;

    void copy(BTnode<elemType>* tar, BTnode<elemType>* src);
};

为class template定义为一个inline函数,做法与non-template class定义一个inline函数一样。

template <typename elemType>
inline BinaryTree<elemType>::
BinaryTree() : _root(0) 
{}

这个member function的定义始于关键字template和一个参数列表,然后便是函数定义本身,并带有inline及class scope运算符。inline一词必须紧接着template和参数列表之后。

在class scope运算符出现之后BinaryTree<elemType>::其后所有都被视为位于class定义范围内。

以下是BinaryTree的copy constructor、copy assignment operator及destructor的定义:

template <typename elemType>
inline BinaryTree<elemType>::
BinaryTree(const BinaryTree& rhs) 
{ copy(_root, rhs._root); }

template <typename elemType>
inline BinaryTree<elemType>::
~BinaryTree()
{ clear(); }

template <typename elemType>
inline BinaryTree<elemType>&
BinaryTree<elemType>::
operator=(const BinaryTree& rhs)
{
    if (this != &rhs)
    { 
        clear();
        copy(_root, rhs._root);
    }
    return *this;
}

6.3 Template类型参数的处理

实际运用中,不论内置类型或class类型,都可能被指定为class template的实际类型。我建议,将所有的template类型参数视为”class类型“处理。这意味着我们会把它声明为const reference,而不是by value方式传递。

在constructor定义中,我选择在member initialization list内为每个类型参数进行初始化操作:

template <typename valType>
inline BTnode<valType>::
BTnode(const valType& val) : _val(val)
{
    _cnt = 1;
    _lchild = _rchild = 0;
}

这样一来,当用户为valType指定一个class类型时,可以保证效率最佳。

如果是在constructor函数体内初始化,那么初始化操作分为两步:(1)函数体执行时,default constructor会先作用于_val身上。(2)函数体内会以copy assignment operator将val复制给_val。

当如果是在member initialization list中初始化只需以copy constructor将val复制给_val。

6.4 实现一个Class Template

每当我们插入某个新值,都必须建立BTnode对象、加上初始化。

以insert()为例,如果根节点之值尚未设定。

template <typename elemType>
inline void
BinaryTree<elemType>::
insert(const elemType& elem)
{
    if (!_root)
        _root = new BTnode<elemType>(elem);
    else
        _root->insert_value(elem);
}

new表达式可分解为两个操作:(1) 向程序的空闲空间(free store)请求内存。如果分配到足够的空间,就返回一个指针,指向新对象。(2) 如果第一步成功,并且外界指定了一个初值,这个新对象便会以最适当的方式被初始化。

当根节点存在时,insert_value()才会被调用。小于根节点的所有数值放在根节点左子树,大于根节点的所有数值放在根节点的右子树。insert_value()会通过左右子节点递归(recursively)调用自己,直到以下任何一种情形发生:(1)合乎资格的子树并不存在,(2)欲插入的数值已在树中。下面为实现内容:

template <typename valType>
void BTnode<valType>::
insert_value(const valType& val)
{
    if (val == _val)
    {   
        _cnt++;
        return;
    }

    if (val < _val)
    {
        if (!_lchild)
            _lchild = new BTnode(val);
        else
            _lchild->insert_value(val);
    }
    else
    {
        if (!_rchild)
            _rchild = new BTnode(val);
        else
            _rchild->insert_value(val);
    }
}

移除某值的操作更为复杂,因为我们必须保持二叉树的次序不变。一般的算法是,以节点的右子节点取代节点本身,然后搬移左子节点,使它成为右子节点的左子树的叶节点,如果此刻并无右子节点,那么就以左子节点取代节点本身。。

template <typename elemType>
inline void
BinaryTree<elemType>::
remove(const elemType& elem)
{
    if (_root)
    {
        if (_root->_val == elem)
            remove_root();
        else
            _root->remove_value(elem, _root);
    }
}

无论是remove_root()或remove_value(),都会搬离左子节点,使它成为右子节点的左子树的叶节点。我将这一操作剥离至lchild_leaf()。以下是实现内容:

template <typename valType>
void BTnode<valType>::
lchild_leaf(BTnode* leaf, BTnode* subTree)
{
    while (subTree->lchild)
        subTree = subTree->_lchild;
    subTree->lchild = leaf;
}

以下是讨论根节点

template <typename elemType>
void BianryTree<elemType>::
remove_root()
{
    if (!_root)
        return;

    BTnode<elemType>* tmp = _root;
    if (_root->_rchild)
    {
        _root = _root->rchild;

        if (tem->_lchild)
        {
            BTnode<elemType>* lc = tmp->_lchild;
            BTnode<elemType>* newlc = _root->_lchild;

            if (!newlc)
                _root->lchild = lc;
                
            else
                BTnode<elemType>::lchild_leaf(lc, newlc);
        }
    }
    else
        _root = _root->lchild;

    delete tmp;

}

remove_value()有两个参数:将被删除的值以及一个指针,指向目前关注的节点的父节点。第二个参数是reference to pointer。以pointer来传递,我们能够更改该pointer所指之物,而不是pointer本身。为了改变pointer本身,我们也将其声明为引用。reference to pointer,我们不但可以改变pointer,还可以改变pointer指向的对象。

template <typename valType>
void BTnode<valType>::
remove_value(const valType& val, BTnode*& prev)
{
    if (val < _val)
    {
        if (!_lchild)
            return;
        else
            _lchild->remove_value(val, _lchild);
    }
    else if (val > _val)
    {
        if (!_rchild)
            return;
        else
            _rchild->remove_value(val, _rchild);

    }
    else
    {
        if (_rchild)
        {
            prev = _rchild;
            if (_lchild)
                if (!prev->_lchild)
                    prev->_lchild = _lchild;
                else
                    BTnode<valType>::lchild_leaf(_lchild, prev->_lchild);
        }
        else
            prev = _child;

        delete this;

    }



}

6.5 一个以Function Template完成的Output运算符

template <typename elemType>
inline ostream&
operator<<(ostream& os, const BinaryTree<elemType>& bt)
{
    os << "Tree: " << endl;
    bt.print(os);
    return os;
}

// 因为运算符访问了bt的member,因此需要成为BinaryTree的友元
template <typename elemType>
class BinaryTree
{
    friend ostream& operator<<(ostream& os, const BinaryTree<elemType>& bt);
...
};

6.6 常量表达式与默认参数值

Template参数并非一定得某种类型(type)不可。我们也可以使用常量表达式(const expression)作为template参数。

template <int length>
class num_sequence
{
...
};

全局作用域(global scope)内的函数和对象,其地址也是一种常量表达式,因此也可以被拿来表达这一形式的参数。

template <void (*pf)(int, vector<int>& seq)>
class num_sequence
{
...
};

6.7 以template参数作为一种设计策略

template <typename elemType>
class LessThan
{
public:
    bbol operator()(const elemType& val) const { return val < _val; }
...

};

上述做法存在一个潜在问题,一旦用户所提供的类型并未定义less-than运算符,上述便宣告失败。另一种可行策略是提供第二个class template,将conparison运算符从类定义中剥离。虽然第二个类提供的是和LessThan相同的语义,我们却得为它另外取一个名字,因为class template无法基于参数列表的不同而重载。让我们把它命名为LessThanPred吧。

template <typename elemType, typename Comp = LessThan<elemType>>
class LessThanPred
{
public:
    bool operator()(const elemType& val) const { return Comp(val, _val);}

};

6.8 Member Template Function

当然,我们也可以将member function定义成template形式。

class PrintIt
{
public:
    template <typename elemType>
    void print(const elemType& elem, char delimiter = '\n')
    {
        _os << elem << delimiter;
    }

private:
    ostream& _os;

};

另print()成为PrintIt的member function template,我们便可以在只写一份函数定义的情形下,支持任何类型。

class template内也可以定义member function template。我们可能会将PrintIt原本指定的output stream予以参数化,使其成为可以指定的ostream类型。

 template <typename OutStream>
class PrintIt
{
public:
    template <typename elemType>
    void print(const elemType& elem, char delimiter = '\n')
    {
        _os << elem << delimiter;
    }

private:
    ostream& _os;

};

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值