zoukankan      html  css  js  c++  java
  • 【C++ Primer】用于大型程序的工具

    1. 异常处理

    异常以类似于将实參传递给函数的方式抛出和捕获。异常可以是可传给非引用实參的随意实參的类型,这意味着必须可以复制该类型的对象。

    当抛出一个表达式的时候,被抛出对象的静态编译时类型将决定异常对象的类型。

    抛出指针一般是个坏主意。


    栈展开:沿着嵌套函数调用链继续向上,直到为异常找到一个catch子句。

    栈展开期间。释放局部对象所用的内存并执行类类型局部对象的析构函数


    一个块能够通过new动态分配内存。假设该块因异常而退出。编译器不会删除该指针。已分配的内存将不会释放。


    析构函数应该从不抛出异常。假设真的抛出自己的未经处理的还有一个异常,将会导致调用terminate函数,然后terminate函数将调用abort函数,使得程序非正常退出。


    构造函数发生异常,要保证会适当地撤销已构造的成员。


    对于未被匹配的异常,程序会调用库函数terminate。


    catch子句中的异常说明符(exception specifier)看起来像仅仅包括一个形參的形參表,异常说明符是在其后跟一个(可选)形參名的类型名:catch(const runtime_error &e)

    catch子句中的异常说明符必须是全然类型,不能够为前置声明,由于你的异常处理中经常要訪问异常类的成员。例外:仅仅有你的catch子句使用指针或者引用接收參数。而且在catch子句内你不訪问异常类的成员,那么你的catch子句的异常说明符才干够是前置声明的类型。

    catch的匹配过程是找最先匹配的。不是最佳匹配

    catch的匹配过程中。对类型的要求比較严格同意标准算术转换类类型的转换。(类类型的转化包含种:通过构造函数的隐式类型转化和通过转化操作符的类型转化)



    又一次抛出(rethrow):将异常传递给函数调用链中更上层的函数(而不是同一个函数的下一个catch)。  仅仅用个空throw语句: throw;

    仅仅能出如今catch或者从catch调用的函数中。假设在处理代码不活动时碰到空throw,就调用terminate函数。

    被抛出的异常是原来的异常对象。而不是catch形參

    catch能够改变它的形參。在改变形參之后。假设catch又一次抛出异常,仅仅有当异常说明符是引用的时候。才会传播那些改变。



    能够使用catch(...){}来捕获全部异常,假设与其它catch子句结合使用,它必须是最后一个。否则,不论什么它后面的catch子句都不能被匹配。


    要想处理来自构造函数初始化式的异常。唯一的方法是将构造函数编写为函数測试块:

    A : : A(int a ) 

    try : mem(a), use(new size_t(1))

    {

        // 函数体

    }catch(const std::bad_alloc &e)

    {handle_out_of_memory(e);}


    exception类型定义的唯一操作是一个名为what的虚成员,该函数返回const char* 对象。一般返回用来在抛出位置构造异常对象的信息。



    1、auto_ptr为标准库提供的“资源分配即初始化”类。是接受一个类型形參的模板,它为动态分配的对象提供异常安全特性。

    memory头文件里定义。

    2、auto_ptr操作

    auto_ptr<T> ap;

    创建名为 ap 的未绑定的 auto_ptr 对象

    auto_ptr<T>

    ap(p);

    创建名为 ap 的 auto_ptr 对象。ap 拥有指针 指向的对象。

    该构造函数为explicit

    auto_ptr<T> ap1(ap2);

    创建名为 ap1 的 auto_ptr 对象。ap1 保存原来存储在ap2 中的指针。

    将全部权转给 ap1ap2 成为未绑定的auto_ptr 对象

    ap1 = ap2

    将全部权 ap2 转给 ap1。删除 ap1 指向的对象而且使 ap1指向 ap2 指向的对象,使 ap2 成为未绑定的

    ~ap

    析构函数。删除 ap 指向的对象

    *ap

    返回对 ap 所绑定的对象的引用

    ap->

    返回 ap 保存的指针

    ap.reset(p)

    假设 与 ap 的值不同,则删除 ap 指向的对象而且将 ap绑定到 p

    ap.release()

    返回 ap 所保存的指针而且使 ap 成为未绑定的

    ap.get()

    返回 ap 保存的指针

        auto_ptr仅仅能用于管理从new返回的一个对象,它不能管理动态分配的数组。

    auto_ptr被复制或赋值的时候,有不寻找的行为,因此不能将auto_ptr存储在标准库容器类中。

    3、每一个auto_ptr对象绑定到一个对象或者指向一个对象。当auto_ptr对象指向一个对象的时候。能够说它“拥有”该对象。当auto_ptr对象超出作用域或者另外撤销的时候,就自己主动回收auto_ptr所指向的动态分配对象。

        auto_ptr是能够保存不论什么类型指针的模板。

    演示样例

    void f()
    {
        auto_ptr<int> ap(new int(42)); // allocate a new object
        // code that throws an exception that is not caught inside f
    }   // auto_ptr freed automatically when function ends

    4、注意到,接受指针的构造函数为explicit构造函数,所以必须用初始化的直接形式来创建auto_ptr对象。

    演示样例

    // error: constructor that takes a pointer is explicit and can't be used implicitly
    auto_ptr<int> pi = new int(1024);
    auto_ptr<int> pi(new int(1024)); // ok: uses direct initialization

    pi所指的由new表达式创建的对象在超出作用域时自己主动删除。

    5、auto_ptr的主要目的是在保证自己主动删除auto_ptr对象引用的对象的同一时候,支持普通指针式行为。

    演示样例

    auto_ptr<string> ap1(new string("Hellobaby!"));
    *ap1 = "TRex"// assigns a new value to the object to which ap1 points
    string s = *ap1; // initializes s as a copy of the object to which ap1 points
    if (ap1->empty()) // runs empty on the string to which ap1 points

    6、auto_ptr对象的赋值和复制是破坏性操作行为,与普通指针的复制和赋值有差别。

    普通指针赋值或复制后两个指针指向同一对象。而auto_ptr对象复制或赋值后,将基础对象的全部权从原来的auto_ptr对象转给副本,原来的auto_ptr对象重置成为未绑定状态。

        因此。auto_ptr赋值的左右操作数必须是可改动的左值。

    由于标准库容器要求在复制或赋值后两对象相等。所以auto_ptr不能存储在标准容器中。

    7、默认情况下。auto_ptr的内部指针值置为0

    8、測试auto_ptr对象

        auto_ptr 类型未定义到可用作条件的类型的转换。相反,要測试auto_ptr 对象,必须使用它的 get 成员。该成员返回包括在 auto_ptr 对象中的基础指针。

    演示样例

    // error: cannot use an auto_ptr as a condition
    if (p_auto)
       *p_auto = 1024;
    // revised test to guarantee p_auto refers to an object
    if (p_auto.get())
       *p_auto = 1024;

        应该仅仅用 get 询问 auto_ptr 对象或者使用返回的指针值,不能用 get 作为创建其它 auto_ptr 对象的实參。

    9、reset操作

    不能直接将一个地址(或其他指针)赋给auto_ptr对象。

    演示样例

    #include <iostream>
    #include "memory"
    using namespace std;
     
    int main()
    {
        auto_ptr<int> p_auto(new int(123));
        //p_auto = new int(1024); // error: cannot assign a pointer to an auto_ptr
        if (p_auto.get())
            *p_auto = 1024;
        else
            p_auto.reset(new int(1042));
        return 1;
    }

        正如自身赋值是没有效果的一样,假设调用该 auto_ptr 对象已经保存的同一指针的 reset 函数。也没有效果,不会删除对象。

    10、正确使用auto_ptr类。有例如以下限制:

    1)不要使用auto_ptr对象保存指向静态分配对象的指针。否则。当auto_ptr对象本身被撤销的时候,它将试图删除指向非动态分配对象的指针,导致没有定义的行为。

    2)永远不要使用两个 auto_ptrs 对象指向同一对象,导致这个错误的一种明显方式是,使用同一指针来初始化或者 reset 两个不同的 auto_ptr 对象。

    还有一种导致这个错误的微妙方式可能是,使用一个 auto_ptr 对象的 get 函数的结果来初始化或者 reset还有一个 auto_ptr 对象。

    3)不要使用 auto_ptr 对象保存指向动态分配数组的指针。当auto_ptr 对象被删除的时候,它仅仅释放一个对象—它使用普通delete 操作符,而不用数组的delete [] 操作符。

    4)不要将 auto_ptr 对象存储在容器中。容器要求所保存的类型定义复制和赋值操作符。使它们表现得类似于内置类型的操作符:在复制(或者赋值)之后。两个对象必须具有同样值。auto_ptr 类不满足这个要求。



    异常说明

    1)定义

    演示样例

    void recoup(intthrow(runtime_error);
    void no_problem() throw(); //空说明列表指出函数不抛出不论什么异常

    异常说明是函数接口的一部分,函数定义以及该函数的随意声明必须具有同样的异常说明。假设函数声明没有指定异常说明。则该函数能够抛出随意类型的异常。

    编译器在编译时不能也不会试图验证异常说明。

    假设函数抛出了没有在其异常说明中列出的异常,就直接调用unexpected函数终止程序。

     当某个类的析构函数声明了不抛出异常时,另外一个类继承该类时,而且有成员时,也必须将析构函数声明为不抛出异常。

    基类的虚函数的异常说明能够与派生类中相应虚函数的异常说明不同,但派生类虚函数的异常说明应当更严格。

    基类中虚函数异常列表是派生类异常列表的超集。

    能够在函数指针的定义中提供异常说明。在用还有一指针初始化带异常说明的函数的指针。或者将后者赋值给函数地址的时候,两个指针的异常说明不必同样,可是,源指针的异常说明必须至少与目标指针的一样严格


    2. 命名空间

    给定作用域中出现的名字冲突问题叫做命名空间污染问题(namespace pollution)。

    命名空间可以划分全局命名空间。

    形如:

    namespace cplusplus_primer{

    ...

    }

    命名空间能够在全局作用域或者其它作用域内部定义。但不能在函数或类内部定义。

    命名空间不能以分号结束。

    命名空间外部的代码必须指出名字定义在哪个命名空间:   xxspace::Query q; 也能够使用using namespace **直接说明。


    命名空间能够是不连续的,多个命名空间能够累积定义。可是,假设命名空间的一个部分须要定义在还有一个文件的名字。仍然必须声明该名字

    利用这个性质能够实现接口和实现的分离。


    能够在命名空间外部定义命名空间成员,返回类型以及函数名必须指出名字限定。类似于在类外部定义类成员函数:

    cplus:: Sales_item cpuls:: operate + ( const  Sales_item  & l, const  Sales_item  & r),可是不能定义在不相关的命名空间中。


    全局命名空间   ::member_name


    未命名的命名空间的定义局部于特定文件。从不跨越多个文本文件。当中定义的名字能够直接使用。

    假设在文件最外层定义域中定义未命名的命名空间。则当中名字必须与全局作用域中定义的名字不同

    int i;

    namespace {

        int i;  //  error, 重定义

    }

    在引入命名空间之前,程序必须将名字声明为static,使得他们局部于一个文件。可是c++推荐使用未命名的命名空间来取代


    命名空间别名:  namespace a = cplus;   namespace b = cplus::m;

    using声明一次仅仅引入一个命名空间成员: using std::vector;

    using指示使得特定命名空间的全部名字可见: using namespace std;

    相对于依赖于using指示,对程序使用using声明更好。能够避免名字冲突,除了在函数或其它作用域内部,头文件不应该包括using指示或using声明



    实參相关的查找与类类型形參:

    考虑以下的简单程序:

    std::string s;

    getlint(std::cin, s);

    为什么能够不加std::限定符或者using声明就使用getline呢?

    编译器会在当前作用域、包括调用的作用域以及定义cin类型和string类型的命名空间查找匹配的函数。

    接受类类型形參(或类类型指针及引用形參)的且与类本身定义在同一个命名空间中的函数(包含重载操作符),在用类类型对象(或类类型指针及引用)作为实參的时候是可见的

    所以std::string s; std::cin >> s; 中的操作符不用再加作用域限定符了。



    隐式友元声明与命名空间:

    当一个类声明友元函数(非成员函数)的时候,函数的声明不必是可见的。假设不存在可见的声明,友元声明具有将该函数或类的声明放入外围作用域的效果。假设类在命名空间内部定义。则没有另外声明的友元函数在同一个命名空间中声明。

    namespace A

    {

        class C{

            friend void f(const C&); // 相当于在A中进行了声明,

        }

    }

    void f2()

    {

        A::C cobj;

        f(cobj);  // 这里能够不加命名空间限定符就是由于实參相关查找与类类型形參的原则。

    }



    可能存在异常的程序以及分配资源的程序应该使用类来管理这些资源。使用类管理分配和回收能够保证假设发生异常就释放资源。这一技术常称为“资源分配即初始化”RAII




    3. 多重继承(multiple inheritance)与虚继承


    class Bear: public ZooAnimal{};

    class Panda: public Bear, pulic Endangered{};


    多重继承构造的次序:

    基类构造函数依照基类构造函数在类派生列表中的出现次序调用:在上例中,即是ZooAnimal, Bear, Endangered


    多重继承析构的次序:与构造的次序相反


    多个基类之间的转换:

    对于派生类来说。基类之间都是等价的,假设存在两个函数

    void print(const Bear&);

    void print(const Endangered&);

    调用print(Pandar()) 是会出现二义性的,会产生编译错误


    假定全部基类都将他们的析构函数适当定义为虚函数,那么,不管通过哪种指针类型删除对象,虚析构函数的处理都是一致的:

    delete pbear;

    delete pzooAnimal;


    赋值操作符与复制构造函数的行为与构造函数类似,构造的顺序也一样



    多个基类easy导致二义性:

    假定Bear与Endangered中都定义了print成员,可是Panda中未定义,则panda.print()在编译时会出错,由于不知道调用哪个版本号

    假设两个继承的print函数有不同的形參表,相同会错误发生。

    假设再ZooAnimal中定义了print而Bear类中未定义,也会出错。


    避免二义性的方法有:自己明白指定使用哪个,如 panda.Bear::print()。或者直接在Panda中定义一个print版本号。

    虚继承:
    在虚继承中,对给定虚基类,不管该类在派生层次中作为虚基类出现多少次。仅仅继承一个共享的基类子对象。共享的基类子对象称为虚基类(virtual base class):
    class B: class virtual A{};
    class C: virtual class A{};
    class D: public B, public C{};

    使用虚函数的多重继承层次比没有虚继承的引起更少的二义性。


     a:假设在每一个路径中x表示同一虚基类成员,则没有二义性。由于共享该成员的单个实例;
    b:假设在某个路径中x是虚基类的成员。而在还有一路径中x是后代派生类的成员,也没有二义性,由于特定派生类实例的优先级高于共享虚基类的实例
     c:假设沿每一个继承路径。x表示后代派生类的不同成员,则该成员的直接訪问是二义性的,就像非虚多重继承层次一样,这时能够在派生类中定义一个同名成员函数,在该函数中指定限定名訪问成员x;


    初始化:

    为了解决反复初始化问题。从具有虚基类的类继承的类对初始化进行特殊处理。

    在虚派生中。有最底层派生类的构造函数初始化虚基类。也就是由D来初始化A。假设D没有显示初始化A基类,就使用A的默认构造函数,假设A没有默认构造函数,则代码出错。


    构造与析构:

    不管虚基类出如今继承层次的不论什么地方。总是在构造非虚基类之前构造虚基类。


  • 相关阅读:
    Adjacent Bit Counts(uvalive)
    UVALIVE 4556 The Next Permutation
    vector(实现存图)
    最大连续子序列和(模板)
    全选和反选
    .netCore上传图片,要用FormFileCollection ,不能用List
    .NET-Core中 HttpContext.Response.Write() 中文输出乱码
    Core中Cookie和Session的新用法
    Ajax反填
    复选框变成单选
  • 原文地址:https://www.cnblogs.com/cynchanpin/p/7337438.html
Copyright © 2011-2022 走看看