C++ - 异常介绍和使用

news2024/11/16 22:43:31

前言 

 我们在日常编写代码的时候,难免会出现编写错误带来程序的奔溃,或者是用户在使用我们编写的程序时候,使用错误所带来程序的奔溃。

在C++ 当中 可以对你觉得可能发生 错误 的地方在运行之前进行判断,发生错误可以给出提示。

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

在了解C++ 当中处理错误的异常之前,我们先来了解一下C语言当中处理错误的方式。

终止程序

 C 当中有一个 assert()函数,这个函数在 assert.h 这个头文件当中,这个函数的参数是一个 bool 值,我们可以在 ()当中写入判断的表达式,如果是 false 会直接报异常。比如:发生内存错误,除0错误时就会终止程序。

这种方式虽然一定程度上 方便了 程序员 查找报错位置,只需要看 自己 写的 每一个 assert()函数位置,可以更快的找出发生错误的位置。

但是,这种方式对于用户来说非常不友好,用户难以接受。

 返回错误码

 当发生错误的时候,会报错,但是这个报错需要程序员自己去查找对应的错误。比如系统当中的很多接口函数都是通过把错误码放到 error 当中来表示错误的。

在C语言实际使用当中,返回错误码一直都是其主要的处理错误的方式,只有部分情况下使用终止程序处理非常严重的错误。

但是,错误码的方式还是非常麻烦,比如 有 代码量非常大的工程,有 5 个小组来管理着这个庞大的代码工程,有一天程序报错了,发现在 报错在 第二小组当中输出,但是第二小组经过检查,没有在他们的代码模块当中发现错误,后面才发现,是第一组当中的隐含的错误吗,导致了第二组当中的输出错误。

当代码很多的时候,这种错误码的方式非常的难找错误。

而且,错误码虽然告诉程序员是几号错误,但是我们不可能所以的错误都记得,需要的时候我们还要去查几号错误是什么错误。

所以,在C++当中更新了异常,来更好的解决程序的错误问题。

C++异常

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

 

 语法

 在异常的使用当中有三大关键字:

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

 如果某一个块当中,抛出了异常,那么,我们可以用 try 和 catch 两个关键字去捕捉这个异常。

在 try 块 当中放置可能会抛出异常的代码,这个 try 块 当中的代码被称为是 保护代码。使用 try 和 catch 语句的语法如下所示:
 

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

一块 try 块 可以使用多个 catch 来对不同异常进行处理

 关于 try/catch 语句执行顺序:

try
{
    func();
}catch( ExceptionName e1)
{
    // 异常执行
}

上述代码,如果 当 func()函数没有抛出异常,那么就是和没有  try/catch 语句 一样,正常执行。而且没有此时不会去调用catch函数。如果func()函数抛出异常,那么会直接跳到 catch语句执行。

 其实,当 try 当中出现异常,直接跳到 catch 语句进行处理是一件非常好的事,因为我们一般在处理异常的时候,把异常都在主函数(main)当中进行处理,或者是同一进行处理,不会单独分开处理的。

 直接跳出还有一个好处,当调用链非常深的时候,如果一层一层往外回调的话,非常的麻烦,直接跳到 catch 就很方便。

而且,即使是 直接 跳到 catch 语句,其中应该回调的函数所销毁的函数栈帧也是会销毁的,不会发生 跳到 catch 处在调用链当中还有函数栈帧没有销毁的情况。

 异常的使用

 异常的抛出和匹配原则:

  • 异常是通过抛出对象而引发的,该对象的类型决定了应该激活哪个catch的处理代码。
  •  被选中的处理代码是调用链中与该对象类型匹配且离抛出异常位置最近的那一个。
  • 抛出异常对象后,会生成一个异常对象的拷贝,因为抛出的异常对象可能是一个临时对象,所以会生成一个拷贝对象,这个拷贝的临时对象会在被catch以后销毁。(这里的处理类似于函数的传值返回)

  • catch(...)可以捕获任意类型的异常,问题是不知道异常错误是什么。
  • 实际中抛出和捕获的匹配原则有个例外,并不都是类型完全匹配,可以抛出的派生类对象,使用基类捕获,这个在实际中非常实用。

 C++ 当中抛出是通过的 throw 抛出,throw 可以抛出任意类型,无论是 char* ,int,内置类型还是 自定义类型都可以抛出。

而抛出就要使用 catch 捕获,catch 捕获相同 一种类型的由 throw 抛出的异常。

double func(int a, int b)
{
	if (b == 0)
		throw "func by zero conditon";
	else
		return ((double)a / (double)b);
}

int main()
{
	try
	{
		func(1, 2);
	}
	catch (int errmsg)
	{
		cout << errmsg << endl;
	}

	return 0;
}

 上述 throw 抛出了一个 char* 类型,在 catch 当中就要捕获这个 char*。如在 catch 当中捕获的类型不匹配,比如上述抛出了 char* ,但是我却捕获了 int:
 

double func(int a, int b)
{
	if (b == 0)
		throw "func by zero conditon";
	else
		return ((double)a / (double)b);
}

int main()
{
	try
	{
		func(1, 2);
	}
	catch (int errmsg)
	{
		cout << errmsg << endl;
	}

	return 0;
}

我们注意到 当 throw 抛出的类型 和 catch 捕获的类型不匹配是不能捕获到的。而且,抛出异常之后,必须被捕获,如果不被捕获,编译器就会报错

  在函数调用链中异常栈展开匹配原则(也就是检查原则)

  •  当 throw 抛出异常之后,先检查 throw 本身是否在 try 内部,如果在,再去看 catch 语句当中是否有匹配的 catch 语句,如果上述两个都有,再调到 对应的  catch 块当中进行处理。
  • 如果没有匹配的 catch ,就会退出当前函数栈,继续在调用的栈当中进行查找匹配的 catch。他是层层网上找的,比如现在是 main 函数调用了 func1()函数,func1()调用了 func2()函数,在func2()函数当中抛出了异常,首先会去检查 func2()当中 throw 抛出的异常是否在 func2()函数当中有被 try/catch 内部,如果在 就会去看其中有没有 对应的 catch 块;如果上两个其中一个没有,就会去 上一层函数当中寻找,也就是在 func1()当中寻找,如果还没有找到,再到 main函数当中寻找,如果有多层函数调用,就会这样去寻找,直到找到对应的 catch块。
  •  如果到达 main函数的栈当中,依旧没有找到匹配的,则终止程序(debug 和 release都是一样的)。上述沿着调用链查找匹配的 catch 的过程被称为 栈展开。所以,当我们写了 很多个 catch 之后,把我们想到了异常都写出来了,在最后都要加一个任一类型的 catch 来捕获异常,防止程序终止。
  •  当编译器找到对应的 catch 语句之后,在指向完当前 catch 语句,会顺序接着执行后面的 代码
  • 当一个 try 当中使用了 多个 throw ,也就是说使用者先抛出多个 异常,这个在语法上是不会报错的,但是除了第一个 throw 之外,后面的 throw 就不会在抛出了,相当于没写

例1: 

 如上述例子,当抛出异常之后,回去 try/catch 当中寻找对应的 catch ,然后接着 程序当中 try/catch 之后的代码顺序执行。

例2: 

 按照上述这个例子的执行过程,我们来看看下面这个例子的执行过程:
 

double func2(int a, int b)
{
	if (b == 0)
    {
        throw "func by zero conditon";
        throw 1;
    }
	else
		return ((double)a / (double)b);
}

void func1(int a, int b)
{
	try
	{
		func2(a, b);
	}
	catch (const char* e)
	{
		cout << e << endl;
	}

	cout << "world" << endl;
}

int main()
{
	try
	{
		func1(1, 0);
	}
	catch (const char* errmsg)
	{
		cout << errmsg << endl;
	}

	cout << " hello " << endl;

	return 0;
}

上述这个例子,在 func1 ()函数当中就捕获了 func2()当中的 抛出的异常,也就是说,在main()函数当中捕获 char* 的 catch 没有捕获到,就被 func1()当中的 catch 捕获了,所以main()函数当中的 catch就不会再执行了。

我们还注意到,上述的 func2()当中有两个 throw,他们都位于同一个 try 当中,所以只有第一个 throw 会执行,后面一个 throw 不会执行,第一个 throw 执行就跳到 catch 当中去了,后面的代码就不会执行了。 

 在 func1()当中捕获到之后,执行完 catch 代码,就会接着这个 catch 执行后面的代码,也就是说输出 “world”,所以上述程序输出:

func by zero conditon
world
 hello

例3:

当我们抛出一个 string 类的对象,在其他函数的catch 当中捕获这个 string类对象的 异常。我们知道,string对象出了作用域之后,就会调用析构函数销毁,那么我们在 main()函数当中 捕获 这个 string 对象会不会是野指针呢?

double func2(int a, int b)
{
	if (b == 0)
	{
		string s("func by zero conditon");
		throw s;
	}

	else
		return ((double)a / (double)b);
}

int main()
{
	try
	{
		func2(1, 0);
	}
	catch (const char* errmsg)
	{
		cout << errmsg << endl;
	}

	cout << " hello " << endl;

	return 0;
}

如上述,在 func2()函数当中抛出 string类s 对象,然后在 main()函数当中捕获,其中的errmsg会不会是野指针呢?

不会。可以成功捕获到 s 对象。

因为,这里的返回和 拷贝返回一样,他不会直接返回,而是拷贝一份之后,返回这个拷贝后的对象。这个拷贝的对象,在catch 执行完毕之后才会销毁。

 通用类型 catch

 在日常当中,我们经常会忘记捕获某个异常,比如在代码量很多的项目当中,可能时不时就忘记某个异常的捕获了。

但是,异常不被捕获程序就直接终止,如果这个异常只是一个小异常,稍微处理一下就可以解决的问题,但是因为异常没有捕获就直接导致程序终止了;这可不是我们希望的。

所以,C++ 当中支持 通用类型的 catch,这个特殊的catch语句可以捕获任意类型的 异常,包括 内置类型 和 自定义类型。

语法:
 

catch(...)
{

}

三个 " . " 的意思就是捕获任意类型的异常。

任意类型的捕获一般是放在最后一个 catch 来使用,防止有抛出的异常没有被捕获。

但是,这种通用类型的异常捕获是有弊端
通用类型异常捕获,不知道捕获的类型是什么错误,其他catch 还可以通过 类型匹配来知道是什么类型的错误,但是 通用类型catch 不能匹配 类型,所有没有捕获的异常都由 它来捕获。

 我们不能把 catch(...) 放到其他 catch 的上面优先捕获,会报错

 还可以在 catch(...)当中使用 throw; 

throw; 代表 捕获什么,抛出什么

 支持可以抛出派生类对象,用基类捕获

例1:

这种在程序日常监控当中多次使用,比如你使用的某一个软件,这个软件当中需要点击来执行下一步操作,那么它怎么知道用户是那个时间点击的呢?不知道,因为用户的点击是有随机性的 。所以,一般是使用一个循环来实时监控用户点击的这一个操作的,如果在点击这一操作当中出现了异常没有被我们写到 catch 当中捕获,此时 catch(...) 就可以帮我们 捕获这个异常,防止程序终止。

例2:

 在日常发送消息的过程当中没可能会遇到上述的三个问题,而这些问题我们一般用 id 类辨别存储,在异常类当中,除了会有 string类型的异常信息,还会有一个 id,这个id 代表这是一个什么类型的错误,而且 string 类型的异常表示是记录到日志当中去的。

而且,当抛出异常的时候可能不只是要记录日志,可能还会有其他的处理;比如在隧道里面发送消息,可能就会发送失败,而发送失败的原因是 因为 网络错误,我们回想我们日常使用 微信 或者 qq 发送消息的时候,当网络不好,消息不是 马上就提示 红色感叹号发送失败的,而是先“转圈”。我们看到转圈这个图标的时候,其实就一直在重试发消息。尝试一定次数之后才会 提示发送失败。

  •  由上述两个例子,我们发现,异常基本都是要自己针对程序的错误进行记录和处理的,而这就导致不同异常很少有重复的处理方式;虽然C++ 当中支持我们 抛出任意类型的异常,但是,在实际的项目编写当中,异常是不能随便抛出的,抛出一个 没有 进行 处理的异常可能会出现很多问题
  •  一般情况下,都要抛出一个异常的自定义类型,这个自定义类型至少要包含两个错误信息,一个是 string 类异常说明,用于记录日志;另一个是 异常id,id 的本质是错误编号,有了错误的编号才能更好的分辨错误。有些错误需要进行特殊处理,而不是简单的记录日志
  • 在一个大项目当中,可能会分为很多个组来分配实现不同的板块,假设有一组是专门来写异常捕获的,异常处理的,那么其他组如果按照自己处理自己模块异常的方式去抛出异常的话,写异常的那一组人,在其他人每抛出一个异常就要写一个异常重新捕获。异常通常是在最外层进行捕获,如果都是一个异常的话,在外层如何知道是哪一个模块出错了?
  • 在比如,数据库模块的人,还希望吧 sql 语句的异常给带出去;网络模块的人,希望把网络模块的异常带出去;可能会带一些信息出来,这样的话,外部的异常类就不满足了,但是如果要各个模块定义各个模块的异常类,机会非常的乱。
  • 如果各个模块的人,都定义各自的异常类,在外层捕获异常的人就会非常的繁琐,各种异常都需要写 catch 类捕获,来特殊处理。

 基于上述这种问题存储,C++支持抛出派生类对象,用基类捕获。

 子类可以切割,兼容赋值给基类,没有类型转换的发生。

所以,一般在公司当中,写出异常基类,和一系列派生类,如果员工想要抛异常,只能抛出基类,和其派生类,或者如果想有自己的东西的话,就自己定义一个集成这个异常基类的派生类,然后抛出这个子类自定义异常类:
 

 下面是一个 服务器开发当中使用的 异常继承体系 样例

// 服务器开发中通常使用的异常继承体系
class Exception
{
public:
	Exception(const string& errmsg, int id)
		:_errmsg(errmsg)
		, _id(id)
	{}
	virtual string what() const
	{
		return _errmsg;
	}
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;
	}
private:
	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;
	}
};

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 SQLMgr()
{
	srand(time(0));
	if (rand() % 7 == 0)
	{
		throw SqlException("权限不足", 100, "select * from name = '张三'");
	}
	//throw "xxxxxx";
}

void CacheMgr()
{
	srand(time(0));
	if (rand() % 5 == 0)
	{
		throw CacheException("权限不足", 100);
	}
	else if (rand() % 6 == 0)
	{
		throw CacheException("数据不存在", 101);
	}
	SQLMgr();
}

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

int main()
{
	while (1)
	{
		this_thread::sleep_for(chrono::seconds(1));
		try {
			HttpServer();
		}
		catch (const Exception& e) // 这里捕获父类对象就可以
		{
			// 多态
			cout << e.what() << endl;
		}
		catch (...)
		{
			cout << "Unkown Exception" << endl;
		}
	}
	return 0;
}

捕获的人就解放了,使用捕获父类就行:

 而且,在string 类的异常日志当中,不止写异常报错(错误描述),还有写这个异常类的人的编号,让其他人知道,这个异常是公司里面哪一个人写的
 

上述的 what()函数是按照库当中的 what()函数样子写的,在父类当中what(),只返回一个错误信息,而在子类当中的what()函数就有了 子类异常类的实现人名称,子类异常类独有的错误信息等等,而且父类当中的what()函数写成了虚函数,这样,子类就可以对 what ()函数进行重写,在外部主函数中调用what()函数就可以使用多态的方式来调用

 向上述子类是 sql 异常类,他还带上了 _sql ,把sql 语句也带上了,除了错误信息,还有出错的sql语句。

在库当中也有what()函数,也是返回错误信息的函数。

C++标准库当中异常类的体系结构

 如上述所示,所有的异常子类都继承与一个父类:exception,所以,我们在捕获官方库当中的异常的时候,就可以直接捕获 exception 父类。

各个异常类的说明 

 这些子类的捕获方式都可以像上述实现的 服务器 当中的异常体系结构一样,使用多太多方式来实现,库当中异常类也有 what()函数供我们使用。

 异常规范

 异常其实有一个很大的问题,就是如果抛出异常就会到处乱跳,有时候我们打断点都控制不住,因为我们不清楚哪一个函数可能就抛异常了

异常规格说明的目的是为了让函数使用者知道该函数可能抛出的异常有哪些。

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

// 这里表示这个函数会抛出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();
  • 函数的后面接throw(),表示函数不抛异常。
  • 若无异常接口声明,则此函数可以抛掷任何类型的异常。
  • 如果可能会抛出多个类的异常,在 throw()当中就会 " , " 去分隔。
     

虽然有规范,但是因为 C++ 兼容C语言,就不能强制去这样写,如果是按照 C 语言的语法来写的函数是没有这样的规范的,但是有不能强制,就不能很好的去控制

 而且,在throw()当中还需要判断这个函数抛出哪几个异常,实际上肯定会有人把抛出的异常种类判断不全。而且 在该函数当中可能还会有其他函数,就算把该函数的异常写完了,其他函数不是我实现的,我不一定能写完;就算那个函数有文档说明,写函数的人也不一定能够把异常写完。

而且,当调用的函数多了之后,我需要把每一个函数的抛出哪一个异常写出来,这样整理是不是太麻烦了,还要一个一个函数抛那些异常整理出来。

所以在C++11 当中简化了上述 繁琐的语法:

使用关键字关键字:noexcept,他的意思和 throw() 一样,表示这个函数不会抛出异常:

thread() noexcept;
thread (thread&& x) noexcept;

异常安全

  •  构造函数要对对象的进行构造和初始化,最好不要再构造函数当中抛异常,这样可能会导致对象构造不完全。
  • 同样的,析构函数要对对象进行销毁,对对象当中动态开辟的空间进行释放等等操作,最好不要再析构函数当中去抛出异常,这样带来的后果更加严重,可能会导致资源泄漏(内存泄漏,句柄未关闭等等)。
  • C++ 当中经常会操作 资源泄漏,比如在 new ,delete 当中 抛出异常,在lock和unlock之间抛出了异常导致死锁。在C++ 当中使用  RAII来解决以上问题。
     

 比如下述例子:
 

void func1()
{

}

double func1(int a, int b)
{
	int* array = new int[10];

	func1();

	delete[] array;
}

int main()
{
	try
	{
		func1(1, 0);
	}
	catch ( const char* errmsg)
	{
		cout << errmsg << endl;
	}
	catch (...)
	{

	}

	cout << " hello " << endl;

	return 0;
}

假设 func1()当中调用 func2()函数的时候,func2()函数抛出异常了,这会导致在 func1()当中对 array 数组的 delete 操作被直接跳过了,导致内存泄漏。

解决方式:

方式1:把 try/catch 设法移动到 delete 的 上方,使得在 try.catch 执行完之后 顺序执行 delete操作。

方式2: 在 try/catch 的 catch 当中重新把异常抛出:
如果一定想要在外层函数当中对异常进行会处理,因为可能要写入日志等等操作。那么可以在 func1()当中,先把 func2()当中的异常先拦截下来,把 array 数组 delete 之后,再把这个异常给抛出给 外部 的 try/catch :

void func2()
{

}

double func1(int a, int b)
{
	int* array = new int[10];

	try
	{
		func2();
	}
	catch (const char* errmsg)
	{
		delete[] array;
		throw(errmsg); // 重新抛出异常
	}

}

int main()
{
	try
	{
		func1(1, 0);
	}
	catch ( const char* errmsg)
	{
		cout << errmsg << endl;
	}
	catch (...)
	{

	}

	cout << " hello " << endl;

	return 0;
}

但是上述方式有一个很大的弊端,当 func1()当中的 try 块当中有很多个函数,那么就要对这些个函数都写一个catch 来截取,处理,然后在重新抛出,非常麻烦。

所以我们对上述的方式进行了改进,下述代码只有 func1()部分:
 

double func1(int a, int b)
{
	int* array = new int[10];

	try
	{
		func2();
	}
	catch (...)
	{
		delete[] array;
		throw; // 重新抛出异常
	}

}

 其中的 throw; 代表 捕获什么,抛出什么

 异常的优缺点

 C++异常的优点:

  •  异常对象定义好了,相比错误码的方式可以清晰准确的展示出错误的各种信息,甚至可以包含堆栈调用的信息,这样可以帮助更好的定位程序的bug
  •  返回错误码的传统方式有个很大的问题就是,在函数调用链中,深层的函数返回了错误,那么我们得层层返回错误,最外层才能拿到错误,具体看下面的详细解释。
  •  很多的第三方库都包含异常,比如boost、gtest、gmock等等常用的库,那么我们使用它们也需要使用异常。
  • 部分函数使用异常更好处理,比如构造函数没有返回值,不方便使用错误码方式处理。比如T& operator这样的函数,如果pos越界了只能使用异常或者终止程序处理,没办法通过返回值表示错误。

 C++异常的缺点:

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

 但是总体而言,C++的异常虽然有很多弊端,但是异常所带来的好处是非常好的,极大的方便了我们寻找bug。

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

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

相关文章

JOSEF约瑟 智能电流继电器KWJL-20/L KWLD26 零序孔径45mm 柜内导轨式安装

KWJL-20智能电流继电器 零序互感器&#xff1a; KWLD80 KWLD45 KWLD26 KWJL-20 一、产品概述 KWJL-20系列智能剩余电流继电器&#xff08;以下简称继电器&#xff09;适用于交流电压至660V或更高的TN、TT、和IT系统&#xff0c;频率为50Hz。通过零序电流互感器检测出超过…

TS编译器选项compilerOptions指定编译ES版本和模块化使用规范

compilerOptions是TS的编译器选项&#xff0c;主要在tsconfig.json文件中用于对ts编译为js文件时进行配置 "compilerOptions" : { 配置项 } 一、target指定ts被编译的ES版本 {// compilerOptions 编译器选项"compilerOptions": {// target 用来指定ts被编…

寻找AI-Native创业者

亲爱的科技探险家们和代码魔法师们&#xff1a; 未来的钟声已经敲响&#xff0c;预示着一场极度炫酷的虚拟现实游戏即将展开。从初期简单的智能识别&#xff0c;到设计师级别的图纸设计&#xff0c;生成式AI技术&#xff08;Generative AI&#xff09;以其独特理念和创新模式重…

基于springboot+vue的在线购房(房屋租赁)系统

博主主页&#xff1a;猫头鹰源码 博主简介&#xff1a;Java领域优质创作者、CSDN博客专家、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战 主要内容&#xff1a;毕业设计(Javaweb项目|小程序等)、简历模板、学习资料、面试题库、技术咨询 文末联系获取 项目介绍…

Java21的新特性

Java语言特性系列 Java5的新特性Java6的新特性Java7的新特性Java8的新特性Java9的新特性Java10的新特性Java11的新特性Java12的新特性Java13的新特性Java14的新特性Java15的新特性Java16的新特性Java17的新特性Java18的新特性Java19的新特性Java20的新特性Java21的新特性Java22…

Java下打印直角三角型(另一个方向)

代码如下&#xff1a; public class MyWork {public static void main(String[] args) {int num 5;for (int i 0; i < num; i) {for (int j 0; j < i; j) {System.out.print("-");}for (int j 0; j < num - i; j) {System.out.print("*");}S…

Ubuntu 安装 CUDA 与 OPENCL

前言&#xff1a;最近需要做一些GPU并行计算&#xff0c;因而入坑CUDA和OPENCL&#xff0c;两者都有用到一些&#xff0c;刚好有点时间&#xff0c;同时记录一些学习过程&#xff0c;排掉一些坑&#xff0c;这篇是环境安装篇&#xff0c;基本跟着走就没什么问题&#xff0c;环境…

字节跳动大规模多云CDN管理与产品化实践

近日&#xff0c;火山引擎边缘云融合CDN团队负责人孙益星在LiveVideoStack Con 2023上海站围绕融合CDN团队持续建设多云CDN平台的演进过程&#xff0c;结合建设过程中面临的难点和挑战&#xff0c;介绍了融合CDN团队接下来的主要投入方向&#xff0c;分享了火山引擎在多云应用架…

tp5.1 致命错误: Call to undefined method think\Cache::get()

致命错误: 致命错误: Call to undefined method think\Cache::get() 原因&#xff1a;&#xff08;引用类错误&#xff09; thinkphp5.1中有两个Cache类&#xff1a;think\Cache和think\facade\Cache。 官方文档中说使用think\Cache&#xff0c;但实际是使用think\facade\Cac…

SQL Server数据库中了360后缀勒索病毒怎么办,勒索病毒解密数据恢复

随着互联网的发展&#xff0c;网络安全问题日益凸显&#xff0c;勒索病毒已经成为当今数字威胁中的一大主要犯罪行为之一。其中&#xff0c;360后缀勒索病毒作为一种常见的数据库攻击形式&#xff0c;对数据库的安全性提出了极大挑战。近期我们收到很多企业的求助&#xff0c;企…

RocketMQ 源码分析——NameServer

文章目录 为什么要学RocketMQ源码RocketMQ源码中的技术亮点RocketMQ 模块结构NameServer 源码分析NameServer 整体流程NameServer 启动流程加载KV配置构建NRS通讯接收路由、心跳信息定时任务剔除超时Broker NameServer设计亮点读写锁存储基于内存NameServer无状态化 为什么要学…

SpringIOC之Lifecycle 接口

博主介绍&#xff1a;✌全网粉丝4W&#xff0c;全栈开发工程师&#xff0c;从事多年软件开发&#xff0c;在大厂呆过。持有软件中级、六级等证书。可提供微服务项目搭建与毕业项目实战&#xff0c;博主也曾写过优秀论文&#xff0c;查重率极低&#xff0c;在这方面有丰富的经验…

计算机等级考试信息安全三级填空题-二

1.信息安全的五个根本属性是&#xff1a;机密性、完整性可控性、不可否认性和完整性。 2.在Windows系统中&#xff0c;查看当前已经启动的效劳列表的命令是&#xff1a;net start 3.在数据库中&#xff0c;删除表的命令是&#xff1a;DROP 4.在信息资产治理中&#xff0c;标准…

zabbix的原理与安装

一、Zabbix介绍 1、zabbix 是什么&#xff1f; zabbix是一个开源的IT基础监控软件&#xff0c;能实时监控网络服务&#xff0c;服务器和网络设备的状态&#xff0c;如网络使用&#xff0c;CPU负载、磁盘空间等&#xff0c;主要是包括数据的收集、报警和通知的可视化界面zabbi…

Java下打印1-100以内的质数

代码如下&#xff1a; public class MyWork {public static void main(String[] args) {System.out.println("100以内的质数如下&#xff1a;");for (int num 0; num < 100; num) {if (2 num) {System.out.print(num " ");continue;}for (int i 2;…

晚上弱光拍照不够清晰,学会这几招画面清晰效果好

很多小伙伴喜欢夜晚拍摄&#xff0c;然而拍摄出来的照片经常画面偏暗甚至模糊不清&#xff0c;这是怎么回事&#xff1f; 弱光环境是很多人都比较头疼的拍摄场合&#xff0c;由于光线弱曝光不好把控&#xff0c;并且还很容易出现细节性问题&#xff0c;想要将照片拍好就非常不…

全网最详细的自动化测试(Jenkins 篇)

学习 Jenkins 自动化测试的系列文章 Robot Framework 概念Robot Framework 安装Pycharm Robot Framework 环境搭建Robot Framework 介绍Jenkins 自动化测试 1. Robot Framework 概念 Robot Framework是一个基于Python的&#xff0c;可扩展的关键字驱动的自动化测试框架。 …

MongoDB【部署 02】mongodb使用配置文件启动、添加为系统服务及自启动(一个报错:[13436][NotMasterOrSecondary])

MongoDB使用配置文件启动、添加为系统服务及设置自启动 1.是什么2.下载安装启动配置2.1 下载2.2 安装2.3 配置2.4 使用配置文件启动 3.设置系统服务及自启动3.1 设置为系统服务3.2 自启动 1.是什么 【以下内容来自ChatGPT3.5】 MongoDB是一个流行的开源文档型数据库管理系统&a…

IP地址与代理IP:了解它们的基本概念和用途

在互联网世界中&#xff0c;IP地址和代理IP是两个常见但不同的概念&#xff0c;它们在网络通信、隐私保护和安全方面发挥着重要作用。本文将介绍什么是IP地址和代理IP&#xff0c;以及它们在网络中的作用和应用。 IP地址是什么&#xff1f; IP地址&#xff0c;全称为Internet…

大健康行业千城万企信用建设工作启动大会在京召开

9月19日&#xff0c;为响应商务部、中宣部、国家发改委等13个部门共同举办的“诚信兴商宣传月”活动&#xff0c;中国国际电子商务中心所属北京国富泰信用管理有限公司联合北京华商国医堂集团及旗下东方岐黄商学院&#xff0c;北京华商国医堂中医药研究院举办的共筑信用月&…