Fork me on GitHub

C++:异常、RTTI及类型转化运算符

一、概述

  对C++第十五章做一个简单的回顾,C++的初步学习也快要结束了,今天刚好是元旦,原本计划去年底之前就学习完毕的,但是发现还有两个大章节没学完,真是太懒了。争取年前搞定,明年的大部分时间会专注以太网学习和C++的关联领域。废话不多说了,赶紧码字~C++的第十五章主要介绍了友元、异常和RTTI,友元friend这个东西已经见过好多次了,只不过这里引申到了类中,指明友元类可以访问原类中的所有对象和方法但是要注意友元类/友元成员函数声明和定义的顺序!

二、友元类和嵌套类

2.1 友元类

  一个类成为在另一类中声明时添加friend时,该类被称为友元类,友元类可以访问原类中的私有成员和保护乘员,也可以做更为严格的限制,比如友元成员函数。

  一般,友元类和原类不应具有任何继承关系,也就是定义无关,功能补充的情况。比如C++书中提到的遥控器和电视机,两者没有从属关系,但是都可以针对频道信息进行修改。

2.2 嵌套类

  嵌套类指一个类在另一个类中进行定义时,此时构成嵌套类关系,对于嵌套类的访问权限完全取决于你定义在了private还是public,private只有原生类可访问,public则可以在派生类中访问。

2.3 友元类、嵌套类、类包含的关系

  • 友元类:原生类中进行friend声明,但不进行定义
  • 嵌套类:原生类中进行声明和定义
  • 包含   :原生类中进行声明,但不进行定义

三、异常及RTTI

3.1 异常

  异常是指在编译或运行阶段代码抛出的一些错误信息,从最底层看这些错误信息都必须自己手动编写相关错误信息进行打印,比如最简单的:

int ret;
ret = SetVaule()//随便举一个函数例子
if( ret == false )
{
    cout << "SetVaule Return is False!" << endl;
}

  C++中针对异常的也给与一些处理方式,比如abort()程序停止函数和exit() 函数,异常机制,RTTI等,开发者也可以在适当的位置进行return这些都是针对异常的一些简单处理办法。

3.2 异常机制

  异常机制是C++提出的一种用于try{}引发异常-throw 抛出异常-catch()捕捉异常的一种异常处理方式。

#include <iostream>
#include <cstdlib>
using namespace std;

double hmean1(const double x, const double y);

int main(void)
{
	double x, y, z;
	cout << "enther two number: ";
	while (cin >> x >> y)
	{
		try//防止可能出错得代码块,引发异常后返回try
		{
			z = hmean1(x, y); 
		}
		catch (const char* s)//反馈字符串类型得异常
		{
			cout << s << endl;
			cout << "catch enter next set of numbers: ";
			continue;
		}
		cout << "Harmonic mean of " << x << " and " << y << " is " << z << endl;

	}
	return 0;
}

//引发异常
double hmean1(const double x, const double y)
{
	//错误情况讨论
	if (x == -y)
	{
		throw "bad hmean() ,a = -b not allowed!";//触发异常
		abort();
	}
	return (2.0 * x * y) / (x + y);
}

  这是try-throw-catch异常机制最简单的使用方法,我们只要保证throw的数据类型要和catch保持一致即可,这样才能被正确catch()到。当然,这里throw的类型也可以是类,也可以是派生类,但我们catch()的时候就需要注意了,最先编写的catch()应该是针对派生类的,然后再编写基类的catch(),这样做是为了合法进行栈解退(类似于递归),试想如果当派生类发生了错误时,你首先catch()了基类并做了析构,那派生类中的一些信息是不是没有被析构呢?这一点和虚析构的作用差不多,所以我们catch()要遵循从上到下的理念。

栈解退

  除此之外,针对类我们可以通过包含头文件#include <stdexcept>基于exception类进行public派生以进行更为方便的logic_error()定义并在catch后进行what()打印。(本白只是提出相关知识点用于自己的回顾,有兴趣的小伙伴参阅相关章节即可)

#pragma once
#include<iostream>
#include <string.h>
#include <stdexcept>

using namespace std;

class sales
{
public:
	enum { eMONTH = 12 };//派生类也可以用

	explicit sales(int yr = 0);//防止隐式转化
	sales(int yr, const double* gr, int n);//显式析构

	virtual ~sales() {};//作为基类,要产生派生类时,析构函数要为虚
	int Year() const { return this->year; };
	virtual double operator[](int i) const;//只是读取gross得成员
	virtual double &operator[](int i);//只是修改gross得成员

	//类嵌套
	class bad_index :public logic_error
	{
	private:
		int bi;//索引编号
	public:
		explicit bad_index(int ix=0, const string& wt = "index error in sales object") :bi(ix),logic_error(wt) {};
		int bi_val() const { return this->bi; };
		virtual ~bad_index() throw() {}; /*throw()代表析构函数不会引发任何异常,如果throw(int)代表会引发一个int类型得异常*/
	};

private:
	int year;
	double gross[eMONTH];
	
};

3.3 异常的优劣、迷失及处理方式

  异常好不好呢?好,但是异常会消耗一定的内存,因此如果开发者功力深厚,只需要在自己不确定的点进行异常检查即可(比如静态断言),不用大费周章的进行tyr/catch,这个看个人了,现阶段作为小白我自己觉得异常机制可能就在泛型编程的时候有用吧。

  异常迷失一般有两种,一种是未定义的异常,一种是定义但没有try/catch的异常。这将导致在编译或者运行阶段出现一些难以捉摸的问题,这个时候就要靠开发者自己的经验去查找问题了,但是绝大多数都能通过debug/博客园/csdn得到答案,所以不用慌~

四、RTTI运行阶段的类型识别

  RTTI被称为运行阶段的类型识别,我认为这才是C++面向对象编程过程中异常的精髓~RTTI是为了跟踪程序运行时某个指针指向的对象类别,以防止其访问限制数据。支持RTTI的方法有三种:

  • 类型转换运算符dynamic_cast、const_cast、static_cast
  • typeid()
  • type_info

  需要注意RTTI是针对类的引用和类的指针进行的,并且监控的类必须是包含虚函数的类,因为只有虚函数的类才存在多态,指针/引用的指向对象才会发生变化,这一点大家请参考虚函数的作用。

4.1 typeid

if (typeid(*pg/*基类类指针*/) == typeid(Magnificent/*派生类类型*/))
{
    cout << "You are a big fool!" << endl;
}

4.2 type_info

cout << "GetOne() generate the TypeID name: " << typeid(*pg).name() << endl;//typeid--获取指针指向类的名字,仅能指向创建的指针类型

4.3 类型转换运算符

dynamic_cast:支持从派生类-基类的转换

/*基类*/
class Grand
{
public:
	Grand(int h):hold(h) {};
	virtual void Speak() const { cout << "I am a Grand class.\n"; };
	virtual int value() const { return this->hold; };
private:
	int hold;
};

/*一代派生类*/
class Superb :public Grand
{
public:
	Superb(int h = 0) :Grand(h) {};
	virtual void Speak() const { cout << "I am a Superb class.\n"; };
	virtual void Say() const{ cout << "I hold superb value is" << Grand::value() << endl; };

};

/*二代派生类*/
class Magnificent :public Superb
{
public:
	Magnificent(int h = 0, const char c = 'a') :Superb(h), ch(c) {};
	virtual void Speak() const { cout << "I am a Magnificent class.\n"; };
	virtual void Say() const { cout << "I hold Magnificent value is" << Superb::value() << endl; };
private:
	char ch;
};

/*只是举例说明*/
int main(void)
{
    Grand* pg = new Grand(200);

    if (dynamic_cast <Superb*>(pg) == nullptr)//如果pg为基类指针,则不能转化成功,返回空指针;如果不是基类而是本身,则转化成功,返回转换后的指针
    {
        cout << "Can't transfer Point[PG] to Superb Class!" << endl;
    }
	return 0;
}

const_cast:临时解除指针/引用的const属性

int a=10,b = 11;
const int* pt = &a;//不允许通过指针修改a,也不可以修改指向关系
int* p2 =  const_cast<int*>(pt);//int* p2 = pt;不行

static_cast:支持基类-派生类和派生类-基类的双向转换

Superb* ps = nullptr;
double k = static_cast<double>(10) / 3;
static_cast<Grand*>(ps);//可以进行向下转化
static_cast<Magnificent*>(ps);//可以进行向上转化,虽然ps本身是superb类,转化为第三代类是时会自行调用构造函数 填补一些空白参数

 以上三种RTTI手段联用,就可以进行RTTI啦!!如果这个时候再加上expection类的logic_error/whattry-throw-catch异常机制,这将构成开发中最基础的一套异常处理机制!

posted @ 2022-12-25 18:16  张一默  阅读(41)  评论(0编辑  收藏  举报