【C++】四种类型转换 | C++异常处理机制 | C++11新特性之右值引用和移动构造

一、C++中的四种类型转换

1.1 静态类型转换

静态类型转换:static_cast<Type>(exp)
静态类型转换的两种应用场景:
1.C++中内置类型的转换。
2.C++中的有继承关系存在的场景下的类型强转。

1.1.1 C++中内置类型的转换

与C风格强转类似,但不能用于内置类型间的指针转换,因为每一种类型的指针代表他的操控的空间是不一样的。

代码示例:

#include <iostream>
using namespace std;

int main()
{
    double a = 3.1415926;
    cout << a << endl;
    cout << (int)a << endl;
    cout << static_cast<int>(a) << endl;
    cout << "-------------" <<endl;
    double *p=&a;
    cout << *p << endl;//3.14159
    cout << *((int*)p) << endl;//1293080650出错截断了,只取了前四位。
    //cout << static_cast<int>(p) << endl;报错
    return 0;
}

结果展示:
在这里插入图片描述
总结:
static_cast<int*>(pa)这种报错机制就是一种安全的体现,不允许对内置的指针类型进行转换。所以说static_cast比C风格强转要安全。

1.1.2 C++中的有继承关系存在的场景下的类型强转

如果使用static_cast由子类向父类转型,向上转型,天然安全。因为子类的实例中的类域就已经包括父类的类域空间。

如果使用static_cast由父类向子类转型(不安全的)。如果确认子类的空间已经成功开辟,那么则是安全的。如果不能确认子类的空间被开辟,由父类向下转型,则是不安全的。因为子类的指针可操控的空间大于已经合法定义的父类类间。

代码示例:

#include <iostream>
using namespace std;
class A
{
private:
    int a=100;
public:
    void show()
    {
        cout << "A的属性a:" << a << endl;
    }
};
class B:public A
{
private:
    int b=200;
    int c=300;
    int d=400;
    int e=600;
public:
    void show()
    {
        cout << "B的属性b:" << " b:" << b << " c:" << c << " d:" << d << " e:" << e <<endl;
    }
};

int main()
{
    //子类向父类转型天然且安全
    B b;
    static_cast<A>(b).show();
    ((A)b).show();
    A *p=new B;
    p->show();
    cout << "----------" << endl;
    //父类向子类转型非安全的
    A a;
    static_cast<B*>(&a)->show();//错误访问非法空间

    A *p2=new A;//向下转型不可以
    static_cast<B*>(p2)->show();//错误访问非法空间

    
    //安全情况
    A *p1=new B;//子类空间被开辟的情况下是安全的
    static_cast<B*>(p1)->show();
    
    B *p3=new B;//向上转型安全
    static_cast<A*>(p3)->show();
    return 0;
}

结果展示:
在这里插入图片描述
总结:
向上转型是安全的,向下转型如果不能保证子类空间被开辟则是不安全的。

1.2 动态类型转换

动态类型转换:dynamic_cast(Type*/Type&)(exp)只可以转指针或者引用。
动态类型转换是依赖于多态的实现的,也被称之为安全的转型,所以想使用它必须使用继承。
向下转型必须依赖多态中的虚表才能完成。
dynamic_cast他是依赖于虚函数背后的虚表中有第一个槽位置就存放了多态中的运行类型识别信息。
在这里插入图片描述
代码示例:

#include <iostream>
using namespace std;
class A
{
public:
    virtual void show()
    {
        cout << "正在学习C++" << endl;
    }
};
class B:public A
{
private:
    int b=200;
    int c=300;
    int d=400;
    int e=600;
public:
    void show()override
    {
        cout << "好好学习,天天向上" << endl;
    }
    void showB()
    {
        cout << "B的属性b:" << " b:" << b << " c:" << c << " d:" << d << " e:" << e <<endl;
    }
};

int main()
{
    //向上转型
    B b;
    dynamic_cast<A&>(b).show();
	//向下转型
	//A a;
    //static_cast<B*>(&a)->showB();//程序虽然还能执行,但是已经指向了非法空间,这种行为是不安全的
    //向下转型必须依赖于虚函数
    //dynamic_cast<B*>(&a)->showB();
    //没有虚函数不允许向下转型,有了虚函数后,如果子类空间没有被开辟,dynamic_case会返回一个空指针,
    //空指针调用函数,程序会直接终止,这是一种安全机制,所以向下转型推荐使用dynamic_cast
    //是虚函数作为背书的,转换的时候,dynamic首先会去虚表中看一下第一个槽的类型识别信息有没有你要转换的类型,
    //有可以转,没有则返回一个空指针
    A *p=new B;
    dynamic_cast<B*>(p)->showB();
    return 0;
}

结果展示:
在这里插入图片描述
总结:

  1. 向下转型static_cast<B*>(&a)->showB();程序虽然还能执行,但是已经指向了非法空间,这种行为是不安全的。
  2. 转换的时候,dynamic首先会去虚表中看一下第一个槽的类型识别信息有没有你要转换的类型,有可以转,没有则返回一个空指针。
  3. dynamic_cast<B*>(&a)->showB();没有虚函数不允许向下转型,有了虚函数后,如果子类空间没有被开辟,dynamic_case会返回一个空指针,空指针调用函数,程序会直接终止,这是一种安全机制,所以向下转型推荐使用dynamic_cast

1.3 常类型转换

常类型转换:const_cast<Type*/Type&>(exp)
C++所提供的这中常类型转换方式,只针对于常指针,或常引用进行转型。可读性更好。

代码示例:

#include <iostream>
using namespace std;

int main()
{
    int a=100;
    const int &b=a;
    cout << b << endl;
    (int&)b=1000;
    cout << b << endl;
    const_cast<int&>(b)=2000;
    cout << b << endl;

    const int *p=&a;
    //*p=3000;报错
    *(const_cast<int*>(p))=3000;
    cout << a << endl;
    return 0;
}

结果展示:
在这里插入图片描述

1.4 解释类型转换

解释类型转换:reinterpret_cast(Type)(exp)
转换风险最高一种转换方式,一般在公司开发中尽量避免。
底层实现:就是类型变量底层二进制代码的一种直接的拷贝。

代码示例:

#include <iostream>
using namespace std;

int main()
{
    int a=10;
    int *p=&a;
    //将地址转换为十进制打印出来
    cout << reinterpret_cast<long long>(p) << endl;
    cout << p+1 << endl;
    cout << reinterpret_cast<long long>(p)+1 << endl;
    return 0;
}

结果展示:
在这里插入图片描述

二、C++异常处理机制

2.1 C中异常处理的缺陷

代码示例:

#include <iostream>
using namespace std;
float my_div(int a,int b)
{
    if(b==0)
    {
        return -1;
    }
    return a/b;
}
float my_add(int a,int b)
{
    if(my_div(a,b)==-1)
    {
        return -1;
    }
    return my_div(a,b)+a+b;
}
int main()
{
    
    return 0;
}

通过代码我们可以发现c中的div由于可能导致的错误,使得每一个调用它的函数都要对它进行一次判断,十分冗余。并且当div中a=10,b=-10的时候,会得到-1,从而导致调用它的函数判断div出现了错误。

2.2 C++中异常处理机制的套路

为了应对C中处理的缺陷,C++有了自己独特的异常处理机制的套路。

1.throw关键字抛出异常。在有可能出现异常的函数内部使用throw来抛出异常。

throw + 常量 / 字符串 /自定义的类类型的对象。都可以做为throw抛出的异常。

2.使try{…}catch(表达式){…}来捕获异常,及处理异常。

try
{
    //把所有可能抛出异常函数全部放在try这个语句块中。
    //当函数中有异常被抛出时,那么这个函数将自动停止,
    //并跳转到catch语句块中进行下一步的处理。
}catch(exp用来接收异常对象){
    //处理异常的逻辑。
}
C++throw会在嵌套函数中层层抛出,如果外层没有使用try...catch...进行捕获
与处理,那么就会抛到main中,如果main也没有处理main函数将直接中止执行。

代码示例:

#include <iostream>
using namespace std;
float my_div(int a,int b)
{
    if(b==0)
    {
        throw -1;
    }
    if(b==-10)//随便设置看现象用
    {
        throw "随便设置看现象用";
    }
    return a/b;
}
float my_add(int a,int b)
{
    return my_div(a,b)+a;
}
int main()
{
    try {//放入可能会出现异常的函数
        cout << my_add(10,10) << endl;
        cout << my_add(10,-10) << endl;
        cout << my_add(10,0) << endl;//有异常直接跳转到catch
        cout << "异常" << endl;//有异常跳出try,这句代码没有执行
    } catch (int e) {//处理异常的逻辑
        if(e==-1)
        {
            cout << "除数不可以为零" << endl;
        }
    }catch (const char *err){
        cout << err << endl;
    }

    cout << "学习C++" << endl;//出现异常还可以继续向下执行
    return 0;
}

结果展示:
在这里插入图片描述
总结:

  1. try中出现异常语句会直接跳转到catch语句执行处理异常的逻辑。
  2. catch处理完异常会继续向下执行。
  3. try...catch...是做为一个整体出现的 不要分拆开。
  4. 不仅可以抛出数字,也可以抛出字符串。

2.3 C++中的标准异常库之常用异常类

在这里插入图片描述
返回的是字符串。
在这里插入图片描述
代码示例:

#include <iostream>
#include <exception>
using namespace std;
float my_div(int a,int b)
{
    if(b==0)
    {
        throw out_of_range("错误,除数不可以为0");
    }
    return a/b;
}
float my_add(int a,int b)
{
    return my_div(a,b)+a;
}
int main()
{
    try {//放入可能会出现异常的函数
        cout << my_add(10,10) << endl;
        cout << my_add(10,-10) << endl;
        cout << my_add(10,0) << endl;//有异常直接跳转到catch
        cout << "异常" << endl;//有异常跳出try,这句代码没有执行
    } catch (out_of_range& e) {//处理异常的逻辑,不知道多大用个引用
    		//引用栈上的一个临时空间
    		//这里没有使用右值引用是因系统会为异常对象开辟一块空间,
    		//来保存异常的这个结果对象。
            cout << e.what() << endl;//在catch语句块中处理异常的逻辑。
    }

    cout << "学习C++" << endl;//出现异常还可以继续向下执行
    return 0;
}

结果展示:
在这里插入图片描述

2.4 封装一个自定义异常类

#include <iostream>
#include <exception>
using namespace std;
//自定义异常类
class Error
{
private:
    string e;
public:
    Error(string e)
    {
        this->e=e;
    }
    string what()
    {
        return this->e;
    }
};

float my_div(int a,int b)
{
    if(b==0)
    {
        throw Error("错误,除数不可以为0");
    }
    return a/b;
}
float my_add(int a,int b)
{
    return my_div(a,b)+a;
}
int main()
{
    try {//放入可能会出现异常的函数
        cout << my_add(10,10) << endl;
        cout << my_add(10,-10) << endl;
        cout << my_add(10,0) << endl;//有异常直接跳转到catch
        cout << "异常" << endl;//有异常跳出try,这句代码没有执行
    } catch (Error& e) {//处理异常的逻辑
            cout << e.what() << endl;
    }

    cout << "学习C++" << endl;//出现异常还可以继续向下执行
    return 0;
}

结果展示:
在这里插入图片描述

三、C++11新特性之右值引用

3.1 右值引用的来源

在我们想了解右值引用有什么来源以及有什么作用时,首先我们应该掌握一个概念,什么是临时对象

简单来说,临时对象只会产生在栈上不会产生在堆上,在栈上只有空间,没有地址,下一行代码即马上销毁。
临时对象就是一个常量,也是一个右值。

但是在有属性指针指向堆区的情况下,临时对象就会造成计算性能的浪费。
使用const的左值引用来引用这个临时对象,但const修饰的左值引用又无法调用普通函数的情况下,C+11就引入了右值引用的新语法。

3.2 右值引用的语法形式

类型&& 变量 = 常量/临时对象。
右值引用,不能引用左值,只能引用右值。
如果想使用右值引用,引用一个左值,请使用移动语义函数std::move()

代码示例:

#include <iostream>
using namespace std;
class A
{
    int* p;
public:
    A()
    {
        this->p=new int[20];
        cout << "A的构造"  << endl;
    }
    virtual ~A()
    {
        delete [] p;
        cout << "A的析构" << endl;
    }
    A(const A& other)
    {
        this->p = new int[20];
        memmove(this->p,other.p,sizeof(int[20]));
        cout << "发生了拷贝构造" << endl;
    }
    virtual void showInfo()
    {
        cout << "学习C++" << endl;
    }
};
class B: public A
{
public:
    B()
    {
        cout << "B的构造" << endl;
    }
    ~B()
    {
        cout << "B的析构" << endl;
    }
    void showInfo()override
    {
        cout << "好好学习,天天向上" << endl;
    }
};
int main()
{
    A a = B();//临时对象,中间发生了拷贝构造,下一行销毁
    A a1 =A();//没有发送拷贝构造。编译器优化
    cout << "-------------------" << endl;
    const int& c = 100; //int temp = 10; const int& a = temp;
    const A& c1 = B();//A temp = B(); const A& c1 = temp;
    //c1.showInfo();//需要成员函数是常函数,可能遇到无法更改成员函数的情况
    const_cast<A&>(c1).showInfo();//可以解决也可以使用右值引用
    //右值引用:
    A&& c3 = B();
    c3.showInfo();
    //移动语义函数std::move(左值或右值)
    
    A&& c4 = std::move(c3);//相当于把c3这个左值强转成为了右值。
    c4.showInfo();
    cout << "-----------------------" << endl;
    return 0;
}

结果展示:
在这里插入图片描述

总结:

  1. 临时对象,只产生在栈上。只有空间,没有地址,所以他没有办法保存。临时的生命周期非常短暂,短到下一行代码执行时,他就销毁了。临时对象是一个常量,是一个右值。
  2. A a = B();临时对象,中间发生了拷贝构造,下一行销毁从。从结果我们可以发现,在下一行代码执行时就析构了
    A a1 =A();没有发送拷贝构造。编译器优化,下一行代码没有销毁。
  3. 我们不想临时对象刚产生就释放,所以可以const int& c = 100; //编译器底层实现int temp = 10; const int& a = temp;使用左值引用,引用右值。
  4. 因为const修饰,我们要使用常函数。可能遇到无法更改成员函数的情况
  5. 可以使用const_cast<A&>(c1).showInfo();或者右值引用来解决。
  6. 也可以使用右值引用去引用左值。移动语义函数std::move(左值或右值)
    相当于把左值强转成了右值。

3.3 右值引用与C++11中的新构造之移动构造

例如:购票系统,不想发生深拷贝,想使用浅拷贝提高性能。上一个人使用完了直接移给下一个人使用。
代码示例:

#include <iostream>
using namespace std;
class A
{
    int* p;
public:
    A()
    {
        this->p=new int[20];
        cout << "A的构造"  << endl;
    }
    virtual ~A()
    {
        if(p!=nullptr)
        {
            delete [] p;
        }
        cout << "A的析构" << endl;

    }
    A(const A& other)
    {
        this->p=new int[20];
        memmove(this->p,other.p,sizeof(int[20]));
        cout << "发生了拷贝构造" << endl;
    }
    //C++11的移动构造
    A(A&& other)
    {
        this->p=other.p;//浅拷贝
        other.p=nullptr;
        cout << "发生了移动构造" << endl;
    }
};
int main()
{
    A a;
    //A a1=a;隐式调用拷贝构造,发生深拷贝
    A a1=std::move(a);//强转成右值,在隐式调用了移动构造。
    return 0;
}

结果展示:
在这里插入图片描述

评论 15
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

夜猫徐

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值