C++ primer读书笔记 Chapter 16 some tips about Templates and Generic Programming

本文详细介绍了C++中的模板概念,包括函数模板和类模板的区别、类型形参的实参受限转换、函数指针的使用、Queue类模板实现、以及模板友元声明等内容。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.明确模板概念:

第一次接触模板,看完第16章后感觉自己都弄清楚了,可是在回想一遍之后,才发现自己的思绪很混乱。所以,我先总结一下有几类模板:

(1).函数模板:

先给出一个例子:

template <typename T>
     int compare(const T &v1, const T &v2)
     {
         if (v1 < v2) return -1;
         if (v2 < v1) return 1;
         return 0;
     }

这就是一个函数模板,注意在模板定义后面并没有分号(template <typename T>)。

 

模板定义以关键字 template 开始,后接模板形参表,模板形参表是用尖括号括住的一个或多个模板形参的列表,形参之间以逗号分隔。

注意:模板的形参表列不能为空。

(2).类模板:

之前我一直没搞明白,类模板和函数模板到底有什么区别。特意上网查了资料,觉得下面的解释比较易于理解:

(引自博客http://blog.youkuaiyun.com/csw_100/archive/2010/08/27/5844615.aspx,多谢作者)

函数模板的实例化是由编译程序在处理函数调用时自动完成的,而类模板的实例化 必须由程序员在程序中显式地指定。 即函数模板允许隐式调用和显式调用而类模板只能显示调用。

作者接下来给出的例证,是小弟目前还未接触过的知识,只能自己结合着C++primer上的例子来理解。书上给出一个Queue类模板的例子,定义如下:

   template <class Type> class Queue {
     public:
         Queue ();                // default constructor
         Type &front ();          // return element from head of Queue
         const Type &front () const;
         void push (const Type &); // add element to back of Queue
         void pop();              // remove element from head of Queue
         bool empty() const;      // true if no elements in the Queue
     private:
         // ...
     };
与调用函数模板形成对比,使用类模板时,必须为模板形参显式指定实参:

     Queue<int> qi;                 // Queue that holds ints
     Queue< vector<double> > qc;    // Queue that holds vectors of doubles
     Queue<string> qs;              // Queue that holds strings

我想,这就是类模板和函数模板的区别吧,必须显示调用。类模板和函数模板还有一个区别,比较好理解,那就是类模板的主体是类而函数模板的主体是函数。

 

3.关于类型形参的实参的受限转换:

 

  这也是个难点,书上讲到:

  一般而论,不会转换实参以匹配已有的实例化,相反,会产生新的实例。除了产生新的实例化之外,编译器只会执行两种转换:

  const 转换:接受 const 引用或 const 指针的函数可以分别用非 const 对象的引用或指针来调用,无须产生新的实例化。如果函数接受非引用类型,形参类型实参都忽略 const,即,无论传递 const 或非 const 对象给接受非引用类型的函数,都使用相同的实例化。

  数组或函数到指针的转换:如果模板形参不是引用类型,则对数组或函数类型的实参应用常规指针转换。数组实参将当作指向其第一个元素的指针,函数实参当作指向函数类型的指针。

    第一个转换容易理解,关键是第二个。看看书上的例子:

     template <typename T> T fobj(T, T); // arguments are copied
     template <typename T>
     T fref(const T&, const T&);       // reference arguments
     string s1("a value");
     const string s2("another value");
     fobj(s1, s2);     // ok: calls f(string, string), const is ignored
     fref(s1, s2);     // ok: non const object s1 converted to const reference
     int a[10], b[42];
     fobj(a, b); // ok: calls f(int*, int*)
     fref(a, b); // error: array types don't match; arguments aren't converted to pointers

  为什么fref(a,b)的调用会出错呢?书上解释道:fobj 的调用中,数组不同无关紧要,两个数组都转换为指针,fobj 的模板形参类型是 int*。但是,fref 的调用是非法的,当形参为引用时,数组不能转换为指针,ab 的类型不匹配,所以调用将出错。为什么会导致ab类型不匹配呢?这是因为通过引用传递数组时,数组大小成为了实参和形参的一部分。而在上面的例子中,a和b的大小并不一样,因此导致类型不匹配。

 

4.关于函数指针:

不得不说,模板这章真的蛮难的。需要理解的概念很多。接下来说说函数指针的赋值和初始化。

假定有一个函数指针指向返回 int 值的函数,该函数接受两个形参,都是 const int 引用,可以用该指针指向 compare 的实例化        template <typename T> int compare(const T&, const T&);
     // pf1 points to the instantiation int compare (const int&, const int&)
     int (*pf1) (const int&, const int&) = compare;
pf1 的类型是一个指针,指向“接受两个 const int& 类型形参并返回 int 值的函数”,形参的类型决定了 T 的模板实参的类型,T 的模板实参为 int 型,指针 pf1 引用的是将 T 绑定到 int 的实例化。这里要求能确定模板实参,否则调用将出现错误。

 

 

5.实现Queue类:

Queue类的实现将定义两个类,首先我们定义一个QueueItem类,表示Queue的链表中的节点。该类有两个数据成员item和next。item保存Queue中的元素的值,next是队列指向下一个QueueItem对象的指针。Queue中的每个元素保存在一个QueueItem对象中。

编写Queue类:

template <class Type> class QueueItem {
              QueueItem(const Type &t): item(t), next(0) { }
         Type item;           // value stored in this element
         QueueItem *next;     // pointer to next element in the Queue
     };
 

现在充实 Queue 类:

     template <class Type> class Queue {
      public:
         // empty Queue
         Queue(): head(0), tail(0) { }
         // copy control to manage pointers to QueueItems in the Queue
         Queue(const Queue &Q): head(0), tail(0)
                                       { copy_elems(Q); }
         Queue& operator=(const Queue&);
         ~Queue() { destroy(); }
              // return element from head of Queue
         // unchecked operation: front on an empty Queue is undefined
         Type& front()             { return head->item; }
         const Type &front() const { return head->item; }
         void push(const Type &);       // add element to back of Queue
         void pop ();                    // remove element from head of Queue
         bool empty () const {           // true if no elements in the Queue
             return head == 0;
         }
     private:
         QueueItem<Type> *head;         // pointer to first element in Queue
         QueueItem<Type> *tail;         // pointer to last element in Queue
         // utility functions used by copy constructor, assignment, and destructor
         void destroy();                // delete all the elements
         void copy_elems(const Queue&); // copy elements from parameter
     };

这里书上说到一点,应该引起注意的是:

当使用类模板的名字的时候,必须指定模板形参。这一规则有个例外:在类本身的作用域内部,可以使用类模板的非限定名。例如,在默认构造函数和复制构造函数的声明中,名字 QueueQueue<Type> 缩写表示。实质上,编译器推断,当我们引用类的名字时,引用的是同一版本。因此,复制构造函数定义其实等价于:

 Queue<Type>(const Queue<Type> &Q): head(0), tail(0)
                 { copy_elems(Q); }

 对于类模板的用法,我还不是很了解,这里先搁一搁。

 

6.零碎概念:

  模板类是类模板实例出来的产物。

  调用类模板成员函数比调用函数模板更为灵活。用模板形参定义的函数形参的实参允许进行常规转换。这个在STL第二章有比较详细的讲过。

 

7.在看到特定模板的友元关系这一节时,我看到一个比较有趣的东东:

template <class T> class Foo2;
     template <class T> void templ_fcn2(const T&);
     template <class Type> class Bar {
          // grants access to a single specific instance parameterized by char*
          friend class Foo2<char*>;
          friend void templ_fcn2<char*>(char* const &);
          // ...
     };

在这段代码中,我发现了这个模板: template <class T> void templ_fcn2(const T&) 的实例化很有趣,是这样子的: friend void templ_fcn2<char*>(char* const &)。char* const&?什么东西?这里会不会是书上写错了呢?其实这样写是对的。要理解这个,我们先来了解const char*, char const*, char*const的区别。

在网上找到一篇博文,写的简单明了:

(摘自http://www.cnblogs.com/jonnyyu/archive/2007/12/04/89766.html感谢原作者!)

 原文如下:

const char*, char const*, char*const的区别问题几乎是C++面试中每次都会有的题目。

事实上这个概念谁都有只是三种声明方式非常相似很容易记混。
Bjarne在他的The C++ Programming Language里面给出过一个助记的方法:
把一个声明从右向左读。

char  * const cp; ( * 读成 pointer to )
cp is a const pointer to char

const char * p;
p is a pointer to const char;

char const * p;
同上因为C++里面没有const*的运算符,所以const只能属于前面的类型。

同时,在C++标准规定,const关键字放在类型或变量名之前等价的。

这样三个的区别就清楚了。

不过,我再附上另外两篇博文,帮助大家更深入的理解const:

http://blog.youkuaiyun.com/oury/archive/2005/02/07/283836.aspx

(感谢作者)
所以,这里的const T&是一个引用类型的形参,实例化为char*型,就是一个const的指针指向char型。即为char *const&。

(这部分是我自己总结出来的,如有不足,恳请大家指出。)

 

8.总结模板的友元声明:(摘自C++primer)

1)普通友元:

非模板类或非模板函数可以是类模板的友元:

        template <class Type> class Bar {
         // grants access to ordinary, nontemplate class and function
         friend class FooBar;
         friend void fcn();
         // ...
     };
(2)一般模板友元关系:

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

        template <class Type> class Bar {
         // grants access to Foo1 or templ_fcn1 parameterized by any type
         template <class T> friend class Foo1;
         template <class T> friend void templ_fcn1(const T&);
         // ...
     };

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

(3)特定的模板友元关系:

除了将一个模板的所有实例设为友元,类也可以只授予对特定实例的访问权:

     template <class T> class Foo2;
     template <class T> void templ_fcn2(const T&);
     template <class Type> class Bar {
          // grants access to a single specific instance parameterized by char*
          friend class Foo2<char*>;
          friend void templ_fcn2<char*>(char* const &);
          // ...
     };

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

     template <class T> class Foo3;
     template <class T> void templ_fcn3(const T&);
     template <class Type> class Bar {
         // each instantiation of Bar grants access to the
         // version of Foo3 or templ_fcn3 instantiated with the same type
         friend class Foo3<Type>;
         friend void templ_fcn3<Type>(const Type&);
         // ...
     };
这些友元定义了 Bar 的特定实例与使用同一模板实参的 Foo3temp1_fcn3 的实例之间的友元关系。每个 Bar 实例有一个相关的 Foo3temp1_fcn3 友元:

     Bar<int> bi;    // Foo3<int> and templ_fcn3<int> are friends
     Bar<string> bs; // Foo3<string>, templ_fcn3<string> are friends

这个也好理解,不多说。

 

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

     template <class T> class A;
     template <class T> class B {
     public:
         friend class A<T>;      // ok: A is known to be a template
         friend class C;         // ok: C must be an ordinary, nontemplate class
         template <class S> friend class D; // ok: D is a template
         friend class E<T>;      // error: E wasn't declared as a template
         friend class F<int>;    // error: F wasn't declared as a template
      };

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


9.成员模板:

要注意的是,在类外定义时,成员模板的定义必须包含类模板形参以及自己的模板形参。首先是类模板形参表,后面接着成员自己的模板形参表。assign 函数定义的开头为

 

 

 

 

 

 

 

 

 

 

 


 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值