一篇文章教会你什么是C++异常

在这里插入图片描述

一篇文章教会你什么是C++异常

  • C语言传统的处理错误的方式
    • 断言检查
    • 返回值检查
    • 全局错误码
    • 设置全局错误处理函数
  • C++异常概念
    • 基本概念
    • 注意事项
  • 异常的使用
    • 异常的抛出和捕获
    • 异常的重新捕获
    • 异常安全
    • 异常规范
  • 自定义异常体系
  • C++标准库的异常体系
    • 1. std::exception
    • 2. std::bad_alloc
    • 3. std::bad_cast
    • 4. std::bad_typeid
    • 5. std::bad_exception
    • 6. std::logic_error
      • 6.1 std::invalid_argument
      • 6.2 std::domain_error
      • 6.3 std::length_error
      • 6.4 std::out_of_range
    • 7. std::runtime_error
      • 7.1 std::range_error
      • 7.2 std::overflow_error
      • 7.3 std::underflow_error
  • 异常的优缺点
    • 异常的优点
    • 异常的缺点

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

在传统的C语言中,错误处理通常采用以下方法:

断言检查

#include <stdio.h>
#include <assert.h>

int main() {
    int x = 5;
    int y = 7;

    // 检查条件,如果条件不满足,程序会终止并输出错误信息
    assert(x == y); // 这个例子会触发断言失败,因为x不等于y

    printf("After assert\n");
    return 0;
}

在这个例子中,assert(x == y) 会检查条件 x == y 是否为真。如果条件为假,程序会停止执行,并输出一条错误信息,显示失败的条件,以及在代码中的位置。

  • assert 在调试时非常有用,但在发布产品版本时,默认情况下通常会被禁用。这是因为在生产环境中,终止程序并输出错误信息可能不是一个良好的做法。
  • 在开发阶段,assert 可以帮助开发者快速定位代码中的问题,但不应该被用于处理预期可能发生的错误,如文件打开失败、内存分配失败等情况。对于这些情况,通常应该使用其他错误处理机制。

返回值检查

在C语言中,函数通常返回一个表示操作成功与否的值。例如,标准库函数fopen() 用于打开文件,如果成功打开文件,它将返回一个指向文件的指针,否则返回NULL。所以,调用这个函数后需要检查返回的指针是否为NULL,以确定文件是否成功打开。

FILE *file = fopen("example.txt", "r");
if (file == NULL) {
    // 处理文件打开失败的情况
} else {
    // 文件成功打开,可以进行读取或写入操作
    // 记得在结束后关闭文件:fclose(file);
}

全局错误码

在C中,有时候会使用全局变量来存储错误码。例如,标准库中的errno是一个表示发生错误类型的全局变量。函数会将错误码写入errno,然后调用方可以根据这个值判断是否发生了错误。

#include <stdio.h>
#include <errno.h>

int main() {
    FILE *file = fopen("example.txt", "r");
    if (file == NULL) {
        printf("Error number: %d\n", errno);
        // 可以使用 perror("fopen") 打印具体错误信息
    } else {
        // 文件成功打开,可以进行读取或写入操作
        // 记得在结束后关闭文件:fclose(file);
    }
    return 0;
}

设置全局错误处理函数

C语言中还允许你设置一个全局的错误处理函数,通过signal函数可以设置程序在遇到某些错误信号时调用特定的函数进行处理。

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>

void error_handler(int signal) {
    // 处理特定信号的代码
    printf("Error signal %d occurred\n", signal);
    exit(signal);
}

int main() {
    signal(SIGSEGV, error_handler); // 捕获段错误信号
    // 其他代码
    return 0;
}

C++异常概念

在 C++ 中,异常是一种用于处理程序运行时错误和异常情况的机制。当发生异常时,程序可以选择性地捕获和处理这些异常,避免导致程序崩溃或出现未定义行为。异常提供了一种更结构化的方式来处理错误,与传统的错误码或返回值不同。

基本概念

  1. 抛出异常(Throwing an exception):当程序执行过程中遇到错误或异常情况时,可以使用 throw 关键字抛出异常。异常通常是某种特定类型的对象,用于传递关于错误的信息。

    throw MyException("Something went wrong");
    
  2. 捕获异常(Catching an exception):使用 try-catch 块来捕获并处理异常。try 块包含可能抛出异常的代码,而 catch 块用于捕获并处理特定类型的异常。

    try {
        // 可能抛出异常的代码
    } catch (MyException& e) {
        // 处理 MyException 类型的异常
        std::cerr << "Caught an exception: " << e.what() << std::endl;
    }
    
  3. 异常传递(Exception propagation):如果在 try 块内抛出异常,程序将尝试匹配对应的 catch 块来处理异常。如果没有匹配到相应的 catch 块,异常会被传递到调用栈的上一层,直到找到匹配的处理器或者导致程序终止。

注意事项

  • 异常类型:通常,异常是某个特定类型的对象。C++允许使用任何类型(包括内置类型和自定义类型)作为异常,但最佳实践是使用继承自 std::exception 的自定义异常类。
  • 资源管理:异常可以破坏程序正常的控制流程。在使用动态分配的资源时(如内存或文件句柄),确保使用资源管理技术(比如 RAII)来避免资源泄露。
  • 异常成本:抛出和捕获异常会带来一定的性能开销。因此,在性能敏感的代码路径上过度使用异常可能不是最佳选择。
  • 异常安全性:在设计和编写代码时,要考虑异常对程序状态的影响。确保当发生异常时,程序状态不会出现不一致或资源泄露。

异常处理是C++中强大而灵活的特性,使得代码更加健壮和易于维护。然而,它需要小心谨慎地使用,特别是在设计高性能或对性能敏感的系统时。

异常的使用

异常的抛出和捕获

异常的抛出和匹配原则

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

使用基类捕获,在实际中非常实用

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

  1. 首先检查throw本身是否在try块内部,如果是再查找匹配的catch语句。如果有匹配的,则调到catch的地方进行处理。
  2. 没有匹配的catch则退出当前函数栈,继续在调用函数的栈中进行查找匹配的catch。
  3. 如果到达main函数的栈,依旧没有匹配的,则终止程序。上述这个沿着调用链查找匹配的catch子句的过程称为栈展开。所以实际中我们最后都要加一个catch(…)捕获任意类型的异常,否则当有异常没捕获,程序就会直接终止。
  4. 找到匹配的catch子句并处理以后,会继续沿着catch子句后面继续执行。
#include <iostream>
using namespace std;

double Division(int a, int b) {
    if (b == 0)
        throw "Division by zero condition!";
    else
        return static_cast<double>(a) / static_cast<double>(b);
}

void Func() {
    try {
        int len, time;
        cin >> len >> time;
        cout << Division(len, time) << endl;
    }
    catch (const char* errmsg) {
        cout << errmsg << endl;
        throw;  // 重新抛出异常,以便在主函数中捕获
    }
}

int main() {
    while (true) {
        try {
            Func();
        }
        catch (const char* errmsg) {
            cout << errmsg << endl;
        }
        catch (...) {
            cout << "未知异常" << endl;
        }
    }
    return 0;
}

在上面的示例中,展现了一个除法函数在进行除0操作时的异常触发抛出和捕获。

异常的重新捕获

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

double Division(int a, int b)
{
	// 当b == 0时抛出异常
	if (b == 0)
	{
		throw "Division by zero condition!";
	}
	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;
	}
	// ...
	cout << "delete []" << array << endl;
	delete[] array;
}
int main()
{
	try
	{
		Func();
	}
	catch (const char* errmsg)
	{
		cout << errmsg << endl;
	}
	return 0;
}

这里可以看到如果发生除0错误抛出异常,另外下面的array没有得到释放。所以这里捕获异常后并不处理异常,异常还是交给外面处理,这里捕获了再重新抛出去,但是这样的处理方法还是有很大问题的,后面接触智能指针才能更好的解决这个问题

#include <iostream>
#include <memory>

double Division(int a, int b) {
    // 当b等于0时抛出异常
    if (b == 0) {
        throw "Division by zero condition!";
    }
    return static_cast<double>(a) / static_cast<double>(b);
}

void Func() {
    // 使用智能指针unique_ptr管理动态分配的数组内存
    std::unique_ptr<int[]> array(new int[10]);
    try {
        int len, time;
        std::cin >> len >> time;
        std::cout << Division(len, time) << std::endl;
    } catch (...) {
        std::cout << "发生异常" << std::endl;
        // 无需手动管理内存,unique_ptr会处理
        throw; // 重新抛出异常以在主函数中捕获
    }
    // 无需显式删除,unique_ptr会处理
}

int main() {
    try {
        Func();
    } catch (const char* errmsg) {
        std::cout << errmsg << std::endl;
    }
    return 0;
}

在这个改进版本中,使用了 std::unique_ptr 来管理动态分配的数组内存,避免了手动的内存管理操作,确保异常发生时资源能够被正确释放。

异常安全

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

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

  • C++中异常经常会导致资源泄漏的问题,比如在new和delete中抛出了异常,导致内存泄漏,在lock和unlock之间抛出了异常导致死锁,C++经常使用智能指针中的RAII来解决以上问题,后面的文章中我们会详细讲解智能指针。

异常规范

  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++ 中,也就是C++11之后的标准中,throw 声明已经被视为过时的特性,而且在 C++11 标准之后,它的使用变得越来越不推荐。C++11 引入了 noexcept 关键字,用于指示函数是否可能抛出异常

// C++11 中新增的noexcept,表示不会抛异常
thread() noexcept;
thread (thread&& x) noexcept;

自定义异常体系

自定义异常体系在企业中的运用程度取决于具体的项目需求、规模和开发团队的实践。以下是一些关于自定义异常体系在企业中的运用的考虑因素:

  1. 异常分类和处理: 在大型项目中,通常会有多个模块和子系统,每个子系统可能面临不同的异常情况。自定义异常体系可以帮助将异常细化为不同的类型,使得在处理异常时更具体、更有针对性。
  2. 错误信息传递: 自定义异常体系可以包含更多有关错误的信息,例如错误代码、错误描述、触发异常的上下文等。这有助于更好地诊断和解决问题。
  3. 业务逻辑的清晰性: 在大型项目中,有可能涉及到复杂的业务逻辑。通过使用自定义异常,可以使代码更具可读性和可维护性,使得开发人员更容易理解和调试代码。
  4. 统一的错误处理策略: 自定义异常体系可以促使团队建立统一的错误处理策略,提高代码一致性。这对于维护和协同开发非常有帮助。
  5. 与第三方库和框架的集成: 在与第三方库和框架集成时,可能需要处理它们可能抛出的异常。自定义异常可以作为中介,使得整个系统的异常处理更加一致。
  6. 团队经验和偏好: 不同的开发团队可能有不同的经验和偏好,有些团队更喜欢使用标准库的异常,而有些团队则更愿意使用自定义异常体系。这取决于团队的技术文化和项目的特定需求。

总体而言,自定义异常体系在企业中的运用可以提高代码的质量、可维护性和可读性,特别是在大型项目中。然而,这并不意味着在所有情况下都必须使用自定义异常体系。在某些情况下,标准库提供的异常可能已经足够满足需求。最终,选择使用何种异常体系应该基于项目的具体要求和团队的实际情况。

下面是一个简易的服务器开发中通常使用的异常继承体系

#include<iostream>
#include<Windows.h>
using namespace std;

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 = '鱼佬'");
	}
}
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)
	{
		Sleep(100);
		try {
			HttpServer();
		}
		catch (const Exception& e) 
		{
			cout << e.what() << endl;
		}
		catch (...)
		{
			cout << "Unkown Exception" << endl;
		}
	}
	return 0;
}

可以看到上面的自定义异常例子中是继承的派生类对象,捕获一个基类

在C++中,通过捕获基类异常对象而不是派生类异常对象的好处主要在于代码的灵活性和简洁性。这种做法被称为基类异常捕获,它有以下几个优势:

  1. 多态的利用: 当派生类的异常对象被捕获时,基类异常指针或引用可以指向派生类的对象。这允许你使用多态的特性,即便捕获的是基类异常,你仍然可以访问派生类特有的信息。这对于在一个统一的异常处理中处理多种异常类型非常有用。
  2. 异常处理的一致性: 使用基类异常捕获可以提供更一致的异常处理方式,因为不需要为每个派生类都编写相应的 catch 块。这对于维护和修改代码是更为方便的。
  3. 简化异常处理逻辑: 使用基类异常捕获可以减少异常处理代码的数量,使得代码更简洁易读。这尤其在大型项目中,有助于降低维护的复杂性。

虽然基类异常捕获有其优势,但在一些情况下,特别是对于特定的异常处理需求,直接捕获派生类异常也是合理的选择。最佳实践取决于具体的应用场景和代码设计目标。

C++标准库的异常体系

C++ 标准库定义了一组异常类,这些异常类被组织成层次结构,形成了一个异常体系。这个体系的根是 std::exception 类,而其他具体的异常类都直接或间接地继承自它。让我们来详细了解一下 C++ 标准库的异常体系:

1. std::exception

std::exception 是 C++ 标准库中异常体系的根类。它定义了一个虚函数 what(),用于返回异常的描述信息。

class exception {
public:
    exception() noexcept;
    virtual ~exception() noexcept;
    virtual const char* what() const noexcept;
};
  • what(): 返回一个 C 风格的字符串,提供关于异常的描述信息。通常,自定义的异常类应该继承自 std::exception 并覆盖这个函数。

2. std::bad_alloc

std::bad_alloc 是用于内存分配失败的异常类,它继承自 std::exception

class bad_alloc : public exception {
public:
    bad_alloc() noexcept;
    bad_alloc(const bad_alloc& other) noexcept;
    virtual const char* what() const noexcept override;
};
  • what(): 提供有关内存分配失败的信息。

3. std::bad_cast

std::bad_cast 是用于类型转换失败的异常类,它继承自 std::bad_alloc

class bad_cast : public bad_alloc {
public:
    bad_cast() noexcept;
    bad_cast(const bad_cast& other) noexcept;
    virtual const char* what() const noexcept override;
};
  • what(): 提供有关类型转换失败的信息。

4. std::bad_typeid

std::bad_typeid 是用于 typeid 运算符无效的异常类,它继承自 std::bad_alloc

class bad_typeid : public bad_alloc {
public:
    bad_typeid() noexcept;
    bad_typeid(const bad_typeid& other) noexcept;
    virtual const char* what() const noexcept override;
};
  • what(): 提供有关 typeid 运算符无效的信息。

5. std::bad_exception

std::bad_exception 是一个通用的异常类,用于表示异常规范未匹配的异常,它继承自 std::exception

class bad_exception : public exception {
public:
    bad_exception() noexcept;
    bad_exception(const bad_exception& other) noexcept;
    virtual const char* what() const noexcept override;
};
  • what(): 提供有关异常规范未匹配的信息。

6. std::logic_error

std::logic_error 是用于表示逻辑错误的基类,它继承自 std::exception

class logic_error : public exception {
public:
    explicit logic_error(const std::string& what_arg);
    explicit logic_error(const char* what_arg);
};
  • what(): 通常由具体的派生类实现,提供有关逻辑错误的信息。

std::logic_error 是用于表示逻辑错误的基类,它有四个派生类,每个派生类都对应于一种特定的逻辑错误。这四个派生类分别是:

6.1 std::invalid_argument

class invalid_argument : public logic_error {
public:
    explicit invalid_argument(const std::string& what_arg);
    explicit invalid_argument(const char* what_arg);
};

invalid_argument 类表示函数参数无效的错误。例如,当一个函数接收到一个无效的参数时,可以抛出这个异常。

6.2 std::domain_error

class domain_error : public logic_error {
public:
    explicit domain_error(const std::string& what_arg);
    explicit domain_error(const char* what_arg);
};

domain_error 类表示在数学领域上的错误。例如,当一个函数接收到一个超出其定义域的参数时,可以抛出这个异常。

6.3 std::length_error

class length_error : public logic_error {
public:
    explicit length_error(const std::string& what_arg);
    explicit length_error(const char* what_arg);
};

length_error 类表示由于长度错误导致的异常。例如,当试图超过某个容器或字符串的最大长度时,可以抛出这个异常。

6.4 std::out_of_range

class out_of_range : public logic_error {
public:
    explicit out_of_range(const std::string& what_arg);
    explicit out_of_range(const char* what_arg);
};

out_of_range 类表示由于超出有效范围导致的异常。例如,当试图访问数组、容器或字符串中的不存在的元素时,可以抛出这个异常。

每个派生类都有两个构造函数,一个接受一个 std::string 类型的参数,另一个接受一个 const char* 类型的参数,用于提供关于异常的描述信息。在实际使用中,可以选择使用适当的派生类来更具体地表示不同的逻辑错误。

7. std::runtime_error

std::runtime_error 是用于表示运行时错误的基类,它继承自 std::exception

class runtime_error : public exception {
public:
    explicit runtime_error(const std::string& what_arg);
    explicit runtime_error(const char* what_arg);
};
  • what(): 通常由具体的派生类实现,提供有关运行时错误的信息。

std::runtime_error 是用于表示运行时错误的基类,它有三个派生类,每个派生类对应于一种特定的运行时错误。这三个派生类分别是:

7.1 std::range_error

class range_error : public runtime_error {
public:
    explicit range_error(const std::string& what_arg);
    explicit range_error(const char* what_arg);
};

range_error 类表示由于超出范围导致的异常。例如,当试图使用一个超出有效范围的索引访问数组、容器或字符串时,可以抛出这个异常。

7.2 std::overflow_error

class overflow_error : public runtime_error {
public:
    explicit overflow_error(const std::string& what_arg);
    explicit overflow_error(const char* what_arg);
};

overflow_error 类表示由于溢出导致的异常。例如,当进行整数运算导致结果超出了类型的表示范围时,可以抛出这个异常。

7.3 std::underflow_error

class underflow_error : public runtime_error {
public:
    explicit underflow_error(const std::string& what_arg);
    explicit underflow_error(const char* what_arg);
};

underflow_error 类表示由于下溢导致的异常。例如,当进行浮点数运算导致结果小于类型能够表示的最小值时,可以抛出这个异常。

这些异常类提供了一个通用的、层次化的异常体系,允许程序员根据需要选择适当的异常类来表示和处理不同类型的错误。在编写自己的异常类时,通常建议继承自 std::exception 或其派生类,以保持与标准库一致的异常体系结构

异常是一种用于处理程序运行时错误的机制,它有一些优点和缺点,具体取决于应用的上下文和设计选择。

异常的优缺点

异常的优点

  1. 分离错误处理逻辑: 异常允许将错误处理逻辑与正常业务逻辑分离开来。这有助于使代码更清晰,减少错误处理代码与业务逻辑的耦合。
  2. 集中错误处理: 异常提供了一种集中处理错误的机制。在函数调用链中的任何一层发生错误时,异常可以传递到最合适的地方进行处理,而不需要在每一层都显式检查错误。
  3. 代码简洁性: 使用异常可以使代码更简洁,因为你不需要在每个函数调用后都检查返回值或错误码。
  4. 更容易维护: 异常可以提高代码的可维护性,因为错误处理的逻辑不会分散在各个函数中,而是集中在异常处理的地方。
  5. 适用于异常情况: 异常通常用于处理那些在正常情况下不太可能发生的错误,例如内存分配失败、数组越界等。

异常的缺点

  1. 性能开销: 异常处理可能引入一些性能开销,尤其是在异常被抛出和捕获的时候。如果异常被滥用,可能会影响程序的性能。
  2. 可预测性差: 异常使得程序的控制流变得不太可预测。在一些情况下,异常可能会导致代码更难理解和调试。
  3. 不适合一些应用: 在一些对性能和可预测性要求极高的应用中,例如实时系统或嵌入式系统,异常处理可能不太适用。
  4. 可能导致资源泄漏: 如果异常发生时,未能适当地释放资源,可能导致资源泄漏,例如未关闭的文件、未释放的内存等。
  5. 滥用可能导致问题: 异常应该用于处理异常情况,而不应该被滥用用作常规的控制流。滥用异常可能导致代码难以理解和维护。

总的来说,异常是一种强大的错误处理机制,但在使用时需要谨慎。在一些特定的情况下,例如对性能和可预测性要求极高的系统中,开发者可能更倾向于使用传统的错误处理机制。在其他情况下,合理使用异常可以提高代码的清晰度和可维护性。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【算法每日一练]-图论(保姆级教程 篇1(模板篇)) #floyed算法 #dijkstra算法 #spfa算法

今天开始讲图论 目录 图的存储 算任意两点的最短路径: floyed算法&#xff1a; 算一个点到其他所有点的最短距离 dijkstra算法: spfa算法&#xff1a; 图的存储 其实&#xff1a;邻接矩阵和链式向前星都能存边的信息&#xff0c;vector只能存点的信息&#xff0c;再搭配上v[]…

2023年【汽车驾驶员(中级)】考试题库及汽车驾驶员(中级)模拟考试题库

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 汽车驾驶员&#xff08;中级&#xff09;考试题库根据新汽车驾驶员&#xff08;中级&#xff09;考试大纲要求&#xff0c;安全生产模拟考试一点通将汽车驾驶员&#xff08;中级&#xff09;模拟考试试题进行汇编&…

产品运营的场景和运营策略

一、启动屏 1&#xff0e;概念 启动屏&#xff0c;特指 APP 产品启动时即显示的界面&#xff0c;这个界面一般会停留几秒钟时间&#xff0c;在这个时间内 APP 会在后台加载服务框架、启动各种服务 SDK 、获取用户地理位置、判断有无新版本、判断用户账户状态以及其他系统级别的…

QGIS之十九矢量投影

效果 步骤 1、准备数据 2、Qgis矢量投影 Qgis工具箱中搜索“投影” 3、结果

鸿蒙HarmonyOS从零实现类微信app效果第一篇,基础界面搭建

最近鸿蒙HarmonyOS开发相关的消息非常的火&#xff0c;传言华为系手机后续将不再支持原生Android应用&#xff0c;所以对于原Android应用开发对应的Harmony版本也被一系列大厂提上了日程。作为一个名义上的移动端开发工程师&#xff08;(⊙o⊙)…&#xff0c;最近写python多过A…

【计算机网络】VLAN原理和配置

目录 1、VLAN的原理 1.1、什么是VLAN 1.2、为什么要使用VLAN 1.3、VLAN的三种端口类型 1.4、VLAN的划分方法 2、VLAN的配置 1、VLAN的原理 1.1、什么是VLAN VLAN&#xff08;Virtual Local Area Network&#xff09;即虚拟局域网&#xff0c;是将一个物理的LAN在逻辑上…

十年软件测试老程序告诉你性能测试的左移右移到底能干嘛

常规的性能测试一般都是在测试阶段集成测试时候才开始介入&#xff0c;很容易测试时间不够&#xff0c;可不可以借鉴测试左移右移的思路&#xff0c;更早的介入和发现性能风险&#xff0c;然后在测试阶段更专注于分析优化&#xff1f; 借着这个问题&#xff0c;结合自己的实践…

Maven依赖管理项目构建工具的安装与配置

本篇来自尚硅谷的笔记&#xff0c;在线视频观看&#xff1a;Maven依赖管理项目构建工具&#xff0c;更多笔记欢迎访问&#xff1a;小熊学Java 一、Maven简介 1、为什么学习Maven 1.1、Maven是一个依赖管理工具 ①jar 包的规模 随着我们使用越来越多的框架&#xff0c;或者框…

linux DMA设备驱动详解

一&#xff0c;DMA相关定义&#xff08;fpga、wait_queue 、device、interrupt、 dma_request_channel 函数、dma_start_transfer函数、poll、read&#xff0c;platform总线&#xff09; DMA (直接内存读写)是Direct Memory Access的缩写&#xff0c;也就是内存到内存&#xf…

k8s自定义Endpoint实现内部pod访问外部应用

自定义endpoint实现内部pod访问外部应用 endpoint除了可以暴露pod的IP和端口还可以代理到外部的ip和端口 使用场景 公司业务还还没有完成上云&#xff0c; 一部分云原生的&#xff0c;一部分是实体的 业务上云期间逐步实现上云&#xff0c;保证各个模块之间的解耦性 比如使…

使用GPT-4训练数据微调GPT-3.5 RAG管道

原文&#xff1a;使用GPT-4训练数据微调GPT-3.5 RAG管道 - 知乎 OpenAI在2023年8月22日宣布&#xff0c;现在可以对GPT-3.5 Turbo进行微调了。也就是说&#xff0c;我们可以自定义自己的模型了。然后LlamaIndex就发布了0.8.7版本&#xff0c;集成了微调OpenAI gpt-3.5 turbo的…

敏捷开发中如何写好用户故事

写好用户故事是敏捷开发中非常重要的一环&#xff0c;它们是描述用户需求的核心。以下是一些关于如何编写优秀用户故事的建议&#xff1a; 使用标准模板&#xff1a; 一个常用的用户故事模板是“As a [用户角色]&#xff0c;I want [功能]&#xff0c;so that [价值]”。这种模…

《RT-DETR魔术师》专栏介绍 CSDN独家改进创新实战 专栏目录

RT-DETR魔术师专栏介绍&#xff1a; https://blog.csdn.net/m0_63774211/category_12497375.html ✨✨✨魔改创新RT-DETR &#x1f680;&#x1f680;&#x1f680;引入前沿顶会创新&#xff08;CVPR2023&#xff0c;ICCV2023等&#xff09;&#xff0c;助力RT-DETR &#…

【C#学习】常见控件学习

】 如何让Button控件只显示图片 第一步&#xff1a;设置按钮背景图片&#xff0c;并且图片随按钮大小变化 第二步&#xff1a;设置按钮使之只显示图片 button1.FlatStyle FlatStyle.Flat;//stylebutton1.ForeColor Color.Transparent;//前景button1.BackColor Color.Tran…

java项目之共享充电宝管理系统(ssm框架)

风定落花生&#xff0c;歌声逐流水&#xff0c;大家好我是风歌&#xff0c;混迹在java圈的辛苦码农。今天要和大家聊的是一款基于ssm的共享充电宝管理系统。项目源码以及部署相关请联系风歌&#xff0c;文末附上联系信息 。 项目简介&#xff1a; 管理员&#xff1a;首页、个…

Linux系统编程——进程的创建

函数名 fork&#xff1a;创建一个子进程 函数原型 pid_t fork(void); 调用该函数时&#xff0c;需包含以下头文件 #include <unistd.h>返回值 fork函数调用成功&#xff0c;返回两次PID &#xff08;1&#xff09;返回值为0&#xff0c;代表当前进程是子进程 &am…

thinkphp 自定义错误页面

在访问无效的UI 这个效果不好&#xff0c;要改成自定义的 <?php namespace app\controller;class ErrorController {public function __call($method,$args){return error request!;} }之后就是提示

深度学习之基于YoloV5钢材表面缺陷检测系统

欢迎大家点赞、收藏、关注、评论啦 &#xff0c;由于篇幅有限&#xff0c;只展示了部分核心代码。 文章目录 一项目简介 二、功能三、系统四. 总结 一项目简介 深度学习技术在计算机视觉领域的应用为表面缺陷检测系统的发展提供了强大的推动力。本文将介绍基于YoloV5的钢材表面…

UE5、CesiumForUnreal实现加载GeoJson绘制墙体(Wall)功能(StaticMesh方式)

文章目录 1.实现目标2.实现过程2.1 实现原理2.2 具体代码2.3 应用测试2.3.1 流动材质2.3.2 蓝图测试3.参考资料1.实现目标 与上一篇以StaticMesh方式实现面类似,本文通过读取GeoJson数据,在UE中以StaticMeshComponent的形式绘制出墙体数据,并支持Editor和Runtime,在Editor下…

2013年03月14日 Go生态洞察:走向Go 1的道路 ️

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全专栏》 &#x1f995; 文章图文…