C++发展后期增加的工具

在C++发展的后期,增加了一些功能,作为工具来使用,主要有模板(包括函数模板和类模板)、异常处理、命名空间和运行时类型识别。1997年ANSI C++委员会将它们纳入了ANSI C++标准。

1、异常处理

       程序中常见的错误有两大类: 语法错误和运行错误。C++处理异常采取的办法是: 如果在执行一个函数过程中出现异常,可以不在本函数中立即处理,而是发出一个信息,传给它的上一级(即调用它的函数),它的上级捕捉到这个信息后进行处理。如果上一级的函数也不能处理,就再传给其上一级,由其上一级处理。如此逐级上送,如果到最高一级还无法处理,最后只好异常终止程序的执行。这样做好处是使底层的函数专门用于解决实际任务,把处理异常的任务上移到某一层去处理,以提高效率。

C++处理异常的机制是由3个部分组成的,即检查(try)、抛出(throw)和捕捉(catch)。把需要检查的语句(包括其所调用的函数)放在try块中,throw用来当出现异常时发出一个异常信息,而catch则用来捕捉异常信息,如果捕捉到了异常信息,就处理它。

throw语句一般是由throw运算符和一个数据组成,其形式为

throw 表达式;

try-catch的结构为

try

      {被检查的语句} 

catch(异常信息类型 [变量名])

      {进行异常处理的语句}

程序开始运行后,按正常的顺序执行到try块,开始执行try块中花括号内的语句。如果在执行try块内的语句过程中没有发生异常,则catch子句不起作用,流程转到catch子句后面的语句继续执行。

如果在执行try块内的语句(包括其所调用的函数)过程中发生异常,则throw运算符抛出一个异常信息。throw抛出异常信息后,流程立即离开本函数,转到其上一级的函数。

在进行异常处理后,程序并不会自动终止,继续执行catch子句后面的语句。

由于catch子句是用来处理异常信息的,往往被称为catch异常处理块或catch异常处理器。

注意:

(1)被检测的函数必须放在try块中,否则不起作用。

(2) try块和catch块作为一个整体出现,catch块是try-catch结构中的一部分,必须紧跟在try块之后,不能单独使用,在二者之间也不能插入其他语句。但是在一个try-catch结构中,可以只有try块而无catch块。即在本函数中只检查而不处理,把catch处理块放在其他函数中。

(3) try和catch块中必须有用花括号括起来的复合语句,即使花括号内只有一个语句,也不能省略花括号。

(4) 一个try-catch结构中只能有一个try块,但却可以有多个catch块,以便与不同的异常信息匹配。

    (5) catch的使用有如下两种格式:

    catch(double)

    catch只检查所捕获异常信息的类型,而不检查它们的值。因此如果需要检测多个不同的异常信息,应当由throw抛出不同类型的异常信息。

    异常信息可以是C++系统预定义的标准类型,也可以是用户自定义的类型(如结构体或类)。如果由throw抛出的信息属于该类型或其子类型,则catch与throw二者匹配,catch捕获该异常信息。

    还可以有另外一种写法,即同时指定类型名和变量名,如

    catch(double d) //使得d 获得a的值。

(6) 如果在catch子句中没有指定异常信息的类型,而用了删节号“…”,则表示它可以捕捉任何类型的异常信息,如

catch(…) {cout<<″OK″<<endl;}

它能捕捉所有类型的异常信息,并输出″OK″。

这种catch子句应放在try catch结构中的最后,相当于“其他”。如果把它作为第一个catch子句,则后面的catch子句都不起作用。

(7) try catch结构可以与throw出现在同一个函数中,也可以不在同一函数中。当throw抛出异常信息后,首先在本函数中寻找与之匹配的catch,如果在本函数中无trycatch结构或找不到与之匹配的catch,就转到离开出现异常最近的trycatch结构去处理。

    (8) 在某些情况下,在throw语句中可以不包括表达式,如

     throw;

表示“我不处理这个异常,请上级处理”。

(9) 如果throw抛出的异常信息找不到与之匹配的catch块,那幺系统就会调用一个系统函数terminate,使程序终止运行。

#include <iostream>

#include <cmath>

using namespace std;

int main()

{

double triangle(double,double,double);

double a,b,c;

cin>>a>>b>>c;

try

{

while(a>0&&b>0&&c>0)

{

cout<<triangle(a,b,c)<<endl;

cin>>a>>b>>c;

}//while

}//try

catch(double)

{

 {cout<<"a="<<a<<",b="<<b<<",c="<<c<<",that is not a triangle!"<<endl;}

 cout<<"end"<<endl;

}//catch

return 1;

}

double triangle(double a,double b,double c)

{

double s=(a+b+c)/2;

if((a+b)<=c||(a+c)<=b||(c+b)<=a) throw a;

return sqrt(s*(s-a)*(s-b)*(s-c));

}

C++允许在声明函数时列出可能抛出的异常类型,如

double triangle(double,double,double) throw(double);

表示triangle函数只能抛出double类型的异常信息。如果写成

double triangle(double,double,double) throw(int,double,float,char);

则表示triangle函数可以抛出int,double,float或char类型的异常信息。异常指定是函数声明的一部分,必须同时出现在函数声明和函数定义的首行中。如果在声明函数时未列出可能抛出的异常类型,则该函数可以抛出任何类型的异常信息。如果想声明一个不能抛出异常的函数,可以写成以下形式:

double triangle(double,double,double) throw();//throw无参数

这时即使在函数执行过程中出现了throw语句,实际上也并不执行throw语句,并不抛出任何异常信息,程序将非正常终止。

       C++的异常处理机制会在throw抛出异常信息被catch捕获时,对有关的局部对象进行析构(调用类对象的析构函数),析构对象的顺序与构造的顺序相反,然后执行与异常信息匹配的catch块中的语句。

2、命名空间

命名空间是ANSI C++引入的可以由用户命名的作用域,用来处理程序中常见的同名冲突。在C语言中定义了3个层次的作用域,即文件(编译单元) 、函数和复合语句。C++又引入了类作用域,类是出现在文件内的。在不同的作用域中可以定义相同名字的变量,互不干扰。如果在文件中定义了两个类,在这两个类中可以有同名的函数。在引用时,为了区别,应该加上类名作为限定,如

void A::fun1( )             //定义A类中的fun1函数

void B::fun1( )             //定义B类中的fun1函数

名字冲突,即在同一个作用域中有两个或多个同名的实体。如文件所包含的两个头文件中都有fun这个函数,便会产生冲突。

人们希望ANSI C++标准能够提供一种机制、一种工具,使由库的设计者命名的全局标识符能够和程序的全局实体名以及其他库的全局标识符区别开来。

       ANSI C++增加了命名空间(namespace)。所谓命名空间,实际上就是一个由程序设计者命名的内存区域。程序设计者可以根据需要指定一些有名字的空间域,把一些全局实体分别放在各个命名空间中,从而与其他全局实体分隔开来。如

namespace ns1//指定命名空间ns1

{int a;

double b;

}

命名空间成员包括变量a和b,a和b仍然是全局变量,仅把它们隐藏在指定的命名空间中而已。如果在程序中要使用变量a和b,须加上命名空间名和作用域分辨符“::”,如ns1::a,ns1::b。这种用法称为命名空间限定(qualified),这些名字(如ns1::a)称为被限定名(qualified name)。C++中命名空间的作用类似于操作系统中的目录和文件的关系。命名空间的作用是建立一些互相分隔的作用域,把一些全局实体分隔开来,以免产生名字冲突。可以设置多个命名空间,每个命名空间名代表一个不同的命名空间域,不同的命名空间不能同名。

在声明一个命名空间时,花括号内不仅可以包括变量,而且还可以包括以下类型: 

变量(可以带有初始化);

常量;

函数(可以是定义或声明);

结构体;

类;

模板;

命名空间(在一个命名空间中又定义一个命名空间,即嵌套的命名空间)。

ns1::Student stud1(101,″Wang″,18);//用命名空间ns1中声明的Student类定义stud1

stud1.get_data( );                  //不要写成ns1::stud1.get_data( );

cout<<ns1::fun(5,3)<<endl;         //调用命名空间ns1中的fun函数

命名空间名::命名空间成员名

C++提供了一些机制,能简化使用命名空间成员的手续。

(1)使用命名空间别名

可以为命名空间起一个别名(namespace alias),用来代替较长的命名空间名。如

namespace Television//声明命名空间,名为Television

 {…}

可以用一个较短而易记的别名代替它。如

namespace TV = Television;//别名TV与原名Television等价

(2)使用using 命名空间成员名

using后面的命名空间成员名必须是由命名空间限定的名字。例如

using ns1::Student;

using声明的有效范围是从using语句开始到using所在的作用域结束。如果在以上的using语句之后有以下语句:  

Student stud1(101,″Wang″,18);//相当于ns1::Student

上面的语句相当于

ns1::Student stud1(101,″Wang″,18);

using ns1::fun;//声明其后出现的fun是属于命名空间ns1中的fun

cout<<fun(5,3)<<endl;   //此处的fun函数相当于ns1::fun(5,3)

(3) 使用using namespace 命名空间名

using namespace语句的一般格式为

using namespace命名空间名;

例如

using namespace ns1;

声明了在本作用域中要用到命名空间ns1中的成员,在使用该命名空间的任何成员时都不必用命名空间限定。

当然,C++还允许使用没有名字的命名空间,如

namespace//命名空间没有名字

{void fun()                          //定义命名空间成员

    {cout<<″OK.″<<endl;}

}

标准头文件(如iostream)中函数、类、对象和类模板是在命名空间std中定义的。

这样,在程序中用到C++标准库时,需要使用std作为限定。如

std::cout<<″OK.″<<endl; //声明cout是在命名空间std中定义的流对象。

这样,在std中定义和声明的所有标识符在本文件中都可以作为全局量来使用。但是应当绝对保证在程序中不出现与命名空间std的成员同名的标识符。也可直接使用using namespace 命名空间名;语句来处理。

3、早期的函数库

    在C++程序中可以使用C语言的函数库。

1)#include <math.h> //C的传统方法

2)用C++的新方法。C++标准要求系统提供的头文件不包括后缀.h,如iostream、string。与C不同,C++所用的头文件名是在C语言的相应的头文件名(但不包括后缀.h)之前加一字母c。此外,由于这些函数都是在命名空间std中声明的,因此在程序中要对命名空间std作声明。如

#include <cstdio>

#include <cmath>

using namespace std;

发布了208 篇原创文章 · 获赞 30 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/hopegrace/article/details/104168394