Chinaunix首页 | 论坛 | 博客
  • 博客访问: 63120
  • 博文数量: 30
  • 博客积分: 1456
  • 博客等级: 上尉
  • 技术积分: 370
  • 用 户 组: 普通用户
  • 注册时间: 2008-03-08 22:31
文章分类

全部博文(30)

文章存档

2011年(1)

2008年(29)

我的朋友
最近访客

分类: C/C++

2008-12-08 12:38:47

异常、类和继承

这三种方式相互关联,首先、从一个异常类派生出另一;其次,可以在类定义中嵌套异常类声明来组合异常,第三,这种嵌套声明本身可被继承,还可用作基类。

注意:在派生类中重新定义基类的方法时,函数特征标必须相同,但返回类型可以不同,条件是派生类方法的返回类型是直接或间接地从基类的返回类型派生而来的。

 

异常何时会迷失方向?

异常被引发后,在两种情况下,会导致问题。首先,如果它是在带异常规范的函数中引发的,则必须与规范列表中的某种异常匹配,否则称为意外异常。在默认情况下,程序终止。如果异常不是在函数中引发的,则它必须捕获。否则异常被称为未捕获异常。

原则上,异常规范应包含函数调用的其他函数引发的异常。

与提供给set_terminate()的函数的行为相比,提供给set_unexpected()的函数的行为受到更严格的限制。具体说,unexpeceted_handler函数可以:

1)通过调用terminate()(默认行为)abort()exit()来终止程序。

2)引发异常--结果取决于unexpected_handler函数所引发的异常以及引发意外异常的函数的异常规范:

   如果新引发的异常与原来的异常规范匹配,则程序将从那里开始进行正常处理,即寻找与新引发的异常匹配的catch块。

   如果新引发的异常与原来的异常规范不匹配,且异常规范中没有包括std::bad_exception类型,则程序将调用terminate().

   如果新引发的异常与原来的异常规范不匹配,且原来的异常规范中包含了std::bad_exception类型,则不匹配的异常将被std::bad_exception异常所取代

简言之,如果要捕获所有的异常,则下面这样做:

#include

using namespace std;

void myUnexpected()

{

  throw std::bad_exception();

}

接下来在程序的开始位置,将意外异常操作指定为调用该函数:set_unexpected(myUnexpected);

最后将bad_exception类型包括在异常规范中,并添加如下语句

double Argh(double,double)throw(out_of_bounds,bad_exception);

...

try {

 x=Argh(a,b);

}

catch(out_of_bounds &ex)

{ }

catch(bad_exception &ex)

{ }

 

 

RTTI

运行阶段类型识别(Runtime Type Identification)的简称。其旨在为程序在运行阶段确定对象的类型提供一种标准方式。

1)用途:有一类层次结构,其中的类都是从同一个基类派生而来的,则可以让基类指针指向其中任何一个类的对象。这样可以调用这样的函数:在处理一些信息后,选择一个类,并创建这种类型的对象,然后返回的它的地址,而该地址可以被赋给基类指针。如果知道指针指向哪种对象呢?

2)工作原理:

1dynamic_cast操作符将使用一个指向基类的指针来生成一个指向派生类的指针;否则,该操作符返回0-空指针。

2typeid操作符返回一个指出对象的类型的值。

3type_info结构存储了有关特定类型的信息。

只能将RTTI用于包含虚函数的类层次结构,原因在与只有对于这种类层次结构,才应该派生对象的地址赋给基类的指针。

提示:只有那些指针类型与对象类型相同的类型转换才一定是安全的。

如:

Superb *pm=dynamic_cast(pg);

通常,如果指向的对象(*pt)的类型为Type或者是从Type直接或间接派生而来的类型,则表达式dynamic_cast(pt) 将指针pt转换为type类型的指针,否则,结果为0,即空指针。

typeid操作符和type_info

typeid草祖父使得能够确定两个对象是否为同种类型。可接收两种参数类名结果为对象的表达式。

typeid操作符返回一个对type_info对象的引用,其中type_info是在头文件typeinfo中定义的一个类。type_info类重载了==和!=操作符,以便可以使用这些操作符来对类型进行比较。例如:

如果pg指向的是一个Magnificent对象,则表达式:typeid(Magnificent)==typeid(*pg)的结果为true。如果pg是一个空指针,程序将引发一个bad_typeid异常。

 

类型转换:严格地限制允许的类型转换,并添加4个类型转化操作副

dynamic_cast const_cast static_cast reinterpret_cast

可以根据目的选择一个适合的操作符,而不是使用通用的类型转换。

对于dynamic_cast 假设HighLow两个类,而phpl的类型分别为High *Low *,则仅当LowHigh的可访问基类时,语句:p1=dynamic_castph;才将一个Low*指针赋给p1;否则该语句将空指针赋给pl

用途是:使得能够在类层次结构中进行向上转换,而不允许其他转换。

static_cast 仅当type_name可被隐式转换为expression所属的类型或expression可被隐式转换为type_name所属的类型时,上述转换才是合法的,否则将出错。

假设HighLow的基类,而Pond是一个无关的类;

High bar; Low blow; ...

High *pb=static_cast(&blow);   合法

Low *pl=static_cast(&bar);     合法

Pond *pmer=static_cast(&blow)  非法

第一种是向上转换可以显示进行,第二种是从基类指针到派生类指针,咋不进行显式类型转换情况下,将无法进行。但由于无需进行类型转换,便可以进行另一个方向的类型转换,因此向下转化是合法的。

reinterpret_cast操作符用于天生危险的类型转换。它不允许删除const,但会执行其他令人生厌的操作。因此使用次操作符可简化对这种行为的跟踪工作。

struct dat {short a;short b};

long value=0xA224B118;

dat *pd=reinterpret_cast(&value);

cout << pb->a;  显示这个值的前两个字节。

const_cast操作用于执行只有一种用途的类型转换,即改变值为或volatile,如果类型其他方面被修该的话,则上述类型转化将出错。

 

 

string模板类和STL标准库

 

string实际上是模板规范basic_string的一个typedef,同时省略了与内存管理相关的参数。size_type是一个依赖于实现的完整性,是在string头文件中定义的。string类将string::npos定义为字符串的最大长度,通常为无符号int的最大值。NBTS来表示空字符结束的字符串。

输入:

cin.getline(info,100,':');//read up to :,discard:

getline(stuff,':');      //read up to :,discard:

这二者主要区别在于string版本的getline()将自动调整目标string对象的大小,使之刚好能够存储输入的字符。

cin.operator>>(fname)

operator>>(cin,lname)

这两个函数都自动调整目标string的大小,使之与输入匹配。限制:1string对象的最大允许长度,由常量string::npos指定。2)程序可以使用的内存量。

string版本的getline()函数从输入读取字符,并将其存储到目标string中,知道发生下列3中情况之一:

1)到达文件结尾,输入流的eofbit将被设置,意味着方法fail()eof()都将返回TRUE

2)遇到分解字符(\n),将版分解符从输入流中删除,但不存储它

3)读取的字符数达到最大允许值,将设置输入流的failbit,方法fail()将返回TRUE

输入流对象有一个统计系统,用于跟踪流的错误状态。检测到文件尾后将设置eofbit寄存器,检测到输入错误时将设置failbit寄存器,出现无法识别的故障时将设置badbit寄存器,一切顺利时将设置goodbit寄存器。

string版本的operator>>()函数类似,只是它不断读取,知道遇到空白字符并将其留在输入队列中,而不是不断读取,知道遇到分解字符并将其丢弃。

size()length()成员函数都返回字符串中的字符数。但length()来自较早的string类,而size()则是为提供STL兼容性而添加的。

rfind()方法查找子字符串或字符最后一次出现的位置,find_first_of()方法字符串中查找参数中任何一个字符首次出现的位置。find_first_not_of()方法字符串中查找一个不被包含在参数中的字符。

string库实际上是基于一个模板类的

template ,class Allocator=allocator >

basic_string {...};

 

typedef basic_stringstring;

typedef basic_stringwstring;

trains描述关于被选择的字符类型的特定情况,如何对值进行比较。

 

auto_ptr

这是一个模板类,用于管理动态内存分配的用法。auto_ptr模板定义了类似指针的对象,可以将new获得(直接或间接)的地址赋给这种对象。当auto_ptr对象过期时,其析构函数将使用delete来释放内存。因此,如果将new返回的地址赋给auto_ptr对象时,无须记住稍后释放内存。在auto_ptr对象过期时,这些内存将自动释放。 其原型:

auto_ptrpd(new double);new doublenew返回的指针,指向新分配的内存块。它是auto_ptr构造函数的参数,即它是对应于原型中形参p的实参。auto_ptr的构造函数是显式的,这意味着不存在从指针到auto_ptr对象的隐式类型转换。

auto_ptrpd;

double *p_reg=new double;

pd=p_reg;                   //not allowed 隐式转换

pd=auto_ptr(p_reg);    //allowed 显式转换

auto_ptrpauto=p_reg;   //not allowed 隐式转换

auto_ptrpauto(p_reg);  //allowed 显式转化

模板能够通过构造函数将auto_ptr对象初始化为一个常规指针。它是一种智能指针。

注意事项:

auto_ptr pi(new int[200]);//not allowed

对于newnew[],必须使用deletedelete[]auto_ptr模板使用的是delete,而不是delete[],因此它智能与new一起使用,而不能与new[]一起使用。

string vacation("I wandered lonely as a cloud.");

auto_ptrpvac(&vacation); //NO!

这将把delete操作符用于非堆内存,这是错误的。只能对new分配的内存使用auto_ptr对象,而不要对由new[]分配的或通过声明变量分配的内存使用它。

下面的赋值情况:

auto_ptr ps(new string("I relksfjsadkfj"));

auto_ptr vocation;

vocation=ps;

这是不可接受的,因为psvocation都过期时,程序将试图删除同一个对象两次,要避免这种问题方法有多种:

1)定义复制操作符,使之执行深复制。这样两个指针将指向不同的对象,其中的一个对象是另一个对象的拷贝。

2)建立所有权概念,对特定的对象,智能有一个智能指针可拥有它。智能指针的构造函数智能删除该智能指针拥有的对象。并使赋值操作转然所有权。

3)创建智能跟高的指针,跟踪引用特定对象的智能指针数。这被称为引用技术。

 

 

STL

STL提供了一组表示容器、迭代器、函数对象和算法的模板。

1vector模板类,在计算中,矢量对应数组、而不是数学矢量。要创建vector模板对象,可使用通常的表示法来指出要使用的类型。另外,vector模板使用动态内存分配,因此可以用初始化来指出需要多少矢量。

#include

using namespace std;

vector ratings(5);   //a vector of 5 ints

int n;

cin >> n;

vector scores(n); //a vector of n doubles

由于操作符[]被重载,因此创建vector对象后,可以使用通常的数组表示法来访问各个元素。

各种STL容器模板都接受一个可选的模板参数,该参数指定是哟个哪个分配器对象来管理内存。

template >

       class vector{...

如果省略该模板参数的值,则容器模板将默认使用allocator类。

 

对矢量执行的操作

size()--返回容器中元素数目、swap()—交换两个容器的内容、begin()—返回一个指向容器中第一个元素的迭代器、end()—返回一个表示超过容器尾的迭代器。

迭代器是一个冠以指针,它可以是指针,也可以是可对其执行类似指针的操作——如解除引用和递增--的对象。

例如:要为vectordouble类型规范声明一个迭代器 vector::iterator pd;

假设scores是一个vector对象:vectorscores;则可以用迭代器pd执行这样的操作:

       pd=scores.begin();

       *pd=22.3;

       ++pd;

什么是超过结尾呢?它是一种迭代器,指向容器最后一个元素后面的那个元素。

比如上面的代码来显式容器的内容:

       for(pd=scores.begin();pd!=scores.end();pd++)

              cout << *pd << endl;

vector模板类包含一些只有某些STL容器才有的方法。push_back()是一个方便的方法,它将元素添加到矢量的末尾,这样做时,它将负责内存管理,增加矢量的长度,使之能够容纳新的成员。

vector scores;

double temp;

while(cin >> temp && temp>=0)

    scores.push_back(temp);

cout << "you entered " << scores.size() << " socres.\n";

每次循环都给scores增加一个元素。

erase()方法删除矢量中给定区间的元素。它接受两个迭代器参数,这些参数定义了要删除的区间。第一个迭代器指向区间的起始处,第二个迭代器位于区间终止处的后一个位置。

       scores.erase(scores.begin(),scores.begin()+2);

将删除第一个和第二个元素,即删除begin()begin()+1指向的元素。

insert()方法的功能与erase()相反,它接受3个迭代器参数,第一个参数指定了新元素的插入位置,第二个和第三个迭代器参数定义了被插入区间,该区间通常是另一个容器对象的一部份。

vector old;

vector new;

 

old.insert(old.begin(),new.begin()+1,new.end());

将矢量new中除第一个元素意外的所有元素插入到old矢量的第一个元素的前面。

old.insert(old.end(),new.begin()+1,new.end())将新元素插入到old.end()前面,即矢量最后一个元素的后面。

 

STL从更广泛的角度定义了非成员(non-member)函数来执行这些操作,即不是为每个容器类定义find()成员函数,而是定义了一个适用于所有容器类的非成员函数find()

for_each()函数可用于许多容器类,它接受3个参数。前两个是容器中区间的迭代器,最后一个是指向函数的指针(更普遍地说,最后一个参数是一个函数对象)。该函数将被指向的函数应用与容器区间中的各个元素。被指向的函数不能修改容器元素的值。可以for_each()函数来带起for循环。

可以将代码:

       vector::iterator pr;

       for(pr=books.begin();pr!=books.end();pr++)

          ShowReview(*pr);

替换为:

       for_each(books.begin();books.end(),ShowReview)

这样可避免显式地使用迭代器变量。

Random_shuffle()函数接受两个指定区间的迭代器参数,并随机排列该区间中的元素。

将随机排列books矢量中所有元素。与可用于任何容器类的for_each()不同,该函数要求容器类允许随机访问,vector类可以做到这一点。

sort()函数也要容器支持随机访问。该函数有两个版本,第一个版本接受两个定义区间的迭代器参数,并使用为存储在容器中的类型元素定以的<操作符,对区间的元素进行操作。例如:

       vector cool;

       sort(cool.begin(),cool.end());

将按升序对cool的内容进行排序。排序时使用内置的<操作符对值进行比较。

如果容器元素是用户定义的对象,则要使用sort(),必须定义能够处理该类型对象的operator<()函数。

若要降序排序,则使用另一种个是的sort(),它接受3个参数,前两个参数也是指定区间的迭代器,最后一个参数是指向要使用的函数的指针(函数对象),而不是用于比较的operator<()。返回值可转换boolfalsef表示两个参数的不正确。

阅读(808) | 评论(0) | 转发(0) |
0

上一篇:C++ primer 笔记 六

下一篇:C++ primer 笔记 八

给主人留下些什么吧!~~