二十三.运行时类型信息(了解)
1.typeid操作符
#include <typeinfo>
typeid(类型/对象)//返回typeinfo对象,用于描述类型信息
①typeinfo类中包含了name()成员函数,可以获取字符串形式的类型信息。
②typeinfo类支持比较操作符重载,可以直接进行类型之间比较;如果类型之间存在多态的继承关系,typeid还可以利用多态的语法特性,确定实际的对象类型。
//运行环境:Visual Studio 2019
#include <iostream>
#include <typeinfo>
using namespace std;
//具有多态继承关系的父子类
class A{virtual void foo(void){} };
class B:public A{void foo(void){} };
class C:public A{void foo(void){} };
void func(A& ra)
{
cout << typeid(ra).name() << endl;
if (typeid(ra) == typeid(B))
{
cout << "针对B子类处理" << endl;
}
else if (typeid(ra) == typeid(C))
{
cout << "针对C子类处理" << endl;
}
}
int main(void)
{
int i = 0;
cout << typeid(int).name() << endl; //int
cout << typeid(i).name() << endl; //int
int* a1[5]; //指针数组
int(*a2)[5]; //数组指针
cout << typeid(a1).name() << endl; //int* __ptr64[5]
cout << typeid(a2).name() << endl; //int(*__ptr64)[5]
//函数指针数组
cout << typeid(int (*[5])(float, char)).name() << endl; //int (__cdecl*[5])(float,char)
B b;
func(b);
C c;
func(c);
return 0;
}
2.dynamic_cast操作符
2.1 static_cast的问题和dynamic_cast
问题:
eg.
#include <iostream>
using namespace std;
class A { virtual void foo(void) {} };
class B :public A { void foo(void) {} };
class C :public A { void foo(void) {} };
int main(void)
{
B b;
A* pa = &b; //向上类型
B* pb = static_cast<B*>(pa);//合理
C* pc = static_cast<C*>(pa);//不合理
//pa是A*,是基类,到B*转换是向上造型,到C*转换也是向上造型;都是基类到子类的转换
//但C* pc = static_cast<C*>(pa);不合理,因为pa指向的是B对象,若转成B*能对应上;
//但C*转换的话,让pc指向B对象,即把B子类的当成C类型转换,这样肯定不合理的,但编译器不会报错,且三个指针地址相同
cout << "pa=" << pa << endl;
cout << "pb=" << pb << endl;
cout << "pc=" << pc << endl;
//输出:pa=pb=pc
return 0;
}
解决方法:将static_cast替换为dynamic_cast
eg.
#include <iostream>
using namespace std;
class A { virtual void foo(void) {} };
class B :public A { void foo(void) {} };
class C :public A { void foo(void) {} };
int main(void)
{
B b;
A* pa = &b; //向上类型
//B* pb = static_cast<B*>(pa);//合理
//C* pc = static_cast<C*>(pa);//不合理
B* pb = dynamic_cast<B*>(pa);//合理
C* pc = dynamic_cast<C*>(pa);//不合理
cout << "pa=" << pa << endl;
cout << "pb=" << pb << endl;
cout << "pc=" << pc << endl;//0,表示失败
A& ra = b;
C& rc = dynamic_cast<C&>(ra);//不合理,error
return 0;
}
2.2 语法
目标变量 = dynamic_cast<目标类型>(源类型变量);
2.3 适用场景
主要用于具有多态继承关系的父子类指针或引用之间的显式类型转换。
(注:在转换过程中,dynamic_casdt会检查目标对象和期望转换的类型是否一致,如果一致则转换成功,否则失败;如果转换是指针,返回NULL表示失败,如果转换是引用,抛出"bad_cast"异常表示失败;)
二十四.异常机制(Exception)
1.软件开发中常见的错误
①语法错误
②逻辑错误
③功能错误
④设计缺陷
⑤需求不符
⑥环境异常
⑦操作不当
2.传统C中的错误处理(了解)
2.1 通过返回值表示错误
优点:函数调用路径中所有栈对象都可以被右花括号正确的析构,不会内存泄漏。
缺点:错误处理流程比较复杂,需要逐层返回值判断,代码臃肿。
eg.
#include <iostream>
#include <cstdio>
#pragma warning( disable : 4996 )
using namespace std;
class A {
public:
A(void) { cout << "A的构造函数" << endl; }
~A(void) { cout << "A的构造析构" << endl; }
};
int func3() {
A a;
FILE* file = fopen("xx.txt", "r");
if (file == NULL) {
cout << "文件打开失败" << endl;
return -1;
}
cout << "func3正常执行" << endl;
fclose(file);
return 0;
}
int func2() {
A a;
if (func3() == -1) {
return -1;
}
cout << "func2正常执行" << endl;
return 0;
}
int func1() {
A a;
if (func2() == -1) {
return -1;
}
cout << "func1正常执行" << endl;
return 0;
}
int main() {
if (func1() == -1) {
return -1;
}
cout << "main正常执行" << endl;
return 0;
}
2.2 通过远程跳转处理错误
优点:不需要逐层返回值判断,一步到位处理错误处理,代码精炼。
缺点:函数调用路径中的栈对象失去被析构机会,有内存泄漏的风险。
eg.
#pragma warning( disable : 4996 )
#include <iostream>
#include <cstdio>
#include <csetjmp>//远程跳转
jmp_buf g_env;
using namespace std;
class A {
public:
A(void) { cout << "A的构造函数" << endl; }
~A(void) { cout << "A的构造析构" << endl; }
};
int func3() {
A a;
FILE* file = fopen("xx.txt", "r");
if (file == NULL) {
longjmp(g_env, -1);
}
cout << "func3正常执行" << endl;
fclose(file);
return 0;
}
int func2() {
A a;
func3();
cout << "func2正常执行" << endl;
return 0;
}
int func1() {
A a;
func2();
cout << "func1正常执行" << endl;
return 0;
}
int main() {
if (setjmp(g_env) == -1) {
cout << "文件打开失败" << endl;
return -1;
}
func1();
cout << "main正常执行" << endl;
return 0;
}
3.C++异常机制
3.1 异常抛出
throw 异常对象;
(注:异常对象可以是基本类型也可以是类类型对象。)
3.2 异常检测和捕获
try{
可能引发异常语句;
...
}
catch(异常类型1){
针对异常类型1的处理
}
catch(异常类型2){
针对异常类型2的处理
}
...
eg.
#pragma warning( disable : 4996 )
#include <iostream>
#include <cstdio>
using namespace std;
class FileError{
public:
FileError(const string& file,int line)
:m_file(file),m_line(line){
cout << "出错位置:" << m_file << "," << m_line << endl;
}
private:
string m_file;
int m_line;
};
class A{
public:
A(void){ cout << "A的构造函数" << endl; }
~A(void){ cout << "A的构造析构" << endl; }
};
int func3(){
A a;
FILE* file = fopen("xx.txt","r");
if(file==NULL){
//预定义的宏:
//__FILE__:当前文件名字的字符串
//__LINE__:当前位置行号(int)
throw FileError(__FILE__,__LINE__);
//throw -1;//抛出异常
}
cout << "func3正常执行" << endl;
fclose(file);
return 0;
}
int func2(){
A a;
func3();
cout << "func2正常执行" << endl;
return 0;
}
int func1(){
A a;
func2();
cout << "func1正常执行" << endl;
return 0;
}
int main(){
try{
func1();
cout << "main正常执行" << endl;
}
catch(int ex){
if(ex == -1){
cout << "文件打开失败" << endl;
return - 1;
}
}
catch(FileError& ex){
cout << "File Open Error" << endl;
return -1;
}
return 0;
}
(注:catch子句根据异常对象类型自上而下顺序匹配,而不是最优匹配,
因此要把对子类类型异常捕获语句要写在前面,否则将会被基类类型的异常捕获语句提前的截获。)
eg.
#include <iostream>
using namespace std;
class ErrorA{};
class ErrorB:public ErrorA{};
void func(void){
throw ErrorA();
//throw ErrorB();
}
int main(void){
try{
func();
}
//对子类类型的捕获语句要写在前面
catch(ErrorB& ex){
cout << "针对子类的异常处理" << endl;
}
catch(ErrorA& ex){
cout << "针对基类的异常处理" << endl;
}
return 0;
}
4.函数的异常说明
4.1 定义
函数的异常说明用于告诉函数调用者,在该函数执行期间可能会抛出的异常类型。
返回类型 函数名(形参表) throw(异常类型表) {函数体}
eg:
//说明func函数执行时可能会抛出FileError或int类型异常
void func(void) throw(FileError,int){...}
4.2 函数的异常说明是一种承诺
函数的异常说明是一种承诺,表示该函数所抛出的异常类型不会超出说明范围。但如果抛出异常说明以外的其它类型,则无法被该函数的调用者正常捕获,而是继续向上抛出,最终被系统捕获,导致进程终止。
#include <iostream>
using namespace std;
class FileError {};
class MemoryError {};
//函数声明
void func(void) throw(MemoryError, FileError);
//函数定义
//说明该函数可能会抛出FileError或MemoryError异常
void func(void) throw(FileError, MemoryError) {
//throw FileError();
throw MemoryError();
}
int main(void) {
try {
func();
}
catch (FileError& ex) {
cout << "针对文件的错误处理" << endl;
}
catch (MemoryError& ex) {
cout << "针对内存的错误处理" << endl;
}
return 0;
}
4.3 函数异常说明的两种极端形式
--》不写函数异常说明,表示可以抛出任何异常
--》空异常说明,“throw()”,表示不会抛出任何异常
4.4 规定
如果函数的声明和定义分开写,要保证异常说明类型一致,但是顺序无所谓。
//补充虚函数覆盖的条件
4.5 虚函数的异常说明
如果基类中的虚函数带有异常说明,那么该函数在子类中的覆盖版本不能说明比基类抛出更多异常,否则将会因为“放松throw限定”而导致编译失败。
eg.
#include <iostream>
using namespace std;
class FileError{};
class MemoryError{};
class Base{
public:
virtual void func(void)
throw(FileError,MemoryError){}
virtual ~Base(void) throw() {}
};
class Derived:public Base{
public:
void func(void)
throw(FileError){}
~Derived(void) throw() {}
string m_s;
};
int main(void){
return 0;
}
5.标准异常类(exception)
class exception{
public:
exception() throw() { }
virtual ~exception() throw() ;
//返回一个c风格的字符串描述当前错误的一般原因。
virtual const char* what() const throw();
};
eg.
#include <iostream>
#include <exception>
using namespace std;
class FileError:public exception{
public:
const char* what(void) const throw(){
cout << "针对文件错误处理" << endl;
return "FileError";
}
};
class MermoryError:public exception{
public:
const char* what(void) const throw(){
cout << "针对内存错误处理" << endl;
return "MermoryError";
}
};
void func(void){
//throw FileError();
throw MermoryError();
}
int main(void){
try{
func();
}
catch(exception& ex){
cout << ex.what() << endl;
}
return 0;
}
6.构造函数和析构函数中的异常
①构造函数可以抛出异常,但是对象将会被不完整构造,这样的对象其析构函数不再会被自动调用执行,因此在构造函数抛出异常之前,需要手动释放之前分配的动态资源。
问题:构造函数抛出异常以后,析构函数未被执行,动态资源内存未被释放,易造成内存泄漏。
#include <iostream>
using namespace std;
class A {
public:
A(void) {
cout << "动态资源分配" << endl;
if ("error") {
throw - 1;
}
cout << "构造函数正常结束" << endl;
}
~A(void) {
cout << "动态资源释放" << endl;
}
};
int main(void) {
try {
A a;
}
catch (int ex) {
cout << "捕获到异常:" << ex << endl;
}
return 0;
}
//输出:
//动态资源分配
//捕获到异常:-1
解决方法:在抛出异常之前在构造函数中进行动态资源的释放。
#include <iostream>
using namespace std;
class A{
public:
A(void){
cout << "动态资源分配" << endl;
if("error"){
cout << "动态资源释放" << endl;
throw -1;
}
cout << "构造函数正常结束" << endl;
}
~A(void){
cout << "动态资源释放" << endl;
}
};
int main(void){
try{
A a;
}
catch(int ex){
cout << "捕获到异常:" << ex << endl;
}
return 0;
}
//输出:
//动态资源分配
//动态资源释放
//捕获到异常:-1
②析构函数不要抛出异常!(语法上可以,但是不要这么做)
eg.
#include <iostream>
using namespace std;
class A{
public:
void func(void){
throw -2;
}
~A(void){
throw -1;
}
};
int main(void){
try{
A a;
a.func();//throw -2
}//throw -1
catch(int ex){
cout << "捕获到异常:" << ex << endl;
}
return 0;
}
二十五.I/O流(了解)
1.主要I/O流类
2.格式化I/O
2.1 格式化函数(成员函数)
eg.
cout << 10/3.0 << endl;//3.33333
cout.precision(10);
cout << 10/3.0 << endl;//3.333333333
2.2 流控制符(全局函数)
eg.
#include <iomanip>
cout << 10/3.0 << endl;//3.33333
cout << setprecision(10) <<10/3.0 << endl;//3.333333333
eg.
#include <iostream>
#include <iomanip>
using namespace std;
int main(void) {
#if 0
//格式化函数:
cout << "[";
cout.width(10);//设置域宽
cout.fill('$');//设置空白位置用'$'填充
cout.setf(ios::showpos);//显示正整数"+"
cout.setf(ios::internal);//数据靠右,符号靠左
cout << 12345 << "]" << endl;
#endif
//流控制符
cout << "[" << setw(10) << setfill('$') << showpos << internal << 12345 << "]" << endl;
//输出:[+$$$$12345]
//hex:十六进制
//left:左对齐
//showbase:显示十六进制的进制标识0x
cout << "[" << setw(8) << hex << left << showbase << 100 << "]" << endl;
//输出:[0x64$$$$]
return 0;
}
3.字符串流
#include <sstream>
istringstream //面向字符串读操作,类似sscanf()
ostringstream //面向字符串写操作,类似sprintf()
eg.
#include <iostream>
#include <sstream>
using namespace std;
int main(void) {
//读操作
istringstream iss;
iss.str("123 4.56 hello");
int i = 0;
double d = 0.0;
char s[10] = { 0 };
//char buf[] = {"123 4.56 hello"}
//sscanf(buf,"%d %lf %s",&i,&d,s);
iss >> i >> d >> s;
cout << "读到结果:" << i << "," << d << "," << s << endl;
//写操作
ostringstream oss;
int i2 = 321;
double d2 = 6.54;
char s2[] = "world";
//char buf[1024] = {0};
//sprintf(buf,"%d %lf %s",i2,d2,s2);
oss << i2 << ' ' << d2 << ' ' << s2;
cout << "写操作结果:" << oss.str() << endl;
return 0;
}
4.文件流
#include <fstream>
ifstream//面向文件读操作,类似fscanf()
ofstream//面向文件写操作,类似fprintf()
eg.
#include <iostream>
#include <fstream>
using namespace std;
int main(void){
//写文件操作
ofstream ofs("file.txt");
ofs << 123 << " " << 4.56 << " " << "hello" << endl;
ofs.close();//关闭
//读文件操作
ifstream ifs("file.txt");
int i=0;
double d=0.0;
string str;
ifs >> i >> d >> str;
cout << "读到内容:" << i << "," << d << "," << str << endl;
ifs.close();
return 0;
}
5.二进制I/O
//二进制读操作,类似fread
istream& istream::read(char* buffer,streamsize num);
//二进制写操作,类似fwrite
ostream& ostream::write(const char* buffer,size_t num);
eg.
#include <iostream>
#include <fstream>
using namespace std;
int main(void) {
ofstream ofs("last.txt");
char wbuf[] = "end!";
ofs.write(wbuf, sizeof(wbuf));
ofs.close();
ifstream ifs("last.txt");
char rbuf[100] = { 0 };
ifs.read(rbuf, sizeof(rbuf));
cout << "读到数据:" << rbuf << endl;
ifs.close();
return 0;
}