1、类型转换
通过改变一个变量的类型为别的类型,从而改变该变量的表达方式。
c++提供四种类型转换操作符
基本语法:int a = 100; char c = static_cast<char>(a);
例程
#include <iostream>
#include <cstring>
#include <memory>
using namespace std;
class animal{};
class people
{
public:
int a;
};
class student:public people
{
public:
int b;
};
int main(void)
{
//static_cast
//基础类型转换,允许
int a1=99;
char c1 = static_cast<char>(a1);
cout<<"c1="<<c1<<endl;
//基础类型指针, 不允许转换
//int *np = NULL;
//char *cp = static_cast<char *>(np);
//对象指针,不允许转换
//people *p = NULL;
//animal *st = static_cast<animal *>(p);
//转换具有继承关系的对象指针, 允许
people *p = NULL;
student *st = static_cast<student *>(p);
//父类转子类,子类转父类。都允许转换
student *st1 = NULL;
people *p1 = static_cast<people *>(st1);
//对象引用,同对象指针
people p2;
people &rep2 = p2;
student &st2 = static_cast<student &>(rep2);
//------------------------------------------------
//dynamic_cast做类型安全检查,大空间转小空间是允许的。子类转父类
student *st3 = NULL;
people *p3 = dynamic_cast<people *>(st3);
//但是小空间转大空间的时候(父类转子类),指针访问成员就会不安全,也就不被允许转换。
//people *p4 = NULL;
//student *st4 = dynamic_cast<student *>(p4);
//------------------------------------------------
//const_cast一般用于取消const修饰的指针、引用、或对象指针、对象引用。或者对非const的加上const修饰。
int a = 10;
const int &b = a; //此时已经不允许 修改b了 b=20是不被允许的
int &c = const_cast<int &>(b);
c = 20;
cout<<"a="<<a<<endl;
cout<<"b="<<b<<endl;
cout<<"c="<<c<<endl;//打印出来都是一样的
int aa = 100;
int *bb = &aa;
const int *cc = const_cast<const int *>(bb);
//*cc = 200; 已经不被允许
//------------------------------------------------
return 0;
}
(1)、static_cast 用于内置的类型转换(int、char),或者是有继承关系的对象指针、对象引用的转换。
(2)、dynamic_cast 只能转换有继承关系的对象指针、或引用。但是dynamic_cast会做类型安全检查。
(3)、const_cast一般用于取消const修饰的指针、引用、或对象指针、对象引用。或者对非const的加上const修饰。
(4)、reintrpret_cast 就是c语言的强转,要注意自己进行安全检查
结论:1、程序员必须清楚的知道要转换的变量,转换前是什么类型,转换后是什么类型,以及转换后有什么后果
2、一般情况下,不建议类型转换,避免进行类型转换。
2、c++异常机制
我们在C语言中处理异常,一般都是通过函数返回值来进行判断并处理。且C语言的异常处理逻辑完全交由程序员管理,必须逐级处理。如果func1中调用func2,又func2调用func3,如果func3返回的异常,func2没有处理,则在func1中可能发生不可预期的问题。
c++中提供了异常处理机制。当出现异常时,由程序员抛出异常,可以跳级捕获,并且一旦发生捕获异常,就必须处理异常。且异常抛出的类型没有做限定,可以是一个int、char等基础数据类型,也可以是一个类的对象、或者是结构体,并且异常本身也是一个类,拥有自己的成员,可以传递足够的信息。
下面看一个简单的案例,介绍了异常的语法
#include <iostream>
#include <cstring>
#include <memory>
using namespace std;
int divide(int x, int y)
{
if (0 == y)
{
throw y;//抛出异常
}
return x/y;
}
void test()
{
//尝试捕获异常
try
{
divide(10, 0);
}
//异常处理
catch(int err)
{
//异常是根据类型进行匹配的,所以形参要加上int
cout<<"错误:除数为"<<err<<endl;
}
}
void CallDivide(int x, int y)
{
divide(x, y);
}
//异常处理可以不用逐级响应,可以跳级处理。
void test1()
{
try
{
CallDivide(10, 0);
//异常应该是出现在CallDivide中,但是c++会跳级、跨函数处理,且一旦有异常出现,就必须会处理
}
//异常处理
catch(int err)
{
//异常是根据类型进行匹配的,所以形参要加上int
cout<<"错误:除数为"<<err<<endl;
}
}
int main(void)
{
test();
test1();
return 0;
}
throw关键字抛出一个异常。
try{ do func... },执行方法,并尝试捕获异常
catch(数据类型){ 处理异常,do something ...},关键字根据数据类型匹配异常,并进行处理。catch抓住的数据,就是throw抛出的数据。
异常是跨函数的,异常必须被处理,如果不处理,程序会崩溃。有些编译器甚至不允许编译通过。
3、栈解旋
概念意义:在调用func中,出现了异常。异常被抛出且处理掉的时候,func中的局部对象、变量等会被释放。相当于return之前释放栈空间。
4、异常接口声明
(1)为了加强程序的可读性,可以在函数声明中列出可能抛出异常的所有类型。比如函数声明void func()throw(A,B);这个函数func只能抛出类型A,B及其子类型的异常
(2)如果函数声明中没有包含异常接口声明,则此函数可以抛出任何类型的异常
(3)一个不抛出任何类型异常的函数可以声明为void func()throw();
(4)如果一个函数抛出了他的异常接口声明锁不允许抛出的异常,unexcepted函数会被调用,该函数默认行为调用terminate函数中断程序。
注意:C++11已经弃用动态异常规范。
下面是一个抛出类对象的案例,注意分析对象的生命周期
#include <iostream>
#include <cstring>
#include <memory>
using namespace std;
class myException
{
public:
myException(const char *str)
{
cout<<"myException(char *str)..."<<endl;
this->Err = new char[strlen(str)+1];
strcpy(this->Err, str);
}
myException(const myException &another)
{
cout<<"myException(const myException &another)..."<<endl;
if (NULL == another.Err)
{
this->Err = new char[0+1];
strcpy(this->Err, "");
}
else
{
this->Err = new char[strlen(another.Err)+1];
strcpy(this->Err, another.Err);
}
}
myException & operator=(const myException &another)
{
cout<<"myException & operator=(const myException &another)..."<<endl;
if (this->Err == another.Err)
{
return *this;
}
if (this->Err != NULL)
{
delete[] this->Err;
this->Err = NULL;
}
this->Err = new char[strlen(another.Err)+1];
strcpy(this->Err, another.Err);
return *this;
}
~myException()
{
cout<<"~myException()..."<<endl;
if (this->Err != NULL)
{
delete[] this->Err;
this->Err = NULL;
}
}
void showErr()
{
cout<<this->Err<<endl;
}
public:
char *Err;
};
void func()
{
myException p("异常!!!");
throw p;
//throw "exceptions!!!";
}
int main(void)
{
try
{
func();
}
catch(char const *str)
{
cout<<str<<endl;
}
//如果使用指针 或者引用来接异常的时候,要注意栈变量的生命周期
catch(myException err)
{
err.showErr();
}
return 0;
}
执行结果
5、c++的标准异常类
(1)在实际使用中,我们往往自己定义个一个异常类,继承自标准异常类。这样有更好的使用便捷性。
(2) 在继承标准异常类时,应当重载父类的what函数和虚析构函数
6、案例:自定义异常类
#include <iostream>
#include <cstring>
#include <memory>
#include <stdexcept>
using namespace std;
class myOutOfRange : public exception
{
public:
myOutOfRange(const char *str)
{
cout<<"myOutOfRange(const char *str)..."<<endl;
this->err = new char[strlen(str)+1];
strcpy(this->err, str);
}
virtual const char* what() const noexcept
{
return err;
}
virtual ~myOutOfRange()
{
/*if (this->err != NULL)
{
delete[] this->err;
}编译器差异,实际上vs上是需要自己手动释放的*/
}
public:
char *err;
};
class people
{
public:
people()
{
m_age = 0;
}
void setAge(int age)
{
if (age < 0 || age > 200)
{
throw myOutOfRange("年龄应该在0-200之间");
}
this->m_age = age;
}
public:
int m_age;
};
void func()
{
people p;
try
{
p.setAge(1000);
}
catch(exception err)
{
cout<<err.what()<<endl;
}
}
int main(void)
{
func();
return 0;
}
执行结果