C++『异常』

✨个人主页: 北 海
🎉所属专栏: C++修行之路
🎃操作环境: Visual Studio 2022 版本 17.6.5

成就一亿技术人


文章目录

  • 🌇前言
  • 🏙️正文
    • 1.异常基本概念
      • 1.1.C语言异常处理方式
      • 1.2.C++异常处理方式
    • 2.异常的使用
      • 2.1.异常的抛出与捕获
      • 2.2.异常的重新抛出
      • 2.3.异常安全
      • 2.4.异常规范
    • 3.异常体系
      • 3.1.C++标准库的异常体系
      • 3.2.自定义异常体系
    • 4.异常的优缺点
  • 🌆总结


🌇前言

异常处理在软件开发中扮演着关键的角色,它为程序员提供了一种有力的手段来处理和响应程序执行过程中可能出现的错误。本文将深入探讨异常的基本概念、异常处理方式、异常的使用技巧和异常体系的设计,以帮助开发者更好地理解和应用异常处理机制


🏙️正文

1.异常基本概念

1.1.C语言异常处理方式

C语言 中,面对异常主要有以下两种处理方式:

  1. 返回错误码
  2. 终止进程

比如 main 函数有一个返回值,只有返回值(错误码)为 0 时才表示程序正常退出,如果发生越界访问、堆栈溢出等行为时,会返回其他数值

部分错误码及其对应的错误信息对照表格如下

代码错误信息
0成功(Success)
1一般错误(General error)
2误用shell命令(Misuse of shell command)
126无法执行(Cannot execute)
127命令未找到(Command not found)
128无效的退出参数(Invalid exit argument)
130Ctrl+C终止(Terminated by Ctrl+C)
255退出状态未知(Unknown exit status)

至于其他代码的具体含义,取决于编译器的不同的实现,比如上面的 3 号错误码在 VS 中就表示 异常退出,具体原因是 越界访问


除了返回错误码外,C语言 还支持通过函数终止进程,说白了就是给进程发送 信号

可以使用 exit(err_code)abord()assert(bool_exp) 等函数终止进程

exit(err_code) 支持在终止进程时设置错误码,可以根据自己的需要建立 [错误码, 错误信息] 的映射关系

abord() 函数则是直接发送 6 号信号来终止进程

至于 assert(bool_exp) 常用于非法情况的检查判断,bool_exp 是一个返回类为 bool 的表达式,如果该表达式为 ,那么 assert 函数就会触发,并终止进程

注意: 使用 assert 需要包含相关头文件

#include <iostream>
#include <cassert>using namespace std;int main()
{int x = 10;int y = 0;// 简易整数除法[&]()->void{// 除数(分母)不能为 0assert(y);cout << x / y << endl;}();return 0;
}

assert 最大的优点在于 指明终止原因,以及原因出现的具体路径、具体行号,对于程序调试十分友好,需要 注意 的是 assert 只能在 Debug 模式下使用,Release 模式中 assert 会被自动删除

1.2.C++异常处理方式

无论是 错误码 还是 终止进程,都只能提供简略的错误信息,对于 C++ 这种面向对象的语言来说太无力了,需要一种全新的异常处理方式:将异常化做一个对象,配合异常体系解读异常

万物皆可为对象,所以新的异常处理方式非常强大

C++ 中新增了以下三个关键字,用于实现 异常监测、异常抛出、异常捕获

  • try 监测当前代码区域是否存在异常
  • throw 识别到异常后,抛出异常
  • catch 捕获抛出的异常(如果有的话)

注:throw 是一个关键字,可以直接在后面跟异常对象,也可以像函数调用一样传递异常对象,类似于 sizeof 关键字

比如这样就可以使用 C++ 的异常处理方式

void func()
{// 出现异常,抛出throw "这是一个异常"// 或者// throw("这是一个异常")
}int main()
{try{// 监测代码当前代码区域func();}catch(const char* ps){// 捕获异常// 可以对 ps 进行操作}return 0;
}

注意: catch 块捕获的异常对象类型,必须与 throw 抛出的异常对象类型匹配上,否则会导致异常无法捕获,导致程序异常终止

如果正确编写异常处理的代码,try 内的代码发生异常时可以优雅处理,不至于直接引发进程终止,因此 try 内的代码又被称为 保护代码


2.异常的使用

2.1.异常的抛出与捕获

异常的使用比较简单,将之前整数相除的代码改成 C++ 的异常处理方式

void divisor(int x, int y)
{if (y == 0){// 除 0 错误,抛出异常throw("除数(分母)不能为0");}cout << "结果:" << x / y << endl;
}int main()
{try{divisor(10, 0);}catch (const char* s){cout << s << endl;}return 0;
}

通常需要在异常捕获的地方记录日志,方便排查错误

如果传入的数据是正确的,就不会触发异常,程序正常运行

// ...int main()
{try{divisor(10, 10);}catch(const char* s){cout << s << endl;}return 0;
}

异常在抛出后是必须被捕获的,如果不写 catch 块相关代码或者 catch 块中的类型与抛出的异常类型不匹配,在出现异常后,进程会因异常没有被捕获,而被 abort 函数终止

void divisor(int x, int y)
{if (y == 0){// 除 0 错误,抛出异常throw("除数(分母)不能为0");}cout << "结果:" << x / y << endl;
}int main()
{try{divisor(10, 0);}catch (int s) // 故意写错类型{cout << s << endl;}return 0;
}

现在的编译器都很智能,如果你在代码编写阶段一个 catch 块都没写,会直接报语法错误,所以一定要确保抛出的异常,能被正确捕获


catch 块至少得存在一个,也可以存在多个,当同时存在多个 catch 块时,抛出的异常会根据栈帧顺序,被最近的 catch 块捕获

catch 块只能进入一次,异常被捕获后,无法再进入其他 catch

注意: 如果出现多个类型不匹配的 catch 块时,异常会被类型匹配,且最近的 catch 块捕获

void divisor(int x, int y)
{if (y == 0){// 除 0 错误,抛出异常throw("除数(分母)不能为0");}cout << "结果:" << x / y << endl;
}void calc()
{int x = 10;int y = 0;try{divisor(x, y);}catch (const char* s){cout << "void calc()" << endl;cout << s << endl;}
}int main()
{try{calc();}catch (const char* s){cout << "int main()" << endl;cout << s << endl;}return 0;
}

divisor 函数捕获异常后,main 函数中不再捕获异常,代码正常运行结束;一般异常捕获这个工作会交给最外层统一处理,比如这里的 main 函数,此时如果出现了异常,代码会直接跳转至 main 函数中,至于中间的栈帧会被 throw 自动清理

void divisor(int x, int y)
{if (y == 0){// 除 0 错误,抛出异常throw("除数(分母)不能为0");}cout << "结果:" << x / y << endl;
}void calc()
{int x = 10;int y = 0;divisor(x, y);
}int main()
{try{calc();}catch (const char* s){cout << "int main()" << endl;cout << s << endl;}return 0;
}


在实际使用中,并不会这样直接抛出一个字符串,而是构建一个 异常信息类,抛出一个 异常对象,类中包罗万象,需要包含最基本的两个信息:错误码、错误信息

// 异常信息类
class Exception
{
public:Exception(int errcode, const string& content):_errno(errcode), _content(content){}void what() const{// 打印异常信息cout << "发生了异常" << endl;cout << "\t错误码为: " << _errno << endl;cout << "\t错误信息为: " << _content << endl;}
private:int _errno = 0;string _content;
};

这样一来,在出现异常时,可以构建一个异常对象并抛出

为什么要设计错误码?
因为在某些场景中,不方便直接暴露错误,比如消息发送过程中,如果遇到网络问题,检测到错误码为 x,会不断重试,直到发送成功或超时,这样能使用户体验更好

throw(Exception(3, "除数(分母)不能为0"));// 现在引用的是临时对象
catch(const Exception& e);

注意: catch 块捕捉时,不可以直接使用左值引用,因为抛出的是一个局部对象


当出现未知异常时,如何解决?

通过 catch(...) 捕获,支持捕获任意类型的异常

void calc()
{// 故意抛出一个未知异常throw(10);
}int main()
{try{calc();}catch (const Exception& e){cout << "int main()" << endl;e.what();}catch (...){cout << "int main()" << endl;cout << "未知异常" << endl;}return 0;
}

catch(...) 就相当于异常捕获的底线,如果前面的 catch 块都无法捕获异常,此时就轮到 catch(...) 登场,避免程序因异常无法捕获而终止


异常支持使用父类指针/引用捕获子类对象,假设当前项目中存在:网络异常、数据异常、SQL异常 等多种异常信息类,如果想让最外层的 catch 块捕获所有异常对象,可以让这些异常信息类都继承自同一个父类,同时重写父类中的虚函数,再通过父类指针/引用捕获

#include <iostream>
#include <string>
#include <windows.h>using namespace std;// 父类
class Exception
{
public:Exception(int errcode, const string& content):_errno(errcode), _content(content){}virtual string what() const{return to_string(_errno) + " : " + _content;}protected:int _errno = 0;string _content;
};// 网络子类
class HttpException : public Exception
{
public:HttpException(int errcode, const string& content, const string& url, const string& type):Exception(errcode, content), _url(url), _type(type){}// 重写virtual string what() const{return to_string(_errno) + " : " + _content + " ---> " + _type + " " + _url;}private:string _url; // 资源路径string _type; // 请求类型
};// 内存子类
class CacheException : public Exception
{
public:CacheException(int errcode, const string& content):Exception(errcode, content){}// 重写virtual string what() const{return to_string(_errno) + " : " + _content;}
};// SQL子类
class SqlException : public Exception
{
public:SqlException(int errcode, const string& content, const string& sql):Exception(errcode, content),_sql(sql){}// 重写virtual string what() const{return to_string(_errno) + " : " + _content + " ---> " + _sql;}private:string _sql; // SQL语句
};void SqlServe()
{int n = rand();if (n % 5 == 3)throw(SqlException(3, "数据表不存在", "select * from t2"));else if (n % 5 == 4)throw(SqlException(4, "权限不足", "drop table t1"));// 操作完成cout << "请求已完成" << endl << "-------------------" << endl;
}void CacheServe()
{int n = rand();if (n % 5 == 2)throw(CacheException(2, "数据不存在"));// 进入下一层SqlServe();
}void HttpServe()
{int n = rand();if (n % 5 == 0)throw(HttpException(0, "请求的资源不存在", "/index.html HTTP/1.1", "GET"));else if (n % 5 == 1)throw(HttpException(1, "没有访问权限", "/image.html HTTP/1.1", "POST"));// 进入下一层CacheServe();
}int main()
{srand((size_t)time(nullptr));while (true){Sleep(1000);try{cout << "开始请求" << endl;HttpServe();}catch (const Exception& e){// 异常处理cout << e.what() << endl << "===================" << endl;}catch (...){cout << "未知异常" << endl;}}return 0;
}

这里用到了 继承 + 多态 相关知识,当子类对象赋值给父类指针/引用时,会触发 切片 机制,这个过程是天然发生的,所以但凡是从该父类派生出的子类对象,都可以被正常接收

这种玩法在实际开发中非常实用,项目组可以根据自己的需求,设计继承体系,以及异常体系

注意: 如果同时存在类型为父类及子类的 catch 块,异常会被较近的 catch 块捕捉

2.2.异常的重新抛出

异常抛出后,可能会导致某些栈帧中的代码没有被执行,从而引发内存泄漏等问题,比如下面场景中就出现了内存泄露问题

// 异常信息类
class Exception
{
public:Exception(int errcode, const string& content):_errno(errcode), _content(content){}string what() const{return to_string(_errno) + " : " + _content;}
public:int _errno = 0;string _content;
};void divisor(int x, int y)
{if (y == 0){// 除 0 错误,抛出异常Exception e(3, "除数(分母)不能为0");throw(e);}cout << "结果:" << x / y << endl;
}void calc()
{int x = 10, y = 0;int* arr = new int[10];divisor(x, y);delete[] arr;cout << "delete[] arr: " << arr << endl;
}int main()
{try{calc();}catch (const Exception& e){cout << "int main()" << endl;cout << e.what() << endl;}catch (...){cout << "int main()" << endl;cout << "未知异常" << endl;}return 0;
}


可以看到,动态开辟的空间并没有被正确释放,这是因为异常抛出后,throw 会清理 calc 的栈帧,导致其中的代码没有被执行,要想正确的释放内存,需要在 calc 函数中主动捕获异常,将空间释放后,重新抛出异常

注:throw 表示捕获到什么异常,就抛出什么异常

void calc()
{int x = 10, y = 0;int* arr = new int[10];try{divisor(x, y);}catch (...){delete[] arr;cout << "delete[] arr: " << arr << endl;throw;}delete[] arr;cout << "delete[] arr: " << arr << endl;
}

现在空间被释放了,同时异常正常交给了最外层处理,不过这种写法的代码不容易维护,好在 C++ 中诞生了 智能指针,能自动释放空间,这也是下一篇博客的内容

为什么异常要在统一的地方进行处理?

  • 统一记录日志
  • 针对某些错误进行额外处理

2.3.异常安全

异常在使用时需要注意以下几点

1.最好不要在构造函数中抛出异常,因为对象的构造和初始化是需要时间的,如果在构造途中抛出了异常,会导致对象构造不完整

2.最好不要在析构函数中抛出异常,析构函数清理资源的过程同样需要时间,析构途中抛出异常可能会引发内存泄漏

3.在使用诸如 new/deletemalloc/freefopen/fcloselock/unlock 等资源管理配套函数时,需要特别注意资源泄漏或者死锁问题,在发生捕获到异常后,需要先把资源释放了,再考虑异常处理

2.4.异常规范

异常就像一只薛定谔的猫,你永远不知道别人是否抛出、何时抛出,为了让异常的使用更加规范,C++98 标准规定:

  1. 可以在函数的后面接 throw(type1, type2, type3 ...),列出这个函数可能抛掷的所有异常类型
  2. 函数的后面接 throw( ),表示该函数不会抛出异常
  3. 若无异常接口声明,则此函数可以抛掷任何类型的异常

比如这样编写函数:

void func1() throw(int, char, string); // 可能抛出这三种类型的异常void func2() throw(); // 该函数不会抛出异常void func3(); // 该函数可以抛出任何类型的异常

在标准库函数中,就采用了这种规范写法

C++98 中的异常规范过于繁琐,由于异常规范并非强制性语法,实际使用过程中有很多人都不会遵守,于是在 C++11 中对异常规范进行了相关更新,化繁为简,只需使用一个 noexcept 关键字表明该函数不会抛出异常

void func2() noexcept; // 该函数不会抛出异常

推荐使用 C++11 中新方案,对于不会抛出异常的函数,使用 noexcept 关键字修饰

  • noexcept 可以检测当前函数中是否发生了 throw 抛出异常的行为

如果加了 noexcept 关键字后,函数仍然抛出异常,是否会报错?
答案是会的,会直接被 abort 函数终止进程,所以可以放心使用 noexcept 关键字;即便是在异常抛出与异常捕获的中间函数中使用 noexcept 修饰,在异常抛出后,进程也会被终止;总之就是加了 noexcept 修饰后,所有该函数涉及的操作,都不会出现异常

注:如果使用 throw() 修饰,仍然抛出异常后,进程不会终止,所以还是推荐使用 noexcept


3.异常体系

3.1.C++标准库的异常体系

C++ 标准库中提供了一套 异常体系,其中包含了各种常见异常,我们也可以继承 std::exception 父类,重写其中的虚函数,实现其他方面的异常

异常描述
std::exception该异常是所有标准C++异常的父类
std::bad_alloc该异常可以通过new抛出
std::bad_cast该异常可以通过dynamic_cast抛出
std::bad_typeid该异常可以通过typeid抛出
std::bad_exception这在处理C++程序中无法预期的异常时非常有用
std::logic_error理论上可以通过读取代码来检测到的异常
std::runtime_error理论上不可以通过读取代码来检测到的异常
std::domain_error当使用了一个无效的数学域时,会抛出该异常
std::invalid_argument当使用了无效的参数时,会抛出该异常
std::length_error当创建了太长的std::string时,会抛出该异常
std::out_of_range该异常可以通过方法抛出,例如std::vector和std::bitset<>::operator
std::overflow_error当发生数学上溢时,会抛出该异常
std::range_error当尝试存储超出范围的值时,会抛出该异常
std::underflow_error当发生数学下溢时,会抛出该异常

3.2.自定义异常体系

虽然 C++ 标准库中提供了标准异常体系,但实际上大多数公司会根据实际项目定义自己的异常体系,比如之前的 SqlException 等异常信息类,就属于自定义异常体系

为什么要自定义异常体系?
因为公司中的项目一般都会进行模块划分,不同的模块用于实现不同的功能,如果不通过自定义异常体系来规范异常行为,会导致整个项目的异常处理及其麻烦,有了自定义异常体系后,只需要通过一个父类指针/引用,即可捕获不同子类对象异常,统一进行处理


4.异常的优缺点

异常的优点

  • 可以展示更丰富的错误信息,更好的定位程序 Bug
  • 错误码是层层返回的,不方便定位问题,而异常是则直接被捕获的
  • 很多的第三方库都包含了异常,需要与其进行兼容,比如 boostgtestgmock
  • 部分函数使用异常更好表示错误,比如 T& operator[](size_t pos) 如果越界了就抛异常,而不是返回 T() 或 断言

异常的缺点

  • 执行流跨度过大,并且非常混乱,导致跟踪调试程序时比较困难
  • 异常有一些性能上的开销(当代硬件速度很快,可以忽略不计)
  • C++ 没有垃圾回收机制,资源需要自己管理,可以使用 RAII 来处理资源管理问题
  • C++ 标准库的异常体系定义不够好,导致出现了各种异常体系,比较混乱
  • 异常尽量规范使用,否则后果不堪设想
    1. 抛出异常类型都继承自一个基类
    2. 函数是否抛异常可以使用 noexcept 注明

总体而言,异常 利大于弊,在工程项目中鼓励使用异常,OO 语言基本都是使用异常处理错误,这是大势所趋


🌆总结

以上就是本次关于C++『异常』的全部内容了,异常处理是软件开发中重要的错误管理工具,本文深入探讨了异常的基本概念、C++中的处理方式、使用技巧和异常体系设计。尽管异常提供了丰富的错误信息,但其使用需要谨慎考虑执行流、性能开销等因素。在面对项目需求时,程序员应权衡利弊,以确保异常处理在提高代码可维护性和可靠性方面发挥最佳效果


星辰大海

相关文章推荐

C++ 进阶知识

C++11『lambda表达式 ‖ 线程库 ‖ 包装器』

C++11『右值引用 ‖ 完美转发 ‖ 新增类功能 ‖ 可变参数模板』

C++11『基础新特性』

C++11『右值引用与移动语义』

C++11『基础新特性』

C++ 哈希的应用【布隆过滤器】

C++ 哈希的应用【位图】

C++【哈希表的完善及封装】

C++【哈希表的模拟实现】

C++【初识哈希】

C++【一棵红黑树封装 set 和 map】

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

在线网页生成工具GrapesJS

项目地址 https://github.com/GrapesJS/grapesjshttps://github.com/GrapesJS/grapesjs 项目简述 这是一个基于node.js的在线网页生成项目&#xff0c;对简化开发有很大的帮助。 主要使用的语言如下&#xff1a; 编辑页面如下&#xff1a; 使用也很简洁 具体可以看下项目。…

12. MySQL 锁机制

目录 概述 MylSAM引擎 InnoDB引擎 概述 锁是计算机协调多个进程或线程并发访问某一资源的机制&#xff08;避免争抢&#xff09;。在数据库中&#xff0c;除传统的计算资源(如CPU、RAM、I/O等&#xff09;的争用以外&#xff0c;数据也是一种供许多用户共享的资如何保证数据…

2023年第十届GIAC全球互联网架构大会-核心PPT资料下载

一、峰会简介 谈到一个应用&#xff0c;我们首先考虑的是运行这个应用所需要的系统资源。其次&#xff0c;是关于应用自身的架构模式。最后&#xff0c;还需要从软件工程的不同角度来考虑应用的设计、开发、部署、运维等。架构设计对应用有着深远的影响&#xff0c;它的好坏决…

Leetcode659. 分割数组为连续子序列

Every day a Leetcode 题目来源&#xff1a;659. 分割数组为连续子序列 解法1&#xff1a;哈希 贪心 定义两个哈希表&#xff1a; numsCount&#xff1a;统计数组 nums 中各元素出现次数。tailCount&#xff1a;存储以数字 i 结尾的且符合题意的连续子序列个数。 算法&a…

极兔单号查询,极兔快递物流查询,一键筛选出退回件

批量查询极兔快递单号的物流信息&#xff0c;一键筛选出其中的退回件。 所需工具&#xff1a; 一个【快递批量查询高手】软件 极兔快递单号若干 操作步骤&#xff1a; 步骤1&#xff1a;运行【快递批量查询高手】软件&#xff0c;并登录 步骤2&#xff1a;点击主界面左上角的…

【Bootloader学习理解----跳转优化异常】

笔者接着来介绍一下Bootloader的跳转代码以及优化 1、跳转代码理解 跳转代码可能要涉及到芯片架构的知识,要跳转到对应的位置&#xff0c;还要设置相关的SP 堆栈指针&#xff0c;具体可以参考笔者这篇文章BootLoader的理解与实现。 STM32的跳转代码如下所示&#xff1a; u32 …

基于以太坊的智能合约开发Solidity(基础篇)

参考教程&#xff1a;基于以太坊的智能合约开发教程【Solidity】_哔哩哔哩_bilibili 1、第一个程序——Helloworld&#xff1a; //声明版本号&#xff08;程序中的版本号要和编译器版本号一致&#xff09; pragma solidity ^0.5.17; //合约 contract HelloWorld {//合约属性变…

Python轴承故障诊断 (四)基于EMD-CNN的故障分类

目录 前言 1 经验模态分解EMD的Python示例 2 轴承故障数据的预处理 2.1 导入数据 2.2 制作数据集和对应标签 2.3 故障数据的EMD分解可视化 2.4 故障数据的EMD分解预处理 3 基于EMD-CNN的轴承故障诊断分类 3.1 训练数据、测试数据分组&#xff0c;数据分batch 3.2 定义…

stu05-前端的几种常用开发工具

前端的开发工具有很多&#xff0c;可以说有几十种&#xff0c;包括记事本都可以作为前端的开发工具。下面推荐的是常用的几种前端开发工具。 1.DCloud HBuilder&#xff08;轻量级&#xff09; HBuilder是DCloud&#xff08;数字天堂&#xff09;推出的一款支持HTML5的web开发…

硬件开发笔记(十四):RK3568底板电路LVDS模块、MIPI模块电路分析、LVDS硬件接口、MIPI硬件接口详解

若该文为原创文章&#xff0c;转载请注明原文出处 本文章博客地址&#xff1a;https://hpzwl.blog.csdn.net/article/details/134634186 红胖子网络科技博文大全&#xff1a;开发技术集合&#xff08;包含Qt实用技术、树莓派、三维、OpenCV、OpenGL、ffmpeg、OSG、单片机、软硬…

软考高级备考-系统架构师(机考后新版教材的备考过程与资料分享)

软考高级-系统架构设计师 考试复盘1.考试结果2.备考计划3.个人心得 资料分享 考试复盘 1.考试结果 三科压线过&#xff0c;真是太太太太太太太幸运了。上天对我如此眷顾&#xff0c;那不得不分享下我的备考过程以及一些备考资料&#xff0c;帮助更多小伙伴通过考试。 2.备考…

time模块(python)

一.sleep休眠 [rootrhel8 day04]# vim demo01_time.py import time def banzhuan():print("搬砖")time.sleep(3.5) #让程序休眠3.5秒print("结束")banzhuan()[rootrhel8 day04]# python3 demo01_time.py 搬砖 结束运行时&#xff0c;会发现程序中间暂停…

【3DsMax】制作简单的骨骼动画

效果 步骤 首先准备4个板子模型展开放置好 添加一个4段的骨骼 选中其中的一块板子添加蒙皮命令 在蒙皮的参数面板中&#xff0c;设置每块板子对应哪块骨骼 设置好后你可以发现此时就已经可以通过骨骼来控制模型了 接下来就可以制作动画 点击左下角“时间配置”按钮 设置一下动…

HarmonyOS--ArkTS(1)--基本语法(1)

目录 基本语法概述 声明式UI描述 自定义组件 创建自定义组件 自定义组件的结构--struct &#xff0c;Component&#xff0c;build()函数 生命周期 基本语法概述 装饰器&#xff1a; 用于装饰类、结构、方法以及变量&#xff0c;并赋予其特殊的含义。如上述示例中Entry、C…

VSCode安装与使用

VS Code 安装及使用 1、下载 进入VS Code官网&#xff1a;地址&#xff0c;点击 DownLoad for Windows下载windows版本 注&#xff1a; Stable&#xff1a;稳定版Insiders&#xff1a;内测版 2、安装 双击安装包&#xff0c;选择我同意此协议&#xff0c;再点击下一步 选择你…

【Vulnhub 靶场】【hacksudo: ProximaCentauri】【简单 - 中等】【20210608】

1、环境介绍 靶场介绍&#xff1a;https://www.vulnhub.com/entry/hacksudo-proximacentauri,709/ 靶场下载&#xff1a;https://download.vulnhub.com/hacksudo/hacksudo-ProximaCentauri.zip 靶场难度&#xff1a;简单 - 中等 发布日期&#xff1a;2021年06月08日 文件大小&…

【LeetCode:70. 爬楼梯 | 递归 -> 记忆化搜索 -> DP】

&#x1f680; 算法题 &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&#xff0c;…

【图片版】计算机组成原理考前复习题【第3章 存储系统-2(Cache)】

目录 前言 考前复习题&#xff08;必记&#xff09; 结尾 前言 在计算机组成原理的学习过程中&#xff0c;我们深入探索了计算机系统概述这一重要领域。计算机系统作为现代科技的核心&#xff0c;是整个计算机科学的基石。我们将学到的知识与理论转化为了能够解决现实问题…

如何防止恶意调用和攻击对抖音商品详情API的影响?

防止恶意调用和攻击对抖音商品详情API的影响是开发者和平台必须关注的问题。恶意调用和攻击可能导致服务中断、数据泄露或其他安全问题&#xff0c;对平台和用户造成损失。本文将介绍一些常见的恶意调用和攻击方式&#xff0c;并提出相应的防范措施&#xff0c;以确保抖音商品详…

python画动漫形象(魔法少女小圆晓美焰,super beautiful)

1.源代码 import turtle as te import time WriteStep 15 # 贝塞尔函数的取样次数 Speed 5 Width 600 # 界面宽度 Height 500 # 界面高度 Xh 0 # 记录前一个贝塞尔函数的手柄 Yh 0 def Bezier(p1, p2, t): # 一阶贝塞尔函数 return p1 * (1 - t) p2 * t def Bezier_2(x1…