C++ Primer 学习笔记_81_模板与泛型编程 --类模板成员[续1]

模板与泛型编程

--类模板成员[续1]

二、非类型形参的模板实参

template <int hi,int wid>
class Screen
{
public:
    Screen():screen(hi * wid,‘#‘),
        cursor(hi * wid),height(hi),width(wid) {}

    //..

private:
    std::string screen;
    std::string::size_type cursor;
    std::string::size_type height,width;
};

这个模板有两个形参,均为非类型形参。当用户定义Screen对象时,必须为每个形参提供常量表达式以供使用。

    Screen<24,48> hp;	//hi的模板实参为24,wid=48

【注解】

非类型模板实参必须是编译时常量表达式。

三、类模板中的友元声明

在类模板中可以出现三种友元声明,每一种都声明了与一个或多个实体友元关系:

1)普通非模板类或函数的友元声明,将友元关系授予明确指定的类或函数。

2)类模板或函数模板的友元声明,授予对友元所有实例的访问权。

3)只授予对类模板或函数模板特定实例的访问权的友元声明。

1、普通友元

template <typename Type> class Bar
{
    friend class FooBar;
    friend void fcn();
};

普通非模板类FooBar和函数fcn可以访问Bar类的任意private成员和protected成员。

2、一般模板友元关系

友元关系可以是类模板或函数模板:

template <typename Type> class Bar
{
    template <typename T> friend class Foo1;
    template <typename T> friend void temp_fcn1(const Type &);
};

这些友元声明使用与类本身不同的类型形参,该类型形参指的是Foo1和 temp1_fcn1的类型形参。在这两种情况下,都将没有数目限制的类和函数设为Bar的友元。

这个友元声明在Bar与其友元Foo1和temp_fcn1的每个实例之间建立了一对多的映射。对Bar的每个实例而言,Foo1或 temp1_fcn1的所有实例都是友元。

3、特定的模板友元关系

只授予对特定实例的访问权:

template <typename Type> class Foo2;
template <typename Type>
void temp1_fcn2(const Type &);

template <typename Type> class Bar
{
    friend class Foo2<char *>;
    friend void temp1_fcn2<char *>(char * const &);
};

即使Foo2本身是类模板,友元关系也只扩展到Foo2的形参类型为char*实例

下面形式的友元更为常见:

template <typename Type> class Foo2;
template <typename Type>
void temp1_fcn2(const Type &);

template <typename T> class Bar
{
    friend class Foo3<T>;
    friend void temp1_fcn3<T>(const T &);
};

这些友元定义了Bar的特定实例与使用同一模板实参的Foo3或 temp1_fcn3的实例之间的友元关系。每个Bar实例有一个相关的Foo3和 temp1_fcn3友元:

    Bar<int> bi;
    Bar<string> bs;

只有与给定 Bar实例有相同模板实参的那些Foo3或 temp1_fcn3版本是友元。因此,Foo3<int>可以访问Bar<int>的私有部分,但不能访问Bar<string>或者任意其他Bar实例的私有部分。

4、声明依赖性

当授予对给定模板的所有实例的访问权的时候,在作用域中不需要存在该类模板函数模板声明。实质上,编译器将友元声明也当做类或函数声明对待

想要限制对特定实例化友元关系时,必须在可以用于友元声明之前声明类或函数:

template <class T> class A;
template <class T> class B
{
public:
    friend class A<T>;	//OK:类A已声明
    friend class C;		//OK:C为一个普通非模板类,相当于声明

    template <class S> friend class D;	//OK:D是一个泛化的模板类

    friend class E<T>;	//Error:E在此之前还没有声明或定义
    friend class F<int>;	//Error:同上
};

如果没有事先告诉编译器该友元是一个模板,则编译器将认为该友元是一个普通非模板类或非模板函数。

四、QueueQueueItem的友元声明

QueueItem类不打算为一般程序所用它的所有成员都是私有的。为了让Queue类使用QueueItem类,QueueItem类必须将Queue声明为友元。

1、将类模板设为友元

对于实例化的Queue类的每种类型,我们想要Queue类和QueueItem类之间的一对一映射:

template <class Type> class Queue;
template <class Type> class QueueItem
{
    friend class Queue<Type>;
};

2Queue输出操作符

因为希望输出任意类型Queue的内容,所以需要将输出操作符也设为模板:

template <typename Type>
ostream &operator<<(ostream &os,const Queue<Type> &q)
{
    os << "< ";
    for (Queue<Type> *p = q.head; p ;
            p = p -> next)
    {
        os << p -> item << " ";
    }
    os << ">";

    return os;
}

如果Queue为空,则for循环不执行,结果输出一对尖括号。

3、将函数模板设为友元

输出操作符需要成为Queue类和QueueItem类的友元。因为它需要使用Queue的head和QueueItem的next和item。我们的类将友元关系授予用同样类型实例化的输出操作符的特定实例:

template <typename Type> class Queue;
template <typename Type>
ostream &operator<<(ostream &,const Queue<Type> &); //首先声明

template <typename Type> class QueueItem
{
    friend class Queue<Type>;
    friend ostream &
    operator<< <Type>(ostream &,const Queue<Type> &);
    //AS Before
};

template <typename Type> class Queue
{
    friend ostream &
    operator<< <Type>(ostream &,const Queue<Type> &);
    //As Before
};

每个友元声明授予对对应operator<<实例的访问权。

4、类型依赖性与输出操作符

Queue的输出operator<<依赖于item对象的operator<<实际输出每个元素:

	os << p -> item << " ";

当使用p-> item 作为<<操作符的操作数的时候,使用的是为item所属的任意类型而定义的<<。

因此,绑定到Queue且使用Queue输出操作符的每种类型本身必须有输出操作符。为没有定义输出操作符的类创建Queue对象是合法的,但输出保存这种类型的Queue对象会发生编译时(或链接时)错误。

//P555 习题16.38/39/40
template <size_t hi,size_t wid>
class Screen;
template <size_t hi,size_t wid> ostream &
operator<<(ostream &,const Screen<hi,wid> &);
template <size_t hi,size_t wid> istream &
operator>>(istream &,Screen<hi,wid> &);

template <size_t hi,size_t wid>
class Screen
{
    friend ostream &
    operator<< <hi,wid>(ostream &,const Screen<hi,wid> &);

    friend istream &
    operator>> <hi,wid>(istream &,Screen<hi,wid> &);
public:
    typedef std::string::size_type index;

    Screen():
        screen(hi * wid,‘#‘),cursor(0),height(hi),width(wid) {}

private:
    string screen;
    index cursor;
    index height,width;
};

template <size_t hi,size_t wid>
ostream &operator<<(ostream &os,const Screen<hi,wid> &s)
{
    os << "screen: " << s.screen << "\t"
       << "height: " << s.height << "\t" << "width: " << s.width;
    return os;
}

template <size_t hi,size_t wid>
istream &operator>>(istream &in,Screen<hi,wid> &s)
{
    in >> s.screen >> s.height >> s.width;
    if (in)
    {
        s.cursor = s.height * s.width;
    }

    return in;
}

//习题16.42
template <typename Type>
istream &operator>>(istream &in,Queue<Type> &q)
{
    Type item;
    in >> item;
    q.push(item);

    return in;
}

五、成员模板

任意类都可以拥有本身为类模板或函数模板的成员,这种成员称为成员模板,成员模板不能为虚。

考虑Queue类的复制构造函数:它接受一个形参,是Queue<Type>的引用。想要通过从vector对象中复制元素而创建Queue对象,是办不到的,因为没有从vector到 Queue的转换。类似地,想要Queue<short>复制元素到Queue<int>,也办不到。同样的逻辑应用于赋值操作符,它也接受一个Queue<Type>&类型的形参。

我们希望定义一个构造函数和一个 assign成员,使容器类型元素类型都能变化

在Queue例子中,我们将定义构造函数和 assign成员接受一对在其他序列指明范围的迭代器,这些函数将有一个表示迭代器类型模板类型形参

【注意】

标准queue类没有定义这些成员:不支持从其他容器建立queue对象或给 queue对象赋值。我们在这里定义这些成员只是为了举例说明

1、定义成员模板

模板成员声明看起来像任意模板的声明一样:

template <typename Type> class Queue
{
public:
    //函数形参是指明要复制元素范围的迭代器
    template <typename Iter>
    Queue(Iter beg,Iter end):head(0),tail(0)
    {
        copy_elems(beg,end);
    }

    template <typename Iter>
    void assign(Iter,Iter);
    //As Before

private:
	//As Before
    template <typename Iter>
    void copy_elems(Iter,Iter);
};

2、在类外部定义成员模板

当在类外定义成员模板的时候,必须包含两个模板形参表:

1)assign成员:

//T:类模板形参;Iter:成员函数自己的模板形参
template <class T> template<class Iter>
void Queue<T>::assign(Iter beg,Iter end)
{
    destroy();
    copy_elems(beg,end);
}

2)copy_elems成员:

template <typename Type> template<typename Iter>
void Queue<Type>::copy_elems(Iter beg,Iter end)
{
    while (beg != end)
    {
        push(*beg);
        ++ beg;
    }
}

【小心地雷】

因为assign函数删除现在容器中的成员,所以传给assign函数的迭代器有必要引用不同容器中的元素。标准容器的assign成员和迭代器构造函数有相同的限制。

3、成员模板遵循常规访问控制

成员模板遵循与任意其他类成员一样的访问规则。如果成员模板为私有的,则只有该类的成员函数和友元可以访问该成员模板。

4、成员模板和实例化

成员模板只有在程序中使用时实例化。只是类模板的成员模板实例化比类模板的普通成员函数的实例化要稍微复杂一些,成员模板有两种模板形参:由类定义的和由成员模板本身定义。其中:类模板形参由调用函数的对象的类型确定,成员定义的模板形参的行为与普通函数模板一样。这些形参都通过常规模板实参推断而确定。

理解下面一段程序:

    short a[4] = {0,3,6,9};
    Queue<int> qi(a,a + 4);
    vector<int> vi(a,a + 4);

    qi.assign(vi.begin(),vi.end());

qi的定义将实例化:

void Queue<int>::Queue(short *, short *);

qi的assign成员的实例化:

void Queue<int>::assign(vector<int>::iterator,
                        vector<int>::iterator);

C++ Primer 学习笔记_81_模板与泛型编程 --类模板成员[续1],布布扣,bubuko.com

时间: 2024-10-10 07:27:19

C++ Primer 学习笔记_81_模板与泛型编程 --类模板成员[续1]的相关文章

C++ Primer 学习笔记_72_面向对象编程 --句柄类与继承[续]

面向对象编程 --句柄类与继承[续] 三.句柄的使用 使用Sales_item对象能够更easy地编写书店应用程序.代码将不必管理Item_base对象的指针,但仍然能够获得通过Sales_item对象进行的调用的虚行为. 1.比較两个Sales_item对象 在编写函数计算销售总数之前,须要定义比較Sales_item对象的方法.要用Sales_item作为关联容器的keyword,必须能够比較它们.关联容器默认使用keyword类型的小于操作符,可是假设给Sales_item定义小于操作符,

C++ Primer 学习笔记_81_模板与泛型编程 -类模板成员[续一]

模板与泛型编程 --类模板成员[续1] 二.非类型形参的模板实参 template <int hi,int wid> class Screen { public: Screen():screen(hi * wid,'#'), cursor(hi * wid),height(hi),width(wid) {} //.. private: std::string screen; std::string::size_type cursor; std::string::size_type height

C++ Primer 学习笔记_82_模板与泛型编程 --类模板成员[续2]

模板与泛型编程 --类模板成员[续2] 六.完整的Queue类 Queue的完整定义: template <typename Type> class Queue; template <typename Type> ostream &operator<<(ostream &,const Queue<Type> &); template <typename Type> class QueueItem { friend clas

C++ Primer 学习笔记_80_模板与泛型编程 --类模板成员

模板与泛型编程 --类模板成员 引言: 这一节我们介绍怎样实现前面提到的Queue模板类. 标准库将queue实现为其他容器之上的适配器.为了强调在使用低级数据结构中设计的编程要点,我们将Queue实现为链表.实际上,在我们的实现中使用标准库可能是个更好的决定!!-_-. 1.Queue的实现策略 如图所示,我们实现两个类: 1)QueueItem类表示Queue的链表中的节点,该类有两个数据成员item和next: a. item保存Queue中元素的值,它的类型随Queue的每个实例而变化:

C++ Primer 学习笔记_82_模板与泛型编程 -类模板成员[续二]

模板与泛型编程 --类模板成员[续2] 六.完整的Queue类 Queue的完整定义: template <typename Type> class Queue; template <typename Type> ostream &operator<<(ostream &,const Queue<Type> &); template <typename Type> class QueueItem { friend clas

C++ Primer 学习笔记_89_用于大型程序的工具 --异常处理[续2]

用于大型程序的工具 --异常处理[续2] 八.自动资源释放 考虑下面函数: void f() { vector<string> v; string s; while (cin >> s) { v.push_back(s); } string *p = new string[v.size()]; //... delete p; } 在正常情况下,数组和vector都在退出函数之前被撤销,函数中最后一个语句释放数组,在函数结束时自动撤销vector. 但是,如果在函数内部发生异常,则将

C++ Primer 学习笔记_94_用于大型程序的工具 --命名空间[续3]

用于大型程序的工具 --命名空间[续3] 六.重载与命名空间 正如我们所见,每个命名空间维持自己的作用域,因此,作为两个不同命名空间的成员的函数不能互相重载.但是,给定命名空间可以包含一组重载函数成员. 1.候选函数与命名空间 命名空间对函数匹配有两个影响.一个影响是明显的:using声明或using 指示可以将函数加到候选集合.另一个影响则微妙得多. 正如前节所见,有一个或多个类类型形参的函数的名字查找包括定义每个形参类型的命名空间.这个规则还影响怎样确定候选集合,为找候选函数而查找定义形参类

C++ Primer 学习笔记_100_特殊工具与技术 --优化内存分配[续2]

特殊工具与技术 --优化内存分配[续2] 七.一个内存分配器基类 预先分配一块原始内存来保存未构造的对象,创建新元素的时候,可以在一个预先分配的对象中构造:释放元素的时候,将它们放回预先分配对象的块中,而不是将内存实际返还给系统.这种策略常被称为维持一个自由列表.可以将自由列表实现为已分配但未构造的对象的链表. 我们将定义一个名为 CachedObj 的新类来处理自由列表.像 QueueItem 这样希望优化其对象分配的类可以使用 CachedObj 类,而不用直接实现自己的 new 和 del

C++ Primer 学习笔记_99_特殊工具与技术 --优化内存分配[续1]

特殊工具与技术 --优化内存分配[续1] 三.operator new函数和operator delete 函数 – 分配但不初始化内存 首先,需要对new和delete表达式怎样工作有更多的理解.当使用new表达式 string *sp = new string("initialized"); 的时候,实际上发生三个步骤: 1)首先,表达式调用名为operator new 的标准库函数,分配足够大的原始的未类型化的内存,以保存指定类型的一个对象; 2)接下来,运行该类型的一个构造函数