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

c++标准程序库----第三章 一般概念

2019-07-05 11:51 1401 查看

3.1 命名空间(namespace) std

所谓namespace,是指标识符的某种可见范围。
事实上,c++标准程序库中的所有标识符都被定义于一个名为std的namespace中。
由于namespace的概念,使用c++标准程序库的任何标识符时,有三种选择:
1、直接指定标识符,例如std::ostream而不是ostream

std::cout<<std::hex<<3.4<<std:endl;

2、使用using declaration,使用声明的方式
using std::cout; using std::endl;

于是先前例子可写成
cout<<std::hex<<3.4<<endl;

3、using directive 我的理解是全局声明,这是最简便的方法。如果对namespace std采用using directive,便可让std内定义的所有标识符都有效曝光
using namespace std; 	cout<<hex<<3.4<<endl;

但这种方法可能导致意外的命名冲突,更糟的是甚至导致不一样的行为。如果场合不够明确(例如在头文件、模块或程序库中),就应避免使用using directive。

当然,我们也可以定义自己的命名空间,2.2.4中提过

2.2.4命名空间

//定义命名空间josuttis
namespace josuttis{
class File;
void myGlobalFunc();
}
//使用命名空间
josuttis::File obj;
...
josuttis::myGlobalFunc();

3.2 头文件(Header Files)

c++标准程序库中所有标识符都定义域namespace std里头

  • c++头文件命名:
#include<iostream>
#include<string>
  • c标准头文件命名
#include<cstdlib>//was <stdlib.h>
#include<cstring>//was <string.h>

为了向下兼容c,旧式的c标准头文件仍然有效,如果需要,你还是可以使用它们,如:

#include<stdlib.h>

3.3错误(Error)和异常(Exception)处理

异常即可以被硬件引发,又可以被软件引发。由硬件引发的异常通常由中断服务进程产生,例如,算术运算溢出和除数为0所产生的异常;由软件引发的异常由throw语句产生,操作系统和应用程序都可能引发异常。
【Tips:】
【1、程序至多执行其中一个异常处理过程。在相应的异常处理过程执行完毕后,紧随其后的异常处理过程都将被忽略,程序将继续执行这些异常处理过程之后的语句】
【2、异常处理过程在处理完异常之后,还可以执行没有操作数的throw语句,继续传播该类型的异常,tips:【没有操作数的throw只能在catch中执行,用于继续传播异常】【有操作数的throw只能在try中发出】。】
【3、异常发生后,首先产生一个描述异常现场和错误的对象【新版编译程序可以定义为模板类型】,然后定义顺序依次匹配异常处理过程的参数。先声明的异常处理过程将先得到执行机会。】
【4、基于3,派生类【子类:private(or protected) 父类】的异常处理过程应放在捕获基类异常的处理过程之前,catch(…)只能放在所有异常处理过程之后。否则,其它异常处理过程根本得不到捕获机会】
【5、对于未经处理的异常或传播后未经处理的异常,c++的监控系统将调用void terminate()处理。一般情况下,会通过指向abort()的指针调用abort()终止程序。。应用程序可以调用set_terminate()将指向abort()的指针指向自定义的处理函数】
【6、违反异常规格会导致发生不可意料的异常,不可意料的异常可由不可意料的异常处理过程unexpected处理,unexpected一般会终止应用程序的执行。unexpected可以引发一个已经声明了的异常,或者引发一个bad_exception类型的异常。】
【例11.5】

/*本例定义了一个类模板A的构造函数定义了异常接口,函数模板g定义了异常接口*/
#include<iostream>
template<class S>
class A{
S a;
public:
operator S(){ return a; }
A()throw(){ a = 0; }
A(S x){ a = x; }
};
void f(void)throw(A<double>)
{
try{
throw *(new A<double>(2.5));
}
catch (A<double>&m){
std::cout << "f:" << m << "\n";
//delete &m;
}
}
template <class B>
B g(B x)throw(A<B>)
{
try{
throw *(new A<B>);
}
catch (A<B> &m){
std::cout << "B:" << m << "\n";
//delete &m;
}
return x;
}
void main(void)
{
f();
g(1);
}

在2.2.3节中提出了异常处理的概念

通过异常处理,c++标准程序库可以在不“污染”函数接口(亦即参数和返回值)的情况下处理异常。如果你遇到一个意外情况,可以通过“抛出一个异常”来停止一般的(正常的)处理过程:

class Error;
void f()
{
...
if(exception-condition){
throw Error();//创建Error类的对象,并抛出它作为异常。
}
}

上述throw开始了stack unwinding(栈回退)过程,也就是说,它将使得退离任何函数区段时的行为就像以return语句返回一样,然而程序却不会跳转到任何地点。对于所有被声明于某区段,而该区段却因程序异常而退离的局部对象而言,其析构函数(destructor)会被调用。栈回退的动作会持续直到退出main()或直到有某个catch子句捕捉并处理了该异常为止。

第一种情况的话,程序会结束:

int main()
{
try{
...
f();
...
}
catch(const Error&){
...//handle exception
}
}

这里try区段中任何“类型为Error的异常”都将在catch子句获得处理。发生异常时,产生一个Error类型的对象,由catch捕获到。【异常非回调函数,不会再返回到f()的位置】

异常对象(exception objects)其实就是一般类型或基本类型的对象,可以是int、string,也可以是类体系中某个template classes。通常你会设计一个特殊的额error类体系,你可以运用异常对象的状态(state),将任何信息从错误被侦测到的地方带往错误被处理的地方。

我们可以使用异常规格(exception specification)来指明某个函数可能抛出哪些异常。如:

void f() throw(bad_alloc);//f()只可能丢出bad_alloc异常
如果声明一个空白异常规格,那就表明该函数不会抛出任何异常
void f() throw();//f()不抛出任何异常

违反异常规格会导致发生不可意料的异常,不可意料的异常可由不可意料的异常处理过程unexpected处理,unexpected一般会终止应用程序的执行。unexpected可以引发一个已经声明了的异常,或者引发一个bad_exception类型的异常。

3.3.1标准异常类别(Standard Exception Classes)

语言本身或标准程序库所抛出的所有异常,都派生自基类 **exception**。
这些标准异常类型可分为三组:
1、语言本身支持的异常
2、c++标准程序库发出的异常
3、程序作用域(scope of a program)之外发生的异常

  • 语言本身支持的异常
    是核心语言的一部分,如果一下操作失败,就会抛出这一类异常。
  • 全局操作符new操作失败,会抛出bad_alloc异常
  • 执行期间,当一个加诸于reference身上的“动态型别转换操作”失败时,dynamic_cast会抛出bad_cast异常
  • 执行期 型别辨识(RTTI)过程中,如果交给typeid的参数为零或空指针,typeid操作符会抛出bad_typeid异常
  • 如果发生非预期的异常,bad_exception异常会接受处理:当函数抛出异常规格(exception specification)以外的异常,bad_exception就会调用unexpected()。例如:
class E1;
class E2;//没有派生关系
void f()throw(E1)//异常规格:只抛出E1类型的异常
{
...
throw E1();//抛出E1类型的异常
...
throw E2();//调用unexpected(),然后调用terminate()
}

解释:f()之中抛出“类型为E2”的异常,这种动作违反了异常规格(exception specification)的设定,于是唤起unexpected,后者会唤起terminate()终止程序

然而如果在你的异常规格中列出bad_exception,那么unexpected()总是会重新抛出(rethrows)bad_exception异常。

class E1;
class E2;//没有派生关系
void f()throw(E1,std::bad_exception)//异常规格:抛出E1类型的异常和其它非预期的异常
{
...
throw E1();//抛出E1类型的异常
...
throw E2();//调用unexpected(),然后抛出bad_exception然后调用terminate()
}

因此,如果异常规格罗列了bad_exception,那么任何未列于规格的异常,都将在函数unexpected()中被代之以bad_exception

  • c++标准程序库发出的异常
    c++标准程序库异常总是派生自logic_error。所谓逻辑错误,包含违背逻辑前提或违反class的不变性。类别有:

  • Invalid_argument表示无效参数,例如将bitset(array of bits)以char而非’0’、'1’进行初始化

  • length_error指出某个行为“可能超越了最大极限”,例如对着某个字符串附加太多字符

  • out_of_range指出参数值“不在预期范围内”,例如在诸如array的容器或字符串中采用一个错误索引

  • domain_error指出专业领域内的错误

  • 此外 标准程序库I/O部分提供了一个名为ios_base::failure的特殊异常,当数据流(data stream)由于错误或由于到达文件尾端而发生状态改变时,就可能抛出这个异常

  • 程序作用域(scope of a program)之外发生的异常
    派生自runtime_error的异常,用来指出“不在程序范围内,且不容易回避”的时间。c++标准程序库针对执行期错误提供了以下三个classes:

  • [ ]range_error指出内部计算时发生区间错误(range error)

  • [ ]overflow_error指出算术运算发生上溢出(overflow)

  • underflow_error指出算术运算发生下溢出(underflow)
    c++标准程序库自身可能抛出range_error、out_of_range和invalid_argument异常。然而由于标准程序库会用到语言特性以及客户所写的程序代码,所以也可能间接抛出任何异常。尤其是,无论何时分配存储空间,都有可能抛出bad_alloc异常。

异常类别的头文件

基础exception和bad_exception定义于。bad_alloc定义于。bad_cast和bad_typeid定义于。ios_base::failure定义于。其它异常类别都定义于。

3.3.2异常类别(Exception Classes)的成员

标准异常的接口只含一个成员函数:what() 。用以获取“型别本身以外的附加信息”。
它返回一个以null结束的字符串:
定义

namespace std{
class **exception**{
public:
virtual const char* **what()** const throw();//并没有将字符串的值进行初始化,因此在派生类中必须重新定
//义what
...
};
}

要注意的是,除了what(),再没有任何异常提供任何其他成员函数,能够描述异常的种类,可以将what内容打印出来或者程序员自己推断引发异常的原因

try{
...
}
catch(const std::exceptions &error){
std::cerr<<error.what()<<<std::enld;// print  implementation-defined error message
}

3.3.3抛出标准异常

允许抛出标准异常。生成时都只需要一个string参数,它将成为what()返回的描述字符串【这里定义,what()返回】。如logic_error定义如下:

namespace std{
class logic_error:public exception{
public:
explicit logic_error(const string& whatString);
};
}

提供这种功能的标准异常有:logic_error及其派生类别、runtime_error及其派生类别ios_base::failure。你不能抛出exception,也不能抛出任何用以支持语言核心性质的异常。
想要抛出一个标准异常,只需生成一个描述该异常的字符串,并将它初始化,交给异常对象。

std::string s;
...
throw std::out_of_range(s);

由于char*可被隐式转换为string,所以

throw std::out_of_range("out_of_range(somewhere,somehow)");

3.3.4从标准异常类别(Exception Classes)派生新类别

另一个在程序中采用标准异常类别的情况是,定义一个直接或间接派生自exception的特定异常类别。要这么做,首先必须确保what()机制正常运作。what()是个虚拟函数,所以提供what()的方法之一就是自己实现what();

namespace MyLib{
class MyProbelm:public std::exception{
public:
MyProblem(...){
}
virtual const char* what() const throw(){//what()函数

}
}
};
void f(){
..
//创建一个异常对象并抛出它
throw MyProblem(...);
}

或派生自标准异常

namespace MyLib{
class MyRangeProbelm:public std::out_of_range{
public:
MyRangeProblem(const string &whatString):out_of_range(WhatString){
};
...
}
}
};
void f(){
..
//创建一个异常对象并抛出它
throw MyRangeProblem("here is my special range problem");
...
}

补充c++程序设计实践的内容 异常对象的析构

引发异常后,首先在引发异常的函数内部寻找异常处理过程,如果没有找到异常处理过程,则在调用该函数的函数内继续寻找,就这样一直找到顶层调用者main。如果main也没有处理异常,则由c++的监控系统处理异常,监控系统通常会终止程序。

异常处理也可能产生内存泄露问题。程序在引发异常时,产生一个异常对象,在这个对象作为实参传递给异常处理过程中也存在浅拷贝构造问题。因此,如果异常对象含有指向动态内存的指针成员,则异常对象所属的类必须定义深拷贝构造函数。【使用指针或引用传递】
【例11.7】

/*局部对象的析构过程:局部对象的析构按函数的返回顺序进行,调用顺序是f g h ,返回顺序就是h g f */
/*
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<string>class EPISTLE{
char *msg;
public:
EPISTLE(const char *s){
msg = new char[strlen(s) + 1];
strcpy(msg, s);
std::cout << msg;
}
~EPISTLE(){
if (msg){ std::cout << msg; delete msg; msg = 0; }//析构时输出
}
operator const char*(){ return msg; }
};
void h(){
EPISTLE h("I am in h()\n");//局部变量或局部对象,在正常情况下,局部对象在函数返回前,自动调用析构函数析构。在出现异常时,C++为了支持局部对象自动析构,为每个函数建立了调用环境,在沿函数的调用链反向搜索的过程中,c++的监控系统会自动析构所有局部对象,但监控系统不析构new生成的局部对象
throw new EPISTLE("I have throw an exception\n");//c++的监控系统会自动析构所有局部对象,但监控系统不析构new生成的局部对象,因此需要delete该异常对象
}
void g(){
EPISTLE g("I am in g()\n");
h();
}
void f(){
EPISTLE f("I am in f()\n");
g();
}
void main(void){
try{
f();
}
catch (const EPISTLE *m){
delete m;//析构捕获到的异常对象m("I have throw an exception")
}
}*/

如果在执行构造函数的过程中引发了异常,则只有构造完毕的基类对象得到析构,没有完全构造好的派生类对象将不会析构。

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: