C++程序在运行时会遇到的不正常情况,比如:0作为除数、数组下标越界、打开不存在的文件、远程机器连接超时等等,一旦出现这些问题,会引发算法失效、程序运行时无故停止等现象,这就要求我们在设计软件算法时要考虑周全,而使用C++的异常机制是最常见的办法。
异常的抛出和处理主要使用三个关键字:throw、try、catch。
示例代码:
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
#include <iostream>
#include <stdexcept> using std::cout; using std::endl; using std::domain_error; double fun ( double x, double y ) { if ( 0 == y ) { throw domain_error ( "Error of dividing zero." ); } // 抛出异常 return x / y; } int main () { double ans; try { ans = fun ( 2, 3 ); cout << "2/3 is: " << ans << endl; ans = fun ( 4, 0 ); cout << "4/0 is: " << ans << endl; } catch ( domain_error e ) { cout << e.what(); } // 捕获异常 return 0; } |
当一个程序抛出一个异常时,程序的执行就会停止在程序中出现throw的地方,并且把异常对象传递到程序的另一个部分。异常对象中含有调用程序可以用来处理异常的信息。
传递的信息中最重要的部分常常纯粹是一个异常被抛出的事实。抛出异常的事实,连同异常对象的类型,已经足以让调用程序知道该如何处理。domain_error是一个标准库定义在头文件<stdexcept>中的异常类型,它用来说明一个函数的实参是这个函数不能接受的。当我们创建一个domain_error对象来抛出的时候,可以给它一个字符串,用来描述什么地方出错了。捕捉这个异常的程序,可以把这个字符串用在诊断信息中。
1
|
throw domain_error (
"Error of dividing zero." );
|
try尝试执行其后{}中的语句,如果这些语句中有一处发生了domain_error异常,程序就会转到执行catch子句。
每个标准库异常,比如domain_error,都有一个可选参数,可以用来描述引起异常的问题。每种标准库异常都可以通过一个名叫what的成员函数,来取得这个参数的一份副本。
1
2 3 4 |
catch ( domain_error e )
{ cout << e.what(); } |
处理机制:异常事件在C++中表示为异常对象(exception object)。异常事件发生时,由操作系统为程序设置当前异常对象,然后执行程序的当前异常处理代码块,在包含了异常出现点的最内层的try块,依次匹配同级的catch语句。如果匹配catch语句成功,则在该catch块内处理异常;然后执行当前try...catch...块之后的代码。如果在当前的try...catch...块没有能匹配该异常对象的catch语句,则由更外一层的try...catch...块处理该异常;如果当前函数内的所有try...catch...块都不能匹配该异常,则递归回退到调用栈的上一层函数去处理该异常。如果一直回退到主函数main()都不能处理该异常,则调用系统函数terminate()终止程序。
标准异常类定义在C++标准程序库的四个头文件中:
<exception>中定义了exception类
<new>中定义了bad_alloc类
<type_info>中定义了bad_cast类
<stdexcept>中定义了runtime_error、logic_error类
所有的异常类都是exception类的子类。
runtime_error类(表示运行时才能检测到的异常)包含了overflow_error、underflow_error、range_error几个子类;
logic_error类(一般的逻辑异常)包含了domain_error、invalid_argument、out_of_range、length_error几个子类;
各种标准异常类都定义了一个接受字符串的构造函数,字符串初始化式用于为所发生的异常提供更多的信息。所有异常类都有一个what()虚函数,它返回一个指向C风格字符串的指针。
应用程序可以从各种标准异常类派生自已的异常类。
一些目前还看不明白的参考:
异常处理中需要注意的问题
REFERENCE:
http://ticktick.blog.51cto.com/823160/191881
http://zh.wikipedia.org/wiki/C%2B%2B%E5%BC%82%E5%B8%B8%E5%A4%84%E7%90%86
http://blog.csdn.net/tuwen/article/details/2295853
另外,关于异常和错误比较深入的探讨可以参考刘未鹏的博客:
http://blog.csdn.net/pongba/article/details/1815742
__________________________________________________________________________________________________________________________________________________________________________________
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
#include <iostream>
using std::cout; using std::endl; int sum ( int x, int y ) { return x + y; } double sum ( double x, double y ) { return x + y; } int main () { cout << sum( 3, 4) << endl; cout << sum( 3. 2, 4. 3) << endl; return 0; } |
让几个函数具有相同的名字,这就是函数的重载。系统可以通过调用时,实参的数目和类型来区分具有相同名字的重载函数。具有相同类型参数表、仅在返回值类型上不同的重载函数会引起错误。
为什么需要函数重载?
REFERENCE:
http://www.cnblogs.com/skynet/archive/2010/09/05/1818636.html
http://blog.csdn.net/sendy888/article/details/1738997
__________________________________________________________________________________________________________________________________________________________________________________
C++中的引用就是某一变量(目标)的一个别名,对引用的操作与对变量直接操作完全一样。
使用引用时要注意:
常引用
常引用声明方式:const 类型标识符 &引用名=目标变量名;
用这种方式声明的引用,不能通过引用对目标变量的值进行修改,从而使引用的目标成为const,达到了引用的安全性。
假设有如下函数声明:
1
2 |
string foo( );
void bar(string &s); |
那么下面的表达式将是非法的:
1
2 |
bar(foo( ));
bar( "hello world"); |
另外一个例子:
1
2 3 4 |
vector<string> str;
vector<string> &s1 = str; const vector<string> &cs = str; vector<string> &s2 = s1; |
1
2 3 |
const vector<string> str1;
vector<string> &s3 = cs; vector<string> &s4 = str1; |
引用的作用:
引用的一个重要作用就是作为函数的参数。以前的C语言中函数参数传递是值传递,如果有大块数据作为参数传递的时候,采用的方案往往是指针,因为这样可以避免将整块数据全部压栈,可以提高程序的效率。C++中又增加了一种同样有效率的选择(在某些特殊情况下又是必须的选择),就是引用。
使用指针作为函数的参数虽然也能达到与使用引用相同的效果,但是,在被调函数中同样要给形参分配存储单元,且需要重复使用"*指针变量名"的形式进行运算,这很容易产生错误且程序的阅读性较差;另一方面,在主调函数的调用点处,必须用变量的地址作为实参。而引用更容易使用,更清晰。
如果既要利用引用提高程序的效率,又要保护传递给函数的数据不在函数中被改变,就应使用常引用。
引用型参数应该在能被定义为const的情况下,尽量定义为const。
注意:
使用引用的时机:
必须使用引用:流操作符<<和>>、赋值操作符=的返回值、拷贝构造函数的参数、赋值操作符=的参数
下面的情况都是推荐使用引用,但是也可以不使用引用。如果不想使用引用,完全可以使用指针或者其它类似的东西替代:
另外一些情况下,不能返回引用:+-*/四则运算符
REFERENCE:
http://jpkc.jwc.bupt.cn:4213/C++/down/c++paper/C++%E4%B8%AD%E7%9A%84%E5%BC%95%E7%94%A8.htm
http://developer.51cto.com/art/201107/277349.htm
__________________________________________________________________________________________________________________________________________________________________________________
头文件中:
1
2 3 4 5 6 7 |
#ifndef GUARD_median_h
#define GUARD_median_h #include <vector> double median ( std::vector< double> ); #endif |
源文件中:
头文件包含的原则:
只要文件(.h和.cpp)中出现的变量或者类型不是定义于或声明于本文件中的,我们就应该包含那些声明或者定义了这些变量或者类型的头文件。
REFERENCE:
http://blog.csdn.net/lyanliu/article/details/2195632
===========================================================================================================
顺序访问:存取第N个数据时,必须先访问前(N-1)个数据(比如:list)
随机访问:存取第N个数据时,不需要访问前(N-1)个数据,直接就可以对第N个数据操作(比如:vector)
索引只支持能够随机访问的容器的元素,而迭代器可以访问所有容器的元素。
C++标准库提供了叫做迭代器(iterator)的类型系列,使用它可以按照标准库控制的方式来访问数据结构。这种控制使得标准库可以高效地实现。
一个迭代器是一个值,它能够
每个标准库容器,都定义了两个相关的迭代器类型:
1
2 |
container-type::const_iterator
container-type::iterator |
系统可以把iterator类型转化为const_iterator类型,但是反向的转换是不允许的。end和begin成员函数返回值的类型为iterator。
我们常常可以把使用索引的程序改写为使用迭代器的程序。
REFERENCE:
http://bbs.csdn.net/topics/340028558
__________________________________________________________________________________________________________________________________________________________________________________
list类型支持在容器中的任意位置进行快速的插入和删除,但是按顺序访问较vector慢。
list和vector的选择:
对迭代器的影响:
从一个vector中删除一个元素时,指向被删除元素和它之后的所有元素的迭代器都会失效(因为删除后会移动后面的所有元素)。使用push_back为vector添加一个元素,会使所有指向这个vector的迭代器失效(为新元素分配的空间可能会导致整个vector空间的重新分配)。在循环中使用这些操作时要特别小心,必须确保循环没有任何无效的迭代器的副本。
对于list来说,erase和push_back操作都不会使指向其它元素的迭代器失效,只有被删除的元素的迭代器会失效。
通用的sort函数不能使用在list容器上,因为list不支持随机访问,list自己提供了sort函数作为它的一个成员函数,专门对list中的数据排序。
1
2 3 4 5 6 7 8 9 10 11 12 13 |
C++中vector和list的区别
vector 和built-in数组类似,它拥有一段连续的内存空间,并且起始地址不变,因此它能非常好的支持随即存取,即[]操作符,但由于它的内存空间是连续的, 所以在中间进行插入和删除会造成内存块的拷贝,另外,当该数组后的内存空间不够时,需要重新申请一块足够大的内存并进行内存的拷贝。这些都大大影响了 vector的效率。 list就是数据结构中的双向链表,因此它的内存空间可以是不连续的,通过指针来进行数据的访问,这个特点使得它的随即存取变的非常没有效率,因此它没有 提供[]操作符的重载。但由于链表的特点,它可以以很好的效率支持任意地方的删除和插入。 如果需要高效的随即存取,而不在乎插入和删除的效率,使用vector 如果需要大量的插入和删除,而不关心随即存取,则应使用list REFERENCE: http://www.cppblog.com/sleepwom/archive/2012/02/17/165844.html |
__________________________________________________________________________________________________________________________________________________________________________________
容器的成员函数:
1
2 3 4 5 |
erase c.erase(it) c.erase(b, e)删除vector中的一个元素,参数为iterator或者为iterator区间[b, e),不能操作索引。函数返回一个迭代器,指向被删除元素的下
一个位置。 insert c.insert(d, b, e)复制迭代器区间[b, e)所指元素,并把这些副本插入到容器c中的d位置之后。 empty c.empty() 谓词,表示容器c中是否没有任何元素。 push_back c.push_back(t) 把一个值为t的元素添加到容器末尾。 |
vector特有成员函数:
1
2 |
v.reserve(n) 为那个元素预留空间,但并不初始化它们。这个操作并不改变容器的长度,它影响的只是调用insert或者push_back函数时,vector内存分配的频率。
v.resize(n) 给v一个等于n的新长度。如果n小于当前v的长度,n之后的元素被删除;如果n大于当前长度,新的空间中会填充根据v包含类型来适当初始化的值。 |
list特有成员函数:
1
|
l.sort() l.sort(cmp) 使用list包含的类型操作符 < 或者谓词cmp来对list中的成员排序。
|
string的一些函数:
1
2 |
s.substr(i, j) 使用s中索引标识为[i, i + j)之间的元素创建一个新的字符串。
getline(is, s) 从流is中读取一行输入,并保存于s中。 |
===========================================================================================================
它是可以产生迭代器的函数,并且产生的迭代器与它带有的参数(参数为一个容器,返回一个迭代器)有相关的属性。迭代器适配器定义在头文件<iterator>中,
STL提供了许多基于迭代器的适配器,如back_insert_iterator,front_insert_iterator,insert_iterator, reverse_iterator,istream_iterator,ostream_iterator,istreambuf_iterator,ostreambuf_iterator等。
这些适配器大致可以分为三类:插入迭代器、反向迭代器和IO迭代器。
REFERENCE:
http://www.cnblogs.com/cobbliu/archive/2012/04/17/2440347.html
__________________________________________________________________________________________________________________________________________________________________________________
不能直接使用重载函数的函数名直接作为其他函数的参数,因为系统无法区分使用的是哪个版本的重载函数,我们应该额外使用一个辅助函数来代替原重载函数作为参数进行传递。
1
2 3 4 5 6 7 8 9 10 11 |
int sum (
int x,
int y );
double sum ( double x, double y ); compute ( a, b, sum ); // 使用错误 sum_aux ( double x, double y ) // 定义辅助函数 { return sum ( x, y ); } compute ( a, b, sum_aux ); // 正确使用 |
__________________________________________________________________________________________________________________________________________________________________________________
库算法(sort/remove_if/partition等)是作用在容器元素上的,并不是作用在容器本身上,不改变容器本身的属性,而容器的成员函数(erase/push_back等),是直接作用在容器上的,会改变容器的属性。
__________________________________________________________________________________________________________________________________________________________________________________
<numeric>头文件中:
1
|
accumulate(b, e, t)创建一个局部变量,并用t初始化,这个变量的类型与t的类型一致,并且把区间[b, e)的所有元素都加到t上,然后把得到的结果的副本返回。
|
1
2 3 4 5 6 |
find(b, e, t)
find_if(b, e, p) search(b, e, b2, e2) 在区间[b, e)中查找给定值的算法。find查找值t,并返回第一个t的位置,如果没有值为t的元素,则返回e;find_if用谓词p检测每个元素,并返回第一个 使谓词为真的位置,如果没有使谓词为真的元素,则返回e;search查找区间[b2, e2)所表示的值序列,并返回第一个[b2, e2)所表示的值序列的位置,如 果没有此序列,则返回e。 |
1
2 3 4 5 |
copy(b, e, d)
remove_copy(b, e, d, t) remove_copy_if(b, e, d, p) 把区间[b, e)中的序列复制到d指代的目的地的算法。copy复制整个序列;remove_copy复制不等于t的所有元素;remove_copy_if复制所有使得谓词p 为假的元素。 |
1
2 3 4 |
|
remove_if(b, e, p) 排列容器以使在区间[b, e)中使谓词p为假的元素位于这个区间的头部。返回一个迭代器,这个迭代器指示了位于那些
不被“删除”的元素之后的那个位置。 remove(b, e, t) 作用与remove_if一样,但是只是检测那些元素不等于t的值。 transform(b, e, d, f) 对区间[b, e)中的元素执行函数f,并把结果存储在d中。 |
1
2 3 4 5 |
partition(b, e, p)
stable_partition(b, e, p) 以谓词p划分区间[b, e)中的元素,让那些使谓词为真的元素处于容器的头部。返回一个迭代器,指向第一个使谓词为假的元素;或者, 如果对所有的元素谓词都为真,那就返回e。 stable_partition保证在划分的每一个区间中使元素原来的顺序保持不变。 |
__________________________________________________________________________________________________________________________________________________________________________________
使用库算法来判断回文
1
2 3 4 |
bool is_palindrome (
const string &s )
{ return equal ( s.begin(), s.end(), s.rbegin() ); } |
===========================================================================================================
vector和list都是序列式容器(Sequential containers),序列式容器中的元素都是按照指定的次序来排列的,当使用push_back或者insert后,之前的元素顺序不发生改变。
map是关联式容器(Associative containers),其中的元素是按照本身的值进行排列的,而不是按照我们插入的先后顺序排列的,我们不需要对关联式容器进行排序,关联式容器会根据自身的排序方法,让我们更快地查找特定的值。
map中的每个元素都是一个pair,每个pair都有一个用来进行查找的键(key)和这个键所关联的值(value),我们把这种pair称为键值对(key-value pair),每个值都和一个独一无二的键相对应,这样我们就可以根据元素的键,来快速地插入或者读取对应的元素。当我们把一个特定的pair插入到map时,这个键一直会伴随同一个值,直到我们删除这个pair。
key作为map的索引,既可以是整数,也可以是字符串或者是其他可以通过比较进行排序的类型。
map<K, V>这个map,键的类型为K,值的类型为V,map中的pair为pair<const K, V>,pair中的键都是const的,所以那些试图改变键的操作都是被禁止的。
如果ite是map的迭代器,通过ite->first访问键,ite->second访问值。
map<K, V> m(cmp) 创建一个新的空map,其中的键类型为const K,值类型为V,并使用谓词cmp来决定元素的顺序。
m[k]使用类型为K的键k来索引这个map,并返回类型为V的左值。如果map中并没有这个k键,系统就会用这个键创建一个新的值初始化的元素,并把它插入到这个map中。由于在map上使用[]可能会创造一个新的元素,所以[]不能使用在const map对象上。
m.begin() m.end() 生成map的迭代器,对这个迭代器解引用得到的是一个pair,并不是一个值。
关联式容器可用提供高效的方法来查找包含特定值的元素,而且还可以提供附加的信息。
由于关联式容器是自排序的,所以我们的程序不可能改变元素的顺序,因此,改变容器内容的算法往往并不适用于关联式容器。
__________________________________________________________________________________________________________________________________________________________________________________
很多方法看起来是不可能有效的,递归就是这样的方法之一。
理解递归的关键就是开始去理解它,剩下的自然就水到渠成了。
__________________________________________________________________________________________________________________________________________________________________________________
在C++中参数可以设置缺省值,设置了缺省值之后,这个参数在调用时就可以省略。
注意:设置缺省值的参数只能是最后的几个参数。也就是说某一个参数一旦设置了缺省值,其后而的参数也必须设置缺省值。
例如:
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
#include <iostream>
using namespace std; int sum( int x = 0, int y = 100, int z = 0) { return x + y + z; } //int sum(int x, int y=100, int z=0) { ... } //这是正确的 //int sum(int x, int y, int z=0) { ... } //这也是正确的 //int plus(int x, int y=100, int z) { ... } //这是错误的 int main ( ) { cout << sum() << endl; cout << sum( 6) << endl; cout << sum( 6, 10) << endl; cout << sum( 6, 10, 20) << endl; return 0; } |
c++函数的默认参数在哪里定义?
答:函数原型声明里和函数定义中都行。但是必须满足两个规则:
例子:
可以通过编译:
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
#include <iostream>
using namespace std; int add( int m1, int m2, int m3, int m4); int add( int m1, int m2, int m3 = 0, int m4 = 0) { return m1 + m2 + m3 + m4; } void main() { cout << add( 1, 3) << "," << add( 1, 3, 5) << "," << add( 1, 3, 5, 7) << endl; } |
不能通过编译的例子
1
2 3 4 5 6 7 8 9 10 11 12 13 14 |
#include <iostream>
using namespace std; int add( int m1, int m2, int m3, int m4); void main() { cout << add( 1, 3) << "," << add( 1, 3, 5) << "," << add( 1, 3, 5, 7) << endl; } int add( int m1, int m2, int m3 = 0, int m4 = 0) { return m1 + m2 + m3 + m4; } |
REFERENCE:
http://www.quanxue.cn/JC_CLanguage/Cpp/Cpp03.html
http://finaleden.iteye.com/blog/580428
http://blog.csdn.net/weiwangchao_/article/details/6928645
===========================================================================================================