C++专题:异常处理与转换函数,智能指针,STL模板

目录

异常处理

转换函数

智能指针

STL标准模板库


异常处理

什么是异常?

程序中常见的错误分为两大类:编译时错误运行时错误。编译时的错误主要是语法错误,如关键字拼写错误、语句末尾缺分号、括号不匹配等。运行时出现的错误统称为异常,对异常的处理称为异常处理,他是一种比较有效的处理系统运行时错误的方法。一般一个错误处理系统包含:异常,标准错误码,错误日志记录及监测系统

异常的基本思想:

        让一个函数在发现了自己无法处理的错误时抛出一个异常,然后它的直接或间接调用者能够处理这个问题。

处理异常办法:检查(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;
  }
    

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值