目录
异常处理
什么是异常?
程序中常见的错误分为两大类:编译时错误和运行时错误。编译时的错误主要是语法错误,如关键字拼写错误、语句末尾缺分号、括号不匹配等。运行时出现的错误统称为异常,对异常的处理称为异常处理,他是一种比较有效的处理系统运行时错误的方法。一般一个错误处理系统包含:异常,标准错误码,错误日志记录及监测系统
异常的基本思想:
让一个函数在发现了自己无法处理的错误时抛出一个异常,然后它的直接或间接调用者能够处理这个问题。
处理异常办法:检查(try)→抛出(throw)→catch(捕获)
如果在执行一个函数的过程中出现异常,可以不在本函数中立即处理,而是发出一个信息,传给它的上一级(即调用函数)来解决,如果上一级函数也不能处理,就再传给其上一级,由其上一级处理。如此逐级上传,如果到最高一级还无法处理,运行系统一般会自动调用系统函terminate(),由它调用abort终止程序。
标准异常
标准异常的实质就是C++已经定义好了很多类,当出现错误时可以报告相应的标准错误信息,使用what可以打印指定信息,也可以重写,我们来看一张图:
可以看到所有的标准异常都是由exception派生而来,这意味着当我们不知道异常错误类型我们可以在catch中用execption接收所有标准异常类型。
下面我们来看一段代码看看标准异常的使用:
#include<iostream>
using namespace std;
#include <stdexcept> //标准异常头文件
/************************************************************************
* 文件说明
************************************************************************/
#if 0
//低版本,显示说明函数抛出什么类型的异常
float func(int x, int y) throw( invalid_argument );
float func(int x, int y) throw( invalid_argument)
#elif 0
//C++11标准以后,不需要显示说明该函数抛出什么类型的异常,因为 默认所有函数都会抛异常
float func(int x, int y);
float func(int x, int y)
#else
//如果函数声明定义为 noexcept,表示该函数不会抛出异常。如果函数中强行throw(),会出现段错误
float func(int x, int y) noexcept;
float func(int x, int y) noexcept
#endif
{
if( y == 0 ){ //检查错误
// return -1;
#if 0
//定义无效参数类型的 对象,用想传递的错误信息初始化
invalid_argument tmp("error, y = 0, needs y != 0");
//抛出类对象,就是在抛出异常,立即结束该函数向下执行
throw tmp;
#else
// 抛出匿名对象
throw invalid_argument("error, y=0, but need y != 0");
#endif
cout << "------------" << endl;
}
return x/y;
}
int main(int argc, char *argv[])
{
int a, b;
AA: cin >> a >> b;
try{
cout << func(a, b) << endl;
} catch ( exception &err){ //捕获异常, 如果不知道函数会抛出什么类型的异常,那么捕获时 使用 exception类型
//打印异常保存的错误信息
cout << err.what() << endl;
//相应的异常处理
goto AA;
}
return 0;
}
下面我列出常见标准异常的表:
当然除去标准异常,我们也可以根据需求写出我们自己的异常处理:自定义异常
#include<iostream>
using namespace std;
class MyException{
public:
MyException(const char *err) noexcept : errmsg(err){ }
const char * priError() const noexcept {
return errmsg;
}
private:
const char *errmsg;
};
float func(int x, int y)
{
if(y == 0)
//抛出完全自定义异常类
throw MyException("error, needs y!=0");
return x/y;
}
int main(int argc, char *argv[])
{
int a, b;
cin >> a >> b;
try{
cout << func(a, b) << endl;
} catch ( MyException &err){
cout << err.priError() << endl;
}
return 0;
}
转换函数
类作为C++的自定义类型,需要类型转换时,C++提供类型转换函数将一个类的对象转换成另一种类型的数据,他的本质还是运算符重载,只是重载的运算符是类名这个特殊的自定义类型。
语法:
operator 类型(){
}
注意:
1、转换函数没有数据类型,但是有 return返回值
2、转换函数必须是类的成员函数,而且空参数
3、不能定义到 void 、数组、函数类型的转换
4、转换函数常为 const修饰,因为它并不改变数据成员的值
#include<iostream>
using namespace std;
class Subclass;
class Base{
public:
Base(int x) : x(x){}
int getValue(){ return x; }
operator int(){ return x; }//转换函数
operator Subclass();//转换函数
private:
int x;
};
class Subclass : public Base{
public:
Subclass(int x, int y) : Base(y), x(x){}
int getValue(){ return x; }
private:
int x;
};
Base::operator Subclass(){
return Subclass(x, 0);
}
int main(int argc, char *argv[])
{
int a = 3;
Base obj(5);
obj = a; //移动构造(重载了 =运算符)
cout << obj.getValue() << endl;
obj = 97;
a = obj; // 调用类 Base 中的转换函数, 转换为 int类型
cout << a << endl;
char ch = obj;
cout << ch << endl;
Base obj1(3);
Subclass obj2(5, 1);
obj1 = obj2;
cout << obj1.getValue() << endl;
obj1 = 23;
obj2 = obj1; //调用Base类中的转换函数,转换为 Subclass类型
cout << obj2.getValue() << endl;
return 0;
}
本来在C++中向下隐式转换是不允许的,比如上面代码中基类对象obj1赋值给派生类对象obj2,基类向下转换为派生类,但是我们可以通过转换函数来实现!!
标准转换函数
上面是我们自定义的转换函数,当然C++中也有标准的转换函数供我们使用,下面我们一张图来列出:
智能指针
智能指针(smart pointer)是个特殊的类模板,重载了“->”和“*”运算符,实现了C++的自动内存回收机制,可以帮助我们在一些大型项目中减少许多麻烦,以免忘记释放指针造成的内存泄漏。智能指针类通过将一个计时器与类指向的对象相关联,引用计数跟踪该类有多少个对象的指针指向同一对象。
C++中有三种智能指针,shared_ptr,unique_ptr和weak_ptr。
share_ptr:共享资源的智能指针,当引用计数变为0时,它所指向的对象就会被删除
unique_ptr:独占资源的智能指针,同一时刻只能有一个unique_ptr指向给定对象,生命周期结束,则将指向对象销毁。
1.为动态申请的内存提供异常安全
2.将动态申请内存的所有权传递给某个函数
3.从某个函数返回动态申请内存的所有权
4.从容器中保存指针
weak_ptr:弱指针
1.检测资源共享的指针所指向的对象是否还存在,如果已经被销毁,那么避免再用共享指针去指向他,或者去操作它
2.确认指针的指向已经断开
#include<iostream>
using namespace std;
#include <memory>
class Base{
public:
Base(int x): x(x){ cout << "line: " << __func__ << endl; }
~Base(){ cout << "line: " << __func__ << endl; }
int getValue(){ return x; }
private:
int x;
};
void test()
{
//定义资源共享的智能指针 p(实质是一个类模板的对象),
//当该函数结束时,该对象的生命周期将会结束,系统会自动调用析构函数回收堆区资源
#if 0
shared_ptr<Base> p(new Base(12));
#else
// make_shared<>():函数模板,目的将堆区开辟的空间,进行共享处理,安全性比 new更高
shared_ptr<Base> p = make_shared<Base>( Base(13) );
#endif
//智能指针 p,重载 ->和*号运算符,因此访问 成员必须用 -> 运算符
cout << p->getValue() << endl;
//定义另一个shared_ptr指针,指向同一个对区
shared_ptr<Base> q = p;
}
int main(int argc, char *argv[])
{
test();
return 0;
}
如果在上面代码中,我们没有使用智能指针,每次调用函数,new出对象后没有delete,那么对象指针指向的堆区空间在函数运行完成后并没有释放,且对象也没有销毁!
弱指针的应用案例
#include <iostream>
#include <memory>
using namespace std;
class Demo{
public:
explicit Demo(){cout<<"ok"<<endl;}
virtual ~Demo(){cout<<"~"<<endl;}
void func()
{
cout<<"func"<<endl;
}
};
int main()
{
shared_ptr<Demo> p=make_shared<Demo>();
weak_ptr<Demo> pp=p;
p.reset();//重置清零对象
if(pp.expired())//检测弱指针指向的对象是否还存在
{
cout<<"obj is not exist"<<endl;
}
return 0;
}
看看运行结果
独占指针 应用案例:
#include <iostream>
#include <memory>
using namespace std;
class Demo{
public:
explicit Demo(){cout<<"Demo"<<endl;}
virtual ~Demo(){cout<<"~Demo"<<endl;}
void func()
{
cout<<"func"<<endl;
}
};
void safeheap()
{
unique_ptr<Demo> p(new Demo);
p->func();
//unique_ptr<Demo> pp=p;error同一时刻只能有一个独占指针指向对象
}
int main()
{
safeheap();
return 0;
}
STL标准模板库
标准模板库(Standard Template Library
)中包含了很多实用的组件,利用这些组件,程序员编程方便而高效。
STL可分为六个部分:
1.容器(containers):特殊的数据结构,实现了数组,链表,队列等,实质是类模板
2.迭代器(iterators):特殊的指针,操作访问容器的数据,实质是运算符的重载
3.空间配置器(allocator):类模板
4.配接器(adapters):提供接口
5.算法(algorithms):模板函数,读写容器对象的逻辑算法:排序,遍历,查找等
6.仿函数(functors):类似函数,通过重载()运算符来模拟函数行为的类
容器又分为序列容器和关联容器
序列容器:vector ,deque,list
关联容器:set,multiset,map,multimap
Vector
vector向量相当于一个数组,在内存中分配一块连续的内存空间进行存储,支持不指定vector大小的存储。通常默认的内存分配能完成大部分情况下的存储
优点:
1.可以不指定大小,使用push_back,pop_back来进行动态操作
2.随机访问方便,支持【】操作符和vector.at()
3.节省空间
缺点:
1.在内部进行插入删除操作效率低
2.只能在vector的最后进行push,pop,不能再头部进行push,pop
3.当动态添加的数据超过默认分配的大小时要进行整体的重新分配,拷贝和释放。
#include <iostream>
#include <vector>
using namespace std;
int main() {
vector<int> nums;
nums.insert(nums.begin(), 99);//在头部插入数据
nums.insert(nums.begin(), 34);
nums.insert(nums.end(), 1000);//在尾部插入数据
nums.push_back(669);
cout << "\n当前nums中元素为: " << endl;
for (int i = 0; i < nums.size(); i++)
cout << nums[i] << " " <<endl;;
cout << nums.at(2);//打印下标为2的元素
nums.erase(nums.begin());
nums.pop_back();//将尾部元素出栈
cout << "\n当前nums中元素为: " << endl;
for (int i = 0; i < nums.size(); i++)
cout << nums[i] << " "<<endl; ;
return 0;
}
List
list双向链表
每一个结点都包括信息块info,一个前驱指针Pre,一个后驱指针Post。可以不分配必须的空间大小,方便进行添加和删除操作,使用的是非连续的内存空间进行存储。
优点:
1.不使用连续内存完成动态操作
2.在内部方便的进行插入和删除操作
3.可在两端进行push,pop
缺点:
1.不能进行内部的随机访问,不支持【】运算符和vector.at()
2.相对于vector占用内存多
#include <iostream>
#include <list>
using namespace std;
int main() {
list<int> number;
list<int>::iterator niter;
number.push_back(123);
number.push_back(234);
number.push_back(345);
cout << "链表内容:" << endl;
for (niter = number.begin(); niter != number.end(); ++niter)
cout << *niter << endl;
number.reverse();
cout << "逆转后的链表内容:" << endl;
for (niter = number.begin(); niter != number.end(); ++niter)
cout << *niter << endl;
number.reverse();
return 0;
}
deque
deque双端队列
优点:
1.随机访问方便,支持【】和vector.at()
2.在内部方便的进行插入和删除操作
3.可在两端进行push和pop
缺点:
占用内存多
区别:
1.如果需要高效的随即存取,而不在乎插入和删除的效率,使用vector
2.如果你需要大量的插入和删除,不关心随机存取,应用list
3.如果你需要随机存取,且关心两端数据的插入和删除,则应使用deque.
push_back();
push_front();
insert();
pop_back();
pop_front();
erase();
begin();
end();
rbegin();
rend();
size();
maxsize();
#include <iostream>
#include <memory>
#include <queue>
using namespace std;
#define NUMMAX 6
int main()
{
int i;
queue<int> one;
for(i=0;i<NUMMAX;i++)
one.push(i);
queue <int>::reference refer=one.front();
for(i=0;i<one.size();i++)
{
cout<<"NO."<<i<<":"<<refer<<endl;
refer++;
}
one.pop();
one.pop();
one.pop();
refer=one.front();
for(i=0;i<one.size();i++)
{
cout<<"NO."<<i<<":"<<refer<<endl;
refer++;
}
refer=one.back();
cout<<"back:"<<refer<<endl;
refer=one.front();
cout<<"front:"<<refer<<endl;
return 0;
}