• 2009-02-24

    C语言类型声明 - [C/C++]

    C语言语法的设计在类型声明上有一个简单的原则, 即"变量怎么使用就怎么声明"。C语言声明的优先级规则如下:

    A. 解析声明从名字开始, 然后按照优先级顺序依次读取。B. 优先级从高到低依次是: B.1 括号括起来的部分优先级最高。B.2 后缀操作符(括号"()"表示函数,"[]"表示数组)B.3 前缀操作符*表示指针。C. Const和Volatile后紧跟类型符(int, long等)则作用于类型说明符,其他情况下作用于其左边的紧邻的指针星号。

    利用上述规则分析 char* const *(*next)(); 1. 首先从名字next开始. 2. next前边的*表示next是一个指针。 3. 然后把*next看做一个整体, 括号括起来, 其前面是一个*, 后边是(). 后缀优先, 所以"()"表示是一个函数。 4. 由前缀*看知道此函数的返回值是一个指针. 5. 是一个指向char* const类型的指针。

    另一个例子:char *(*(**foo [][8])())[] 的分析过程如下

          char *(*(**foo [][8])())[]
      char *(*(**foo [][8])())[] foo 是 … char
      char *(*(**foo [][8])())[] foo 是 一个数组 … char
      char *(*(**foo [][8])())[] foo 是 一个数组且成员为 8维数组 … char
      char *(*(**foo [][8])())[] foo 是 一个数组且成员为 8维数组且成员为 指针且指向 … char
      char *(*(**foo [][8])())[] foo 是 一个数组且成员为 8维数组且成员为 指针且指向 指针且指向 … char
      char *(*(**foo [][8])())[] foo 是 一个数组且成员为 8维数组且成员为 指针且指向 指针且指向 函数且返回 … char
      char *(*(**foo [][8])())[] foo 是 一个数组且成员为 8维数组且成员为 指针且指向 指针且指向 函数且返回 指针且指向 … char
      char *(*(**foo [][8])())[] foo 是 一个数组且成员为 8维数组且成员为 指针且指向 指针且指向 函数且返回 指针且指向 数组 … char
      char *(*(**foo [][8])())[] foo 是 一个数组且成员为 8维数组且成员为 指针且指向 指针且指向 函数且返回 指针且指向 数组且成员为 指针且指向 char

    一些其他的规则:

    1. 函数的返回值不能是一个函数,所以 foo()() 非法。2. 函数的返回值不能是一个数组,所以 foo()[] 非法 3. 数组里面不能有函数所以 foo[]()非法。

    1. 函数的返回值允许是一个函数指针,所以 int(*fun())() 合法。2.  函数的返回值允许上一个数组的指针,所以 int(*foo*()[] 合法 3. 数组里面运行有函数指针所以 int(*foo[])()合法 4. 数组里允许有其他数组,所以 int foo[][] 合法。

    typedef int(*fun())() 定义fun为一个函数指针, 返回int, 参数为空。这正体现了怎么使用就怎么声明的原则. 例如: char (*Jack)[20] 就定义了一个Jack, 可以这样使用"Jack=(char (*) [20]) malloc(20)" 也可以如下使用

    typedef char (*Jack)[20];  Jack x = (Jack)malloc(20);

  • hash_map<Key, Data, HashFcn, EqualKey, Alloc>

    Description

    Hash_map is a Hashed Associative Container that associates objects of type Key with objects of type Data. Hash_map is a Pair Associative Container, meaning that its value type is pair<const Key, Data>. It is also a Unique Associative Container, meaning that no two elements have keys that compare equal using EqualKey.

    Looking up an element in a hash_map by its key is efficient, so hash_map is useful for "dictionaries" where the order of elements is irrelevant. If it is important for the elements to be in a particular order, however, then map is more appropriate(map有缺省的less函数和=函数,所以会对存储的数据自动进行排序,如果是自定义类作为key,则必须重载less和=函数。而hash_map是根据hash值去查找的,所以效率高。).

    Example

    struct eqstr
    {
    bool operator()(const char* s1, const char* s2) const
    {
    return strcmp(s1, s2) == 0;
    }
    };

    int main()
    {
    hash_map<const char*, int, hash<const char*>, eqstr> months;

    months["january"] = 31;
    months["february"] = 28;
    months["march"] = 31;
    months["april"] = 30;
    months["may"] = 31;
    months["june"] = 30;
    months["july"] = 31;
    months["august"] = 31;
    months["september"] = 30;
    months["october"] = 31;
    months["november"] = 30;
    months["december"] = 31;

    cout << "september -> " << months["september"] << endl;
    cout << "april -> " << months["april"] << endl;
    cout << "june -> " << months["june"] << endl;
    cout << "november -> " << months["november"] << endl;
    }

    Definition

    Defined in the header hash_map, and in the backward-compatibility header hash_map.h. This class is an SGI extension; it is not part of the C++ standard.

    Template parameters

    Parameter Description Default
    Key The hash_map's key type. This is also defined as hash_map::key_type.  
    Data The hash_map's data type. This is also defined as hash_map::data_type.  
    HashFcn The hash function used by the hash_map. This is also defined as hash_map::hasher. hash<Key>
    EqualKey The hash_map key equality function: a binary predicate that determines whether two keys are equal. This is also defined as hash_map::key_equal. equal_to<Key>
    Alloc The hash_map's allocator, used for all internal memory management. alloc

    Model of

    Unique Hashed Associative Container, Pair Associative Container

    Type requirements

    Public base classes

    None.

    Members

    Member Where defined Description
    key_type Associative Container The hash_map's key type, Key.
    data_type Pair Associative Container The type of object associated with the keys.
    value_type Pair Associative Container The type of object, pair<const key_type, data_type>, stored in the hash_map.
    hasher Hashed Associative Container The hash_map's hash function.
    key_equal Hashed Associative Container Function object that compares keys for equality.
    pointer Container Pointer to T.
    reference Container Reference to T
    const_reference Container Const reference to T
    size_type Container An unsigned integral type.
    difference_type Container A signed integral type.
    iterator Container Iterator used to iterate through a hash_map. [1]
    const_iterator Container Const iterator used to iterate through a hash_map.
    iterator begin() Container Returns an iterator pointing to the beginning of the hash_map.
    iterator end() Container Returns an iterator pointing to the end of the hash_map.
    const_iterator begin() const Container Returns an const_iterator pointing to the beginning of the hash_map.
    const_iterator end() const Container Returns an const_iterator pointing to the end of the hash_map.
    size_type size() const Container Returns the size of the hash_map.
    size_type max_size() const Container Returns the largest possible size of the hash_map.
    bool empty() const Container true if the hash_map's size is 0.
    size_type bucket_count() const Hashed Associative Container Returns the number of buckets used by the hash_map.
    void resize(size_type n) Hashed Associative Container Increases the bucket count to at least n.
    hasher hash_funct() const Hashed Associative Container Returns the hasher object used by the hash_map.
    key_equal key_eq() const Hashed Associative Container Returns the key_equal object used by the hash_map.
    hash_map() Container Creates an empty hash_map.
    hash_map(size_type n) Hashed Associative Container Creates an empty hash_map with at least n buckets.
    hash_map(size_type n, 
    const hasher& h)
    Hashed Associative Container Creates an empty hash_map with at least n buckets, using h as the hash function.
    hash_map(size_type n,
    const hasher& h,
    const key_equal& k)
    Hashed Associative Container Creates an empty hash_map with at least n buckets, using h as the hash function and k as the key equal function.
    template <class InputIterator>
    hash_map(InputIterator f, InputIterator l)
    [2]
    Unique Hashed Associative Container Creates a hash_map with a copy of a range.
    template <class InputIterator>
    hash_map(InputIterator f, InputIterator l,
    size_type n)
    [2]
    Unique Hashed Associative Container Creates a hash_map with a copy of a range and a bucket count of at least n.
    template <class InputIterator>
    hash_map(InputIterator f, InputIterator l,
    size_type n, const hasher& h)
    [2]
    Unique Hashed Associative Container Creates a hash_map with a copy of a range and a bucket count of at least n, using h as the hash function.
    template <class InputIterator>
    hash_map(InputIterator f, InputIterator l,
    size_type n, const hasher& h,
    const key_equal& k)
    [2]
    Unique Hashed Associative Container Creates a hash_map with a copy of a range and a bucket count of at least n, using h as the hash function and k as the key equal function.
    hash_map(const hash_map&) Container The copy constructor.
    hash_map& operator=(const hash_map&) Container The assignment operator
    void swap(hash_map&) Container Swaps the contents of two hash_maps.
    pair<iterator, bool>
    insert(const value_type& x)
    Unique Associative Container Inserts x into the hash_map.
    template <class InputIterator>
    void insert(InputIterator f, InputIterator l)
    [2]
    Unique Associative Container Inserts a range into the hash_map.
    void erase(iterator pos) Associative Container Erases the element pointed to by pos.
    size_type erase(const key_type& k) Associative Container Erases the element whose key is k.
    void erase(iterator first, iterator last) Associative Container Erases all elements in a range.
    void clear() Associative Container Erases all of the elements.
    const_iterator find(const key_type& k) const Associative Container Finds an element whose key is k.
    iterator find(const key_type& k) Associative Container Finds an element whose key is k.
    size_type count(const key_type& k) const Unique Associative Container Counts the number of elements whose key is k.
    pair<const_iterator, const_iterator>
    equal_range(const key_type& k) const
    Associative Container Finds a range containing all elements whose key is k.
    pair<iterator, iterator>
    equal_range(const key_type& k)
    Associative Container Finds a range containing all elements whose key is k.
    data_type& 
    operator[](const key_type& k) [3]
    hash_map See below.
    bool operator==(const hash_map&, 
    const hash_map&)
    Hashed Associative Container Tests two hash_maps for equality. This is a global function, not a member function.

    New members

    These members are not defined in the Unique Hashed Associative Container and Pair Associative Container requirements, but are specific to hash_map.
    Member Description
    data_type& 
    operator[](const key_type& k) [3]
    Returns a reference to the object that is associated with a particular key. If the hash_map does not already contain such an object, operator[] inserts the default object data_type(). [3]

    Notes

    [1] Hash_map::iterator is not a mutable iterator, because hash_map::value_type is not Assignable. That is, if i is of type hash_map::iterator and p is of type hash_map::value_type, then *i = p is not a valid expression. However, hash_map::iterator isn't a constant iterator either, because it can be used to modify the object that it points to. Using the same notation as above, (*i).second = p is a valid expression.

    [2] This member function relies on member template functions, which at present (early 1998) are not supported by all compilers. If your compiler supports member templates, you can call this function with any type of input iterator. If your compiler does not yet support member templates, though, then the arguments must either be of type const value_type* or of type hash_map::const_iterator.

    [3] Since operator[] might insert a new element into the hash_map, it can't possibly be a const member function. Note that the definition of operator[] is extremely simple: m[k] is equivalent to (*((m.insert(value_type(k, data_type()))).first)).second. Strictly speaking, this member function is unnecessary: it exists only for convenience.

  • 在常规的编程任务中使用新的 <tuple> 库 作者:Danny Kalev

    原文出处:Tackle Common Programming Tasks Using the New <tuple> Library

      C++ 标准委员会目前正在进行标准库的更新和增强。Tuple 类型是最近添加到 C++ 标准中的内容之一。Tuple 是一个大小固定的异构对象集合。Tuple 类型非常强大,它有助于简化一些常见的编程任务。
      本文代码所依赖的常规编译器均支持 C++ 98 规范,但是目前<tulpe>库还并不一定是 IDE 标准库的一部分。因此,要想使用这个库必须到 Boost 下载并安装。但今后的大多数开发环境肯定都会支持<tuple>库的。

    如何模拟从一个函数中返回多个类型?如何同时进行多个值的赋值和比较?
     
    使用 <tuple> 库定义 tuple 对象并处理之。

    1、构造和初始化
      tuple 类型 tuple 类模板的特化或实例。目前的标准库支持 0-10个元素的 tuple。每个元素可以有不同的类型。在下面的例子中,t 被定义为 tuple 类型,它包含两个元素,类型分别为 int 和 double:

    #include <tuple>
    tuple <int, double> t(1, 3.14);

      为简单起见,我不使用名字空间的限定。tuple 所在的实际名字空间及其辅助函数是根据所使用的库声明的。Boost 库在 boost::tuples 中声明 tuple。 标准 C++ 通常会在 std 中声明。
    如果你省略初始化例程,那么将应用默认的初始化替代:

    tuple <std::string, int*> u; //initialized to: string(),0

    2、辅助函数
      为了得到 tuple 的元素个数,使用 tuple_size():

    int sz=tuple_size <tuple <int, const double, std::string> >::value;//3

    make_tuple() 用于构造 tuple 类型。该函数按照其参数创建一个 tuple 类型:

    void f(int i);
    T1=make_tuple(&f); // returns: tuple<void (*)(int)>
    T2=make_tuple("hi", 2); // tuple< const char (&)[3], int>

    tuple_element() 函数返回单个元素的类型。该函数以索引和 tuple 类型为参数:

    //获得第一个元素的类型
    T=tuple_element <0, tuple<int, int, char> >::type;//int

      如果你需要存取实际的元素,而非类型,那么就用 get<N>() 函数。注意 tuple 使用基于 0 的索引。

    tuple <int, double> t;
    int n=get<0>(t); //获得第一个元素
    get<1>(t)=0.5; //给第二个元素赋值

    3、现实世界中的 tuple
      下面让我们考察一下 tuple 类型的应用,假设你需要实现这样一个函数:将某个文件名转换为FILE * 和文件描述符。大家知道,C++ 不允许一个函数返回多个类型的值。通常的做法是定义两个名字稍有差别的函数来解决这个问题的,例如:

    int convert_filename(const char * path);
    FILE * fconvert_filename(const char * path);

      POSIX 库充满了这样的函数集。在这种情况下是不会用重载机制的,因为你无法定义仅有返回值不同的函数的重载版本。例如:

    int convert_filename(const char* path);
    FILE* convert_filename(const char* path); //出错

      通过使用 tuple 类型来包装两个返回类型,你可以模拟单个函数返回多个类型。像往常一样,使用 typedef 来隐藏繁琐的语法:

    typedef tuple<int, FILE *> file_t;
    file_t convert_filename(const char* path);

    在面向对象环境中,你也可以扩展 file_t 为一个适合的 fstream 对象。
      tuple 提供了一个优雅的解决方案来解决另外一个问题,就是用整型来仿真浮点值。现在你可以借助 tuple 用两个纯粹的整型替代:

    typedef tuple<__int64, int> Currency;

    例如,有一个 USD 类:

    class USD
    {
    private:
    Currency curr;
    public:
    explicit USD(__int64 d=0, int c=0): curr(d,c) {}
    //..
    };

      注意有一个非常重要的地方是数据成员声明为私有。该类的使用者将不会注意其实现已经改变,因为接口完好无损。
      这样做非常好,你可能会问:“难道不能把两个整型打包在一个定义良好的结构里来实现吗?”当然可以,但是,tuple 有一个结构所没有的优势:那就是它已经重载了关系操作符 <、>、== 等。因此,你可以象下面这样轻松地比较两个货币的值:

    bool operator==(const USD& u1, const USD& u2)
    {
    return u1.curr==u2.curr;
    }

      这里,当且仅当 u1 中的每一个元素与 u2 中对应的元素相等时,tuple 重载的 == 才返回true,例如:

    Currency curr1(100,99); 
    Currency curr2(100,98);
    Currency curr3(100,98);

    bool res=curr1==curr2; //false
    res=curr2==curr3; //true

    4、tuple 使用之最
      Tuple 有许多其它用途。例如,你可以象下面这样创建一个 tuple 引用和 cv 限定类型:

    int i; char c; 
    make_tuple(ref(i),ref(c)); // tuple <int &, char&>
    make_tuple(cref(i), c); // tuple <const int &, char>

      ref 类模板充当引用包装器。同样,cref 将引用包装成常量(const)对象。为了简化引用元素的 tuple 的创建过程,可以用 tie()函数:

    tie(i, c); //等同于:make_tuple(ref(i), ref(c));

      包含非常量引用元素的 tuple 可被用于将另外的 tuple “解包(unpack)”成实际的对象。

    tie(i, c)=make_tuple(1, ''a'');

    经过这样的赋值,i=1,c=''a''。这个技术在解包返回 tuple 的函数时很有用。

    使用 <map> 库创建关联容器 作者:Danny Kalev

    下载源代码 原文出处:Use the <map> Library to Create Associative Containers

      关系数据库,科学计算应用以及基于Web的系统常常需要类似 vector 的容器,其索引可以是任何数据类型,不一定是整数。这样的容器叫关联容器,或者 map。例如,目录服务应用可以将私人姓名作为索引来存储,电话号码作为其关联的值:
    directory["Harry"]=8225687;// 插入 "Harry" 并与他的电话号码关联
    iterator it=directory.find("Harry");// 获取 Harry 的电话号码

      其它关联容器的应用还包括将 URLs 映射到 IP 的 DNS 服务器,字典,库存清单,工资表等等。那么如何突破整型索引的局限,实现用其它数据类型作为索引的关联容器呢?答案是:使用 <map> 库创建和处理关联容器。

    Pair 和 Map
      最近的一篇文章中,我介绍了 tuple 的概念,它是不同类型元素的集合。在这篇文章中,有一个内容没有提到,那就是 C++98 标准库已经具备一个特殊的 tuple 类型——pair它将键值(也就是第一个元素)与某个值(第二个值)关联。例如:

    #include <utility> //definition of pair
    #include <string>

    pair <string, string> prof_and_course("Jones", "Syntax");
    pair <int, string> symbolic_const (0, "false");

    标准库还定义了一个辅助函数,方便 pair 类型的创建:

    string prof;
    string course;
    make_pair(prof,course);//returns pair <string,string>

    第一步:构造和初始化一个 map 对象
      假设你正在开发一个地址簿程序,地址簿包含姓名和 e-mail 地址。类模板 map 在 <map> 中定义i,它是一个使用类型对的关联容器,第一个元素是索引,第二个元素是关联的值。使用方法如下:

    #include <map>
    map <string, string> addresses;

    为了添加元素,使用下标算符:

    addresses["Paul W."]="paul@mail.com";

      这里,串“Paul W.”是索引或键值,“paul@mail.com”是其关联的值。如果该 map 已经包含了此键值,那么当前所关联的值不会改变: 

    addresses["Paul W."]="newaddr@com.net"; // 不起作用
    Note: The fully defined STL map defines a comparison operator on the map declaration so that the indexes can be ordered. This is used to provide a default comparison operator for the data type. In the bove example, the key type is an integer and the C++ "equals" (=) and "less than"
    operator (<) can operate on an integer so the example works. The use of the STL algorithm std::less<> can be specified
    explicitly as in the following declaration: std::map<int, string, std::less< int > >. If defining your own class as the index (first value), C++ will not
    know how to perform the comparison, thus you will have to provide an operator to perform this function. The first element in a map can be a class or even another STL container
    such as a pair. If using a class, the class must provide an overloaded "equals" (=) and "less than" operator (<).

    第二步:搜索
      在不插入元素的情况下,如果你想检查某个元素是否存在,可以使用 find()成员函数。find()有两个重载的版本:

    iterator find(const key_type& k);
    const_iterator find(const key_type& k) const;

    通常,用 typedef 可以使代码更可读一些:

    typedef map <string, string>::const_iterator CIT;

    CIT cit=addresses.find("Paul W.");
    if (cit==addresses.end())
    cout << "sorry, no such key" << endl;
    else
    cout << cit->first << ''\t'' << cit->second << endl;

    表达式中 cit->first 和 cit->second 分别返回键值及其关联的值

    第三步:元素遍历
      现在让我们看一个更现实的情况。假设你正在经营一家旅行社,每一个代理做一单业务都可以获得奖金。这些代理的信息存储在某个文件中,其格式如下:

    Bob 35
    Bob 90
    Jane 80.25
    Sue 100
    Jane 65.5

      你的应用程序必须汇总所有代理的奖金并将每个代理的奖金总数显示出来.首先,创建一个 map,然后读取该数据文件:

    map <string, double> bonuses;
    string agent;
    double bonus=0;

    ifstream bonusfile("bonuses.dat");
    if(!bonusfile)
    {
    // 报告出错信息并终止程序
    }

    while (bonusfile >> agent >> bonus)
    {
    bonuses[agent]+=bonus;// 累加每个代理的奖金
    }

      不管理相不相信,就这么简单!且让我们来分析一下该循环。打开数据文件之后,while 循环读取每个值对,并将其存入 agent 和 bouns 对象。接着,它将 agent 和 bouns 插入到该 map。此处的关键技巧是:如果键值(agent)已经存在,那么 += 操作符便将最新读取的 bouns 累加到存储在 map 中当前的 bouns 中。因为表达式:

    map[key]

      返回与键值关联的值。当重载的 += 操作符被调用时,该值便被累加到新读取的 bouns 中。最后累加的和覆盖 map 中旧的关联值。记住:当你使用下标算符机制时,纯粹的赋值操作并不会改写现有的值,只有重载的 += 才这么做。

    幸运的是,map 并不包含一个必须要初始化的键值,表达式:

    map[key]

      返回默认的初始化 T,而 T 在上述例子中是 double 类型。默认的初始值为 0。至此,map 包含代理及其奖金汇总值对。下面的循环用来显示这些值对,输出如图一所示:

    for(CIT p=bonuses.begin(); p!=bonuses.end(); ++p)
    {
    cout << p->first <<''\t'' << p->second <<endl;
    }


    图一 代理奖金汇总数

    散列的关联容器
      标准库目前还不提供散列关联容器。这种容器可以大大改进性能,因为它们使用散列算法从原来的字符串索引派生短键值。但是,许多 IDEs 提供非标准散列容器扩展。值得庆幸的是,新的 C++0X 标准弥补了这一点,在 C++ 中添加了一组散列容器和与之相关的算法。

    使用 <multimap> 库创建重复键关联容器 作者:Danny Kalev

    原文出处:Use multimap to Create Associative Containers with Duplicate Keys

      在“使用 <map> 库创建关联容器”一文中,我们讨论了标准库中的 map 关联容器。但那只是 map 容器的一部分。标准库还定义了一个 multimap 容器,它与 map 类似,所不同的是它允许重复键。这个属性使得 multimap 比预想的要更有用:比如在电话簿中相同的人可以有两个以上电话号码,文件系统中可以将多个符号链接映射到相同的物理文件,或DNS服务器可以将几个URLs映射到相同的IP地址。在这些场合,你可以象下面这样:
    // 注: 伪码
    multimap <string, string> phonebook;
    phonebook.insert("Harry","8225687"); // 家里电话
    phonebook.insert("Harry","555123123"); // 单位电话
    phonebook.insert("Harry"," 2532532532"); // 移动电话

      在 multimap 中能存储重复键的能力大大地影响它的接口和使用。那么如何创建非唯一键的关联容器呢?答案是使用在 <map> 库中定义的 multimap 容器。

    提出问题
      与 map 不同,multimap 可以包含重复键。这就带来一个问题:重载下标操作符如何返回相同键的多个关联值?以下面的伪码为例:

    string phone=phonebook["Harry];

      标准库设计者的解决这个问题方法是从 multimap 中去掉下标操作符。因此,需要用不同的方法来插入和获取元素以及和进行错误处理。

    插入
      假设你需要开发一个 DNS 后台程序(也就是 Windows 系统中的服务程序),该程序将 IP 地址映射匹配的 URL 串。你知道在某些情况下,相同的 IP 地址要被关联到多个 URLs。这些 URLs 全都指向相同的站点。在这种情况下,你应该使用 multimap,而不是 map。例如:

    #include <map>
    #include <string>

    multimap <string, string> DNS_daemon;

      用 insert() 成员函数而不是下标操作符来插入元素。insert()有一个 pair 类型的参数。在“使用 <map> 库创建关联容器”中我们示范了如何使用 make_pair() 辅助函数来完成此任务。你也可以象下面这样使用它:

    DNS_daemon.insert(make_pair("213.108.96.7","cppzone.com"));
    DNS_daemon.insert(std::pair<string,string>("213.108.96.7","cppzone1.com"));
    // 3) Assignment using member function insert() and "value_type()"
    DNS_daemon.insert(multimap<string,string>::value_type("213.108.96.7","cppzone2.com"));

      在上面的 insert()调用中,串 “213.108.96.7”是键,“cppzone.com”是其关联的值。以后插入的是相同的键,不同的关联值:

    DNS_daemon.insert(make_pair("213.108.96.7","cppluspluszone.com"));

      因此,DNS_daemon 包含两个用相同键值的元素。注意 multimap::insert() 和 map::insert() 返回的值是不同的。

    typedef pair <const Key, T> value_type;
    iterator insert(const value_type&); // #1 multimap

    pair <iterator, bool> insert(const value_type&); // #2 map

      multimap::insert()成员函数返回指向新插入元素的迭代指针,也就是 iterator(multimap::insert()总是能执行成功)。但是 map::insert() 返回 pair<iterator, bool>,此处 bool 值表示插入操作是否成功。

    查找单个值
      与 map 类似,multimap 具备两个版本重载的 find()成员函数:

    iterator find(const key_type& k);
    const_iterator find(const key_type& k) const;

    find(k) 返回指向第一个与键 k 匹配的 pair 的迭代指针,这就是说,当你想要检查是否存在至少一个与该键关联的值时,或者只需第一个匹配时,这个函数最有用。例如:

    typedef multimap <string, string> mmss;
    void func(const mmss & dns)
    {
    mmss::const_iterator cit=dns.find("213.108.96.7");
    if (cit != dns.end())
    cout <<"213.108.96.7 found" <<endl;
    else
    cout <<"not found" <<endl;
    }

    处理多个关联值
      count(k) 成员函数返回与给定键关联的值得数量。下面的例子报告了有多少个与键 “213.108.96.7” 关联的值:

    cout<<dns.count("213.108.96.7") //output: 2
    <<" elements associated"<<endl;

      为了存取 multimap 中的多个值,使用 equal_range()、lower_bound()和 upper_bound()成员函数:
    equal_range(k):该函数查找所有与 k 关联的值。返回迭代指针的 pair,它标记开始和结束范围。下面的例子显示所有与键“213.108.96.7”关联的值:

    typedef multimap <string, string>::const_iterator CIT; 
    typedef pair<CIT, CIT> Range;
    Range range=dns.equal_range("213.108.96.7");
    for(CIT i=range.first; i!=range.second; ++i)
    cout << i->second << endl; //output: cpluspluszone.com
    // cppzone.com

      lower_bound() 和 upper_bound():lower_bound(k) 查找第一个与键 k 关联的值,而 upper_bound(k) 是查找第一个键值比 k 大的元素。下面的例子示范用 upper_bound()来定位第一个其键值大于“213.108.96.7”的元素。通常,当键是一个字符串时,会有一个词典编纂比较:

    dns.insert(make_pair("219.108.96.70", "pythonzone.com"));
    CIT cit=dns.upper_bound("213.108.96.7");
    if (cit!=dns.end()) //found anything?
    cout<<cit->second<<endl; //display: pythonzone.com

    如果你想显示其后所有的值,可以用下面这样的循环:

    // 插入有相同键的多个值
    dns.insert(make_pair("219.108.96.70","pythonzone.com"));
    dns.insert(make_pair("219.108.96.70","python-zone.com"));

    // 获得第一个值的迭代指针
    CIT cit=dns.upper_bound("213.108.96.7");

    // 输出: pythonzone.com,python-zone.com
    while(cit!=dns.end())
    {
       cout<<cit->second<<endl;
       ++cit;
    }
    结论
      虽然 map 和 multimap 具有相同的接口,其重要差别在于重复键,设计和使用要区别对待。此外,还要注意每个容器里 insert()成员函数的细微差别。

      作者简介
      Danny Kalev 是一名通过认证的系统分析师,专攻 C++ 和形式语言理论的软件工程师。1997 年到 2000 年期间,他是 C++ 标准委员会成员。最近他以优异成绩完成了他在普通语言学研究方面的硕士论文。 业余时间他喜欢听古典音乐,阅读维多利亚时期的文学作品,研究 Hittite、Basque 和 Irish Gaelic 这样的自然语言。其它兴趣包括考古和地理。Danny 时常到一些 C++ 论坛并定期为不同的 C++ 网站和杂志撰写文章。他还在教育机构讲授程序设计语言和应用语言课程。

    附STL文档中Pair的部分说明

    Category: containerscomponent type: concept

    Description

    A Pair Associative Container is an Associative Container that associates a key with some other object. The value type of a Pair Associative Container is pair<const key_type, data_type>. [1]

    Refinement of

    Associative Container

    Associated types

    One new type is introduced, in addition to the types defined in the Associative Container requirements. Additionally, Pair Associative Container introduces one new type restriction
    Key type X::key_type The type of the key associated with X::value_type.
    Data type X::data_type The type of the data associated with X::value_type. A Pair Associative Container can be thought of as a mapping from key_type to data_type.
    Value type X::value_type The type of object stored in the container. The value type is required to be pair<const key_type, data_type>.

    Notation

    X A type that is a model of Pair Associative Container
    a Object of type X
    t Object of type X::value_type
    d Object of type X::data_type
    k Object of type X::key_type
    p, q Object of type X::iterator

    Definitions

    Valid expressions

    None, except for those defined in the Associative Container requirements.

    Expression semantics

    Complexity guarantees

    Invariants

    Models

    Notes

    [1] The value type must be pair<const key_type, data_type>, rather than pair<key_type, data_type>, because of the Associative Container invariant of key immutability. The data_type part of an object in a Pair Associative Container may be modified, but the key_type part may not be. Note the implication of this fact: a Pair Associative Container cannot provide mutable iterators (as defined in the Trivial Iterator requirements), because the value type of a mutable iterator must be Assignable, and pair<const key_type, data_type> is not Assignable. However, a Pair Associative Container can provide iterators that are not completely constant: iterators such that the expression (*i).second = d is valid.

    See also Associative Container, Simple Associative Container

  • 一个私有的或保护的派生类不是子类,因为非公共的派生类不能做基类能做的所有的事。例如,下面的代码定义了一个私有继承基类的类:
    #include <iostream.h>
    class Animal
    {
    public:
     Animal(){}
     void eat(){cout<<"eat\n";}
    };

    class Giraffe:private Animal
    {
    public:
     Giraffe(){}
     void StrechNeck(double)
     {cout<<"strech neck \n";}
    };

    class Cat:public Animal
    {
    public:
     Cat(){}
     void Meaw(){cout<<"meaw\n";}
    };
    void Func(Animal& an)
    {
     an.eat();
    }
    void main()
    {
     Cat dao;
     Giraffe gir;
     Func(dao);
     //Func(gir);//error
     gir.eat();
    }

      函数Func()要用一个Animal类型的对象, 但调用Func(dao)实际上传递的是Cat类的对象。因为Cat是公共继承Animal类, 所以Cat类对象拥有Animal的所有成员的使用。Animal 对象可以做的事,Cat对象也可以做。
      但是,对于gir对象就不一样。Giraffe类私有继承了Animal类,意味着对象gir不能直接访问Animal类的成员。其实,在gir对象空间中,包含有Animal类的对象,只是无法让其公开访问。
      公有继承就像是三口之家的小孩,饱受父母的温暖,享有父母的一切(public和protected的成员)。 其中保护的成员不能被外界所享有,但可以为小孩所拥有。 只是父母还是有其一点点隐私(private成员)不能为小孩所知道。
      私有继承就像是离家出走的小孩,一个人在外面飘泊。他(她)不能拥有父母的住房和财产(如:gir.eat()是非法的), 在外面自然也就不能代表其父母,甚至他(她)不算是其父母的小孩。但是在他(她)的身体中,流淌着父母的血液,所以,在小孩自己的行为中又有其与父母相似的成分。
      例如,下面的代码中,Giraffe继承了Animal类,Giraffe的成员函数可以访问像Animal对象那样访问其Animal成员:

        //**********************
        //**   ch17_5.cpp  **
        //**********************

        #include

        class Animal
        {
         public:
          Animal(){}
          void eat(){ cout <<"eat.\n"; }
        };

        class Giraffe :private Animal
        {
         public:
          Giraffe(){}
          void StretchNeck(){ cout <<"stretch neck.\n"; }
          void take(){ eat(); } //ok
        };

        void Func(Giraffe & an)
        {
         an.take();
        }

        void main()
        {
         Giraffe gir;
         gir.StretchNeck();
         Func(gir); //ok
        }

      运行结果为:
        stretch neck.
        eat.

      上例中,gir对象就好比是小孩。eat()成员函数是其父母的行为,take()成员函数是小孩的行为,在该行为中,渗透着父母的行为。但是小孩无法直接使用eat()成员函数,因为,离家出走的他(她)无法拥有其父母的权力。
      保护继承与私有继承类似,继承之后的类相对于基类来说是独立的;保护继承的类对象,在公开场合同样不能使用基类的成员:
        #include
        class Animal
        {
         public:
          Animal(){}
          void eat(){cout<<"eat\n";}
        };
        class Giraffe:protected Animal
        {
         Giraffe(){}
         void StrechNeck(double){cout<<"strechneck\n";}
         void take()
         {
          eat(); //ok
         }
        };
        void main()
        {
         Giraffe gir;
         gir.eat(); //error
         gir.take(); //ok
         gir.StretchNeck();
        }

      派生类的三种继承方式小结:
      公有继承public)、 私有继承(private)和保护继承(protected)是常用的三种继承方式。
    1.对于公有继承方式:
      ·基类成员对其对象的可见性与一般类及其对象的可见性相同,公有成员可见,其他成员不可见。这里保护成员与私有成员相同。
      ·基类成员对派生类的可见性对派生类来说,基类的公有成员和保护成员可见:基类的公有成员和保护成员作为派生类的成员时,它们都保持原有的状态;基类的私有成员不可见:基类的私有成员仍然是私有的,派生类不可访问基类中的私有成员。
      ·基类成员对派生类对象的可见性对派生类对象来说,基类的公有成员是可见的,其他成员是不可见。
      所以,在公有继承时,派生类的对象可以访问基类中的公有成员;派生类的成员函数可以访问基类中的公有成员和保护成员。
    2.对于私有继承方式:
      ·基类成员对其对象的可见性与一般类及其对象的可见性相同,公有成员可见,其他成员不可见。
      ·基类成员对派生类的可见性对派生类来说,基类的公有成员和保护成员是可见的:基类的公有成员和保护成员都作为派生类的私有成员,并且不能被这个派生类的子类所访问;基类的私有成员是不可见的:派生类不可访问基类中的私有成员。
      ·基类成员对派生类对象的可见性对派生类对象来说,基类的所有成员都是不可见的。
      所以,在私有继承时,基类的成员只能由直接派生类访问,而无法再往下继承。
    3.对于保护继承方式:
      这种继承方式与私有继承方式的情况相同。两者的区别仅在于对派生类的成员而言,
      ·基类成员对其对象的可见性与一般类及其对象的可见性相同,公有成员可见,其他成员不可见。
      ·基类成员对派生类的可见性对派生类来说,基类的公有成员和保护成员是可见的:基类的公有成员和保护成员都作为派生类的保护成员,并且不能被这个派生类的子类所访问;基类的私有成员是不可见的:派生类不可访问基类中的私有成员。
      ·基类成员对派生类对象的可见性对派生类对象来说,基类的所有成员都是不可见的。
      所以,在保护继承时,基类的成员也只能由直接派生类访问,而无法再往下继承。

    本章小结
       C++支持多重继承,从而大大增强了面向对象程序设计的能力。
      多重继承是一个类从多个基类派生而来的能力。派生类实际上获取了所有基类的特性。
      当一个类是两个或多个基类的派生类时,必须在派生类名和冒号之后,列出所有基类的类名,基类间用逗号隔开。
      派生类的构造函数必须激活所有基类的构造函数,并把相应的参数传递给它们。
      派生类可以是另一个类的基类,这样,相当于形成了一个继承链,当派生类的构造函数被激活时,它的所有基类的构造函数也都会被激活。
      在面向对象的程序设计中,继承和多重继承一般指公共继承。在无继承的类中,protected和private控制符是没有差别的。在继承中,基类的private对所有的外界都屏蔽(包括自己的派生类), 基类的protected控制符对应用程序是屏蔽的,但对其派生类是可访问的。
      保护继承和私有继承只是在技术上讨论时有其一席之地。

  • 总结:

        1. 互斥量与临界区的作用非常相似,但互斥量是可以命名的,也就是说它可以跨越进程使用。所以创建互斥量需要的资源更多,所以如果只为了在进程内部是用的话使用临界区会带来速度上的优势并能够减少资源占用量。因为互斥量是跨进程的互斥量一旦被创建,就可以通过名字打开它。

        2. 互斥量(Mutex),信号灯(Semaphore),事件(Event)都可以被跨越进程使用,临界区只可以在进程内的不同线程间使用。前三者可以使用WaitForSingleObject来等待进程和线程退出。

        3. 通过互斥量可以对指定资源被进行独占的方式使用。信号量可以进行访问者个数限制的访问。“事件”有信号后可以通知多个等待者,而互斥量只能通知一个。

       4. Linux(POSIX标准):

    • mutexes - Mutual exclusion lock: Block access to variables by other threads. This enforces exclusive access by a thread to a variable or set of variables. (Mutexes can be applied only to threads in a single process and do not work between processes as do semaphores. )
    • joins - Make a thread wait till others are complete (terminated).
    • condition variables - data type pthread_cond_t

    MSDN中关于此有如下表:

    TypeDescription
    EventNotifies one or more waiting threads that an event has occurred. For more information, see Event Objects.
    MutexCan be owned by only one thread at a time, enabling threads to coordinate mutually exclusive access to a shared resource. For more information, see Mutex Objects.
    SemaphoreMaintains a count between zero and some maximum value, limiting the number of threads that are simultaneously accessing a shared resource. For more information, see Semaphore Objects.
    Waitable timerNotifies one or more waiting threads that a specified time has arrived. For more information, see Waitable Timer Objects.

    1、  Event
    用事件(Event)来同步线程是最具弹性的了。一个事件有两种状态:激发状态和未激发状态。也称有信号状态和无信号状态。事件又分两种类型:手动重置事件和自动重置事件。手动重置事件被设置为激发状态后,会唤醒所有等待的线程,而且一直保持为激发状态,直到程序重新把它设置为未激发状态。自动重置事件被设置为激发状态后,会唤醒“一个”等待中的线程,然后自动恢复为未激发状态。所以用自动重置事件来同步两个线程比较理想。MFC中对应的类为CEvent.。CEvent的构造函数默认创建一个自动重置的事件,而且处于未激发状态。共有三个函数来改变事件的状态:SetEvent,ResetEvent和PulseEvent。用事件来同步线程是一种比较理想的做法,但在实际的使用过程中要注意的是,对自动重置事件调用SetEvent和PulseEvent有可能会引起死锁,必须小心。

    2、  Critical Section
    使用临界区域的第一个忠告就是不要长时间锁住一份资源。这里的长时间是相对的,视不同程序而定。对一些控制软件来说,可能是数毫秒,但是对另外一些程序来说,可以长达数分钟。但进入临界区后必须尽快地离开,释放资源。如果不释放的话,会如何?答案是不会怎样。如果是主线程(GUI线程)要进入一个没有被释放的临界区,呵呵,程序就会挂了!临界区域的一个缺点就是:Critical Section不是一个核心对象,无法获知进入临界区的线程是生是死,如果进入临界区的线程挂了,没有释放临界资源,系统无法获知,而且没有办法释放该临界资源。这个缺点在互斥器(Mutex)中得到了弥补。Critical Section在MFC中的相应实现类是CcriticalSection。CcriticalSection::Lock()进入临界区,CcriticalSection::UnLock()离开临界区。

    3、  Mutex
    互斥器的功能和临界区域很相似。区别是:Mutex所花费的时间比Critical Section多的多,但是Mutex是核心对象(Event、Semaphore也是),可以跨进程使用,而且等待一个被锁住的Mutex可以设定TIMEOUT,不会像Critical Section那样无法得知临界区域的情况,而一直死等。MFC中的对应类为CMutex。Win32函数有:创建互斥体CreateMutex() ,打开互斥体OpenMutex(),释放互斥体ReleaseMutex()。Mutex的拥有权并非属于那个产生它的线程,而是最后那个对此Mutex进行等待操作(WaitForSingleObject等等)并且尚未进行ReleaseMutex()操作的线程。线程拥有Mutex就好像进入Critical Section一样,一次只能有一个线程拥有该Mutex。如果一个拥有Mutex的线程在返回之前没有调用ReleaseMutex(),那么这个Mutex就被舍弃了,但是当其他线程等待(WaitForSingleObject等)这个Mutex时,仍能返回,并得到一个WAIT_ABANDONED_0返回值。能够知道一个Mutex被舍弃是Mutex特有的。

    4、  Semaphore
    信号量是最具历史的同步机制。信号量是解决producer/consumer问题的关键要素。对应的MFC类是Csemaphore。Win32函数CreateSemaphore()用来产生信号量。ReleaseSemaphore()用来解除锁定。Semaphore的现值代表的意义是目前可用的资源数,如果Semaphore的现值为1,表示还有一个锁定动作可以成功。如果现值为5,就表示还有五个锁定动作可以成功。当调用Wait…等函数要求锁定,如果Semaphore现值不为0,Wait…马上返回,资源数减1。当调用ReleaseSemaphore()资源数加1,当时不会超过初始设定的资源总数.

  • C++语言允许重载increment 和 decrement操作符的两种形式。然而有一个句法上的问题,重载函数间的区别决定于它们的参数类型上的差异,但是不论是increment或decrement的前缀还是后缀都只有一个参数。为了解决这个语言问题,C++规定后缀形式有一个int类型参数(此参数没有任何意义,仅用于区别),当函数被调用时,编译器传递一个0做为int参数的值给该函数
    class UPInt { // "unlimited precision int"
    public:
    UPInt& operator++(); // ++ 前缀
    const UPInt operator++(int); // ++ 后缀
    UPInt& operator--(); // -- 前缀
    const UPInt operator--(int); // -- 后缀
    UPInt& operator+=(int); // += 操作符,UPInts
    // 与ints 相运算
    ...
    };
    UPInt i;
    ++i; // 调用 i.operator++();
    i++; // 调用 i.operator++(0);
    --i; // 调用 i.operator--();
    i--; // 调用 i.operator--(0);
    这个规范有一些古怪,不过你会习惯的。而尤其要注意的是:这些操作符前缀与后缀形式返回值类型是不同的前缀形式返回一个引用,后缀形式返回一个const类型。下面我们将讨论++操作符的前缀与后缀形式,这些说明也同样适用于--操作符。

    从你开始做C程序员那天开始,你就记住increment的前缀形式有时叫做“增加然后取回”,后缀形式叫做“取回然后增加”。这两句话非常重要,因为它们是increment前缀与后缀的形式上的规范。
    // 前缀形式:增加然后取回值
    UPInt& UPInt::operator++()
    {
    *this += 1; // 增加
    return *this; // 取回值
    }
    // postfix form: fetch and increment
    const UPInt UPInt::operator++(int)
    {
    UPInt oldValue = *this; // 取回值
    ++(*this); // 增加
    return oldValue; // 返回被取回的值
    }
    后缀操作符函数没有使用它的参数。它的参数只是用来区分前缀与后缀函数调用。如果你没有在函数里使用参数,许多编译器会显示警告信息,很令人讨厌。为了避免这些警告信息,一种经常使用的方法是省略掉你不想使用的参数名称;如上所示。很明显一个后缀increment必须返回一个对象(它返回的是增加前的值),但是为什么是const对象呢?假设不是const对象,下面的代码就是正确的:
    UPInt i;
    i++++; // 两次increment后缀
    这组代码与下面的代码相同:
    i.operator++(0).operator++(0);
    很明显,第一个调用的operator++函数返回的对象调用了第二个operator++函数。有两个理由导致我们应该厌恶上述这种做法,第一是与内置类型行为不一致。当设计一个类遇到问题时,一个好的准则是使该类的行为与int类型一致。而int类型不允许连续进行两次后缀increment:
    int i;
    i++++; // 错误!
    第二个原因是使用两次后缀increment所产生的结果与调用者期望的不一致。如上所示,第二次调用operator++改变的值是第一次调用返回对象的值,而不是原始对象的值。因此如果:
    i++++;
    是合法的,i将仅仅增加了一次。这与人的直觉相违背,使人迷惑(对于int类型和UPInt都是一样),所以最好禁止这么做。C++禁止int类型这么做,同时你也必须禁止你自己写的类有这样的行为。最容易的方法是让后缀increment 返回const对象。当编译器遇到这样的代码:
    i++++; // same as
    i.operator++(0).operator++(0);
    它发现从第一个operator++函数返回的const对象又调用operator++函数,然而这个函数是一个non-const成员函数,所以const对象不能调用这个函数。如果你原来想过让一个函数返回const对象没有任何意义,现在你就知道有时还是有用的,后缀increment和decrement就是例子。(更多的例子参见Effective C++ 条款21)

    如果你很关心效率问题,当你第一次看到后缀increment函数时,你可能觉得有些问题。这个函数必须建立一个临时对象做为它的返回值,(参见条款M19),上述实现代码建立了一个显示的临时对象(oldValue),这个临时对象必须被构造并在最后被析构。前缀increment函数没有这样的临时对象。由此得出一个令人惊讶的结论,如果仅为了提高代码效率,UPInt的调用者应该尽量使用前缀increment,少用后缀increment,除非确实需要使用后缀increment。让我们明确一下,当处理用户定义的类型时,尽可能地使用前缀increment,因为它的效率较高

    我们再观察一下后缀与前缀increment操作符。它们除了返回值不同外,所完成的功能是一样的,即值加一。简而言之,它们被认为功能一样。那么你如何确保后缀increment和前缀increment的行为一致呢?当不同的程序员去维护和升级代码时,有什么能保证它们不会产生差异?除非你遵守上述代码里的原则,这才能得到确保。这个原则是后缀increment和decrement应该根据它们的前缀形式来实现。你仅仅需要维护前缀版本,因为后缀形式自动与前缀形式的行为一致。

    正如你所看到的,掌握前缀和后缀increment和decrement是容易的。一旦了解了他们正确的返回值类型以及后缀操作符应该以前缀操作符为基础来实现的规则,就足够了。

  • 定义在全局或名字空间范围内的全局变量,在一个类中被声明为static,或,在一个文件范围被定义为static。这三种变量统称“非局部静态对象”。这三种对象的初始化顺序“C++”未作明确规定,所以我们不能作任何假设也不能依赖这种假设,如下程序:

    class FileSystem { ... }; // 在个类在你

    FileSystem theFileSystem;

    class Directory { // 由程序库的用户创建
    public:
    Directory();
    ...
    };
    Directory::Directory()
    {
    通过调用theFileSystem 的成员函数
    创建一个Directory 对象;
    }

    Directory tempDir; // 临时文件目录

    tempDir和theFileSystem都是全局变量,其初始化顺序未定,但是tempDir在其初始化时的构造函数里用到了theFileSystem变量,如果此变量尚未进行初始化,则出现问题。

    如何解决这个问题呢?我们知道,虽然关于 "非局部" 静态对象什么时候被初始化,C++几乎没有做过说明;但对于函数中的静态对象(即,"局部" 静态对象)什么时候被初始化,C++却明确指出:它们在函数调用过程中初次碰到对象的定义时被初始化。所以,如果你不对非局部静态对象直接访问,而用返回局部静态对象引用的函数调用来代替,就能保证从函数得到的引用指向的是被初始化了的对象。这样做的另一个好处是,如果这个模拟非局部静态对象的函数从没有被调用,也就永远不会带来对象构造和销毁的开销;而对于非局部静态对象来说就没有这样的好事。

    上边的程序实现方式修改如下:

    class FileSystem { ... }; // 同前
    FileSystem& theFileSystem() // 这个函数代替了之前的theFileSystem 对象
    {
    static FileSystem tfs; // 定义和初始化
    // 局部静态对象
    // (tfs = "the file system")
    return tfs; // 返回它的引用
    }

    class Directory { ... }; // 同前
    Directory::Directory()
    {
    同前,除了theFileSystem 被
    theFileSystem()代替;
    }

    Directory& tempDir() // 这个函数代替了之前的tempDir 对象
    { // static Directory td; // 定义和初始化
    // 局部静态对象
    return td; // 返回它的引用
    }

    这种技术 ---- 有时称为 "单一模式"(译注:即Singleton pattern,参见"Design Patterns" 一书)---- 本身很简单。首先,把每个非局部静态对象转移到函数中,声明它为static。其次,让函数返回这个对象的引用。这样,用户将通过函数调用来指明对象。换句话说,用函数内部的static 对象取代了非局部静态对象。(参见条款M26)

  • 一是C风格的类型转换过于粗鲁,能允许你在任何类型之间进行转换。不过如果要进行更精确的类型转换,这会是一个优点。在这些类型转换中存在着巨大的不同,例如把一个指向const对象的指针(pointer-to-const-object)转换成指向非const对象的指针(pointer-to-non-const-object)(即一个仅仅去除const的类型转换),把一个指向基类的指针转换成指向子类的指针(即完全改变对象类型)。传统的C风格的类型转换不对上述两种转换进行区分。(这一点也不令人惊讶,因为C风格的类型转换是为C语言设计的,而不是为C++语言设计的)。二来C风格的类型转换在程序语句中难以识别。在语法上,类型转换由圆括号和标识符组成,而这些可以用在C++中的任何地方。人工阅读很可能忽略了类型转换的语句,而利用象grep的工具程序也不能从语句构成上区分出它们来。

    C++通过引进四个新的类型转换操作符克服了C风格类型转换的缺点,这四个操作符是, static_cast, const_cast, dynamic_cast, 和reinterpret_cast。在大多数情况下,对于这些操作符你只需要知道原来你习惯于这样写,(type) expression 而现在你总应该这样写:static_cast<type>(expression) 例如,假设你想把一个int转换成double,以便让包含int类型变量的表达式产生出浮点数值的结果。如果用C风格的类型转换,你能这样写:int firstNumber, secondNumber;
    ...
    double result = ((double)firstNumber)/secondNumber;
    如果用上述新的类型转换方法,你应该这样写:
    double result = static_cast<double>(firstNumber)/secondNumber;
    这样的类型转换不论是对人工还是对程序都很容易识别。

    static_cast在功能上基本上与C风格的类型转换一样强大,含义也一样。它也有功能上限制。例如,你不能用static_cast象用C风格的类型转换一样把struct转换成int类型或者把double类型转换成指针类型,另外,static_cast不能从表达式中去除const属性,因为另一个新的类型转换操作符const_cast有这样的功能。

    其它新的C++类型转换操作符被用在需要更多限制的地方。const_cast用于类型转换掉表达式的const或volatileness属性。通过使用const_cast,你向人们和编译器强调你通过类型转换想做的只是改变一些东西的constness或者 volatileness属性。这个含义被编译器所约束。如果你试图使用const_cast来完成修改constness 或者volatileness属性之外的事情,你的类型转换将被拒绝。下面是一些例子:
    class Widget { ... };
    class SpecialWidget: public Widget { ... };
    void update(SpecialWidget *psw);
    SpecialWidget sw; // sw 是一个非const 对象。
    const SpecialWidget& csw = sw; // csw 是sw的一个引用
    // 它是一个const 对象
    update(&csw); // 错误!不能传递一个const SpecialWidget* 变量给一个处理SpecialWidget*类型变量的函数
    update(const_cast<SpecialWidget*>(&csw));// 正确,csw的const被显示地转换掉(csw和sw两个变量值在update函数中能被更新)
    update((SpecialWidget*)&csw);// 同上,但用了一个更难识别的C风格的类型转换
    Widget *pw = new SpecialWidget;
    update(pw); // 错误!pw的类型是Widget*,但是update函数处理的是SpecialWidget*类型
    update(const_cast<SpecialWidget*>(pw));//错误!const_cast仅能被用在影响constness or volatileness的地方上。不能用在向继承子类进行类型转换。

    到目前为止,const_cast最普通的用途就是转换掉对象的const属性。

    第二种特殊的类型转换符是dynamic_cast,它被用于安全地沿着类的继承关系向下进行类型转换。这就是说,你能用dynamic_cast把指向基类的指针或引用转换成指向其派生类或其兄弟类的指针或引用,而且你能知道转换是否成功。失败的转换将返回空指针(当对指针进行类型转换时)或者抛出异常(当对引用进行类型转换时):
    Widget *pw;
    ...
    update(dynamic_cast<SpecialWidget*>(pw));
    // 正确,传递给update函数一个指针是指向变量类型为SpecialWidget的pw的指针
    // 如果pw确实指向一个对象,否则传递过去的将使空指针。
    void updateViaRef(SpecialWidget& rsw);
    updateViaRef(dynamic_cast<SpecialWidget&>(*pw));
    //正确。 传递给updateViaRef函数
    // SpecialWidget pw 指针,如果pw
    // 确实指向了某个对象
    // 否则将抛出异常
    dynamic_casts在帮助你浏览继承层次上是有限制的。它不能被用于缺乏虚函数的类型上(参见条款M24),也不能用它来转换掉constness
    int firstNumber, secondNumber;
    ...
    double result = dynamic_cast<double>(firstNumber)/secondNumber;
    // 错误!没有继承关系
    const SpecialWidget sw;
    ...
    update(dynamic_cast<SpecialWidget*>(&sw));
    // 错误! dynamic_cast不能转换
    // 掉const。
    如你想在没有继承关系的类型中进行转换,你可能想到static_cast。如果是为了去除const,你总得用const_cast。

    这四个类型转换符中的最后一个是reinterpret_cast。使用这个操作符的类型转换,其的转换结果几乎都是执行期定义(implementation-defined)。因此,使用reinterpret_casts的代码很难移植。reinterpret_casts的最普通的用途就是在函数指针类型之间进行转换。例如,假设你有一个函数指针数组:
    typedef void (*FuncPtr)(); // FuncPtr is 一个指向函数的指针,该函数没有参数返回值类型为void
    FuncPtr funcPtrArray[10]; // funcPtrArray 是一个能容纳10个FuncPtrs指针的数组
    让我们假设你希望(因为某些莫名其妙的原因)把一个指向下面函数的指针存入funcPtrArray数组:
    int doSomething();
    你不能不经过类型转换而直接去做,因为doSomething函数对于funcPtrArray数组来说有一个错误的类型。在FuncPtrArray数组里的函数返回值是void类型,而doSomething函数返回值是int类型。
    funcPtrArray[0] = &doSomething; // 错误!类型不匹配
    reinterpret_cast可以让你迫使编译器以你的方法去看待它们:
    funcPtrArray[0] = // this compiles
    reinterpret_cast<FuncPtr>(&doSomething);
    转换函数指针的代码是不可移植的(C++不保证所有的函数指针都被用一样的方法表示),在一些情况下这样的转换会产生不正确的结果(参见条款M31),所以你应该避免转换函数指针类型,除非你处于着背水一战和尖刀架喉的危急时刻。一把锋利的刀。一把非常锋利的刀。
    如果你使用的编译器缺乏对新的类型转换方式的支持,你可以用传统的类型转换方法代替static_cast, const_cast, 以及reinterpret_cast。也可以用下面的宏替换来模拟新的类型转换语法:
    #define static_cast(TYPE,EXPR) ((TYPE)(EXPR))
    #define const_cast(TYPE,EXPR) ((TYPE)(EXPR))
    #define reinterpret_cast(TYPE,EXPR) ((TYPE)(EXPR))
    你可以象这样使用使用:
    double result = static_cast(double, firstNumber)/secondNumber;
    update(const_cast(SpecialWidget*, &sw));
    funcPtrArray[0] = reinterpret_cast(FuncPtr, &doSomething);
    这些模拟不会象真实的操作符一样安全,但是当你的编译器可以支持新的的类型转换时,它们可以简化你把代码升级的过程。没有一个容易的方法来模拟dynamic_cast的操作,但是很多函数库提供了函数,安全地在派生类与基类之间进行类型转换。如果你没有这些函数而你有必须进行这样的类型转换,你也可以回到C风格的类型转换方法上,但是这样的话你将不能获知类型转换是否失败。当然,你也可以定义一个宏来模拟dynamic_cast的功能,就象模拟其它的类型转换一样:
    #define dynamic_cast(TYPE,EXPR) (TYPE)(EXPR)
    请记住,这个模拟并不能完全实现dynamic_cast的功能,它没有办法知道转换是否失败。
    我知道,是的,我知道,新的类型转换操作符不是很美观而且用键盘键入也很麻烦。如果你发现它们看上去实在令人讨厌,C风格的类型转换还可以继续使用并且合法。然而,正是因为新的类型转换符缺乏美感才能使它弥补了在含义精确性和可辨认性上的缺点。并且,使用新类型转换符的程序更容易被解析(不论是对人工还是对于工具程序),它们允许编译器检测出原来不能发现的错误。这些都是放弃C风格类型转换方法的强有力的理由。还有第三个理由:也许让类型转换符不美观和键入麻烦是一件好事。

  • 指针与引用看上去完全不同(指针用操作符“*”和“->”,引用使用操作符“. ”),但是它们似乎有相同的功能。指针与引用都是让你间接引用其他对象。你如何决定在什么时候使用指针,在什么时候使用引用呢?

    首先,要认识到在任何情况下都不能使用指向空值的引用。一个引用必须总是指向某些对象。因此如果你使用一个变量并让它指向一个对象,但是该变量在某些时候也可能不指向任何对象,这时你应该把变量声明为指针,因为这样你可以赋空值给该变量。相反,如果变量肯定指向一个对象,例如你的设计不允许变量为空,这时你就可以把变量声明为引用。“但是,请等一下”,你怀疑地问,“这样的代码会产生什么样的后果?”
    char *pc = 0; // 设置指针为空值
    char& rc = *pc; // 让引用指向空值
    这是非常有害的,毫无疑问。结果将是不确定的(编译器能产生一些输出,导致任何事情都有可能发生)。应该躲开写出这样代码的人,除非他们同意改正错误。如果你担心这样的代码会出现在你的软件里,那么你最好完全避免使用引用,要不然就去让更优秀的程序员去做。我们以后将忽略一个引用指向空值的可能性。
    因为引用肯定会指向一个对象,在C++里,引用应被初始化
    string& rs; // 错误,引用必须被初始化
    string s("xyzzy");
    string& rs = s; // 正确,rs指向s
    指针没有这样的限制。
    string *ps; // 未初始化的指针
    // 合法但危险
    不存在指向空值的引用这个事实意味着使用引用的代码效率比使用指针的要高。因为在使用引用之前不需要测试它的合法性。
    void printDouble(const double& rd)
    {
    cout << rd; // 不需要测试rd,它
    } // 肯定指向一个double值
    相反,指针则应该总是被测试,防止其为空:
    void printDouble(const double *pd)
    {
    if (pd) { // 检查是否为NULL
    cout << *pd;
    }
    }

    指针与引用的另一个重要的不同是指针可以被重新赋值以指向另一个不同的对象。但是引用则总是指向在初始化时被指定的对象,以后不能改变
    string s1("Nancy");
    string s2("Clancy");
    string& rs = s1; // rs 引用 s1
    string *ps = &s1; // ps 指向 s1
    rs = s2; // rs 仍旧引用s1,
    // 但是 s1的值现在是
    // "Clancy"
    ps = &s2; // ps 现在指向 s2;
    // s1 没有改变

    总的来说,在以下情况下你应该使用指针,一是你考虑到存在不指向任何对象的可能(在这种情况下,你能够设置指针为空),二是你需要能够在不同的时刻指向不同的对象(在这种情况下,你能改变指针的指向)。如果总是指向一个对象并且一旦指向一个对象后就不会改变指向,那么你应该使用引用。还有一种情况,就是当你重载某个操作符时,你应该使用引用。最普通的例子是操作符[]。这个操作符典型的用法是返回一个目标对象,其能被赋值。
    vector<int> v(10); // 建立整形向量(vector),大小为10;
    // 向量是一个在标准C库中的一个模板(见条款M35)
    v[5] = 10; // 这个被赋值的目标对象就是操作符[]返回的值
    如果操作符[]返回一个指针,那么后一个语句就得这样写:
    *v[5] = 10;
    但是这样会使得v看上去象是一个向量指针。因此你会选择让操作符返回一个引用。(这有一个有趣的例外,参见条款M30),当你知道你必须指向一个对象并且不想改变其指向时,或者在重载操作符并为防止不必要的语义误解时,你不应该使用指针。而在除此之外的其他情况下,则应使用指针。

  • 公有继承体现“是一个”(即子是一个父)的概念

    当写下类D("Derived" )从类B("Base")公有继承时,你实际上是在告诉编译器(以及读这段代码的人):类型D 的每一个对象也是类型B 的一个对象,但反之不成立;你是在说:B 表示一个比D 更广泛的概念,D 表示一个比
    B 更特定概念;你是在声明:任何可以使用类型B 的对象的地方,类型D 的对象也可以使用,因为每个类型D 的对象是一个类型B 的对象。相反,如果需要一个类型D 的对象,类型B 的对象就不行:每个D "是一个" B, 但反之不成
    立。C++采用了公有继承的上述解释。看这个例子:
    class Person { ... };
    class Student: public Person { ... };
    从日常经验中我们知道,每个学生是人,但并非每个人是学生。这正是上面的层次结构所声明的。我们希望,任何对 "人" 成立的事实 ---- 如都有生日----也对 "学生" 成立;但我们不希望,任何对 "学生" 成立的事实 ---- 如都在
    某一学校上学 ----也对 "人" 成立。人的概念比学生的概念更广泛;学生是一种特定类型的人。
    在C++世界中,任何一个其参数为Person 类型的函数(或Person 的指针或Person 的引用)可以实际取一个Student 对象(或Student 的指针或Student的引用):
    void dance(const Person& p); // 任何人可以跳舞
    void study(const Student& s); // 只有学生才学习
    Person p; // p 是一个人
    Student s; // s 是一个学生
    dance(p); // 正确,p 是一个人
    dance(s); // 正确,s 是一个学生,
    // 一个学生"是一个"人
    study(s); // 正确
    study(p); // 错误! p 不是学生
    只是公有继承才会这样。也就是说,只是Student 公有继承于Person 时,C++的行为才会象我所描述的那样。私有继承则是完全另外一回事(见条款42),至于保护继承,好象没有人知道它是什么含义。另外,Student "是一个"
    Person 的事实并不说明Student 的数组 "是一个" Person 数组。关于这一话题的讨论参见条款M3。
    公有继承和 "是一个" 的等价关系听起来简单,但在实际应用中,可能不会总是那么直观有时直觉会误导你。例如,有这样一个事实:企鹅是鸟;还有这样一个事实:鸟会飞。如果想简单地在C++中表达这些事实,我们会这样
    做:
    class Bird {
    public:
    virtual void fly(); // 鸟会飞
    ...
    };
    class Penguin:public Bird { // 企鹅是鸟
    ...
    };
    突然间我们陷入困惑,因为这种层次关系意味着企鹅会飞,而我们知道这不是事实。发生什么了?造成这种情况,是因为使用的语言(汉语)不严密。说鸟会飞,并不是说所有的鸟会飞,通常,只有那些有飞行能力的鸟才会飞。如果更精确一点,我们都知道,实际上有很多种不会飞的鸟,所以我们会提供下面这样的层次结构,它更好地反映了现实:
    class Bird {
    ... // 没有声明fly 函数
    };
    class FlyingBird: public Bird {
    public:
    virtual void fly();
    ...
    };
    class NonFlyingBird: public Bird {
    ... // 没有声明fly 函数
    };
    class Penguin: public NonFlyingBird {
    ... // 没有声明fly 函数
    };
    这种层次就比最初的设计更忠于我们所知道的现实。但关于鸟类问题的讨论,现在还不能完全结束。因为在有的软件系统中,说企鹅是鸟是完全合适的。比如说,如果程序只和鸟的嘴、翅膀有关系而不涉及到飞,最初的设计就很合适。这看起来可能很令人恼火,但它反映了一个简单的事实:没有任何一种设计可以理想到适用于任何软件。好的设计是和软件系统现在和将来所要完成的功能密不可分的(参见条款M32)。如果程序不涉及到飞,并且将来也不会,那么让Penguin 派生于Bird 就会是非常合理的设计。实际上,它会比那个区分会飞和不会飞的设计还要好,因为你的设计中不会用到这种区分。在设计层次中增加多余的类是一种很糟糕的设计,就象在类之间制定了错误的继承关系一样。
    对于 "所有鸟都会飞,企鹅是鸟,企鹅不会飞" 这一问题,还可以考虑用另外一种方法来处理。也就是对penguin 重新定义fly 函数,使之产生一个运行时错误:
    void error(const string& msg); // 在别处定义
    class Penguin: public Bird {
    public:
    virtual void fly() { error("Penguins can't fly!"); }
    ...
    };
    解释型语言如Smalltalk 喜欢采用这种方法,但这里要认识到的重要一点是,上面的代码所说的可能和你所想的是完全不同的两回事。它不是说,"企鹅不会飞",而是说,"企鹅会飞,但让它们飞是一种错误"。怎么区分二者的不同?这可以从检测到错误发生的时间来区分。"企鹅不会飞" 的指令是由编译器发出的,"让企鹅飞是一种错误" 只能在运行时检测到。为了表示 "企鹅不会飞" 这一事实,就不要在Penguin 对象中定义fly 函数:
    class Bird {
    ... // 没有声明fly 函数
    };
    class NonFlyingBird: public Bird {
    ... // 没有声明fly 函数
    };
    class Penguin: public NonFlyingBird {
    ... // 没有声明fly 函数
    };
    如果想使企鹅飞,编译器就会谴责你的违规行为:
    Penguin p;
    p.fly(); // 错误!
    用Smalltalk 的方法得到的行为和这完全不同。用那种方法,编译器连半句话都不会说。C++的处理方法和Smalltalk 的处理方法有着根本的不同,所以只要是在用C++编程,就要采用C++的方法做事。另外,在编译时检测错误比在运行时检测错误有某些技术上的优点,详见条款46。
    也许你会说,你在鸟类方面的知识很贫乏。但你可以借助于你的初等几何知识,对不对?我是说,矩形和正方形总该不复杂吧?
    那好,回答这个简单问题:类Square(正方形)可以从类Rectangle(矩
    形)公有继承吗?
    Rectangle
    ^
    | ?
    Square
    "当然可以!" 你会不屑地说,"每个人都知道一个正方形是一个矩形,但反过来通常不成立。" 确实如此,至少在高中时可以这样认为。但我不认为我们还是高中生。
    看看下面的代码:
    class Rectangle {
    public:
    virtual void setHeight(int newHeight);
    virtual void setWidth(int newWidth);
    virtual int height() const; // 返回当前值
    virtual int width() const; // 返回当前值
    ...
    };
    void makeBigger(Rectangle& r) // 增加r 面积的函数
    {
    int oldHeight = r.height();
    r.setWidth(r.width() + 10); // 对r 的宽度增加10
    assert(r.height() == oldHeight); // 断言r 的高度未变
    }
    很明显,断言永远不会失败。makeBigger 只是改变了r 的宽度,高度从没被修改过。现在看下面的代码,它采用了公有继承,使得正方形可以被当作矩形来处理:
    class Square: public Rectangle { ... };
    Square s;
    ...
    assert(s.width() == s.height()); // 这对所有正方形都成立
    makeBigger(s); // 通过继承,s "是一个" 矩形
    // 所以可以增加它的面积
    assert(s.width() == s.height()); // 这还是对所有正方形成立
    很明显,和前面的断言一样,后面的这个断言也永远不会失败。因为根据定义,正方形的宽和高应该相等。
    那么现在有一个问题。我们怎么协调下面的断言呢?调用makeBigger 前,s 的宽和高相等;makeBigger 内部,s 的宽度被改变,高度未变;· 从makeBigger 返回后,s 的高度又和宽度相等。(注意s 是通过引用传给makeBigger 的,所以makeBigger 修改了s 本身,而不是s 的拷贝)
    怎么样?
    欢迎加入公有继承的精彩世界,在这里,你在其它研究领域养成的直觉 ---- 包括数学 ---- 可能不象你所期望的那样为你效劳。对于上面例子中的情况来说,最根本的问题在于:对矩形适用的规则(宽度的改变和高度没关系)不适
    用于正方形(宽度和高度必须相同)。但公有继承声称:对基类对象适用的任何东西 ---- 任何!---- 也适用于派生类对象。在矩形和正方形的例子(以及条款40 中涉及到set 的一个类似的例子)中,所声称的原则不适用,所以用公有继承来表示它们的关系只会是错误。当然,编译器不会阻拦你这样做,但正如我们所看到的,它不能保证程序可以工作正常。正如每个程序员都知道的,代码通过编译并不说明它能正常工作。但也不要太担心你多年积累的软件开发直觉在步入到面向对象设计时会没有用武之地。那些知识还是很有价值,但既然你在自己的设计宝库中又增加了
    继承这一利器,你就要用新的眼光来扩展你的专业直觉,从而指导你开发出正确无误的面向对象程序。很快,你会觉得让Penguin 从Bird 继承或让Square从Rectangle 继承的想法很可笑,就象现在某个人向你展示一个长达数页的函
    数你会觉得可笑一样。也许它是解决问题的正确方法,只是不太合适。
    当然,"是一个" 的关系不是存在于类之间的唯一关系。类之间常见的另两个关系是 "有一个" 和 "用...来实现"。这些关系在条款40 和42 进行讨论。这两个关系中的某一个被不正确地表示成 "是一个" 的情况并不少见,这将导致
    错误的设计。所以,一定要确保自己理解这些关系的区别,以及怎么最好地用C++来表示它们。

    明智地使用私有继承, 私有继承体现的是“用父来实现子”关系,虽然“分层”同样可以表达这种关系,而且大多数情况下建议用“分层”,但是有些情况下可以利用继承的优点来做到一些“分层”做不到的事情
    条款35 说明,C++将公有继承视为 "是一个" 的关系。它是通过这个例子来证实的:假如某个类层次结构中,Student 类从Person 类公有继承,为了使某个函数成功调用,编译器可以在必要时隐式地将Student 转换为Person。这个例子很值得再看一遍,只是现在,公有继承换成了私有继承:
    class Person { ... };
    class Student: // 这一次我们
    private Person { ... }; // 使用私有继承
    void dance(const Person& p); // 每个人会跳舞
    void study(const Student& s); // 只有学生才学习
    Person p; // p 是一个人
    Student s; // s 是一个学生
    dance(p); // 正确, p 是一个人
    dance(s); // 错误!一个学生不是一个人
    很显然,私有继承的含义不是 "是一个",那它的含义是什么呢?"别忙!" 你说。"在弄清含义之前,让我们先看看行为。私有继承有那些行为特征呢?" 那好吧。关于私有继承的第一个规则正如你现在所看到的:和公有继承相反,如果两个类之间的继承关系为私有,编译器一般不会将派生类对象(如Student)转换成基类对象(如Person)。这就是上面的代码中为对象s调用dance 会失败的原因。第二个规则是,从私有基类继承而来的成员都成为了派生类的私有成员,即使它们在基类中是保护或公有成员。行为特征就这些。这为我们引出了私有继承的含义:私有继承意味着 "用...来实现"。如果使类D 私有继承于类B,这样做是因为你想利用类B 中已经存在的某些代码,而不是因为类型B 的对象和类型D 的对象之间有什么概念上的关系。因而,私有继承纯粹是一种实现技术。用条款36 引入的术语来说,私有继承意味着只是继承实现,接口会被忽略。如果D 私有继承于B,就是说D 对象在实现中用到了B 对象,仅此而已。私有继承在软件 "设计" 过程中毫无意义,只是在软件 "实现" 时才有用
    私有继承意味着 "用...来实现" 这一事实会给程序员带来一点混淆,因为条款40 指出,"分层" 也具有相同的含义。怎么在二者之间进行选择呢?答案很简单:尽可能地使用分层,必须时才使用私有继承。什么时候必须呢?这往往是指有保护成员和/或虚函数介入的时候 ---- 但这个问题过一会儿再深入讨论。

    条款41 提供了一种方法来写一个Stack 模板,此模板生成的类保存不同类型的对象。你应该熟悉一下那个条款。模板是C++最有用的组成部分之一,但一旦开始经常性地使用它,你会发现,如果实例化一个模板一百次,你就可
    能实例化了那个模板的代码一百次。例如Stack 模板,构成Stack<int>成员函数的代码和构成Stack<double>成员函数的代码是完全分开的。有时这是不可避免的,但即使模板函数实际上可以共享代码,这种代码重复还是可能存在。这种目标代码体积的增加有一个名字:模板导致的 "代码膨胀"。这不是件好事。对于某些类,可以采用通用指针来避免它。采用这种方法的类存储的是指针,而不是对象,实现起来就是:创建一个类,它存储的是对象的void*指针。创建另外一组类,其唯一目的是用来保证类型安全。这些类都借助第一步中的通用类来完成实际工作。下面的例子使用了条款41 中的非模板Stack 类,不同的是这里存储的是通用指针,而不是对象:
    class GenericStack {
    public:
    GenericStack();
    ~GenericStack();
    void push(void *object);
    void * pop();
    bool empty() const;
    private:
    struct StackNode {
    void *data; // 节点数据
    StackNode *next; // 下一节点
    StackNode(void *newData, StackNode *nextNode)
    : data(newData), next(nextNode) {}
    };
    StackNode *top; // 栈顶
    GenericStack(const GenericStack& rhs); // 防止拷贝和
    GenericStack& // 赋值(参见
    operator=(const GenericStack& rhs); // 条款27)
    };
    因为这个类存储的是指针而不是对象,就有可能出现一个对象被多个堆栈指向的情况(即,被压入到多个堆栈)。所以极其重要的一点是,pop 和类的析构函数销毁任何StackNode 对象时,都不能删除data 指针 ---- 虽然还是得要
    删除StackNode 对象本身。毕竟,StackNode 对象是在GenericStack 类内部分配的,所以还是得在类的内部释放。所以,条款41 中Stack 类的实现几乎完全满足the GenericStack 的要求。仅有的改变只是用void*来替换T。
    仅仅有GenericStack 这一个类是没有什么用处的,但很多人会很容易误用它。例如,对于一个用来保存int 的堆栈,一个用户会错误地将一个指向Cat对象的指针压入到这个堆栈中,但编译却会通过,因为对void*参数来说,指针
    就是指针。为了重新获得你所习惯的类型安全,就要为GenericStack 创建接口类(interface class),象这样:
    class IntStack { // int 接口类
    public:
    void push(int *intPtr) { s.push(intPtr); }
    int * pop() { return static_cast<int*>(s.pop()); }
    bool empty() const { return s.empty(); }
    private:
    GenericStack s; // 实现
    };
    class CatStack { // cat 接口类
    public:
    void push(Cat *catPtr) { s.push(catPtr); }
    Cat * pop() { return static_cast<Cat*>(s.pop()); }
    bool empty() const { return s.empty(); }
    private:
    GenericStack s; // 实现
    };
    正如所看到的,IntStack 和CatStack 只是适用于特定类型。只有int 指针可以被压入或弹出IntStack,只有Cat 指针可以被压入或弹出CatStack。IntStack和CatStack 都通过GenericStack 类来实现,这种关系是通过分层(参见条款40)来体现的,IntStack 和CatStack 将共享GenericStack 中真正实现它们行为的函数代码。另外,IntStack 和CatStack 所有成员函数是(隐式)内联函数,这意味着使用这些接口类所带来的开销几乎是零。但如果有些用户没认识到这一点怎么办?如果他们错误地认为使用GenericStack 更高效,或者,如果他们鲁莽而轻率地认为类型安全不重要,那该怎么办? 怎么才能阻止他们绕过IntStack 和CatStack 而直接使用GenericStack(这会让他们很容易地犯类型错误,而这正是设计C++所要特别避免的)呢?没办法!没办法防止。但,也许应该有什么办法。在本条款的开始我就提到,要表示类之间 "用...来实现" 的关系,有一个选择是通过私有继承。现在这种情况下,这一技术就比分层更有优势,因为通过它可以让你告诉别人:GenericStack 使用起来不安全,它只能用来实现其它的类。具体做法是将GenericStack 的成员函数声明为保护类型:
    class GenericStack {
    protected:
    GenericStack();
    ~GenericStack();
    void push(void *object);
    void * pop();
    bool empty() const;
    private:
    ... // 同上
    };
    GenericStack s; // 错误! 构造函数被保护
    class IntStack: private GenericStack {
    public:
    void push(int *intPtr) { GenericStack::push(intPtr); }
    int * pop() { return static_cast<int*>(GenericStack::pop()); }
    bool empty() const { return GenericStack::empty(); }
    };
    class CatStack: private GenericStack {
    public:
    void push(Cat *catPtr) { GenericStack::push(catPtr); }
    Cat * pop() { return static_cast<Cat*>(GenericStack::pop()); }
    bool empty() const { return GenericStack::empty(); }
    };
    IntStack is; // 正确
    CatStack cs; // 也正确
    和分层的方法一样,基于私有继承的实现避免了代码重复,因为这个类型安全的接口类只包含有对GenericStack 函数的内联调用。在GenericStack 类之上构筑类型安全的接口是个很花俏的技巧,但需要手工去写所有那些接口类是件很烦的事。幸运的是,你不必这样。你可以让模板来自动生成它们。下面是一个模板,它通过私有继承来生成类型安全的堆栈接口
    template<class T>
    class Stack: private GenericStack {
    public:
    void push(T *objectPtr) { GenericStack::push(objectPtr); }
    T * pop() { return static_cast<T*>(GenericStack::pop()); }
    bool empty() const { return GenericStack::empty(); }
    };
    这是一段令人惊叹的代码,虽然你可能一时还没意识到。因为这是一个模板,编译器将根据你的需要自动生成所有的接口类。因为这些类是类型安全的,用户类型错误在编译期间就能发现。因为GenericStack 的成员函数是保护类
    型,并且接口类把GenericStack 作为私有基类来使用,用户将不可能绕过接口类。因为每个接口类成员函数被(隐式)声明为inline,使用这些类型安全的类时不会带来运行开销;生成的代码就象用户直接使用GenericStack 来编写的一样(假设编译器满足了inline 请求 ---- 参见条款33)。因为GenericStack 使用了void*指针,操作堆栈的代码就只需要一份,而不管程序中使用了多少不同类型的堆栈。简而言之,这个设计使代码达到了最高的效率和最高的类型安全。很难做得比这更好。
    本书的基本认识之一是,C++的各种特性是以非凡的方式相互作用的。这个例子,我希望你能同意,确实是非凡的。
    从这个例子中可以发现,如果使用分层,就达不到这样的效果。只有继承才能访问保护成员,只有继承才使得虚函数可以重新被定义。(虚函数的存在会引发私有继承的使用,例子参见条款43)因为存在虚函数和保护成员,有时私
    有继承是表达类之间 "用...来实现" 关系的唯一有效途径
    。所以,当私有继承是你可以使用的最合适的实现方法时,就要大胆地使用它。同时,广泛意义上来说,分层是应该优先采用的技术,所以只要有可能,就要尽量使用它。

    通过分层来体现 "有一个" 或 "用...来实现"
    使某个类的对象成为另一个类的数据成员,从而实现将一个类构筑在另一个类之上,这一过程称为 "分层"(Layering)。例如:
    class Address { ... }; // 某人居住之处
    class PhoneNumber { ... };
    class Person {
    public:
    ...
    private:
    string name; // 下层对象
    Address address; // 同上
    PhoneNumber voiceNumber; // 同上
    PhoneNumber faxNumber; // 同上
    };
    本例中,Person 类被认为是置于string,Address 和PhoneNumber 类的上层,因为它包含那些类型的数据成员。"分层" 这一术语有很多同义词,它也常被称为:构成(composition),包含(containment)或嵌入embedding)。条款35 解释了公有继承的含义是 "是一个"。对应地,分层的含义是 "有一个" 或 "用...来实现"。上面的Person 类展示了 "有一个" 的关系。一个Person 对象 "有一个" 名字,地址,电话号码和传真号码。你不能说,一个人 "是一个" 名字或一个人 "是一个" 地址;你得说,一个人 "有一个" 名字, "有一个" 地址,等等。大多数人对区分这些没什么困难,所以混淆 "是一个" 和 "有一个" 的情况相对来说比较少见。
    稍微有点麻烦的是区分 "是一个" 和 "用...来实现"。例如,假设需要一个类模板,用来表示任意对象的集合,并且集合中没有重复元素。程序设计中,重用(Reuse)是再好不过的一件事了,而且你也许已经读过条款49 中关于C++
    标准库的总体介绍,那么,你的第一反应一定是想采用标准库中的set 模板。是啊,既然可以使用别人所写的东西,为什么还要再去写一个新的模板呢?但是,深入研究set 的帮助文档后,你会发现,set 的下述限制将不能满足
    你的程序要求:set 要求包含在它内部的元素必须是完全有序的,即,对set 中的任两个元素a 和b 来说,一定可以确定:要么a<b,要么b<a。对许多类型来说,这个要求很容易满足,而且,对象间完全有序使得set 可以在性能方面提供某些保证,这一点很吸引人。(参见条款49 了解标准库在性能上更多的保证)然而,你所需要的是更广泛的东西:一个类似set 的类,但对象不必完全有序;用C++标准所包装的术语来说,它们只需要所谓的 "相等可比较性":对于同种类型的a 和b 对象来说,要能确定是否a==b。这种要求更简单,它更适合于那些表示颜色这类东西的类型。总不能说红色比绿色更少或绿色比红色更少吧?看来,对你的程序来说,还是得需要自己来写个模板。当然,重用还是件好事。作为数据结构专家,你知道,在实现集合的众多选择中,一个最简单的办法是采用链表。你一定猜到了什么。对,标准库中正有这么一个list 模板(用来产生链表类)!所以可以重用它。具体来说,你决定让自己的Set 模板从list 继承。即,Set<T>将从list<T>继承。因为,在你的实现中,Set 对象实际上将是list 对象。于是你这样声明Set模板:
    // Set 中错误地使用了list
    template<class T>
    class Set: public list<T> { ... };

    至此,一切好象都很正确,但实际上错误不小。正如条款35 所说明的,如果D "是一个" B,对B 成立的所有事实对D 也成立。但是,list 对象可以包含重复元素,所以如果3051 这个值被增加到list<int>中两次,list 中将包含3051
    的两个拷贝。相反,Set 不可以包含重复元素,所以如果3051 被增加到Set<int>中两次,Set 中将只包含这个值的一个拷贝。于是,说一个Set "是一个" list 就是弥天大谎,因为如上所述,有一些在list 对象中成立的事实在Set 对象中不成立。
    因为这两个类的关系并非 "是一个",所以用公有继承来表示它们的关系就是一个错误。正确的方法是让Set 对象 "用list 对象来实现":
    // Set 中使用list 的正确方法
    template<class T>
    class Set {
    public:
    bool member(const T& item) const;
    void insert(const T& item);
    void remove(const T& item);
    int cardinality() const;
    private:
    list<T> rep; // 表示一个Set
    };
    Set 的成员函数可以利用list 以及标准库其它部分所提供的大量功能,所以,实现代码既不难写也很易读:
    template<class T>
    bool Set<T>::member(const T& item) const
    { return find(rep.begin(), rep.end(), item) != rep.end(); }
    template<class T>
    void Set<T>::insert(const T& item)
    { if (!member(item)) rep.push_back(item); }
    template<class T>
    void Set<T>::remove(const T& item)
    {
    list<T>::iterator it =
    find(rep.begin(), rep.end(), item);
    if (it != rep.end()) rep.erase(it);
    }
    template<class T>
    int Set<T>::cardinality() const
    { return rep.size(); }
    这些函数很简单,所以很自然地想到将它们作为内联函数;但在做最后决定前,还是回顾一下条款33 所做的讨论。(上面的代码中,find, begin, end,push_back 等函数是标准库基本框架的一部分,它们可用来对list 这样的容器
    模板进行操作。标准库框架的总体介绍参见条款49 和M35。)值得指出的是,Set 类的接口没有做到完整并且最小(参见条款18)。从完整性上来说,它最大的遗漏在于不能对Set 中的内容进行循环,而这一功能对很多程序来说是必需的(标准库中的所有成员都提供了这一功能,包括set)。Set 的另一个缺陷是没有遵循标准库所采用的容器类常规(见条款49 和M35),从而造成使用Set 时更难以利用库中其它的部分。Set 的接口尽管有这些瑕疵,但下面这一点不能被掩盖:Set 在理解它和list的关系上,具有无可辩驳的正确性。这种关系并非 "是一个"(虽然初看会以为是),而是 "用...来实现",通过分层来实现这种关系是类的设计者应该感到自豪的。
    顺便说一句,当通过分层使两个类产生联系时,实际上在两个类之间建立了编译时的依赖关系。关于为什么要考虑到这一点以及如何减少这方面的麻烦,参见条款34。