类型转换
- 知识回顾
- static_cast
- const_cast
- reinterpret_cast
- 类型转换
- dynamic_cast
- 动态转换和静态转换区别
- 动态转换的使用
知识回顾
static_cast
静态转换应用范围:
- 基本数据类型的转换,但不能实现基本数据类型指针的转化,但是可以将无类型转成其他类型
- 也可以将整型转换成枚举类型
- 可以将左值转成右值
- 可以实现上行转换,也就是将派生类指针转换成基类指针
class PtrInt
{
private:
int* pval; // new .heap ; stack . data;
public:
PtrInt(int* p = nullptr) :pval(p)
{
}
~PtrInt()
{
delete pval;
pval = nullptr;
}
PtrInt(const PtrInt& it) :pval(new int(0))
{
*pval = *it.pval;
//*pval = it.*pval;
}
PtrInt& operator=(const PtrInt& it)
{
if (this != &it)
{
delete pval;
pval = new int(*it.pval);
//pval = it.pval;
}
return *this;
}
PtrInt (PtrInt&& it) :pval(it.pval)
{
it.pval = nullptr;
}
PtrInt& operator=(PtrInt&& it)
{
if (this != &it)
{
delete[]pval;
pval = it.pval;
it.pval = nullptr;
}
return *this;
}
};
PtrInt func(int x) {
PtrInt tmp(new int(x));
return tmp;
}
int main() {
PtrInt a(new int(10));
a = func(100);
return 0;
}
首先创建a对象,然后进入func函数,在函数中创建了一个tmp对象,我们知道函数内部创建的局部对象会在函数结束时进行释放。所以我们创建的tmp对象最终会被释放掉,所以呢在return结束前会生成一个将亡值,将亡值的指针指向我们tmp对象的堆内存,然后释放tmp对象时就不会释放堆内存,最后func函数返回的将亡值调用移动赋值来对a对象进行赋值。如果在tmp对象前加上const,编译器就不会移动他的资源,如果加上static关键字,其就会在数据区,不会进行移动资源。
const_cast
去常性转换,强制转换的目标类型必须是指针或引用。
int main() {
const int a = 10;
int& b = const_cast<int&>(a);
b = 100;
cout << "a:" << a << endl;
cout << "b:" << b << endl;
}
思考上面代码输出结果是什么?输出结果a为10,b为100,有的同学可能回想b是a的引用,b的值改成了100,为什么a的值不是100呢?事实上,在运行时a的值的确被赋值成了100,也就是说a的值在运行时同b一样都是一百,但是常变量a在编译时就会进行替换,将a的值替换成10,所以输出为10。
reinterpret_cast
重新解释,其类似于强转,但是只适应于指针或引用的转换,不适用于值的转换。
int main() {
int a = 0x61626364;
char* cp = reinterpret_cast<char*>(&a);
cout << *cp << endl;
cout << *(cp+1) << endl;
cout << *(cp + 2) << endl;
cout << *(cp + 3) << endl;
return 0;
}
同样观察上面代码思考其输出结果?
其输出结果是的d,c,b,a,0x61626364,我们的计算机是小端存储,所以地址存放低位,高地址存放高位,cp为低地址,存放16进制的64,而16进制的64转换成10进制就是100,正好是字母d的ASK码值,然后依次移动1字节,也就是63,62,61对应cba,对应输出结果是dcba。
类型转换
dynamic_cast
用法:dynamic_cast<type_name>(expression)
动态转换允许运行时刻进行类型转换,从而使程序能够在一个类层次结构中安全的转换类型,把基类指针转换成派生类指针,或者把指向基类的左值转化成派生类的引用。
必须是公有继承,基类要有虚函数
特点:
- C++其他三种类型转换均在编译时,而dynamic_cast在运行时执行的类型转换。
- 如果对指针类型dynamic_cast转换失败,返回结果为nullptr。
- 如果针对引用类型的dynamic_cast失败,则会抛出异常。
- 在类的层次结构进行上行转换时,dynamic_cast和static_cast的效果是一样的。
- 进行下行转换时,dynamic_cast具有类型检查的功能,比static_cast更安全。
class Object
{
private:
int value;
public:
Object(int x = 0) :value(x) {}
virtual void func() { cout << "value: " << value << endl; }
};
class Base : public Object
{
private:
int num;
public:
Base(int x = 0) :Object(x + 10), num(x) {}
virtual void func() { cout << "num: " << num << endl; }
};
int main() {
Base base(100);
Object obj(10);
Object* obja = (Object*)&base;
Object* objb = static_cast<Object*>(&base);
Object* objc = dynamic_cast<Object*>(&base);
Object& oba = (Object&)base;
Object& obb = static_cast<Object&>(base);
Object& obc = dynamic_cast<Object&>(base);
Object oca = (Object)base;
Object ocb = static_cast<Object>(base);
Object occ = dynamic_cast<Object>(base);//err
Object&& oc = dynamic_cast<Object&&>(base);
}
运行上面一段代码就可以应正我们的结论,在指针和引用转换的前提下进行上行转换,动态转换和静态转换都是一样的,但是在值转换的时候,动态转换就会报错,但可以将派生类对象转换为基类右值引用。(前提是继承关系,有虚函数)这也就是编译时转换。
int main() {
Base base(10);
void* vp = dynamic_cast<void*>(&base);
Base* bp = dynamic_cast<Base*>(&vp);
return 0;
}
通过这段代码我们可以发现动态转化可以将有类型指着转换成无类型指针,不可以将无类型指针转换成有类型指针。
动态转换和静态转换区别
仍然是上面的类和函数,执行下面一段代码
int main() {
Base base(10);
Object obj(20);
Object* op = &obj;
//Object* op=&base;
Base* bp = dynamic_cast<Base*>(op);
bp->func();
Base* dp = static_cast<Base*>(op);
dp->func();
}
我们可以分析上面代码,当基类指针指向派生类对象时,然后将其强转成派生类指针,然后调用func虚函数很显然其调用的是派生类的虚函数,因为其查找虚表时的虚表指针指向Base的虚表。静态转换和静态转换在此时结果是一样的,但是将op指向obj对象时,将op强转成Base指针类型就会出现强转错误导致bp指针为nullptr,不能执行func函数,静态转换可以运行并且输出基类的func函数。
这是什么原因呢?是这样的,因为静态转换是在编译时就确定了绑定关系,所以dp不管指针指向基类对象还是派生类对象都会调用op指向的的对象的虚表来查找调用函数。而动态转换会根据虚表指针来动态查找虚表的RTTI,找其基类类型,如果其基类没有找到也就是说不能把基类指针赋值给派生类指针,就会返回nullptr。所以也就不能调用func函数。
动态转换通过虚表指针查看虚表,通过RTTI(运行时识别,存在一个指针)来查找信息,信息中存在其类型名,基类指针,派生类指针。
我们使用动态转换在基类的虚表中查找其Base类型是查不到的所以返回nullptr,而把派生类指针转换成基类指针就可以通过基类指针找到Obj类型名从而实现动态转换。
动态转换的使用
class Goods
{
float _weight; // 重量
public:
Goods(float wt) : _weight(wt) {}
virtual ~Goods() { std::cout << "~Trash()" << std::endl; }
float GetWeight() const { return _weight; }
virtual float GetPrice() const = 0; // 价格
};
// 铜
class Copper : public Goods
{
static float price;
public:
Copper(float wt) : Goods(wt) {}
float GetPrice() const { return price; }
static void SetPrice(float newprice) {
price = newprice;
}
};
float Copper::price = 2.80;
// 纸张
class Paper : public Goods
{
static float price;
public:
Paper(float wt) : Goods(wt) {}
float GetPrice() const { return price; }
static void SetPrice(float newprice) {
price = newprice;
}
};
float Paper::price = 0.20;
// 玻璃
class Glass : public Goods
{
static float price;
public:
Glass(float wt) : Goods(wt) {}
float GetPrice() const { return price; }
static void SetPrice(float newprice) {
price = newprice;
}
};
float Glass::price = 0.32;
template<class Container >
void sumPrice(Container& bin)
{
float total = 0;
for (auto p : bin)
{
//cout << typeid(x).name() << endl;
total += p->GetPrice() * p->GetWeight();
cout << "wight of : " << typeid(*p).name() << " = " << p->GetWeight() << endl;
}
cout << "Total price = " << total << endl;
}
int main()
{
srand(time(0)); // Seed the random number generator
vector<Goods*> bin;
int n = rand() % 100; // 0 99
for (int i = 0; i < n; i++)
{
switch (rand() % 3)
{
case 0:
bin.push_back(new Copper((rand() % 1000) / 10.0));
break;
case 1:
bin.push_back(new Paper((rand() % 1000) / 10.0));
break;
case 2:
bin.push_back(new Glass((rand() % 1000) / 10.0));
break;
}
}
// Note: bins hold exact type of object, not base type:
vector<Glass*> glassBin;
vector<Paper*> paperBin;
vector<Copper*> coppBin;
// Sort the Trash
for(auto xp:bin){
Glass* gp = dynamic_cast<Glass*>(xp);
Paper* pp = dynamic_cast<Paper*>(xp);
Copper* cp = dynamic_cast<Copper*>(xp);
if (gp != nullptr) {
glassBin.push_back(gp);
}
else if (pp != nullptr) {
paperBin.push_back(pp);
}
else if (cp != nullptr) {
coppBin.push_back(cp);
}
}
sumPrice(coppBin);
sumPrice(paperBin);
sumPrice(glassBin);
sumPrice(bin);
for (auto& x : bin)
{
delete x;
x = nullptr;
}
}
看上面代码,可以看出设置了一个抽象类,还有三个派生类,铜,玻璃,纸张。最后存在一个模板类型的函数来计算总价格。
主函数中设置了随机数材料总数,其三类材料也用了随机值,有一个Goods指针类型的容器,每创建一个类就将其放入容器中,然后设置三个材料类型的指针容器,将不同材料放入不同容器,最后计算价格。这段代码重点在于
for(auto xp:bin){
Glass* gp = dynamic_cast<Glass*>(xp);
Paper* pp = dynamic_cast<Paper*>(xp);
Copper* cp = dynamic_cast<Copper*>(xp);
if (gp != nullptr) {
glassBin.push_back(gp);
}
else if (pp != nullptr) {
paperBin.push_back(pp);
}
else if (cp != nullptr) {
coppBin.push_back(cp);
}
}
很显然我们用了动态转换来查找虚表判断其类型,如果类型相匹配便返回派生类的指针类型,不匹配则返回nullptr,通过判断返回值来判断其类型放入不同容器进行材料分类。