【C++入门到精通】异常 | 异常的使用 | 自定义异常体系 [ C++入门 ]

在这里插入图片描述

阅读导航

  • 引言
  • 一、C++异常的概念
  • 二、异常的使用
    • 1. 异常的抛出和捕获
      • (1)throw
      • (2)try-catch
      • (3)catch(. . .)
      • (4)异常的抛出和匹配原则
      • (5)在函数调用链中异常栈展开匹配原则
    • 2. 异常的重新抛出
    • 3. 异常安全
    • 4. 异常规范
  • 三、自定义异常体系
    • 1. 创建基类异常
    • 2. 创建派生异常
    • 3. 抛出异常
    • 4. 捕获和处理异常
    • 5. 自定义异常体系示例
  • 四、C++标准库的异常体系
  • 五、异常的优缺点
    • 1. C++异常的优点
    • 2. C++异常的缺点
    • 3. 总结
  • 温馨提示

引言

在C++编程中,异常处理是一项重要的技术,它允许我们更好地应对程序运行过程中可能出现的错误和异常情况。本文将介绍C++中异常处理的基本概念、语法和最佳实践。我们将深入探讨try-catch块的使用方式,以及如何抛出和捕获不同类型的异常。

希望本文能够帮助您更好地理解和运用C++异常处理机制,并为您的编程工作带来便利和效益。祝您阅读愉快!

一、C++异常的概念

C++异常是一种用于处理程序运行时错误和异常情况的机制。当程序在执行过程中遇到无法正常处理的错误时,可以通过抛出异常来中断当前的执行流程,并将控制权转交给异常处理器。异常处理器可以捕获并处理异常,从而使程序能够优雅地处理错误情况,避免崩溃或数据丢失。异常可以表示各种不同类型的错误,例如除以零、访问不存在的内存地址、文件打开失败等。

二、异常的使用

如果有一个块抛出一个异常,捕获异常的方法会使用 trycatch 关键字。try 块中放置可能抛出异常的代码,try 块中的代码被称为保护代码

1. 异常的抛出和捕获

(1)throw

throw关键字用于抛出异常。当程序在执行过程中遇到无法正常处理的错误时,可以通过throw语句抛出一个异常,让异常处理器来捕获并处理这个异常。例如:

if (count == 0) {
    throw std::runtime_error("Divide by zero exception");
}

在上面的例子中,如果count等于0,则会抛出一个std::runtime_error异常,并附带异常信息“Divide by zero exception”。

(2)try-catch

try-catch块用于捕获和处理异常。try块用于包含可能引发异常的代码,catch块则用于捕获和处理特定类型的异常。例如:

try {
    // 可能引发异常的代码
} catch (std::exception& e) {
    // 捕获std::exception及其派生类的异常
    std::cerr << "Exception caught: " << e.what() << std::endl;
} catch (...) {
    // 捕获其他类型的异常
    std::cerr << "Unknown exception caught" << std::endl;
}

在上面的例子中,try块包含可能引发异常的代码,catch块捕获std::exception及其派生类的异常,并输出异常信息。如果没有合适的catch块来处理异常,则会跳转到最近的catch(...)块。

(3)catch(. . .)

catch(...)块用于捕获任何类型的异常。如果程序抛出了一个未被其他catch块捕获的异常,则会跳转到最近的catch(...)块,并执行相关的异常处理代码。

(4)异常的抛出和匹配原则

  1. 异常是通过抛出对象而引发的,该对象的类型决定了应该激活哪个catch的处理代码。
  2. 被选中的处理代码是调用链中与该对象类型匹配且离抛出异常位置最近的那一个
  3. 抛出异常对象后,会生成一个异常对象的拷贝,因为抛出的异常对象可能是一个临时对象,所以会生成一个拷贝对象,这个拷贝的临时对象会在被catch以后销毁。(这里的处理类似于函数的传值返回)
  4. catch(…)可以捕获任意类型的异常,问题是不知道异常错误是什么。
  5. 实际中抛出和捕获的匹配原则有个例外,并不都是类型完全匹配,可以抛出的派生类对象,使用基类捕获,这个在实际中非常实用

(5)在函数调用链中异常栈展开匹配原则

  1. 首先检查throw本身是否在try块内部,如果是再查找匹配的catch语句。如果有匹配的,则调到catch的地方进行处理。
  2. 没有匹配的catch则退出当前函数栈,继续在调用函数的栈中进行查找匹配的catch
  3. 如果到达main函数的栈,依旧没有匹配的,则终止程序
  4. 找到匹配的catch子句并处理以后,会继续沿着catch子句后面继续执行。

🚨🚨注意:上述这个沿着调用链查找匹配的catch子句的过程称为栈展开。所以实际中我们最后都要加一个catch(…)捕获任意类型的异常,否则当有异常没捕获,程序就会直接终止
在这里插入图片描述

2. 异常的重新抛出

在C++中,异常的重新抛出是指在一个catch块中捕获到一个异常后,再次将它抛出,以便由更高层次的异常处理器来处理。这种重新抛出异常的操作可以在catch块中使用throw语句来实现。

⭕重新抛出异常的语法如下:

try {
    // 可能引发异常的代码
} catch (ExceptionType1& e) {
    // 异常处理代码
    throw; // 重新抛出异常
} catch (ExceptionType2& e) {
    // 异常处理代码
    throw; // 重新抛出异常
}

在上面的代码中,当在catch块中捕获到ExceptionType1类型的异常时,我们可以选择重新抛出该异常,让更高层的异常处理器来处理。通过throw语句,异常会被重新抛出并继续向上寻找匹配的catch块。

重新抛出异常的好处在于,在某些情况下,当前的catch块可能无法完全处理异常,并希望由更上层的异常处理器来处理。这样可以将异常传递给更高层的代码进行处理,而不是简单地在当前的catch块中忽略掉异常。

🚨注意:在重新抛出异常时,可以选择不指定异常类型,即使用throw;来重新抛出原始的异常。这样做的好处是可以保持异常的类型和信息不变,使得更高层的异常处理器可以正常地捕获和处理异常。

3. 异常安全

在C++中,异常安全是指程序在抛出异常时仍然能够保证程序的正确性和资源的释放。异常安全是一个重要的编程概念,因为在实际开发中,程序运行过程中难免会遇到各种异常情况,如内存不足、IO错误等,而这些异常可能会导致程序崩溃或者产生未知的错误,给程序的稳定性和可维护性带来很大的挑战

为了提高程序的异常安全性,我们需要采取一些措施,主要包括以下几个方面:

🍁 使用RAII技术

RAII(Resource Acquisition Is Initialization)是一种资源获取即初始化的技术,通过C++对象的构造函数获取资源,利用析构函数自动释放资源。这样可以确保资源的正确释放,即使在抛出异常的情况下也能够保证资源的正确释放。比如使用std::unique_ptr智能指针管理内存,std::lock_guard管理锁等。(关于RAII我们智能指针这篇文章有所讲解)

🍁不要泄漏资源

在程序执行过程中,如果没有正确地释放资源,就会导致资源泄漏,进而影响程序的正确性和稳定性。因此,在编写程序时,要确保所有获取的资源都要正确释放,以避免资源泄漏。

🍁异常处理

在程序中,如果遇到异常情况,则需要正确地处理这些异常。一般来说,我们可以使用try-catch语句块来捕获并处理异常,确保程序在抛出异常时仍然能够保证程序的正确性和资源的释放。

4. 异常规范

异常规范(Exception Specification)是一种在C++函数声明中指定函数可能抛出的异常类型的方法。异常规范可以告诉调用者该函数可能会抛出哪些异常,以便调用者能够适当地处理这些异常。在C++中,有两种类型的异常规范:动态异常规范和静态异常规范。

🍪动态异常规范(Dynamic Exception Specification):

动态异常规范使用throw关键字在函数声明中列出函数可能抛出的异常类型。例如:

void functionName() throw(ExceptionType1, ExceptionType2);

上述代码表示函数functionName可能会抛出ExceptionType1ExceptionType2类型的异常。如果函数抛出了未在异常规范中列出的异常,就会调用std::unexpected()函数,该函数默认会调用std::terminate()函数终止程序。

🍪静态异常规范(Static Exception Specification):

静态异常规范使用noexcept关键字指示函数不会抛出任何异常。例如:

void functionName() noexcept;

上述代码表示函数functionName不会抛出任何异常。这样的函数被称为不抛出异常的函数,编译器可以对其进行更多的优化。

在C++11之后,推荐使用noexcept来指示函数不会抛出异常,而避免使用动态异常规范。同时,C++11引入了std::nothrow关键字,用于在发生异常时返回一个null指针,而不是抛出异常。

🚨注意如果函数声明了静态异常规范,但实际上抛出了异常,程序会调用std::terminate()函数终止程序

三、自定义异常体系

✅在C++中,可以通过自定义异常类来创建自己的异常体系。自定义异常体系可以用于对不同类型的异常进行分类和处理。在实际的程序中都会定义一套继承的规范体系。这样大家抛出的都是继承的派生类对象,捕获一个基类就可以了
在这里插入图片描述
⭕下面是创建自定义异常体系的步骤

1. 创建基类异常

创建一个基类异常,作为自定义异常体系的根。这个基类异常可以是std::exception的子类或者直接继承自std::exception。例如:

class MyException : public std::exception {
public:
    virtual const char* what() const throw() {
        return "My exception occurred";
    }
};

在基类异常中,通常会重写what()函数,该函数返回异常的描述信息。

2. 创建派生异常

根据需要,可以创建多个派生异常类,用于表示不同类型的异常。这些派生异常类可以继承自基类异常或者其他已有的异常类。例如:

class OutOfRangeException : public MyException {
public:
    virtual const char* what() const throw() {
        return "Out of range exception";
    }
};

class NullPointerException : public MyException {
public:
    virtual const char* what() const throw() {
        return "Null pointer exception";
    }
};

在派生异常类中,也可以重写what()函数,以提供特定异常的描述信息。

3. 抛出异常

在程序中遇到异常情况时,可以使用throw语句抛出自定义的异常对象。例如:

void myFunction() {
    throw OutOfRangeException();
}

以上代码示例在myFunction()函数中抛出了一个OutOfRangeException异常对象。

4. 捕获和处理异常

在调用可能引发异常的代码时,可以使用try-catch语句块捕获并处理异常。例如:

try {
    myFunction();
} catch (MyException& ex) {
    std::cout << "Caught exception: " << ex.what() << std::endl;
}

以上代码示例在调用myFunction()时捕获并处理了MyException及其派生异常的对象。

自定义异常体系可以根据具体需求进行扩展和细化,以提供更好的异常分类和处理能力。通过创建不同类型的异常类,可以更好地组织和处理程序中的异常情况。

5. 自定义异常体系示例

#include <iostream>
#include <string>
#include <ctime>
#include <chrono>
#include <thread>

using namespace std;

// 自定义异常基类
class Exception
{
public:
    // 构造函数,接收错误信息和错误码作为参数
    Exception(const string& errmsg, int id)
        : _errmsg(errmsg)
        , _id(id)
    {}

    // 返回异常信息的函数
    virtual string what() const
    {
        return _errmsg;
    }

protected:
    string _errmsg; // 异常信息
    int _id;        // 异常码
};

// 派生自Exception的SqlException异常类
class SqlException : public Exception
{
public:
    // 构造函数,接收错误信息、错误码和SQL语句作为参数
    SqlException(const string& errmsg, int id, const string& sql)
        : Exception(errmsg, id)
        , _sql(sql)
    {}

    // 返回异常信息的函数
    virtual string what() const
    {
        string str = "SqlException:";
        str += _errmsg;
        str += "->";
        str += _sql;
        return str;
    }

private:
    const string _sql; // SQL语句
};

// 派生自Exception的CacheException异常类
class CacheException : public Exception
{
public:
    // 构造函数,接收错误信息和错误码作为参数
    CacheException(const string& errmsg, int id)
        : Exception(errmsg, id)
    {}

    // 返回异常信息的函数
    virtual string what() const
    {
        string str = "CacheException:";
        str += _errmsg;
        return str;
    }
};

// 派生自Exception的HttpServerException异常类
class HttpServerException : public Exception
{
public:
    // 构造函数,接收错误信息、错误码和请求类型作为参数
    HttpServerException(const string& errmsg, int id, const string& type)
        : Exception(errmsg, id)
        , _type(type)
    {}

    // 返回异常信息的函数
    virtual string what() const
    {
        string str = "HttpServerException:";
        str += _type;
        str += ":";
        str += _errmsg;
        return str;
    }

private:
    const string _type; // 请求类型
};

// SQL管理函数
void SQLMgr()
{
    srand(time(0));
    if (rand() % 7 == 0)
    {
        // 如果随机数满足条件,抛出SqlException异常
        throw SqlException("权限不足", 100, "select * from name = '张三'");
    }
}

// 缓存管理函数
void CacheMgr()
{
    srand(time(0));
    if (rand() % 5 == 0)
    {
        // 如果随机数满足条件,抛出CacheException异常
        throw CacheException("权限不足", 100);
    }
    else if (rand() % 6 == 0)
    {
        // 如果随机数满足条件,抛出CacheException异常
        throw CacheException("数据不存在", 101);
    }
    SQLMgr();
}

// HTTP服务器函数
void HttpServer()
{
    // ...
    srand(time(0));
    if (rand() % 3 == 0)
    {
        // 如果随机数满足条件,抛出HttpServerException异常
        throw HttpServerException("请求资源不存在", 100, "get");
    }
    else if (rand() % 4 == 0)
    {
        // 如果随机数满足条件,抛出HttpServerException异常
        throw HttpServerException("权限不足", 101, "post");
    }
    CacheMgr();
}

int main()
{
    while (1)
    {
        this_thread::sleep_for(chrono::seconds(1));

        try {
            // 调用HttpServer函数可能会抛出异常
            HttpServer();
        }
        catch (const Exception& e) {
            // 捕获Exception及其派生类的异常对象
            cout << e.what() << endl; // 输出异常信息
        }
        catch (...) {
            // 捕获其他类型的异常
            cout << "Unkown Exception" << endl;
        }
    }

    return 0;
}

这是一个基于C++的自定义异常体系。在该代码中,自定义了三个异常类,分别是SqlExceptionCacheExceptionHttpServerException,这些异常类都派生自基类Exception。通过自定义异常类,可以更准确地捕获异常,同时也可以对不同类型的异常进行不同的处理。

在代码中,SqlException用于处理SQL语句相关的异常,CacheException用于处理缓存相关的异常,HttpServerException用于处理HTTP服务器相关的异常。每个异常类都有一个构造函数,用于初始化异常信息和错误码等参数。在捕获异常时,可以根据异常类型来选择相应的处理方式,比如输出错误信息、记录日志、重试操作等等。

四、C++标准库的异常体系

⭕C++ 提供了一系列标准的异常,定义在 中,我们可以在程序中使用这些标准的异常。它们是以父子类层次结构组织起来的,如下图所示:

在这里插入图片描述
下表是对上面层次结构中出现的每个异常的说明:

异常类型描述
std::exception所有异常类的基类
std::logic_error程序逻辑错误
std::invalid_argument无效参数
std::domain_error域错误
std::length_error长度错误
std::out_of_range超出范围
std::future_error异步执行错误
std::runtime_error运行时错误
std::range_error范围错误
std::overflow_error溢出错误
std::underflow_error下溢错误
std::regex_error正则表达式错误
std::bad_alloc内存分配错误
std::bad_cast类型转换错误
std::bad_exception未捕获的异常
std::bad_function_call错误的函数调用
std::bad_typeidtypeid操作错误
std::bad_weak_ptrweak_ptr错误
std::ios_base::failureI/O流错误
std::system_error系统调用错误
std::filesystem::filesystem_error文件系统错误
std::experimental::filesystem::filesystem_error实验性文件系统错误

五、异常的优缺点

异常是一种处理程序运行时错误的机制,它提供了一种在程序中处理异常情况的方式。以下是异常的优点和缺点:

1. C++异常的优点

  • 异常对象定义好了,相比错误码的方式可以清晰准确的展示出错误的各种信息,甚至可以包含堆栈调用的信息,这样可以帮助更好的定位程序的bug
  • 异常可以提高代码的可读性和可维护性,将错误处理代码与正常逻辑分离开来,使代码更加清晰易懂。
  • 异常可以通过层级结构进行处理,允许异常在不同的层次上被捕获和处理,从而提高了程序的灵活性和扩展性。

2. C++异常的缺点

  • 异常可能会导致性能问题,因为在发生异常时需要执行一些额外的操作,如调用析构函数、回收内存等,这些操作会增加程序的开销。
  • 异常可能会影响代码的可移植性,因为异常处理机制在不同的编译器和操作系统上可能有所不同,需要针对不同的平台进行调整。
  • 异常可能会导致程序的安全性问题,因为异常可以破坏程序的状态,可能会被恶意利用。此外,异常也可能会隐藏错误,使得程序的调试和测试变得更加困难。
  • 异常会导致程序的执行流乱跳,并且非常的混乱,并且是运行时出错抛异常就会乱跳。这会导致我们跟踪调试时以及分析程序时,比较困难。

3. 总结

综上所述,异常总体而言,利大于弊,所以工程中我们还是鼓励使用异常的。在使用异常时,我们应该权衡利弊,根据实际情况选择合适的处理方式,保证程序的健壮性和安全性。

温馨提示

感谢您对博主文章的关注与支持!另外,我计划在未来的更新中持续探讨与本文相关的内容,会为您带来更多关于C++以及编程技术问题的深入解析、应用案例和趣味玩法等。请继续关注博主的更新,不要错过任何精彩内容!

再次感谢您的支持和关注。期待与您建立更紧密的互动,共同探索C++、算法和编程的奥秘。祝您生活愉快,排便顺畅!
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/305410.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Java项目:112SSM在线电影订票系统

博主主页&#xff1a;Java旅途 简介&#xff1a;分享计算机知识、学习路线、系统源码及教程 文末获取源码 一、项目介绍 在线电影订票系统基于SpringSpringMVCMybatis开发&#xff0c;系统分为前台和后台&#xff0c;前台主要用来用户浏览电影信息&#xff0c;订票&#xff0c…

C/C++学习笔记 vcpkg使用备忘及简要说明

一、简述 vcpkg 是一个免费的 C/C 包管理器&#xff0c;用于获取和管理库。从 1500 多个开源库中进行选择&#xff0c;一步下载并构建&#xff0c;或者添加您自己的私有库以简化构建过程。由 Microsoft C 团队和开源贡献者维护。 官方教程 vcpkg 文档 | Microsoft Learnvcpkg …

day-04 字符串中的额外字符

思路 动态规划&#xff0c;每个字符要么额外要么不是额外 解题方法 int[] dp new int[n1]; dp[i] 表示从字符串开头到字符串索引i位置的最少额外字符数 dp[i 1] Math.min(dp[i 1], dp[j]) dp[j]表示假设s第i个字符不是额外的&#xff0c;可能等于dp[i 1]&#xff0c;也可…

GPS 模拟器

GPS 工具包&#xff1a;https://www.ni.com/es/support/downloads/software-products/download.gnss-test-toolkit.html#333303 GPS-SDR-SIM&#xff1a;https://github.com/osqzss/gps-sdr-sim GPS LabVIEW &#xff1a;http://mikioblog.dolphinsystem.jp/2017/08/gps-sdr-si…

Presto大数据学习网站:让你轻松驾驭大数据处理!

介绍&#xff1a;Presto是一个由Facebook开源的分布式SQL查询执行引擎&#xff0c;它被设计用于处理各种规模的数据并进行快速分析查询。这个引擎具有优秀的兼容性&#xff0c;可以支持众多的数据源&#xff0c;包括但不限于HDFS、关系型数据库管理系统&#xff08;RDBMS&#…

JVM的FastThrow优化机制

前言&#xff1a; 前一阵子&#xff0c;在公司排查线上问题发现&#xff1a;出问题的方法报空指针异常&#xff0c;但是没有异常堆栈信息和Message。我一开始以为是代码中做了处理&#xff0c;但是经过翻阅代码发现不是。最后一番查找资料&#xff0c;这种现象是JVM的一种优化机…

C# 日期转换“陷阱”

在 C# 中&#xff0c;日期转换可能会遇到一些陷阱。以下是一些常见的陷阱和如何避免它们&#xff1a; 时区问题 日期和时间通常与时区相关&#xff0c;但在转换时可能会忽略或混淆时区信息。确保在转换日期时始终考虑到时区&#xff0c;并使用正确的时区进行转换。 DateTime…

npm、pnpm和yarn 的区别

包管理工具是JavaScript开发中不可或缺的一部分&#xff0c;它们可以帮助我们方便地安装、更新、删除和管理项目所依赖的各种库和模块。 目前&#xff0c;最流行的包管理工具有npm、yarn和pnpm&#xff0c;它们各有各的特点和优劣势。 本文将试着对这三个工具进行全面的对比。…

Python-PyQt5树莓派上位机

Python-PyQt5树莓派上位机 一个使用PythonQT设计的树莓派的上位机&#xff0c;功能大概如下 1.笔记本电脑与树莓派的通讯是否成功显示&#xff08;给个信号显示判断是否通讯成功&#xff09;&#xff1b; 2.阈值的设置显示&#xff1b; 3.图像成像的显示&#xff1b; 4.是否发生…

9.建造者模式

文章目录 一、介绍二、代码三、实际使用总结 一、介绍 建造者模式旨在将一个复杂对象的构建过程和其表示分离&#xff0c;以便同样的构建过程可以创建不同的表示。这种模式适用于构建对象的算法&#xff08;构建过程&#xff09;应该独立于对象的组成部分以及它们的装配方式的…

学习笔记——C++二维数组

二维数组定义的四种方式&#xff1a; 1&#xff0c;数据类型 数组名[ 行数 ][ 列数 ]&#xff1b; 2&#xff0c;数据类型 数组名[ 行数 ][ 列数 ]{{数据1&#xff0c;数据2}&#xff0c;{数据3&#xff0c;数据4}}&#xff1b; 3&#xff0c;数据类型 数组名[ 行数…

【计算机网络】TCP原理 | 可靠性机制分析(二)

个人主页&#xff1a;兜里有颗棉花糖 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 兜里有颗棉花糖 原创 收录于专栏【网络编程】【Java系列】 本专栏旨在分享学习网络编程、计算机网络的一点学习心得&#xff0c;欢迎大家在评论区交流讨论&#x1f48c; T…

如何做好档案统一管理?

档案统一管理是指将一个组织或机构的所有档案资料进行集中管理和整理的一种管理方式。档案统一管理的目标是确保档案的完整性、准确性和可访问性&#xff0c;提高档案的利用价值和管理效率。 要做好档案统一管理&#xff0c;需要以下几个步骤&#xff1a; 1. 确定档案的分类与命…

【S32K 进阶之旅】 NXP S32K3 以太网 RMII 接口调试(2)

前言 前文介绍了 NXP S32K3 以太网 RMII 接口调试的开发环境搭建&#xff0c;下面开始详解软件调试步骤。没看过第一节的小伙伴请移步《【S32K 进阶之旅】 NXP S32K3 以太网 RMII 接口调试&#xff08;1&#xff09;》&#xff0c;话不多说我们直接进入正题。 lwip Stack 介绍 …

debug OpenBLAS library 和 应用示例

1. 构建openblas lib git clone gitgithub.com:OpenMathLib/OpenBLAS.git cd OpenBLAS/ 如果要安装在自定义文件夹中&#xff0c;可以修改 PREFIX 的定义&#xff1a; 将 PREFIX /opt/OpenBLAS 修改成 PREFIX ../local/ 然后构建&#xff1a; make -j make install 如果要…

09.面向对象进阶

面向对象进阶 在前面的章节我们已经了解了面向对象的入门知识&#xff0c;知道了如何定义类&#xff0c;如何创建对象以及如何给对象发消息。为了能够更好的使用面向对象编程思想进行程序开发&#xff0c;我们还需要对Python中的面向对象编程进行更为深入的了解。 property装…

OpenAI ChatGPT-4开发笔记2024-03:Chat之Tool和Tool_Call(含前function call)

Updates on Function Calling were a major highlight at OpenAI DevDay. In another world,原来的function call都不再正常工作了&#xff0c;必须全部重写。 function和function call全部由tool和tool_choice取代。2023年11月之前关于function call的代码都准备翘翘。 干嘛…

考公还是互联网?

来源 花了 10 小时检索&#xff0c;汇总的有效信息 V2EX&#xff0c;牛客&#xff0c;Google&#xff0c;Bing&#xff0c;广东省人事考试网&#xff0c;国家公务员局&#xff0c;B站&#xff0c;小红书&#xff0c;知乎&#xff0c;掘金&#xff0c;Github&#xff0c;公务员…

Rollup-plugin-bundle-analyzer VS Rollup-plugin-visualizer

分析和可视化Rollup打包后的文件的插件 Rollup-plugin-bundle-analyzerRollup-plugin-visualizer Rollup-plugin-bundle-analyzer和Rollup-plugin-visualizer都是用于分析和可视化Rollup打包后的文件的插件&#xff0c;但它们在功能和使用方式上存在一些差异。 Rollup-plugi…

低成本高回报:如何利用免版素材库提升设计品质?

免版素材库起源于互联网的发展&#xff0c;是指一种包含大量图片、图标、字体等创意资源的网站或平台&#xff0c;这些资源多为设计师和相关行业人士创作&#xff0c;并免费提供给用户使用。免版素材库的资源通常遵循一定的授权协议&#xff0c;如CC0&#xff08;Creative Comm…