精华内容
下载资源
问答
  • auto_ptr到底能不能作为容器的元素?

    千次阅读 2014-09-10 09:38:41
    【摘要】对C++语言本身来说,它并在乎用户把什么类型的对象作为STL容器的元素,因为模板类型参数在理论上可以为任何类型。比如说STL容器仅支持“值”语义而支持“引用(&)”语义,并非因为模板类型参数不能为...

      

    【摘要】对C++语言本身来说,它并不在乎用户把什么类型的对象作为STL容器的元素,因为模板类型参数在理论上可以为任何类型。比如说STL容器仅支持“值”语义而不支持“引用(&)”语义,并非因为模板类型参数不能为引用,而是因为如果容器元素为引用类型,就会出现“引用的引用”、“引用的指针”等C++语言不支持的语法和语义。智能指针是一种模拟原始指针行为的对象,因此理论上也可以作为容器的元素,就象原始指针可以作为容器元素一样。但是智能指针毕竟是一种特殊的对象,它们在原始指针共享实值对象的基础能力上增加了自动销毁实值对象的能力,如果将它作为容器的元素,可能导致容器之间共享元素对象实值,这不仅不符合STL容器的概念和“值”语义,也会存在安全隐患,同时也会存在许多应用上的限制,特别是象STL中的auto_ptr这样的智能指针。本文深入地阐述了auto_ptr这种较简单的智能指针“可以”或者“不可以”作为容器元素的根本原因,以及它作为容器元素会存在的限制和带来的问题,最后说明auto_ptr存在的真正意义、正确的使用方法以及它的替代品——带有引用计数能力的智能指针,当容器之间需要共享元素对象时,或者程序中存在大量的指针传递而担心资源泄漏时,这样的智能指针就特别有用。

    【关键字】auto_ptr  容器  智能指针

     

    一、引言

    Scott Meyers在《More Effective C++[3]一书中对智能指针及其相关问题(构造、析构、复制、提领、测试以及类型转换等)作了深入的分析,其中也提到“STLauto_ptr这种在复制时会把对实值对象的拥有权转交出去的智能指针不宜作为STL容器的元素”,而且在他的《Effective STL[4]Item 8中明确指出了这一点。Nicolai M.Josuttis的《The C++ Standard Library[5]中有一节专门针对auto_ptr的阐述也指出“auto_ptr不满足STL标准容器对元素的最基本要求”。但是他们都是从容器的需求、语义以及应用的安全性来阐述,而没有从语言的静态类型安全性和auto_ptr的实现方案角度深入地分析其原因,因此有些读者看了之后可能仍然不明就里:它是如何不满足容器需求的?它是如何违反C++的静态类型安全性从而避免误用的?

    我们知道,可以作为STL容器的元素的数据类型一般来说需要满足下列条件:

    1)可默认构造的(Default Constructible),也即具有publicdefault constructor,不论是用户显式定义的还是编译器自动合成的。但是用户定义的带参数的constructor(包括copy constructor)会抑制编译器合成default constructor。实际上并非任何情况下任何一种容器都强制要求其元素类型满足这一要求,特别是关联式容器,因为只有序列式容器的某些成员函数才可能明确地或隐含地使用元素类型的默认构造函数,如果你不使用这样的成员函数,编译器就不需要元素类型的默认构造函数;

    2)可拷贝构造(Copy Constructible)和拷贝赋值(Copy Assignable)的,即具有publiccopy constructorcopy assignment operator,不论是编译器自动合成的还是用户显式定义的。其它版本的operator=()重载并不会抑制编译器合成copy assignment operator,如果你没有显式定义它的话。这个条件可归结为:元素必须是可拷贝的(Copyable),但实际上拷贝赋值的要求也不是强制的,原因和默认构造函数类似;

    3)具有publicdestructor,不论是编译器自动合成的还是用户显式定义的;

    4)对于关联式容器,要求其元素必须是可比的(Comparable)。

    auto_ptr满足上述条件吗?至少满足前三条,因此至少可以作为序列式容器的元素;如果为auto_ptr定义了比较运算符的话,应该还可以把它作为关联式容器的元素。

    但是auto_ptr的特点是接管和转移拥有权,而不是像原始指针那样可以共享实值对象,即:auto_ptr在初始化时接管实值对象和拥有权,而在拷贝时(拷贝构造和拷贝赋值)会交出实值对象及其拥有权。因此,auto_ptr对象和它的拷贝绝对不会共享实值对象,任何两个auto_ptr也不应该共享同一个实值对象。这就是说,auto_ptr对象和它的拷贝并不相同。然而根据STL容器“值”语义的要求,可拷贝构造意味着一个对象必须和它的拷贝相同(标准中的正式定义比这稍复杂一些)。同样,可赋值意味着把一个对象赋值给另一个同类型对象将产生两个相同的对象。显然,auto_ptr不能满足这一要求,似乎与上面的结论矛盾!

    那么问题究竟出在哪里呢?

     

    二、copy constructorcopy assignment operator的形式

    在揭开auto_ptr的神秘面纱之前需要了解copy constructorcopy assignment operator的几种合法形式。任何一个类都允许两种形式的copy constructor[1]C代表任何一个类):

    C(const C& copy);

    C(C& copy);

    同样,copy assignment operator也允许类似的两种形式(返回值类型视实际需要可改变):

    C& operator=(const C& copy);

    C& operator=(C& copy);

    实际上,由于copy assignment operator为普通的运算符重载成员函数,因此还可以为下列形式:

    C& operator=(C copy);

    这两个函数具体是什么形式,取决于用户的定义或者该类的成员对象及其基类具有什么样的copy constructorcopy assignment operator。比如,如果基类的copy constructor为第一种形式,那么编译器自动为派生类合成的copy constructor也为第一种形式;相反为第二种形式。Copy assignment operator亦类似。具体细节可参考[8]

    这两种形式的区别就在于参数有无修饰符const:如果有const修饰,则该函数体不能修改实参对象(即拷贝源),也不能调用其non-const成员函数;如果没有const修饰,则该函数可以修改实参对象,也可以调用其non-const成员函数。

    从语言的角度讲,任何对象都可以放到容器中(只要不是引用,因为STL容器不支持“引用”语义),只是某些类型的对象会存在安全隐患或者其容器会受到很大的应用限制。如果要防止用户把一些不适宜的对象放入容器中,就要求对象的设计和实现者使用一些语言支持的但不常用的特征。也就是说,要能够在编译阶段就阻止这种具有潜在危险性的行为。常用的方法就是迫使其违反C++静态类型安全规则。

    下面我们来看一看auto_ptr到底是如何通过迫使其违反C++静态类型安全规则而在编译时阻止将其作为容器元素的。

    三、auto_ptr源码分析

    其实auto_ptr的拥有权管理非常简单。根据上一节的阐述,可以使用两种方案来实现auto_ptr。下面是拷贝构造函数和拷贝赋值函数采用non-const参数的一个实现版本:

    template<class T>

    class auto_ptr

    {

    private:

    T      *m_ptr;            // 原始指针

    public:

    explicit auto_ptr(T *p = 0) throw()        // explicit constructor

    : m_ptr(p){ }                          // *p必须是运行时创建的对象

     

    auto_ptr(auto_ptr& other) throw()         // 非常规copy constructor

    : m_ptr(other.release()){ }           // 转让拥有权,修改了实参对象

     

    #ifdef _SUPPORT_MEMBER_TEMPLATES_

    template<class U>

    auto_ptr(auto_ptr<U>& other) throw()

    : m_ptr(other.release()){ }           // 转让拥有权,修改了实参对象

    #endif

        

    auto_ptr& operator=(auto_ptr& other) throw() // 非常规assignment

    {

    if (&other != this) {

    delete m_ptr;                     // 释放实值对象

    m_ptr = other.release();          // 交出拥有权,修改了实参对象

    }

    return (*this);

    }

        

    #ifdef _SUPPORT_MEMBER_TEMPLATES_

    template<class U>

    auto_ptr& operator=(auto_ptr<U>& other) throw()

    {

    if (other.get() != this->get()) {

    delete m_ptr;                   // 释放实值对象

    m_ptr = other.release();        // 交出拥有权,修改了实参对象

    }

    return (*this);

    }

    #endif

            

                // 从析构函数看,m_ptr必须指向动态创建的对象

    ~auto_ptr(){ delete m_ptr; }           // destructor,“delete 0”没有任何问题!

    T& operator*() const throw(){ return *m_ptr; }

    T* operator->() const throw(){ return m_ptr; }

    T* get() const throw(){ return m_ptr; }

     

    T* release() throw(){

    T *temp = m_ptr;

    m_ptr = 0;          // 必要!修改成员,释放拥有权

    return temp;

    }

    void reset(T *p = 0) throw(){

    if (p != m_ptr) {

    delete m_ptr;

    m_ptr = p;

    }

    }

    bool owns() const{ return (m_ptr != 0); }

    …      // 这里省略了一些无关紧要的东西

    };

    如你所见,该auto_ptr实现版本的copy constructorcopy assignment operator的参数类型都是non-const的,因为这两个函数都会修改实参对象的数据成员,即调用其release方法(non-const方法)释放其对实值对象的拥有权,并把实值对象的指针置为0。如果参数类型为const的,那么这种修改就不可能直接进行。所以,一旦用一个auto_ptr对象去构造另一个auto_ptr对象,或者把一个auto_ptr对象赋值给另一个auto_ptr对象,你就不能再使用原来的那个auto_ptr对象了,因为反引用NULL指针会导致运行时异常,除非你让它重新接管一个新的实值对象。

    这个版本的auto_ptr就不能作为任何容器的元素,如果你这样做了,在编译阶段就会检查出错误,即违反了C++的静态类型安全规则。比如:

    std::list< std::auto_ptr<int> >  la;     // auto_ptr列表

    std::auto_ptr<int> p1(new int(1));

    std::auto_ptr<int> p2(new int(2));

    std::auto_ptr<int> p3(new int(3));

    la.push_back(p1);                        // compiling-error!

    la.push_back(p2);                        // compiling-error!

    la.push_back(p3);                        // compiling-error!

     

    set<auto_ptr<int> > sa;                  // auto_ptr集合:假设为auto_ptr定义了operator<

    sa.insert(p1);                           // compiling-error!

    sa.insert(p2);                           // compiling-error!

    sa.insert(p3);                           // compiling-error!

    STL容器管理元素的方法是动态创建元素的拷贝,并负责管理这些动态分配的资源,即值的深拷贝语义(deep copy),具体由一个可定制的memory allocator来负责,不过这不是我们讨论的重点,因此忽略。可以想象std::list<T>::push_back方法的实际动作如下:

    template<typename T>

    void list<T>::push_back(const T& x)

    {

    T *p = operator new(sizeof(T));  // 分配内存空间

    new (p) T(x);                    // placement new,调用T的copy constructor

    ……                             // 将p交给容器管理,调整容器大小

    }

    由于auto_ptrcopy constructor被显式地定义为接受non-const&,因此上述函数实现就需要将一个const T& x转换为non-const&,显然是违反静态类型安全规则的。STL容器不能使用强制类型转换来帮你达到此目的,否则它本身就不是类型安全的了。

    其它追加元素的方法如insert的某些版本也是一样的道理。

    上述auto_ptr通过采用非常规copy constructorcopy assignment operator使“企图将auto_ptr对象作为STL容器元素”的行为在编译阶段就被检测出来,从而避免了潜在的危险。

    然而如果auto_ptr采用常规copy constructorcopy assignment operator形式,编译器就无能为力了,因为它们不违反C++静态类型安全规则。P.J.Plauger版本(MS VC++采用的实现)的auto_ptr就是一个例子!

    P.J.Plauger版本的auto_ptr确实可以作为容器的元素,这并不是因为它没有修改拷贝源的拥有权,而是它的release函数虽然是const member function,却在修改拥有权时使用了const_cast强制类型转换。因为在一个const member function里面,编译器把当前对象看成是一个const对象(即this的类型为const auto_ptr<T> * const),调用copy constructor时通过强制类型转换就可以修改实参对象的拥有权属性,尽管它是const &传递。

    下面是拷贝构造函数和拷贝赋值函数采用const参数的一个实现版本:

    template<class T>

    class auto_ptr

    {

    private:

    T      *m_ptr;            // 原始指针

    public:

    explicit auto_ptr(T *p = 0) throw()        // explicit constructor

    : m_ptr(p){ }                          // *p必须是运行时创建的对象

     

    auto_ptr(const auto_ptr& other) throw()   // 常规copy constructor

    : m_ptr(other.release()){ }           // 转让拥有权,修改了实参对象

     

    #ifdef _SUPPORT_MEMBER_TEMPLATES_

    template<class U>

    auto_ptr(const auto_ptr<U>& other) throw()

    : m_ptr(other.release()){ }           // 转让拥有权,修改了实参对象

    #endif

        

    auto_ptr& operator=(const auto_ptr& other) throw() // 常规assignment

    {

    if (&other != this) {

    delete m_ptr;                     // 释放实值对象

    m_ptr = other.release();          // 交出拥有权,修改了实参对象

    }

    return (*this);

    }

        

    #ifdef _SUPPORT_MEMBER_TEMPLATES_

    template<class U>

    auto_ptr& operator=(const auto_ptr<U>& other) throw()

    {

    if (other.get() != this->get()) {

    delete m_ptr;                   // 释放实值对象

    m_ptr = other.release();        // 交出拥有权,修改了实参对象

    }

    return (*this);

    }

    #endif

     

                // 从析构函数看,m_ptr必须指向动态创建的对象

    ~auto_ptr(){ delete m_ptr; }           // destructor,“delete 0”没有任何问题!

    T& operator*() const throw(){ return *m_ptr; }

    T* operator->() const throw(){ return m_ptr; }

    T* get() const throw(){ return m_ptr; }

     

    T* release() const throw(){

    T *temp = m_ptr;

    ((auto_ptr<T>*)this)->m_ptr = 0;     // 必要!修改成员,释放拥有权

    return temp;

    }

    void reset(T *p = 0) throw(){

    if (p != m_ptr) {

    delete m_ptr;

    m_ptr = p;

    }

    }

    bool owns() const{ return (m_ptr != 0); }

    …      // 这里省略了一些无关紧要的东西

    };

    一旦如此实现,auto_ptr容器就可以顺利通过编译并可能正确执行。例如:

    int main()

    {

     typedef std::list<std::auto_ptr<int> >  IntPtrList;

    IntPtrList  la;  // or:std::vector<std::auto_ptr<int> >  va;

    std::auto_ptr<int> p1(new int(1));

    std::auto_ptr<int> p2(new int(2));

    std::auto_ptr<int> p3(new int(3));

    std::auto_ptr<int> p4(new int(4));

    std::auto_ptr<int> p5(new int(5));

    la.push_back(p1);                   // ok! 转交所有权

    la.push_back(p2);                   // ok! 转交所有权

    la.push_back(p3);                   // ok! 转交所有权

    la.push_back(p4);                   // ok! 转交所有权

    la.push_back(p5);                   // ok! 转交所有权

    // 不能再使用p1、p2、p3、p4、p5

    for (IntPtrList::const_iterator first = la.begin(),

          last = la.end(); first != last; ++first)

    std::cerr << **first << ‘\t’;

    // 不能再使用p1、p2、p3、p4、p5

    return 0;

    } // la析构的时候会自动调用每一个auto_ptr元素的析构函数,从而保证释放动态分配的内存

     

    输出:

    1   2   3   4   5

    但是把auto_ptr作为容器元素毕竟是一个危险的动作,而且这样的容器在使用时会受到很大的限制。如果上面的程序接着使用la,比如创建它的拷贝,调整它的大小,甚至对它排序,那么la就可能遭到破坏,它的所有元素会变成无效指针,或者里面夹杂了无效指针,甚至有可能丢失一些元素,而你却没有意识到。例如,假设为auto_ptr定义了泛型比较运算符:

    int main()

    {

     typedef std::vector<std::auto_ptr<int> >  IntPtrVector;

    IntPtrVector  va;

    std::auto_ptr<int> p1(new int(1));

    std::auto_ptr<int> p2(new int(2));

    std::auto_ptr<int> p3(new int(3));

    std::auto_ptr<int> p4(new int(4));

    std::auto_ptr<int> p5(new int(5));

    va.push_back(p1);

    va.push_back(p2);

    va.push_back(p3);

    va.push_back(p4);

    va.push_back(p5);

     

     (注意:以下操作并非放在一起进行,仅是示范)

     IntPtrVector vb = va;    // va丧失对所有实值对象的拥有权,元素成为NULL指针

     vb.resize(10);           // 新增的元素都为NULL指针

     std::sort(vb.begin(), vb.end());      // 可能会使其中某些元素成为NULL指针

     std::auto_ptr<int> t = vb.front();    // 改变了容器元素

     std::auto_ptr<int> r = vb[3];         // 改变了容器元素

     

     std::list<std::auto_ptr<int> >  la;

     std::copy(vb.begin(), vb.end(), std::back_inserter(la)); // copy改变了拷贝源

     

    return 0;

    }

    Scott Meyers在《Effective STL[4]Item 8中详细地分析了对auto_ptr容器进行排序时可能会导致的问题。但是在MSVC++环境下经测试,并没有出现书中所描述的悲惨结果,而是结果正确。主要的原因在于C++标准并没有要求std::sort等泛型算法的实现必须采用某一种方法,而是只规定了它们的接口、功能和应该达到的性能要求(容器也是如此)。因此,不同的STL实现可能采取不同的方法,比如有的sort实现采用快速排序法,而有的采用插入式排序法,等等。不同的排序方法在遭遇auto_ptr这样的容器时可能就会产生不同的结果。

    P.J.Plauger版本在这方面的防范能力确实不如SGI版本做得好!不过没关系,STL的源代码都是公开的,你可以比较不同的实现甚至修改它们,使之更安全、更适合你的应用。

    四、auto_ptr对象作为容器元素的危险性

    应该说,从应用的方便性和安全角度出发,容器应该要求其元素对象的拷贝与原对象相同或者等价,但auto_ptr显然不满足这一条。auto_ptr作为容器元素的危险性主要表现在如下几个方面:

    1)将auto_ptr对象插入容器中之后企图继续使用它,比如通过它调用实值对象的成员函数,然而此时它指向的实值对象已经交给容器中的某一个元素对象了;

    2)就象auto_ptr和它的拷贝并不相同一样,auto_ptr容器和它的拷贝也不一样;如果对某些成员函数的返回结果使用不当的话,可能无意中会产生不期望的结果。因此其应用受到很大限制,必须小心应付;

    3)某些算法将无法用于这样的容器。比如sort等会修改区间的算法,因为它们的实现调用元素对象的copy constructorcopy assignment operator,可能会释放掉某些元素对实值对象的拥有权;就连本来不会修改源区间的算法如copy,如果应用于auto_ptr容器,也可能修改源区间;还有的算法比如find等要求元素对象提供比较能力,如果auto_ptr不是可比的,那也不能用于auto_tr容器;

    4)不可移植。目前有些STL实现比如SGI版本可以在编译阶段阻止这种行为,但是某些STL实现仍然允许这样做。

    鉴于此,无论你使用的STL平台是否允许auto_ptr容器,你都不应该这样做。

    然而许多其它的功能强大的智能指针,比如使用了引用计数的智能指针,作为容器的元素时不会存在上述问题,但是auto_ptr不是这样的智能指针。关于智能指针的更详细阐述还可参考Andrei Alexandrescu的《Modern C++ Design[6]一书第7章。

    可见,智能指针“可以”还是“不可以”作为容器的元素并非绝对的,不仅与STL的实现有关,而且与STL容器的需求和安全性以及容器的语义有关。

    五、auto_ptr的正确用法

    既然auto_ptr在复制或赋值时会使原来的auto_ptr失效,那么我们只要防止其复制和赋值行为的发生就可以了。比如在传递auto_ptr对象时使用const &const *传递而不是值传递。例如:

    void func(const auto_ptr<int>& pInt)

    {

    cout << *pInt << endl;

    }

    int main()

    {

    auto_ptr<int> a(new int(100));

    func(a);

    }

    但即使这样,如果遇到象P.J.Plauger那样实现的auto_ptr,还是不能保证在函数内部不会出现对它的拷贝或者赋值。

    再就是不要用静态创建的对象来初始化auto_ptr。例如:

    int main()

    {

    int x(100);

    auto_ptr<int> a(&x);

    }// 这里调用delete删除本地对象,错误!

    由于auto_ptr是对象化的智能指针,具有自动释放资源的能力,因此它真正有价值的用途是在发生异常时避免资源泄漏。比如,如果不使用auto_ptr,则下列代码在发生异常的情况下不得不多次手工释放资源:

    class A{ … };

    void func()

    {

    A *pA = new A;

    try

    {

    …                // using *pA

    }

    catch(…)

    {

    delete pA;       // 发生异常时要显式释放

    throw;

    }

    delete pA;           // 函数退出时还要显式释放

    }

    现在有了auto_ptr,我们就可以这么做:

    class A{ … };

    void func()

    {

    auto_ptr<A> pA(new A);

    …                // using *pA

    }

    这是因为C++有一个保证:本地对象在函数退出时总是会被销毁,而不论函数以何种方式退出。也就是说,不管是在发生异常的情况下函数退出,还是函数的正常退出,堆栈都要展开,每一个本地对象的析构函数都会被依次调用。关于资源泄漏和智能指针的相关话题,请参考[3]的第9101128等条款,其中有极详细和精彩的论述。

    如果想防止无意中修改auto_ptr对实值对象的拥有权,可以使用const auto_ptr,这样的auto_ptr只能使用引用或指针传递,不能值传递,也不能赋值和拷贝构造。例如:

    class A{ … };

    void func()

    {

    const auto_ptr<A> p1(new A);

    …                               // using *pA

    auto_ptr<A> p2(p1);              // error!

    auto_ptr<A> p3;

    p3 = p1;                         // error!

    }

    关于auto_ptr的运用技巧可参考[5]的相关章节。

     

    【参考资料】

    [1]STL implementation, SGI co., 2000.

    [2]STL implementation, P.J.Plauger, 1995.

    [3]More Effective C++, Scott Meyers, 1998.

    [4]Effective STL, Scott Meyers, 2001.

    [5]The C++ Standard Library, Nicolai M.Josuttis, 1999.

    [6]Modern C++ Design, Andrei Alexandrescu, 2001.

    [7]Generic Programming and the STL, H.Austern, 1999.

    [8]Inside the C++ Object Model, Stanley B.Lippman, 1996.



    [1] 注意:C(C copy); 并非一个递归函数,它是非法的,因为它是一个悖论。

    展开全文
  • Spring IoC容器

    千次阅读 2019-05-31 11:40:01
    核心技术 发行版5.0.8 这一部分的指导文档覆盖了所有的完全集成到Spring框架中的...Spring框架拥有他自己的AOP框架,该技术在概念上是很容器理解的,并且成功的解决了在Java企业编程中的AOP需求的80%的点。 该框...

    核心技术

    发行版5.0.8


    这一部分的指导文档覆盖了所有的完全集成到Spring框架中的技术。

    这些技术中首要的便是Spring框架的控制反转(IoC)容器。Spring框架的IoC容器的全面的处理是与Spring的面向切面编程(AOP)的彻底实现紧密相关的。Spring框架拥有他自己的AOP框架,该技术在概念上是很容器理解的,并且成功的解决了在Java企业编程中的AOP需求的80%的点。

    该框架也提供了与AspectJ(当前最丰富的-就特征而言-并且是在Java企业空间中最成熟的AOP实现)的集成。

    1、IoC容器

    1.1、Spring IoC容器和beans的介绍

    这篇章节覆盖了IoC原则的Spring框架的的实现。IoC也被称为依赖注入(DI)。这是对象定义他们依赖关系的过程,也就是说,其他对象仅仅通过构造器参数,工厂方法参数,或者是在它被构造或从一个工厂方法中返回后设置到对象实例上的属性来工作。当他创建bean的时候,容器接着注入这些依赖。这个过程从根本上是相反的,因此,名称“控制反转”,bean它本身控制实例或者是通过使用类的直接构造器或一个机制,例如服务定位器模式来定位他的依赖。

    org.springframework.beansorg.springframework.context是Spring框架的IoC容器的基础。BeanFactory接口提供了一个先进的配置机制能够管理任何类型的对象。ApplicationContext是BeanFactory的一个子接口。它增加了与Spring AOP特征的更加简单的集成;消息资源处理(用于国际化中),事件发布和应用层特定的上下文例如用于Web应用中的WebApplicationContext。

    简而言之,BeanFactory提供了配置框架和基本的功能,并且ApplicationContext添加了更多的企业特定的功能。ApplicationContextBeanFactory的一个完备的超级,并且被唯一使用在描述Spring IoC容器的这一章节中。关于使用BeanFactory而不是ApplicationContext的更多信息,请查看1.16小节的BeanFactory的介绍

    在Spring中,那些组成你应用骨干的和那些被Spring IoC容器管理的对象被称为_beans_。一个Bean是一个被实例化的,组装的或其他被Spring IoC容器管理的对象。此外,简单的说,一个Bean就是你的应用程序中的对象中的一个。Beans和他们之间的依赖被映射到由容器使用的配置元数据中。

    1.2 容器概览

    接口org.springframework.context.ApplicationContext代表着Spring IoC容器并且负责实例化,配置和组装上述的beans。该容器使它的指令通过读取配置元数据作用于哪些对象去初始化,配置和组装。配置元数据表现在XML,Java注解或Java代码中。它允许您表达组成应用程度的对象以及这些对象之间的丰富的依赖性。

    很多ApplicationContext接口的实现为开箱即用的。在独立的应用程序中,创建一个ClassPathXmlApplicationContextFileSystemXmlApplicationContext的实例是通用的。虽然XML是定义配置元数据的原始格式,但是你可以通过提供少量的XML配置来指示容器使用Java注释或代码作为元数据格式,以声明性地支持这些附加的元数据格式。

    在大多数的应用程序场景中,不需要显式的用户代码来实例化Spring IoC容器的一个或多个实例。例如,在一个Web应用场景中,在应用程序的web.xml文件中,一个简单的样板Web描述符XML通常就足够了。如果你正在使用基于Eclipse开发环境的Spring工具套件,这个样板配置可以通过很少的鼠标点击或按键来简单的创建。

    下面的图表是Spring如何工作的高层视图。你的应用程序是与配置元数据绑定在一起的,所以在ApplicationContext被创建和初始化之后,你将会有一个完成的配置完成的和可执行的系统或应用程序。

    1.2.1 配置元数据

    正如前面的图标显示,Spring IoC容器消费了一种配置元数据的形式;这个配置元数据表示应用程序开发人员告诉Spring容器如何在应用程序中实例化,配置和组装对象。

    传统上,配置元数据是以简单直观的XML格式提供的,这篇章节主要使用它来传递Spring IoC容器的关键概念和特征。

    基于XML的元数据不是唯一允许的配置元数据的形式。Spring IoC容器本身与实际写入配置元数据的格式完全分离。这些天,很多开发者选择_基于Java的配置_用于他们的Spring应用程序。

    关于Spring容器中使用其他元数据格式的更多信息,请查看:

    • 基于注解的配置:Spring 2.5引入了基于注解的配置元数据的支持。
    • 基于Java的配置:从Spring 3.0开始,很多由Spring的Java配置项目提供的特征变成了核心Spring框架的一部分。因此,你可以在你的应用程序类之外通过使用Java而不是XML文件来定义beans。为了使用这些新的特征,请查看@Configuration@Bean@Import@DependsOn注解。

    Spring配置至少包含一个或超过一个容器必须要管理的bean定义。基于XML的配置元数据显示了这些beans在上层<beans/>元素中被配置成<bean/>元素。Java配置典型地在一个@Configuration类中使用@Bean注解方法。

    这些bean定义对应于组成你的应用程序的实际对象。典型地就是你定义服务层对象,数据访问对象(DAOs),表示对象,例如Struts Action实例,基础对象,例如HibernateSessionFactories,JMS Queues等。通常情况下,容器中不配置细粒度域对象,因为通常创建和加载域对象是DAOs和业务逻辑的责任。然而,你可以使用带有AspectJ的Spring集成来配置在IoC容器控制之外被创建的对象。请查看使用AspectJ来依赖注入Spring的域对象

    下列示例展示了基于XML配置元数据的基本结构:

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd">
    
        <bean id="..." class="...">
            <!-- 在这里配置该bean的合作者和相关配置 -->
        </bean>
    
        <bean id="..." class="...">
            <!-- 在这里配置该bean的合作者和相关配置 -->
    
        </bean>
    
        <!-- 更多bean的配置 -->
    
    </beans>
    

    id属性是一个字符床,你可以使用该字符串来声明个人的bean定义。class属性定义bean的类型并且使用完全限定的类名。id属性的值指向协作对象。指向协作对象的XML没有在这个示例中展示出来;查看依赖获取更多信息。

    1.2.2 实例化一个容器

    实例化一个Spring IoC容器是直截了当的。位置路径或提供给AplicationContext构造器的路径实际是资源字符串,它允许容器从外部资源,如本地文件系统,从Java CLASSPATH等中加载配置元数据。

    ApplicationContext context = new ClassPathXmlApplicationContext("service.xml","daos.xml");
    

    在你学习Spring的IoC容器之后,你可能想要了解更多的关于Spring Resource的信息,正如在Resource中描述的,Resource提供了一个方便的机制用于从在URI语法中定义的位置中读取一个输入流。特别的,Resource路径被用于构建应用上下文,正如在Application contexts和Resouces paths中描述的。

    下面的示例展示了服务层对象(services.xml)配置文件:

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd">
    
        <!-- services -->
    
        <bean id="petStore" class="org.springframework.samples.jpetstore.services.PetStoreServiceImpl">
            <property name="accountDao" ref="accountDao"/>
            <property name="itemDao" ref="itemDao"/>
            <!-- additional collaborators and configuration for this bean go here -->
        </bean>
    
        <!-- more bean definitions for services go here -->
    
    </beans>
    

    下列示例展示了数据访问对象daos.xml文件:

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd">
    
        <bean id="accountDao"
            class="org.springframework.samples.jpetstore.dao.jpa.JpaAccountDao">
            <!-- additional collaborators and configuration for this bean go here -->
        </bean>
    
        <bean id="itemDao" class="org.springframework.samples.jpetstore.dao.jpa.JpaItemDao">
            <!-- additional collaborators and configuration for this bean go here -->
        </bean>
    
        <!-- more bean definitions for data access objects go here -->
    
    </beans>
    

    在前面的例子中,服务层包含类PetStoreServiceImpl,还有两个类型为JpaAccountDaoJpaItemDao(基于JPA的对象/关系映射标准)的数据访问对象。property name元素指向JavaBean属性的名称,ref指向另外bean定义的名称。idref的链接表示协作对象之间的依赖。想要了解配置一个对象依赖的更多细节,请查看依赖

    组合基于XML配置的元数据

    bean定义跨越多个XML文件是有用的。通常情况下,每一个独立的XML配置文件在你的架构中代表一个逻辑层或模块。

    你可以使用应用上下文构造器从这些XML片段中加载bean定义。这个构造器采用多个Resource位置,就像在上一小节中展示的那样。相应替代的,使用一个或多个<import/>元素从其他一个或多个文件中加载bean定义。例如:

    <beans>
        <import resource="services.xml"/>
        <import resource="resources/messageSource.xml"/>
        <import resource="/resources/themeSource.xml"/>
    
        <bean id="bean1" class="..."/>
        <bean id="bean2" class="..."/>
    </beans>
    

    在上面的示例中,额外的bean定义从三个文件中被加载上来:services.xml, messageSource.xmlthemeSource.xml。在做importing的时候,所有位置路径都是与定义文件相对的,所以在做importing的时候,service.xml必须在相同的目录或作为文件的类路径的位置,然而messageSource.xmlthemeSource.xml必须位于引入文件的位置下面的resource位置。正如你所看到的,主斜杠是被忽略的,但是考虑到这些路径都是相对的,不使用主斜杠是最好的形式。被引入文件的内容,包括顶层的<bean/>元素,根据Spring模式来说,都必须要是有效的XML的bean定义。

    使用相对路径"…/"在父目录中引入文件是可以的,但是不被推荐的。这样做在文件中也就是在当前应用外面创建了一个依赖。特别地,对于“classpath:”路径(例如,“classpath:…/service.xml”)来说这样的引用是不被推荐的,在这种情况下,运行时解决进程选择“最近的”类路径然后寻找他的父目录。类路径配置的改变可能会导致一个不同的,错误的目录的选择。

    你可以一直使用完全合格的资源位置而不是相对路径:例如,“file:C:/config/services.xml"或"classpath:/config/services.xml”。然而,要知道你正在连接你的应用的配置到特定的绝对路径位置。一般来说,对于这样绝对位置保持间接方向是更可取的,例如,通过在运行时对JVM系统属性进行解析的占位符"${…}"。

    导入指令是有beans名称空间本身提供的一个特征。除了普通的bean定义之外,更多的配置特征在由Spring,“context”和"util"名称空间提供的XML名称空间的选择中是可用的。

    Groovy Bean定义 DSL

    作为一个用于外部的配置元数据的例子,bean定义可以在Spring的Groovy Bean定义DSL中被表达,正如从Grails框架中所知道的那样。特别的,这样的配置将会和下面展示的一样作为一个结果存放在".groovy"文件中。

    beans {
        dataSource(BasicDataSource) {
            driverClassName = "org.hsqldb.jdbcDriver"
            url = "jdbc:hsqldb:mem:grailsDB"
            username = "sa"
            password = ""
            settings = [mynew:"setting"]
        }
        sessionFactory(SessionFactory) {
            dataSource = dataSource
        }
        myService(MyService) {
            nestedBean = { AnotherBean bean ->
                dataSource = dataSource
            }
        }
    }
    

    这个配置样式很大程度上与XML的bean定义相同,甚至支持Spring的XML配置命名空间。这也允许直接通过一个"importBeans"来导入XML bean定义文件。

    1.2.3. 使用容器

    ApplicationContext是能够维护不用bean及其依赖的注册表的高级工厂的接口。使用方法T getBean(String name,Class<T> requiredType),你可以检索你的bean的实例。

    // 创建和配置bean
    ApplicationContext context = new ClassPathXmlApplicationContext("services.xml","daos.xml");
    // 检索配置的实例
    PetStoreService service = context.getBean("petStore",PetStoreService.class);
    // 使用配置的实例
    List<String> userList = service.getUsernameList();
    

    使用Groovy配置,bootstrapping看上去很相似,仅仅是一个不同的上下文实现类,该实现类是Groovy认识的。(但是也理解XML的bean定义)。

    ApplicationContext context = new GenericGroovyApplicationContext("services.groovy","daos.groovy");
    

    最灵活的变量便是与读者代理(reader delegates),例如用于XML文件的XmlBeanDefinitionReader相结合的GenericApplicationContext。

    GenericApplicationContext context = new GenericApplicationContext();
    new XmlBeanDefinitionReader(context).loadBeanDefinitions("services.xml", "daos.xml");
    context.refresh();
    

    或者是用于Groovy文件的GroovyBeanDefinitionReader

    GenericApplicationContext context = new GenericApplicationContext();
    new GroovyBeanDefinitionReader(context).loadBeanDefinitions("services.groovy", "daos.groovy");
    context.refresh();
    

    这样的阅读者代理可以混合的,并且可以在相同的应用上下文相匹配,如果希望的话,可以从不同的配置源读取bean定义。

    然后你可以使用getBean来取回你的bean的实例。ApplicationContext接口有一些其他的方法来取回beans,但是理想情况下你的应用代码中应该永远不使用他们。事实上,你的应用代码不应该有对getBean()方法的调用,因此,也不应该有对Spring API的依赖。例如,Spring伴随着web框架的集成为多个web框架组件,例如控制器和JSF管理beans,的依赖注入,允许你通过元数据在一特定的bean上声明一个依赖(例如autowiring注解)。

    1.3 Bean概览

    一个Spring IoC容器管理一个或多个beans。这些beans被你提供给容器的配置元数据创建(例如,XML中的<bean/>定义)。

    在容器本身,这些bean定义被代表成BeanDefinition对象,该对象包含下述元数据:

    • 包限定类名:特别的,被定义的bean的实际实现类。
    • Bean行为配置元素,这标记bean在容器内的行为(范围,声明周期回调等等)。
    • 该bean需要的其他bean的引用来做这个工作。这些引用也被称为协作者或依赖。
    • 在新的创建的对象中要去设置的其他配置设定 - 例如,池子的大小限制,或管理连接池的bean中要使用的连接数。

    这个元数据被翻译成一组属性来组成每一个bean定义。下面的表格描述了这些属性:

    属性 在…中描述
    Class 实例化Beans
    Name 命名Beans
    Scope Beans范围
    Constructor arguments 依赖注入
    Properties 依赖注入
    Autowiring mode Autowiring 协作者
    Lazy initialization mode 懒实例化Beans
    Initialization method 实例化回调
    Destruction method 析构回调

    除了包含如何创建一个特定bean的bean定义之外,ApplicationContext实现也允许在容器外被创建的现存的对象的注册。这可以通过getBeanFactory()方法访问ApplicationContext的BeanFactory来实现,这返回BeanFactory的DefaultListableBeanFactory的实现。DefaultListableBeanFactory支持通过registerSingleton(..)registerBeanDefinition(..)来注册。然而,典型的应用程序只使用通过常规beans定义元数据定义的beans.

    Bean元数据和人工提供的单实例需要尽早被注册,以便容器在autowiring阶段和其他自动创建阶段正确的解释他们。然而,重载现存的元数据和现存的单一实例在一定程度上是被支持的,新的beans的注册在运行时(同时访问工厂)是不被官方支持的,并且可能会导致同时访问异常,在bean容器中的 不一致的状态或两者都会发生。

    1.3.1 命名Beans

    每一个bean都有一个或多个标识符。这些标识符在承载这个bean的容器中必须要是唯一的。一个bean通过仅有一个标识符。然而,如果他需要超过一个标识符,剩余的标识符被认为成别名。

    在基于XML的配置元数据中,你是用id属性,name属性或两者都使用来指定bean标识符。id属性允许你指定确切的一个id。按照惯例,这些名字是字母数字的(‘myBean’,'someService’等等),但是他们也可以包含特殊的字符。如果你想要为该bean引入其他别名,你也可以在name属性中指定他们,通过逗号(,),分号(;)或空格分隔。作为历史注释,在Spring 3.1之前的版本中,id属性本定义在xsd:id类型上,这约束了可能的字符。至于3.1,他被定义为xsd:string类型上。注意,bean id的唯一性依然被容器是强制的,虽然不再被XML解析器强制。

    你不需要必须为一个bean提供一个name或一个id属性。如果你不明显提供一个nameid属性,容器将会为该bean生成一个唯一的名字。然而,如果你想要通过name引用那个bean,通过使用ref元素或服务定位器搜寻,你必须要提供一个名称。不提供一个名称的动机与使用inner beansautowiring 协作者相关。

    Bean命名惯例:当命名bean的时候,惯例就是为实例使用标准的Java惯例。也就是说,bean名称以小写字母开始,并且以驼峰式命名。这样命名的例子包含accountManageraccountServiceuserDaologinController等等。

    一致的命名bean使您的配置更易于阅读和理解。如果你使用Spring AOP,当将建议应用于与名称相关的一组bean时,它有很大的帮助。

    随着在类路径中组件的搜索,Spring 为未命名的组件生成bean名称,遵循上述描述的规则:基本上,采用简单的类命名并且转换这些初始的字符为小写字母。然而,在特别情况下,当超过一个字符并且第一个和第二个字符都是大写情况下,原来的例子被保存下载。这些是与定义在java.beans.Introspector.decapitalize的规则是一样的。

    在Bean定义之外为一个Bean定义别名

    在一个bean定义本身,你可以为bean提供超过一个名称,通过使用由id属性和name属性中任何其他名称指定的最多一个名称的组合。这些名称相当于相同bean的别名,并且在一些情况下是有用的,例如,在一个应用中让一个组件通过使用指定到那个组件的bean名称来引用一个通用的依赖。

    然而,在bean实际定义的地方指定所有的别名并不总是足够的。有时需要为在别处定义的bean引入别名。在大系统中,这是常用的例子,在这样的系统中,配置被分离到每一个子系统中,每一个子系统都有他自己的对象定义组。在基于XML的配置元数据中,你可以使用<alias/>元素来完成。下面的例子展示了如何去做:

    <alias name="fromName" alias="toName" />
    

    在这个例子中,一个bean被命名为fromName,在使用这个别名定义之后,被引用为toName

    例如,用于子系统A的配置元数据通过名称subsystemA-dataSource来引用一个数据源。用于子系统B的配置元数据通过名称subsystemB-dataSource来引用一个数据源。当使用这两个子系统组成主应用的时候,该主应用程序通过名称myApp-dataSource引用数据源。为了让三个名称指向同一个对象,你可以添加下面的别名定义到配置元数据中:

    <alias name="myApp-dataSource" alias="subsystemA-dataSource"/>
    <alias name="myApp-dataSource" alias="subsystemB-dataSource"/>
    

    现在每一个组件和主程序可以通过唯一的名称引用数据源,并保证不会与任何其他定义(有效创建命名空间)冲突,因此他们指向相同的bean。

    Java配置:如果你是用Java配置,@Bean注解可以被使用来提供别名。查看使用@Bean注解了解详情。

    1.3.2 实例化Bean

    一个bean定义实际上是创建一个或多个对象的菜单。当容器被询问的时候,它搜索菜单找到被命名的bean ,然后使用被该bean定义封装的配置的元数据来创建或获取一个真实的对象。

    如果你使用基于XML的配置元数据,你指定<bean/>元素的class属性中要被实例化的对象的类型。这个class属性(该属性内部是位于BeanDefinition实例的Class属性)通常是强制的。(对于异常情况,查看通过使用一个实例工厂方法实例化Bean定义继承)你可以通过下述任一一个方法来使用Class属性。

    • 典型的,在容器本身直接创建bean的情况下,通过调用他的构造函数来创建bean类,与Java编码的new操作符是一样的,从而指定要被构造的bean类。

    • 指定包含static工厂方法的实际的类,该方法被调用来创建对象,在不太通常的情况下,容器调用类中的static工厂方法来创建bean。从static工厂方法中返回的对象类型可能完全是相同的类或是其他类。

    展开全文
  • 容器的作用

    千次阅读 2015-01-30 18:49:15
    业务对象容器里运行,被容器管理。像EJB就是过去管理J2EE业务对象时最常用的容器。  任何容器都应该包含如下服务: 生命周期管理:最起码,容器应该负责创造构件。查找服务:容器必须有一种途径来获得...

    什么是轻量级容器?为什么我们需要轻量级容器?

    什么是轻量级容器

           所谓容器(container),指应用代码的运行框架。业务对象在容器里运行,被容器管理。像EJB就是过去管理J2EE业务对象时最常用的容器。

           任何容器都应该包含如下服务:

    • 生命周期管理:最起码,容器应该负责创造构件。
    • 查找服务:容器必须有一种途径来获得受控构件的引用,以使构件可以互操作。
    • 配置管理:容器应该尽可能的讲简单的易变换的配置值从代码中提取出来,为构件提供统一的方法来配置。
    • 依赖解析:管理各个构件之间的管理。

          上面讲的是容器核心模块应该提供的服务,但是容器还必须提供其它一些服务,增加实用性。

    • 企业级服务:为构件提供事务管理。
    • 线程管理:对于构件的操作,容器需要给出一个线程模型。
    • 对象池:提供一个实例池来管理对象。
    • 集群服务:容器的提供集群服务在需要支持有状态的构件是必须的,但是如果是无状态,容器提供集群服务就不是很重要了,因为容器无需为集群维护状态。
    • 管理:如JMX。
    • 远程服务:提供远程服务和访问远程对象。
    • 扩展性:可以为某一类构件提供定制的服务。

          一个轻量级容器应该遇有下列特性:

    • 非侵入性:可以管理业务代码,但是不应该给代码强加入对容器的依赖。
    • 可以快速启动。
    • 部署简单。
    • 轻量级容器是用纯JAVA开发的,不依赖J2EE。

    需要容器的理由

    以前也讲过EJB作为容器很多不令人满意的地方,那我们需要容器的理由呢?

    • 可接插性:容器将调用方法与具体的实现策略分开。虽然Java接口提供了很好的接口和实现分离,但是必须用一种方法去帮我们找到一个接口的实现,用容器调用显然比在代码中硬编码要好。
    • 服务定位:容器像是个服务的盒子,如果没有容器了,配置管理这些服务是十分困难的。
    • 为开发和部署量身订做服务。

    轻量级容器的优势

    由于代码对容器的依赖很少,很显然带来了如下好处:

    • 提高代码的复用度;在开发过程中没有假定必须在某个平台下运行。
    • 提高可测试性:在容器之外就可以完成测试。

    容器对构件的编写限制很少:

    • 更好的面向对象:容器对构件的编写限制很少,这样可以更好的实践面向对象了。
    转自:http://www.iteye.com/topic/188719

    展开全文
  • 【C++】顺序容器

    千次阅读 2016-11-08 23:44:21
    一个容器就是一些特定类型对象的集合。顺序容器为程序员提供了控制元素存储和访问顺序的能力。这种顺序依赖于元素的值,而是与元素加入容器时的位置相对应。顺序容器的概述下表列出了标准库中的顺序容器,所有顺序...

      一个容器就是一些特定类型对象的集合。顺序容器为程序员提供了控制元素存储和访问顺序的能力。这种顺序不依赖于元素的值,而是与元素加入容器时的位置相对应。

    顺序容器的概述

    下表列出了标准库中的顺序容器,所有顺序容器都提供了快速顺序访问元素的能力。但是不同容器在一下方面都有不同程度的性能折中。

    1. 向容器添加或删除元素的代价
    2. 非顺序访问容器元素的代价
    顺序容器类型
    vector ------ 可变大小数组。支持快速随机访问。在尾部之外的位置插入或删除元素可能很慢
    deque ------双端队列。支持快速随机访问。在头尾位置插入删除速度很快
    list --------双向链表。只支持双向顺序访问。在list中任何位置进行插入删除操作速度都很快
    forward_list ----单向链表。只支持单向顺序访问。在链表任何位置进行插入删除速度都很快
    array--------固定大小数组。支持快速随机访问。不能添加或删除元素
    string--------与vector相似的容器,但专门用来保存字符。随机访问速度快。在尾部插入删除速度快

       
    容器保存元素的策略对容器操作的效率有着固有的,有时是重大的影响。

    string和vector将元素保存在连续的内存空间中,由于元素是连续存储的,所以通过下标来计算其地址是很快速的。但是在插入删除某元素时,其之后的元素都需要移动,非常耗时。
    
    list和forward_list两个容器的设计目的是令容器任何位置的添加和删除操作都很快速。作为代价,这两个容器不支持随机访问,如果想访问某个元素只能遍历整个容器。这两个容器的额外内存开销很大。
    
    deque更为复杂。支持快速随机访问,在中间位置插入和删除元素的代价很高,但是在两端添加或删除元素是很快的。
    
    forward_list和array是新C++标准增加的类型。array与内置数组相比,更加安全更易使用,array对象的大小是固定的。forward_list的设计目标是达到与手写单向链表结构相当的性能,所以为了节省开销,无size操作。
    

    确定使用哪种容器

    通常,使用vector是最好的选择,除非你有很好的理由选择其他容器。
    

    基本原则:

    如果程序有很多小元素,且空间的额外开销很重要,则不要使用list或者forward_list。
    
    如果程序要求在中间位置插入或删除元素,应使用list或forward_list。
    
    如果程序需要在头尾位置插入或删除元素,但不会在中间位置插入或删除操作,则用deque。
    
    如果程序在输入时需要在容器中间位置插入元素,随后需要随机访问元素,则:
    --首先,确定是否 真的需要在中间位置插入元素。当处理输入数据时,很容易地向vector尾部追加元素,然后在调用sort来重排容器中的元素,从而避免在中间位置插入元素。
    --如果必须在中间位置插入元素,考虑在输入阶段用list,一旦输入完成后,将list中的内容拷贝到一个vector中。
    

    例子:判断下列情况应该用哪种容器 
     
    a.读取固定数量的单词,按字典序的方式插入容器中

    需要频繁地在容器内部插入数据,vector在尾部之外插入元素很慢,deque在头尾之外很慢,所以最好用list。
    

    b.从文件中读取未知数量的整数,并将这些数排序,然后将它们打印出来。

    整数占用空间不大,而且快速的**排序算法需频繁的随机访问元素**,将list排除,无需在头部进行插入删除操作,所以不需用deque,故选择vector。
    

    容器库概览

      一般来说,每个容器都定义在一个头文件中,文件名与类型名相同。即,deque定义在头文件deque中,以此类推。容器均定义为模板类,当我们定义对象时,必须提供额外的信息来生成特定的容器类型。
    比如定义一个list对象,其元素类型是int的deque:list<deque<int>> em;
      顺序容器几乎可以保存任意类型的元素,但某些容器操作对元素类型有其自己的特殊要求。

    迭代器

    迭代器介绍

      迭代器提供对一个容器中的对象的访问方法,并且定义了容器中对象的范围。迭代器就如同一个指针。事实上,C++的指针也是一种迭代器。但是,迭代器不仅仅是指针,因此你不能认为他们一定具有地址值。例如,一个数组索引,也可以认为是一种迭代器。用的最多的还是容器迭代器。

    使用迭代器

      和指针不一样的是,获取迭代器不是使用取地址符,有迭代器的类型同时拥有返回迭代器的成员。比如这些成员都有名为begin()和end()的成员,其中begin成员负责返回第一个元素的迭代器;end成员负责返回最后一个元素的下一个位置的迭代器,通常称为尾后迭代器
    ps:如果容器为空,则begin和end返回的迭代器相同,且都是尾后迭代器。

    迭代器运算符

    标准容器迭代器的运算符 作用
    *iter 返回迭代器iter所指元素的引用
    iter->mem 解引用iter并获取该元素的名为mem的成员,等价于(*iter).mem
    ++iter 令iter指向容器的下一个元素
    –iter 令iter指向元素的上一个元素
    iter1 == iter2 判断两个迭代器是否相等,如果两个迭代器指的是同一个元素或者他们是同一个容器的尾后迭代器,则相等。
    iter1 != iter2 判断两个迭代器是否不想等。判断条件同上

    和指针类似,通过解引用来获取迭代器所指的的元素。举个例子

    /*将 s 中的字母变成大写的*/
    string s = "abcdef";
    for(auto it = s.begin(); it != s.end(); ++it)
        *it = toupper(*it);

    迭代器类型

      一般来说我们并不知道迭代器的类型(我们也无需知道),而实际上,那些拥有迭代器的标准库类型使用iterator和const_iterator来表示迭代器的类型:
    vector<int>::iterator it;  //it 能读写vector<int>的元素
    string::iterator it2;    //it2能读写string对象中的字符
    vector<int>::const_iterator it3;  //it3 只能读 不能写vector<int>的元素
    string::const_iterator it4;    //it4只能读 不能写 string对象中的字符

      const_iterator和常量指针差不多,能读取但不能修改它所指向的元素值如果vector或string对象是常量,那么只能使用const_iterator。

    迭代器和迭代器类型

      我们认定某个类型是迭代器当且仅当它支持一套操作,这套操作使得我们能访问容器的元素或者从某个元素移动到另外一个元素。
      每个容器定义了一个名为iterator的类型,该类型支持迭代器类型所规定的一系列操作。

    迭代器范围

      一个迭代器范围由一对迭代器表示,两个迭代器分别指向同一个容器中的元素或者尾元素之后的位置,即begin和end。这种元素范围被称为左闭合区间,其标准数学描述为[begin,end)
      
    标准库之所以使用左闭合范围,是因为这种范围有三种方便的性质:
    1. 如果begin和end相等,则范围为空。
    2. 如果begin和end不想等,则范围至少包含一个元素,且begin指向该范围中的第一个元素。
    3. 我们可以对begin递增若干次,使begin==end。

      begin和end有多个版本:带r的版本返回反向迭代器;以c开头的返回const迭代器。不以c开头的函数都是被重载过的,也就是说实际上有两个名为begin的成员,一个是const成员,返回容器的const_iterator类型;一个是非常量成员,返回容器的iterator类型。可以将一个普通的iterator转换为对应的const_iterator,但反之不行。以c开头的是C++新标准引入的,可以只读的访问容器,但不能写。
      

    结合解引用和成员访问操作

      解引用迭代器可获得迭代器的对象,如果该对象的类型恰好是类,就有可能希望进一步访问它的成员。
      例如:对于一个由字符串组成的vector对象来说,要想检查其元素是否为空,令it是该vector的迭代器,只需检查it所指的字符串是否为空就可以了。代码如下:

    (*it).empty()  //先解引用it,调用结果对象的empty成员
    *it.empty()   //error:试图访问it的名为empty成员,但it是迭代器,没有empty成员   
    为了简化上式,C++语言定义了箭头运算符(->)。箭头运算符把解引用和成员访问两个操作结合在一起。。即it->empty()(*it).empty()意思相同。

    某些对vector对象的操作会使迭代器失效

    不能在范围for循环中向vector对象添加元素。
    任何一种改变vector对象容量的操作,都会使迭代器失效,比如push_back。原因后面再讲。
    

    谨记:但凡是使用了迭代器的循环体,都不要向迭代器所属的容器添加元素。

    例子:编写一个程序创建一个包含10个整数的vector对象,然后使用迭代器将对象元素的值变为原来的两倍,输出vector对象的内容。

      vector<int> arr;
        for(decltype(arr.size()) ix=0; ix!=10; ix++)
            arr.push_back(ix);  //向对象中添加元素
    
        vector<int>::iterator it;
        for(it = arr.begin();it!=arr.end();++it)
            (*it) *= 2;         //将元素的值变为原来的二倍
    
        for(it = arr.begin();it!=arr.end();++it)
            cout << *it << " "; //输出
        cout << endl;

    容器类型成员

      每个容器都定义了多个类型,如size_type、iterator和const_iterator等。除了上面用过的迭代器类型,大多数容器还提供反向迭代器反向迭代器就是一种反向遍历的迭代器,与正向迭代器相比,各种操作都发生了颠覆,比如对反向迭代器执行++操作,会的到上一个元素。

    容器定义和初始化

    将一个容器初始化为另一个容器的拷贝

    两种方式:

    1.  直接拷贝整个容器,要求容器类型及其元素类型必须匹配。
    2.  拷贝由一个迭代器对指定的元素范围,容器类型及其元素类型都可不同,只要能将要拷贝的元素转换为初始化的容器的元素类型即可。
    

    看下面例子:

    list<string> authors = {"Milton","Shakespare","Austen"};
    vector<const char*> articles = {"a","an","the"};
    
    list<string> list1(authors);      //正确:类型匹配
    deque<string> authlist(authors);  //错误:容器类型不匹配
    vector<string> words(articles);   //错误:元素类型不匹配
    
    //正确:可将const char*转换为string
    forward_list<string> words(articles.begin(),articles.end());

    列表初始化

    list<string> authors = {"Milton","Shakespare","Austen"};
    这样做时,我们就显式地指定了容器中的每个元素的值,除了array的容器,初始化列表还隐含地指定了容器的大小,即容器将包含和初始值一样多的元素。

    与容器大小相关的构造函数

      顺序容器(array除外)提供了另一个构造函数,它接受一个容器大小和一个(可选的)元素初始值。如果我们不提供初始值,那么标准库会创建一个值初始化器。(顺序容器特有,关联容器并不支持)
      如果元素是内置类型或提供了默认构造函数的类类型,那么可以只为构造函数提供一个容器大小参数,如果元素类型没有默认构造函数,则必须指定一个显式的元素初始值。

    vector<int> arr(10,-1); //10个int元素,每个都初始化为-1
    vector<int> arr1(10);   //10个int元素,每个都初始化为0--int默认

    标准库array具有固定大小

      与内置数组类似,标准库array的大小也是类型的一部分,定义array对象时,除了指定元素类型,还要指定容器大小:

    array<int,10>      //类型为:保存10个int的数组
    array<string,10>   //类型为:保存10个string的数组

      与其他容器不同,一个默认的array是非空的:它包含了与其大小一样多的元素。如果我们对array进行列表初始化,那么初始值的数目必须等于或小于array的大小。如果小于,那么它们用来初始化array中靠前的元素,所有剩下的元素进行值初始化。注意的是,如果元素的类型是类类型,那么该类必须有一个默认构造函数。
      与内置数组一个不同的地方是,内置数组不能进行拷贝或对象赋值操作,但array无此限制

    array<int,10> digits = {0,1,2,3,4,5,6,7,8,9};
    array<int,10>  digits1 = digits;  //正确

      array要求初始值的类型必须和要创建的容器类型一致,同时还要求元素的类型和大小也都一样。

    举个例子来复习一下本小节的初始化方法

    对6种创建和初始化vector对象的方法,每一种都给出实例。解释每个vector包含什么值。

    vector<int> a1;                  //默认初始化,容器为空
    vector<int> a2(copy);            //a2初始化为copy的拷贝
    vector<int> a3(copy.begin(),copy.end());//a3迭代器参数初始化和a2相同
    vector<int> a4 = {1,2,3,4,5,6};  //列表初始化 6个元素,分别为123456
    vector<int> a5(a4.begin()+1,a4.end()-1); //两个迭代器指定范围的拷贝 值为2345
    vector<int> a6(10,22);           //10个元素,每个值为22

    赋值和swap

    使用赋值运算符

      赋值运算符可用于所有容器,要求左边和右边的运算对象具有相同的类型。赋值运算符将其左边容器中的全部元素替换为右边容器中元素的拷贝。如果两个容器原来大小不同,那么赋值后两者的大小都与右边容器的原大小相同。标准库array类型也允许赋值,但是不能将一个花括号列表赋予它。

    使用assign

      此外,顺序容器中除了array以外的其他容器还可以使用assign赋值,允许我们从一个不同但相容的类型赋值,或者从容器的一个子序列赋值。

    list<string> names;
    vector<const char*> oldstyle;
    names = oldstyles;   //error:容器类型不相同
    name.assign(oldstyle.cbegin(),oldstyle.cend()); //right

    assign第二个版本接受一个整型值和一个元素值。它用指定数目且具有相同给定值的元素替换容器中原有的元素:

    list<string> slist(1);     //1个元素,为空string
    slist.assign(10,"Hiya!");  //10个元素,每个都是"Hiya!"

    使用swap

      swap操作交换两个相同类型容器的内容。调用swap之后,两个容器的元素将会交换,除array之外,swap不对任何元素进行拷贝、插入删除操作,可以保证在常数时间内完成,交换两个容器内容的操作保证会很快,因为元素本身并未改变,swap只是交换了两个容器的内部数据结构。
      除string之外,指向容器的指针、引用和迭代器不会在swap操作后不会失效,他们仍指向swap之前所指向的那些元素。

    
    int main()
    {
        vector<int> v1 = {1,2,3,4};
        vector<int> v2 = {5,6,7};
        vector<int>::iterator it1 = v1.begin();
        vector<int>::iterator it2 = v2.begin();
    
        cout << "交换之前 *it1 = " << *it1 << endl;
        cout << "交换之前 *it2 = " << *it2 << endl;
        v1.swap(v2);
        cout << "交换之后 *it1 = " << *it1 << endl;
        cout << "交换之后 *it2 = " << *it2 << endl;
    
        //注意交换后it1为v2的迭代器
        while(it1 != v2.end())
        {
            cout << *it1++ << " ";
        }
    
        return 0;
    }

    这里写图片描述
      
      swap交换之前,两个v1,v2代表的是容器,it1,it2代表的是迭代器;交换后,我们看到it1和it2所指的元素并未改变,it1本来指的是v1[2]中的元素,交换后it1指向v2[2]。
      对于array容器来说,交换两个容器,会真正交换它们的元素,因此交换两个array所需的时间与array中的数目成正比。

    容器大小操作

    1. 成员函数 size 返回容器中当前所有元素的数目
    2. 成员函数 empty 当size为0时返回true,否则返回false;
    3. 成员函数 max_size 返回一个大于或等于该类型容器所能容纳的最大元素数的值

    ps:forward_list 不支持 size。

    关系运算符

      每个容器都支持相等运算符(== && !=);除了无序关联容器外的所有容器都支持关系运算符(> < <+ >=)。关系运算符左右两边的运算对象必须是相同类型的容器,且元素类型也要相同。
      容器的关系运算符使用元素的关系运算符来完成比较。比较两个容器实际上是进行元素的逐对比较。

    1.  如果两个容器具有相同大小且所有元素都两两对应相等,则这两个容器相等,否则不等。
    2.  如果两个容器大小不同,但较小的容器中每个元素都等于较大容器中的对应元素,则较小容器小于较大容器。
    3.  如果两个容器都不是另一个容器的前缀子序列,则它们的比较结果取决于第一个不想等的元素的比较结果。
    

    顺序容器操作

    向顺序容器添加元素

      除array外,所有标准库容器都提供了灵活的内存管理。在运行时可以动态添加或删除元素来改变容器大小。当我们在使用这些操作时,不同的容器采用不同的策略来分配元素空间,而这些策略直接影响性能。向一个vector、string或deque插入元素会使所有指向容器的迭代器、引用和指针失效。

    向顺序容器添加元素的操作 array不支持这些操作
    forward_list 有自己专门的insert和emplace,不支持push_back 和 emplace_back
    vector 和 string 不支持push_front和emplace_front
    c.push_back(t) 在c的尾部添加一个值为t的元素
    c.push_front(t) 在c的头部添加一个值为t的元素
    c.insert(it,t) 在迭代器it指向的元素之前创建一个值为t的元素。返回指向新添加的元素的迭代器
    c.insert(it,n,t) 在迭代器it指向的元素之前创建n个值为t的元素。返回指向新添加的第一个元素的迭代器;若n为0,则返回it
    c.insert(it,b,e) 迭代器b和e指定的范围内的元素插入到迭代器it指向的元素之前。b和e不能指向c中的元素。返回指向新添加的第一个元素的迭代器;若范围为空,则返回it
    c.insert(it,il) il是一个花括号包围的元素值列表将这些给定值插入到迭代器it指向的元素之前。返回指向新添加的第一个元素的迭代器;若列表为空,则返回it

        
      向一个vector、string或deque插入元素会使所有指向容器的迭代器、指针和引用失效。
      当我们用一个对象来初始化容器时,或是采用上述方法将一个对象插入到容器中时,实际上放入到容器中的是对象值的一个拷贝,而不是对象本身。就像我们将一个对象传递非非引用参数一样,容器中的元素与提供值的对象之间没有任何关联。随后对容器中元素的任何改变都不会影响到原始对象,反之亦然。

    使用emplace操作

      新标准引进了三个新成员--emplace_front、emplace、emplace_back,这些操作构造而不是拷贝元素。这些操作分别对应push_front、insert、push_back,允许我们将元素防止在容器头部、一个指定的位置之前或容器尾部。
    区别:
      当我们调用push或insert时,我们将元素类型的对象传递给它们,这些对象被拷贝到容器中。而当我们调用一个emplace成员函数时,则是将参数传递给元素类型的构造函数,emplace成员使用这些参数在容器管理的内存空间中直接构造元素。

    /*在c的末尾构造一个Sales_data对象*/
    //使用三个参数的Sales_data构造函数
    c.emplace_back("97801-2-2-2-",25,19.22);
    //error
    c.push_back("97801-2-2-2-",25,19.22);
    //right创建一个临时对象传递给push_back
    c.push_back(Sales_data("97801-2-2-2-",25,19.22));

    emplace函数的参数根据元素类型而变化,参数必须与元素类型的构造函数相匹配。

    访问元素

    在容器中访问元素的操作
    c.back() 返回c中尾元素的引用。若c为空,函数行为未定义
    c.front() 返回c中首元素的引用。若c为空,函数行为未定义
    c[n] 返回c中下标为n的元素的引用,n是一个无符号整数。若n>c.size(),函数行为未定义
    c.at(n) 返回下标为n的元素的引用。如果下标越界则抛出一out_of_range异常

       
      访问成员函数返回的是引用,如果我们希望使用此变量来改变元素的值,必须将变量定义为引用类型。

    int main()
    {
        vector<int> v = {1,2,4,5,6,7};
    
        //空vector会出现段错误
        vector<int> v1;
        cout << v.front() << " ";
        cout << v.back() << " ";
        cout << v[0] << " ";
        cout << v.at(0) << " ";
    
        return 0;
    }

    输出:1 7 1 1

    删除元素

    顺序容器的删除操作 array不支持这些会改变大小的操作
    forward_list 有自己特殊版本的erase,不支持pop_back
    vector 和 string 不支持pop_front
    c.push_back() 删除c中尾元素。若c为空,则函数行为未定义。函数返回void
    c.push_front() 删除c中首元素。若c为空,则函数行为未定义。函数返回void
    c.erase(p) 删除迭代器p所指定的元素,返回一个指向被删元素之后元素的迭代器,若p指向尾元素,则返回尾后迭代器。若p是尾后迭代器,则函数行为未定义
    c.erase(b,e) 删除迭代器b和e之间的元素,e指向我们要删除的最后一个元素之后的元素。返回一个指向最后一个被删元素之后元素的迭代器,若e本身就是尾后迭代器,则函数也返回尾后迭代器
    c.clear() 删除c中所有元素。返回void

    改变容器大小

    我们可以使用resize来增大或缩小容器。

    c.resize(n,t);  //调整c的大小为n个元素。任何新添加的元素都初始化为值t
    c.resize(n);  //调整c的大小为n个元素。若当前大小大于所要求的大小,则多出的后部元素被删除;若必须添加元素,则对新元素进行值初始化。

    ps:resize操作接受一个可选的元素值参数,用来初始化添加到容器中的元素。如果调用者未提供此参数,新元素进行值初始化。如果是类类型,则我们必须提供初始值,或者元素类型必须提供一个默认构造函数。

    容器操作可能使迭代器失效

      向容器中添加或删除元素的操作可能会使指向容器元素的指针、引用或迭代器失效,使用失效后的指针、引用或迭代器是一种严重的程序设计错误,很可能会引起与为初始化指针一样的错误。
      

    1. 在向容器添加元素后:

        vector和string:
        存储空间被重新分配,则指向容器元素的指针、引用或迭代器都会失效。
        存储空间未被重新分配,则指向容器插入元素之前的指针、引用或迭代器扔有效,但指向容器插入位置之后的指针、引用或迭代器将会失效。
        deque:
        插入到除首尾位置之外的任何位置,都会使指向容器元素的指针、引用或迭代器失效。如果在首尾位置添加元素,迭代器会失效,但指向存在元素的指针或引用不会失效。
        list和forward_list:
        指向容器元素的指针、引用或迭代器(包括尾后迭代器和首前迭代器)仍有效。
    

    2. 从容器删除元素后

    vector和string:
    指向被删元素之前的指针、引用或迭代器仍有效。
    deque:
    如果在首尾位置之外的任何位置删除元素,那么指向被删除元素之外的任何元素的指针、引用或迭代器也会失效。如果是删除deque的尾元素,则尾后迭代器也会失效,但其他迭代器、引用或指针不受影响;如果是删除首元素,其他元素都不受影响。
    list和forward_list:
    指向容器其他位置的迭代器(包括尾后迭代器和首前迭代器)、引用或指针都不会失效。
    

    ps:
      添加/删除vector、string或deque元素的循环程序必须考虑迭代器、引用和指针可能失效的问题。程序必须保证每个循环步中都更新迭代器、引用或指针。
      当我们添加/删除vector或string的元素后,或在deque中首元素之外的任何位置添加/删除元素之后,原来end返回的迭代器总是失效。因此,添加删除元素的循环程序必须反复调用end,而不能在循环之前保存end返回的迭代器。。。通常C++标准库的实现中end()操作都很快,部分就是因为这个原因。

    额外的string操作

    s.substr(pos,n) 返回一个string,包含s中从pos开始的n个字符的拷贝。pos的默认值为0,n的默认值为s.size()-pos,即拷贝从pos开始的所有字符
    
    string s("hello world");
    string s2 = s.substr(0,5);  //s2 = hello
    string s3 = s.substr(6);    //s3 = world
    string s4 = s.substr(6,11); //s4 = world
    string s5 = s.substr(12);   //抛出一个out_of_range异常
    
    s.append(args) 将args追加到s.返回一个指向s的引用
    s.replace(range,args) 删除s中范围range内的字符,替换为args指定的字符。range或者是一个下标和一个长度,或者是一对指向s德尔迭代器。返回一个指向s的引用
    string s = "C++ primer";
    string s2 = s.append(" 5th Ed."); //s2 = "C++ Primer 5th Ed."
    s2.replace(11,3,"Fifth"); //s2 = "C++ Primer Fifth Ed."
    
    string搜索操作
    s.find(args) 查找s中args第一次出现的位置,若找到返回第一个匹配位置的下标,否则返回npos
    s.rfind(args) 查找s中args最后一次出现的位置(逆向搜索 从右向左)
    s.find_first_of(args) 在s中查找args中任何一个字符 第一次 出现的位置
    s.find_last_of(args) 在s中查找args中任何一个字符 最后一次 出现的位置
    s.find_first_not_of(args) 在s中查找第一个不在args中的字符
    s.find_last_not_of(args) 在s中查找最后一个不在args中的字符
    每个操作都接受一个可选的第二参数,可用来指定从什么位置开始搜索
    s.find(args,pos)  //pos为 s 的下标
    

    vector对象是如何增长的?

    策略:

      为了支持快速随机访问,vector将元素连续存储--每个元素紧挨着前一个元素存储。假定容器是连续存储的,且容器的大小是可变的,考虑向vector或string中添加元素会发生什么:如果没有空间容纳元素,容器不可能简单地将它添加到内存中其他位置--因为元素必须连续存储。*容器必须分配新的内存空间来保存已有元素和新元素,将已有元素从旧位置移动到新空间中,然后添加新元素,释放旧存储空间。*如果没添加一个元素,vector就执行这样一次内存分配,性能会慢到无法接受。
      标准库实现者采用了可以减少容器空间重新分配次数的策略。当不得不获取新的内存空间时,vector和string的实现通常会分配比新空间需求更大的内存空间。容器预留这些空间作为备用,可用来保存更多的新元素。
      这种分配策略性能上要高效的多。

    管理容量的成员函数

    vector和string类型提供了一些成员函数,允许我们与它的实现的内存分配部分互动。

    容器大小管理操作
    :shrink_to_fit()适用于vector string deque capacity() reserve(n) 适用于vector和string
    c.shrink_to_fit() 请将capacity()减少为与size()相同大小
    c.capacity() 不重新分配内存空间的话,c可以保存多少元素
    c.reserve(n) 分配至少能容纳n个元素的内存空间(尽影响预先分配多大的内存空间)

    具体分配时的相互调用规律

      只有当需要的内存孔家超过当前容量时,reserve调用才会改变vector的容量。如果需求大小大于当前容量,reserve至少分配与需求一样大小的内存空间(可能更大)。
      如果需求大小小于或等于当前容量,reserve什么都不做。特别是,当需求大小小于当前容量时,容器不会退回内存空间。因此,在调用reserve后,capacity将会大于或等于传递给reserve的参数。
      这样,调用reserve永远也不会减少容器占用的内存空间。类似的,resize成员只改变容器中元素的数目,而不是容器的容量。

    capacity和size的区别

    容器的size是指它已经保存的元素的数目;

    capacity是在不分配新的内存空间的前提下它最多可以保存多少个元素。

    /*************************************************************************
        > File Name: 9.4.cpp
        > Author: Tanswer
        > Mail: 98duxm@gmail.com
        > Created Time: 2016年11月08日 星期二 22时53分14秒
     ************************************************************************/
    
    #include <iostream>
    #include <string>
    #include <vector>
    #include <algorithm>
    
    using namespace std;
    
    int main()
    {
        vector<int> ivec;
        //size应为0;capacity的值依赖于具体实现
        cout << "ivec:size: " << ivec.size() << " capacity: " << ivec.capacity() << endl;
    
        //向ivec添加24个元素
        for(int i=0; i!=24; i++)
            ivec.push_back(i);
    
        //size应为24;capacity的值依赖于具体实现
        cout << "ivec:size: " << ivec.size() << " capacity: " << ivec.capacity() << endl;
    
        //现在预分配一些空间
        ivec.reserve(50);  //将capacity至少设定为50,可能会更大
        //size应为24;capacity的值依赖于具体实现,应该大于等于50
        cout << "ivec:size: " << ivec.size() << " capacity: " << ivec.capacity() << endl;
    
        //添加元素用光这些空间
        while(ivec.size() != ivec.capacity())
            ivec.push_back(0);
    
        //capacity应该未改变,size和capacity不相等
        cout << "ivec:size: " << ivec.size() << " capacity: " << ivec.capacity() << endl;
    
        //由于我们只使用了预留空间,所以内存空间不会重新分配
        ivec.push_back(42);
        cout << "ivec:size: " << ivec.size() << " capacity: " << ivec.capacity() << endl;
    
    
        return 0;
    }

    输出结果:

    这里写图片描述

    展开全文
  • kubernetes容器编排系统介绍

    万次阅读 2016-10-18 11:12:26
    Kubernetes作为容器编排生态圈中重要一员,是Google大规模容器管理系统borg的开源版本实现,吸收借鉴了google过去十年间在生产环境上所学到的经验与教训。 Kubernetes提供应用部署、维护、 扩展机制等功能,利用...
  • 关联容器

    千次阅读 2007-05-22 16:04:00
    关联容器
  • C++中STL各容器详解

    千次阅读 2019-03-09 17:10:57
    2)迭代器(Iterator),提供了访问容器对象的方法。例如,可以使用一对迭代器指定list或vector中的一定范围的对象。迭代器就如同一个指针。事实上,C++的指针也是一种迭代器。但是,迭代器也可以...
  • docker容器技术基础

    千次阅读 2018-10-24 15:56:48
    什么是容器 容器是对应用程序及其依赖关系的封装。 容器的优点 容器与主机的操作系统共享资源,提高了效率,性能损耗低 容器具有可移植性 容器是轻量的,可同时运行数十个容器,模拟分布式系统 ...
  • 百问百答之Java容器

    千次阅读 2016-04-30 13:07:20
    下列说法正确的是()A. LinkedList继承自List B. AbstractSet继承自Set C. HashSet继承自AbstractSet D. WeakMap继承自HashMap答案:ACJava集合类框架的基本接口有哪些?Java 集合类提供了一套设计良好的支持对一组...
  • 顺序容器及相应的容器适配器

    千次阅读 2011-08-16 11:34:15
    本文主要讨论C++标准库中的顺序容器及相应的容器适配器,这些内容主要涉及顺序容器类型:vector、list、deque,顺序容器适配器类型:stack、queue、priority_queue。  如果文中有错误或遗漏之处,敬请指出,谢谢!...
  • 顺序容器及相应的容器适配器

    千次阅读 2007-03-07 14:27:00
    概述 标准库中的容器分为顺序容器和关联容器。顺序容器(sequential container)内的元素按其位置存储和访问,顾名思义,这些内部元素是顺序存放的;顺序容器内的元素排列次序与元素值无关,而是由元素添加到容器里的...
  • C++中容器vector的用法

    千次阅读 2015-08-30 12:30:53
    参考C++ primer.  vector 是同一种类型的对象的集合,每个对象都有一个...一个容器中的所有对象都必须是同一种类型的。我们将在第 9 章更详细地介绍容器。  使用 vector 之前,必须包含相应的头文件。本书给
  • Python_序列对象内置方法详解_String

    千次阅读 2015-11-01 19:40:55
    len 获取序列对象的长度 zip 混合两个序列对象 enumerate 枚举出序列对象的元素 sorted 序列的排序 reversed 返回一个逆序访问的迭代器 min 取出sequence中的最小值 max 取出sequence中的最大值
  • Java集合容器全面分析

    千次阅读 2016-08-07 17:45:17
    集合可以用来储存任何类型的对象,给程序员提供了代码编写的灵活性,但是同时也带来了类型安全的问题。JDK1.5集以上的版本支持Generics,这种新概念的提出改变了Java传统代码的编写方式。(Generics type)编程概念...
  • C语言下的容器及泛型编程

    千次阅读 2013-04-20 19:21:55
    众所周知,C++语言提供了大名鼎鼎的标准模板库(STL)作为C++语言下的编程利器受到无数青睐,而C语言下并没有提供类似的工具,使得C语言的开发变得尤为困难和专业化。Nesty框架的NCollection容器为C语言提供了一整套...
  • 1、分布式对象包集成了面向对象的语言的特征和优点。能够使用户用类似面向对象的语言调用的层次上去实现远程的方法调用。 2、分布式对象有下面的一些优点:1、包装性。2、他将一个对象的实现和对象本身分离了。3、...
  • 1. Master 集群的控制节点,负责整个集群的管理和控制,kubernetes的...kube-apiserver:资源增删改查的入口kube-controller-manager:资源对象的大总管kube-scheduler:负责资源调度(Pod调度)etcd Server:kubern
  • 在Faces API中有两个类是要经常使用的. 一个是FacesContext 一个是ExternalContext....FacesServlet对象下列3个取自Web容器对象传给javax.faces.context.FacesContextFactory对象的getFacesContext
  • 模板和容器

    千次阅读 2015-05-14 15:01:57
    不管是对象还是函数指针等等,它们都是可以被作为参数传递,或者被作为变量保存的。因此我们就可以把一个仿函数传递给一个函数,由这个函数根据需要来调用这个仿函数(有点类似回调)。 STL 模板库中,大量使用了...
  • 例如,Traversable类 的map方法会返回另一个Traversable对象作为结果,但是这个结果类型在子类中被重写了。例如,在一个List上调用map会又生成一个List,在Set上调用会再生成一个Set,以此类推。 scala > List ( 1 ...
  • Core Data 教程:多托管对象上下文

    千次阅读 2016-11-11 09:02:27
    原文:Multiple Managed Object Contexts with Core Data Tutoria 作者:Matthew Morey 译者:kmyhy 托管对象上下文是一个专门给托管对象使用的内存快照。...多托管对象上下文的 app 很难调试,它并适用
  • Java的容器 List、Set、Map的区别

    千次阅读 2016-11-04 23:11:52
    从传统意义上讲,数组是我们的一个很好的选择,前提是我们事先已经明确知道我们将要保存的对象的数量。一旦在数组初始化时指定了这个数组长度,这个数组长度就是可变的,如果我们需要保存一个可以动态增长的数据...
  • kubernetes (k8s)容器编排系统

    千次阅读 2019-01-15 10:04:20
    Kubernetes作为容器编排生态圈中重要一员,是Google大规模容器管理系统borg的开源版本实现,吸收借鉴了google过去十年间在生产环境上所学到的经验与教训。 Kubernetes提供应用部署、维护、 扩展机制等功能,利用...
  • C++ STL 基础及应用(6) 容器

    千次阅读 2016-06-05 23:04:47
    其实 STL 提供了专家级的几乎我们所需要的各种容器,功能更好,效率更高,复用性更强,所以开发应用系统应该首选 STL 容器类,摒弃自己的容器类,尽管它可能花费了你很多的开发时间。 本章将介绍 STL 中的通用容器,...
  • Python基础之:Python中的内部对象

    千次阅读 2021-04-06 09:04:32
    Python中内置了很多非常有用的对象,本文将会介绍Python中的内置函数,内置常量,内置类型和内置异常。
  • Java集合容器简介

    千次阅读 2011-11-09 19:06:26
    Java集合容器主要有以下几类:  1,内置容器:数组  2,list容器:Vetor,Stack,ArrayList,LinkedList,  CopyOnWriteArrayList(1.5),AttributeList(1.5),RoleList(1.5),RoleUnresolvedList(1.5),  ...
  • java面试题32:Java网络程序设计中,下列正确的描述是() A:Java网络编程API建立在Socket基础之上 B:Java网络接口只支持tcP以及其上层协议 C:Java网络接口只支持UDP以及其上层协议 D:Java网络接口支持IP...
  • stl容器--总结

    千次阅读 2013-09-20 19:14:37
    STL主要包含容器、算法、迭代器三大核心部分; 序列式容器中的元素顺序与元素值无关,只与元素插入的次序和存放位置有关;三种序列式容器,即Vectors(向量)、Deque(双向队列)和List(双向链表)。 vector:向量...
  •  本文主要讨论C++标准库中的顺序容器及相应的容器适配器,这些内容主要涉及顺序容器类型:vector、list、deque,顺序容器适配器类型:stack、queue、priority_queue。 如果文中有错误或遗漏之处,敬请指出,谢谢!...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 30,489
精华内容 12,195
关键字:

下列对象不能作为容器的是