✔类型转换和异常

01 类型转换

(1) 传统的类型转换

传统的类型转换,功能是非常强大的,可以进行任意类型之间的类型转换(但是不能保证正确性)

形式:

        类型  变量  = (目标类型)表达式;

        把表达式转换为目标类型


        double b;

        int x = (int)b;

        

        int *p = (int *)malloc(...);// malloc的返回值是void *

        

        long p = 0x01020304;

        int *q = (int *)p;// 可以,把0x01020304当成地址编号来处理

        *q = 100;// 有问题,因为0x01020304是自己杜撰的地址,有可能不存在,有可能不能访问

在C++中,传统的类型转换方式仍然可用

为了增加安全性,强调风险,让程序员明白自己在做什么

C++增加了自己的类型转换方式        

(2) C++中新增的类型转换
1. static_cast<目标类型>(表达式)

        只能用于良性转换(可以进行隐式转换的地方)

        a. 基本类型之间的相互转换

                int,short,double,char,long ......

                        int ---> double

                        double ---> int

                        char ---> int 

                        enum ---> int

        b. 可以把void *转成任意类型的指针


        c. 把枚举转为int
 

2. const_cast<目标类型>(表达式)

        主要是用于移出指针 / 引用的CV属性(const / volatile)
        将const / volatile类型的指针 / 引用转换为非const / 非volatile类型的指针 / 引用 

                const:不可变的
                volatile:易变的,防止编译器优化

#include <iostream>

using namespace std;

int main() {

    const int x = 1024;

    // int * <=====> const int *
    // int *p = &x; // error,x是const int类型,不能直接赋值给int *
    // int *p = (int*)&x; // ok
    int *p = const_cast<int*>(&x); // ok
    *p = 250; // 把250写入到x的存储空间

    cout << "x: " << x << endl; // x: 1024
    cout << "*p: " << *p << endl; // *p: 250
    cout << "&x: " << &x << endl; // &x: 0x7ffed03bf40c
    cout << "p: "<< p << endl; // p: 0x7ffed03bf40c
    // C++对于常量的处理有点类似于编译时期的宏定义,是一个值替换的过程
    // 在编译的时候,代码中所有用到x的地方,都会被替换成1024
    // 所以常量在编译时期就已经确定了,不能在运行时修改

    volatile int y = 1024;

    int *q = const_cast<int*>(&y); // ok
    *q = 250; // 把250写入到y的存储空间

    cout << "y: " << y << endl; // y: 250
    cout << "*q: " << *q << endl; // *q: 250
    cout << "&y: " << &y << endl; // &y: 1  修饰的是易变的,y的地址被隐藏了
    cout << "q: "<< q << endl; // q: 0x7ffc5fe98c24

    return 0;
}
3. reinterpret_cast<目标类型>(表达式)

        重新解释,可以用于任意类型之间的强制转换(类似于传统的强制转换)

        做法:
                对表达式底层存储的二进制数据进行重新的解析,只不过不修改二进制的值
                是对static_cast的补充,可以完成良性转换以外的任何类型转换
                        如:不同指针之间的类型转换
                                        int *----->double *
                                        char *----->int *
                                还可以用于整数和指针之间的转换
                                        int ----->int *  // 把int数值解释为int*
                                        int *----->int 
                                        .... 

#include <iostream>

using namespace std;

// 枚举类,枚举量的值是私有的,不能直接使用,必须通过枚举类来访问,
// 枚举类是强类型的,不能隐式转换为int
// 在C++11中,引入了枚举类,枚举类是强类型的,不会暴露在最外层的作用域中
// 因此不会与其他枚举类型冲突
enum class Color1 {
    RED,
    GREEN,
    BLUE
};

enum Color2 { // 在传统的枚举中,枚举量的值是暴露在最外层的作用域的
    RED,
    GREEN,
    BLUE
};

int main() {
    
    double a = 1.1;

    // static_cast只能用于安全的类型转换(可以进行隐式转换的地方)
    // int *q = static_cast<int *>(&a); // error

    // reinterpret_cast可以用于任意类型之间的强制转换(类似于传统的强制转换)
    int *q = reinterpret_cast<int *>(&a); // 正确
    cout << *q << endl; // -1717986918

    int b = (int)a;
    int c = static_cast<int>(a);
    cout << b << endl; // 1
    cout << c << endl; // 1

    int d = ::RED;
    cout << d << endl; // 0
    d = Color2::RED;
    cout << d << endl; // 0

    int e = static_cast<int>(Color1::RED);
    cout << e << endl; // 0

    // e = Color1::RED; // error,Color1::RED是枚举类型,不能隐式转换为int
    e = (int)Color1::RED; // 正确
    cout << e << endl; // 0

    // 假设有一个硬件寄存器地址为0x010203040506
    long addr = 0x010203040506;
    // char *p = static_cast<char *>(addr); // error
    char *p = reinterpret_cast<char *>(addr); // 正确

    return 0;
}
4. dynamic_cast<目标类型>(表达式)

        基类和派生类之间的转换,而且必须包含虚函数,否则编译不通过

02. 异常(exception)

异常是C++中新增加的一种错误处理方式

在C++中,错误一般分两种,编译时错误() 运行时错误


所谓异常,指在程序运行过程中,由于系统条件,操作不当等原因引起的运行错误

常见的异常包括 除零错误、指针访问收保护的空间、数组越界、内存分配失败、要打开的文件不存在......等

为了提高系统的健壮性,加强容错能力,在编程过程中,这些异常必须进行处理,来防止系统奔溃。将异常(错误处理)分散在各处进行处理不利于代码的维护,尤其是对于在不同地方发生的同一种异常,都要编写相同的处理代码,也是一种不必要的重复和冗余,如果能在发生各种异常时让程序都执行到同一个地方,这些地方能够对所有异常进行集中处理,则程序就会更容易编写和维护


鉴于上述原因,C++引入了异常处理机制

其基本思想是:函数A在执行过程中发现异常时可以不加以处理,而只是"抛出一个异常"给A的调用者,假设为B。抛出异常而不加以处理会导致函数A立即中止,在这种情况下,函数B可以选择捕获A抛出的异常进行处理,也可以选择置之不理,如果置之不理,这个异常就会被抛给B的调用者,以此内推

如果一层层的函数都不处理异常,异常最终会被抛给最外层的main函数,main函数应该处理异常,如果main函数也不处理异常,那么程序就会立即异常的中止

一层一层的异常传递就是一个通信的过程

异常对象就是传递的内容,存在类型和内容

C++中异常处理机制由三部分组成:

        a. 检测异常 --- 测试这些代码是不是出了问题

                try

        b. 抛出异常 --- 有错误了我们需要将一些异常值给抛出来,被检测到

                throw

        c. 捕获异常

                catch

C++引入了throw,try,catch关键字用于异常处理
        通常将一组可能产生异常的代码放在try包含的块内,若try块中的某一行发生了异常,则try块中该行后面的代码将不再执行,由块内的throw语句抛出异常(抛出异常后,该函数throw语句后面的内容将不再执行),由try后面的catch结构接收异常并处理该异常,
如果抛出的异常没有匹配的catch结构,则会从上往下执行第一个符合接收条件的catch语句,如果try块中没有异常发生,则catch结构不会执行,接着执行catch后面的语句,不管有没有产生异常,都会执行catch后面的代码

try {
    // 包含可能抛出异常的语句和函数;
} catch (类型名1 [形参名1]) {
    // 可能出现的异常1
    // 异常处理方法
} catch (类型名2 [形参名2]) {
    // 可能出现的异常2
    // 异常处理方法
} catch(...) {
    // 如果不确定异常类型,在这里可以捕获所有类型异常!
}

throw语句抛出异常后,由它的直接或间接调用者处理该异常,如果到最后都没有处理该异常,编译器自动调用系统的 terminate接口 直接终止程序

异常处理机制能够实现错误检测和错误处理的分离
        一种思想:在所有支持异常处理的编程语言中(例如:java),要认识到的一个思想

                在异常处理过程中,由问题检测代码可以抛出一个对象给问题处理代码,通过这个对象的类型和内容,实际上完成了两个部分的通信,通信的内容是“出现了什么错误,该如何处理”

​​​​​​​​​​​​​​

如果throw中抛出一个对象,那么无论在catch中使用什么接收(基类对象、引用、指针或者子类对象、引用、指针),在传递到catch之前,编译器都会另外构造一个对象的副本,也就是说,如果你以一个throw语句抛出一个对象类型,在catch处也是通过一个对象接收,那么该对象经历了两次复制,即调用了两次拷贝构造

        一次是在throw时,将“抛出的对象”复制到一个“临时对象”(这一步是必须的)

        然后是因为catch处使用对象接收,那么需要再从"临时对象"复制到"catch"的形参“变量中”

        如果在catch中使用“引用”来接收参数,那么不需要第二次复制,即形参成为临时变量的别名

C++标准库定义一组异常类,用于报告标准库函数遇到的问题,这些异常类型也可在自己程序中使用,常见如下:

        头文件:include <exception>

        

        std::logic_error              逻辑错误

        std::runtime_error        只有在运行时才能检测出的错误

        std::out_of_range         范围越界

        std::bad_alloc               内存分配失败

        ......


所有异常类型只定义了一个名为 what 的成员函数,该函数没有任何参数,返回一个C风格的字符串        const  char *  用于提供异常信息


例如:

throw std::runtime_error("stack is empty");

catch (std::runtime_error &err) {
    cout << err.what() << endl;
}
================================================
new失败,会自动抛出异常 bad_alloc
try {
    p = new int[100000000000000000];
} catch (std::bad_alloc &err) {
    cout << "err: " << err.what() << endl;
} catch (...) {
    cout << "异常" << endl;
}
cout << "byebye" << endl;

注意:

        构造函数中可以抛出异常,普通的成员函数也可以抛出异常

        析构函数不能抛出异常!析构函数默认就是 noexcept 的

        

        如果一个函数承诺不抛出异常,可以在函数参数列表后加 noexcept 来进行说明

        noexcept 是一种承诺,承诺该函数不抛出异常

        如果一个函数用 noexcept 说明了,然后又在函数内部抛出了异常,编译时会有警告,执行时,如果调用了该函数,系统直接终止该进程​​​​​​​

#include <iostream>
#include <string>
#include <exception> // 异常相关的头文件

using namespace std;

class Test {
    private:
        int m_a;
    public:
        Test(int a) : m_a(a) {
            cout << "构造函数" << endl;
        }
        Test(const Test &t) : m_a(t.m_a) {
            cout << "拷贝构造函数" << endl;
        }
        ~Test() {
            cout << "析构函数" << endl;
        }
        void show() const noexcept { // noexcept表示这个函数承诺不抛出异常
            cout << "m_a = " << m_a << endl;
        }
};

// 判断一个浮点数是否为0
bool isZero(double a) {
    const double delta = 0.000001; // 误差范围
    return a - 0 < delta && a - 0 > -delta;
}

// 除法函数
double divide(double a, double b) {
    // 判断除数是否为0,如果除数为0则抛出一个异常
    if (isZero(b)) {
        // 抛出异常,异常实际上是一个参数
        // 参数是有类型和数值的
        // int x = 10;
        // throw x;
        // throw double{10.1};
        throw Test{10}; // 会在异常处理完成之后,才会结束生存期
        // throw std::runtime_error{"除数不能为0"};
        // 如果上面的throw语句执行了,则下面的代码不会执行
        cout << "除数不能为0" << endl; // 不会执行
    }
    return a / b;
}

int main() {

    double a = 10, b = 0;
    try {
        cout << "a / b =: " << divide(a , b) << endl;
        cout << "除法成功" << endl; // 如果上面的除法成功,则执行这行代码,否则不执行
    } catch (int x) { // 接收try中所有产生的int异常
        cout << "异常类型为int" << endl;
        cout << "捕获到异常,异常的数值为:" << x << endl;
    } catch (double x) { // 接收try中所有产生的double异常
        cout << "异常类型为double" << endl;
        cout << "捕获到异常,异常的数值为:" << x << endl;
    } 
    // const catch (Test t) // 一般使用常引用
    catch (const Test &t) { // 接收try中所有产生的Test异常
        cout << "异常类型为Test" << endl;
        t.show();
    } catch (std::runtime_error &w) { // 接收try中所有产生的runtime_error异常
        cout << "异常类型为runtime_error" << endl;
        cout << "捕获到异常,异常的描述为:" << w.what() << endl;
    }
    catch (...) { // 接收try中所有产生的异常
        cout << "所有的异常类型" << endl;
    }
    cout << "程序结束" << endl; // 不管上面的除法是否成功,这一行代码都会执行

    return 0;
}

// (1)使用引用
// g++ 0.cpp
/*
    a / b =: 构造函数
    异常类型为Test
    m_a = 10
    析构函数
    程序结束
*/

// g++ 0.cpp -fno-elide-constructors
/*
    a / b =: 构造函数
    拷贝构造函数
    析构函数 // 析构Test{10}
    异常类型为Test
    m_a = 10
    析构函数
    程序结束
*/

// (2)不使用引用
// g++ 0.cpp
/*
    a / b =: 构造函数
    拷贝构造函数
    异常类型为Test
    m_a = 10
    析构函数
    析构函数 // 析构Test{10}
    程序结束
*/

// g++ 0.cpp -fno-elide-constructors
/*
    a / b =: 构造函数
    拷贝构造函数
    析构函数  // 析构Test{10}
    拷贝构造函数
    异常类型为Test
    m_a = 10
    析构函数
    析构函数 // 将“抛出的对象”复制到一个“临时对象”,析构产生的临时变量
    程序结束
*/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值