• Abstraction:

    本文描述了Java语言的嵌入类、内部类的具体分类和行为表现。

    1         概述

    我们知道,Java是一种完全的面向对象的语言,作为对象的灵魂,类的种类是多种多样的。类大致可以分外部类和内部类两种,外部类就是我们通常使用的类,而内部类的使用要比外部类少的多,最常见的是GUI事件侦听器。内部类的应用虽然不多,但是如果能够有效的使用内部类,能达到事半功倍的效果。废话不多说,下面开始:

    2         内部类和嵌套类

    要讨论内部类和嵌套类,首先要分清他们两者的区别于联系。

    首先,内部类(Inner Classes)和嵌套类(Nested Classes)是指在一个类里面定义的另一个类。其次无论是内部类还是嵌套类,在编译时都被当作一个独立的整体。对于访问他们的其他对象来说(假如他们对这个类来说是可见的)他们的使用和我们通常用的类是一样的。

    但是,内部类和嵌套类的区别在于:

    1.         嵌套类是静态的,而内部类不是,也就是说嵌套类的实例化不需要外部类的实例。但是内部类是需要这个实例的。

    2.         嵌套类可以任意声明静态成员,内部类不允许声明除了编译时常量以外的任何静态成员。这一限制也适用于静态初始化函数。

    3.         嵌套类都是命名的,匿名的类声明不能声明运行时静态成员(不管声明是不是静态的)。

    注意:类内部声明的接口都属于嵌入类。

    下面我们来分别讨论一下他们的具体行为表现。

    3         内部类

    内部类是指在一个类里面以非静态形式声明的另一个类。内部类的实例化需要外部实例的存在。一个类可以拥有多个内部类,一个外部实例可以拥有多个内部类的实例。但是内部类有且只有一个外部实例,并且在实例化时就已经指定,无法更改。

    3.1       内部类的分类

    内部类根据访问权限不同可以分为以下一种类型:普通内部类局部内部类

    普通内部类和局部内部类主要的区别在于作用域和访问权限的不同,普通内部类可以被所有人访问(只要访问控制符允许),而局部内部类的作用域更像一个变量,只能在定义它的函数内部被使用,其他人是无法使用这个类的。而且局部类可以访问定义它的函数中的final变量。

    根据声明方式不同又可以分为:命名内部类匿名内部类。命名内部类和匿名内部类的区别在于:

    首先,命名类可以抽象的。匿名类不能为抽象,事实上,匿名内部类在编译时被隐形的声明为最终的(final)

    其次,命名类声明可以继承一个父类并实现多个接口,匿名类只能有一个父类(或者接口)。

    再次,命名类可以被多次实例化,而匿名类只能在定义时被实例化一次。

    最后,命名类可以声明多个构造函数并控制访问哪一个父类的构造函数,匿名类无法声明构造函数。

    下面分别描述这些区别:

    3.2       普通内部类

    普通内部类是指在类成员定义中定义的类,这些类可以拥有访问控制符(public, private)。如果访问控释符允许,则这些类可以被外面直接应用。普通命名内部类可以声明为一个接口,或者是抽象的。嵌入类其实是特殊的普通内部类,但由于其特殊性,故在下面独立讨论。

    3.2.1       声明

    3.2.1.1       命名类

    class OuterClass{

    //Outer class deflation
          class InnerNamedClass{
                   //Inner class definition

    }
    }

    3.2.1.2       匿名类

    声明匿名类时,可以使用一个类或者接口作为他的父类。

    class OuterClass{
        //Outer class deflation
        Object unnamedObject new Object(){
            //Inner class definition
        }
    }

    3.2.2       实例化

    3.2.2.1       命名类

    外部或静态方法

    OuterClass outerObject new OuterClass();
    OuterClass.InnerNamedClass innerObject outerObject.new InnerNamedClass();

    内部

    InnerNamedClass innerObject new InnerNamedClass();

    3.2.2.2       匿名类

    定义时即完成实例化

    3.2.3       访问权限:

    3.2.3.1       内部实例对外部实例的访问权限为:

    外部类定义或继承的所有字段

    3.2.3.2       外部实例对内部实例的访问权限为:

    内部类定义或继承的所有字段

    3.2.3.3       其他对象对内部实例的访问权限为:

    若内部类不可见,则只能访问其超类定义的字段

    若内部类可见,则可访问内部实例的非私有字段,具体情况与通常的类类似

    3.2.4       备注:

    内部类的外部实例是在构造时作为参数传给构造函数的,在SUN JDK中,保存外部实例的字段通常被声明为:

    final OutterClass this$0;

    3.3       局部内部类

    局部内部类,是指在函数体内声明的类,这种类是局域性的,只在函数内声明后有效,他最大的特点是:可以访问定义它的函数中的final变量。

    3.3.1       声明:

    3.3.1.1      命名类:

    class OuterClass{
        //Outer class deflation
        void aMethod(){
            class InnerNamedClass{
                //Inner class definition
            }
        }
    }

    3.3.1.2      匿名类:

    class OuterClass{
        //Outer class deflation
        void aMethod(){
            Object unnamedObject new Object(){
                //Inner class definition
            }
        }

    3.3.2       实例化:

    3.3.2.1      命名类:

    局部类的作用域仅限定义该类的方法中,无法在外部实例化或访问,实例化方法与通常类相同

    3.3.2.2      匿名类:

    定义时即完成实例化

    3.3.3       访问权限:

    3.3.3.1      内部实例对外部实例的访问权限为:

    外部类定义或继承的所有字段

    定义该内部类的方法在定义类之前所定义的所有final变量

    3.3.3.2      外部实例对内部实例的访问权限为:

    在定义该内部类的方法中可以访问内部类定义或继承的所有字段

    在外部实例的其他字段中只能访问其超类定义的字段

    3.3.3.3      其他对象对内部实例的访问权限为:

    只能访问其超类定义的字段

    3.3.4       备注:

    当试图访问一个final变量时,编译器实际上把该变量的值赋值给内部类的一个隐含字段中。编译器会自动的在构造函数中添加相应的参数。在SUN JDK中,这个隐含字段通常被命名为:val$varname

    3.4       使用内部类的注意事项:

    本节包括了一些使用内部类时需要注意的事项,这些事项对所有内部类都适用。

    3.4.1       静态成员

    首先需要注意的是,内部类不能非常量声明静态成员,任何静态成员的声明都会被当成编译错误,例如:

    InnerClasses.java:12: 内部类不能有静态声明

                    static String a;

                                   ^

    1 错误

             先要了解更加详细的信息,请参考附录A

    3.4.2       访问外部实例

    要在一个内部实例中访问外部实例,请使用:

    OuterClass.this

    3.4.3       字段覆盖

    在内部类中声明的与外部类同签名的字段将覆盖外部字段,要访问外部字段请使用外部实例对象前缀。

    OuterClass.this.field

    OuterClass.this.Method();

    4         嵌套类

    嵌套类(Nested Classes)其实是普通内部类的一种特殊形式,首先它的声明是静态的,这就表示了这个类不需要外部实例。也表示了他不能访问外部类的实例字段。但是相应的,嵌套类可以拥有非常量静态成员。事实上,JDK通常把嵌套类当成一个具有特殊名字的独立类。

    另外,嵌套类还拥有一种特殊形式:匿名嵌套类。前面说过,匿名类都是内部类,但是匿名嵌套类是一个特例,从理论上讲,他既不属于嵌套类,也不属于内部类。匿名嵌套类 不允许拥有非常量静态成员,但是他也没有外部实例供访问。

    4.1       声明:

    4.1.1       命名类:

    class OuterClass{

    //Outer class deflation
          static class StaticInnerNamedClass{
                   //Inner class definition

    }
    }

    4.1.2       匿名类:

    class OuterClass{
        //Outer class deflation
        static Object unnamedObject new Object(){
            //Inner class definition
        }
    }

    4.2       实例化

    4.2.1       内部

    StaticInnerNamedClass staticInnerNamedObject new StaticInnerNamedClass();

    4.2.2       外部

    OuterClass.StaticInnerNamedClass staticInnerNamedObject new OuterClass.StaticInnerNamedClass();

  • 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控制符对应用程序是屏蔽的,但对其派生类是可访问的。
      保护继承和私有继承只是在技术上讨论时有其一席之地。

  • 在网络历史的早期,国际标准化组织(ISO)和国际电报电话咨询委员会(CCITT)共同出版了开放系统互联的七层参考模型。一台计算机操作系统中的网络过程包括从应用请求(在协议栈的顶部)到网络介质(底部) ,OSI参考模型把功能分成七个分立的层次。图2.1表示了OSI分层模型。

      ┌─────┐
      │ 应用层 │←第七层
      ├─────┤
      │ 表示层 │
      ├─────┤
      │ 会话层 │
      ├─────┤
      │ 传输层 │
      ├─────┤
      │ 网络层 │
      ├─────┤
      │数据链路层│
      ├─────┤
      │ 物理层 │←第一层
      └─────┘
      图2.1 OSI七层参考模型

      OSI模型的七层分别进行以下的操作:

    第一层 物理层
      第一层负责最后将信息编码成电流脉冲或其它信号用于网上传输。它由计算机和网络介质之间的实际界面组成,可定义电气信号、符号、线的状态和时钟要求、数据编码和数据传输用的连接器。如最常用的RS-232规范、10BASE-T的曼彻斯特编码以及RJ-45就属于第一层。所有比物理层高的层都通过事先定义好的接口而与它通话。如以太网的附属单元接口(AUI),一个DB-15连接器可被用来连接层一和层二。

    物理层(PhysicalLayer),规定通信设备的机械的、电气的、功能的和过程的特性,用以建立、维护和拆除物理链路连接。具体地讲,机械特性规定了网络连接时所需接插件的规格尺寸、引脚数量和排列情况等;电气特性规定了在物理连接上传输bit流时线路上信号电平的大小、阻抗匹配、传输速率距离限制等;功能特性是指对各个信号先分配确切的信号含义,即定义了DTE和DCE之间各个线路的功能;规程特性定义了利用信号线进行bit流传输的一组操作规程,是指在物理连接的建立、维护、交换信息是,DTE和DCE双放在各电路上的动作系列。

    在这一层,数据的单位称为比特(bit)。

    属于物理层定义的典型规范代表包括:EIA/TIA RS-232、EIA/TIA RS-449、V.35、RJ-45等。

    第二层 数据链路层
      数据链路层通过物理网络链路提供可靠的数据传输。不同的数据链路层定义了不同的网络和协议特征,其中包括物理编址、网络拓扑结构、错误校验、帧序列以及流控。物理编址(相对应的是网络编址)定义了设备在数据链路层的编址方式;网络拓扑结构定义了设备的物理连接方式,如总线拓扑结构和环拓扑结构;错误校验向发生传输错误的上层协议告警;数据帧序列重新整理并传输除序列以外的帧;流控可能延缓数据的传输,以使接收设备不会因为在某一时刻接收到超过其处理能力的信息流而崩溃。数据链路层实际上由两个独立的部分组成,介质存取控制(Media Access Control,MAC)和逻辑链路控制层(Logical Link Control,LLC)。MAC描述在共享介质环境中如何进行站的调度、发生和接收数据。MAC确保信息跨链路的可靠传输,对数据传输进行同步,识别错误和控制数据的流向。一般地讲,MAC只在共享介质环境中才是重要的,只有在共享介质环境中多个节点才能连接到同一传输介质上。IEEE MAC规则定义了地址,以标识数据链路层中的多个设备。逻辑链路控制子层管理单一网络链路上的设备间的通信,IEEE 802.2标准定义了LLC。LLC支持无连接服务和面向连接的服务。在数据链路层的信息帧中定义了许多域。这些域使得多种高层协议可以共享一个物理数据链路。

    数据链路层(DataLinkLayer):在物理层提供比特流服务的基础上,建立相邻结点之间的数据链路,通过差错控制提供数据帧(Frame)在信道上无差错的传输,并进行各电路上的动作系列。  

    数据链路层在不可靠的物理介质上提供可靠的传输。该层的作用包括:物理地址寻址、数据的成帧、流量控制、数据的检错、重发等。

    在这一层,数据的单位称为帧(frame)。

    数据链路层协议的代表包括:SDLC、HDLC、PPP、STP、帧中继等。

    第三层 网络层
      网络层负责在源和终点之间建立连接。它一般包括网络寻径,还可能包括流量控制、错误检查等。相同MAC标准的不同网段之间的数据传输一般只涉及到数据链路层,而不同的MAC标准之间的数据传输都涉及到网络层。例如IP路由器工作在网络层,因而可以实现多种网络间的互联。

    在计算机网络中进行通信的两个计算机之间可能会经过很多个数据链路,也可能还要经过很多通信子网。网络层的任务就是选择合适的网间路由和交换结点, 确保数据及时传送。网络层将数据链路层提供的帧组成数据包,包中封装有网络层包头,其中含有逻辑地址信息- -源站点和目的站点地址的网络地址。

    如果你在谈论一个IP地址,那么你是在处理第3层的问题,这是“数据包”问题,而不是第2层的“帧”。IP是第3层问题的一部分,此外还有一些路由协议和地址解析协议(ARP)。有关路由的一切事情都在第3层处理。地址解析和路由是3层的重要目的。网络层还可以实现拥塞控制、网际互连等功能。

    在这一层,数据的单位称为数据包(packet)。

    网络层协议的代表包括:IP、IPX、RIP、OSPF等。

    第四层 传输层
      传输层向高层提供可靠的端到端的网络数据流服务。传输层的功能一般包括流控、多路传输、虚电路管理及差错校验和恢复。流控管理设备之间的数据传输,确保传输设备不发送比接收设备处理能力大的数据;多路传输使得多个应用程序的数据可以传输到一个物理链路上;虚电路由传输层建立、维护和终止;差错校验包括为检测传输错误而建立的各种不同结构;而差错恢复包括所采取的行动(如请求数据重发),以便解决发生的任何错误。传输控制协议(TCP)是提供可靠数据传输的TCP/IP协议族中的传输层协议。

    第4层的数据单元也称作数据包(packets)。但是,当你谈论TCP等具体的协议时又有特殊的叫法,TCP的数据单元称为段(segments)而UDP协议的数据单元称为“数据报(datagrams)”。这个层负责获取全部信息,因此,它必须跟踪数据单元碎片、乱序到达的数据包和其它在传输过程中可能发生的危险。第4层为上层提供端到端(最终用户到最终用户)的透明的、可靠的数据传输服务。所为透明的传输是指在通信过程中传输层对上层屏蔽了通信传输系统的具体细节。

    传输层协议的代表包括:TCP、UDP、SPX等。

    第五层 会话层
      会话层建立、管理和终止表示层与实体之间的通信会话。通信会话包括发生在不同网络应用层之间的服务请求和服务应答,这些请求与应答通过会话层的协议实现。它还包括创建检查点,使通信发生中断的时候可以返回到以前的一个状态。

    这一层也可以称为会晤层或对话层,在会话层及以上的高层次中,数据传送的单位不再另外命名,统称为报文。会话层不参与具体的传输,它提供包括访问验证和会话管理在内的建立和维护应用之间通信的机制。如服务器验证用户登录便是由会话层完成的。

    第六层 表示层
      表示层提供多种功能用于应用层数据编码和转化,以确保以一个系统应用层发送的信息可以被另一个系统应用层识别。表示层的编码和转化模式包括公用数据表示格式、性能转化表示格式、公用数据压缩模式和公用数据加密模式。
      公用数据表示格式就是标准的图像、声音和视频格式。通过使用这些标准格式,不同类型的计算机系统可以相互交换数据;转化模式通过使用不同的文本和数据表示,在系统间交换信息,例如ASCII(American Standard Code for Information Interchange,美国标准信息交换码);标准数据压缩模式确保原始设备上被压缩的数据可以在目标设备上正确的解压;加密模式确保原始设备上加密的数据可以在目标设备上正确地解密。
      表示层协议一般不与特殊的协议栈关联,如QuickTime是Applet计算机的视频和音频的标准,MPEG是ISO的视频压缩与编码标准。常见的图形图像格式PCX、GIF、JPEG是不同的静态图像压缩和编码标准。

    这一层主要解决拥护信息的语法表示问题。它将欲交换的数据从适合于某一用户的抽象语法,转换为适合于OSI系统内部使用的传送语法。即提供格式化的表示和转换数据服务。数据的压缩和解压缩, 加密和解密等工作都由表示层负责。

    第七层 应用层
      应用层是最接近终端用户的OSI层,这就意味着OSI应用层与用户之间是通过应用软件直接相互作用的。注意,应用层并非由计算机上运行的实际应用软件组成,而是由向应用程序提供访问网络资源的API(Application Program Interface,应用程序接口)组成,这类应用软件程序超出了OSI模型的范畴。应用层的功能一般包括标识通信伙伴、定义资源的可用性和同步通信。因为可能丢失通信伙伴,应用层必须为传输数据的应用子程序定义通信伙伴的标识和可用性。定义资源可用性时,应用层为了请求通信而必须判定是否有足够的网络资源。在同步通信中,所有应用程序之间的通信都需要应用层的协同操作。
      OSI的应用层协议包括文件的传输、访问及管理协议(FTAM) ,以及文件虚拟终端协议(VIP)和公用管理系统信息(CMIP)等。

    应用层为操作系统或网络应用程序提供访问网络服务的接口。

    应用层协议的代表包括:Telnet、FTP、HTTP、SNMP等。

    2.2 TCP/IP分层模型

      TCP/IP分层模型(TCP/IP Layening Model)被称作因特网分层模型(Internet Layering Model)、因特网参考模型(Internet Reference Model)。图2.2表示了TCP/IP分层模型的四层。
      ┌────────┐┌─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┐
      │        ││D│F│W│F│H│G│T│I│S│U│ │
      │        ││N│I│H│T│T│O│E│R│M│S│其│
      │第四层,应用层 ││S│N│O│P│T│P│L│C│T│E│ │
      │        ││ │G│I│ │P│H│N│ │P│N│ │
      │        ││ │E│S│ │ │E│E│ │ │E│它│
      │        ││ │R│ │ │ │R│T│ │ │T│ │
      └────────┘└─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┘
      ┌────────┐┌─────────┬───────────┐
      │第三层,传输层 ││   TCP   │    UDP    │
      └────────┘└─────────┴───────────┘
      ┌────────┐┌─────┬────┬──────────┐
      │        ││     │ICMP│          │
      │第二层,网间层 ││     └────┘          │
      │        ││       IP            │
      └────────┘└─────────────────────┘
      ┌────────┐┌─────────┬───────────┐
      │第一层,网络接口││ARP/RARP │    其它     │
      └────────┘└─────────┴───────────┘
          图2.2 TCP/IP四层参考模型

      TCP/IP协议被组织成四个概念层,其中有三层对应于ISO参考模型中的相应层。ICP/IP协议族并不包含物理层和数据链路层,因此它不能独立完成整个计算机网络系统的功能,必须与许多其他的协议协同工作。
      TCP/IP分层模型的四个协议层分别完成以下的功能:

    第一层 网络接口层
      网络接口层包括用于协作IP数据在已有网络介质上传输的协议。实际上TCP/IP标准并不定义与ISO数据链路层和物理层相对应的功能。相反,它定义像地址解析协议(Address Resolution Protocol,ARP)这样的协议,提供TCP/IP协议的数据结构和实际物理硬件之间的接口。

    第二层 网间层
      网间层对应于OSI七层参考模型的网络层。本层包含IP协议、RIP协议(Routing Information Protocol,路由信息协议),负责数据的包装、寻址和路由。同时还包含网间控制报文协议(Internet Control Message Protocol,ICMP)用来提供网络诊断信息。

    第三层 传输层
      传输层对应于OSI七层参考模型的传输层,它提供两种端到端的通信服务。其中TCP协议(Transmission Control Protocol)提供可靠的数据流运输服务,UDP协议(Use Datagram Protocol)提供不可靠的用户数据报服务。

    第四层 应用层
      应用层对应于OSI七层参考模型的应用层和表达层。因特网的应用层协议包括Finger、Whois、FTP(文件传输协议)、Gopher、HTTP(超文本传输协议)、Telent(远程终端协议)、SMTP(简单邮件传送协议)、IRC(因特网中继会话)、NNTP(网络新闻传输协议)等,这也是本书将要讨论的重点。

    一般意义上说交换机是工作在数据链路层。但随着科技的发展,现在有了三层交换机,三层交换机已经扩展到了网络层。也就是说:它等于“数据链路层 + 部分网络层”。交换机中传的是帧。通过存储转发来实现的。

    路由器是工作在网络层。路由器中传的是IP数据报。主要是选址和路由。

    1.什么是交换机

    交换机也叫交换式集线器,它通过对信息进行重新生成,并经过内部处理后转发至指定端口,具备自动寻址能力和交换作用,由于交换机根据所传递信息包的目的地址,将每一信息包独立地从源端口送至目的端口,避免了和其他端口发生碰撞。广义的交换机就是一种在通信系统中完成信息交换功能的设备。

    2.交换机的工作原理

    在计算机网络系统中,交换机是针对共享工作模式的弱点而推出的。集线器是采用共享工作模式的代表,如果把集线器比作一个邮递员,那么这个邮递员是个不认识字的“傻瓜”--要他去送信,他不知道直接根据信件上的地址将信件送给收信人,只会拿着信分发给所有的人,然后让接收的人根据地址信息来判断是不是自己的!而交换机则是一个“聪明”的邮递员--交换机拥有一条高带宽的背部总线和内部交换矩阵。交换机的所有的端口都挂接在这条背部总线上,当控制电路收到数据包以后,处理端口会查找内存中的地址对照表以确定目的MAC(网卡的硬件地址)的NIC(网卡)挂接在哪个端口上,通过内部交换矩阵迅速将数据包传送到目的端口。目的MAC若不存在,交换机才广播到所有的端口,接收端口回应后交换机会“学习”新的地址,并把它添加入内部地址表中。

    可见,交换机在收到某个网卡发过来的“信件”时,会根据上面的地址信息,以及自己掌握的“常住居民户口簿”快速将信件送到收信人的手中。万一收信人的地址不在“户口簿”上,交换机才会像集线器一样将信分发给所有的人,然后从中找到收信人。而找到收信人之后,交换机会立刻将这个人的信息登记到“户口簿”上,这样以后再为该客户服务时,就可以迅速将信件送达了。

    3.交换机的性能特点

    1)独享带宽

    由于交换机能够智能化地根据地址信息将数据快速送到目的地,因此它不会像集线器那样在传输数据时“打扰”那些非收信人。这样一来,交换机在同一时刻可进行多个端口组之间的数据传输。并且每个端口都可视为是独立的网段,相互通信的双方独自享有全部的带宽,无须同其他设备竞争使用。比如说,当A主机向 D主机发送数据时,B主机可同时向C主机发送数据,而且这两个传输都享有网络的全部带宽--假设此时它们使用的是10Mb的交换机,那么该交换机此时的总流通量就等于2×10Mb=20Mb。

    2)全双工

    当交换机上的两个端口在通信时,由于它们之间的通道是相对独立的,因此它们可以实现全双工通信。

    1.路由器的作用

    通过集线器或交换机,我们可以将很多台电脑组成一个比较大的局域网(图3),但是当机器的数量达到一定数目时,问题也就来了:对于用集线器构成的局域网而言,由于采用“广播”工作模式,当网络规模较大时,信息在传输过程中出现碰撞、堵塞的情况越来越严重,即使是交换机,这种情况也同样存在。其次,这种局域网不安全,也不利于管理。

    为了解决这些问题,人们便将一个较大的网络划分为一个个小的子网、网段,或者直接将它们划分为多个VLAN(即虚拟局域网),在一个VLAN内,一台主机发出的信息只能发送到具有相同VLAN号的其他主机,其他VLAN的成员收不到这些信息或广播帧。采用VLAN划分网络后,可有效地抑制网络上的广播风暴,增加网络的安全性,使管理控制集中(图4)。

    既然是局域网,万一分别处于不同VLAN的主机需要互相通信时该怎么办呢?这时候就得通过路由器(Router,转发者)来帮忙了。路由器可以将处于不同子网、网段、VLAN的电脑连接起来,让它们自由通信。另外,我们都知道目前的网络有很多种结构类型,且不同网络所使用的协议、速度也不尽相同。当两个不同结构的网络需要互连时,也可以通过路由器来实现。路由器可以使两个相似或不同体系结构的局域网段连接到一起,以构成一个更大的局域网或一个广域网。

    可见,路由器是一种连接多个网络或网段的网络设备,它能将不同网络、网段或VLAN之间的数据信息进行“翻译”,以使它们能够相互“读”懂对方的数据,从而构成一个更大的网络。

    2.路由器的工作原理

    所谓路由就是指通过相互连接的网络把信息从源地点移动到目标地点的活动。那么路由器具体是如何进行“翻译”工作的呢?我们平时在学习、翻译英语时,肯定会准备一本英汉字典,通过它来实现英文与中文之间的互现转换。而对于路由器而言,它也有这种用于翻译的字典--路径表。路径表(Routing Table)保存着各种传输路径的相关数据,如子网的标志信息、网上路由器的个数和下一个路由器的名字等内容。路径表可以是由系统管理员固定设置好的,也可以由系统动态修改,可以由路由器自动调整,也可以由主机控制。

    通过路由器可以让不同子网、网段进行互连,因此路由器与集线器、交换机不同,它一般安装在网络的“骨干”部位,而不像集线器、交换机那样工作在基层。比如说一个较大规模的企业局域网,基于管理、安全、性能的考虑,一般都会将整个网络划分为多个VLAN,如此一来,当VLAN与VLAN之间进行通讯时,就必须使用路由器。

    对于该企业网而言,肯定还需要与互联网相连,对于企业而言,一般都是通过租用电信的DDN专线或者利用ADSL、Cable、ISDN等方式将企业网接入互联网,而此时由于网络体系及所用协议的不同,也需要路由器来完成企业网与互联网的互连工作。

    点击放大

    一般来说,在路由过程中,信息至少会经过一个或多个中间节点。通常,人们会把路由和交换进行对比,这主要是因为在普通用户看来两者所实现的功能是完全一样的。其实,路由和交换之间的主要区别就是交换发生在OSI参考模型的第二层(数据链路层),而路由发生在第三层,即网络层。这一区别决定了路由和交换在移动信息的过程中需要使用不同的控制信息,所以两者实现各自功能的方式是不同的。路由器通过路由决定数据的转发。转发策略称为路由选择,这也是路由器名称的由来。

    三剑客的外观比较

    前面我们已经讲解了集线器、交换机、路由器的工作原理,但是对于很多初学者来说,有时也希望能够从外观上去区分它们。当然,集线器、交换机、路由器在外观上肯定有所区别,但这些往往只能作为参考信息,毕竟现在很多集线器、交换机与路由器产品在外观上看非常相似。而这里面最难区分的就是普通桌面型的集线器与交换机,而路由器相对比较容易识别。

  • 在Windows XP上加装Fedora9,完成后发现Fedora9不但把IDE硬盘认作了SAST硬盘,还把自动添加的windows启动项中的分区搞错了(root (hd0,4)是我的数据分区,不是系统分区)。修改"/boot/grub/grub.conf"中的分区号后再重启,无效。 windows启动后就处于类黑屏状态,倒不再是原先分区错时显示" Rootnoverify(hd0,0) Chainloader +1"的错误提示了。

    参考...
  • 总结:

        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,当时不会超过初始设定的资源总数.

  •  UML 2 中的阴和阳

    在 UML 2 中有二种基本的图范畴:结构图和行为图(structure diagrams and behavior diagrams)。每个 UML 图都属于这二个图范畴。结构图的目的是显示建模系统的静态结构。它们包括类,组件和(或)对象图。另一方面,行为图显示系统中的对象的动态行为,包括如对象的方法,协作和活动之类的内容。行为图的实例是活动图,用例图和序列图大体上的结构图

    大体上的结构图(Structure diag...
  • 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)