Essential C++读书笔记
Essential C++读书笔记
一、C++编程基础
1、对象初始化 (两种不同的初试化语法)
int num_tries = 0, num_right = 0
int num_tries(0)//构造函数语法,主要用来处理“多值初试化”
#include<complex>
complex<double> purei(0,7);
2、特殊字符
当在Windows操作系统中使用常量字符串来表示文件路径时,应以特殊符号来表示斜杠
”F:\ essential\ pragrams\ charpter1\ ch1_main.cpp“
3、const 常量
被定义为const的对象在获得初值之后无法进行变动
const int max_tries = 3;
const double_PI = 3.14
4、条件判断
switch :
请特别注意:当某标签与switch表达式的值匹配时,在这种情况下其后的所有case标签也会被依次执行;只有在明确使用break语句来终止执行的情况下才会停止
switch(num_tries)
{
case 1:
cout<<"\n";
break:
case 2:
cout<<" ";
break;
default:
cout<<" ";
break;
}
5、arrays和Vectors
初始化 :利用已初始化的array作为vector的初值
int elem_vals[seq_size]={1,2,3,4,5,6,7,8};
vector<int> elem_seq{elem_vals,elem_vals+seq_size};
vector 可以知道自身的元素个数
elem_seq.size()
6、指针
防止对NULL指针进行操作可以检验该指针所含有的地址是否为0
if(pi && *pi != 1024)
{
*pi = 1024;
}
对于vector的指针的声明以及初始化
vector<int> *pv = 0;
定义vector的指针数组
vector<int> *seq_addrs[seq_cnt]={&fibonacci, &lucas, &pell, &triangular}
rand()和srand()函数
rand函数每次调用会生成一个位于0到int所能表示的最大整数值之间的整数值。
srand函数接受并设置随机数生成器的种子参数seed。
将随机数生成器的种子设定为5后,则可以使rand函数产生的数值限定在0至5之间。
7、文件的读写
声明 outfile 后:若指定的 file 不存在,则生成该 file;否则此 file 会 open 用于输出,并将原 file 的数据移除。
#include<fstream>
ofstream outfile("seq_data.txt");//输出模式
ifstream infile("seq_data.txt");//读取模式
fstream iofile("seq_data.txt",ios_base::in|ios_base::app)//读写模式,追加模式
以追加模式 开启文件
ofstream outfile("seq_data.txt",ios_base::qpp);
if(!outfile)
cerr<<"Unable to save!"//cerr代表标准错误输出设备
else
outfile<<user_name<<endl;
当启动为附加模式时,在未进行过定位操作的情况下,默认情况下会将当前位置设置于尾部区域,并尝试进行当前内容的读取操作,则必然会导致无法完成该操作。通过调用seekg()函数可将当前路径下的起始位置重置至整个文件的开头部分。
iofile.seekg(0)
二、面向过程的编程风格
1、撰写函数
为了能够调用函数的需求而言,在编程过程中必须进行函数声明。通常情况下,在编写程序时会将函数声明放置在包含所需功能的头文件中;特别地,在像cstdlib.h这样的标准库头文件中特意包含了exit()函数的声明。
int fibon_elem(int pos)//即函数原型,不需要写函数主体
使用**exit()**必须包含头文件
#include<cstdlib>
2、调用函数
当我们在执行某个函数操作时,在虚拟机的内存空间中临时创建出一个特定区域(被称为"堆栈"),这个区域主要负责为每个待传递的数据参数提供存储位置,并且还能为每个被定义的对象分配独立的空间;当该操作完成之后系统会自动释放这些临时占用的空间
3、引用
int ival = 1024;//对象,类别为int
int *pi = &ival;//指针
int &rval = ival;//引用,代表一个int对象
int jval = 4096;
rval = jval;//即把jval赋值给rval所代表的对象,即ival
C++不允许改变引用所代表的对象
4、生存空间及生存范围
除static以外,函数内定义的对象只存活与函数执行之际
变量若不在函数体内声明,则自其声明位置至文件末尾始终可见,并必被初始化为0
5、动态内存管理
不管是在本地范围还是在文件范围内。
另外一种情况是动态范围(Dynamic Range),其内存来源于程序的自由空间配置(也称为堆内存)。
这种内存必须由程序员自行进行管理。
其配置可以通过new expression实现。
而释放则通过delete expression完成。
int *pi;
pi = new int(1024);
int *pia = new int[24];//配置数组,C++不可以从heap配置数组的同时为其元素设定初值
delete pi;//
delete []pia;//删除数组中的所有对象
如果不适用delete,则从heap配置来的对象永远不会释放,即内存泄漏
6、提供默认参数值
void display(const vector<int> &vec,ostream &os =cout)//将cout设置为默认的ostream参数
{
for(int ix = 0;ix<vec.size();++ix)
os<<vec[ix]<<' ';
os<<endl;
}
默认参数值提供的两个规则
- 如果某个参数设置了一个默认值,则该参数右侧的所有后续参数都必须也设定有默认值。
- 每个默认值只能指定一次。
7、声明inline函数(还不懂)
旨在具体而言,在进行程序优化时。对于被声明为inline的那些子程序而言,在执行每个子程序调用时直接展开该子程序的具体内容,并将其作为独立的一份代码块进行处理。这样一来,在运行相应的程序段时会显著提高其执行效率
inline bool_fibon_elem(int pos, int &elem)
{
//内联函数必须跟函数定义一起,不然无法构成内联函数
}
定义在类声明之中的成员函数将自动地成为内联函数
class A
{
public:
void FOO(int x, int y)
{
///自动地成为内联函数
}
}
调用函数的过程:在调用之前需要先保存好寄存器,并在程序返回时恢复原来的状态。同时将实际参数复制到位,并转而执行一个新的指令位置。这会导致一定的开销。
引入内联函数的主要目标是为了解决程序中函数调用效率的问题。简单来说就是说,在编译器编译程序的时候会将程序中出现的那些内联函数的调用表达式替换成该相应内联函数的具体实现代码。而其他非内联功能则是在运行时才被替代进去。其实这本质上是通过空间代价换取时间效率的一种权衡策略。因此通常情况下 内联功能代码长度会在1到5行之间
- 在一个类中所定义的任何内部函数都是内联函数。
- 定义一个内联函数应在它首次被调用之前完成。
- 在一个类中所定义的任何内部函数都是该类说明范围内的自动执行功能。
8、函数重载(略简单,应在后期有补充)
参数表不相同的多个函数可以拥有相同的函数名称
void display_message(char ch);
void display_message(const string&);
void display_message(const string&, int);
void display_message(const string&, int, int);
9、模板函数(后期应有补充)
函数模板可用于将单一函数的内容与期望显示的不同种类的vector型别进行整合
该文档中定义了function template(函数模板),其格式由关键词开始,并用一对<>包围起来的一组一个或多个识别符组成。这些识别符用于延缓决定数据类别的选择过程,在此过程中每当用户通过此模板生成一个新函数时,则必须明确指定相关的数据类别信息
template <typename elemType>
void display_message(const string &msg, const vector<elemType> &vec)
{
cout<<msg;
for(int ix = 0; ix<vec.size();++ix)
{
elemType t = vec[ix];
cout<<t<<' ';
}
}
vector<int> ivec;
string msg;
//....
display_message(msg, ivec);//将elemType绑定为int
//....
vector<string> ivec;
string msg;
//....
display_message(msg, ivec);//将elemType绑定为string
10、函数指针
函数指针:必须指明起所指向函数的返回值类型及参数表
const vector<int> * (*seq_ptr)(int);//seq_ptr是一个指针,指向具有const vector<int>* (int)形式的函数
bool seq_elem(int pos, int&elem, const vector<int> *(*seq_ptr)(int))
{
//调用seq_ptr所指的函数
const vector<int> *pseq = seq_ptr(pos);//函数名即是指针
if(!pseq)
{
elem = 0;
return false;
}
elem = (*pseq)[pos-1];
return true;
}
11、头文件
头文件扩展名习惯上是**.h**,标准程序库例外,他们没有扩展名。
函数只能有一个定义, 但允许多个声明. 为了避免重复引用, 我们不将函数的定义包含在头文件中, 以减少不必要的重复引入.
如果被识别为标准或专属于项目的头文件,则以尖括号对其实现名称进行标注;编译器在处理此类时,通常会优先考虑这些默认驱动目录中的内容。
若实际实现名称以一对双撇号包围,则认为其是来自用户自行提供的扩展;而在此情况下,
搜索该扩展时,
开发环境将从包含该扩展的实际模块所属目录开始查找。
一次定义规则的例外 :
- inline函数的定义旨在便于扩展其内容,在每一个调用点处编译器都能够访问该函数的定义。因此,在编译器环境中将其定义放入包含文件是必要的操作。
- const对象一旦超越了当前文件范围就不可见于其他地方。这使得我们可以在一个项目中为多个模块独立地重新定义const对象而不引发冲突。
//NumSeq.h
bool seq_elem(int pos,int &elem);
const vector<int> *fibon_seq(int size);
const vector<int> *lucas_seq(int size);
const vector<int> *pell_seq(int size);
//....
const int seq_cnt = 6;//之所以可以定义是因为是const object
extern const vector<int>* (seq_array)([seq_cnt])(int);
三、泛型编程风格
1、Iterators(泛型指针)
所有标准容器均包含一个名为**begin()**的方法,并给出一个指向第一个元素的迭代器;其对应的end()方法则会给出指向最后一个元素的迭代器。
vector<string> svec;
vector<string>::iterator iter=svec.begin();
//::表示此iterator乃是位于string vector 定义式内的嵌套型别
实现一个find()函数使其能够同时处理一组指针变量或一组指向该容器中的迭代器对象
template<typename IteratorType, typename elemType>
IteratorType find(IteratorType first,IteratorType last,const elemType &value)
{
for(;first!=last;++first)
if(value==*first)
return first;
return last;
}
2.所有容器的共通操作(包括string类)
比较运算符:用于判断两个对象是否相等(==)或者不相等(!=)。
赋值运算符:用于将一个容器的内容复制到另一个容器。
空成员函数:当容器没有任何存储内容时会返回布尔值true;否则返回false。
大小成员函数:该成员函数会计算并返回当前容器中所包含的具体数据项的数量。
清空成员函数:通过调用此方法可以清除当前容器中的所有数据项。
迭代器前驱函数:此方法会给出指向当前迭代器所指位置前一个有效数据的位置引用。
迭代器后继函数:此方法会给出指向当前迭代器所指位置后一个有效数据的位置引用。
插入成员函数:该方法可实现单个数据项或者一定区间内多个数据项的具体插入操作。
擦除成员函数:此方法不仅能够实现指定区间内全部数据项的具体擦除操作;还能给出擦除之后下一个有效数据的位置引用。
insert()和erase()视容器本身为循序式或关联式而有所不同
3.序列式容器
- vectors这一数据结构基于动态数组实现了一定的空间效率。
- lists作为一种常见的数据结构设计模式通常用于顺序访问操作。
- double-ended queues一种高效的双端队列实现了先进先出的基本功能特性
产生容器的方法
//1.产生空的容器
list<string> slist;
vector<int> ivec;
//2.产生特定大小的容器,以默认值为初值,int,double等内建型别的算术型别默认值为0
list<int> ilist(1024);
vector<string svec> svec(32);
//3.产生特定大小的容器,并为每个元素指定初值
vector<int> ivec(10, -1);
list<string> slist(16,"unassigned");
//4.以一对iterators产生容器
int a[8] = {1,2,3,4,5,6,7,8};
vector<int> fib(ia, ia+8);
//5.根据某个容器产生新容器
list<string> slist;//空容器
list<string> slist2(slist);
一些特殊的安插方法
push_back();
pop_back();
//下面的支持deque和list
push_front();
pop_front();
front();//读取最前端的值
4.使用泛型算法
必须包含头文件
#include<algorithm>
一些泛型算法
- find()用于判断无序集合中是否包含某个特定值,并通过指定的 iterators(first, last)限定搜索范围。若成功找到,则函数会返回一个迭代器对象指向目标元素位置;若未找到,则返回指定的结束迭代器last。
- binary_search()主要用于对有序序列进行查找操作:若查找成功则返回true;否则return false。
- count()统计并返回满足条件元素的数量。
- search()判断指定子序列是否存在于给定容器中:若存在则函数会返回子序列起始位置对应的迭代器;否则return 容器末尾位置。
- max_element(vc.begin(), vc.end())计算并返回给定区间内最大值。
- copy(vec.begin(), vec.end(), temp.begin())将vec容器的所有元素按顺序复制到temp容器起始位置。
- sort(temp.begin(), temp.end())对temp容器中的所有元素按照升序进行排列操作。
5.Function Objects
function objects 实现了类对外运算功能。这类class通过重载函数调用操作符(function call operator),使得函数对象能够模拟出原本需要独立函数实现的行为(如greater),从而提升了执行效率,并使得调用操作符能够以单行形式呈现(即in-line)。
6个算术运算:
plus<type>,minus<type>,negate<type>,multiplies<type>,divides<type>,modules<type>
六个关系
less<type>,less_equal<type>,greater<type>,greater_equal<type>,equal_to<type>,not_equal_to<type>
三个逻辑运算
logical_and<type>,logical_or<type>,logical_not<type>
function object adapters will bind the parameters of function objects to specific values, enabling binary function objects to become unary function objects.
- 该指令用于将指定数值赋值给第一个操作数。
- 该指令用于将指定数值赋值给第二个操作数。
- 该函数对象支持对布尔值的操作。
- 该函数对象支持对双目运算符的操作。
vector<int> filter(const vector<int> &vec, int val, less<int> &Lt)
{
vector<int> nvec;
vector<int>::const_iterator iter = vec.begin();
while((iter = find_if(iter, vec.end(), bind2nd(lt, val)))!=vec.end())
{
//find_if函数寻找比*iter值小的数
//bind2nd把val这个值绑定到lt的第二参数
nvec.push_back(*iter);
iter++;
}
return nvec;
}
消除filter()与vector元素型别以及vector容器类型的相依关联
template<typename InputIterator, typename OutIterator, typename ElemType, typename COmp>
filter(InputIterator first, Inputerator last, OutputIterator at, const ElemType &val, COmp pred)
{
while((first = find_if(first, last, bind2nd(pred, val)))!=last)
{
//观察进行情况
cout<<"found value:"<<*first<<endl;
*at++=*first++;
}
return at;
}
6、使用map
该Map实体包含一个命名为first的字段用于存储键值对,并拥有一个命名为second的字段用于存储对应的value。
查询map是否存在某个key,三种方法
int count = 0;
if(!(count = words["vermeer"]))
//如果索引的key不在map中,则key会被自动加入map中并且其value会被设为默认值
//利用map的find()函数
words.find("vermeer")
//利用map的count(),count()会返回某特定项目在map内的个数
int count = 0;
string search_word("vermeer");
if(words.count(serch_word))
count = words[search_word];
7、使用Set
对于每个键值而言,在集合中仅能存储一个实例;若需存储多个相同键值,则需采用多重集;在默认情况下,在集合中所包含的元素将根据其所属类型默认采用小于运算符来进行排序
int ia[10] = {1,3,5,8,5,3,1,5,8,1};
vector<int> vec{ia,ia+10};
set<int> iset(vec.begin(),vec.end());
//iset的元素为{1,3,5,8}。
8、Iterator Inserters
传统的实现元素复制功能的方法包括将数据复制到目标容器中完成。这意味着目标容器的容量必须预先确定下来。然而如果希望目标容器的容量能够灵活变化我们可以采用以下方法(需包含#include<iterator>头文件):
该函数将通过调用push_back()函数来替代assignment运算符,并将参数设置为容器自身。
vector<int> result_vec;
unique_copy()(ivec.begin(),ivec.end(),back_inserter(result_vec));
inserter()用容器的insert()函数来替代assignment运算符。该函数接受两个参数:一个是待插入的容器对象(如vector、deque等),另一个是迭代器对象(或称序列),用于指定插入位置。
vector<int> svec_res;
unique_copy()(svec.begin(),svec.end(),inserter(svec_res,svec_res.end()));
The front_inserter() function will replace the assignment operator with the push_front() method of the container, and is only applicable to lists and deques.
list<int> ilist_clone;
copy(ilist.begin(),ilist.end(),front_inserter(ilist_clone));
9、iostream iterators
#include<iostream>
#include<fstream>
#include<iterator>
#include<algorithm>
#include<vector>
#include<string>
using namespace std;
int main()
{
ifstream in_file("input_file.txt");
ofstream out_file("output_file.txt");
if(!in_file||!out_file)
{
cerr<<"!!unable to open!\n";
return -1;
}
istream_iterator<string> is(in_file);//从in_file读取
istream_iterator<string> eof;//表示读取最后元素的下一位置,对于标准输入装置end-of-file即代表last
vector<string> text;
copy(is,eof,back_insert(text));
sort(text.begin(),text.end());
ostream_iterator<string> os(out_file, " ");
copy(text.begin(),text.end(),os);
}
四、基于对象的编程风格
1、如何实现一个class
通常地,在编程结构中
class Stack{
public:
bool push(const string&);
bool pop(string &elem);
bool peek(string &elem);
bool empty();
bool full();
int size(){return _stack.size();}//size()定义于class本身内,其他member则仅仅是声明
private:
vector<string> _stack;
};
inline bool Stack::empty()
{
return _stack.empty();
}//empty为Stack的一个member,并且是inline函数
bool Stack::pop(string &elem)
{
if(empty())
reutrn false;
elem = _stack.back();
_stack.pop_back();
return true;
}
inline bool Stack::full()
{
return _stack.size() == _stack.max_size();
}
bool Stack::peek(string &elem)
{
if(empty())
return false;
elem = _stack.back();
return true;
}
bool Stack::push(const string &elem)
{
if(full())
return false;
_stack.push_back(elem);
return true;
}
2、Constructors和Destructors(构造函数和析构函数)
构造函数名必须与类名称一致。语法规定下, 构造函数不应指定返回类型, 并无需返回任何值。此构造函数也可重载。
class Triangular{
public:
Triangular();//default constructors
Triangular(int len);
Triangular(int len, int beg_pos);
private:
int _length;//元素数目
int _beg_pos;//起始位置
int _next;//下一个迭代目标
}
Triangular::Triangular()
{
//默认的构造函数,第一种情况
_length=1;
_beg_pos = 1;
_next=0;
}
//第二种情况
class Triangular{
public:
Triangular(int len=1, int bp=1);//提供了默认参数
}
Triangular::Triangular(int len, int bp)
{
_length = len>0?len:1;
_beg_pos = bp>0?bp:1;
_next = _beg_pos-1;
}
//调用方法
Triangular t;//1,1
Triangular t2(12);//12,1
Triangular t3(8,3);//8,3
成员初始表
成员初始表紧跟在冒号后,采用逗号分隔的形式列出成员信息,在括号内用于将数值分配给每个member名称.
Triangular::Triangular(const Triangular &rhs)
:_length(rhs._length),_beg_pos(rhs._beg_pos),_next(rhs._beg_pos-1)
{}//空的
析构函数
该类成员中的destuctor是由用户提供自定义的一个。一旦某个类提供destructors,在对象生命周期结束时会自动调用destuctor处理善后工作。destuctor的主要职责是释放在constructors中或对象生命周期中配置好的资源。
析构函数执行时间
在对象生命周期终结、被销毁的时候
,在执行delete操作的时候
,在以下两种情况下:
当delete操作作用于指向对象的指针时
或者当delete操作涉及指向对象基类类型的指针时;
其中基类对应的析构函数是纯虚函数的情况下;
在这种情况下,
对象i作为对象o的一个成员,
当析构函数被调用的时候,
对象i也会自动触发自己的析构函数。
规定 :
- class名称前加~
- 无返回值
- 无参数
- 不可被重载
class Matrix{
public:
Matrix(int row, int col)
{
_pmat = new double[row*col];
}
~Matrix()
{
delete {}_pmat;//执行析构函数时释放heap内存
}
private:
int _row,_col;
double* _pmat;
}
成员逐一初始化
Triangular tril(8);
Triangular tri2=tri1;//class data members会被依次赋值
二阶构造法
当类的成员较为轻量级,在仅包含赋值等基本操作时,使用常规的构造函数即可满足需求。
然而,在实际开发中,默认采用面向对象思维构建程序时所涉及的类往往较为复杂,在涉及动态内存分配以及文件打开等功能模块时会遇到更为棘手的问题。
调用构造函数后难以获取这些复杂操作的实际完成情况。
如果出现动态内存分配失败现象但依然能够在主程序中成功创建对象实例,则此类对象常被称为半成品对象。
在后续对该对象进行的操作过程中可能会导致程序运行异常。
为了避免可能出现的问题,在设计该类时采用了分段式构造机制。具体而言,在类对象生成的过程中我们将初始化过程划分为两个独立的阶段:第一阶段主要负责构建那些在初始化过程中不容易出错的部分;第二阶段则专注于处理可能涉及内存动态申请的操作,并通过返回一个布尔值来判断整体初始化是否成功。需要注意的是,在这两个步骤之间必须通过静态成员函数实现衔接
#include <iostream>
using namespace std;
class A
{
private:
int m;
int n;
int* p;
A() //第一段构造函数,一般的,纯粹的赋值操作不会发生错误
{
m = 1;
n = 2;
}
bool construct() //第二段构造函数,防止文件打开、系统资源的申请失败导致半成品对象
{
bool ret = true;
p = new int(3);
if (!p)
ret = false;
return ret;
}
public:
static A* NewInstance() //必须写为静态函数,以方便用类名调用该函数, 而不用创建对象实例,返回的是指针类型
{
A* ret = new A(); //完成第一段对象实体
if (!(ret && ret->construct()))//ret->construct()完成第二段对象实体,并利用返回值来判断对象是否创建成功
{
delete ret;
ret = NULL;
}
return ret;
}
void showdata()
{
cout << "m = " << m << endl;
cout << "n = " << n << endl;
cout << "*p = " << *p << endl;
}
};
void main()
{
A* p_A = A::NewInstance(); //静态调用,不同动态申请
p_A->showdata();
delete p_A;
3、mutable(可变)和const(不变)
const
当传递给某个函数的参数是一个const类型的对象时,则必须确保该函数内部调用的所有成员都无法修改传入的对象的状态,并且因为这种情况下无法避免地会导致
int sum(const Triangular &train)
{
int beg_pos = train.beg_pos();
int length = train,length();
int sum = 0;
for (int ix=0;ix<length;++ix)
sum+=train.elem(beg_pos+ix);
return sum;
//此函数调用的成员函数有可能会更改train的值
}
class Triangular
{
public:
int length() const{return _length;}//声明该成员函数不会更改类的值
int beg_pos() const{return _beg_pos;}//const修饰词紧接函数参数表后,在class外定义者,如果是const,则定义中也需要const
int elem(int pos)const;
//非const
bool next(int &val);
void next_reset(){_next = _beg_pos-1;}
//....
private:
int _length;
int _beg_pos;
int _next;
static vector<int> _elems;
}
bool Triangular::next(int &value) const
{
if(_next<_beg_pos+_length-1)
{
value = _elem[_next++];
return true;
}
return false;
}
int Triangular::elem(int pos)const{return _elems[pos-1];}
即使某些方法没有明显修改类对象的值;但如果存在修改的可能性,则这些方法无法声明为const
class val_class
{
public:
val_class(const BigClass &v)
:_val(v){}
BigClass& val() const{return _val;}//此处返回了一个BigClass的非const引用,因此有可能更改成员值,所以声明为const类型会报错
private:
BigClass _val;
}
mutable
当我们声明一个成员函数为const时,在该函数试图修改类中另一个非const成员变量的情况下(如果我们认为该被修改变量是可以变化的),则允许将该方法标记为mutable。
int sum(const Triangular &train)
{
if(!train.length())
return 0;
int val,sum=0;
train.next_reset();
while(train.next(val))
sum+=val;
return sum;
}
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、this指针
this指针在member functions内用来寻址其调用者(一个对象)
Triangular& Triangular::copy(Triangular *this, const Triangular &rhs)
{
if(this!=&rhs)
{
this->_length = rhs._length;
this->_beg_pos = rhs._beg_pos;
this->_next = rhs._beg_pos-1;
}
return *this;//this指向这个类对象,*this则代表这个类对象本身
}
Triangular tr1(8);
Triangular tr2(8,9);
copy(&tr1,tr2);
5、Static Class Member
static data member
The static data member is designated as a unique shared resource among all objects of the same type, enabling access to its members. Since it represents only one instance, its clear definition must be provided within the program code file.
class Triangular
{
public:
//...
private:
static vector<int> _elems;
static const int _size = 1024;//这种数据成员变量可以直接赋初值
};
vector<int> Triangular::_elems;
Triangular::Triangular(int len, int beg_pos)
:_length(len>0?len:1),_beg_pos(beg_pos>0?beg_pos:1)//定义一个构造函数,使用成员初始表赋值
{
_next = _beg_pos-1;
int elem_cnt = _beg_pos + _length -1;
if(_elem.size()<elem_cnt)//获取方法可以直接获取
gen_elements(elem_cnt);//扩充elem?
}
static member function
通常情况下,在C++编程中,成员函数必须由其对应类的对象来进行调用。这个对象会被绑定在该成员函数的this指针上,并通过它来访问存储在其对象中的non-static data members。
bool Triangular::is_elem(int value)
{
if(!_elems.size()||_elems[_elems.size()-1]<value)
gen_elems_to_value(value);
vector<int>::iterator found_it;
vector<int>::iterator end_it = _elems.end();
found_it = find(_elems.begin(),end_it,value);
return found_it!=end_it;
}//该成员函数没有调用任何non-static data members,于是想是否可以以非成员函数的形式调用?
if(Triangular::is_elme(8))...//这样调用,需要加class scope,在跟无任何对象有瓜葛的情况下调用
//member function只有在"不存取任何non-static data members"的情况下才能被声明为static
class Triangular
{
public:
static bool is_elem(int);
static void gen_elements(int length);//此笔记未写定义
static void gen_elems_to_value(int value);
static void display(int length, int beg_pos,ostream *os =cout);
}
6、Iterator Class
对class进行运算符重载,先定义一个class的Iterator
Triangular train(1,8);
Triangular::iterator it = train.begin(), end_it = train.end();
while(it!=end_it)
{
cout<<*it<<' ';//两个class的object怎么比较
++it;
}
//重载运算符
class Triangular_iterator
{
public:
Triangular_iterator(int index):_index(index-1){}//成员初始表
bool operator==(const Triangular_iterator&) const;
//重载的运算符是带有特殊名称的函数,函数名是由关键字 operator 和其后要重载的运算符符号构成的。与其他函数一样,重载运算符有一个返回类型和一个参数列表。
bool operator!=(const Triangular_iterator&) const;
int operator*() const;
Triangular_iterator& operator++();//前置版
Triangular_iterator& operator++(int);//后置版
private:
void check_integrity() const;
int _index;
};
inline bool Triangular_iterator::operator==(const Triangular_iterator &rhs) const
{
return _index==rhs._index;//如果两个对象的_index相等则说这两对象相等
}
运算符重载规则 :
- 应仅允许现有运算符(如
.,*,::,?:)进行扩展。- 操作数数量保持不变。
- 运算符的优先级必须保持不变。
- 运算符函数的参数列表中至少包含一个类类型参数。
运算符的定义
//类似成员函数
inline int Triangular_iterator::operator*() const//此处的const声明该成员函数不会更改类对象的值
{
check_integrity();
return Triangular::_elems[_index];//静态成员变量调用时需要加class限定域
}
//类似非成员函数
inline int operator*(const Triangular_iterator &rhs)//此处传进一个const引用,表示无法更改这个引用所代表的类对象
{
rhs.check_integrity();
return Triangular::_elems[_index];
}//非成员运算符的参数列中一定比对应的成员运算符多一个参数,即this指针,这个this指针隐代表左侧操作数
嵌套型别
#include "Triangular_iterator.h"
class Triangular
{
public:
//将Triangular_iterator 内嵌至Triangular中并typedef为 iterator
typedef Triangular_iterator iterator;
Triangular_iterator begin() const
{
return Triangular_iterator(_beg_pos);//返回一个类的对象,上面有构造函数
}
Triangular_iterator end() const
{
return Triangular_iterator(_beg_pos + _length);
}
private:
int _beg_pos;
int _length;
//....
}
7、友元函数
每个类都可以定义其他函数或类作为其友元,并且所谓的friendship具有相同的访问权限与成员函数相同的能力,并能够访问该类的私有成员。
inline int operator*(const Triangular_iterator &rhs)
{
rhs.check_integrity();//直接调用私有成员
return Triangular::_elems[rhs.index()];
}//想要通过编译需要将operator*()声明为友元
class Triangular
{
friend int operator*(const Triangular_iterator &rhs);
//....
}
class Triangular_iterator
{
friend int operator*(const Triangular_iterator &rhs);
//....
}
//也可以将其他类中的函数声明为友元函数
class Triangular
{
//在此定义之前必须先定义Triangular_iterator让Triangular知道,否则无法知道下面的函数的确是Triangular_iterator的成员函数
friend int Triangular_iterator::operator*();
friend void Triangular_iterator::check_integrity();
}
定义类与类之间的friend关系
class Triangular
{
//以下可以使Triangular_iterator得所有成员函数都成为Triangular的friend
friend class Triangular_iterator;
}
8、copy assignment operator
Triangular tri1(8),tri2(8,9);
tri1 = tri2;//默认的成员逐一复制操作,class data members会被依次复制过去。
//但有些时候无法进行默认的复制,因此需要重载该运算符
Matrix& Matrix::operator=(const Matrix &rhs)//Matrix在4.2有,如果直接用默认复制,tri2de _pamt会在析构函数被回收,但tri1的_pmat同样还指向这块空间,因此会出错
{
if(this!=&rhs)
{
_row = rhs._row;_col = rhs._cos;
int elem_cnt = _row*_col;
delete{} _pmat;//没懂
_pmat = new double[elem_cnt];//解决办法:重新申请一块内存
for(int ix=0;ix<elem_cnt;++ix)
_pmat[ix]=rhs._pmat[ix];
}
return *this;
}
9、function object
所谓function object乃是一种"提供有function call运算符"的class
函数对象无处不在,在实际应用中我们可以看到很多类似的场景。举个例子来说,在使用std::map时,默认情况下我们会选择像 std::map<int, Anyclass> 这样的类型作为映射数据结构的标准配置。然而这种映射数据结构在排序时,默认是以 int 类型的键值进行比较的这一特性源于 STL 库(STL 即 Standard Template Library 标准模板库)的设计理念,在其实现过程中会选择 std::less 作为默认的比较函数对象这一设定就是典型的功能体实例
//当编译器在编译过程中遇到函数调用,例如:It(ival),It可能是函数名称,也可能是函数指针,也可能是一个提供了function call运算符的function object,但如果It是class object,编译器会转化为:It.operator(ival)
class LessThan
{
public:
LessThan(int val):_val(val){}
int comp_val()const {return _val;}//基值的读取
void comp_val(int nval){_val=nval;}//基值的写入
bool operator()(int _value)const;//重载了运算符()?()是function call运算符
private:
int _val;
};
//function call运算符实现如下:
inline bool LessThan::operator()(int value)const {return value<_val;}
//将function call运算符施加于对象身上
int count_less_than(const vector<int> &vec,int comp)
{
LessThan It(comp);
int count = 0;
for(int ix=0;ix<vec.size();++ix)
if(It(vec[ix]))
++count;
return count;
}
//通常将function object当做参数传给泛型算法
void print_less_than(const vector<int>&vec, int comp, ostream &os=cout)
{
LessThan It(comp);//此处即声明了一个function object
vector<int>::const_iterator iter=vec.begin();
vector<int>::const_iterator ite_end = vec.end();
os<<"elements less than"<<It.comp_val()<<endl;
while((iter=find_if(iter,it.end(),It))!=it_end)//即在iter和it.end()之间找比comp小的值
{
os<<*iter<<' ';
++iter;
}
}
10、iostream运算符重载
ostream& operator<<(ostream &os, const Triangular &rhs)
{
os<<"("<<rhs.beg_pos()<<","<<rhs.length()<<')';
rhs.display(rhs.length(),rhs.beg_pos(),os);//此处display为上面之前定义过的一个函数,笔记没写
return os;//返回的os起什么作用?
}//
Triangular tir(6,3);
cout<<tri<<'\n';//此处的第一个<<是重载过的,第二个没有
//(3,6)6 10 15 21 28 36
11.指针:指向Class Member Functions
class num_sequence
{
public:
typedef void(num_sequence::*PtrType)(int);//PtrType代表了一个函数指针,指向有void (int)类型的函数,但被限制在num_sequence这个类中,此处的typedef定义了一个名为PtrType的指针,指向void(num_sequence::)(int)
void fibonacci(int);
void pell(int);
void lucas(int);
void triangular(int);
void sequare(int);
void pentagonal(int);
//...
private:
PtrType _pmf;
};
//定义一个指针
PtrType pm=&num_sequeence::fibonacci;
五、面向对象编程风格
1、面向对象编程概念
面向对象编程概念最主要的特性是:
继承
该机制决定了子代与父代之间的关联,并由父代为所有后代共同提供的对外公开接口以及私有实现内容所构成;每个子代都有机会补充或修改其从父代继承而来的功能。
多态
Polymorphism是指基于不同继承关系的对象调用同一个函数而导致了不同的行为模式。具体来说,在一对基于相同继承关系的对象中存在一个共享的功能:具有相同名称、参数和返回值的函数。通过调用这个功能的不同实现(即在两个对象中的应用),我们能够实现各自对象执行各自特定事件的能力。
动态绑定
- 在非面向对象的编程风格中使用mat.check_in();时,在编译阶段基于mat所属类确定执行相应的check_in()函数,在此之前就已经确定了这种绑定方式被称为静态绑定。
而在面向对象编程方法中只有在运行时根据mat所指的对象来选择调用相应的check_in()函数的情况被称为动态绑定。- 关于动态绑定与动态对象:https://www.cnblogs.com/raichen/p/5622197.html
void loan_check_in(LibMat& mat)
{
//mat实际代表LibMat这个抽象类所派生出的类的对象
mat.check_in();
if(mat.is_late())
mat.assess_fine();
if(mat.waiting_list())
mat.notify_availiable();
}
2、漫游:面向对象编程思维
通常情况下,默认所有member function的决议程序均会在编译阶段静态完成。如果希望使其在运行时动态决定,则应在声明前加入关键字virtual。
class LibMat
{
public:
LibMat(){cout<<"LibMat::LibMat() default constructor!\n";}//1
virtual ~LibMat(){cout<<"LibMat::~LibMat() destructor!\n";}//2
virtual void print() const
{
cout<<"LibMat::print()--I am a LibMat object!\n";//3
}
}
void print(const LibMat &mat)
{
cout<<"in global print():about to print mat.print()\n";//4
mat.print();//5
}//挡在main函数中传入LibMat的派生类对象时,语句执行顺序1, 派生类构造函数,4,5,派生类析构函数,2
创建派生类book 为了清晰表明该新类是基于已存在的类设计的 在其名称后紧跟冒号: 接着关键字public和基类名称
class Book:public LibMat
{
public:
Book(const string &title,const string &author)
:_title(title),_author(author)
{
cout<<"Book::Book("<<_title<<","<<_author<<")constructor\n";
}
virtual ~Book()
{
cout<<"Book::~Book() destructor~\n";
}
virtual void print() const
{
cout<<"Book::print()--I am a Book object!\n"
<<"My title is:"<<_title<<'\n'
<<"My author is:"<<_author<<endl;
}//重载了LibMat的print函数
const string& title() const{return _title;}
const string& author() const{return _author;}
protected:
string _title;
string _author;//protected的所有成员除派生类外都不得直接取用
}
当使用继承类时(即派生类),无需刻意分辨哪些成员是由父类继承而来还是直接在子类中进行定义的)。这些成员会被透明地处理(即统一管理)
3、不带继承的多态
这节内容没有笔记;主要是阐述了不使用继承机制去实现多态性所存在的明显不便;采用面向对象编程的方法通过继承机制实现多态性是非常实用的选择。
4、定义一个抽象基类
定义抽象类
- 确定所有子类间共有的一系列操作行为。
- 探索如何识别那些受类型影响的操作行为——即确定哪些操作在不同派生类中需要采用不同的实现方式。
- 评估每个操作行为在继承体系中的访问权限……如果某个操作在基类中无需被派生类调用,则应声明为
private……
class num_sequence
{
public:
virtual ~num_sequence(){};
virtual int elem(int pos) const=0;//这个函数之于这个抽象基类无实际意义,便定义为纯虚函数,派生类可以重载该函数以实现多态,将纯虚函数赋值为0,意思便是令它为纯虚函数
static int max_elems(){return _max_elems;}
virtual ostream& print(ostream &os =cout)const=0;
protected:
virtual void gen_elems(int pos)const=0;
bool check_integrity(int pos) const;
const static int _max_elems=1024;
};
bool num_sequence::check_integrity(int pos) const
{
if(pos<=0||pos>_max_elems)
{
cerr<<"!!invalid position:"<<pos
<<"Cannot honor request\n";
return false;
}
return true;
}
ostrean& operator<<(ostream&os, const num_sequence &ns)
{
return ns.print(os);
}
任何类如果声明一个或多个纯虚函数,则因为其接口不完整(因为纯虚函数没有 bodies),无法为其生成任何对象;这样的类只能作为派生体使用,并且前提是这些派生体必须为所有虚拟功能提供确切的实现。
5、定义一个派生类
派生类由两部分构成:一部分是基于基类构建的子对象,在基类具备non-static data members的情况下形成;另一部分则是自身特有属性(由自身的non-static data members构成)。
该类须对继承自其父类的所有纯虚函数实现相应的功能,并且此结构体还需声明自身独有的成员变量以完成特定功能需求。
class Fibonacci:public num_sequence{
public:
Fibonacci(int len=1,int beg_pos=1)
:_length(len),_beg_pos(beg_pos){}
virtual int elem(int pos) const;
virtual const char* what_am_i() const{return "Fibonacci";}
virtual ostream& print(ostream &os=cout)const;
int length() const{return _length;}//非virtual,因为基类未提供实体可供改写,因此基类指针无法调用
int beg_pos() const{return _beg_pos;}
protected:
virtual void gen_elems(int pos)const;//定义未写
bool check_integrity(int pos) const;
int _length;
int _beg_pos;
static vector<int> _elems;
}
//在类本身之外对虚拟函数进行定义时,不需指明关键词virtual
int Fibonacci::
elem(int pos) const
{
if(!check_integrity(pos))//继承来的,但如果派生类声明了相同的函数,便会遮蔽朱基类的那份member,如果在派生类内使用继承而来的那份member,必须使用class scope运算符加以修饰
return 0;
if(pos>_elems.size())
Fibonacci::gen_elems(pos);//通过class scope运算符可以清楚的让编译器知道想调用哪一份函数实体,于是执行期发生的虚拟机制便被遮掩了。
return _elems[pos-1];
}
inline bool Fibonacci::
check_integrity(int pos) const
{
if(!num_sequence::check_integrity(pos))
return false;
if(pos>_elems.size())
Fibonacci::gen_elems(pos);
return true;
}//但由于check_integrity为被声明为virtual,因此实际调用时不会根据实际对象来进行决议
num_sequence *ps=new Fibonacci(12,8);//num_sequence的指针,但实际指向Fibonacci
ps->check_integrity(pos);//根据ps的型别,会被静态决议为num_sequence::check_integrity()
//改进,在check_integrity中使用virtual
bool num_sequence::check_integrity(int pos,int size)
{
if(pos<=0||pos>_max_elems)
{
//....
}
if(pos>size)
gen_elems(pos);
return true;
}
6、运用继承体系
?讲了下派生类定义好后咋用,多么神奇
7、基类抽象程度修改
在原有设计中,默认设置较为简单. 现提升基类的抽象程度.
class num_sequence{
public:
virtual ~num_sequence(){}
virtual const char* what_am_i() const=0;
int elem(int pos) const;
ostream& print(ostream&os=cout) const;
int length()const{return _length;}
int beg_pos const{return _beg_pos;}//在派生类中未定义,当在派生类中调用时调用的是基类的,但由于基类的构造函数中赋予了_beg_pos的值,由派生类的构造函数所调用
static int max_elems(){return 64;}
protected:
virtual void gen_elems(int pos)const=0;
bool check_integrity(int pos, int size)const;
num_sequence(int len,int bp,vector<int>&re)
:_length(len),_beg_pos(bp),_relems(re){}
int _length;
int _beg_pos;
vector<int> &_relems;
};
class Fibonacci:public num_sequence{
public:
Fibonacci(int len=1,int beg_pos=1);
virtual const char*what_am_i() const
{return "Fibonacci";}
protected:
virtual void gen_elmes(int pos) const;
static vector<int> _elems;
};
8、初始化、析构、复制
num_sequence作为一个抽象基类,在面向对象编程中具有特殊地位,在这种情况下我们无法直接创建该类型的具体实例;其在继承结构中充当基础角色,并始终作为派生类所具有的子对象存在;基于这一特性,在设计其父类时必须将构造函数声明为protected而不是public
派生类的构造函数行为涉及多个步骤,在具体实现时需要遵循特定顺序:首先包括调用基类构造函数,并随后调用自身构造函数。在该过程中自身不仅需要为本类的数据成员赋初值,还需适当初始化其基类的数据成员。
inline Fibonacci::
Fibonacci(int len, int beg_pos)
:num_sequence(len, beg_pos, _elems)//因为基类有数据成员变量需要初始化
基类的destructor会在派生类的destructor结束之后被自动调用
9、在派生类中定义一个虚拟函数
当我们创建派生物时需要做出选择:是要去除基类中的虚函数 并将其作为基础来实现 或者直接继承其虚函数 并将其作为基础来实现 若我们选择前者 则该派生物也将被视为一个抽象型别 并且无法为其定义实体模型
若希望对基类提供的虚拟函数进行重写,则所定义于派生类的新功能类型必须与基类声明的功能原型完全一致;该功能原型应包含以下几个要素:参数列表;返回类型;以及是否为常量功能
虚拟函数的静态决议:
-
为确保正确性,在基类的所有构造与析构操作中需声明其为纯虚数
-
当创建继承类对象时,在其构造过程中先会触发基类的操作,在基层面若试图调用其定义的纯虚数成员操作,则由于继承对象的数据成员尚未完成初始化工作而无法直接访问继承方的具体实现操作;此时编译器将默认将其静态绑定到基层对应的操作功能上
- 当使用的是基类的对象,而非基类对象的pointer或reference时
void print(LibMat object, const LibMat* pointrt, const LibMat &reference)
{
object.print();//必定调用LibMat::print(),因为是对象,此处print是个虚拟函数
pointer->print();
reference.print();//均会动态决议
}
10、执行期的型别鉴定机制
typeid运算符:
基于执行期的类型识别机制,在编译器优化中帮助我们实现对多态化的class pointer或class reference的有效查询,并通过分析其指向的对象属性来确定所指对象的实际类型
#include<typeinfo>
inline const char* num_sequence::
what_am_i() const
{
return typeid(*this).name();
}//typeinfo.typeid运算符会返回一个type_info对象,其中存储着与型别相关的种种信息,每一个多态类如FIbonacci都会对应一个type_info对象,该对象的name()函数会返回const char*,y用以表示类名称
//type_info class也支持相等和不相等两个比较操作
num_sequence *ps=&fib;
if(typeid(*ps)==typeid(Fibonacci))
ps->Fibonacci::gen_elems(64);
//ps的确指向Fibonacci对象
//但ps仍然是num_sequence的指针,它不知道自己指向了fibonacci对象,因此ps->gen_elems(64)不可食用
六、以template进行编程
1、Class Template的定义
template <typename elemType>
class BinaryTree
{
public:
BinaryTree();
BinaryTree(const BinaryTree&);
~BinaryTree();
BinaryTree& operater=(const BinaryTree&);
bool empty(){return _root==0;}
void clear();
private:
BTnode<elemType> *_root;
//将src所指之子树复制到tar所指之子树
void copy(BTnode<elemType>* tar, BTnode<elemType>* src);//此处的BTnode必须以template parameter list加以修饰,一般规则是,在class template 及其members的定义式中,不须如此,除此之外的其他场合都需要以parameter list加以修饰
};
//在类主体之内定义inline函数与non-template class一样,在类主体之外不一样
template<typename elemType>
inline BinaryTree<elemType>::
BinaryTree()_root(0)
{}//第二次出现的BinaryTree不用template parameter list加以修饰,因为实在class定义域内
//以下是BinaryTree的copt constructor、copy assignment operator及destructor的定义:
template<typename elemType>
inline BinaryTree<elemType>::
BinaryTree(const BinaryTree &rhs)
{
copy(_root,rhs._root);
}
template<typename elemType>
inline BinaryTree<elemType>::
~BinaryTree()
{
clear();
}
template<typename elemType>
inline BinaryTree<elemType>&
BinaryTree<elemType>::
operator=(const BinaryTree &rhs)
{
if(this!=&rhs)
{
clear();
copy(_root,rhs._root);
}
return *this;
}
2、实现一个Class Template
template<typename elemType>
inline void BinaryTree<elemType>::insert(const elemType &elem)
{
if(!_root)
_root=new BTnode<elemType>(elem);//构造一个BTnode
else
_root->insert_value(elem);
}
//insert_value()会通过左侧子节点或右侧子节点递归调用自己,直到以下任何一种情形发生才停止:(1)合乎条件的子树并不存在(2)欲安插的数值已在树中,由于每个数值只能在树中出现一次,所以用_cnt记录这个节点的安插次数
template<typename valType>
void BTnode<valType>::insert_value(const valType &val)
{
if(val==_val)
{
_cnt++;return;
}
if(val<_val)
{
if(!_lchild)
_lchild=new BTnode(val);
else _lchild->insert_value(val);
}
else
{
if(!_rchild)
_rchild=new BTnode(val);
else _rchild->insert_value(val);
}
}
3、一个以Function Template完成的Output运算符
template<typename elemType>
inline ostream& operator<<(ostream& os, const BinaryTree<elemType>& bt)
{
os<<"Tree:"<<endl;
bt.print(os);//print是BinaryTree<elemType> 的私有成员函数,因此<<运算符必须是friend
return os;//此处返回os是为了接收后面的输入流
}
BinaryTree<string> bts;
cout<<bts<<endl;
4、常量表达式与默认参数
模板参数并不是必须特定类型的值,并非不能使用常量表达式作为模板参数。
template <int len>
class num_sequence
{
public:
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 对象,但是基类num_sequence因为参数len(16)导致元素数目为16
Fibonacci<16> fib2(17);//Fibonacci 对象,beg_pos为17
即len不需要在类中单独定义了
5.Member Template Functions
可以讲member functions定义成template形式
class PrintIt{
public:
PrintIt( ostream &os)
:_os( os ){}
//下面是一个member template function
void print( const elemType &elem, char delimiter ='\n')
{
_os<<elem<<delimiter;
}
private:
ostream& _os;
};
//用法
PrintIt to_standard_out( cout );
to_standard_out.print("hello");
Class template内也可以定义member template function
template<typename OutStream>
class PrintIt
{
public:
PrintIt( OutStream &os)
:_os(os){}
template<typename elemType>
void print(const elemType &elem, char delimiter ='\n')
{
_os<<elem<<delimiter;
}
private:
ostream& _os;
};
PrintIt to_standard_out( cout );
to_standard_out.print("hello");
七、异常处理
1.抛出异常
inline void Triangular_iterator::
check_integrity()
{
if( _index >= Triangular::_max_elems)
throw iterator_overflow( _index, Triangular::_max_elems);//抛出异常,会直接调用拥有两个参数的constructor
if( _index >= Triangular::_elems.size())
Triangular::gen_elements( _index + 1 );
}
//异常类定义
class iterator_overflow
{
public:
iterator_overflow( int index, int max)
:_index( index ),_max( max ){}
int index() {return _index};
int max() {return _max};
void what_happened( ostream &os = cerr )
{
os<<"Internal error: current index"
<<_index<<" exceeds maximum bound: "
<<_max;
}
private:
int _index;
int _max;
};
2.捕捉异常
我们可以通过单一或连续的catch子句来捕获被抛出的对象。catch子句由三个组成部分构成:关键词catch及其后的内容等。
extern void log_message( const char *);
extern string err_messages[];
extern ostream log_file;
bool some_function()
{
bool status = true;
//....
catch(int errno)
{
log_message( err_messages[errno]);
status = false;
}//对应throw 42
catch( const char* str)
{
log_message(str);
status = false;
}//对应throw “panic:no buffer!\n”;
catch( iterator_overflow &iof)
{
iof.what_happend( log_file );
status = false;
}//对应throw iterator_overflow( _index, Triangular::_max_elems)
return status;
}
此案例完整地展示了异常处理的过程。流程经过所有的catch子句后, 由正常的程序接收, 在该案例中, 在正常程序执行完所有catch子句后, 正常程序将重新处理返回状态的这一行。
有时难以实现对所有异常事件的全面处理,在除了记录信息之外的情况下,则需要将该异常再抛以便让其他捕获器来参与进一步处理。
catch( iterator_overflow &iof)
{
iof.what_happend( log_file );
throw;
}
//捕捉任何异常
catch(...)
{
log_messag("exception of unknown type");
//清理,然后离开
}
3.提炼异常
在编程中使用 catch 机制时,在 try 块内部设置一个 catch 子句能够有效处理可能出现的错误情况。通过关键字 try 来启动一个代码块,并在其内部包含一系列执行的语句或操作。相应的错误处理则放置在该代码块之后的位置上,并且当该代码块内部出现错误时,则会触发后续设置好的错误捕获机制。
bool has_elem( Triangular_iterator first, Triangular_iterator last, int elem)
{
bool status = true;
try
{
while( first != last )
{
if( *first == elem )
return status;
++first;
}
}//try中如果有异常抛出(iterator_overflow类型),会被捕捉
catch( iterator_overflow &iof)
{
log_message( iof.what_happend() );
log_message( *check if iterator address same container*);
}
status = false;//异常捕捉后程序恢复从这开始,又或者没有找到elem
return status;
}
//其中*first会调用被重载的 * 运算符
inline int Triangular_iterator::
operator *()
{
check_integrity();
return Triangular::elems[_index];
}
//又调用check_integrity
inline void Triangular_iterator::
check_integrity()
{
if( _index >= Triangular::_max_elems )
throw iterator_overflow( _index, Triangular::_max_elems);
//....后面语句在异常被处理之前无法执行
}
当查找的索引超过_max_elems时就会触发一次检查冲突(check_integrity()),在此时异常处理机制 会顺着函数调用链条扫描是否存在能够捕获此次冲突的捕获子句。
4.局部资源管理
extern Mutex m;
void f()
{
//申请资源
int *p = new int;
m.acquire();
process( p );
//释放资源
m.release();
delete p;
}//此函数内看似申请了资源并且在最后释放了资源,但如果process这个函数抛出异常呢?就不会释放申请的资源
C++的发明者采用了基于日语影响的资源管理手法;对于对象来说,在构造函数内部执行初始化操作,并且在构造函数内部完成资源获取;同时,在析构函数内部进行相应的资源释放。
#include <memory>
void f()
{
auto_ptr<int> p( new int);
MutexLock ml( m );
process( p );
//p和m1的destructor会在此处被悄悄调用
}
如果process( p );在运行过程中触发异常事件,则C++确保在该事件引发的异常处理机制完成某个函数任务处理过程之前调用所有局部对象的构造函数
5.标准异常
如果new表达式无法在程序的内存分配过程中获得足够的内存空间来支持其运行所需的资源,则该程序将会触发bad_alloc异常
vector<string>* init_text_vector( ifstream &infile )
{
vector<string> *ptext = 0;
try
{
ptext = new vector<string>;
//打开file和file vector
}
catch( bad_alloc )
{
cerr << "ouch.heap memory exhausted!\n ";
//..清理并离开
}
return ptext;
}
完结撒花
