概述
- 本博客主要记录一些学习c++中的一些心得体会(持续更新中~~~)
《essential c++》笔记
第一章
1.5 运用 Array 和 Vector
vector的初始化(可以用以初始化的array作为该vector的初值)
int elem_vals[ seq_size ] = {
1, 2, 3, 4
};
vector<int> elem)seq( elem_vals, elem_vals+seq_size );
上例中传入两个值给elem_seq。这两个值都是实际内存位置。
1.7 文件的读写操作
#include<fstream>
ofstream outfile("nameOfFile", ios_base;:app);
//在文件尾添加。(不加第二个参数默认覆盖源文件内容)
//文件打开出错时。
if( !outfile )
cerr << "Oops! Unable to save session data!\n";
else
outfile << user_name << ''
<< num_tries << ''
<< num_right << endl;
第二章
2.2 调用函数
1)引用、指针、赋值的区别与联系
int ival = 1024;
int *pi = &ival;//指针指向一个Int对象
int &rval = ival;//引用,代表一个int对象
一旦引用完后,面向reference的所有操作都和面对reference所代表的对象所进行的操作一般无二。
pointer参数和reference参数之间更重要的差异是,pointer可能(也可能不)指向某个实际对象。当我们提领pointer时,一定要先确定其值并非0。至于reference,则必定会代表某个对象,所以不需要做此检查。
传引用操作传递的也是地址。一般可以用 const vector<int> &vec) 来告诉调用者函数不会利用引用来修改其中的变量。
注意:对根本不存在的对象(函数体中定义的变量)进行寻址操作,是个很不好的习惯。
2)堆内存的管理:利用 new 和 delete 进行分配和释放。
int *pi;
pi = new int(1024);//在申请内存时已经进行了初始化
delete pi;//会释放pi所指的对象。(删除时不需要检验pi是否非零)
delete [] pia;//会释放pia所指的对象中的所有对象。(例如数组中的值)
2.5 声明inline函数(Declaring a Function Inline)
将函数声明成 inline ,表示要求编译器在每个函数调用点上,将函数的内容展开。
定义 inline 函数的格式为:
inline bool fibon_elem(int pos, int &elem) {}
- 注意:inline 函数适合用于体积小且别多次调用的函数是,且其定义常常被放在头文件中
2.7 定义并使用模板函数
function template 以关键字 template 开场,其后由一对尖括号(< >)包围起来的一个或多个标识符。标识符表示我们希望推迟决定的数据类型。
例如:
template <typename elemType>
void display_message(const string &msg, const vector<elemType> &vec){
}//该函数已经被定义成了模板函数
2.8 函数指针带来更大的弹性
例如:
const vector<int>* (*seq_ptr)( int );//括号不能掉
用法示例:
函数指针常常与枚举类型一同使用(更加高效便捷)
//第一步:首先定义一个数组,内放函数指针
const vector<int>* (*seq_array[]) ( int ) = {
fibon_seq, lucas_seq, pell_seq,
triang_seq, square_seq, pent_seq
}
//第二部:定义枚举类型,用一组辅助记忆的常量来进行索引操作
enum ns_type {
ns_fibon, ns_lucas, ns_pell,
ns_triang, ns_square, ns_pent
}
//第三步:使用时就可以很方便
seq_ptr = seq_array[ ns_pell ];//调用ns_pell的函数指针
2.9 设定头文件
函数的定义只能有一份。不过可以有多次声明。一般不把函数的定义放入头文件,因为同一个程序的多个代码文件可能都会包含这个头文件。
但是 inline 函数的定义应该放在头文件中,而不是把它放在各个不同的代码文件中。
跨文件项目中需要注意:inline 函数和 const object 一样,都是”一次定义“规则下的例外,const object 的定义只要一出文件外就会不可见。这意味着可以在多个程序代码文件中对其加以定义,不会导致任何错误。
- 在头文件 a.h 中有以下定义
const int a;//定义两个对象
const vector<int>* (*seq_array[a]) (int);
这样定义并不正确,因为seq_array在a.h中被定义了,而非声明(seq_array并不是一个常量,而是指向常量的指针)。
- 下面的 a.h 文件被广泛采用
const int a;
//通过增加关键字extern来标注为声明
extern const vector<int>* (*seq_array[a]) (int);
练习题总结:
- 要多用 inine 和 const 关键字(使用时需要考虑是否应该应该加上这两个关键字)
- 在定义函数时,可以多加一个参数 ostream 并将其默认值设为 cout,这将使你的函数更加具有弹性
- c++ 中函数调用参数时,应该注意习惯性使用 & (引用)
第三章:泛型编程风格
导语:泛型编程通过 function template 技术,达到”与操作对象的类型相互独立“的目的。而要想操作与容器无关,迭代器( iterator )非常关键。
3.1 指针的算术运算
由于 vector 可能为空,而数组不会为空。所以为了程序的普适性,我们可以这样获取 vector 的第一个元素。
//相当于手动实现了 begin() 方法
template <typename elemType>
inline elemType* begin(const vector<elemType &vec){
return vec.empty()? 0: &vec[0];
}
3.2 了解 Iterator (泛型指针)
每个标准容器都提供一个名为 begin() 的操作函数,可返回一个iterator ,指向第一个元素。同样的,end() 函数会返回一个只想最后一个元素的下一位置的 iterator 。
const vector<string> cs_vec;
vector<string>::const_iterator = cs_vec.begin();
可以用箭头运算符调用底部的 string 元素所提供的操作。
cont << "( " << iter->size() << " <: " << *iter << endl;
c++ 提供了超过60个泛式算法可供使用。(具体见书中附录 B )
- 补充自《c++PrimerPlus》——为何需要使用迭代器
- 模板使得算法独立于存储的数据类型,而迭代器使算法独立于使用的容器类型。
3.3 所有容器的共同特性
所有容器都提供了 insert() 函数用以插入元素,以及用 erase() 删除元素。(这两个函数的行为视容器本身特性而有所不同)。
- 容器分类:
顺序性容器
关联性容器
3.4 如何使用顺序性容器
1)顺序性容器头文件:
#include<vector>
#include<list>
#include<deque>//队列(适合在两头进行操作)
2)用法介绍
list<int> ilist(1024);//设定大小,没有设定初始值(一般为0)
list<string> slist(16, "unassigned");//设定大小和初始值
可以通过一对 iterator 来产生容器。这对 iterator 用来标示一整组作为初始的元素的范围。
int ia[8] = {1, 3, 4, 5, 5, 2, 3, 4};
vector<int> mvec(ia, ia+8);
还可以通过某个容器产生出新容器。复制原容器中的元素,作为新容器的初值。
list<string> slist;
list<string> slist2(slist);
3)末端插入和删除函数
- push_back() 和 pop_back() 函数
- list 和 deque 还提供了 push_front() 和 pop_front()函数。
- insert(iterator position, elemType value) 可将 value 插入到 position 之前。
- insert(iterator position, int count, elemType value) 可在 position 之前插入 count 个元素,这些元素的值都和 value 相同。
- insert(iterator1 position, iterator2 first, iterator2 last) 可在 position 之前插入 [first, last) 所标示的各个元素。
注意:
push_front() 和 pop_back() 函数不会返回被删除的元素。因此,如果要读取最前端元素的值,应该采用 front() 函数。读取末端元素的值,可以采用 back() 函数。
运用示例:
list<int> ilist;
list<int>::iterator it = ilist.begin();
while(it != ilist.end()){
if(*it >= value){
insert(it, value);
break;
}
++it;
}
if(it == ilist.end())
ilist.push_back(value);
4)下面介绍一些关于删除的操作函数
pop_front() 和 pop_back() 都属于特殊化的删除(erase) 操作。
- iterator erase(iterator posit) 可以删除 posit 所知的元素。(通常与 find() 函数一起使用)
- iterator erase(iterator first, iterator last) 删除 [first, last) 之间的元素
注意:
- 以上两个删除函数返回的 iterator 都指向被删除的最后一个元素的下一位置。
- list 并不支持 iterator 的偏移运算。所以不能写成下面这样。
slist.erase(it1, it1+num_tries);//对于list会报错!
3.6 如何设计一个泛型算法
1)标准库事先定义了一组 function object(函数对象),分为算术运算、关系运算和逻辑运算。
示例如下:
#include<functional>
//使用 greater<int>来调用 sort() 函数。
sort(vec.being(), vec.end(), greater<int>() );//元素会以降序排序!
2)运用标准可提供的 adapter (适配器)
binder adapter 会将 function object 的参数绑定至某特定值。
标准库提供了两个 binder adapter:bind1st 会将指定值绑定之第一操作数,bind2nd 则会将指定值绑定至第二个操作数。
示例:
bind2nd(less<int>, val);
//将 val 绑定与 less<int> 的第二个参数身上。
//当后面需要调用 less<int> 时,只需要 bind2nd(适当的参数) 即可
另一种 adapter 是所谓的 negator,它会对 function object 的真伪值取反。not1 可对 unary function object(单参数函数对象),not2 可对 binary function object 的真伪值取反。
3.7 使用 Map
1)以下的 for 循环会打印出所有单字及其出现次数:
#include<string>
#include<map>
map<string, int> words;
map<string, int>::iterator it = words.begin();
for(; it != words.end(); ++it){
cout << it->first << " " << it->second << endl;
}
map 对象有一个名为 first 的 menber,对应于 key。另有一个名为 second 的 member,对应于 value。
2)如何查询一个 map 内是否含有某个 key。
- 方法一:把 key 当成索引使用。
int count = 0;
if(! (count = words["vereer"]) )
//不在其中
- 方法二:利用 map 内置的 find() 函数(注意不要和泛型算法 find() 混淆)
words.find("vermeer");
如果 key 在 map 里面,find() 函数会返回一个 iterator ,指向 key/value 形成的一个 pair (pair class 是标准库中的一员)。反之返回 end();
- 方法三:利用 map 提供的 count() 函数。count() 函数会返回某特定项在 map 内的个数。
int count = 0;
string search_word("vermeer");
if( words.count(search_word) )// ok, 它存在
count = words[ search_word ];
注意:
- map 会默认按照 key 来进行排序
- 可以与结构体一同使用构造一个 key 对应多个 value 的情况
3.9 如何使用 Iterator Inserter
1)原来的 filter() 的问题;将原来的 vector 中的元素进行筛选后存入新的 vector 中。但是不知道新的 vector 会占用多少内存。
2)解决办法:使用 Iterator Inserter ()来改变赋值操作为插入操作,即可避免这个问题。
filter(ivec.begin(), ivec.end(), back_inserter(ivec2), elem_size, greater<int>());
由于将ivec2 传给 inserter adapter ,元素的赋值操作即被替换成了插入操作。
3)三种inserter adapter:
- 注意事项:
- 使用iterator时,最好是用++,不要使用+1或者+=这种不知道是否被重载过的运算符。
3.10使用 iostream Iterator
注意:
- 应该区分开什么是 stream ,什么是 iostream Iterator(流迭代器),具体应用实例见下面代码
ifstream in_file("input_file.txt");
ofstream out_file("output_file.txt");
istream_iterator<string> is(in_file);//进行绑定
istream_iterator<string> eof;
vector<string> text;
copy(is, eof, back_inserter(text));//已插入的方式将流中的元素插入到text中。
1)包含 <iterator> 头文件后可以使用 iostream Iterator 类。称为 istream_iterator 和 ostream_iterator,分别支持单一类型的元素读取和写入。
例如:以下代码提供了一个 first iterator,它将 is 定义为一个”绑定标准输入设备“的 istream_iterator。以及一个表示”要读取的最后一个元素的下一位置“的 last iterator。
istream_iterator<string> is(cin);
istream_iterator<string> eof;//不指定 istream 对象,即表示 end-of-file。
2)使用举例
ostream_iterator<string> os(cout, " ");//输出元素时以空格相间隔开。
copy(text.begin(), text.end(), os);//将text中的内容打印到os代表的ostream中。
3)文件相关操作
只需要将 istream_iterator 绑定至 ifstream object,将 ostream_iterator 绑定至 ofstream object 即可。
ifstream in_file("input_file.txt");
ofstream out_file("output_file.txt");
第四章:基于对象的编码风格
4.1 如何实现一个Class
1)基本概念
- private member 只能在 member function或者是 class friend 内被访问。
- 所有的 member function 都必须在 class 主体内部进行声明,至于是否要同时进行定义,可自由决定。如果要在class 内部进行定义,则其会被自动地视为 inline 函数。
- 若想将函数定义为 inline 函数,必须将其定义写在头文件中。非 inline 函数应该在程序代码文件中定义
4.2 什么是构造函数和析构函数
1)
以下代码是将 t5 定义为一个函数,而不是声明为一个类。因为 c++ 必须要兼容 c
Triangular t5();
//以下为正确的写法
Triangular t5;
2)成员初始化列表(常用方式)
使用举例
Triangular::Triangular(int len, int bp)
: _name( "Triangualr" )
{
_length = len > 0 ? len : 1;
_beg_pos = bp > 0 ? bp : 1;
_next = _beg_pos - 1;
}
3)默认 class 之间赋值,会发生“成员逐一初始化”的操作。我们要问问自己这种行为是否合适,如果不合适,应该提供 copy constructor ,并在其中编写正确的初始化操作。
例如:如果某个类中有一个成员是指针时,此类的不同对象之间赋值时会发生将两个指针指向同一个地方的位置,如果其中一个对象发生析构,则会发生错误。
4.3 何为 mutable(可变)和 const (不变)
1)设置 const menber function
如果需要在 class 主体以外定义,则必须同时在声明和定义中指定 const 。
int length() const { return _length; }
int beg_pos() const { return _beg_pos; }
2)一些注意事项
class val_class{
public:
val_class( const BigClass &v )
: _val(v) {};
//这个函数存在问题!!!
BigClass& val() const { return _val; }
private:
BigClass _val;
};
因为这个函数会将 _val 开放出去,这是不允许的!
解决方法:利用重构函数
const BigClass& val() { return _val; }
BigClass& val() { return _val; }
- 补充:c++中对成员函数声明 const ,const修饰的是this指针指向的内存空间,使得this指针只有可读属性。即此函数不能改变类中的任何成员。
3)关键字:mutable
有些类的成员发生改变,并不会改变一个类对象的性质。为了方便,可以用 mutable 对其进行声明,从而方便定义 const member function 。
例如:在数列中,一个指定数列下一个位置的成员发生改变并不会改变该数列的性质,因此可以设定为 mutable
class Triangular{
public:
bool next (int &val) const;
void next_reset() const { _next = _beg_pos - 1;}
private:
mutable int _next;
int _beg_pos;
int _length;
}
4.5 静态类成员
1)static data member(静态数据对象)用来表示唯一的,可共享的 member。它可以在同一类的所有对象中被访问
在使用时,类似于 golbal object 的定义。唯一的差别是,其名称后必须附上 class scope 运算符。
vector<int> Triangular::_elems;
//也可进行赋值操作
int Triangular::_elems = 8;
如果类中有 const static data member (静态常量数据成员),可以在声明时为其明确指定初值。
2)static member function(静态成员函数)
member function 只有在“不访问任何 non-static member”的条件下才能够被声明为 static。其使用不用依赖任何对象
在 class 主题外部进行 member function 的定义时,无需重复加上关键字 static(这个也适用于 static data member)
4.6 打造一个 Iterator Class
1)为自己的类设计一个特殊的 friend 类,可以进行运算符重载。
例如:
class Triangular_iterator
{
public:
Triangular_iterator( int index ) : _index( index - 1){}
bool operator==(const Triangular_iterator& ) const;
bool operator!=(const Triangular_iterator& ) const;
int operator*() const;
Triangular_iterator& operator++();
Triangualr_iterator operator++(int);
private:
void check_integrity() const;
int _index;
}
后置版自增自减运算符需要带一个 int 型参数(为了和前置的进行重载),编译器会自动为后置版产生一个 int 参数(其值必为零)。用户不必为此烦恼。
4.7 合作关系必须建立在友谊的基础上
1)任何 class 都可以将其他的 function 或者 class 指定为朋友(friend)。而所谓朋友,即具备了与 class member function 相同的访问权限,可以访问 class 的 private member。
只要在某个函数的原型前加上关键字 friend,就可以将它声明为某个 class 的 friend。
2)我们可以令 class A 与 class B 建立 friend 关系,借此让 class A 的所有 member function 都成为 class B的 friend。例如:
class Triangular{
//下面一行代码令 Triangular_iterator 的所有 member function都成为 Triangular 的 friend
friend class Triangular_iterator;
}
注意:
- friend 函数是一个函数,它不是类的成员,但可以访问类的私有和受保护成员。 友元函数不被视为类成员;它们是获得了特殊访问权限的普通外部函数。 友元不在类的范围内,并且不是使用成员选择运算符 (来调用的 。 和- >) ,除非它们是另一个类的成员。 friend 函数由授予访问权限的类声明。 friend 声明可放置在类声明中的任何位置。 它不受访问控制关键字的影响。
4.9 实现一个 function object
1)定义:一种“提供有 function call 运算符”的 class。
有关函数对象的详细介绍,可以参照此篇博客
当编译器在编译过程中遇到函数调用,例如:
It( ival );
其中 It 可能是函数名称,可能是函数指针,也可能是一个提供了 function call 运算符的 function object。如果 It 是个 class object,编译器便会在内部将此语句转化成
It.operator(ival);
2)通常我们会把 function object 当作参数传给泛式算法,例如:
LessThan It(comp);//声明一个函数对象(一个类对象)
vector<int>::const_iterator iter = vec.begin();
vector<int>::const_iterator it_end = vec.end();
while( ( iter = find_if( iter, it_end, It) ) != it_end ){
os << *iter << ' ';
++iter;
}
4.10 重载 iostream 运算符
1)如何才能使下面代码运行呢?
cout << trian << endl;//其中trian是一个class object
答案是提供一份重载的 output 运算符:
ostream& operator<<(ostream &os, const Triangular &rhs){
os << " ( " << rhs.beg_pos() << ", "
<< rhs.length() << " ) ";
rhs.display( rhs.length(), rhs.beg_pos(), os );
return os;
}
注意:这种运算符重载并非是 member function。原因如下:
- 如果作为 member function ,其左操作数必须是隶属于同一个class 的对象,那么就需要写成 tri << cout << ‘\n’,这将使用户感到奇怪!
第五章;面向对象编程风格
5.1 面向对象编程概念
- 两个主要特质:继承和多态
1)继承机制定义了父子关系,父类定义了所有子类共通的共有接口和私有实现。
我们可以利用 libmat 从而避免使用实际对象,例如
void loan_check_in( LibMat &mat){
// mat 实际上代表某个派生类的对象
// 例如 Book, RentalBool, Magazine等
mat.check_in();
if(mat.is_late())
mat.assess_fine();
if(mat.waiting_list())
mat.notify_available();
}
当函数调用时,mat 会只想我们程序中的某个实际对象。此外,被调用的 check_in() 函数也势必会被解析成 mat 所代表的实际对象所拥有的那个 check_in() 函数。
3)动态绑定是面向对象编程风格的第三个独特概念。
mat.check_in();
如果为静态绑定 (static binding)程序在编译时就会依据 mat 所属的类决定究竟执行哪一个 check_in() 函数。
面向对象编程中,每次调用仅能在执行过程中依据 mat 所知的实际对象来决定调用哪一个 check_in()。
动态绑定:“找出实际被调用的究竟是哪一个派生类的 check_in() 函数”这一解析操作会延迟至运行时才进行。
5.2 面向对象编程思维
1)默认情况下,成员函数的解析在编译时静态的进行。若要令其在运行时动态进行,我们就得在它的声明前加上关键字 virtual。(虚函数)
函数原参数为基类,如果传入的是派生类时,基类和派生类的 constructor 都会被调用执行。(对象被销毁时同理,只是次序颠倒)
2)如何通过类的继承实现派生类 Book 呢?
class Book : public LibMat {
public:
...
private:
...
}
protected: 声明的变量(在派生类内部声明)只能被派生类直接使用,使用派生类时不必刻意区分“继承而来的成员”和“自身定义的成员”。
5.4 定义一个抽象基类
1)定义一个抽象基类的第一个步骤就是找出所有子类共通的操作行为。
2)下一步就是找出哪些操作行为与类型相关。就是有哪些操作行为必须根据不同的派生类有不同的实现方式。注意:static member function 无法被声明成虚拟函数
3)下一步:确定某个操作行为的访问层级。
在定义抽象基类时,对于每个虚函数,要么得有其定义,要么可设为“纯”虚函数。——如果对于该类而言,这个虚函数没有实质意义的话。
virtual void gen_elems( int pos ) const = 0;
如果一个类声明了纯虚函数,由于其接口的不完整性,这种类只能作为派生类的子对象使用。而且前提是这些派生类必须为所有虚函数提供确切的定义。
凡是及类定义有一个及以上的虚函数时,应该将其析构函数声明为 virtual 。
5.5 定义一个派生类
1)在类之外对虚函数进行定义时,不必指明关键字 virtual。
访问权限:
public: 能被类成员函数、子类函数、友元访问,也能被类的对象访问。
private: 只能被类成员函数及友元访问,不能被其他任何访问,本身的类对象也不行。
protected: 只能被类成员函数、子类函数及友元访问,不能被其他任何访问,本身的类对象也不行。
2)每当派生类有某个 member 与其基类的 member 同名,便会遮掩住基类的那份 member 。(可用 class scope运算符加以限定)
5.7 抽象基类是否越抽象越好?
1)如果将基类设置的太抽象,并且需要频繁加入新的子类。由于每个派生类不仅需要提供本身专属的元素产生算法,还必须支持特定元素的搜索。这样会使子类的加入工作变得更为复杂。
2)另一种设计方式:将所有派生类共有的实现内容剥离出来,移至基类内。
5.8 初始化,析构,复制
1)数据成员如果是引用,必须在构造函数中进行初始化。(引用永远不可能为空,并且一经指向便不可改变指向的对象)
2)由于我们无法为抽象基类定义任何对象。所以我们将基类的构造函数声明为protected而不是 public。
如何使用基类的构造函数,例如:
inline Fibonacci::
Fibonacci( int len, int beg_pos )
: num_sequence( len, beg_pos, _elems ) {}
5.9 在派生类中定义一个虚函数
1)当我们在派生类中,为了覆盖基类的某个虚函数,而进行声明操作时,不一定得加上关键字 virtual 。
当基类的虚函数返回某个基类形式(通常是pointer或者reference)时:派生类中的同名函数便可以返回该基类所派生出来的类型。
2)下面两种情况,虚函数机制不会出现预期行为:(1):虚函数出现在基类的构造函数或者析构函数中时。(2)当我们使用的是基类的对象,而不是基类的pointer或者reference时。
第一种情况的解释:编译器在构造基类时,派生类还未被初始化,所以不会根据派生类的类型进行调整,而是一律使用基类中的虚函数。
第二种情况的解释:传值与传地址的区别。
5.10 运行时的类型鉴定机制
1)使用 typeid() 可以得到运行时的类的类型。使用前必须先包含头文件 typeinfo。
例如:
what_am_i() const
{return typeid(*this).name(); }//会得到运行时调用此类的类名称。
第六章:Programming with Templates
6.1 被参数化的类型
1)举例说明:我们的二叉树包含两个 class :一个为 BinaryTree,用来储存一个指针,指向根结点,另一个是BTnode,用来储存节点实值,以及连接至左右两个子节点的链接。
2)什么时候需要使用模板限定符<elemType>?
一般规则是:在 class template及其 member 的定义中,不必如此。其他情况下都需要使用。
例如:
template <typename elemType>
class BinaryTree{
BTnode<elemType> &_root;//此处的定义必须使用模板限定符
}
BinaryTree<string> st;//此时_root便指向“节点值类型为string”的BTnode。
6.2 Class Template的定义
- 在类外定义成员函数时需要注意的地方:见下面的示例
//这是在类外定义成员函数,注意语法规则
template <typename elemType>
inline BinaryTree<elemType>::
BinaryTree() : _root(0) {}
6.3 Template 类型参数的处理
1)应该尽可能地将所有的 template 类型参数视为“class类型”来处理。
//我们会建议在构造函数中这样写
BTnode( const valType &val)
: _val(val) {}//使用成员初始化列表
//而不建议这样写,因为它可能是 class 类型
BTnode( const valType &val){ _val = val; }
6.5 一个以 function template 完成的 output 运算符
template<typename elemType>
inline ostream&
operator<<( ostream &os, const BinaryTree<elemType> &bt ){return os;}
//编译器会将前述地第二参数指定为 BinaryTree<string>
BinaryTree< string > bts;
cout << bts << endl;
6.6常量表达式与默认参数值
1)模板参数不一定非是某种类型不可。也可用常量表达式。例如:
template <int len>
class num_sequence{//基类
num_sequence( int beg_pos=1);
};
template<int len>//派生类
class Fibonacci : public num_sequence<len> {
public:
Fibonacci( int beg_pos = 1)
: num_sequence<len>(beg_pos) {}
};
Fibonacci< 16 > fib1;
Fibonacci< 16 > fib2( 17 );
6.8 成员模板函数
class PrintIt{
public:
PrintIt(ostream &os)
: _os(os) {}
template <typename elemType>//定义为模板成员函数,以便可以处理所有类型。
//编译器会自动地更具实际情况调整类型
void print(const elemType &elem, char delimiter = '\n'){
_os << elem << delimiter;
}
private:
ostream& _os;
};
第七章:异常处理 Exception Handling
7.1 抛出异常
1)c++ 通过 throw 表达式产生异常:一般都会抛出异常的对象。例如:
throw iterator_overflow(_index, 10);//抛出提前设计好的异常对象
7.2 捕获异常
1)可以利用单条或者一连串地 catch 子句来捕获被抛出的异常对象。
2)典型使用举例:
try {
throw CSomeOtherException();
}
catch(...) {//表示可以处理所有异常。
// Catch all exceptions - dangerous!!!
// Respond (perhaps only partially) to the exception, then
// re-throw to pass the exception to some other handler
// ...
throw;
}
7.4 局部资源管理
1)c++保证,函数中的所有局部对象的 destructor 都会被调用。不论是否有任何异常被抛出,其析构函数都会被调用。
7.5 标准异常
1)我们也可以将自己编写的对象继承于 exception 基类之下。首先必须包含头文件 exception,而且必须提供自己的 what()。(在抽象基类 exception 中,what()被设计为纯虚函数。)
2)stringstream class 提供“内存内的输出操作”,可以自动将数值对象转换成对应的字符串,我们不必考虑存储空间、转换算法等问题。