您的位置:首页 > 编程语言 > C语言/C++

C++ Primer 学习笔记十七 —— 用于大型程序的工具

2013-06-09 18:00 543 查看
记录笔记原则:

1.用简单易懂的语言叙述自己的理解,避免照搬原文

2.用实例说明,避免空洞

3.多做总结和横向对比,避免片面

异常处理

为什么要用异常处理?

异常是用来简化错误处理的.

一般来说比较大的程序,对待错误处理有两种模式:1. 返回错误码;2. 异常处理,

异常处理机制:函数调用不用再检查返回值,而且可以在很高层统一捕获异常,而不用每层都检查,尤其是在复杂调用层次关系的时候,这样省略了很多if else语句;

举例说明:

1.返回错误码

int funcA()
{
if (正常)
{
return 0;
}
else
{
printf 错误日志;
return -1;
}
}
int funcB()
{
int result = funcA();
if (result == 0)
{
return 0;
}
else
{
printf 错误日志;
return -1;
}
}
int funcC()
{
int result = funcB();
if (result == 0)
{
return 0;
}
else
{
printf 错误日志;
return -1;
}
}
2.异常处理

void funcA()
{
if (错误)
{
throw runtime_error(错误信息);
}

// 正常逻辑
}
void funcB()
{
funcA();
}
void funcC()
{
try
{
funcB();
}
catch (const runtime_error &e)
{
printf 错误日志;
}
}


抛出异常的类型

异常可以是任意类型(标准的或自定义的),只要是可复制的;

runtime_error类型是标准库异常类,throw runtime_error("some msg")创建了runtime_error对象;

一般在catch处理异常的时候,抛出异常的块中的局部存储已经不存在了;

不要抛出指针;

异常对象

在throw的时候由编译器创建的特殊对象,并初始化为被抛出对象的副本;

throw runtime_error("abc");

异常对象 = runtime_error("abc")

栈展开

throw抛出异常后,沿着函数调用链向上,查找catch,找不到则函数退出,继续查找,最后找不到的话调用terminate函数非正常终止程序,这个过程叫做栈展开;

在栈展开的过程中,退出函数时,编译器会释放局部对象(如果是类对象则调用析构函数),但是动态资源不会释放;

不要在析构函数中抛出异常,因为在栈展开期间,析构函数又抛出异常会导致terminate;

捕获异常

基类的catch形参可以捕获派生类型的异常对象;

catch子句执行结束后,继续执行try catch块后面的语句;

捕获所有异常

catch (...)
{
// code
}


参数传递过程

throw后面的表达式——> 异常对象——>catch形参

标准库异常类

标准库异常类都有一个what()成员函数,返回C字符串;

exception 最常见问题,用法:exception()

runtime_error 运行时错误,用法: runtime_error("some msg")

bad_alloc new时分配失败,用法: bad_alloc()

自定义异常类

一般从标准库异常类派生出一个自定义异常类;

class MyException: public runtime_error
{
}


auto_ptr

异常安全:如果发生异常,被分配的动态资源也能正确释放;

作用:为动态分配的对象提供异常安全;

// delete不会被执行,内存泄露了
void f()
{
int *p = new int(42);
throw exception();
delete p;
}
// 自动释放内存
void f()
{
auto_ptr<int>  ap(new int(42));
throw exception();
}


注意:

不能指向静态对象;
不使用两个auto_ptr指向同一个对象;
不能指向数组;
不能将auto_ptr存储在容器;

命名空间

一个命名空间定义了一个作用域,每个作用域内名字必须唯一;

全局命名空间

全局命名空间是隐式声明的,存在于每个文件中;

::member_name


未命名的命名空间

只在包含该命名空间的文件中可见,相当于static声明,局部于一个文件可见;

建议使用未命名的命名空间取代文件中的static声明;

namespace {
int i;
}


两种using用法

using std:cout; 引入到当前作用域中;

using namespace std; 引入到包含的父作用域中;

namespace aaa
{
int a;
}
int a;
void func()
{
using namespace aaa;

a = 1;   // 错误,二义性,a看起来导入到了全局作用域中
::a = 1;   // ok
aaa::a = 1;  // ok
}
建议:避免使用using namespace方法,名字全部导入,容易造成命名空间污染;

多重继承与虚继承

多重继承比起单个继承有什么特别的?

构造基类的顺序:按照派生列表中的顺序依次构造;

假如两个基类都定义同名的成员,在派生类调用时会出现二义性,需指明哪个版本;

虚继承



class A {};
class B1: virtual public A {};
class B2: virtual public A {};
class C: public B1, public B2 {};
virtual 关键字陈述了再后代派生类中共享该基类的单个实例的愿望;

虚基类的初始化由最底层派生类的构造函数初始化,此时中间层派生类将忽略虚基类的初始化;

异常处理

为什么要用异常处理?

异常是用来简化错误处理的.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐