C++笔记day34 类型转换 异常

104 阅读4分钟

类型转换

C++提供一种新的类型转换语法,好处是更清晰的表明自己要干什么,程序员可以扫一眼就知道一个强制转换的目的。

静态类型转换  static_cast

用途

1. 允许内置数据类型转换
2. 允许父子之间的指针或者引用的转换

语法

static_cast<目标类型>(原变量/原对象)

动态类型转换  dynamic_cast

**不允许内置数据类型转换*8

允许父子之间指针或者引用的转换(在下行转换时 多一个类型检查)

父转子  不安全的  转换失败

子转父  安全   转换成功

如果发生多态,总是安全,可以成功

语法

dynamic_cast<目标类型>(原变量/原对象)

常量转换   const_cast

只允许 指针或者引用 之间转换

语法

const   _cast<目标类型>(原变量/原对象)

重新解释转换(啥都能转)

reinterpret_cast 最不安全一种转换,不建议使用

示例:

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;

//1. 静态类型转换 static_cast
void test01() 
{
	//1. 允许内置数据类型之间转换
	char a = 'a';
	double d = static_cast<double>(a);
	cout << d << endl;
}

class Base {};
class Son :public Base {};
class Other {};

void test02()
{
	Base* base = NULL;
	Son* son = NULL;

	// 语法: static_cast<目标类型>(原对象)
	// 父子之间的指针或者引用可以转换
	//将base 转为Son* 父转子, 向下类型转换 不安全
	Son* son2 = static_cast<Son*>(base);

	//son 转为 Base * 子转父 向上类型转换 安全
	Base* base2 = static_cast<Base*>(son);

	//base转为Other*  报错
	Other* other = static_cast<Other*>(base);

}

//2. 动态类型转换 dynamic_cast
void test03() 
{
	//char c = 'c';
	//double d = dynamic_cast<double>(c);
}

void test04() 
{
	Base* base = NULL;
	Son* son = NULL;

	//将base 转为Son* 父转子, 向下类型转换 不安全
	Son* son2 = dynamic_cast<Son*>(base);
	//son 转为 Base * 子转父 向上类型转换 安全
	Base* base2 = dynamic_cast<Base*>(son);
}

//3. 常量转换 const_casr
void test05() 
{
	const int* p = NULL;
	int* pp = const_cast<int*>(p);

	const int* ppp = const_cast<const int*>(pp);

	//不可以将非指针或非引用做const_cast 转换,因为放的地方不一样,变量放在栈上

	//对引用
	int num = 10;
	int& numRef = num;

	const int& num2 = const_cast<const int&>(numRef);

}

// 4. 重新解释转换 reinterpret_cast 最不安全一种转换,不建议使用。啥都能转
void test06() 
{
	int a = 10;
	int* p = reinterpret_cast<int*>(a);

	Base* base = NULL;
	//base 转 Other *
	Other* other = reinterpret_cast<Other*>(base);
}

异常的基本语法

image.png

C语言中处理异常的两种方法

  1. 用整型的返回值标识错误。
  2. 使用errno宏(可以简单理解为一个全局整型变量)去记录错误。

缺点: 一是不一致,有的返回0是成功,有的返回值0是失败。 二是函数返回值只能有一个,返回错误代码就不能返回其他值。

C++异常的处理关键字

image.png

关键字

try  throw  catch
  1. 可以出现异常的代码 放到 try块

  2. 利用throw抛出异常

  3. 利用catch捕获异常

  4. catch( 类型) 如果想捕获其他类型 catch(…)

  5. 如果捕获到的异常不想处理,而继续向上抛出,利用 throw

  6. 异常必须有函数进行处理,如果都不去处理,程序自动调用 terminate函数,中断掉

  7. 异常可以是自定义数据类型

示例

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;

class MyExcept
{
public:
	void printError() 
	{
		cout << "我自己的异常" << endl;
	}
};

int myDivision(int a, int b) 
{
	if (b == 0) 
	{
		//return -1;//C返回异常的方式
		//throw - 1;//抛出int类型的异常
		//throw 'a';//抛出char类型的异常
		//throw 3.13;//抛出double类型的异常
		//string str = "abc";
		//throw str;//抛出string类型的异常
		throw MyExcept();//抛出MyException的匿名对象,此处是懒得起名了
	}
	return a / b;
}

void test01() 
{
	int a = 10;
	int b = 0;
	// C语言处理异常有缺陷: 返回值不统一,返回值只有一个无法区分是结果还是异常
	//int ret = myDivision(a, b);
	//if (ret == -1) 
	//{
	//	cout << "异常" << endl;
	//}

	try 
	{
		myDivision(a, b);
	}
	catch (int) 
	{
		cout << "int 类型异常的捕获" << endl;
	}
	catch (char) 
	{
		cout << "char类型异常的捕获" << endl;
	}
	catch (double)
	{
		//捕获到了异常但是不想处理,继续向上抛出异常,直接用throw就可以了
		//异常必须有函数进行处理,如果没有任何处理,程序自动调用terminate函数,让程序中断
		throw;
		cout << "double类型异常的捕获" << endl;
	}
	catch (MyExcept e) 
	{
		e.printError();
	}
	catch (...)
	{
		cout << "其他类型异常的捕获" << endl;
	}
}

int main()
{
	try 
	{
		test01();
	}
	catch (double) 
	{
		cout << "main函数中double类型异常的捕获" << endl;
	}
	catch (...)
	{
		cout << "main函数中其他类型异常的捕获" << endl;
	}


	system("pause");
	return EXIT_SUCCESS;
}

栈解旋

从try代码块开始,到throw抛出异常之前,所有栈上的数据都会被释放掉,释放的顺序和创建顺序相反的,这个过程我们称为栈解旋

异常的接口声明

在函数中 如果限定抛出异常的类型,可以用异常的接口声明

语法: void func()throw(int ,double)

throw(空)代表 不允许抛出异常

异常变量的生命周期

  1. 抛出的是 throw MyException();  catch (MyException e) 调用拷贝构造函数 效率低

  2. 抛出的是 throw MyException();  catch (MyException &e)  只调用默认构造函数 效率高 推荐

  3. 抛出的是 throw &MyException(); catch (MyException *e) 对象会提前释放掉,不能在非法操作

  4. 抛出的是 new MyException();   catch (MyException *e) 只调用默认构造函数 自己要管理释放

示例

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;

class MyExcept 
{
public:
	MyExcept() 
	{
		cout << "MyExcept的默认构造函数调用" << endl;
	}
	MyExcept(const MyExcept &e)
	{
		cout << "MyExcept的拷贝构造函数调用" << endl;
	}
	~MyExcept() 
	{
		cout << "MyExcpet的析构函数调用" << endl;
	}
};

void doWork() 
{
	throw new MyExcept();
}

void test01() 
{
	try 
	{
		doWork();
	}
	//抛出是 throw MyExcept();   catch (MyExcept e) 调用拷贝构造函数 效率低
	//抛出是 throw MyExcept();   catch (MyExcept &e) 只调用默认构造函数 效率高 推荐
	//抛出是 throw MyExcept();   catch (MyExcept *e) 对象会提前释放掉,不能在非法操作
	//抛出是 throw new MyExcept();   catch (MyExcept *e) 只调用默认构造函数,要自己管理释放 delete
	catch (MyExcept * e) 
	{
		cout << "自定义异常捕获" << endl;
		delete e;
	}
}

int main()
{

	test01();

	system("pause");
	return EXIT_SUCCESS;
}

异常的多态使用

1. 提供基类异常类

class BaseException

纯虚函数  virtual void printError() = 0;

2.子类空指针异常 和  越界异常 继承 BaseException

3. 重写virtual void printError()

4. 测试 利用父类引用指向子类对象

示例

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;

//异常的基类
class BaseExcept 
{
public:
	virtual void printError() = 0;
};
//空指针异常
class NULLPointerExcept :public BaseExcept 
{
public:
	virtual void printError() 
	{
		cout << "空指针异常" << endl;
	}
};

//越界异常
class OutOfRangeExcept :public BaseExcept
{
public:
	virtual void printError()
	{
		cout << "越界异常" << endl;
	}
};

void doWork() 
{
	//throw NULLPointerExcept();
	throw OutOfRangeExcept();
}

void test01() 
{
	try
	{
		doWork();
	}
	catch (BaseExcept& e)
	{
		e.printError();
	}
}
int main()
{

	test01();

	system("pause");
	return EXIT_SUCCESS;
}

系统标准异常

  1. 引入头文件 

     #include <stdexcept>
    
  2. 抛出越界异常

     throw out_of_range(“…”)
    
  3. 获取错误信息 

     catch( exception & e )     e.what();
    

示例

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
#include<stdexcept>

class Person 
{
public:
	Person(int age) 
	{
		if (age < 0 || age>150) 
		{
			throw out_of_range("年龄必须在0~150之间");
		}
		else 
		{
			this->m_Age = age;
		}

	}
	int m_Age;
};

void test01() 
{
	try 
	{
		Person p(151);
	}
	//catch (out_of_range& e) 也可以像下面这样直接用多态,省得记;一般我们主要做catch操作
	catch(exception & e)
	{
		cout<<e.what()<<endl;
	}
}

int main()
{
	test01();
	

	system("pause");
	return EXIT_SUCCESS;
}

编写自己的异常类

  1. 编写myOutofRange 继承 Exception类

  2. 重写 virtual const char *  what() const

  3. 将sting 转为 const char *

     .c_str()
    
  4. const char * 可以隐式类型转换为 string  反之不可以

  5. 测试,利用多态打印出错误提示信息

示例

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;

class MyOutOfRangeExcept :public exception 
{
public:
	MyOutOfRangeExcept(const char * str)
	{
		//const char * 可以隐式类型转换为string,反之不可以
		this->m_errorInfo = str;
	}
	MyOutOfRangeExcept(string str) 
	{
		this->m_errorInfo = str;
	}
	//重写父类中的虚函数
	virtual const char* what() const 
	{
		return m_errorInfo.c_str();//c_str函数可以将str转为const char *
	}
	string m_errorInfo;
};

class Person
{
public:
	Person(int age)
	{
		if (age < 0 || age>150)
		{
			throw MyOutOfRangeExcept(string("年龄必须在0~150之间"));
		}
		else
		{
			this->m_Age = age;
		}

	}
	int m_Age;
};

void test01() 
{
	try 
	{
		Person p(1000);
	}
	catch (exception& e) 
	{
		cout<<e.what() << endl;
	}
}

int main()
{
	test01();


	system("pause");
	return EXIT_SUCCESS;
}

标准输入流

C++输入输出三方面内容

image.png

cin.get() 获取一个字符

cin.get(两个参数) 获取字符串

利用cin.get获取字符串时候,换行符遗留在缓冲区中

cin.getline() 获取字符串

利用cin.getline获取字符串时候,换行符不会被取走,也不在缓冲区中,而是直接扔掉

cin.ignore() 忽略 默认忽略1个字符, 如果填入参数X,代表忽略X个字符

cin.peek()  偷窥 

cin.putback()  放回 放回原位置

示例

void test01() 
{
	//输入 a s "回车" 第一次 a ,第二次 s 第三次 换行 第四次 等待下次输入
	char c = cin.get();
	cout << "c=" << c << endl;

	c = cin.get();
	cout << "c=" << c << endl;

	c = cin.get();
	cout << "c=" << c << endl;

	c = cin.get();
	cout << "c=" << c << endl;
}

void test02() 
{
	char buf[1024] = {0};
	cin.get(buf, 1024);
	//利用cout.get()取字符串的时候,换行符会遗留在缓冲区中
	char c = cin.get();
	if (c == '\n') 
		{
		cout << "换行符遗留在缓冲区" << endl;
		}
	else 
	{
		cout << "换行符不在缓冲区" << endl;
	}

	cout << buf << endl;
}

void test03() 
{
	char buf[1024] = { 0 };
	//利用cin.getline获取字符串时候,换行符不会被取走,也不在缓冲区中
	cin.getline(buf, 1024);

	char c = cin.get();
	if (c == '\n')
	{
		cout << "换行符遗留在缓冲区" << endl;
	}
	else
	{
		cout << "换行符不在缓冲区" << endl;
	}

	cout << buf << endl;
}

//cin.ignore() 忽略,默认忽略1个字符,如果填入参数x,代表忽略x个字符
void test04() 
{
	cin.ignore();
	char c = cin.get();
	cout << "c=" << c << endl;
}

//cin.peek()  偷窥,可以看第一个字符,不从缓冲区取走
void test05() 
{
	char c = cin.peek();
	cout << "c= " << c << endl;

	c = cin.get();
	cout << "c= " << c << endl;

	c = cin.get();
	cout << "c= " << c << endl;
}

//cin.putback() 放回,将缓冲区里内容放回原来的位置
void test06() 
{
	char c = cin.get();
	cin.putback(c);

	char buf[1024] = { 0 };

	cin.getline(buf, 1024);
	cout << buf << endl;

}

案例1

判断用户输入的内容 是字符串还是数字

//案例1. 判断用户输入的内容是字符串还是数字
void test07() 
{
	cout << "请输入一个字符串或者数字" << endl;
	char c = cin.peek();

	if (c >= '0' && c <= '9')
	{
		int num;
		cin >> num;
		cout << "您输入的是数字 为" << num << endl;
	}
	else 
	{
		char buf[1024] = { 0 };
		cin >> buf;
		cout << "您输入的字符串 为" << buf << endl;
	}
}

案例2

用户输入 0 ~ 10 之间的数字,如果输入有误,重新输入

标志位  cin.fail()   0代表正常  1代表异常

cin.clear()  cin.sync() 清空缓冲区 重置标志位

// 案例2. 用户输入 0 到 10 之间的数字,如果输入有误,重新输入
void test08() 
{
	cout << "请输入0-10之间的数字" << endl;
	int num;
	while (true) 
	{
		cin >> num;

		if (num >= 0 && num <= 10 && cin.fail()==0)
		{
			cout << "输入正常,输入值为:" << num << endl;
			break;
		}

		//重置标志位,清空缓冲区
		cin.clear();
		cin.sync();
		cin.ignore(); //VS2013以上版本加入这句
		//如果标志位为0,如果标志位为1,缓冲区异常
		//cin.fail()
		cout << "输入有误,请重新输入" << endl;
	}

}

标准输出流

cout.put() //向缓冲区写字符

cout.write() //从buffer中写num个字节到当前输出流中。

格式化输出

  1. 通过 流成员函数 格式化输出

     int number = 99;
    
     cout.width(20); //指定宽度为20
    
     cout.fill('*'); //填充
    
     cout.setf(ios::left);  //左对齐
    
     cout.unsetf(ios::dec); //卸载十进制
    
     cout.setf(ios::hex);  //安装十六进制
    
     cout.setf(ios::showbase);  //显示基数
    
     cout.unsetf(ios::hex);  //卸载十六进制
    
     cout.setf(ios::oct);   //安装八进制
    
     cout << number << endl;
    
  2. 通过控制符  格式化输出

     引入头文件  #include< iomanip>
    
     int number = 99;
    
     cout << setw(20)     //设置宽度
    
         << setfill('~')  //设置填充
    
         << setiosflags(ios::showbase)  //显示基数
    
         << setiosflags(ios::left)  //设置左对齐
    
         << hex   //显示十六进制
    
         << number
    
         << endl;
    

示例

//cout.put() 向缓冲区写字符
//cout.write() 从buffer中写num个字节到当前输出流中

void test01() 
{
	//cout.put('h').put('e');
	
	char buf[] = "hello world";
	cout.write(buf, strlen(buf));

}

//1. 通过流成员函数 格式化输出
void test02() 
{
	int num = 99;
	cout.width(20);//指定宽度为20
	cout.fill('*'); //填充
	cout.setf(ios::left);//输出的内容左对齐
	cout.unsetf(ios::dec);//卸载十进制
	cout.setf(ios::hex);//安装十六进制
	cout.setf(ios::showbase);//显示基数
	cout.unsetf(ios::hex);//卸载十六进制
	cout.setf(ios::oct);//安装八进制

	cout << num << endl;
}

//2. 使用控制符 格式化输出 需要包含头文件 <iomanip>
void test03() 
{
	int num = 99;
	cout << setw(20) <<setfill('^') <<setiosflags(ios::showbase) <<setiosflags(ios::left) <<hex<< num << endl;
}

文件读写

头文件  #inlcude < fstream>

写文件 

ofstream  ofs (文件路径,打开方式  ios::out )

判断文件是否打开成功  ofs.is_open

ofs << “…”

关闭文件  ofs.close();

读文件

ifstream  ifs(文件路径,打开方式  ios::in)

判断文件是否打开成功  ofs.is_open

利用4种方式 对文件进行读取

关闭文件  ifs.close();

示例

void test01()
{
	//写文件 o --输出
	ofstream ofs("./test.txt", ios::out | ios::trunc);

	//ofs.open("./test.ext", ios::out | ios::trunc);  设置打开方式 以及路径
	if (!ofs.is_open())
	{
		cout << "文件打开失败" << endl;
		return;
	}
	ofs << "姓名:孙悟空" << endl;
	ofs << "年龄:999" << endl;
	ofs << "性别:男" << endl;

	//关闭文件
	ofs.close();

}
void test02() 
{
	//读文件 i——输入
	ifstream ifs;
	ifs.open("./test.txt", ios::in);

	if (!ifs.is_open()) 
	{
		cout << "文件打开失败" << endl;
		return;
	}
	//第一种方式 读  推荐
	//char buf[1024] = { 0 };
	//while (ifs >> buf) 
	//{
	//	cout << buf << endl;
	//}

	//第二种方式 读
	//char buf[1024] = { 0 };
	//while (ifs.getline(buf, sizeof(buf))) 
	//{
	//	cout << buf << endl;
	//}
	//第三种方式
	//string buf;
	//while (getline(ifs,buf)) //全局函数 getline
	//{
	//	cout << buf << endl;
	//}
	//第四种方式 不推荐
	char c;
	while ((c = ifs.get()) != EOF) 
	{
		cout << c;
	}
	ifs.close();
}