版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
1、面向对象程序设计与实践第11章C+高级机制主讲人:杨 峰华中科技大学数字化工程与仿真中心主要内容异常名字空间标准库1 异常1.1程序的正确性实现正确的软件编写的软件应满足用户的要求。至少应满足以下3个要求:对于所有正确的输入能产生正确的结果。对于所有不正确的输入能给出合理的提示信息。程序运行遇到错误后能终止程序的运行。较高要求包括:硬件故障情况下,程序能继续运行或合理终止(掉电、硬盘损坏,网络故障等)系统软件故障情况下,程序能继续运行或合理终止(操作系统,数据库管理系统等)正确的实现软件编写的程序应该尽量减少错误。程序开发过程中会出现4种错误:编译错误(compile-time errors)
2、。编译器发现的错误,说明程序代码不符合计算机语言规定的语法标准。链接错误(link-time errors)。链接器发现的错误,一般是函数实现与函数声明不对应,全局变量未定义,未链接需要的库等。运行时错误(run-time errors)。一般表示与硬件或系统软件发生冲突,比如网络中断,硬盘耗尽,硬件中断错误,软件中断错误,资源读写竞争,除0等等。逻辑错误(logic errors)。程序编写中有bug,程序不能得到正确的计算结果。减少错误一般原则为:通过良好的代码组织减少错误,较小错误的影响范围(充分利用函数和类实现封装,如果出现错误,尽量把错误的范围限制在封装的范围内,不波及到程序的其它部
3、分)通过调试和测试消除错误。通过以上方法尽量减少错误,并使得未发现和未纠正的错误不会严重影响程序的正常运行。编写正确的程序虽然需要大量的实践经验,但是从一开始就进行良好的训练更重要。解决错误解决编译错误编译错一般都是语法错误,比如:写掉了行尾的;号把英文的;号写成了汉字的;号括号没有配对类型名称,变量名称,函数名称,头文件名称键入错误一般情况下,编译器给出的编译错误都有相应描述,根据描述的内容查找到指定的代码行纠正语法错误先解决第一个编译错对应的代码行,然后再编译。如果还有编译错,依此循环,直至消除全部编译错解决错误解决链接错误函数定义和函数声明不对应(一般是因为函数名键入错误)全局变量声明和
4、使用了却忘记了定义(前面加extern是全局变量的声明,而不是定义)没有链接需要的库(标准库自动链接,但其它库需要告诉IDE库的名称和路径才能正确链接)解决运行时错误通过一套完整的错误检查,报告与处理机制来解决运行时错误解决逻辑错误通过调试和测试手段来解决逻辑错误1.2 函数的防错设计函数设计的基本原则:函数负责检查和报告错误,函数的调用者负责处理错误报告错误有4种方式通过返回值通过全局的错误变量;通过assert宏通过异常通过返回值报告错误返回整型和指针类型的函数可以通过返回值报告错误整型返回值返回负数表示函数执行中出现了运行时错误,不能得到预期的结果。通常各种负返回值应该定义为符号常量或c
5、onst常量的形式。指针返回值返回空指针表示函数执行中遇到了错误或没有执行函数功能。const int TheFirstError = -1;const int TheSecondError = -2;const int TheThirdError = -3;.int Func( argument list ).if( . )return TheFirstError;.if( . )return TheSecondError;.return 0;通过返回值报告错误示例:通过全局的错误变量报告错误返回浮点型变量的函数,因为任何一个浮点型的值都可以是合法的结果,所以找不到一个可以表示函数执行错误的
6、值作为返回值。这时,可以使用全局的errno变量。C标准库的头文件中声明了一个全局变量errno存储错误代码。一些库函数通过给errno赋值来产生函数执行出错的信号。在调用函数后,可以检查errno的值是否为0检查函数执行是否正常。在调用前需要将errno先清0,因为这是一个全局变量,函数只管修改它的值,而不管复原它。errno = 0;y = sqrt(x);if( errno != 0 )fprintf( stderr, sqrt error, program terminatedn);exit( -1 );通过errno报告错误示例:通过assert宏报告错误assert宏原型为 voi
7、d assert( int expression );该宏带一个参数,该参数是一个正常情况一定为真的表达式。执行时检查参数表达式的值,如果该值为假(0),则向stderr输出一行错误信息,并调用abort函数终止程序执行。显示的错误信息包括参数表达式,源文件名和代码行号使用assert宏需包含 或 通过assert宏报告错误代码示例:char *p = new charN+1;assert( p ); /保证p不为空assert函数会影响程序执行性能,一般只在开发过程中使用,程序开发完成提供给用户使用时定义NDEBUG宏来取消assert:#define NDEBUG#include C语言的
8、3种报告错误方法的比较第1种最常用不太方便,报告错误占用了返回值,函数要传回计算结果只能通过指针类型的参数传递如果调用函数不检查返回值,这种方式也起不到作用第2种基本上没用第3种很常用只对调试版本有效,对发行版assert宏直接被跳过,不产生任何作用assert宏只能作为调试过程中发现bug的一种方法,不能作为程序正式运行时报告错误的方式。C+的错误报告方式异常C+中,如果函数执行过程中发生错误,则在发生错误的地方抛出一个异常(throw exception),函数不再往下执行。函数的调用者通过try-catch块捕获可能发生的异常并进行相应处理。如果调用者没有进行捕获,那么该异常一直向上传递
9、直到main函数,如果main函数仍然没有捕获,那么程序会终止执行。采用异常后,程序异常退出运行的可能性增加,但至少不会让程序在一种不受控制的状态下运行。使用哪种方式报告错误?函数返回值表示函数执行过程中可以预见到的不正常情况,比如文件打不开,数据库连不上,给sqrt一个负数参数等等。errno只是函数返回值的替代方案(找不到一个合适的返回值表示错误)assert用于检查完全不应该发生的情况(比如给指针参数提供一个空指针)assert宏只用于调试版本,而在发行版本中不起作用assert宏实际上是用来防止程序中出现的bug。异常主要用于编程时无法预见的运行时错误。如网络连接中断,硬盘耗尽等。函数
10、的前置条件与后置条件每个函数总是包含输入,处理和输出3个部分。函数对输入参数的正确性要求称为函数的前置条件(pre-conditions)。函数成功执行后产生的结果称为后置条件(post-conditions)。函数的前置和后置条件都应该在函数首部的注释中写明。前置条件前置条件反映函数对输入参数的要求。如果输入参数不满足要求,采用assert宏或抛出异常的方式报告错误。用assert宏报告错误示例:void my_strcpy( char *dest, const char *src)assert( dest & src );while( *dest+ = *src+ );用异常报告错误示例:
11、double area(double a, double b, double c )if( a0 | b0 | c c & a+c b & b+c a) )throw invalid_argument(不是一个三角形);double s = ( a + b + c ) / 2.0;double area = sqrt( s * (s-a) * (s-b) * (s-c) );return area;后置条件后置条件描述函数成功执行后的结果:如果函数只通过返回值返回,则应当描述这个返回值。如果函数还通过参数返回值,要描述对参数的修改内容。示例:阶梯型电阻电路计算程序中void getResist
12、s(double rs, int rs_num)后置条件:接收用户输入,向电阻值数组中填充rs_num个电阻值,每个电阻值都大于0。1.3 异常异常的作用函数防错的基本原则是函数负责检测和报告错误,调用函数处理错误 C语言使用的返回值或全局错误变量报告错误的方法,每调用一个函数后面都要跟上一大堆if-elseif-else语句来检测调用过程中是否出现了错误,过多的错误处理代码常常淹没了真正起核心作用的业务函数的调用代码 C+通过引入异常机制,将正常调用函数和对错误进行处理的代码严格分开,提高了程序的可读性和可维护性。所谓异常,就是某种类的实例对象,这些类很简单,一般只需要一个字符串的数据成员和
13、构造函数能提供错误信息即可。使用异常报告错误函数抛出异常函数负责检测和报告异常。当函数中出现异常后,由函数抛出异常。抛出异常的语法:throw expr;示例:计算三角形面积的函数,如果输入参数不合理,抛出异常if( a0 | b0 | c0 )throw invalid_argument(边长不能为负);或写为:if( a0 | b0 | c0 )invalid_argument error(边长不能为负);throw error;即先构造一个异常类的实例变量,然后抛出这个异常。这里invalid_argument类是C+规定的标准异常。使用异常报告错误抛出异常以后函数中,执行到throw语
14、句和执行到return语句一样,函数出栈,所有局部变量析构如果函数中动态分配了内存或其它资源没有释放就会产生内存泄漏(C+编程建议,类应该在构造函数中动态分配内存或资源,在析构函数中释放)函数抛出异常出栈以后,与return不同,代码并不是回到调用函数的地方,而是跳转到捕获该异常的代码处(如果该异常没有被捕获,程序就会异常终止)C+规定的标准异常标准异常的基类是exception。该类含有一个成员函数用于提供what()出错信息。该函数的原型为:virtual const char* what();exception的派生类分为3类:(1)语言核心异常,直接派生自exception。包括bad
15、_alloc, bad_cast, bad_typeid, bad_exception。这些异常由运行时抛出,用户程序不应抛出这些异常。(2)逻辑异常,派生自logic_error。表示程序运行中出现了逻辑错误(表示程序有bug)。包括 length_error(长度过长), domain_error(专业领域范围内的错误), out_of_range(下标访问越界), invalid_argument(无效参数)。还有一个ios_base:failure(直接派生自exception),表示IO过程中出现了错误。(3)运行异常,派生自runtime_error。表示运行中遇到了不可预知的运行
16、错误。包括range_error(内部运算超出数据类型范围), overflow_error(算术运算上溢), underflow_error(算术运算下溢)。运行异常也由运行时抛出,用户程序不抛出这些异常。实现自己的异常异常类都是很简单的类,构造函数接收一个描述错误的字符串。为和标准异常类行为一致,提供一个what()成员函数返回错误描述字符串。class baseExceptionpublic:baseException(const string& str = ):msgString(str)if (msgString = )msgString = Unspecified exceptio
17、n;string what() const return msgString; protected:string msgString;示例:自定义异常的基类class memoryAllocationError: public baseExceptionpublic:memoryAllocationError(const string& msg = ):baseException(msg);class rangeError: public baseExceptionpublic:rangeError(const string& msg = ):baseException(msg);派生一些更特
18、化的异常类。这些派生类不增加任何成员,只用于标识不同的运行错误。捕获异常函数抛出异常,调用函数通过 try-catch块捕获异常调用可能发生异常函数的代码应包围在try-catch块中catch语句用于捕获异常,语法格式为:try可能抛出异常的代码catch(异常类型 变量名)处理异常的代码catch接收一个异常类型变量作为参数,可以是普通类型,也可以是引用类型。为保证what()执行的多态效果,最好用引用类型。catch块的处理通常比较简单,打印异常变量提供的信息,然后返回或退出程序。一个try块可以后接多个catch块捕捉最特化的异常类放在最前面越泛化的异常类放在后面最后一个catch块用
19、.号表示捕捉所有未指定的异常。try.catch( const indexRangeError &err )cerr err.what() endl;return -1;catch( const memoryAllocationError &err )cerr err.what() endl;return -1;catch( const baseException &err )cerr err.what() endl;return -1;catch(.)cerr Oops! Unknown exception. endl;return -1;catch语句示例:2 名字空间2.1名字空间的概念
20、名字空间提供了一种把相关的函数,变量,自定义类型分类到一起的机制提供这种分类机制的目的是防止不同厂家之间的变量名发生冲突名字空间就是名字定义的一个集合C+标准库的所有名字都属于名字空间std使用名字空间中的一个名字应使用作用域运算符:,称为完全限定名std:string name;std:cout name;使用完全限定名访问cin和cout:为减少输入量,可以通过using声明(using declaration)引入一个名字:using std:string;using std:cout;using std:cin;更简单一点,也可以直接使用using指令(using directive)
21、引入一个名字空间的所有名字:using namespace std;使用using指令抵消了名字空间防止名字冲突的作用,例如:using namespace one;using namespace other;假设这2个名字空间都有叫LadderNum的变量:LadderNum = 5;以上语句有歧义,编译器无法确定变量属于哪个名字空间。此时应采用完全限定名:one:LadderNum = 5;或通过using声明确定优先选用的名字空间:using one:LadderNum;2.2 建立自己的名字空间名字空间是完全开放的。只要是包含在namespace aname.大括号内的内容都属于该名字
22、空间。名字空间可以跨越多个源文件。示例:将阶梯型电阻电路计算程序中所有代码放置到名字空间LadderCircuit中namespace LadderCircuitconst int MAX_RESISTS = 500;extern int resistTotal;extern double srcVoltage;extern double resistsMAX_RESISTS;extern double resistVoltagesMAX_RESISTS;extern double resistCurrentsMAX_RESISTS;void getResistsTotal();void ge
23、tSrcVoltage();void getResists();void compute();void displayResult();LadderCircuit.h现在这些变量属于名字空间LadderCircuit,它们的作用域范围是名字空间LadderCircuit,而不再是全局作用域了。input.cpp#include #include #include LadderCircuit.husing std:cin;using std:cout;using std:endl;namespace LadderCircuitint resistTotal;double srcVoltage;d
24、ouble resistsMAX_RESISTS;void getResistsTotal(). void getResists(). void getSrcVoltage(). 这些变量和函数都属于名字空间LadderCircuitcompute.cpp#include LadderCircuit.hnamespace LadderCircuitdouble resistVoltagesMAX_RESISTS;double resistCurrentsMAX_RESISTS;void compute() . output.cpp类似main.cpp#include LadderCircuit
25、.hvoid main()LadderCircuit:getResistsTotal();LadderCircuit:getResists();LadderCircuit:getSrcVoltage(); pute();LadderCircuit:displayResult();#include LadderCircuit.husing namespace LadderCircuit;void main()getResistsTotal();getResists();getSrcVoltage();compute();displayResult();或者简写:使用完全限定名使用using指令3
26、 C+标准库3.1 标准库的组成C+的标准库是C+标准规定的随编译器一起提供的一套可以直接使用的函数和类的集合C+的标准库主要是基于泛型编程技术,几乎所有的函数和类都是模板,以前称为标准模板库(Standard Template Library,简称STL),现在称为C+标准库C+标准库可以看做由以下部分组成存储群体数据的数据容器,分为序列容器和关联容器序列容器按元素进入容器的顺序存放关联容器在元素进入容器是就根据排序原则放置在合适的位置。迭代器迭代器提供遍历群体数据容器的方法算法算法可以在遍历群体数据容器时对容器中的每个元素进行操作函数对象替代函数指针string类型替代C风格的字符串ios
27、tream流库替代C的printf,scanf和文件输入输出函数3.2群体数据容器向量vector语言提供的基本数组使用上有很多不便:数组大小不能改变需要另外一个整数变量描述数组实际存放数据的长度不能自行完成复制数组的工作数组只是一个存储数据的集合,本身并不具有管理这种存储的运算和操作标准库提供的向量vector用于替代普通的数组,能够自行管理存储内容并进行运算使用更简便#include #include using namespace std;int main( ) vector numbers; for( int i = 0; i 10; i+ ) numbers.push_back( i
28、*2 ); for( int i = 0; i numbers.size(); i+ ) cout numbers i = numbersi endl; return 0;vector使用示例:声明一个向量类型的变量,其中没有数据在向量尾部添加数据,自动扩展所需内存空间向量中的数据长度用下标运算符访问向量中的数据元素,行为和数组相同使用向量类型包含需要的头文件 #include 定义向量变量 vector numbers;该语句构造一个向量变量,该向量现在是空的,里面没有元素,长度为0vector才是一个类型,vector本身不是类型,只是一个类型模板vector表明自己是一个存放整型数据的向
29、量numbers是一个变量,不是一群变量为向量添加数据 numbers.push_back(i*2)该循环向向量中增加10个元素。增加数据后,向量的长度为10向量自身管理内存的大小存取向量中的数据 cout numbersi向量的数据访问与普通数组相同,用下标运算符下标范围从0开始向量能管理自身的长度,通过size()方法获得变量的长度调整向量的长度可以在定义向量变量时指定向量的长度 vector numbers(10);这种定义方式像一个函数调用,函数调用参数指定向量的长度这时向量中有10个数据,它们都是0若调用numbers.push_back(),那么是从第11个位置向后增加数据,并增加
30、向量的长度调用resize()方法重新设置向量的长度numbers.resize(20); 将向量的长度增长到20,增加的数据也会清0也可以减小向量长度,末尾数据销毁复制向量vector numbers_a, numbers_b;for( int i=0; i10; +i )numbers_a.push_back( i*2 );numbers_b = numbers_a;向量通过赋值运算符实现向量的复制。例如:而数组,编程者则不得不自己写复制代码:int a10, b10;for( int i=0; i10; +i )ai = i*2;for( int i=0; i10; +i )bi = a
31、i;向量作为函数参数向量变量是单个变量,可以作为函数参数传递,由于一个向量变量中存储了很多数据元素,通常采用引用类型进行参数传递:double avg( const vector &v )double sum = 0.0;for( int i=0; iv.size(); +i )sum += vi;return sum / v.size();向量编程示例例1:冒泡排序templatevoid BubbleSort( vector& v )int n = v.size();for( int pass=1; passn; +pass )for( int i=0; i vi+1 )T tmp = v
32、i+1;vi+1 = vi;vi = tmp;向量自行管理长度,只需要1个参数应用排序函数对字符串进行排序:void main()vector strs;cout 请输入字符串(空行结束): endl;while( 1 )string s;getline( cin, s );if( s.size() = 0 )break;strs.push_back( s );BubbleSort( strs );for( int i=0; istrs.size(); +i )cout strsi endl;string替代C风格字符串,向量替代数组BubbleSort()是一个函数模板,对各种类型通用标准库
33、已经提供了标准的排序函数:.#include void main()vector strs;.sort( strs.begin(), strs.end() );.sort()缺省按照从小到大排序,若改为从大到小排序:sort( strs.begin(), strs.end(), greater() );迭代器,指向第一个数据元素迭代器,指向最后一个数据元素后例2:向数组中插入元素if( waitInsert v.back() )v.push_back( waitInsert );elsefor( vector:iterator iter = v.begin(); iter != v.end()
34、; +iter )if( waitInsert *iter & waitInsert *(iter+1) )v.insert( iter+1, waitInsert );break;第一个数据元素的值最后一个数据元素的值迭代器,行为就像一个指针指定插入的位置用取内容运算符获取元素的数据值向量变量的front()和back()方法获取第0个元素和最后一个元素的值insert()方法向数组中插入一个元素,第1个参数为插入位置,第2个参数是待插入的数据不需要管理插入的具体细节,既不需要预留空间,也不需要去移动位置,这些工作标准库自动完成。向量的遍历除了象C风格数组一样用下标实现外,还可以使用标准库的
35、迭代器机制实现对vector类型的向量进行遍历的迭代器数据类型为vector:iterator反向遍历的迭代器的数据类型为vector:reverse_iterator只读遍历的迭代器的数据类型为vector:const_iteratorbegin()方法返回vector的第一个位置的迭代器, end()方法返回vector的最后一个可用位置的迭代器反向遍历时用rbegin()和rend()方法迭代器的行为和指针类似,可以通过加、减算术运算表示在向量中向前、向后移动,用取内容运算符*取出迭代器指向位置的数据值。例3:实现数组顺序反转直接使用标准库的reverse算法需要头文件 #include
36、 将ch05-07.cpp中的my_reverse()调用改为 reverse( v.begin(), v.end() );例4:数组元素的查找修改ch05-08.cpp的二分查找代码,改造后的函数模板原型为:templateint BinarySearch( const vector& v, T value );或调用标准库的find算法查找元素:vector:iterator result;result = find( numbers.begin(), numbers.end(), 16 );例5:阶梯型电阻电路计算程序用向量类型实现LadderCircuit.h#include usin
37、g std:vector;double getSrcVoltage();void getResists( vector& );void compute(const vector&, double, vector&, vector&);void displayResult(const vector&, const vector&, const vector&);传递向量只需要一个参数应使用引用类型input.cppvoid getResists(vector& rs)int num = 0;for( ; ; )cout 请输入第 +num r;if( r 0 )cout 输入电阻为负数,取绝对值
38、 endl;r = -r;if( abs(r) numeric_limits:epsilon() )break;rs.push_back( r ); /接下页输入一个电阻值,添加到向量尾部可输入任意多个电阻值,输入0结束input.cpp/接上页if( rs.size() % 2 != 0 )double r = rs.back() / 2.0;rs.pop_back();rs.push_back( r );rs.push_back( r );for( int i=0; irs.size(); +i )cout 第 i/2+1 节:;if( i%2 = 0 )cout 串联电阻=;elseco
39、ut 并联电阻=;cout rsi ;if( (i+1)%2 = 0 )cout endl;若输入电阻值为奇数,将最后一个电阻拆两半compute.cppvoid compute(const vector &rs, double v, vector &vs, vector &is)int num = rs.size() - 1;vsnum = 1.0;isnum = vsnum / rsnum;-num;isnum = isnum+1;vsnum = isnum * rsnum;-num;for( ; num 0 ; )vsnum = vsnum+1 + vsnum+2;isnum = vsn
40、um / rsnum;-num;isnum = isnum+1 + isnum+2;vsnum = isnum * rsnum;-num;double PortVoltage = vs0 + vs1;double k = v / PortVoltage;for( int i=0; irs.size(); +i )vsi *= k;isi *= k;只读的输入参数使用引用常量计算代码与数组版本完全相同main.cppvoid main()double srcVoltage;vector resists;getResists( resists );srcVoltage = getSrcVoltage();vector resistVoltages( resists.size() );vector resistCurrents( resists.size() );compute(resists
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 2025版酒店餐饮服务承包及品牌合作合同3篇
- 2024版三方共同投资合作协议版B版
- 二零二五年度旧电动车买卖合同范本含电池回收协议3篇
- 2025版金融产品质押抵押担保合同范本12篇
- 2024年车辆运输全程保障协议
- 2025信用卡担保合同担保合同范本
- 二零二五年家庭赡养老人个人所得税分摊代理执行合同3篇
- 2025数据采编录入和维护合同
- 2024某科技公司与网络游戏运营商游戏开发合作合同
- 二零二五年度房地产担保合同答辩状编写要点3篇
- 机场安检突发事件应急预案
- 2024年高考真题-化学(天津卷) 含解析
- 2024年招投标培训
- 新反诈知识考试题库200题(含答案)
- 部编版三年级上册语文期末复习资料
- 建筑施工承插型盘扣式钢管脚手架技术标准
- 相反国课件-大班
- 2023-2024学年新疆乌鲁木齐130中学九年级(上)期末物理试卷
- 2023-2024学年江苏省徐州市九年级(上)期末物理试卷
- 小学英语名词单数变复数的语法规则及练习题含答案
- 四川省绵阳市高中2025届高三二诊模拟考试物理试卷含解析
评论
0/150
提交评论