C++ primer 薄片系列之大型程序的工具

捕获异常

class A
{
public:
    A()
    {

    }
    ~A()
    {
        std::cout << "I am done" << std::endl;
    }
};
void test()
{
    try
    {
        A a;
        throw std::runtime_error("sdss");//捕获异常能保证A 的析构函数正确执行
    }
    catch(std::exception e)
    {
        std::cout << e.what() << std::endl;
    }
}

捕获所有异常

void test2()
{
    try{
        throw std::runtime_error("sdss");
    }catch(...)
    {

    }
}

noexcept

函数所有声明和定义语句均需要包含noexcept
c++11 之前不抛出异常的声明方式是throw()

void mm()noexcept;
void mm()noexcept
{

}
void nn()throw();
void nn()throw()
{

}

noexcept(bool)
noexcept接受一个可选参数bool

void re()noexcept(true)
{

}
noexcept(re())返回个bool 变量,其中re()抛出异常则返回false, 否则返回true
void mm2()noexcept(noexcept(re()))//mm2和re抛出异常表现一致
{

}

函数指针及该函数所指的函数必须具有一致的异常说明
但是如果显示声明了指针可能抛出异常的话,则该指针能够指向任何抛出异常和不抛出异常的函数

void re()noexcept(true)
{

}
void m2()
{
    throw std::runtime_error("sss");
}
int main()
{
    void (*pf1)() noexcept = re;//pf1 显示声明为不抛出异常, 和re函数的异常说明一致
    void (*pf2)() noexcept = m2; //错误,m2没有声明不抛出异常,但是pf2被声明了抛出异常. But 编译器能通过,编译器不会检测异常声明
    void (*pf3)() = re;//我们显示声明了pf3可能抛出异常,则该指针能指向任何函数,也就是既可以指向抛出异常的函数,也可以指向不抛出异常的函数
    pf2();//能够正确运行
}

exception 继承体系

最顶层:exception
继承于exception的类: std::bad_cast, std::logic_error, std::runtime_error, std::bad_alloc
继承于std::runtime_error的类 : std::overflow_error, std::underflow_error, std::range_error
继承于std::logic_error的类: std::domain_error, std::invalid_argument, std::out_of_range, std::length_error

可以写自己的错误类型

class M:public std::runtime_error
{
public:
    explicit M(const std::string &s):std::runtime_error(s)
    {

    }
};
void test()
{
    try
    {
        throw M("haha");
    }
    catch( M e)
    {
        std::cout << e.what() << std::endl;
    }
}

命令空间

namespace nn
{
}

内联命名空间

//nn.h
inline namespace nn//第一次出现的时候必须标注为inline
{
}
namespace nn//再写的时候可以省略inline
{
}
//main.cpp
namespace LL
{
#include"nn.h"
    int n = 0;
    void test()
    {
        m = 10;//这里的m即是nn.h中 的m。并且LL中不能再有同名变量m
        std::cout << m << std::endl;
    }
}

未命名空间

未命名空间中定义的变量具有静态生命周期,第一次使用前创建,并且到程序结束时销毁
只能在给定文件内不连续,但是不能跨文件。
未命名空间的变量名与全局作用域的变量名要有所区别

int i;
namespace
{
    int i;
    void set()
    {
        i = 10;
    }
    void test()
    {
        std::cout << i << std::endl;
    }
}


int main()
{
    //std::cout << i << std::endl;//把这句话注释掉,程序正确通过编译,打开则通不过。因为未命名空间内的变量是在第一次使用前创建,所以注释掉的时候,相当于该名称还没有使用,而打开的时候,相当于使用了该命名空间的变量,因此编译器检测到二义性
}

using声明和using 指示

namespace LongName
{
    int l;
    int p = 10;
}

void usingtest()//using 声明
{
    namespace ln = LongName;//命名空间的别名

    ln::l = 10;
    std::cout << ln::l << std::endl;
    using ln::p;//一条using语句一次只能引入命名空间的一个成员。作用域从using声明,到using 声明所在的作用域结束为止

    std::cout << p << std::endl;
}
int l = 5;//全局变量
void usingtest2()//using指示
{
    using namespace LongName;//using 指示,和using 声明类似,不过using指示无法指明哪些名字是不可见的,因为空间名字都是可见的
    //l=10;//错误,二义性
    ::l = 10;//正确,指向全局变量l
    LongName::l = 5;//正确
}

using 指示可以用在命名空间本身的实现文件中

//A.h
namespace A
{
}
//A.cpp
using namespace A;

实参查找和类类型的形参

std::string s;
std::cin >> s;//等价于 operator>>(std::cin, s);
//operator>> 是定义在标准库string  中, string 又在std命名空间中
//正常情况下应该写成 using std;然后才可以直接写 operator>>(std::cin,s)
//但是c++ 命名空间中名字的隐藏规则有个例外。
//当我们给函数传递一个类类型的参数时,除了常规的作用域查找外,还会查找实参类所属的命名空间。
//因此编译器发现operator>>时,首先 在当前作用域查找合适的函数,接着查找语句外层的作用域,随后由于>>表达式的形参是类类型的,所以编译器 会查找cin和s所属的命名空间。编译器会查找istream和string的命名空间std.在std查找时,编译器找到了string的输出运算符函数
namespace A//如果这里namespace是个匿名空间的话,则其作用域将提升至整个文件,这个时候匿名空间的print会与下面的相同形参的print 函数会冲突
{
    void print(int k)
    {
        std::cout << "int" << std::endl;
    }
    void print(double t)
    {
        std::cout << "double" << std::endl;
    }
}
void print(int k)
{
    std::cout << "all" << std::endl;
}
int main()
{
    using A::print;//将会引用A空间里的print  的所有重载版本, 并且会隐藏全局函数print
    print(1);// 输出"int"
}

多重继承

class A
{
public:
    A()
    {

    }
    A(const std::string &)
    {

    }
};
class B
{
public:
    B()
    {

    }
    B(const std::string &)
    {

    }

};
class C: public A, public B
{
public:
    using A::A;
    using B::B;
    C()
    {

    }
};

int main()
{
    C c;//错误,C 需要实现自己的C(const std::string&)版本
}

多重继承下,不同基类实现同一个函数,将会引发函数调用错误

class A
{
public:
    A()
    {
        std::cout << "A" << std::endl;
    }
    A(const std::string &)
    {

    }

};
class B
{
public:
    B()
    {
        std::cout << "B" << std::endl;
    }
    B(const std::string &)
    {

    }

};
void print(A &a)
{

}
void print(B &b)
{

}
class C: public A, public B
{
public:
    using A::A;
    using B::B;

    C(const std::string &s):A(s),B(s)
    {

    }
};

int main()
{
    C c("haha");
    print(c);//错误,二义性
    print(static_cast<A&>(c));//正确
}

虚继承

考虑这样一个例子

class M
{
public:
    M()
    {
        std::cout << "M" << std::endl;
        m = new char;
    }
    ~M()
    {
        std::cout << "end M" << std::endl;
    }
private:
    char *m;
};
class A : public M
{
public:
    A()
    {
        std::cout << "A" << std::endl;
    }
    ~A()
    {
        std::cout << "End A" << std::endl;
    }

};
class B: public M
{
public:
    B()
    {
        std::cout << "B" << std::endl;
    }
    ~B()
    {
        std::cout << "end B" << std::endl;
    }

};
class C:public A,public B
{
public:
    C()
    {
         std::cout << "C" << std::endl;
    }
    ~C()
    {
         std::cout << "end C" << std::endl;
    }
};
int main()
{
    C c;
}

A,B继承M, C 同时继承 A 和B, 上述程序的执行结果中将会输出两遍M的构造和析构函数体
默认情况下,C 将含有M的2个子对象。这种情况和我们预想的可能不一样,假设我们预想M应当只有一个对象。这个时候,就需要用到虚继承了。

class M
{
public:
    M()
    {
        std::cout << "M" << std::endl;
    }
    ~M()
    {
        std::cout << "end M" << std::endl;
    }
};
class A : public virtual M //public 后面添加virtual即可
{
public:
    A()
    {
        std::cout << "A" << std::endl;
    }
    ~A()
    {
        std::cout << "End A" << std::endl;
    }

};
class B: public virtual M //public 后面添加virtual即可
{
public:
    B()
    {
        std::cout << "B" << std::endl;
    }
    ~B()
    {
        std::cout << "end B" << std::endl;
    }

};
class C:public A,public B
{
public:
    C()
    {
         std::cout << "C" << std::endl;
    }
    ~C()
    {
         std::cout << "end C" << std::endl;
    }
};


int main()
{
    C c;
}

这里共享的M子对象,就被称为虚基类。无论虚基类在继承体系中出现多少次,派生类都只会包含唯一一个共享的虚基类子对象。
程序的输出结果是

M
A
B
C
end C
end B
end A
end M

可以看出C的构造函数顺序是,先构造虚基类,再依次向上构造继承类。析构的时候,则按照相反顺序。
可以再看看,如果把两个virtual删除,也就是不让M成为虚基类的输出结果

M
A
M
B
C
end C
end B
end M
end A
end M

构造顺序按照继承顺序依次构造每个类,遇到继承的类有基类的时候,则先构造其基类对象,再构造派生部分。析构函数则逆序。
如果继承多个虚基类的话,则按照继承的顺序依次构造相应虚基类

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值