C/C++ 基础语法注意事项 (三)

  • 健壮性指的是软件在异常环境下仍然能够正常运行的能力,健壮性是非常重要的软件质量属性。提高C++应用软件健壮性的基本手段之一就是使用异常处理技术。

  • C++保证:如果一个异常在抛出点没有得到处理,那么它将一直被抛向上层调用者,直至main()函数,直到找到一个类型匹配的异常处理器,否则调用terminate()结束程序。异常处理机制实际上是一种运行时通知机制。

  • 任何一种类型都可以当作异常类型,因此任何一个对象都可以当作异常对象。包括基本数据类型的变量,常量,任何类型的指针,引用,结构等,真值空结构或者空类的对象。

  • 在一个函数内尽量不要出现多个并列的try块,也不要使用嵌套的try块,否则不仅会导致程序结构复杂化,增加运行时的开销,而且很容易出现逻辑错误。一个try后面至少要跟一个catch,哪怕是catch(..) {}也可以。

  • 异常说明 返回值 函数名(参数列表)throw(T1, T2, T3)_抛出三种异常,throw()不抛出异常,没有throw代表抛出任何异常。

  • 所有try throw语句之间构造起来的局部对象的析构函数将被自动调用(以与构造相反的顺序),然后清退堆栈(就像函数正常退出那样),但是一定要保证动态分配的内存也要释放掉。

  • 对象在构造和析构的过程中也可能出现错误,但是他们没有返回值来表示运行时的错误信息,最合适的方式就是抛出异常。如果构造函数抛出异常(即构造失败)的话,前面分配好的内存控件将被释放,这得益于new运算符内部的实现。但是注意,从析构函数中抛出异常是极其危险的,这是因为析构含数据不仅会在正常情况下当对象生命期结束时被调用,而且当发生异常从而函数堆栈清退时也会被调用。最好在析构函数中就地将异常处理掉不要让它们传播出去。

  • 如果全局对象在程序开始运行之前构造,所以他们的构造函数中有异常抛出的话,将永远不会被捕获。全局对象的析构函数也是一样,因为它们在程序结束后才会被调用,这些异常之后操作系统才能捕获到,应用程序是无能为力的。

  • 如果实在无法判断到底会有什么异常抛出,那就使用catch(void*)catch(…),但它们必须放在异常组合的最后面,并且catch(void*)放在catch(…)的前面。

  • 派生类的虚函数成员函数的异常说明要与积累虚函数异常说明相同,甚至更加严格,更特殊。

  • Dynamic_cast<>运算符,RTTI机制必须维护一棵继承树,及base class table模型。只有这样dynamic_cast<>才能够通过遍历其继承树来确定一个待转换的对象和目标类型之间是否存在is-a关系。而typeid()运算符不需要遍历继承树,实际上调用typeid()运算符的开销与虚函数动态绑定的开销是相等的。

  • 使用RTTI的注意事项:一、使用RTTI对象所属类型必须是多太类。二、要用dyname_case<>转换一个引用,要对std::bad_cast异常进行处理。

  • 内存分配方式:一、从静态存储区域分配。二、在堆栈上分配。三、在堆或自由存储空间上分配。分配原则:如果使用堆栈存储和静态存储就能满足应用要求,就不要使用动态存储。因为,在堆上动态分配内存需要很客观的额外开销。

  • 如果非得要用指针参数去申请内存,那么应该改用“指向指针的指针”或者是“指针的引用” void GetMe(char **p, int num) { *p = (char*)malloc(sizeof(char)* num);}

  • 不要使用return语句返回指向“栈内存”的指针或引用,因为该内存在函数结束时将自动释放。

  • 如果pNULL指针,那么函数free() P无论操作多少次都不会出问题。如果P不是NULL指针,那么函数free()p连续操作两次就会导致程序运行时出现错误。所以每当free指针之后,一定要记得把指针赋值为NULL

  • 标准C++修订了,new的语义,plain new 在失败后抛出标准异常std::bad_alloc而不是返回NULL.然而,所以现在不能通过NULL,进行判断,要使用try catch语句来捕获异常。nothrow new就是不抛出异常的运算符new的形式,nothrow在失败时返回NULL。语句示例:XXX = new(nothrow) XXX.

  • Placement new 的主要用途就是:反复使用一块较大的动态分配成功的内存来构造不同类型的对象或者它们的数组。使用Placement new构造起来的对象或其数组,要显示地调用它们的析构函数来析构,千万不要使用delete。原因是,placement new 构造起来的对象或数组的大小并不一定等于原来分配的内存大小。因此使用delete会造成内存泄漏。

  • 对象指针:泛型指针auto_ptr:当函数即将退出或有异常的时候,不再需要我们显式地用delete来删除每一个动态创建起来的对象。CCpmPtr<>则是一个依赖于侵入式引用计数器的智能指针。

  • 尽量使用迭代器类型,而不是显式的使用指针。只使用迭代器提供的标准操作,不要使用任何非标准的操作。当不会改动容器元素的值时,用const迭代器。

  • 要注意无效的迭代器,例如vectorvector是支持动态扩充的,当扩充的时候会重新分配一块更大的内存,释放掉之前的内存,这个时候之前的迭代器指向的是扩充之前的内存所以迭代器指向的地址是无效的。

  • 在遍历容器的过程中对容器进行插入元素,删除元素等修改操作,特别是连续存储的容器中,这个和重新分配内存有关,这样会使迭代器失效。

  • 存储分配器,有空可以多百度点资料下来看一下。

  • 适配器往往是利用一种已有的比较通用的数据结构(通过组合而非继承)来实现更加具体的,更加贴近实际应用的数据结构。(适配器也算是一个不常用的知识点,如果有需要,可以看一下。)

  • 当容器的方法和泛型算法都可以完成一项工作时,要选择容器本身的方法。

  • Bitset并非容器,因此程度一旦确定就不能改变,亦不支持插入,删除操作,没有迭代器,只有referenceBitser 支持随机访问。

  • Vector<bool>通过吧用户指定的bool值位数折合成由若干个无符号整数组成的vector来节约存储空间。Nword=(L + sizeof(unsigned int)*8 -1)/(sizeof(unsigned int)*8), 其中L表示当前vector的长度,即包含的bool值的个数。bool元素并非byte,而是bit

  • 尽量不要使用slistinsert,erase,previous等操作,因为这些操作需要向前遍历,但是slist不能直接向前遍历,所以它会从头开始向后搜索,所需要的时间与位于当前元素之前的元素个数成正比。如果诺经常需要向前遍历,建议选用list.

  • Priority_queue,优先级队列,只能访问第一个元素,不支持遍历操作,第一个元素始终是优先级最高的元素。

  • 对关联式容器而言,尽量不要使用c风格的字符串(即字符指针)作为键值。如果非用不可,应显式地定义字符串比较运算符,即operator<. Operator==, operator<=等。

  • 当容器作为参数被传递时,请采用引用传递方式。否则将调用容器的拷贝构造函数,其开销是难以想象的。

  • 学习STL的三个层次:运用、研究、扩展。推荐书籍《泛型思维》、《STL源码剖析》、《Effective STL》以及《C++ Templates

你可能感兴趣的:(异常处理,c/c++,STL,高质量编程)