day37-c++异常,智能指针

day37-c++异常,智能指针

一、c++异常

1. 异常简介

C++异常是一种在程序运行过程中发生的意外或错误情况,例如除以零、数组越界、内存不足等。当异常发生时,程序的正常执行流程会被中断,转而执行一段专门用于处理异常的代码,称为异常处理器。异常处理器可以恢复程序的状态,继续执行后续的代码,或者终止程序并报告错误信息。

C++提供了一套异常处理机制,包括以下几个关键字:

  • throw:用于抛出一个异常,可以是任意类型的表达式,例如throw 0; throw "error"; throw std::runtime_error("invalid input");
  • try:用于定义一个可能抛出异常的代码块,例如
    1
    2
    3
    4
    5
    6
    try { 
    int x = 10 / 0;
    }
    catch (...) {
    std::cout << "division by zero\n";
    }
  • catch:用于捕获一个特定类型或所有类型的异常,并执行相应的处理代码,您可以指定想要捕捉的异常类型,这是由 catch 关键字后的括号内的异常声明决定的,一般来说,throw什么类型的异常则要用同类型的catch参数来捕捉。例如
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    #include <iostream>
    using namespace std;
    double division(int a, int b)
    {
    if( b == 0 )
    {
    throw "Division by zero condition!";
    }
    return (a/b);
    }
    int main ()
    {
    int x = 50;
    int y = 0;
    double z = 0;
    try {
    z = division(x, y);
    cout << z << endl;
    }catch (const char* msg) {
    cerr << msg << endl;
    }
    return 0;
    }
    //由于我们抛出了一个类型为 const char* 的异常,因此,当捕获该异常时,我们必须在 catch 块中使用 const char*。
    可以一个try用多个catch来捕捉,只不过前面的捕捉范围要比后面的小。
  • noexcept:用于指定一个函数是否保证不抛出异常,例如void foo() noexcept; void bar() noexcept(false);

2.异常规则

  • throw抛出的异常类型与catch抓取的异常类型要一致;

  • throw抛出的异常类型可以是子类对象,catch可以是父类对象;

  • catch块的参数推荐采用地址传递而不是值传递,不仅可以提高效率,还可以利用对象的多态性。另外,派生类的异常捕获要放到父类异常扑获的前面,否则,派生类的异常无法被扑获;

  • 如果使用catch参数中,使用基类捕获派生类对象,一定要使用传递引用的方式,例如catch (exception &e);

  • 异常是通过抛出对象而引发的,该对象的类型决定了应该激活哪个处理代码;

  • 被选中的处理代码是调用链中与该对象类型匹配且离抛出异常位置最近的那一个;

  • 在try的语句块内声明的变量在外部是不可以访问的,即使是在catch子句内也不可以访问;

  • 栈展开会沿着嵌套函数的调用链不断查找,直到找到了已抛出的异常匹配的catch子句。如果抛出的异常一直没有函数捕获(catch),则会一直上传到c++运行系统那里,导致整个程序的终止。

3. c++类

下面是c++类中常见的异常:

类名 描述
Exception 基本异常类,用作其他异常类的基类。
LogicError 逻辑错误的异常,由程序逻辑引起。
RuntimeError 运行时错误的异常,通常由运行时环境引起。
OutOfBoundsException 超出边界的异常,用于数组或容器访问超出范围。
NullPointerException 空指针异常,当使用空指针时抛出。
InvalidArgumentException 无效参数异常,当传递无效参数给函数或方法时抛出。
FileException 文件异常,当文件操作失败时抛出。
NetworkException 网络异常,当网络操作失败时抛出。

4. 自定义异常

您可以通过继承和重载 exception 类来定义新的异常。下面的实例演示了如何使用 std::exception 类来实现自己的异常:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#include <iostream>
#include <exception>

// 自定义异常类,继承自std::exception基类
class MyException : public std::exception
{
public:
// 构造函数,使用传入的错误消息初始化异常对象
MyException(const char* message) : m_message(message) {}

// 重写what()函数,返回错误消息
const char* what() const noexcept override //noexcept关键字表示该函数不会引发异常。override关键字用于告诉编译器,what()函数是对基类std::exception中的虚函数进行重写。这样做可以帮助确保函数签名与基类中声明的虚函数相匹配。
{
return m_message;
}

private:
const char* m_message; // 错误消息
};

// 自定义除数为零异常类
class DivideByZeroException : public MyException
{
public:
// 构造函数,调用基类构造函数并传入错误消息
DivideByZeroException() : MyException("除数不能为零!") {}
};

// 自定义索引越界异常类
class IndexOutOfBoundsException : public MyException
{
public:
// 构造函数,调用基类构造函数并传入错误消息
IndexOutOfBoundsException() : MyException("索引越界!") {}
};

int main()
{
try
{
int divisor = 0;
if (divisor == 0)
{
throw DivideByZeroException(); // 抛出除数为零异常
}

int arr[] = {1, 2, 3};
int index = 5;
if (index < 0 || index >= sizeof(arr) / sizeof(arr[0]))
{
throw IndexOutOfBoundsException(); // 抛出索引越界异常
}
}
catch (const MyException& ex)
{
std::cout << "捕获到异常: " << ex.what() << std::endl;
}

return 0;
}




二、转换函数

c++转换函数是一种特殊的成员函数,它可以将一个类的对象转换为另一种类型的值。转换函数的一般形式如下:

1
2
3
4
5
operator 类型名()
{
// 转换操作
}

其中,operator是关键字,类型名是要转换的目标类型,没有参数和返回类型。转换函数必须定义在类的内部,不能是静态的或常量的。转换函数可以被显式或隐式地调用,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include <iostream>

using namespace std;

class subclass;

class Base{
private:
int x;
public:
Base(int x){
this->x = x;
}
operator subclass();
};

class subclass:public Base{
private:
int x;
public:
subclass(int x):x(x),Base(x){}
friend ostream &operator <<(ostream &out, const subclass &obj){
out << obj.x;
return out;
}
};

Base::operator subclass(){
return x;
}

int main(){
subclass a(10);
Base b(6);
a = b;
cout << a << endl;
return 0;
}

上面的代码有两个注意点。一个是b=a是调用了Base的转换函数,实际b是个Base的int成员。

另一个是a=6过程中,a是一个subclass的对象,而不是一个基本类型的变量。当你给a赋值为6时,实际上是调用了subclass的构造函数,创建了一个新的subclass对象,并用它替换了原来的a。

C++标准库中提供了一些用于类型转换的标准转换函数。这些函数可以方便地进行常见的类型转换操作。以下是几个常用的标准转换函数:

  • static_cast: 用于执行静态类型转换,将一个表达式转换为指定类型。

    1
    2
    3
    4
    int num = 10;
    double result = static_cast<double>(num); // 将整数 num 转换为双精度浮点数

    void* ptr = static_cast<void*>(&num); // 将指向整数 num 的指针转换为 void* 类型
  • dynamic_cast: 用于执行动态类型转换,在类层次结构中进行多态类型的转换。

    1
    2
    3
    4
    5
    6
    7
    8
    class Base {
    virtual void foo() {}
    };

    class Derived : public Base {};

    Base* basePtr = new Derived();
    Derived* derivedPtr = dynamic_cast<Derived*>(basePtr); // 将基类指针转换为派生类指针
  • const_cast: 用于移除变量的 const 修饰符或 volatile 修饰符。

    1
    2
    3
    4
    5
    const int num = 10;
    int* ptr = const_cast<int*>(&num); // 移除 num 的 const 修饰符,ptr 可以修改 num 的值

    const int& ref = num;
    int& ref2 = const_cast<int&>(ref); // 移除 ref 的 const 修饰符,ref2 可以修改 num 的值
  • reinterpret_cast: 用于执行底层的重新解释转换。

    1
    2
    3
    4
    int num = 10;
    char* charPtr = reinterpret_cast<char*>(&num); // 将整数 num 的地址转换为字符指针

    int* intPtr = reinterpret_cast<int*>(charPtr); // 将字符指针转换为整数指针



三、智能指针

C++ 智能指针是一种能够自动管理动态分配内存的对象,它可以避免手动调用 new 和 delete 导致的内存泄漏或异常安全问题。C++ 标准库提供了四种智能指针:auto_ptr、unique_ptr、shared_ptr 和 weak_ptr。

  • auto_ptr 已经被废弃,不建议使用。
  • unique_ptr是一种独占式的智能指针,它保证同一时间只有一个unique_ptr对象可以指向某个内存资源。当unique_ptr对象被销毁时,它会自动释放所指向的内存资源。unique_ptr不能被复制或赋值,只能通过移动语义来转移所有权。unique_ptr可以指向单个对象或者数组,也可以自定义删除器来处理特殊的资源释放方式。
  • shared_ptr是一种共享式的智能指针,它允许多个shared_ptr对象指向同一个内存资源。每个shared_ptr对象都有一个引用计数,表示有多少个shared_ptr对象共享该资源。当某个shared_ptr对象被销毁时,它会减少引用计数,当引用计数变为0时,才会释放所指向的内存资源。shared_ptr也可以指向单个对象或者数组,也可以自定义删除器。shared_ptr还可以和weak_ptr配合使用,避免循环引用导致的内存泄漏。
  • weak_ptr 是一种不影响引用计数的智能指针,它可以从一个 shared_ptr 或另一个 weak_ptr 构造,它用于观察对象是否还存在,而不延长其生命周期。

智能指针的使用可以简化 C++ 的内存管理,提高代码的可读性和安全性。

unique_ptr 语法格式:

1
2
3
4
5
6
7
8
9
10
std::unique_ptr<T> ptr;  // 创建一个名为 ptr 的 unique_ptr,指向类型为 T 的对象,初始为空指针

std::unique_ptr<T> ptr(new T(args...)); // 创建一个 unique_ptr,同时进行对象的动态内存分配和初始化

ptr.reset(); // 释放 ptr 指向的对象,并将 ptr 重置为空指针

T* rawPtr = ptr.get(); // 获取 ptr 内部存储的原始指针

ptr.release(); // 释放 ptr 的所有权,返回内部存储的原始指针,并将 ptr 重置为空指针

shared_ptr 语法格式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
std::shared_ptr<T> ptr;  // 创建一个名为 ptr 的 shared_ptr,指向类型为 T 的对象,初始为空指针

std::shared_ptr<T> ptr(new T(args...)); // 创建一个 shared_ptr,同时进行对象的动态内存分配和初始化

ptr.reset(); // 释放 ptr 指向的对象,并将 ptr 重置为空指针

T* rawPtr = ptr.get(); // 获取 ptr 内部存储的原始指针

std::shared_ptr<T> ptr2 = ptr; // 使用复制构造函数创建一个新的 shared_ptr,与 ptr 共享对象所有权,引用计数增加

std::shared_ptr<T> ptr3 = std::make_shared<T>(args...); // 使用 make_shared 创建 shared_ptr,更高效的分配内存

ptr.use_count(); // 返回与 ptr 共享对象所有权的 shared_ptr 的数量

ptr.reset(); // 释放 ptr 指向的对象,并将 ptr 重置为空指针,引用计数减少,当引用计数为零时,对象被销毁

下面是shared_ptr的示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <memory>
#include <iostream>

class MyClass {
public:
MyClass() {
std::cout << "Constructor called!" << std::endl;
}
~MyClass() {
std::cout << "Destructor called!" << std::endl;
}
void SomeFunction() {
std::cout << "Some function called!" << std::endl;
}
};

int main() {
std::shared_ptr<MyClass> sharedPtr1(new MyClass());
std::shared_ptr<MyClass> sharedPtr2 = sharedPtr1; // 复制构造函数增加引用计数
std::shared_ptr<MyClass> sharedPtr3 = sharedPtr1; // 引用计数增加到3

sharedPtr1->SomeFunction();
sharedPtr2->SomeFunction();
sharedPtr3->SomeFunction();

// 所有 shared_ptr 离开作用域后,拥有的对象会自动被销毁
return 0;
}

Donate
  • Copyright: Copyright is owned by the author. For commercial reprints, please contact the author for authorization. For non-commercial reprints, please indicate the source.
  • Copyrights © 2020-2024 nakano-mahiro
  • Visitors: | Views:

请我喝杯咖啡吧~

支付宝
微信