【C++ 第十九章】异常

news2024/9/20 10:38:37

在这里插入图片描述



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

传统的错误处理机制:

  1. 终止程序,如 assert,缺陷:用户难以接受。如发生内存错误、除 0 错误时就会终止程序。

  2. 返回错误码,缺陷:需要程序员自己去查找对应的错误。如系统的很多库的接口函数都是通过把错误码放到 errno 中,表示错误

实际中C语言基本都是使用返回错误码的方式处理错误,部分情况下使用终止程序处理非常严重的错误。



2. C++异常概念

异常是一种处理错误的方式,当一个函数发现自己无法处理的错误时就可以抛出异常,让函数的
直接或间接的调用者处理这个错误

  • throw:
    当问题出现时,程序会抛出一个异常。这是通过使用 throw 关键字来完成的。
  • catch:
    在您想要处理问题的地方,通过异常处理程序捕获异常,catch 关键字用于捕获异常,可以有多个catch进行捕获。
  • try:
    try 块中的代码标识将被激活的特定异常,它后面通常跟着一个或多个 catch 块。

如果有一个块抛出一个异常,捕获异常的方法会使用 try 和 catch 关键字。

try 块中放置可能抛出异常的代码,try 块中的代码被称为保护代码。
catch 块中放置对异常识别匹配异常类型及其异常处理代码。


使用 try/catch 语句的语法如下所示:

try
{
  // 保护的标识代码
}
catch( ExceptionName e1 )
{
  // catch 块
}
catch( ExceptionName e2 )
{
  // catch 块
}
catch( ExceptionName eN )
{
  // catch 块
}



3. 异常的使用

3.1 异常的抛出和捕获

异常的抛出和匹配原则

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

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

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

若程序终止,直接报错:

在这里插入图片描述

  1. 找到匹配的 catch 子句并处理以后,会继续沿着catch子句后面继续执行。




在这里插入图片描述



第 1 点:关于 throw 语句在 try 块内部与外部的表现

void Func() {
	// 在 当前函数 try 块外部:不匹配该函数内部的 catch
	// 该 throw:匹配main函数的 catch
	throw "AAAA";

	try {
		//  在 当前函数 try 块外部:匹配该函数内部的 catch
		// 该 throw:匹配 Func 函数的 catch(即下面这个)
		throw "hehe";
	}
	catch (const char* str) {
		cout << "Func:" << str << '\n';
	}
}


int main() {
	try {
		Func();
	}
	catch (const char* str) {
		cout << "main:" << str << '\n';
	}
	return 0;
}

第 4 点:找到匹配的 catch 子句并处理以后,会继续沿着 catch 子句后面继续执行。

void Func() {
	throw "AAAA";
}


int main() {
	try {
		Func();
	}
	catch (const char* str) {
		cout << "main:" << str << '\n';
	}

	// 匹配并处理 catch后,继续执行程序之后的语句
	cout << "hahaha" << '\n'; 

	return 0;
}

在这里插入图片描述


建议:一般 try / catch 语句会放到 main 中,而不是其他子函数,以 main 函数作为异常处理的最终站


抛出异常的代码使用演示:

定义除法函数,当分母输入 0 时,抛出异常,被 Func 函数的 catch 捕捉,并打印该报错字符串

double Division(int a, int b) {
	if (b == 0)
		throw "Division by zero condition !";  // throw 任意类型,然后到函数调用的上一层去找匹配该类型的 catch,一路上一直匹配(匹配到了就停下执行 try/catch语句),直到回到 main,若还找不到则要写 catch(...) 未知错误
	else
		return ((double)a / (double)b);
}

void Func() {
	int a, b;
	cin >> a >> b;

	try
	{
		cout << Division(a, b) << '\n';
	}
	catch (const char* str) {
		cout << "Func:" << str << '\n';
	}
	catch (const int x) {
		cout << x << '\n';
	}
}

int main() {

	try
	{
		Func();
	}
	catch (const char* str) {
		cout << "main:" <<  str << '\n';
	}
	catch (const int x) {
		cout << x << '\n';
	}
	catch (...) {  // 捕捉任何前面 catch 不能捕捉的异常:即未知异常
		cout << "unkown exception" << '\n';
	}

	// 当上面的 try/catch 语句执行完后,会继续执行 catch 后面的语句
	cout << "继续执行 catch 后面的语句" <<  '\n';
	return 0;
}

在这里插入图片描述




3.2 异常的重新抛出


前面抛出异常的使用可以发现一个问题:主要是 异常的抛出执行流,影响了 原先程序执行流的执行

当前函数抛出异常,相当于 return,会直接结束当前函数,跳转下一个层函数,而不执行当前函数的 throw 语句后面的代码(程序不正常执行完,会出事的!!)


⭐可能造成内存泄漏:

若当前函数后段代码时用于 释放空间资源的,此时直接被 throw 中断跳过,导致不能释放空间,而内存泄漏

写入 catch(…) 则接收任何异常,强制执行 资源释放,然后在继续 throw 抛出异常(相当于抛异常的过程中,中途截断了一下,再继续抛出)

这里的 catch(…) 不是为了处理异常,而是间接释放资源

double Division(int a, int b) {
	if (b == 0)
		throw "Division by zero condition !";  // throw 任意类型,然后到函数调用的上一层去找匹配该类型的 catch,一路上一直匹配(匹配到了就停下执行 try/catch语句),直到回到 main,若还找不到则要写 catch(...) 未知错误
	else
		return ((double)a / (double)b);
}

void Func()
{
	// 这里使用 空间
	int* array = new int[10];

	try
	{
		int len, time;
		cin >> len >> time;
		cout << Division(len, time) << endl;
	}
	catch (...)
	{
		cout << "delete []" << array << endl;
		delete[] array;

		throw; // 异常重新抛出,捕获到什么抛出什么
	}

	// 如果 Division 函数抛出异常,则相当于 return 结束该函数,而不会执行下面的 delete[] 
	// 解决方案:若写入 catch(...) 则接收任何异常,强制执行 资源释放,然后在继续 throw 抛出异常(相当于抛异常的过程中,中途截断了一下,再继续抛出v)
	// 这里的 catch(...) 不是为了处理异常,而是间接释放资源
	cout << "delete []" << array << endl;
	delete[] array;
}



这里还有一个坑:new 空间开辟失败会抛出异常

如果中途定义的数组 空间开辟失败,他自己也会抛出异常,结束当前函数,也就造成 array1 无法执行下面的空间释放程序

void Func()
{
	int* array1 = new int[10];
	int* array2 = new int[10];  // 如果这里空间开辟失败抛异常,则导致下面无法资源释放,内存泄漏

	//....
}

我们下面自己抛出一个异常,模拟空间开辟失败抛异常

double Division(int a, int b) {
	if (b == 0)
		throw "Division by zero condition !";  // throw 任意类型,然后到函数调用的上一层去找匹配该类型的 catch,一路上一直匹配(匹配到了就停下执行 try/catch语句),直到回到 main,若还找不到则要写 catch(...) 未知错误
	else
		return ((double)a / (double)b);
}

void Func()
{
	// 这里使用 空间
	int* array1 = new int[10];
	//int* array2 = new int[10];  
	throw "array2 空间开辟失败"; // 假设定义的 array2 空间开辟失败,抛出一个异常


	try
	{
		int len, time;
		cin >> len >> time;
		cout << Division(len, time) << endl;
	}
	catch (...)
	{
		cout << "delete []" << array1 << endl;
		delete[] array1;

		throw; // 异常重新抛出,捕获到什么抛出什么
	}

	cout << "delete []" << array1 << endl;
	delete[] array1;
}

int main() {

	try
	{
		Func();
	}
	catch (const char* str) {
		cout << "main:" <<  str << '\n';
	}
	catch (const int x) {
		cout << x << '\n';
	}
	catch (...) {  // 捕捉任何前面 catch 不能捕捉的异常:即未知异常
		cout << "unkown exception" << '\n';
	}

	return 0;
}

如果数组被正常释放,则会打印 delete[],显然没有,只是到 main 函数捕捉了 array2 抛出的异常


在这里插入图片描述




这种情况就可以使用 智能指针 处理,让智能指针管理该对象的释放(下一章讲解智能指针)



【总结】重新抛出异常 的使用

1、需要特殊处理且一次处理不完:需要对某个异常特殊处理,则先 catch,如果重新处理不了,则将 该异常继续抛出

2、需要释放资源:抛出异常的途中,有些资源需要释放,则 catch(…) 处理一下,再继续抛出 throw;





3.3异常安全

既然抛出异常会导致某段程序执行中断,就要注意以下情况

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

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

C++中异常经常会导致资源泄漏的问题,比如在 new 和 delete 中抛出了异常,导致内存泄
漏,在 lock 和 unlock 之间抛出了异常导致死锁,C++ 经常使用 RAII 来解决以上问题,关于 RAII
我们智能指针这节进行讲解。



3.4 异常规范(noexcept)

  1. 异常规格说明的目的是为了让函数使用者知道该函数可能抛出的异常有哪些。 可以在函数的
    后面接throw(类型),列出这个函数可能抛掷的所有异常类型。
  2. 函数的后面接throw(),表示函数不抛异常。
  3. 若无异常接口声明,则此函数可以抛掷任何类型的异常。

// 这里表示这个函数会抛出A/B/C/D中的某种类型的异常
void fun() throw(A,B,C,D);
// 这里表示这个函数只会抛出bad_alloc的异常
void* operator new (std::size_t size) throw (std::bad_alloc);
// 这里表示这个函数不会抛出异常
void* operator delete (std::size_t size, void* ptr) throw();
// C++11 中新增的 noexcept,表示不会抛异常
thread() noexcept; // 写上 noexcept 表示这里一定没有异常,如果有异常,但也写了这个,会报警告



4.自定义异常体系

在这里插入图片描述

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


前面讲解:异常捕捉,类型一定要严格匹配,不出现类型转换

但是存在一个例外:可以抛出子类对象异常,使用父类捕获(这个很实用)

比如我们设计一个 包含一些基础异常信息的父类

上层程序都将抛出的异常封装到该父类的子类

下层程序使用父类指针匹配 catch 抛出包含异常信息的子类或父类对象或未知异常,这样既能够 catch 到已知的所有异常,并且能够通过捕捉到的 对象,获得到许多信息


例如:聊天服务器开发中通常使用的异常继承体系


(1)我们这里模拟聊天信息的发送,发送信息有两种失败可能:1、网络不稳定,则抛出异常,并重试三次;2、对方不是你的好友;

(2)若发送成功,则调用 CacheMsg 函数存入存储,存储失败也有两种可能:1、权限不足;2、数据不存在;

(3)抛出异常对象,会被 main 函数的 catch 捕捉,利用多态,分析是发送信息异常对象 or 数据存储异常对象,并解析异常信息,打印成日志

main 函数中 catch 只需设置成 父类指针或引用就能接收到 来自不同子类的对象,使得异常的捕获方便许多

因为业务开发,服务通常一直运行不间断,因此我这里直接设置 while(1) 死循环,一直运行

#include<thread>
#include<time.h>
#include<windows.h>

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

// 包含异常信息的父类
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;
};


// 下面开发业务中的所有功能模块类(CacheException、HttpServerException)都继承上面这个 父类
// 以父类为基础,自己再添加本功能模块 需要抛出的异常信息
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;
	}
};

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;
};



void CacheMgr()
{
	if (rand() % 5 == 0)
	{
		throw CacheException("权限不足,无法存储", 100);
	}
	else if (rand() % 6 == 0)
	{
		throw CacheException("数据不存在,存储失败", 101);
	}
}

void sendmsg(const string& s)
{
	if (rand() % 2 == 0)
	{
		throw HttpServerException("网络不稳定,发送失败", 102, "put");
	}
	else if (rand() % 3 == 0)
	{
		throw HttpServerException("你已经不是对象的好友,发送失败", 103, "put");
	}
	else
	{
		cout << "发送成功" << "\n\n";
	}
}

void HttpServer()
{
	// 失败以后,再重试3次
	// 发送 1 次 + 重试3次 = 4次
	for (size_t i = 0; i < 4; i++)
	{
		try
		{
			// 调用发送信息模块
			sendmsg("今天一起看电影吧");

			break; // 若发送成功,这里就会直接 break; 若发送不成功,sendmsg 函数里面会抛异常,并下面这个 catch 捕获:若信息发送不成功是 102 号网络问题,则会重试(即继续for循环)
		}
		catch (const Exception& e)
		{
			if (e.getid() == 102) // 这里反映错误id 的用处:针对某种错误特殊处理(而不用匹配错误字符串码)
			{
				if (i == 3)  // 重试 3 次都没有成功,则抛异常(也相当结束当前)
					throw e;

				cout << "开始第" << i + 1 << "重试" << endl;
			}
			else
			{
				throw e;   // 只要不是 102号错误,将捕获的异常重新抛出:相当于发送失败,不重试了,并把错误信息再次抛出,到 main 函数处再解析
			}
		}
	}

	CacheMgr();  // 信息发送成功后,把信息存入存储
}


int main()
{
	srand(time(0));
	int i = 0; // 表示当前是第几轮发送信息

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

		cout << "|——————开始第 " << ++i << " 轮发送信息测试——————|" << endl;

		try
		{
			// 请求网络服务:发送聊天信息
			HttpServer();
		}
		catch (const Exception& e) // 这里捕获父类对象就可以
		{

			// 捕获异常,解析异常对象中的 异常信息,同时打印当前时间

			using std::chrono::system_clock;
			// 多态
			system_clock::time_point today = system_clock::now(); // 获取当前时间
			std::time_t tt = system_clock::to_time_t(today);
			cout << ctime(&tt) << e.what() << "\n\n\n";

		}
		catch (...)
		{
			cout << "Unkown Exception" << endl;
		}
	}

	return 0;
}



运行演示

请添加图片描述


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

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


在这里插入图片描述


说明:实际中我们可以可以去继承exception类实现自己的异常类。但是实际中很多公司像上面一 样自己定义一套异常继承体系,而不会使用库中的异常。因为C++标准库设计的不够好用doge
int main()
{
	try {
		vector<int> v(10, 5);
		// 这里如果系统内存不够也会抛异常
		v.reserve(1000000000);
		// 这里越界会抛异常
		v.at(10) = 100;
	}
	catch (const exception& e) // 这里捕获父类对象就可以
	{
		cout << e.what() << endl;
	}
	catch (...)
	{
		cout << "Unkown Exception" << endl;
	}
	return 0;
}



6.异常的优缺点

C++异常的优点:

  1. 异常对象定义好了,相比错误码的方式可以清晰准确的展示出错误的各种信息,甚至可以包
    含堆栈调用的信息,这样可以帮助更好的定位程序的 bug。
  2. 返回错误码的传统方式有个很大的问题就是,在函数调用链中,深层的函数返回了错误,那
    么我们得层层返回错误,最外层才能拿到错误,具体看下面的详细解释。
// 1.下面这段伪代码我们可以看到ConnnectSql中出错了,先返回给ServerStart,
ServerStart再返回给main函数,main函数再针对问题处理具体的错误。
// 2.如果是异常体系,不管是ConnnectSql还是ServerStart及调用函数出错,都不用检查,因
为抛出的异常异常会直接跳到main函数中catch捕获的地方,main函数直接处理错误。
int ConnnectSql()
{
	// 用户名密码错误
	if (...)
		return 1;

	// 权限不足
	if (...)
		return 2;
}

int ServerStart() {
	if (int ret = ConnnectSql() < 0)
		return ret;
	int fd = socket()
		if(fd < 0return errno;
}

int main()
{
	if (ServerStart() < 0)
		...

		return 0;
}
  1. 很多的第三方库都包含异常,比如boost、gtest、gmock等等常用的库,那么我们使用它们
    需要使用异常。

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



C++异常的缺点:

  1. 异常会导致程序的执行流乱跳,并且非常的混乱,并且是运行时出错抛异常就会乱跳。这会
    导致我们跟踪调试时以及分析程序时,比较困难。
  2. 异常会有一些性能的开销。当然在现代硬件速度很快的情况下,这个影响基本忽略不计。
  3. C++没有垃圾回收机制,资源需要自己管理。有了异常非常容易导致内存泄漏、死锁等异常安全问题。这个需要使用RAII来处理资源的管理问题。学习成本较高。
  4. C++标准库的异常体系定义得不好,导致大家各自定义各自的异常体系,非常的混乱。
  5. 异常尽量规范使用,否则后果不堪设想,随意抛异常,外层捕获的用户苦不堪言。所以异常
    规范有两点:一、抛出异常类型都继承自一个基类。二、函数是否抛异常、抛什么异常,都
    使用 func() throw();的方式规范化。(公司中为了规范,一般都会要求做这些规范,如
    果你确定这里不会抛异常就加上 noexcept)

总结:异常总体而言,利大于弊,所以工程中我们还是鼓励使用异常的。另外OO的语言基本都是用异常处理错误,这也可以看出这是大势所趋。

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

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

相关文章

趣解网络安全专业术语(保密性、暴露)零基础入门到精通,收藏这一篇就够了

保密性confidentiality 使信息不泄露给未授权的个人、实体、进程&#xff0c;或不被其利用的特性。 想象一下&#xff0c;你有一个神秘的盒子&#xff0c;里面装满了你最珍贵的秘密。这个盒子有一个特殊的锁&#xff0c;只有你和你最亲密的朋友能打开它。这个锁特别聪明&#…

新一代交互模式:LUICUIVUI

随着技术的发展&#xff0c;特别是人工智能和机器学习的进步&#xff0c;交互方式也在不断演变。以下是一些新概念&#xff0c;它们描述了当下和未来可能的交互方式&#xff1a; Conversational UI (CUI)&#xff1a; 以对话为基础的用户界面&#xff0c;用户通过自然语言与系统…

Moveit2 Move Group C++ 接口

系列文章目录 留空 文章目录 系列文章目录前言一、完整代码二、编写步骤三、代码分析1. 引入必要的头文件2. 初始化和配置 ROS2 环境3. 设置 MoveIt 规划组和场景4. 可视化5. 获取基本信息6. 开始演示7. 规划姿态目标8. 可视化计划路径9. 移动到姿势目标10. 规划关节空间目标1…

OpenAI发布GPT-4o mini,3.5从此退出历史舞台?

随着OpenAI在2024年7月18日正式发布GPT-4o Mini&#xff0c;无疑在科技界引发了一场新的风暴。这一创新不仅标志着GPT-3.5模型正式退出历史舞台&#xff0c;更预示着人工智能在自然语言处理领域迈入了一个全新的时代。 之前速度最快的模型一直是GPT3.5&#xff0c;随着后来的GP…

数据结构——开篇

一、数据结构&#xff08;内存中&#xff09; 1、定义 用来保存一种或多种特定关系的数据的集合&#xff08;组织和存储数据&#xff09;。 程序 数据结构算法 2、特定关系 &#xff08;1&#xff09;逻辑结构 数据元素与元素之间的关系。 分类&#xff1a;①集合&…

电脑找不到x3daudio1_7.dll怎么解决?5种方法科学修复x3daudio1_7.dll

如果在使用电脑过程中遇到“找不到x3daudio1_7.dll”的错误&#xff0c;这通常意味着您的系统缺少一个关键组件&#xff0c;它是与 Microsoft DirectX 相关的一个文件&#xff0c;主要用于处理高级音频功能&#xff0c;尤其是在游戏和其他多媒体应用程序中。其实这个问题通常可…

传统CV算法——基于Opencv的多目标追踪算法

基于 OpenCV 的跟踪算法有多种&#xff0c;每种算法都有其特定的应用场景和优缺点。以下是一些常见的基于 OpenCV 的目标跟踪算法&#xff1a; 1. BOOSTING 跟踪器 描述&#xff1a;基于 AdaBoost 算法的跟踪器。它是一种早期的跟踪算法&#xff0c;使用的是基于弱分类器的强…

归并、计数排序(画图详解)

归并排序&#xff1a; 基本思想&#xff1a;先递归再回归&#xff0c;在回归的时候进行归并排序 归并排序&#xff1a; 适用于两个有序数组&#xff0c;合并成一个数组的时候&#xff0c;也就是先要递归&#xff0c;递归到最后就相当于一个元素&#xff0c;一个元素就是有序的。…

Unity数据持久化 之 二进制存储法

本文仅作笔记学习和分享&#xff0c;不用做任何商业用途 本文包括但不限于unity官方手册&#xff0c;unity唐老狮等教程知识&#xff0c;如有不足还请斧正​​ 前置知识&#xff1a;1 Byte 8 bit &#xff0c;所以0000 00001 就是一个字节&#xff0c; 该串数字转为十进制代表1…

通过cmd命令的方式转码MP4为webp动图。附带命令解释。

zihao 通过cmd命令的方式转码MP4为webp动图&#xff1a; 均衡大小和z效果的配置&#xff08;直接拷贝后需要改下路径&#xff09;&#xff1a; ffmpeg -i E:\steam\222.mp4 -vcodec libwebp -filter:v fpsfps24 -lossless 0 -compression_level 5 -q:v 35 -loop 1 -preset def…

深入浅出Promise,循序渐进掌握JavaScript异步编程

一. Promise基本用法 Promise 是 JavaScript 中处理异步操作的一种方式。它是一个对象&#xff0c;代表了一个异步操作的最终完成或失败的结果。 Promise 有三种状态&#xff1a; pending &#xff08;进行中&#xff09;、 fulfilled &#xff08;已成功&#xff09; 和 rej…

如何在SQL Server中恢复多个数据库?

一次性恢复多个 SQL数据库吗可以吗&#xff1f; "是的&#xff0c;可以一次性恢复多个 SQL 数据库。通常情况下&#xff0c;只要备份文件的名称与相应的数据库匹配&#xff0c;且没有附加的日期或时间信息&#xff0c;就可以通过有效的 T-SQL 脚本来完成恢复。如果你希望…

虚幻引擎VR游戏开发03| 键位映射

Enhanced input mapping 按键映射 在虚幻引擎&#xff08;Unreal Engine&#xff09;中&#xff0c;Enhanced Input Mapping 是一个用于管理和处理输入&#xff08;例如键盘、鼠标、手柄等&#xff09;的系统。它提供了一种更灵活、更强大的方式来定义和响应用户输入&#xff…

MMO移动同步(1)

多个客户端同时连入游戏 这篇会从以下五个部分讲解&#xff1a; 同步的基本概念 完善角色进入及离开处理 CharacterManager(C/S) EntityManager(C/S) 打包运行Win客户端 同步基本概念 同步&#xff1a;角色信息&#xff0c;位置&#xff0c;状态同步&#xff1b;客户端和…

神仙公司名单(北京篇)

欢迎来到小落科技每日分享频道 大家好&#xff0c;秋招已经火热进行中了&#xff0c;不知道大家准备得怎么样了&#xff1f;特别是咱们25届的小伙伴们&#xff0c;有没有找到心仪的目标&#xff1f; 想必大家最近和我一样&#xff0c;忙着在各种招聘平台上搜罗信息&#xff0c…

如何在 Cursor 中使用驭码CodeRider?

驭码CodeRider 是极狐GitLab 公司自研发布的 AIGC 产品&#xff0c;可以用来进行 AI 编程和 DevOps 流程处理。本文分享如何在 Cursor 中使用驭码CodeRider。 Cursor 是近期比较火爆的一款 AI 代码编辑器&#xff0c;通过将 AI 能力引入软件研发来提升软件研发效率。而驭码Cod…

水凝胶透镜是什么?能用来干啥?

大家好&#xff0c;今天我们来了解一项关于蛋白质驱动的水凝胶透镜的研究——《Toward Tunable Protein‐Driven Hydrogel Lens》发表于《Advanced Science》。我们的眼睛晶状体主要由蛋白质构成&#xff0c;在视觉中起重要作用。但人造光学系统要实现类似功能却不容易。近年来…

【设计文档】数据库设计说明书(Word实际项目案例参考)

一、 总述 &#xff08;一&#xff09; 编写目的 二、 外部设计 &#xff08;一&#xff09; 环境说明 &#xff08;二&#xff09; 指导 三、 物理实现 &#xff08;一&#xff09; 物理结构 &#xff08;二&#xff09; 安全设计 四、 表设计结构 &#xff08;一&am…

【软件文档】软件系统试运行方案、试运行报告(Word项目实际原件)

一、 试运行目的 &#xff08;一&#xff09; 系统功能、性能与稳定性考核 &#xff08;二&#xff09; 系统在各种环境和工况条件下的工作稳定性和可靠性 &#xff08;三&#xff09; 检验系统实际应用效果和应用功能的完善 &#xff08;四&#xff09; 健全系统运行管理体制&…

【数字人】Facevid2vid:用于视频会议的一次性自由视图说话头合成

论文&#xff1a;https://arxiv.org/pdf/2011.15126 github:GitHub - zhanglonghao1992/One-Shot_Free-View_Neural_Talking_Head_Synthesis: Pytorch implementation of paper "One-Shot Free-View Neural Talking-Head Synthesis for Video Conferencing" 一种新颖…