从C语言到C++_35(异常)C++异常的使用+异常体系+异常优缺点

目录

1. 异常的基本使用

1.1 异常的概念

1.2 异常的抛出和匹配原则

1.3 函数调用链中异常栈展开匹配原则

1.4 异常的重新抛出

1.5 异常的安全问题

1.6 C++98和C++11的异常规范

2. 自定义异常体系

2.1 异常继承体系

2.2 异常体系中的重新抛出

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

4. C++异常的优缺点和总结

5. 笔试选择题

答案及解析

本篇完。


1. 异常的基本使用

C语言传统处理错误的方式:

① 终止程序
比如空指针解引用,除0等异常发生时,程序会直接终止,但是这种方式对于用户来说难以接受,会导致整个进程挂掉。

② 返回错误码
比如打开文件,还有Linux中创建线程等C函数接口,调用后会返回一个返回值,如果发生错误会将错误码返回并放入到全局的errno中。

程序员需要自己去查找对应的错误,非常不直观。

1.1 异常的概念

异常也是一种处理错误的方式。
当一个函数发生异常后,就会将错误抛出,让该函数的直接或间接的调用者处理这个错误。

throw:当问题出现时,程序会抛出一个异常,通过throw关键字完成。

try:try 块中的代码标识将被激活的特定异常,激活的异常才会被抛出,后面跟着的一个或者多个catch块才能捕获该异常。
catch:在想要处理异常的地方,通过catch关键字捕获异常,然后执行相应的代码,可以有多个catch进行捕获。

写一个函数用来执行两个数相除,当除0时抛异常:

double Division(int a, int b)
{if (b == 0){throw "Divide by Zero Error";}else{return ((double)a / (double)b);}
}
  • throw抛出的异常必须是一个对象,可以是自定义类型的对象,也可以是内置类型的对象。

调用这个函数然后试着捕捉异常:

#include <iostream>
using namespace std;double Division(int a, int b)
{if (b == 0){throw "Divide by Zero Error";}else{return ((double)a / (double)b);}
}void Func()
{cout << Division(3, 0) << endl;
}int main()
{try{Func();}catch(const char* errmsg){cout << errmsg << endl;}catch (...){cout << "unknown exception" << endl;}return 0;
}

try块中的代码抛出异常,catch块捕获异常并处理。

  • try块中抛出异常后,只有一个catch块会捕获异常,也只执行一个catch块代码,执行完后跳过所有catch块继续执行后面的代码。
  • catch捕获异常时,会根据( )中的"形参"匹配抛出对象的类型。
  • 如果没有合适的catch匹配,"形参"为(…)的catch就会捕获抛出的任意异常。

1.2 异常的抛出和匹配原则

先把异常的抛出和匹配原则列出来,下面有代码例子。

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

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

③ 抛出异常对象后,会生成一个异常对象的拷贝,因为抛出的异常对象可能是一个临时对象, 所以会生成一个拷贝对象,这个拷贝的临时对象会在被catch以后销毁。(这里的处理类似 于函数的传值返回)

④ catch(...)可以捕获任意类型的异常,问题是不知道异常错误是什么。

⑤ 实际中抛出和捕获的匹配原则有个例外,并不都是类型完全匹配,可以抛出的派生类对象, 使用基类捕获,这个在实际中非常实用,我们后面会详细讲解这个。

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

上面代码发生除0错误时,抛出的异常改成int类型对象:

#include <iostream>
using namespace std;double Division(int a, int b)
{if (b == 0){//throw "Divide by Zero Error";throw 7;}else{return ((double)a / (double)b);}
}void Func()
{cout << Division(3, 0) << endl;
}int main()
{try{Func();}catch(const char* errmsg){cout << errmsg << endl;}catch (int errid){cout << "错误码: " << errid << endl;}catch (...){cout << "unknown exception" << endl;}return 0;
}

抛出的异常只匹配了catch(const int errid)块。

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

一般是这样的:

#include <iostream>
using namespace std;double Division(int a, int b)
{if (b == 0){//throw "Divide by Zero Error";throw 7;}else{return ((double)a / (double)b);}
}void Func()
{try{cout << Division(3, 0) << endl;}catch (const char* errmsg){cout << errmsg << endl;}catch (int errid){cout << "(Func中捕获)错误码: " << errid << endl;}
}int main()
{try{Func();}catch(const char* errmsg){cout << errmsg << endl;}catch (int errid){cout << "错误码: " << errid << endl;}catch (...){cout << "unknown exception" << endl;}return 0;
}

③ 抛出异常对象后,会生成一个异常对象的拷贝,因为抛出的异常对象可能是一个临时对象, 所以会生成一个拷贝对象,这个拷贝的临时对象会在被catch以后销毁。(这里的处理类似 于函数的传值返回)

④ catch(...)可以捕获任意类型的异常,问题是不知道异常错误是什么。

⑤ 实际中抛出和捕获的匹配原则有个例外,并不都是类型完全匹配,可以抛出的派生类对象, 使用基类捕获,这个在实际中非常实用,我们后面会详细讲解这个。

演示下④:

上面代码其它地方不变,throw一个键值对:

1.3 函数调用链中异常栈展开匹配原则

1. 首先检查throw本身是否在try块内部,如果是再查找匹配的catch语句。如果有匹配的,则
调到catch的地方进行处理。
2. 没有匹配的catch则退出当前函数栈,继续在调用函数的栈中进行查找匹配的catch。
3. 如果到达main函数的栈,依旧没有匹配的,则终止程序。上述这个沿着调用链查找匹配的
catch子句的过程称为栈展开。所以实际中我们最后都要加一个catch(...)捕获任意类型的异
常,否则当有异常没捕获,程序就会直接终止。
4. 找到匹配的catch子句并处理以后,会继续沿着catch子句后面继续执行。

1.4 异常的重新抛出

有可能单个的catch不能完全处理一个异常,在进行一些校正处理以后,希望再交给更外层的调用链函数来处理,catch则可以通过重新抛出将异常传递给更上层的函数进行处理。

看一段类似上面的代码,只是在Func函数里改动了,main函数最后打印个return 0;:

#include <iostream>
using namespace std;double Division(int a, int b)
{if (b == 0){throw "Divide by Zero Error";}else{return ((double)a / (double)b);}
}void Func()
{// 这里可以看到如果发生除0错误抛出异常,另外下面的array没有得到释放。int* array = new int[10];cout << Division(3, 0) << endl;cout << "delete []" << array << endl;delete[] array;
}int main()
{try{Func();}catch (const char* errmsg){cout << errmsg << endl;}catch (int errid){cout << "错误码: " << errid << endl;}catch (...){cout << "unknown exception" << endl;}cout << "return 0;" << endl;return 0;
}

 这种情况就会导致内存泄漏,所以就要使用异常的重新抛出,改下Func函数:

void Func()
{// 这里可以看到如果发生除0错误抛出异常,另外下面的array没有得到释放。// 所以这里捕获异常后并不处理异常,异常还是交给外面处理,这里捕获了再重新抛出去。int* array = new int[10];try {cout << Division(3, 0) << endl;}catch (...){cout << "delete []" << array << endl;delete[] array;throw; // 捕获什么抛出什么}cout << "delete []" << array << endl;delete[] array;
}

1.5 异常的安全问题

如果上面的Func函数new了三个空间呢,这样delete三次可以吗?:

void Func()
{int* array = new int[10];int* array2 = nullptr;try{array2 = new int[10];try{cout << Division(3, 0) << endl;}catch (...){cout << "delete []" << array << endl;delete[] array;cout << "delete2 []" << array2 << endl;delete[] array2;throw; // 捕获什么抛出什么}}catch (...){// 捕获new的异常}cout << "delete []" << array << endl;delete[] array;cout << "delete2 []" << array2 << endl;delete[] array2;
}

答案是不行的,new失败也是抛异常,如果前面new的成功,后面new失败了呢,前面的谁释放?

所以这里可以类似这样解决:

void Func()
{int* array = new int[10];int* array2 = nullptr;try{array2 = new int[10];try{cout << Division(3, 0) << endl;}catch (...){cout << "delete []" << array << endl;delete[] array;cout << "delete2 []" << array2 << endl;delete[] array2;throw; // 捕获什么抛出什么}}catch (...){// 捕获new的异常}cout << "delete []" << array << endl;delete[] array;cout << "delete2 []" << array2 << endl;delete[] array2;
}

这只是new两次就已经这么挫了,三次或更多次呢?,所以不适合这样解决,要使用到下一篇学的智能指针RAII解决。

异常安全:

构造函数完成对象的构造和初始化,最好不要在构造函数中抛出异常,否则可能导致对象不
完整或没有完全初始化

析构函数主要完成资源的清理,最好不要在析构函数内抛出异常,否则可能导致资源泄漏(内
存泄漏、句柄未关闭等)


C++中异常经常会导致资源泄漏的问题,比如在new和delete中抛出了异常,导致内存泄
漏,在lock和unlock之间抛出了异常导致死锁,C++经常使用RAII来解决以上问题。

1.6 C++98和C++11的异常规范

在C++中异常经常会导致资源泄漏,比如在new和delete中抛出了异常,导致内存泄漏等等。

尤其是调用非常复杂的时候,后面写程序的人使用了前面程序的接口,但是并不知道前面程序的接口会抛异常,或者是抛什么异常,此时后面的程序员就无法处理抛出的异常,就可能导致资源泄漏等问题。

为了减少因为异常而导致的资源泄漏等问题,C++98时,C++委员会提出了一套建议性规范:

① 在函数的后面接throw(类型),列出这个函数可能抛出的所有异常类型。

void func() throw(A, B, C, D);
// 表示这个函数会抛出A/B/C/D中的某种异常。void* operator new (std::size_t size) throw (std::bad_alloc);
// 这里表示这个函数只会抛出bad_alloc的异常

② 函数的后面接throw(),表示这个函数不抛异常。

void* operator delete(std::size_t size, void* ptr) throw();

③ 若无异常接口声明,则此函数可能抛出任何类型的异常。

void* operator delete(std::size_t size, void* ptr);

这个规范出发点是好的,可以让我们明确会抛出什么异常,进行相应的处理。

但是这个形式非常繁琐,throw(异常类型),有些异常类型是非常复杂的,为了写这个可能发生的异常类型,需要花费更多代价。

所以这个建议性的规范很少有人在用,因为它只是一个建议,而且有没有可能你跟我说你不会抛异常,但是你抛了呢?所以不使用也不会报错。

为了让异常声明更加简洁,C++11对此做出了相应的改进:

  • 在不会抛出异常的函数后加关键字:noexcept
thread() noexcept;
thread(thread&& x) noexcept;

上面代码表示这个两个函数不会抛出异常。这样一来确实简洁了许多,只是在会抛异常的函数中,需要我们自己搞明白会抛什么异常,这很考验写代码的人的素质,因为也只是个建议。所以这还是很少人用,但是建议大家尽量都跟着规范走。

2. 自定义异常体系

这里带着大家看看公司中自定义的异常体系的框架是怎么弄的,在其中讲讲异常的安全和规范等。

因为C++提供的异常体系不是很好用,实际使用中很多公司都会自定义自己的异常体系进行规范的异常管理,因为一个项目中如果大家随意抛异常,那外层的调用者基本就没办法玩了,所以实际中都会定义一套继承的规范体系。这样大家抛出的都是继承的派生类对象,捕获一个基类就可以了。

前面的⑤:实际中抛出和捕获的匹配原则有个例外,并不都是类型完全匹配,可以抛出的派生类对象, 使用基类捕获,这个在实际中非常实用。

这也是继承和多态的一个重要应用、

2.1 异常继承体系

服务器开发中通常使用的异常继承体系的父类:

class Exception
{
public:Exception(const string& errmsg, int id):_errmsg(errmsg), _id(id){}virtual string what() const{return _errmsg;}int getid() const{return _id;}
protected:string _errmsg;   // 错误信息int _id;          // 错误码
};

创建一个异常的基类,如上所示,包含异常信息和异常编号两个成员,注意到一个虚函数what。

做一个项目就会分很多个组,比如网络组,缓存组,数据库组等等,每个组都写一个异常的类,

这些类就会继承上面的类,服务器开发中通常使用的异常继承体系:

#include <iostream>
#include <string>
#include <windows.h> // Sleep
#include <time.h> // time
using namespace std;
// 服务器开发中通常使用的异常继承体系
class Exception
{
public:Exception(const string& errmsg, int id):_errmsg(errmsg), _id(id){}virtual string what() const{return _errmsg;}int getid() const{return _id;}protected:string _errmsg;   // 错误信息int _id;          // 错误码
};class SqlException : public Exception // 数据库层
{
public: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;}
protected:const string _sql;
};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;}
protected:// stack<string> _stPath; // 加上堆栈信息可以查看上下文
};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;}
protected:const string _type;
};void SQLMgr() // 下面的缓存层没出问题就调数据库层
{srand(time(0));if (rand() % 7 == 0){throw SqlException("权限不足", 100, "select * from name = '张三'");}cout << "本次请求成功" << endl; // 最后调用的,前面都没出异常
}
void CacheMgr() // 下面的网络层没出问题就调缓存层
{srand(time(0));if (rand() % 5 == 0){throw CacheException("权限不足", 200);}else if (rand() % 6 == 0){throw CacheException("数据不存在", 201);}SQLMgr();
}
void HttpServer() //网络层
{srand(time(0)); // 模拟if (rand() % 3 == 0){//throw HttpServerException("请求资源不存在", 100, "get");throw HttpServerException("网络错误", 100, "get");}else if (rand() % 4 == 0){throw HttpServerException("权限不足", 101, "post");}CacheMgr();
}int main()
{while (1){Sleep(1000);try{HttpServer(); // 先调用网络层}catch (const Exception& e) // 这里捕获父类对象就可以{// 多态cout << e.what() << endl;// 记录日志}catch (...) // 守住底线{cout << "unknown exception" << endl;}}return 0;
}

抛出的异常是不同类型的对象,但是它们都是派生类,继承基类Exception

当某个函数中抛出异常以后,后面的代码就不再执行了,直接去匹配相应的catch,当前栈帧中匹配不到就跳到上一层栈帧中去匹配。

在main函数中,将try块和catch块放在死循环中,每隔1s执行一次。

catch的类型和所有抛出的异常对象都不是一个类型,但是它是所有异常对象基类的引用。

此时就实现了多态,调用的只是基类Exception中的成员函数what(),但是执行的逻辑就是派生类中what()的逻辑。

从这里也可以看出异常存在的意义:

拿我们经常使用的微信来举例,当网络不好的时候,消息就会发不出去,此时程序就会抛一个异常,表示当前网络状态不佳。如果这个异常没有捕获,而且按照C语言对错误的处理方式,此时微信就崩了,直接退出。采用C++的异常处理机制,会将抛出的这个异常捕获,然后进行处理,比如尝试多次发送等等操作。重点是微信程序不会退出,仍然可以继续运行。

 在实际应用中,很多情况下我们是不希望程序产生异常就让它结束的,而是让它继续运行,并且将异常处理。

2.2 异常体系中的重新抛出

上面异常体系中的网络层:

void HttpServer() //网络层
{srand(time(0)); // 模拟if (rand() % 3 == 0){//throw HttpServerException("请求资源不存在", 100, "get");throw HttpServerException("网络错误", 100, "get");}else if (rand() % 4 == 0){throw HttpServerException("权限不足", 101, "post");}CacheMgr();
}

网络错误一次就抛出异常吗?不应该再重试几次吗,此时异常的重新抛出就派上用场了:

void SeedMsg(const string& s)
{// 要求出现网络错误重试三次srand(time(0));if (rand() % 3 == 0){throw HttpServerException("网络错误", 100, "get");}else if (rand() % 4 == 0){throw HttpServerException("权限不足", 101, "post");}cout << "发送成功: " << s << endl;
}
void HttpServer()
{// 要求出现网络错误,重试3次string str = "等下吃什么";int n = 3;while (n--){try{SeedMsg(str); // 重试3次// 没有发生异常break;}catch (const Exception& e){if (e.getid() == 100 && n > 0)// 网络错误 且 重试3次内{continue;}else{throw e; // 重新抛出}}}
}int main()
{while (1){Sleep(1000);try{HttpServer(); // 先调用网络层}catch (const Exception& e) // 这里捕获父类对象就可以{// 多态cout << e.what() << endl;// 记录日志}catch (...) // 守住底线{cout << "unknown exception" << endl;}}return 0;
}

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

C++标准库采用的就是抛派生类对象异常,捕获基类对象的方式,也是利用了继承和多态。

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

可以看到,C++标准库中,也是通过捕获基类对象来打印各种派生类的异常信息的,但是它的设计并不够好,信息表达不清楚等等,所以实际中很多公司都会像上面一样自定义一套异常继承体系。

4. C++异常的优缺点和总结

C++异常优点:

① 异常对象定义好了,相比错误码的方式可以清晰准确的展示出错误的各种信息,甚至可以包含堆栈调用的信息,这样可以帮助更好的定位程序的bug。


② 返回错误码的传统方式有个很大的问题就是,在函数调用链中,深层的函数返回了错误,那么我们得层层返回错误,最外层才能拿到错误,而C++异常会直接销毁栈帧去异常的上一层匹配catch。


③ 很多的第三方库都包含异常,比如boost、gtest、gmock等等常用的库,那么我们使用它们也需要使用异常。


④ 部分函数使用异常更好处理,比如构造函数没有返回值,不方便使用错误码方式处理。比如T& operator这样的函数,如果pos越界了只能使用异常或者终止程序处理,没办法通过返回值表示错误。

C++异常缺点:

① 异常会导致程序的执行流乱跳,并且非常的混乱,并且是运行时出错抛异常就会乱跳。这会导致我们跟踪调试时以及分析程序时,比较困难。


② 异常会有一些性能的开销。当然在现代硬件速度很快的情况下,这个影响基本忽略不计。


③ C++没有垃圾回收机制,资源需要自己管理。有了异常非常容易导致内存泄漏、死锁等异常安全问题。这个需要使用RAII来处理资源的管理问题。


④ C++标准库的异常体系定义得不好,导致大家各自定义各自的异常体系,非常的混乱。

⑤ 异常的规范只是建议,异常尽量规范使用,否则后果不堪设想,随意抛异常,外层捕获的用户苦不堪言。所以异常规范有两点:一、抛出异常类型都继承自一个基类。二、函数是否抛异常、抛什么异常,都使用 func() throw();的方式规范化。

总结:C++目前异常的缺点只有①比较麻烦,②也有RAII解决了,其它都是小问题,所以异常总体而言,利大于弊,所以工程中我们还是鼓励使用异常的。另外其它OO(Object Oriented)语言基本都是用异常处理错误,这也可以看出这是大势所趋。

5. 笔试选择题

1. 对关于异常说法不正确的是()

A.异常是程序处于一种非法的情况,严重时可能会导致程序崩溃

B.throw可以抛出任意类型的异常

C.异常产生的后果不严重时可以不用处理

D.对于throw所抛出的异常,必须要进行捕获,否则代码最后会崩溃

2. 下列关于异常处理的描述中,理解不正确的是: ()

A.C++语言的异常处理机制通过3个保留字throw、try和catch实现

B.任何需要检测的语句必须在try语句块中执行,并由throw语句抛出异常

C.throw语句抛出异常后,catch利用数据类型匹配进行异常捕获

D.一旦catch捕获异常,不能将异常用throw语句再次抛出

3. 如何捕获异常可以使得下面代码通过编译? ()

class A
{
public:A() {}
};void foo()
{throw new A;
}

A.catch (A x)

B.catch (A * x)

C.catch (A & x)

D.以上都不是

4. 对于异常的捕获列表说法不正确的是()

A.在捕获列表中可以使用基类的引用捕获所有子类的异常对象

B.捕获列表中捕获到的是异常本身

C.catch(...)可以捕获到任意类型的异常

D.捕获列表是按照抛出异常的类型进行捕获的

答案及解析

1. C

A:正确,异常是程序可能会有安全隐患,异常一旦发生,程序就是非法情况

B:正确,throw抛出的是某种类型的异常,捕获时要按照类型进行捕获

C:错误,只要程序中有异常存在,就必须要处理

D:正确

2. D

A:正确,参考异常的抛出与捕获

B:正确,对于有可能会抛出异常的代码,都应该放在try中尝试进行捕获,只有放在try中,throw 语句抛出的异常    才可能会捕获到

C:正确,所有异常都是按照类型进行捕获的

D:错误,有时捕获异常并不是为了处理异常,而是要做一些其他事情,做完后需要将异常重新抛出,交给该异常的处理位置去处理

3. B

异常是按照类型来捕获的,throw后抛出的是A*类型的异常,因此要按照指针方式进行捕获

4. B

A:该条描述不是很严谨,应该是用基类的const类型引用,可以捕获所有的子类的异常对象

B:错误,捕获列表中捕获到的是异常的一份拷贝,因为异常对象在出其函数作用域前要销毁掉

C:正确,catch(...)是万能的捕获方式,任意类型的异常都可以捕获到

D:正确,异常是按照类型捕获的

本篇完。

下一篇:(智能指针RAII)auto_ptr+unique_ptr+shared_ptr+weak_ptr。(使用和模拟实现)

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

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

相关文章

【allegro 17.4软件操作保姆级教程十二】插件器件封装制作

&#x1f449;个人主页&#xff1a; highman110 &#x1f449;作者简介&#xff1a;一名硬件工程师&#xff0c;持续学习&#xff0c;不断记录&#xff0c;保持思考&#xff0c;输出干货内容 目录 制作插件焊盘 放置pin脚 绘制丝印线和装配线 放置位号和value 放置1脚标识…

【VsCode】SSH远程连接Linux服务器开发,搭配cpolar内网穿透实现公网访问(1)

文章目录 前言1、安装OpenSSH2、vscode配置ssh3. 局域网测试连接远程服务器4. 公网远程连接4.1 ubuntu安装cpolar内网穿透4.2 创建隧道映射4.3 测试公网远程连接 5. 配置固定TCP端口地址5.1 保留一个固定TCP端口地址5.2 配置固定TCP端口地址5.3 测试固定公网地址远程 前言 远程…

CSS中如何实现文字阴影效果(text-shadow)?

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ 实现思路⭐ 示例⭐ 写在最后 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 记得点击上方或者右侧链接订阅本专栏哦 几何带你启航前端之旅 欢迎来到前端入门之旅&#xff01;这个专栏是为那些对Web开发感兴趣、刚刚踏入前…

Python采集关键词结果辅助写作

大家好&#xff01;在进行学术研究和 写作时&#xff0c;获取准确、全面的文献资料和相关研究成果是非常重要的。在本文中&#xff0c;我将与你分享使用Python爬虫 采集 学术关键词结果来辅助 写作的方法&#xff0c;帮助你快速获取与研究主题相关的学术文献和 。 **1. 设置搜索…

Pygame编程(9)font模块

Pygame编程&#xff08;9&#xff09;font模块 函数示例 函数 pygame.font.init 初始化字体模块init() -> None pygame.font.quit 反初始化字体模块quit() -> None pygame.font.get_init True,如果字体模块已初始化get_init() -> bool pygame.font.get_default_font …

【React学习】—SetState的使用(九)

【React学习】—SetState的使用&#xff08;九&#xff09; state的简写方式 state属性总结

兄弟,王者荣耀的段位排行榜是通过Redis实现的?

目录 一、排行榜设计方案1、数据库直接排序2、王者荣耀好友排行 二、Redis实现计数器1、什么是计数器功能&#xff1f;2、Redis实现计数器的原理&#xff08;1&#xff09;使用INCR命令实现计数器&#xff08;2&#xff09;使用INCRBY命令实现计数器 三、通过Redis实现“王者荣…

【Redis从头学-13】Redis哨兵模式解析以及搭建指南

&#x1f9d1;‍&#x1f4bb;作者名称&#xff1a;DaenCode &#x1f3a4;作者简介&#xff1a;啥技术都喜欢捣鼓捣鼓&#xff0c;喜欢分享技术、经验、生活。 &#x1f60e;人生感悟&#xff1a;尝尽人生百味&#xff0c;方知世间冷暖。 &#x1f4d6;所属专栏&#xff1a;Re…

8.7.tensorRT高级(3)封装系列-调试方法、思想讨论

目录 前言1. 模型调试技巧总结 前言 杜老师推出的 tensorRT从零起步高性能部署 课程&#xff0c;之前有看过一遍&#xff0c;但是没有做笔记&#xff0c;很多东西也忘了。这次重新撸一遍&#xff0c;顺便记记笔记。 本次课程学习 tensorRT 高级-调试方法、思想讨论 课程大纲可看…

渗透测试漏洞原理之---【XSS 跨站脚本攻击】

文章目录 1、跨站 脚本攻击1.1、漏洞描述1.2、漏洞原理1.3、漏洞危害1.4、漏洞验证1.5、漏洞分类1.5.1、反射性XSS1.5.2、存储型XSS1.5.3、DOM型XSS 2、XSS攻防2.1、XSS构造2.1.1、利用<>2.1.2、JavaScript伪协议2.1.3、时间响应 2.2、XSS变形方式2.2.1、大小写转换2.2.2…

开源与专有软件:比较与对比

&#x1f337;&#x1f341; 博主猫头虎 带您 Go to New World.✨&#x1f341; &#x1f984; 博客首页——猫头虎的博客&#x1f390; &#x1f433;《面试题大全专栏》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33a; &a…

框架分析(6)-Ruby on Rails

框架分析&#xff08;6&#xff09;-Ruby on Rails 专栏介绍Ruby on Rails核心概念以及组件讲解MVC架构模式约定优于配置强大的ORM支持自动化测试丰富的插件生态系统RESTful路由安全性总结 优缺点优点快速开发简单易学MVC架构强大的ORM支持大量的插件和Gem支持 缺点性能问题学习…

【ubuntu】 DNS 设置工具 resolvectl

什么是 resolvectl “resolvectl” 是一个用于管理系统 DNS 解析配置的命令行工具。它是 systemd-resolved 服务的一部分&#xff0c;该服务是在许多基于 Systemd 的 Linux 发行版中用于管理网络配置和 DNS 解析的系统服务。 通过 resolvectl 命令&#xff0c;可以查看当前系…

SpringAOP详解(上)

当需要在方法前后做一些操作就需要借助动态代理来实现 一、动态代理实现方法 1、jdk自带实现方式 jdk实现代理是被代理类实现接口的方式 public interface UserInterface {void test(); }public class UserService implements UserInterface {public void test() {System.o…

spring boot集成redis

第一步&#xff1a;添加maven依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency> 第二步&#xff1a;配置applicaiton.properties文件 #redis的ip地址…

PHP聚合支付网站源码/对接十多个支付接口 第三方/第四方支付/系统源码

PHP聚合支付网站源码/对接十多个支付接口 第三方/第四方支付/系统源码 内附数十个支付接口代码文件。 下载地址&#xff1a;https://bbs.csdn.net/topics/616764485

vue+file-saver+xlsx+htmlToPdf+jspdf实现本地导出PDF和Excel

页面效果如下&#xff08;echarts图表按需添加&#xff0c;以下代码中没有&#xff09; 1、安装插件 npm install xlsx --save npm install file-saver --save npm install html2canvas --save npm install jspdf --save2、main.js引入html2canvas import htmlToPdf from …

快速学会创建uni-app项目并了解pages.json文件

(创作不易&#xff0c;感谢有你&#xff0c;你的支持&#xff0c;就是我前行的最大动力&#xff0c;如果看完对你有帮助&#xff0c;请留下您的足迹&#xff09; 目录 前言 创建 uni-app 项目 通过 HBuilderX 创建 pages.json pages style globalStyle tabBar 前言…

AI时代,程序员需要焦虑吗?

原文来自 微信公众号"互联网技术人进阶之路". 目录 前言一、程序员会被 AI 取代么&#xff1f;二、服务端开发尚难被 AI 取代三、服务端开发何去何从&#xff1f;四、业界首部体系化、全景式解读服务端开发的著作第一部分&#xff1a;服务端开发的技术和方法第二部分…

tomcat更改端口号和隐藏端口号

因为默认端口:8080不会自动隐藏&#xff0c;因此为了更显格调需要将其改为:80 进入tomcat的server文件 将其改为80&#xff0c;之后将tomcat重新启动即可 tomcat启动流程 [rootshang ~]# cd /usr/local/tomcat/apache-tomcat-8.5.92 [rootshang apache-tomcat-8.5.92]# cd b…