C++学习:六个月从基础到就业——异常处理:机制与最佳实践
C++学习:六个月从基础到就业——异常处理:机制与最佳实践
本文是我C++学习之旅系列的第三十八篇技术文章,也是第二阶段"C++进阶特性"的最后一篇,主要介绍C++中的异常处理机制及其最佳实践。查看完整系列目录了解更多内容。
引言
在处理复杂软件系统时,错误处理是一个关键问题。C++提供了异常处理机制,使我们能够分离正常代码流程和错误处理逻辑,从而提高代码的可读性和鲁棒性。然而,异常处理也是C++中最具争议的特性之一,使用不当会导致性能问题、资源泄漏和复杂的控制流。
本文将深入探讨C++异常处理机制的工作原理、最佳实践以及如何在实际项目中有效地使用异常处理。我们将从基础语法开始,逐步深入到高级主题,帮助你掌握这一强大而复杂的语言特性。
异常处理基础
异常处理的基本语法
C++异常处理的基本构造包括三个关键词:try
、catch
和throw
。
#include <iostream>
#include <stdexcept>double divide(double numerator, double denominator) {if (denominator == 0) {throw std::runtime_error("Division by zero!");}return numerator / denominator;
}int main() {try {// 可能抛出异常的代码double result = divide(10, 0);std::cout << "Result: " << result << std::endl;} catch (const std::runtime_error& e) {// 处理特定类型的异常std::cerr << "Caught runtime_error: " << e.what() << std::endl;} catch (const std::exception& e) {// 处理其他标准异常std::cerr << "Caught exception: " << e.what() << std::endl;} catch (...) {// 处理所有其他类型的异常std::cerr << "Caught unknown exception" << std::endl;}std::cout << "Program continues execution" << std::endl;return 0;
}
在上面的例子中:
throw
语句用于抛出异常try
块包含可能抛出异常的代码catch
块用于捕获和处理特定类型的异常...
可以用于捕获任何类型的异常
异常处理的工作机制
当异常被抛出时,C++运行时系统开始"栈展开"(stack unwinding)过程:
- 程序停止执行当前函数中throw语句后的代码
- 运行时系统沿着调用栈向上搜索,寻找处理该异常类型的catch块
- 在栈展开过程中,所有局部对象的析构函数被调用
- 如果找到匹配的catch块,执行该块中的代码
- 执行完catch块后,程序从try-catch结构后的语句继续执行
如果没有找到匹配的catch块,程序将调用std::terminate()
函数,默认终止程序执行。
让我们通过一个示例来观察栈展开过程:
#include <iostream>class Resource {
private:std::string name;
public:Resource(const std::string& n) : name(n) {std::cout << "Resource " << name << " acquired" << std::endl;}~Resource() {std::cout << "Resource " << name << " released" << std::endl;}
};void function2() {Resource r("Function2");std::cout << "About to throw from function2" << std::endl;throw std::runtime_error("Error in function2");std::cout << "This line will never be executed" << std::endl;
}void function1() {Resource r("Function1");std::cout << "Calling function2" << std::endl;function2();std::cout << "This line will never be executed" << std::endl;
}int main() {try {Resource r("Main");std::cout << "Calling function1" << std::endl;function1();} catch (const std::exception& e) {std::cout << "Exception caught: " << e.what() << std::endl;}std::cout << "Program continues" << std::endl;return 0;
}
输出结果:
Resource Main acquired
Calling function1
Resource Function1 acquired
Calling function2
Resource Function2 acquired
About to throw from function2
Resource Function2 released
Resource Function1 released
Resource Main released
Exception caught: Error in function2
Program continues
从输出可以看到,当异常被抛出时,栈展开过程会按照与构造相反的顺序调用所有局部对象的析构函数,确保资源被正确释放。
标准异常
C++标准库异常层次结构
C++标准库提供了一套完整的异常层次结构,根类是std::exception
。以下是主要的标准异常:
std::exception
- 所有标准异常的基类std::logic_error
- 程序逻辑错误,一般可在程序开发时检测std::invalid_argument
- 无效参数std::domain_error
- 参数在有效范围外std::length_error
- 尝试创建太长的对象std::out_of_range
- 访问超出有效范围的元素
std::runtime_error
- 只能在运行时检测的错误std::range_error
- 计算结果超出有效范围std::overflow_error
- 计算导致上溢std::underflow_error
- 计算导致下溢
std::bad_alloc
- 内存分配失败std::bad_cast
- 动态类型转换失败std::bad_typeid
- 使用空指针调用typeidstd::bad_exception
- 意外的异常类型std::bad_function_call
- 调用空函数对象std::bad_weak_ptr
- 通过失效的weak_ptr创建shared_ptr
以下示例展示了如何使用一些常见的标准异常:
#include <iostream>
#include <vector>
#include <stdexcept>void processVector(const std::vector<int>& vec, int index) {// 参数验证if (vec.empty()) {throw std::invalid_argument("Vector cannot be empty");}// 范围检查if (index < 0 || index >= static_cast<int>(vec.size())) {throw std::out_of_range("Index out of range");}// 处理元素std::cout << "Value at index " << index << ": " << vec[index] << std::endl;
}int main() {std::vector<int> numbers;try {processVector(numbers, 0);} catch (const std::invalid_argument& e) {std::cerr << "Invalid argument: " << e.what() << std::endl;} catch (const std::out_of_range& e) {std::cerr << "Out of range: " << e.what() << std::endl;}numbers.push_back(10);numbers.push_back(20);try {processVector(numbers, 5);} catch (const std::exception& e) {std::cerr << "Exception: " << e.what() << std::endl;}try {processVector(numbers, 1);} catch (const std::exception& e) {std::cerr << "This should not be printed" << std::endl;}return 0;
}
自定义异常类
在实际项目中,标准异常可能无法满足所有需求,我们经常需要创建自定义异常类。一个好的做法是从std::exception
或其派生类继承:
#include <iostream>
#include <stdexcept>
#include <string>// 自定义异常基类
class ApplicationError : public std::runtime_error {
public:explicit ApplicationError(const std::string& message): std::runtime_error(message) {}
};// 特定类型的异常
class DatabaseError : public ApplicationError {
private:int errorCode;
public:DatabaseError(const std::string& message, int code): ApplicationError(message), errorCode(code) {}int getErrorCode() const {return errorCode;}
};class NetworkError : public ApplicationError {
private:std::string serverAddress;
public:NetworkError(const std::string& message, const std::string& address): ApplicationError(message), serverAddress(address) {}const std::string& getServerAddress() const {return serverAddress;}
};// 使用异常
void connectToDatabase(const std::string& connectionString) {if (connectionString.empty()) {throw DatabaseError("Empty connection string", 1001);}if (connectionString == "invalid") {throw DatabaseError("Invalid connection format", 1002);}std::cout << "Connected to database: " << connectionString << std::endl;
}void connectToServer(const std::string& address) {if (address.empty()) {throw NetworkError("Empty server address", "unknown");}if (address == "unreachable.com") {throw NetworkError("Server unreachable", address);}std::cout << "Connected to server: " << address << std::endl;
}int main() {try {try {connectToDatabase("invalid");} catch (const DatabaseError& e) {std::cerr << "Database error: " << e.what() << " (Error code: " << e.getErrorCode() << ")" << std::endl;// 例如,在特定错误码的情况下重新抛出异常if (e.getErrorCode() == 1002) {throw NetworkError("Database connection failed, trying backup server", "backup.com");}}} catch (const NetworkError& e) {std::cerr << "Network error: " << e.what()<< " (Server: " << e.getServerAddress() << ")" << std::endl;} catch (const ApplicationError& e) {std::cerr << "Application error: " << e.what() << std::endl;} catch (const std::exception& e) {std::cerr << "Standard exception: " << e.what() << std::endl;}return 0;
}
异常规范与noexcept
异常规范的历史
在C++的历史中,异常规范经历了以下变化:
-
C++98/03: 引入了动态异常规范
throw(type-list)
void func() throw(std::runtime_error, std::logic_error);
-
C++11: 将动态异常规范标记为弃用,引入
noexcept
说明符void func() noexcept; // 保证不抛出异常 void func() noexcept(expression); // 条件性保证
-
C++17: 彻底移除动态异常规范,只保留
noexcept
noexcept说明符
noexcept
说明符用于指定函数不会抛出异常:
void functionA() noexcept; // 保证不会抛出异常
void functionB() noexcept(sizeof(int) > 4); // 条件性保证
如果noexcept
函数确实抛出了异常,std::terminate
将被调用,立即终止程序。
noexcept
的主要用途:
- 优化:编译器可以对标记为
noexcept
的函数进行更积极的优化 - 保证:提供不会抛出异常的保证,特别是在移动操作中
- 文档:作为接口文档的一部分,明确函数的异常行为
noexcept运算符
noexcept
也可以作为运算符使用,检查表达式是否声明为不抛出异常:
#include <iostream>
#include <type_traits>
#include <vector>void mayThrow() {throw std::runtime_error("Error");
}void noThrow() noexcept {// 不抛出异常
}template<typename T>
void templateFunc() noexcept(noexcept(T())) {T t; // 如果T的构造函数不抛异常,这个函数也不抛异常
}int main() {std::cout << std::boolalpha;std::cout << "mayThrow() noexcept? " << noexcept(mayThrow()) << std::endl;std::cout << "noThrow() noexcept? " << noexcept(noThrow()) << std::endl;std::cout << "templateFunc<int>() noexcept? " << noexcept(templateFunc<int>()) << std::endl;std::cout << "templateFunc<std::vector<int>>() noexcept? " << noexcept(templateFunc<std::vector<int>>()) << std::endl;return 0;
}
何时使用noexcept
以下是使用noexcept
的一些指南:
-
移动构造函数和移动赋值运算符:这些操作应尽可能标记为
noexcept
,因为标准库容器会利用这一点进行优化 -
析构函数:默认情况下,析构函数已经隐式标记为
noexcept
,除非显式声明可能抛出异常 -
swap函数:交换函数通常应标记为
noexcept
,因为它们是许多算法的基础 -
内存管理函数:分配/释放内存的函数通常应考虑使用
noexcept
-
简单的getter函数:不执行复杂操作的访问器函数
例如:
class MyString {
private:char* data;size_t length;public:// 析构函数默认为noexcept~MyString() {delete[] data;}// 移动构造函数声明为noexceptMyString(MyString&& other) noexcept: data(other.data), length(other.length) {other.data = nullptr;other.length = 0;}// 移动赋值运算符声明为noexceptMyString& operator=(MyString&& other) noexcept {if (this != &other) {delete[] data;data = other.data;length = other.length;other.data = nullptr;other.length = 0;}return *this;}// 交换函数声明为noexceptvoid swap(MyString& other) noexcept {std::swap(data, other.data);std::swap(length, other.length);}// getter函数声明为noexceptsize_t getLength() const noexcept {return length;}// 可能抛出异常的方法不标记为noexceptvoid resize(size_t newLength) {// 这可能会抛出std::bad_alloc异常char* newData = new char[newLength];// ...}
};
异常安全性
异常安全保证级别
异常安全性是指程序在面对异常时能够维持的可靠性和正确性。C++中通常定义了以下几个级别的异常安全保证:
-
无异常安全保证(No guarantee):
- 出现异常时,程序可能处于未定义状态
- 可能有资源泄漏或数据损坏
- 应避免这种代码
-
基本异常安全保证(Basic guarantee):
- 出现异常时,程序保持在有效状态
- 没有资源泄漏
- 但是对象状态可能已经改变
-
强异常安全保证(Strong guarantee):
- 出现异常时,操作要么完全成功,要么状态不变
- 保证"事务语义"或"原子性"
- 通常通过"copy-and-swap"实现
-
无异常保证(No-throw guarantee):
- 保证操作不会抛出异常
- 对于某些操作(如析构函数)至关重要
实现异常安全
RAII (资源获取即初始化)
RAII是实现异常安全的最基本技术,它确保在构造函数中获取资源,在析构函数中释放资源:
#include <iostream>
#include <fstream>
#include <memory>
#include <mutex>// RAII文件处理
class FileRAII {
private:std::fstream file;
public:FileRAII(const std::string& filename, std::ios::openmode mode): file(filename, mode) {if (!file.is_open()) {throw std::runtime_error("Failed to open file: " + filename);}}// 不需要析构函数,std::fstream会自动关闭文件std::fstream& get() { return file; }
};// RAII互斥锁
class LockRAII {
private:std::mutex& mtx;
public:explicit LockRAII(std::mutex& m) : mtx(m) {mtx.lock();}~LockRAII() {mtx.unlock();}// 禁止复制LockRAII(const LockRAII&) = delete;LockRAII& operator=(const LockRAII&) = delete;
};void processFile(const std::string& filename) {FileRAII file(filename, std::ios::in | std::ios::out);// 使用文件...file.get() << "Writing some data" << std::endl;// 即使这里抛出异常,文件也会被正确关闭if (filename == "bad_file.txt") {throw std::runtime_error("Processing error");}// 正常退出时,文件也会被关闭
}std::mutex globalMutex;void criticalSection() {LockRAII lock(globalMutex);// 临界区代码...std::cout << "Executing critical section" << std::endl;// 即使这里抛出异常,互斥锁也会被释放if (rand() % 10 == 0) {throw std::runtime_error("Random failure in critical section");}// 正常退出时,互斥锁也会被释放
}
Copy-and-Swap技术
Copy-and-Swap是实现强异常安全保证的常用技术:
#include <algorithm>
#include <iostream>
#include <vector>class Database {
private:std::vector<int> data;std::string connectionString;bool isConnected;public:Database(const std::string& conn) : connectionString(conn), isConnected(false) {}// 连接到数据库void connect() {// 假设这可能失败if (connectionString.empty()) {throw std::runtime_error("Empty connection string");}isConnected = true;std::cout << "Connected to database" << std::endl;}// 断开连接void disconnect() {if (isConnected) {isConnected = false;std::cout << "Disconnected from database" << std::endl;}}// 提供强异常安全保证的数据更新void updateData(const std::vector<int>& newData) {// 1. 创建副本std::vector<int> tempData = newData;// 2. 在副本上执行可能抛出异常的操作for (auto& value : tempData) {if (value < 0) {throw std::invalid_argument("Negative values not allowed");}value *= 2; // 一些处理}// 3. 当所有操作都成功时,交换新数据和旧数据data.swap(tempData);// tempData现在包含旧数据,将在函数退出时被销毁}// 使用swap实现强异常安全的赋值运算符Database& operator=(Database other) {// 注意:other是按值传递的,已经创建了副本swap(*this, other);return *this;}// 友元swap函数friend void swap(Database& first, Database& second) noexcept {using std::swap;swap(first.data, second.data);swap(first.connectionString, second.connectionString);swap(first.isConnected, second.isConnected);}// 查看数据void printData() const {std::cout << "Data: ";for (int value : data) {std::cout << value << " ";}std::cout << std::endl;}// 析构函数~Database() {disconnect();}
};
智能指针
智能指针是实现异常安全的另一个关键工具:
#include <memory>
#include <iostream>
#include <vector>class Resource {
public:Resource(int id) : id_(id) {std::cout << "Resource " << id_ << " acquired" << std::endl;}~Resource() {std::cout << "Resource " << id_ << " released" << std::endl;}void use() {std::cout << "Using resource " << id_ << std::endl;}private:int id_;
};void exceptionSafeFunction() {// 使用智能指针自动管理资源auto r1 = std::make_unique<Resource>(1);auto r2 = std::make_shared<Resource>(2);std::vector<std::shared_ptr<Resource>> resources;resources.push_back(std::move(r2));resources.push_back(std::make_shared<Resource>(3));// 即使此处抛出异常,r1和resources中的所有资源都会被正确释放for (const auto& res : resources) {res->use();if (rand() % 3 == 0) {throw std::runtime_error("Random failure");}}r1->use();
}
异常处理的性能考量
异常处理的成本
异常处理机制有一定的性能开销:
- 代码大小:异常处理相关的代码会增加程序大小
- 运行时开销:
- 正常路径:几乎没有开销
- 异常路径:栈展开和异常对象构造有显著开销
- 编译器优化限制:某些优化可能受到异常处理的限制
何时使用异常
基于性能考虑,对于异常处理的使用建议:
-
使用异常处理:
- 真正的异常情况(罕见、非预期的错误)
- 构造函数失败(无法通过返回值报告错误)
- 深层次函数调用中的错误传播
-
避免使用异常:
- 可预见的错误条件(如用户输入验证)
- 性能关键的代码路径
- 实时系统或硬性延迟要求的系统
- 嵌入式系统(资源受限)
错误处理策略比较
// 基于返回值的错误处理
bool parseData1(const std::string& input, int& result) {if (input.empty()) {return false; // 表示解析失败}try {result = std::stoi(input);return true; // 解析成功} catch (...) {return false; // 解析失败}
}// 基于异常的错误处理
int parseData2(const std::string& input) {if (input.empty()) {throw std::invalid_argument("Empty input");}return std::stoi(input); // 内部可能抛出异常
}// 使用示例
void errorHandlingDemo() {std::string input = "abc";// 方法1:使用返回值int result1;if (parseData1(input, result1)) {std::cout << "Parsed value: " << result1 << std::endl;} else {std::cout << "Failed to parse input" << std::endl;}// 方法2:使用异常try {int result2 = parseData2(input);std::cout << "Parsed value: " << result2 << std::endl;} catch (const std::exception& e) {std::cout << "Error: " << e.what() << std::endl;}
}
异常处理最佳实践
设计原则
-
只对异常情况使用异常处理:
- 异常应用于真正的异常情况,而非常规控制流
- 常见的错误应通过返回值或其他机制处理
-
异常类的设计:
- 保持轻量级,避免复杂的异常类
- 提供有意义的错误信息
- 考虑异常类的层次结构
-
异常安全性:
- 确保代码符合基本或强异常安全保证
- 使用RAII、智能指针和copy-and-swap等技术
-
处理位置:
- 在能够有意义处理异常的地方捕获它们
- 避免捕获所有异常然后不处理(空catch块)
具体实践
- 构造函数中使用异常:
- 构造函数无法返回错误码
- 构造失败时应抛出异常
class ConfigManager {
private:std::map<std::string, std::string> settings;public:ConfigManager(const std::string& configFile) {std::ifstream file(configFile);if (!file.is_open()) {throw std::runtime_error("Could not open config file: " + configFile);}// 解析配置文件...std::string line;while (std::getline(file, line)) {// 解析每行,填充settings// 如果格式错误,抛出异常if (!parseLine(line)) {throw std::runtime_error("Invalid config format: " + line);}}}private:bool parseLine(const std::string& line) {// 解析实现...return true;}
};
- 标准库和异常:
- 了解标准库函数何时抛出异常
- 对容器操作、IO操作和类型转换等可能抛异常的操作做好准备
void standardLibraryExceptions() {std::vector<int> vec;try {// 操作可能抛出异常的标准库函数vec.at(10); // 会抛出std::out_of_range} catch (const std::out_of_range& e) {std::cerr << "Range error: " << e.what() << std::endl;}try {// 类型转换可能抛出异常std::string numberStr = "abc";int number = std::stoi(numberStr); // 会抛出std::invalid_argument} catch (const std::invalid_argument& e) {std::cerr << "Invalid argument: " << e.what() << std::endl;}
}
- 异常与RAII:
- 确保资源管理遵循RAII原则
- 避免在析构函数中抛出异常
// 不好的做法:析构函数抛出异常
class BadPractice {
public:~BadPractice() {// 糟糕的设计!throw std::runtime_error("Exception in destructor");}
};// 好的做法:析构函数不抛出异常
class GoodPractice {
public:~GoodPractice() noexcept {try {// 可能失败的操作} catch (const std::exception& e) {// 记录错误但不抛出std::cerr << "Error in destructor: " << e.what() << std::endl;}}
};
- 异常与多线程:
- 理解线程边界如何影响异常处理
- 确保每个线程都有适当的异常处理
#include <thread>
#include <future>void threadExceptionHandling() {// 方法1:使用标准线程,需要在线程内处理异常std::thread t1([]() {try {// 可能抛出异常的代码throw std::runtime_error("Error in thread");} catch (const std::exception& e) {std::cerr << "Thread caught exception: " << e.what() << std::endl;}});t1.join();// 方法2:使用async,可以传播异常auto future = std::async(std::launch::async, []() {// 异常将存储在future中throw std::runtime_error("Error in async task");return 42;});try {// get()将重新抛出存储在future中的任何异常int result = future.get();} catch (const std::exception& e) {std::cerr << "Caught async exception: " << e.what() << std::endl;}
}
文档和契约
明确文档化函数的异常规范是良好实践:
/*** 计算两个数的除法结果。* * @param a 被除数* @param b 除数* @return a除以b的结果* @throws std::invalid_argument 如果b为0*/
double safeDivide(double a, double b) {if (b == 0) {throw std::invalid_argument("Division by zero");}return a / b;
}
调试和错误定位
异常可以包含丰富的上下文信息,帮助诊断问题:
#include <sstream>class ContextualException : public std::runtime_error {
private:std::string contextInfo;public:ContextualException(const std::string& message, const std::string& file,int line,const std::string& function): std::runtime_error(message) {std::ostringstream oss;oss << "Error: " << message<< "\nFile: " << file<< "\nLine: " << line<< "\nFunction: " << function;contextInfo = oss.str();}const char* what() const noexcept override {return contextInfo.c_str();}
};// 使用宏简化异常创建
#define THROW_EXCEPTION(message) \throw ContextualException(message, __FILE__, __LINE__, __func__)void functionA() {THROW_EXCEPTION("Something went wrong in functionA");
}void functionB() {try {functionA();} catch (const ContextualException& e) {std::cerr << "Caught exception with context:\n" << e.what() << std::endl;throw; // 重新抛出}
}
总结
异常处理是C++中处理错误的强大机制,但需要谨慎使用才能充分发挥其优势。本文探讨了以下关键点:
- 基础机制:理解try-catch-throw语法和栈展开过程
- 标准异常:使用标准库提供的异常层次结构
- 自定义异常:创建合理的异常类层次结构
- 异常规范:使用noexcept适当标记函数
- 异常安全保证:理解不同级别的异常安全保证
- 实现技术:使用RAII、智能指针和Copy-and-Swap等技术
- 性能考量:了解异常处理的性能影响
- 最佳实践:掌握异常处理的设计原则和具体实践
遵循这些最佳实践,你可以编写出更健壮、可维护的C++代码,有效地处理各种错误情况,同时避免异常处理带来的潜在问题。
记住,异常处理不仅仅是语言特性,它是一种设计和架构工具,能够帮助我们构建更可靠的软件系统。无论是选择使用异常还是其他错误处理机制,最重要的是保持一致性,并确保所有资源都得到适当管理。
这是我C++学习之旅系列的第三十八篇技术文章。查看完整系列目录了解更多内容。
相关文章:
C++学习:六个月从基础到就业——异常处理:机制与最佳实践
C学习:六个月从基础到就业——异常处理:机制与最佳实践 本文是我C学习之旅系列的第三十八篇技术文章,也是第二阶段"C进阶特性"的最后一篇,主要介绍C中的异常处理机制及其最佳实践。查看完整系列目录了解更多内容。 引言…...
【MongoDB篇】MongoDB的数据库操作!
目录 引言第一节:数据库的“诞生”——如何创建数据库?🤔第二节:数据库的“查阅”——看看我的数据库们!🕵️♀️第三节:数据库的“切换”——我在哪个房间干活?➡️🚪…...
react-新建项目复用node_modules
每次新建定制时,前端都需要npm i来安装依赖,耗时长 失败多。 可以把这个bat文件放到新建分支的前端目录下,修改后双击bat文件运行,如果不需要添加修改依赖,无需运行npm i node_modules.bat里面的内容如下:…...
unity Orbbec Femto Bolt接入unity流程记录 AzureKinectExamples 插件 使用记录
奥比中光的深度相机Orbbec Femto Bolt是Microsoft的Azure Kinect DK的升级版,根据官网的文档配置环境遇到了一些问题,记录一下。 注意: 官网文档链接:Femto Bolt文档 1、首先连接相机到电脑USB3.0,接通电源…...
信息科技伦理与道德3-4:面临挑战
1 人机结合 1.1 人机结合的挑战 如何处理好人与机器的决策的关系?智能决策的不透明、不可解释性…出了问题该谁负责? 案例1:设想救护车调度系统造成混乱 某城市使用一个机器学习平台来进行城市里医院的救护车调度工作。起初,这个…...
对比测评:为什么AI编程工具需要 Rules 能力?
通义灵码 Project Rules 在开始体验通义灵码 Project Rules 之前,我们先来简单了解一下什么是通义灵码 Project Rules? 大家都知道,在使用 AI 代码助手的时候,有时候生成的代码不是自己想要的,或者说生成的代码采纳后…...
git学习之git常用命令
1. 初始化仓库 git init初始化一个新的 Git 仓库。 2. 克隆远程仓库 git clone <repository-url>从远程服务器克隆一个已有仓库到本地。 3. 配置用户名和邮箱 git config --global user.name "Your Name" git config --global user.email "youexampl…...
The Open Group 参加雷丁博物馆的数字革命展览
The Open Group 参加了雷丁博物馆的数字革命展览,庆祝雷丁市转型为数字中心60周年。 展览于3月18日(星期二)向公众开放,将持续至2025年12月24日。展览旨在纪念雷丁市令人惊叹的科技之旅,从1964年数字设备公司ÿ…...
Linux[配置vim]
Linux[配置vim] 我这里的环境是xshell8的虚拟机,Ubuntu 配置好了以后功能嘎嘎多 以下是为 Ubuntu 配置功能增强版 Vim 的详细步骤,包含代码高亮、插件管理、自动补全、文件导航等常用功能: 1. 安装最新版 Vim sudo apt update sudo apt install vim-g…...
【数据结构】图论存储结构深度解析:邻接多重表如何实现无向图O(1)删边?邻接矩阵/链表/十字链对比
邻接多重表 导读一、有向图的存储结构二、邻接多重表三、存储结构四、算法评价4.1 时间复杂度4.2 空间复杂度 五、四种存储方式的总结5.1 空间复杂度5.2 找相邻边5.3 删除边或结点5.4 适用于5.5 表示方式 六、图的基本操作结语 导读 大家好,很高兴又和大家见面啦&a…...
【AlphaFold2】Feature extraction:提取特征,为模型输入做准备|Datapipeline讲解
博主简介:努力学习的22级计算机科学与技术本科生一枚🌸博主主页: Yaoyao2024往期回顾:【深度学习】多头注意力机制的实现|pytorch每日一言🌼: 学习成绩只是表象,而学习能力才是伴随一身的结果🌺…...
Android 实现一个隐私弹窗
效果图如下: 1. 设置同意、退出、点击用户协议、点击隐私协议的函数参数 2. 《用户协议》、《隐私政策》设置成可点击的,且颜色要区分出来 res/layout/dialog_privacy_policy.xml 文件 <?xml version"1.0" encoding"utf-8"?&…...
第三方软件测试报告如何凭借独立公正与专业权威发挥关键作用?
在软件项目里,第三方软件测试报告起着极为关键的作用。第三方有着中立客观的立场。第三方具备专业能力。凭借这些,第三方能为软件质量评估提供可靠依据。下面要从不同方面介绍第三方软件测试报告。 独立公正性 第三方测试机构与软件开发方、使用方不存…...
QT控件 参考Qt的PIMPL设计模式实现使用QWidget控件绘制3D饼状图表和3D柱状图表,使用QChartView绘制圆柱体图表
整体绘制效果就是:Qt 实现3维饼状图 中的内容, 只不过我借鉴了Qt的PIMPL模式重新封装了整个实现过程 实现效果展示 目录导读 实现效果展示前言绘制3D饼状图表PIMPL模式设计类具体实现计算圆弧中心判断点是否在某个扇区中在私有类中绘制绘制3D柱状图表PIMPL模式设计类具体实现绘…...
Android Q允许低内存启用系统弹窗
如果SYSTEM_ALERT_WINDOW权限可用,则返回true。 *从Q开始,在低ram手机上禁用SYSTEM_ALERT_WINDOW。 vendor/mediatek/proprietary/packages/apps/MtkSettings/src/com/android/settings/Utils.java public static boolean isSystemAlertWindowEnabled(Co…...
Leetcode 3532. Path Existence Queries in a Graph I
Leetcode 3532. Path Existence Queries in a Graph I 1. 解题思路2. 代码实现 题目链接:3532. Path Existence Queries in a Graph I 1. 解题思路 这一题算是一个比较典型的DSU的题目,我们就是不断地根据前后节点的距离将其进行聚类,然后…...
AI Agent Protocols:现状、挑战与未来展望
一、引言 在当今人工智能飞速发展的时代,大语言模型(LLMs)的进步使得LLM智能体在各个行业得到了广泛的应用,如客户服务、内容生成、数据分析和医疗保健等领域。 然而,随着越来越多的LLM智能体被部署,一个…...
自动化立库/AGV物流仿真详细步骤
以下是一种可以在预算和周期内实现自动化立库及AGV 方案仿真分析的方法: 一、工具选择 软件工具FlexSim:这是一款流行的离散事件仿真软件。它具有直观的图形用户界面,通过简单的拖拽操作就可以构建自动化立库和 AGV 的模型。其内置的丰富的…...
【题解-Acwing】872. 最大公约数
题目:872. 最大公约数 题目描述 给定 n 对正整数 ai,bi,请你求出每对数的最大公约数。 输入 第一行包含整数 n。 接下来 n 行,每行包含一个整数对 ai,bi。 输出 输出共 n 行,每行输出一个整数对的最大公约数。 数据范围 1 ≤ n ≤ 105, 1 ≤ai, bi ≤ 2109 时空限…...
62.微服务保姆教程 (五) Seata--微服务分布式事务组件
Seata–微服务分布式事务组件 一、什么是分布式事务 1.什么是事务 事务指的是一个操作单元,在这个操作单元中的所有操作最终要保持一致的行为,要么所有操作都成功,要么所有的操作都被撤销。 2.本地事务 本地事务是指基于关系型数据库的事务,也称为传统事务。大多数场景…...
【算法练习】归并排序和归并分治
文章目录 1.归并排序1.1 递归版本1.2 非递归版本 2.归并分治2.1 计算数组的小和2.2 计算翻转对 1.归并排序 归并排序的核心步骤是: 拆分:将无序数组不断对半拆分成小块,直到每个小块只剩一个元素(自然有序)。 合并&a…...
从SOA到微服务:架构演进之路与实践示例
一、架构演进背景 在软件开发领域,架构风格随着业务需求和技术发展不断演进。从早期的单体架构,到面向服务架构(SOA),再到如今的微服务架构,每一次变革都是为了解决当时面临的核心问题。 二、SOA架构解析 2.1 SOA核心概念 SOA&…...
vue+cesium线流动纹理
index.vue页面 <!--线流动纹理实现--> <template><div id"mapContainerFirst"></div> </template> <script lang"ts" setup> import { init as initPolylineTrailLinkMaterialProperty } from ./PolylineTrailLinkM…...
深度学习·经典模型·SwinTransformer
SwinTransformer 主要创新点:移动窗口,基于窗口的注意力计算 Patch Embedding 下采样打包为Pacth:可以直接使用Conv2d 也可以先打包后使用embedding映射。 Patch Merging 类似池化的操作,压缩图片大小,同时通道数增多ÿ…...
在开发板上如何处理curl: (60) SSL certificate problem
目录 引言 问题解析 解决方法 跳过证书验证 采用证书认证 结语 引言 最近一直推荐学生们在课程实验中使用curl及其libcurl。curl 是一个强大的命令行工具,用于在命令行中进行数据传输。它支持多种协议,如 HTTP、HTTPS、FTP、FTPS、SCP、SFTP 等。…...
Ansible 铸就 Linux 安全之盾(Ansible Builds Linux Security Shield)
Ansible 铸就 Linux 安全之盾:自动化基线检查与防护 在当今网络安全形势日益严峻的背景下,Linux 系统作为服务器和关键基础设施的核心,其安全防护显得尤为重要。Ansible 作为一款强大的自动化运维工具,能够帮助我们高效、可靠地实…...
字符串(格式化字符串字面值)进行输出
在 Python 中,print(fnew_obs:{new_obs}) 这种形式是使用 f 字符串(格式化字符串字面值) 进行输出,它可以打印 任何可转换为字符串的数据类型,并且支持在字符串中嵌入表达式。以下是详细说明: 1. 基本功能…...
微服务架构详解:从概念到实践
目录 前言1. 微服务架构概述1.1 什么是微服务?1.2 微服务的核心思想 2. 微服务的优势2.1 可扩展性2.2 高灵活性2.3 容错性和可靠性2.4 高效开发与部署 3. 微服务的挑战3.1 系统复杂性增加3.2 分布式事务和数据一致性3.3 部署和运维的复杂性 4. 微服务的实施与实践4.…...
激光驱鸟:以科技重构生态防护边界
技术原理 激光驱鸟装置的核心机制基于鸟类视觉系统特性。其发射的绿色激光束(波长通常为532纳米)处于鸟类视网膜敏感光谱范围内,当激光束在特定角度扫描时,会形成动态光斑干扰。鸟类视网膜中视锥细胞对绿色光的高敏感度使其产生应…...
【Python魔法方法(特殊方法)】
在 Python 中,许多运算符都可以进行重载,以下是一些常见运算符及其对应的魔法方法(特殊方法): 算术运算符 加法 :__add__ 用于定义对象相加的行为。例如,当你对两个自定义类的实例使用 运算符…...
centos上安装python的3.13版本
在 CentOS 上安装 Python 3.13(或其它自定义版本)最推荐的方法是通过源码编译安装,不会影响系统自带的 Python2/Python3 环境,也更灵活可控。 以下步骤适用于: ✅ CentOS 7 / 8 / 9 ✅ 安装 Python 3.13(…...
实习技能记录【4】-----消息分发中的观察者模型
观察者 观察者模式(Observer Pattern)是一种行为型设计模式,主要用于定义对象之间的一对多依赖关系,让多个观察者对象能够同时监听某个主题对象的状态变化,并在主题对象状态改变时自动通知所有观察者对象。 参考b站博…...
Linux 下编译BusyBox
一、linux下编译 1.拉取busybox源码 git clone https://github.com/mirror/busybox.git 内容如下 2.配置make,建议在linux下单独开一个终端执行 进入busybox源码目录,使用如下命令 make menuconfig 3.报错 解决办法: 安装ncurses sud…...
Linux《进程概念(中)》
在之前的Linux《进程概念(上)》当中我们已经了解了进程的基本概念以及如何去创建对应的子进程,那么接下来在本篇当中我们就继续来进程的学习,在本篇当中我们要学习到进程的状态、进程的优先级、进程切换、Linux真实的调度算法——…...
Linux Vim 使用 显示行号、替换、查找、多文件打开等骚操作
目录 简述 vim的三种模式 概述 转换方式 文本编辑 命令模式 插入(编辑)模式 底行模式 搜索关键字 显示行号 替换 多文件打开 简述 vi编辑器是Linux系统下标准的编辑器。 那么简单的理解,就像是Windows下的记事本。 补充&a…...
AimRT 从零到一:官方示例精讲 —— 三、Executor示例.md
Executor示例 官方仓库:executor 配置文件(configuration_executor.yaml) 依据官方示例项目结构自行编写YAML配置文件: # 基础信息 base_info:project_name: Logger # 项目名称build_mode_tags: ["EXAMPLE", &quo…...
只把夜莺监控当作告警来使用:一种轻量化的运维实践
只把夜莺监控当作告警来使用:一种轻量化的运维实践 在现代的 IT 运维体系中,监控和告警是两个经常被一同提及的概念。然而,在实际工作中,很多团队对监控系统的需求并不一定全面覆盖指标采集、可视化展示、告警触发等功能…...
按键精灵安卓ios辅助工具脚本:实用的文件插件(lua开源)
亮点:此lua插件可再android和ios上通用 1、获取文件的属性 2、改变当前的工作路径为dirpath 3、获取当前的工作路径 4、创建文件夹,支持多级创建 5、删除文件夹 6、递归遍历文件夹 7、设置文件的访问时间和修改时间 函数原型:lfs.Attribute(…...
水库现代化建设指南-水库运管矩阵管理系统建设方案
政策背景 2023年8月24日,水利部发布的水利部关于加快构建现代化水库运行管理矩阵的指导意见中指出,在全面推进水库工程标准化管理的基础上,强化数字赋能,加快构建以推进全覆盖、全要素、全天候、全周期“四全”管理,完…...
若依后台管理系统-v3.8.8-登录模块--个人笔记
各位编程爱好者们,你们好!今天让我们来聊聊若依系统在登录模块的一些业务逻辑,以及本人的一些简介和心得,那么废话不多说,让我们现在开始吧。 以下展示的这段代码,正是若依在业务层对应的登录代码…...
Flip PDF Plus Corp7.7.22电子书制作软件
flip pdf plus corporate7.7.22中文版由FlipBuilder官方出品的一款企业级的翻页电子书制作软件,拥有丰富的模板,主题和动画场景,每本书最大页数1000页,每本书的最大大小1GB,即可以帮助企业用户制作好丰富的电子书籍。 …...
公路安全知识竞赛主持稿串词
合 :尊敬的各位领导、各位来宾 、各位选手 : 大家上午 好! 男 :安全就是生命,安全就是效益,安全是一切工作的重中之重!安全生产只有满分,没有及格。只有安全生产这个环节不出差错,我…...
vscode 配置qt
工具:vscode、qttools、qtconfigure Search Mode改成基于cmake的。 # 在项目中指定Qt的路径 set(Qt5_DIR "/home/jp/qt-everywhere-src-5.12.9/arm-qt/lib/cmake/Qt5") # 用于指定 Qt5 的安装路径 find_package(Qt5 REQUIRED COMPONENTS Widgets)这样就…...
Node.js 事件循环和线程池任务完整指南
在 Node.js 的运行体系中,事件循环和线程池是保障其高效异步处理能力的核心组件。事件循环负责调度各类异步任务的执行顺序,而线程池则承担着处理 CPU 密集型及部分特定 I/O 任务的工作。接下来,我们将结合图示,详细剖析两者的工作…...
Java之BigDecimal
BigDecimal 是 Java 中用于高精度计算的类,特别适合需要精确十进制运算的场景,如金融计算、货币运算、概率计算等。 为什么需要 BigDecimal类 解决浮点数精度问题:float 和 double 使用二进制浮点运算,无法精确表示某些十进制小数…...
Qt5与现代OpenGL学习(四)X轴方向旋转60度
把上面两张图像放到D盘1文件夹内: shader.h #ifndef SHADER_H #define SHADER_H#include <QDebug> #include <QOpenGLShader> #include <QOpenGLShaderProgram> #include <QString>class Shader { public:Shader(const QString& verte…...
基于LVS+Keepalived+NFS的高可用负载均衡集群部署
目录 项目功能 2 项目的部署 2.1 部署环境介绍 2.2 项目的拓扑结构 2.3 项目环境调试 2.4 项目的部署 2.4.1 安装软件; 2.4.2 NFS服务器配置 2.4.3 Web节点配置 2.5 项目功能的验证 2.6 项目对应服务使用的日志 项目功能 负载均衡功能 实现原理:基于LVS(D…...
人工智能数学基础(四):线性代数
线性代数是人工智能领域的核心数学工具之一,广泛应用于数据表示、模型训练和算法优化等多个环节。本文将系统梳理线性代数的关键知识点,并结合 Python 实例,助力读者轻松掌握这一重要学科。资源绑定附上完整资源供读者参考学习! …...
基于C++的IOT网关和平台1:github项目ctGateway
初级代码游戏的专栏介绍与文章目录-CSDN博客 我的github:codetoys,所有代码都将会位于ctfc库中。已经放入库中我会指出在库中的位置。 这些代码大部分以Linux为目标但部分代码是纯C的,可以在任何平台上使用。 源码指引:github源…...
LeetCode 2962.统计最大元素出现至少 K 次的子数组:滑动窗口
【LetMeFly】2962.统计最大元素出现至少 K 次的子数组:滑动窗口 力扣题目链接:https://leetcode.cn/problems/count-subarrays-where-max-element-appears-at-least-k-times/ 给你一个整数数组 nums 和一个 正整数 k 。 请你统计有多少满足 「 nums 中…...