C++ -- 异常

C++中的异常是用于处理程序执行过程中出现的错误情况。通过异常处理,程序可以在遇到错误时优雅地处理这些问题,而不是直接崩溃。

C语言处理错误的方式

C语言传统的处理错误的方式主要有两种:

  • 终止程序:使用如assert这样的宏来检查错误条件,并在条件不满足时直接终止程序。这种方式非常直接但粗暴,不考虑程序的恢复或错误处理,用户难以接受。
  • 返回错误码:函数通过返回值来指示是否成功执行,并将错误码(通常是整数)存储在全局变量(如errno)中或通过其他方式返回。这种方式需要程序员手动检查错误码,并查找对应的错误原因,增加了代码的复杂性和出错的可能性。

示例1:

#include <assert.h>int main()
{int a = 3;assert(a < 5); // 条件成立,不终止程序assert(a > 5); // 条件不成立,终止程序return 0;
}

示例2:

#include <stdio.h>
#include <errno.h>
#include <string.h>int readFile(char *filename, char *buffer, size_t bufferSize) {FILE *file = fopen(filename, "r");if (file == NULL) {return -1; // 文件打开失败}if (fgets(buffer, bufferSize, file) == NULL) {fclose(file);return -2; // 读取失败}fclose(file);return 0; // 成功
}int main() {char buffer[100];if (readFile("example.txt", buffer, sizeof(buffer)) != 0) {printf("Error reading file.\n");return 1;}printf("%s\n", buffer);return 0;
}

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

C++异常概念

C++中的异常是一种处理程序运行期间发生的意外或错误情况的机制。当函数遇到无法处理的错误时,可以抛出一个异常对象,让函数的直接或间接调用者捕获并处理这个异常。异常提供了一种结构化的方式来处理程序中的错误情况,提高了程序的健壮性和可维护性。

异常的用法

C++中异常的使用主要涉及三个关键字:trycatchthrow

  • throw:用于抛出异常。当程序遇到错误情况时,可以使用throw关键字后跟一个异常对象(可以是任何类型的对象,但通常是派生自std::exception或其子类的对象)来抛出异常。
  • trytry块用于包裹可能抛出异常的代码。当try块中的代码抛出异常时,控制权会转移到紧随其后的catch块(如果有的话)。
  • catchcatch块用于捕获并处理异常。可以有多个catch块来捕获不同类型的异常,或者使用一个catch(...)块来捕获所有类型的异常。
double Division(int a, int b)
{if (b == 0)throw "Division by zero condition!"; // 抛出异常return (double)a / (double)b;
}int main()
{try {Division(1, 0); // 会抛出异常Division(2, 1); // 不会抛出异常}catch(const char* errmsg) // 捕获异常{std::cout << errmsg << std::endl;}// 如果没有异常,则向下执行。如果有异常,被捕获处理后,也向下执行// ...  std::cout << "end" << std::endl;return 0;
}

异常的抛出和捕获

异常的抛出和匹配原则

  1. 异常是通过抛出对象而引发的,该对象的类型决定了应该激活哪个catch的处理代码。
  2. 被选中的处理代码是调用链中与该对象类型匹配且离抛出异常位置最近的那一个。
  3. 抛出异常对象后,会生成一个异常对象的拷贝,因为抛出的异常对象可能是一个临时对象,所以会生成一个拷贝对象,这拷贝的临时对象会在被catch以后被销毁。(这里的处理类似于函数的传值返回:catch 接收 throw 抛出的对象 作为参数。这个过程可能会触发移动语义(¬‿¬))。
  4. catch(...) 可以捕获任意类型的异常对象。(使用这一捕获方式,我们就不知道被捕获对象的具体类型)。
  5. 实际中抛出和捕获的匹配原则有个例外,并不都是类型完全匹配:可以抛出派生类对象,使用基类捕获(这里的应用后面详细讲解)。
  1. 激活与throw对象匹配的catch处理代码
#include <iostream>double Division(int a, int b)
{if (b == 0)throw "Division by zero condition!"; // 抛出异常return (double)a / (double)b;
}int main()
{try {Division(1, 0); // 会抛出异常}catch (int errnum){std::cout << "int errnum = " << errnum << std::endl;}catch(const char* errmsg) // 捕获异常{std::cout << "const char* errmsg = " << errmsg << std::endl;}catch (...){std::cout << "..." << std::endl;}std::cout << "end" << std::endl;return 0;
}

运行结果:

const char* errmsg = Division by zero condition!
end
  1. 匹配且离抛出异常位置最近的catch处理代码

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

  1. 首先,需要确认抛出异常的代码逻辑是否位于 try 块内部。只有在 try 块内部抛出的异常才会被查找匹配的 catch 子句。如果不在 try 块内部,则不会进行任何异常处理,异常会直接沿调用栈向上抛出。
  2. 如果当前函数栈中有匹配的,则会跳转执行匹配的catch代码块;如果当前函数栈中没有匹配的,就继续在调用函数的栈中进行查找匹配的catch
  3. 如果到达main()函数的栈中,还没有匹配的catch,则终止程序。上述这个沿着调用链查找匹配的catch子句的过程称为栈展开。在实际应用中,main()最后都要加上catch(...)捕获任意类型的异常,否则当有异常没有被捕获,程序就会直接终止(例如,服务器是一直在运行中的,如果遇到一个异常就要停掉服务器,代价比较大)。
  4. 当找到匹配的catch子句并处理以后,会执行catch子句后面的代码。

示例:

#include <iostream>double Division(int a, int b)
{if (b == 0)throw "Division by zero condition!"; // 抛出异常return (double)a / (double)b;
}void Func()
{try{Division(1, 0);}catch (char ch){std::cout << "char ch" << std::endl;}
}int main()
{try {Func();}catch (const char* errmsg){std::cout << errmsg << std::endl;}catch (...) {std::cout << "unkown exception" << std::endl;}std::cout << "end" << std::endl;return 0;
}

分析:

  1. 首先,Division() 函数中,throw抛出异常的代码逻辑没有位于 try 块内部,则不会进行任何异常处理,异常会直接沿调用栈向上抛出,也就是Func()函数栈中。
  2. Func()函数栈中没有匹配的catchchar 并不匹配const char*类型),就继续在调用函数main()的栈中进行查找匹配的catch
  3. 到达main()函数的栈中,有匹配的catch(类型为const char*),则捕获异常并处理异常。
  4. 当找到匹配的catch子句并处理以后,会执行catch子句后面的代码。

有了上面的知识,匹配原则的第二点应该就能理解了(匹配且离抛出异常位置最近的catch处理代码)。

抛出派生类对象,使用基类捕获

抛出派生类对象并使用基类捕获是一种常见的异常处理模式,它利用了C++中的继承关系和多态性。通过这种方式,可以在不同的层次上处理不同类型的异常,同时保持代码的灵活性和扩展性。

抛出派生类对象,使用基类捕获的使用场景

如果你抛出了一个派生类的对象,并使用基类捕获异常,那么这个异常是可以被捕获的。这是因为派生类的对象可以被视为基类的对象,即派生类对象可以隐式转换为基类对象。

使用场景

  1. 统一异常处理

    • 当你不知道具体抛出哪种类型的异常时,可以使用基类来捕获所有可能的派生类异常。
    • 例如,在一个模块中,你可能需要处理多种不同类型的运行时错误,可以使用 std::runtime_error 作为基类来捕获所有派生类异常。
  2. 分层处理

    • 在多层调用中,可以在不同的层次上捕获和处理不同类型的异常。
    • 下层函数可以抛出具体的异常类型,而上层函数可以使用基类来捕获这些异常,并进行统一处理。
  3. 异常类型扩展

    • 当需要添加新的异常类型时,可以继承已有的基类,而不需要修改现有的捕获逻辑。
    • 这样可以保持代码的可扩展性,未来添加的新异常类型可以无缝集成到现有系统中。

示例代码

假设我们有以下异常类的定义:

#include <iostream>
#include <exception>class BaseException : public std::exception {
public:virtual const char* what() const noexcept = 0;
};class DerivedException1 : public BaseException {
public:const char* what() const noexcept override {return "Derived Exception 1";}
};class DerivedException2 : public BaseException {
public:const char* what() const noexcept override {return "Derived Exception 2";}
};

抛出派生类对象

void functionThatThrows() {throw DerivedException1(); // 抛出派生类异常
}void anotherFunctionThatThrows() {throw DerivedException2(); // 抛出另一个派生类异常
}

使用基类捕获

int main() {try {functionThatThrows();} catch (const BaseException& e) {std::cerr << "Caught base exception: " << e.what() << std::endl;}try {anotherFunctionThatThrows();} catch (const BaseException& e) {std::cerr << "Caught base exception: " << e.what() << std::endl;}return 0;
}

代码分析

  1. 定义异常类

    • BaseException 是一个抽象基类,定义了一个纯虚函数 what()
    • DerivedException1DerivedException2BaseException 的派生类,实现了 what() 函数。
  2. 抛出异常

    • functionThatThrowsanotherFunctionThatThrows 分别抛出 DerivedException1DerivedException2
  3. 捕获异常

    • main 函数中,使用 BaseException 类型的 catch 子句来捕获所有派生类异常。

注意事项

  1. 类型信息丢失

    • 使用基类捕获可能会丢失具体的派生类类型信息。如果需要区分具体的派生类异常,可以在捕获基类异常后进一步判断具体的派生类类型。
  2. 多态性

    • 利用多态性,可以在捕获基类异常后调用虚函数来获取具体的信息。

异常的重新抛出

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

#include <iostream>
double Division(int a, int b)
{if (b == 0)throw "Division by zero condition!";return (double)a / (double)b;
}void Func()
{int* arr = new int[10];try {std::cout << Division(1, 0) << std::endl;}catch (...) {std::cout << "delete[] " << arr << std::endl;delete[] arr;// 这里并没处理Division()实际抛出的异常throw;}// ...std::cout << "delete[]" << arr << std::endl;delete[] arr;
}int main()
{try {Func();}catch(const char* errmsg) {// 这里处理Division()抛出的异常std::cout << errmsg << std::endl;}return 0;
}

补充:try(...); throw; 两者配合使用时,try 捕获什么类型的异常,throw 便会抛出什么类型的异常。

异常安全

  1. 构造函数的主要任务是初始化对象。如果在构造函数中抛出异常,而对象的部分成员已经初始化,这可能会导致对象处于不一致的状态。当这种对象被销毁时,其析构函数可能会尝试访问那些未完全初始化的成员,从而引发更多的问题。
  2. 析构函数主要完成资源的清理。最好不要在析构函数内抛出异常,否则可能导致资源泄露(内存泄漏、句柄未关闭等)。
  3. C++中异常经常会导致资源泄露的问题,比如:在 newdelete 中抛出了异常,导致内存泄露;在 lockunlock 之间抛出了异常,导致死锁。C++经常使用RAII来解决以上问题(关于RAII,智能指针章节会进行讲解~)。

示例一:构造函数:

double Division(int a, int b)
{if (b == 0)throw "Division by zero condition!"; // 抛出异常return (double)a / (double)b;
}class AA
{
public:AA(){std::cout << "AA()" << std::endl;_ptr1 = new int;Division(1, 0);_ptr2 = new int;}~AA(){std::cout << "~AA()" << std::endl;delete _ptr1;delete _ptr2;}
private:int* _ptr1;int* _ptr2;
};int main()
{try{AA a;}catch(...){std::cout << "处理异常" << std::endl;}return 0;
}

运行结果:

AA()
处理异常

代码分析

  1. 构造函数中的异常抛出

    • AA 类的构造函数中调用了 Division(1, 0),该函数会在 b == 0 的情况下抛出异常。
    • 当异常抛出时,构造函数不会继续执行,因此 _ptr2 不会被初始化(实际上某些编译器会将其初始化为nullptr)。
  2. 异常处理

    • 异常被抛出后,程序会跳转到最近的 catch 块进行处理。
    • 因为异常是在构造函数中抛出的,构造函数没有完成,所以对象 a 并没有完全构造完毕。
  3. 析构函数的调用

    • 在构造函数中抛出异常的情况下,对象 a 并没有完全构造完成,因此它的析构函数不会被调用。
    • 在 C++ 中,如果一个对象的构造函数抛出异常,该对象就不会被视为已经构造完成,析构函数也不会被调用

总结

  1. 构造函数中的异常抛出

    • 如果在构造函数中抛出异常,对象不会被视为完全构造完成。
    • 因此,析构函数不会被调用。
  2. 对象状态

    • 对象 a 在构造函数抛出异常后不会被完全构造完成,因此它在 catch 块中不存在完全构造的状态。
    • 析构函数不会被调用,导致 _ptr1 没有被释放,从而产生内存泄漏。

示例二:析构函数:

double Division(int a, int b)
{if (b == 0)throw "Division by zero condition!"; // 抛出异常return (double)a / (double)b;
}class AA
{
public:AA(){_ptr1 = new int;_ptr2 = new int;std::cout << "AA()" << std::endl;}~AA(){delete _ptr1;Division(1, 0);delete _ptr2;std::cout << "~AA()" << std::endl;}
private:int* _ptr1;int* _ptr2;
};int main()
{try{AA a;}catch (...){std::cout << "处理异常" << std::endl;}return 0;
}

代码分析:

  1. 析构函数中的异常抛出

    • 析构函数中调用了 Division(1, 0),如果 b == 0,则抛出异常。
    • 一旦析构函数抛出异常,程序会终止,并且不会执行析构函数中剩余的代码。
  2. 资源释放

    • 析构函数中的异常导致 _ptr2 没有被删除,从而导致内存泄漏。
    • 这是不推荐的做法,因为析构函数应该始终正确地释放资源,而不会抛出异常。

示例三:及时析构

错误示例:

#include <iostream>double Division(int a, int b)
{if (b == 0)throw "Division by zero condition!";return (double)a / (double)b;
}
void Func()
{int* arr = new int[10];int len, time;std::cin >> len >> time;std::cout << Division(len, time) << std::endl;// ...std::cout << "delete[]" << arr << std::endl;delete[] arr;
}int main()
{try {Func();}catch(const char* errmsg) {std::cout << errmsg << std::endl;}return 0;
}

此时如果Division() 函数执行时抛出异常,就会导致 arr 申请的资源没有被释放,造成内存泄漏。

正确示例:

void Func()
{int* arr = new int[10];try {int len, time;std::cin >> len >> time;std::cout << Division(len, time) << std::endl;}catch (...) {std::cout << "delete[] " << arr << std::endl;delete[] arr;throw;}// ...std::cout << "delete[]" << arr << std::endl;delete[] arr;
}

示例四:手动处理异常的缺点

如果遇到以下场景,处理异常就会比较棘手:

void Func()
{int* arr1 = new int[10];int* arr2 = new int[20];int* arr3 = new int[30];// ...delete[] arr1;delete[] arr2;delete[] arr3;
}

这里 arr1 arr2 arr3 在使用 new 申请空间时可能会抛出异常(当空间不足时),这样就有四种情况:

  • arr1 抛出异常,不需要释放空间。
  • arr2 抛出异常,需要释放 arr1 申请的空间。
  • arr3 抛出异常,需要释放 arr1arr2 申请的空间。
  • 没有异常抛出,需要释放 arr1arr2arr3 申请的空间。

那我们在处理异常时,就需要像下面类似写法,才能保证异常安全:

void Func()
{int* arr1 = new int[10];int* arr2;int* arr3;try{arr2 = new int[20];try{arr3 = new int[30];}catch(...){delete[] arr1;delete[] arr2;throw;}}catch(...){delete[] arr1;throw;}// 没有异常delete[] arr1;delete[] arr2;delete[] arr3;
}

这样处理起来会比较麻烦,其实通过 智能指针 可以很好地解决这个问题。

在没有设计出 智能指针 时,为了解决这个问题,就提出了 异常规范 的概念。

异常规范

C++98

  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++98中提供了对异常抛出类型的声明,但它存在一些问题:

  1. 开发人员误用

    • 如果开发人员声明了一个函数不会抛出异常(throw()),但实际上该函数却抛出了异常,编译器只会发出警告而不是错误。
  2. 内部函数抛出异常

    • 即使函数本身没有抛出异常,但如果它调用了其他可能抛出异常的函数,那么整个函数仍然可能抛出异常。
  3. 非强制性

    • 异常规范只是一个建议,编译器不会强制执行。
      但是这个规范在实际应用中,还存在很多问题:

开发人员认为该函数不会抛异常 throw(),但实际上却抛出了异常

double Division(int a, int b) throw()
{if (b == 0)throw "Division by zero condition!";return (double)a / (double)b;
}int main()
{try {Division(1, 0);}catch(const char* errmsg) {std::cout << errmsg << std::endl;}return 0;
}

此时编译器不会报错,而只是警告(有些程序员会不处理警告>﹏<),例如:

 warning C4297: “Division”: 假定函数不引发异常,但确实发生了

函数本体没有出现异常,但内部函数抛出了异常。

double Division(int a, int b) throw(const char*)
{if (b == 0)throw "Division by zero condition!";return (double)a / (double)b;
}void func() throw()
{// ...Division(1, 0);
}int main()
{try {func();}catch(const char* errmsg) {std::cout << errmsg << std::endl;}return 0;
}

此时编译器不会报错,而只是警告( ̄へ ̄),例如:

warning C4290: 忽略 C++ 异常规范,但指示函数不是 __declspec(nothrow)

并且这个异常规范只是一个建议,并不是强制的,所以这个异常规范并不能很好地解决异常安全的问题。

C++11

C++11 引入了 noexcept 关键字来替代旧的异常规范,并且提供了更好的语义和性能优化。

即, 对于可能会抛异常的函数,什么都不要写。确定不抛异常的函数,要加一个 noexcept

1. noexcept 的使用

noexcept 关键字用于声明一个函数不会抛出异常。如果一个函数被声明为 noexcept,那么编译器可以对其进行优化,例如减少异常处理的开销。

2. 语法
// 不会抛出异常
void thread() noexcept;
void thread(thread&& x) noexcept;

如果一个函数没有显式声明 noexcept,则默认行为是它可以抛出任何类型的异常。

3. 与 throw() 的区别
  • noexcept 更加明确地表达了函数不会抛出异常的意图。
  • noexcept 可以用在表达式中,例如 noexcept(expr),用于检测一个表达式是否会抛出异常。
  • noexcept 提供了更好的语义,并且编译器可以利用这一信息进行优化。
4. 使用 noexcept 表达式
#include <iostream>
#include <stdexcept>bool isSafe(int a, int b) noexcept {return b != 0;
}double safeDivision(int a, int b) noexcept(isSafe(a, b)) {return static_cast<double>(a) / static_cast<double>(b);
}int main() {try {std::cout << "Result: " << safeDivision(1, 0) << std::endl;} catch (const std::exception& e) {std::cerr << "Caught exception: " << e.what() << std::endl;}return 0;
}

总结

  1. C++98 中的异常规范

    • 使用 throw(类型列表) 来声明函数可能抛出的异常类型。
    • 使用 throw() 表示函数不会抛出异常。
    • 但存在开发人员误用和内部函数抛出异常的问题。
  2. C++11 中的 noexcept

    • 更加明确地表达了函数不会抛出异常的意图。
    • 可以用在表达式中,例如 noexcept(expr)
    • 提供了更好的语义,并且编译器可以利用这一信息进行优化。

通过使用 noexcept,可以更好地表达函数的异常抛出特性,并且允许编译器进行更有效的优化。这使得代码更易于理解和维护。

标准库异常体系

C++标准库提供了一系列标准的异常类,这些类定义在<exception>头文件中,并以父子类层次结构组织起来。主要的异常类包括:

  • std::exception:所有标准异常类的基类,提供了what()方法用于返回异常的描述信息。
  • std::bad_alloc:当内存分配失败时抛出的异常。
  • std::bad_cast:在使用dynamic_cast进行向下转换时,如果转换失败则抛出此异常。
  • std::bad_typeid:在使用typeid运算符时,如果操作数是一个多态类型对象的指针或引用,但该对象不是当前处理的多态类型或其派生类型之一,则抛出此异常。
  • std::bad_function_call:当尝试调用一个空的std::function对象时抛出的异常。
  • std::invalid_argument:当传递给函数的参数无效时抛出的异常。
  • std::out_of_range:当尝试访问超出有效范围的元素时抛出的异常(如std::vector的越界访问)。
    在这里插入图片描述

异常的优缺点

优点

  • 清晰的错误信息:异常对象可以包含丰富的错误信息,相比错误码的方式可以清晰准确的展示出错误的各种信息,有助于快速定位问题。
  • 便于错误传播:异常可以自动向上层传播,直到找到相应的处理代码,减少了错误处理的代码量。
  • 结构化错误处理:通过trycatchthrow关键字,可以结构化地处理错误情况,提高了代码的可读性和可维护性。
  • 第三方库的支持:很多第三方库(如 Boost、Google Test、Google Mock 等)都内置了异常处理机制,如果你使用这些库,那么自然也需要使用异常来处理错误情况。
  • 部分函数使用异常会更好处理,比如构造函数没有返回,不方便使用错误码方式处理。比如T& operator 这样的函数,如果 pos 越界了只能使用异常或者终止程序处理(使用错误码不方便表示),没办法通过返回值表示错误。

返回错误码的传统方式有个很大的问题就是,在函数调用链中,深层的函数返回了错误,我么需要层层返回错误,最外层才能拿到错误信息,如:

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. 这段伪代码我们可以看到 ConnnectSql 中出错了,先返回给 ServerStartServerStart 再返回给main函数,main函数再针对问题处理具体的错误。
  2. 如果是异常体系,不管是ConnnectSql还是ServerStart及调用函数出错,都不用检查,因为抛出的异常异常会直接跳到main函数中catch捕获的地方,main函数直接处理错误。

缺点

  • 执行流混乱:异常可能导致程序的执行流变得难以预测,增加了调试的难度。当异常被抛出时,程序会立即跳转到最近的 catch 块,这打破了正常的控制流。这种“乱跳”使得程序的逻辑变得难以追踪,尤其是在复杂的多层嵌套调用中。
  • 性能开销:异常处理机制本身需要一定的性能开销,特别是在频繁抛出和捕获异常的情况下。
  • 资源管理问题:在异常发生时,如果资源(如内存、文件句柄等)没有得到正确释放,可能会导致资源泄漏等问题。因此,在使用异常时需要注意资源的管理和释放。
  • 标准库异常体系定义不完善:C++标准库的异常体系定义并不完善,导致开发者往往需要自己定义异常体系,这容易造成混乱。通常,团队内部或项目中采用统一的异常体系,减少混乱。
  • 异常规范:不规范的异常使用会导致代码难以维护和理解。为了保证代码质量,需要遵循一定的异常规范。

总结

尽管C++中的异常处理机制存在一些潜在的缺点,但其提供的清晰的错误信息、便于错误传播、结构化错误处理以及对特殊函数的支持,使得异常处理在许多场景下都是一个非常有用的工具。通过合理使用异常处理,并结合RAII等技术,可以编写出既健壮又易维护的代码。


今天的分享就到这里了,如果,你感觉这篇博客对你有帮助的话,就点个赞吧!感谢感谢……

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

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

相关文章

隐藏SpringBoot自动生成的文件

第一种方法——删除 第二种方法——Settings——Editor——fail types

idea 创建多模块项目

一、新建项目&#xff0c;创建父工程 新建项目&#xff0c;选择 spring initializr 填写相关信息后提交 删除不相关的目录&#xff0c;如下 修改打包方式为 pom&#xff0c;在 pom.xml 文件中新增一行&#xff0c;如下 二、创建子模块 新增子模块 三、修改 pom 文件 修…

怎样用python+sqlalchemy获得mssql视图对应物理表关系(二)

话不多说 目标:为了实现低代码数据视图对接,有必要得到视图所对应物理表及字段名称,字段类型等 1)约束:视图中用到的物理表不能起别名,所以修改上一篇中存储过程建立语句 USE [agui_conn] GO /****** Object: StoredProcedure [dbo].[sp_GetOrdersByTimestamp] Script D…

04 面部表情识别:Pytorch实现表情识别-表情数据集训练代码

总目录&#xff1a;人脸检测与表情分类 https://blog.csdn.net/whiffeyf/category_12793480.html 目录 0 相关资料1 面部表情识数据集2 模型下载3 训练 0 相关资料 面部表情识别2&#xff1a;Pytorch实现表情识别(含表情识别数据集和训练代码)&#xff1a;https://blog.csdn.n…

vscode配置Eslint后保存出现大量波浪线

解决问题&#xff1a;配置代码格式化 快捷键打开设置&#xff1a;ctrlshiftP 输入&#xff1a; format code 选择&#xff1a;

UE5 项目缓存文件删除、版本控制说明(工程目录结构)

文章目录 前言一、项目文件示例二、缓存文件删除、版本控制说明前言 我们在拷贝项目或者使用 Git 进行版本控制,如果不对文件选择性的控制,大量缓存文件会导致传输速度变慢;或者我们的项目报错了,想要删除缓存文件又不知如何下手,哪些是可删除的,哪些又是不可删除的,本…

SLA(立体光固化成型技术)

01 SLA 3D打印技术简介 SLA工艺简介 SLA是"Stereo lithography Appearance"的缩写&#xff0c;即立体光固化成型法。用特定波长与强度的激光聚焦到光固化材料表面&#xff0c;使之由点到线&#xff0c;由线到面顺序凝固&#xff0c;完成一个层面的绘图作业&#x…

OpenCV图像文件读写(4)解码图像数据函数imdecode()的使用

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 从内存缓冲区读取图像。 imdecode 函数从指定的内存缓冲区读取图像。如果缓冲区太短或包含无效数据&#xff0c;函数将返回一个空矩阵 (Mat::dat…

儿童乐园软件下载安装 佳易王游乐场会员扣次管理系统操作教程

一、前言 儿童乐园软件下载安装 佳易王游乐场会员扣次管理系统操作教程 软件为绿色免安装版&#xff0c;已经内置数据库&#xff0c;不需再安装数据库文件&#xff0c;软件解压即可。 二、软件程序教程 1、软件可同时管理多个项目&#xff0c;项目设置方法如图&#xff0c;点…

基于微信小程序爱心领养小程序设计与实现(源码+参考文档+定制开发)

博主介绍&#xff1a; ✌我是阿龙&#xff0c;一名专注于Java技术领域的程序员&#xff0c;全网拥有10W粉丝。作为CSDN特邀作者、博客专家、新星计划导师&#xff0c;我在计算机毕业设计开发方面积累了丰富的经验。同时&#xff0c;我也是掘金、华为云、阿里云、InfoQ等平台…

grafana频繁DataSourceError问题

背景 随着 Grafana 数据量的不断增加&#xff0c;逐渐暴露出以下问题&#xff1a; Grafana 页面加载缓慢&#xff1b;Grafana 告警频繁出现 DatasourceError 错误。 对于第一个问题&#xff0c;大家可以参考这篇文章&#xff1a;Grafana 加载缓慢的解决方案。 不过&#xf…

828华为云征文|华为云Flexus X实例Windows Server 2019安装护卫神防火墙——为企业运维安全发挥重要作用!!!

前言 公司最近需要选购一台华为云Windows服务器部署产品应用&#xff0c;但是考虑到Windows的安全性至关重要。护卫神防火墙无疑是守护Windows系统安全的得力助手。 华为云以其强大的性能和稳定的服务&#xff0c;为众多企业和开发者提供了可靠的云端基础设施。在网络环境日益复…

【Python报错已解决】 TypeError: ‘ellipsis‘ object is not subscriptable

引言&#xff1a; 在Python开发过程中&#xff0c;遇到报错是家常便饭。而每一种报错都像是一个谜题&#xff0c;等待着开发者去解开。今天我们就来探讨一个特定的Python报错&#xff1a;TypeError: ‘ellipsis‘ object is not subscriptable。这个报错可能会让很多开发者感到…

【高效办公】商汤办公小浣熊微信小程序:随时随地搞定数据分析

在这个数据驱动的时代&#xff0c;无论是职场新人还是经验丰富的专业人士&#xff0c;都需要处理大量的数据和报表。为了提高工作效率&#xff0c;各种智能化工具层出不穷。今天要向大家推荐一款由商汤科技研发的高效数据分析工具——“办公小浣熊”&#xff01; 一、办公小浣…

【实战篇】怎么最快地复制一张表?

背景 怎么在两张表中拷贝数据&#xff1f;如果可以控制对源表的扫描行数和加锁范围很小的话&#xff0c;我们简单地使用 insert … select 语句即可实现。 当然&#xff0c;为了避免对源表加读锁&#xff0c;更稳妥的方案是先将数据写到外部文本文件&#xff0c;然后再写回目…

物理加密机的高性能操作

物理加密机&#xff0c;也被称为硬件安全模块(HSM)或加密锁&#xff0c;是一种用于保护敏感数据和软件应用的物理设备。以下是关于物理加密机的详细介绍&#xff1a; 一、定义与功能 物理加密机通过提供强大的加密功能和访问控制&#xff0c;确保数据在存储、处理和传输过程中的…

Linux网络:网络编程套接字

socket 套接字 socket常见API 创建套接字&#xff1a;&#xff08;TCP/UDP&#xff0c;客户端服务器&#xff09; int socket(int domain, int type, int protocol);绑定端口号&#xff1a;&#xff08;TCP/UDP&#xff0c;服务器&#xff09; int listen(int sockfd, int …

椭圆距离计算的简单方法

分析发现找到点到椭圆的最近距离等价于求解一元四次方程。想象一下一个圆和一个椭圆最多相交四次。从这个观点出发,问题转化为找到与椭圆仅相交一次的圆。如果用四次方程表示,其中两个根将在交点处共享,而另外两个根将会是复数。 尽管四次方程的封闭解确实存在,但迭代方法更…

【刷点笔试面试题试试水】不使用任何中间变量如何将a、b的值进行交换?

大家好,这里是国中之林! ❥前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站。有兴趣的可以点点进去看看← 问题: 解答: #include <iostream> using namespace std;void swap1(int&am…

深入探索 RUM 与全链路追踪:优化数字体验的利器

作者&#xff1a;梅光辉&#xff08;重彦&#xff09; 背景介绍 随着可观测技术的持续演进&#xff0c;多数企业已广泛采用 APM、Tracing 及 Logging 解决方案&#xff0c;以此强化业务监控能力&#xff0c;尤其在互联网行业&#xff0c;产品的体验直接关系着用户的口碑&…