C++『异常』

news2024/11/29 13:42:26

✨个人主页: 北 海
🎉所属专栏: 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
		{
			// 除数(分母)不能为 0
			assert(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.coloradmin.cn/o/1299928.html

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

相关文章

perl单行命令统计项目中代码单行过长的信息

项目中单行代码太长是不便于阅读和维护的&#xff0c;这里用perl单行命令实现项目中的单行过长的代码信息统计&#xff0c;方便修改。为方便说明&#xff0c;这里以一个开源项目为例&#xff0c;github链接evpp。以commit id 477033f938fd47dfecde43c82257cd286d9fa38e 为例&am…

陀螺仪LSM6DSV16X与AI集成(4)----Qvar触摸电容配置

陀螺仪LSM6DSV16X与AI集成.4--Qvar触摸电容配置 概述视频教学样品申请源码下载生成STM32CUBEMX串口配置IIC配置CS和SA0设置串口重定向参考程序初始换管脚获取ID复位操作BDU设置Qvar 功能的实现和配置设置量程和速率配置过滤链激活 Qvar 功能获取Qvar数据演示 概述 Qvar&#x…

2-5、包含多个段的程序

语雀原文链接 文章目录 1、概述2、代码段中使用数据示例1&#xff1a;不指定程序入口示例2&#xff1a;指定程序入口原理梳理 3、在代码段中使用栈例子1例子2 4、数据、代码、栈放入不同的段例子1&#xff1a;end start指定程序入口第一步&#xff1a;设置栈顶第二步&#xff…

Golang channle(管道)基本介绍、快速入门

channel(管道)-基本介绍 为什么需要channel&#xff1f;前面使用全局变量加锁同步来解决goroutine的通讯&#xff0c;但不完美 1)主线程在等待所有goroutine全部完成的时间很难确定&#xff0c;我们这里设置10秒&#xff0c;仅仅是估算。 2)如果主线程休眠时间长了&#xff0c…

tgf - 一个开箱即用的golang游戏服务器框架

tgf框架 tgf框架是使用golang开发的一套游戏分布式框架.属于开箱即用的项目框架,目前适用于中小型团队,独立开发者,快速开发使用.框架提供了一整套开发工具,并且定义了模块开发规范.开发者只需要关注业务逻辑即可,无需关心用户并发和节点状态等复杂情况. 使用介绍 创建业务逻辑…

m1通过源码编译xgboost4j的jar

1、下载源码 git clone --recursive https://github.com/dmlc/xgboost cd xgboost 编译xgboost的动态链接库dylib&#xff0c;m1源码编译xgboost的动态链接库dylib文件 2、编译XGBoost的jar文件&#xff1a; A、如果没有安装maven可以通过以下命令进行安装&#xff0c;如果安…

邮件营销软件:10个创新邮件营销策略,提升投资回报率(一)

电子商务和电子邮件营销密不可分。尽管电子商务在蓬勃发展&#xff0c;而很多人对邮件营销颇有微词。但是在电子商务中&#xff0c;邮件营销的确是一种有效营销方式。在本文中&#xff0c;我们将讨论一下邮件营销在电子商务中的有效运用&#xff0c;帮助您的企业在今年尽可能地…

045:Vue读取本地上传JSON文件,导出JSON文件方法

第045个 查看专栏目录: VUE ------ element UI 专栏目标 在vue和element UI联合技术栈的操控下&#xff0c;本专栏提供行之有效的源代码示例和信息点介绍&#xff0c;做到灵活运用。 &#xff08;1&#xff09;提供vue2的一些基本操作&#xff1a;安装、引用&#xff0c;模板使…

HibernateJPA快速搭建

1. 先创建一个普通Maven工程&#xff0c;导入依赖 <dependencies><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version><scope>test</scope></dependency><depe…

windows 10多用户同时远程登陆配置【笔记】

系统环境&多用户访问情况&#xff1a; 1、【win】【R】键入【gpedit.msc】 2、依次选择【计算机配置】→ 【管理模板】 → 【Windows组件】 → 【远程桌面服务】 → 【远程桌面会话主机】 →【连接】 2.1、右键 【允许用户通过使用远程桌面服务进行远程连接】 编辑 …

Go语言基础知识学习(一)

Go基本数据类型 bool bool型值可以为true或者false,例子&#xff1a; var b bool true数值型 类型表示范围int8有符号8位整型-128 ~ 127int16有符号16位整型-32768 ~ 32767int32有符号32位整型-2147783648 ~ 2147483647int64有符号64位整型uint8无符号8位整型0 ~ 255uint16…

linux通过命令切换用户

在Linux中&#xff0c;你可以使用su&#xff08;substitute user或switch user&#xff09;命令来切换用户。这个命令允许你临时或永久地以另一个用户的身份运行命令。以下是基本的用法&#xff1a; 基本切换到另一个用户&#xff08;需要密码&#xff09;&#xff1a;su [用户…

C# 静态构造函数与类的初始化

静态构造函数&#xff1a; 基本概念&#xff1a; 静态构造函数用于初始化任何静态数据。 静态构造函数的常见特性&#xff1a; 静态构造函数不使用访问修饰符或不具有参数。因为静态构造函数由系统调用&#xff0c;无法人为调用&#xff0c;所以就不存在public、private等。…

Gazebo 跟踪8字形和U形轨迹(1) — 错误处理

Gazebo 跟踪8字形和U形轨迹(1) — 错误处理 整个过程还是比较曲折的&#xff0c;主要都是一些细小的问题&#xff0c;跑了很多遍模型才发现 参考轨迹生成问题不大&#xff0c;主要是参考横摆角和参考曲率部分有问题 atan和atan2 首先看下两者的区别 atan 函数&#xff1a;…

智能监控型电源老化房方案

电源适配器专用老化房主要适用于充电器等电源成品&#xff08;半成品&#xff09;作一般性老化测试。其负载主体采用程控式电子负载&#xff0c;保证其稳定度和可调节性。该老化车配备电脑操作监控系统。 模拟量采集/老化房采集软件 一、老化房功能&#xff1a; 1 负载主体&am…

32.768KHz时钟RTC晶振精度PPM值及频差计算

一个数字电路就像一所城市的交通&#xff0c;晶振的作用就是十字路口的信号灯&#xff0c;因此晶振的品质及其电路应用尤其关键。数字电路又像生命体&#xff0c;它的运行就像人身体里的血液流通&#xff0c;它不是由单一的某个器件或器件单元构成&#xff0c;而是由多个器件及…

xtts和ogg不选择?

不选择ogg的理由&#xff1a; 1.需要在源端创建用户赋权&#xff0c;启用数据库最小日志&#xff0c;附加日志等操作--对生产影响较大 2.外键约束过多&#xff0c;割接启用可能很慢https://www.modb.pro/db/201126--割接停机时间影响 3.初始化配置expdp导出可能快照过旧&#x…

11.光线追踪

1.为什么要引入光线追踪 光栅化阶段有些全局效果做的并不好&#xff0c;首先不太好做软阴影&#xff0c;然后是类镜面反射&#xff0c;表面很光滑但是又达不到镜面反射那么光滑&#xff0c;光线反射到这类物体上之后会经过好几次反射&#xff0c;其次是间接光照&#xff0c;光…

File has been changed outside the editor, reload?

编译keil工程&#xff0c;一直提示&#xff1a;该文件在编译器之外被修改&#xff0c;是否重新加载。 解决办法&#xff1a; 关闭.map后缀的文件即可&#xff0c;然后重新build/rebulid可以发现不会重新弹出该错误。

数据集成和人工智能驱动的见解

数字时代使数据成为人们关注的焦点&#xff0c;将其从单纯的二进制序列转变为有价值的组织资产。随着企业越来越多地转向数据驱动战略&#xff0c;数据管理的复杂性也随之增加。当前的任务不仅仅是存储甚至收集数据&#xff0c;而是将其转化为可操作的情报。本博客旨在剖析寻求…