【高级程序设计语言C++】异常与智能指针

news2024/11/19 7:50:16

  • 1. 异常
  • 2. 智能指针
    • 2.1. auto_ptr
    • 2.2. unique_ptr
    • 2.3. shared_ptr
    • 2.4. 循环引用
    • 2.5. weak_ptr
    • 2.6. 定制删除器

1. 异常

当我们编写程序时,可能会遇到各种错误和异常情况,例如除以零、访问无效的内存地址等。为了能够处理这些异常情况,C++提供了异常处理机制。

异常是程序在运行时发生的意外或错误情况。当异常发生时,程序会中断当前的执行流程,并跳转到异常处理代码块。异常处理代码块可以捕获并处理异常,从而使程序能够以一种控制的方式处理错误情况。

在C++中,我们使用try-catch语句来处理异常。try块中包含可能会抛出异常的代码,而catch块用于捕获并处理异常。当异常被抛出时,程序会跳转到最近的匹配的catch块,并执行其中的代码。

下面是一个简单的示例,演示了异常处理的基本用法:

#include <iostream>
#include <stdexcept>

int divide(int a, int b)
{
    if (b == 0)
        throw std::invalid_argument("除以零错误");

    return a / b;
}

int main()
{
    try
    {
        int result = divide(10, 0);
        std::cout << "结果: " << result << std::endl;
    }
    catch (const std::exception& e)
    {
        std::cout << "捕获到异常: " << e.what() << std::endl;
    }

    return 0;
}

在上面的代码中,divide()函数用于计算两个数的商。如果除数为0,我们会抛出一个std::invalid_argument异常。在main()函数中,我们使用try-catch块来捕获可能抛出的异常。如果异常被抛出,程序会跳转到catch块,并打印异常信息。

**这个例子中的异常处理机制使得我们可以在程序中处理各种错误情况,而不是简单地导致程序崩溃。**通过捕获和处理异常,我们可以提供更好的用户体验,并增加程序的可靠性和可维护性。

需要注意的是,异常处理应该在适当的地方进行,以确保程序能够正确处理异常并继续执行。同时,我们应该避免滥用异常,只在真正需要时才使用它们。另外,当使用异常处理时,应该谨慎处理资源的释放,以避免内存泄漏等问题。

当异常处理不在适当的地方进行时,可能会导致程序无法正确处理异常并继续执行。下面是一些反例,展示了异常处理不当的情况:

  1. 异常处理不在适当的地方进行:
#include <iostream>

void divide(int a, int b)
{
    if (b == 0)
        throw "除以零错误";

    std::cout << "结果: " << a / b << std::endl;
}

int main()
{
    try
    {
        divide(10, 0);
    }
    catch (const char* e)
    {
        std::cout << "捕获到异常: " << e << std::endl;
    }

    return 0;
}

在上面的代码中,divide()函数用于计算两个数的商。如果除数为0,我们会抛出一个字符串异常。在main()函数中,我们使用了try-catch块来捕获可能抛出的异常。然而,异常处理的位置不正确,导致程序无法正确处理异常并继续执行。在这个例子中,当异常发生时,divide()函数内部的cout语句不会执行,因此无法输出结果。

  1. 滥用异常:
#include <iostream>

int divide(int a, int b)
{
    if (b == 0)
        throw "除以零错误";

    return a / b;
}

int main()
{
    try
    {
        int result = divide(10, 2);
        std::cout << "结果: " << result << std::endl;
    }
    catch (const char* e)
    {
        std::cout << "捕获到异常: " << e << std::endl;
    }

    return 0;
}

**在上面的代码中,divide()函数用于计算两个数的商。如果除数为0,我们会抛出一个字符串异常。在main()函数中,我们使用了try-catch块来捕获可能抛出的异常。**然而,在这个例子中,我们滥用了异常,将其用作一种控制流程的手段。在实际情况下,除以零并不是一个异常情况,而是一种可以预料到的错误。因此,使用异常来处理这种情况并不合适。

  1. 资源释放问题:
#include <iostream>
#include <fstream>

void readFile()
{
    std::ifstream file("test.txt");
    if (!file)
        throw "无法打开文件";

    // 读取文件内容
    // ...

    file.close(); // 错误的资源释放方式
}

int main()
{
    try
    {
        readFile();
    }
    catch (const char* e)
    {
        std::cout << "捕获到异常: " << e << std::endl;
    }

    return 0;
}

在上面的代码中,readFile()函数用于读取文件内容。如果无法打开文件,我们会抛出一个字符串异常。然而,在这个例子中,我们没有正确处理资源的释放。当异常发生时,file对象不会被关闭,导致资源泄漏。

这些反例展示了异常处理不当的情况,**即异常处理不在适当的地方进行、滥用异常和未正确处理资源释放。**为了确保程序能够正确处理异常并继续执行,我们应该在适当的地方使用try-catch块来捕获和处理异常,并谨慎处理资源的释放,以避免内存泄漏等问题。

2. 智能指针

**当处理资源时,智能指针是一种常用的工具,它可以帮助我们管理资源的生命周期,确保资源在不再需要时被正确释放。**下面是一个简单的智能指针的实现,突出了RAII的作用和类似指针的操作:

template <typename T>
class SmartPtr
{
public:
    explicit SmartPtr(T* ptr = nullptr) : m_ptr(ptr) {}

    ~SmartPtr()
    {
        delete m_ptr;
    }

    /*SmartPtr(const SmartPtr& other)
    {
        m_ptr = new T(*other.m_ptr);
    }

    SmartPtr& operator=(const SmartPtr& other)
    {
        if (this != &other)
        {
            delete m_ptr;
            m_ptr = new T(*other.m_ptr);
        }
        return *this;
    }*/

    T& operator*() const
    {
        return *m_ptr;
    }

    T* operator->() const
    {
        return m_ptr;
    }

private:
    T* m_ptr;
};

上述代码定义了一个SmartPtr类,它使用模板来适应不同类型的资源。该类具有以下特点:

  1. 构造函数接受一个指针作为参数,初始化智能指针。当不再需要该指针时,智能指针会自动释放资源。
  2. 析构函数负责释放资源,确保资源在对象销毁时被正确释放。
  3. 重载了*和->操作符,使得智能指针的使用更类似于原始指针。

以下是一个示例,展示了如何使用SmartPtr类来管理动态分配的整数资源:

#include <iostream>

int main()
{
    SmartPtr<int> ptr(new int(42));

    std::cout << *ptr << std::endl;  // 输出: 42
    *ptr = 24;
    std::cout << *ptr << std::endl;  // 输出: 24

    return 0;
}

在上述示例中,我们创建了一个SmartPtr对象,并通过构造函数将一个动态分配的整数资源传递给它。我们可以通过*操作符解引用智能指针,并通过->操作符访问底层资源的成员。

**使用智能指针可以确保资源在不再需要时被正确释放,从而避免内存泄漏等问题。**希望这个简单的智能指针实现能够帮助你更好地理解智能指针的作用和RAII的概念。

以下是一个使用自己模拟写的智能指针SmartPtr的示例代码,展示了在异常使用不规范的场景下智能指针的作用:

#include <iostream>

template <typename T>
class SmartPtr
{
public:
    SmartPtr(T* ptr = nullptr) : m_ptr(ptr) {}

    ~SmartPtr()
    {
        delete m_ptr;
    }

    T& operator*()
    {
        return *m_ptr;
    }

    T* operator->()
    {
        return m_ptr;
    }

private:
    T* m_ptr;
};

class Resource
{
public:
    Resource()
    {
        std::cout << "Resource acquired." << std::endl;
    }

    ~Resource()
    {
        std::cout << "Resource released." << std::endl;
    }

    void DoSomething()
    {
        std::cout << "Doing something with the resource." << std::endl;
    }
};

void SomeFunction()
{
    SmartPtr<Resource> ptr(new Resource());

    // 在执行某些操作时可能发生异常
    // 这里模拟一个异常的情况
    throw std::runtime_error("An error occurred.");

    ptr->DoSomething();  // 潜在的资源泄漏
}

int main()
{
    try
    {
        SomeFunction();
    }
    catch (const std::exception& e)
    {
        std::cout << "Exception caught: " << e.what() << std::endl;
    }

    return 0;
}

在上述示例中,我们使用自己模拟写的智能指针SmartPtr来管理资源Resource。在SomeFunction函数中,我们通过SmartPtr创建了一个Resource对象,并在执行某些操作时可能发生异常。

img

当异常被捕获并跳转到catch块时,异常的处理会终止当前的函数执行,并继续执行异常处理代码。在这种情况下,即使SomeFunction函数没有完全执行完毕,但由于main函数的结束,整个程序即将退出,因此SomeFunction函数会被提前结束。

当函数结束时,局部变量(包括SmartPtr对象)的生命周期也会结束,它们的析构函数会被自动调用,从而释放资源。因此,即使在SomeFunction函数中发生了异常,SmartPtr对象的析构函数仍然会被调用,确保资源的释放。

2.1. auto_ptr

auto_ptr是C++98标准中提供的一个智能指针,用于管理动态分配的对象。它的特点是在拷贝构造和赋值操作时,会转移指针的所有权,因此只能有一个auto_ptr拥有该指针。在C++11标准中,auto_ptr已被弃用,推荐使用更安全和功能更丰富的unique_ptr、shared_ptr和weak_ptr。

下面是一个简单的模拟实现auto_ptr的示例代码:

template <typename T>
class AutoPtr
{
public:
    explicit AutoPtr(T* ptr = nullptr) : m_ptr(ptr) {}

    ~AutoPtr()
    {
        delete m_ptr;
    }

    AutoPtr(AutoPtr& other)
    {
        m_ptr = other.m_ptr;
        other.m_ptr = nullptr;
    }

    AutoPtr& operator=(AutoPtr& other)
    {
        if (this != &other)
        {
            delete m_ptr;
            m_ptr = other.m_ptr;
            other.m_ptr = nullptr;
        }
        return *this;
    }

    T& operator*()
    {
        return *m_ptr;
    }

    T* operator->()
    {
        return m_ptr;
    }

private:
    T* m_ptr;
};

在上述代码中,AutoPtr类模拟了auto_ptr的功能。它具有一个指向动态分配对象的指针m_ptr,在析构函数中释放资源。

拷贝构造函数和赋值操作符重载实现了指针所有权的转移。在拷贝构造函数中,将传入的AutoPtr对象的指针转移给当前对象,并将传入对象的指针设为nullptr,确保只有一个AutoPtr对象拥有该指针。赋值操作符重载也实现了相同的逻辑,并在赋值前释放当前对象的资源。

通过重载operator*和operator->,我们可以像使用指针一样使用AutoPtr对象,访问所管理的动态分配对象的成员。

需要注意的是,这只是一个简单的模拟实现,可能存在一些问题,例如在多线程环境下的安全性问题。在实际开发中,应该使用C++11标准中提供的更安全和功能更丰富的智能指针,如unique_ptr、shared_ptr和weak_ptr。

void TestAutoPtr()
{
    AutoPtr<int> ap1(new int(223));
    AutoPtr<int> ap2(ap1);

    (*ap2)++;
    (*ap1)++;

    cout << *ap2 << endl;
    cout << *ap1 << endl;
}

运行结果:

img

代码的运行是不正常的,下面通过调试的角度来看看为什么会这样?

img

img

img

2.2. unique_ptr

std::unique_ptr 是 C++ 标准库中的一个智能指针类模板,用于管理动态分配对象的所有权。它提供了独占式所有权,确保在任何时候只有一个 std::unique_ptr 对象可以拥有所管理的对象。

std::unique_ptr 的主要特点如下:

  1. 独占式所有权:std::unique_ptr 禁止拷贝构造和拷贝赋值操作,因此每个 std::unique_ptr 对象只能拥有一个动态分配对象的所有权。这样可以避免多个指针同时管理同一个对象,从而减少内存泄漏和资源竞争的风险。
  2. 自动释放:当 std::unique_ptr 对象超出作用域时,它会自动调用析构函数来释放所管理的对象。这意味着你不需要手动释放内存,从而避免了忘记释放或者多次释放的问题。
  3. 可以指定删除器:你可以使用自定义的删除器来指定在释放所管理的对象时使用的方式。删除器可以是函数指针、函数对象或者 Lambda 表达式,用于在释放对象之前执行一些额外的操作,比如释放资源、关闭文件等。
  4. 支持数组:除了管理单个对象,std::unique_ptr 也可以管理动态分配的数组。你可以使用 std::unique_ptr<T[]> 来创建一个 std::unique_ptr 对象,其中 T 是数组元素的类型。

使用 std::unique_ptr 的示例代码如下:

#include <memory>

int main() {
    // 创建一个 std::unique_ptr 对象,管理一个动态分配的整数
    std::unique_ptr<int> ptr(new int(42));

    // 使用 * 运算符解引用 std::unique_ptr 对象,获取所管理的对象的值
    int value = *ptr;
    std::cout << value << std::endl;

    // 使用 -> 运算符通过 std::unique_ptr 对象访问所管理对象的成员
    ptr->someMemberFunction();

    // 创建一个 std::unique_ptr 对象,管理动态分配的整型数组
    std::unique_ptr<int[]> arr(new int[5]);

    // 使用 [] 运算符访问 std::unique_ptr 对象所管理的数组元素
    arr[0] = 10;
    std::cout << arr[0] << std::endl;

    // std::unique_ptr 对象会在离开作用域时自动释放所管理的对象
    return 0;
}

总的来说,std::unique_ptr 是 C++ 标准库中一个非常有用的智能指针类模板,它提供了简单且安全的管理动态分配对象的方式,可以减少内存泄漏和资源竞争的风险。

下面是模拟实现的一个unique_ptr

template <typename T>
class UniquePtr
{
public:
    explicit UniquePtr(T* ptr = nullptr) : m_ptr(ptr) {}

    ~UniquePtr()
    {
        delete m_ptr;
    }

    UniquePtr(const UniquePtr& other) = delete;

    UniquePtr& operator=(UniquePtr& other) = delete;

    T& operator*()
    {
        return *m_ptr;
    }

    T* operator->()
    {
        return m_ptr;
    }

private:
    T* m_ptr;
};

在拷贝构造和赋值运算符重载加了delete关键字,禁止拷贝。

2.3. shared_ptr

auto_ptr是转移之后就不管了,然后置空导致会有问题。unique_ptr是禁止拷贝,只允许有一份。那如果我想要一个智能指针,它的功能是支持多个智能指针共享同一个对象的所有权。C++标准库里是有这样的一个智能指针的,叫做shared_ptr。下面就简单模拟写一个shared_ptr来认识一下库里shared_ptr的原理。

template <typename T>
class SharedPtr {
public:
    // 构造函数
    SharedPtr(T* ptr = nullptr) : m_ptr(ptr), m_refCount(new int(1)) {}

    // 拷贝构造函数
    SharedPtr(const SharedPtr& other) : m_ptr(other.m_ptr), m_refCount(other.m_refCount) {
        (*m_refCount)++;
    }

    // 析构函数
    ~SharedPtr() {
        (*m_refCount)--;
        if (*m_refCount == 0) {
            delete m_ptr;
            delete m_refCount;
        }
    }

    // 重载赋值运算符
    SharedPtr& operator=(const SharedPtr& other) {
        if (this != &other) {
            (*m_refCount)--;
            if (*m_refCount == 0) {
                delete m_ptr;
                delete m_refCount;
            }
            m_ptr = other.m_ptr;
            m_refCount = other.m_refCount;
            (*m_refCount)++;
        }
        return *this;
    }

    // 解引用运算符
    T& operator*() const {
        return *m_ptr;
    }

    // 成员访问运算符
    T* operator->() const {
        return m_ptr;
    }

private:
    T* m_ptr;         // 指向堆上对象的指针
    int* m_refCount;  // 对象的引用计数
};

所谓的多个智能指针共享一个对象,如图所示:

img

每当创建一个新的 SharedPtr 对象时,引用计数都会加一。当 SharedPtr 对象被销毁时,引用计数减一。当引用计数为零时,表示没有 SharedPtr 对象再引用该对象,可以安全地释放对象的内存。

imgimg

从调试的角度看变化:

imgimg

imgimg

可以看到,虽然有三个智能智能,但都是管同一块资源的,并且利用引用计数避免多次释放和内存泄漏的问题。

需要注意的是

标准库的shared_ptr所管理的资源并不是线程安全的,需要保证自行加锁。

2.4. 循环引用

来看这样一段代码

class B;  // 前向声明

class A
{
public:
    SharedPtr<B> ptrB;
};

class B
{
public:
    SharedPtr<A> ptrA;
};

int main()
{
    SharedPtr<A> a(new A());
    SharedPtr<B> b(new B());

    a->ptrB = b;
    b->ptrA = a;

    return 0;
}

上面的代码会引发循环引用,什么是循环引用呢?看图

img

img

2.5. weak_ptr

weak_ptr是C++11引入的智能指针,用于解决循环引用问题。它是shared_ptr的一个辅助类,允许我们在不增加引用计数的情况下观察和访问由shared_ptr管理的对象。

与shared_ptr不同,weak_ptr并不拥有对象的所有权,它只是对由shared_ptr管理的对象的一个弱引用。因此,weak_ptr不会增加对象的引用计数,也不会阻止对象的销毁。当对象被销毁后,weak_ptr会自动失效。

weak_ptr的主要作用是用来解决循环引用问题。循环引用指的是两个或多个对象之间相互持有对方的shared_ptr,导致它们的引用计数永远不会减为零,从而导致内存泄漏。通过使用weak_ptr,我们可以打破循环引用,避免内存泄漏。

下面是一个简单的weak_ptr

template<class T>
class weak_ptr
{
public:
    weak_ptr()
        :_ptr(nullptr)
    {}

    weak_ptr(const shared_ptr<T>& sp)
        :_ptr(sp.get())
    {}

    weak_ptr<T>& operator=(const shared_ptr<T>& sp)
    {
        _ptr = sp.get(); //获取sp的成员
        return *this;
    }

    // 像指针一样
    T& operator*()
    {
        return *_ptr;
    }

    T* operator->()
    {
        return _ptr;
    }
public:
    T* _ptr;
};

上面的代码实现的是一个简单的weak_ptr,跟标准库里的有很大的不同,有兴趣的同学可以自行去阅读C++官网的文档。

使用weakptr后,修改的代码如下:

class B;  // 前向声明

class A
{
public:
    WeakPtr<B> ptrB;
};

class B
{
public:
    WeakPtr<A> ptrA;
};

void Test()
{
    SharedPtr<A> a(new A());
    SharedPtr<B> b(new B());

    a->ptrB = b;
    b->ptrA = a;
}

这里就不会出现循环引用的场景了,如图所示:

img

img

weak_ptr其实扮演的角色就是帮shared_ptr擦屁股。

2.6. 定制删除器

定制删除器(Custom Deleter)是用于在智能指针释放资源时执行特定操作的函数或函数对象。它可以用来扩展智能指针的功能,以满足特定的需求。

我们可以用一个玩具小车的例子来解释定制删除器的概念。

假设我们有一个玩具小车,它有一个电池作为能源。当我们不再使用这个小车时,我们需要将电池取出并且关闭开关,以节省电池的能量。在这个例子中,玩具小车就是我们要管理的资源,电池就是这个资源的一部分,关闭开关就是我们要执行的特定操作。

在C++中,我们可以使用shared_ptr来管理玩具小车这个资源。我们可以通过定制删除器,在shared_ptr释放资源时执行关闭开关的操作。

下面是一个示例代码,演示了如何使用定制删除器关闭玩具小车的开关:

#include <iostream>
#include <memory>

class ToyCar {
public:
    ToyCar() {
        std::cout << "ToyCar constructed" << std::endl;
    }
    
    ~ToyCar() {
        std::cout << "ToyCar destructed" << std::endl;
    }
    
    void turnOn() {
        std::cout << "ToyCar turned on" << std::endl;
    }
    
    void turnOff() {
        std::cout << "ToyCar turned off" << std::endl;
    }
};

void closeSwitch(ToyCar* car) {
    car->turnOff();
}

int main() {
    std::shared_ptr<ToyCar> car(new ToyCar(), closeSwitch);
    
    car->turnOn();
    
    return 0;
}

在这个例子中,我们定义了一个ToyCar类,表示玩具小车。在ToyCar类中,我们有turnOn和turnOff两个成员函数,分别用于打开和关闭小车的开关。

我们使用shared_ptr来管理玩具小车这个资源,并通过定制删除器closeSwitch来执行关闭开关的操作。closeSwitch是一个普通函数,它接受一个ToyCar*参数,并在函数体中调用turnOff函数来关闭开关。

在main函数中,我们创建了一个shared_ptr对象car,它管理着一个ToyCar实例,并使用closeSwitch作为删除器。当car的引用计数为0时(即没有其他指针指向这个资源),删除器会被调用,关闭小车的开关。

通过定制删除器,我们可以在智能指针释放资源时执行特定的操作,从而扩展智能指针的功能。这在处理一些特殊资源时非常有用,比如文件、数据库连接等。

当使用智能指针管理资源时,可以通过定制删除器来指定在资源释放时执行的特定操作。定制删除器可以是函数、函数对象或者lambda表达式。

定制删除器可以通过两种方式指定:

  1. 通过函数指针或函数对象:可以将一个函数指针或者函数对象作为第二个参数传递给智能指针的构造函数。这个函数或者函数对象将在智能指针释放资源时被调用。
void myDeleter(ToyCar* car) {
    // 执行特定操作
}

std::shared_ptr<ToyCar> car(new ToyCar(), myDeleter);
  1. 通过lambda表达式:可以使用lambda表达式作为删除器。lambda表达式是一种匿名函数,可以直接在智能指针的构造函数中定义,并在其中编写特定操作的代码。
std::shared_ptr<ToyCar> car(new ToyCar(), [](ToyCar* car) {
    // 执行特定操作
});

在lambda表达式中,我们可以使用捕获列表来捕获外部变量,以便在删除器中使用。

下面是使用lambda表达式作为删除器的完整示例代码:

#include <iostream>
#include <memory>

class ToyCar {
public:
    ToyCar() {
        std::cout << "ToyCar constructed" << std::endl;
    }
    
    ~ToyCar() {
        std::cout << "ToyCar destructed" << std::endl;
    }
    
    void turnOn() {
        std::cout << "ToyCar turned on" << std::endl;
    }
    
    void turnOff() {
        std::cout << "ToyCar turned off" << std::endl;
    }
};

int main() {
    std::shared_ptr<ToyCar> car(new ToyCar(), [](ToyCar* car) {
        car->turnOff();
    });
    
    car->turnOn();
    
    return 0;
}

在这个例子中,我们使用lambda表达式作为删除器,关闭玩具小车的开关。lambda表达式接受一个ToyCar*参数,并在函数体中调用turnOff函数来关闭开关。

通过定制删除器,我们可以在智能指针释放资源时执行特定的操作,无论是使用函数指针、函数对象还是lambda表达式,都能够实现这个功能。

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

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

相关文章

(笔记六)利用opencv进行图像滤波

&#xff08;1&#xff09;自定义卷积核图像滤波 import numpy as np import matplotlib.pyplot as plt import cv2 as cvimg_path r"D:\data\test6-6.png" img cv.imread(img_path)# 图像滤波 ker np.ones((6, 6), np.float32)/36 # 构建滤波器&#xff08;卷积…

Three.js后处理后物体表面出现条纹

初始化 WebGLRenderer 时简单启用 logarithmicDepthBuffer: true 解决了问题。 根据文档&#xff0c;启用可能会导致性能下降&#xff0c;因此请根据您的性能预算考虑使用它。 缩小相机的near和far 后处理对于深度精度非常敏感。大视锥体很快就会使此类 AO 通道变得无法使用 th…

小兔鲜儿 - 微信登录

目录 微信登录​ 登录方式 静态结构​ 获取登录凭证​ 获取手机号码​ 微信登录接口(生产环境) 模拟手机登录(开发环境) 用户信息持久化存储​ 涉及知识点&#xff1a;微信授权登录&#xff0c;文件上传&#xff0c;Store 状态管理等。 微信登录​ 微信小程序的开放…

SpringBoot虚拟路径映射

要求&#xff1a;访问&#xff1a;127.0.0.1/image/下的文件时&#xff0c;自动映射到真实路径&#xff1a;D:Files\。 virtualFileDepositPath: /image/** realityFileDepositPath: C:\Users\xin\Desktop\imgCreate\Files\ import org.springframework.beans.factory.annota…

整理的10个更好用的画图软件,设计绘图必看!

随着设计工作的不断发展&#xff0c;画图也成了设计师日常的设计工作之一&#xff0c;今天本文整理了10个好用的画图软件&#xff0c;能满足设计师的多种画图需求&#xff0c;一起来看看吧&#xff01; 1、即时设计 即时设计做为一个功能多样的画图软件&#xff0c;不仅可以满…

DPDW01+ 国产锂电池保护电路

描述&#xff1a; DPDW01是一个锂电池保护电路&#xff0c;为避免锂电池因过充电、过放电、电流过大导致电池寿命缩短或电池被损坏而设计的。它具有高精确度的电压检测与时间延迟电路。 主要特点&#xff1a; 工作电流低&#xff1b; 过充检测 4.3V&#xff0c;过充释放 4.0…

Shell - 根据PID过滤进程信息

文章目录 #!/bin/bash #Function: 根据用户输入的PID&#xff0c;过滤出该PID所有的信息 read -p "请输入要查询的PID: " P nps -aux| awk $2~/^$P$/{print $11}|wc -l if [ $n -eq 0 ];thenecho "该PID不存在&#xff01;&#xff01;"exit fi echo "…

TikTok成旺季“主战场”,大促节点一览无遗

随着海外短视频的兴起&#xff0c;拥有10亿月活用户的TikTok已经成为旺季大促的主要营销渠道。从TikTok上的话题标签&#xff0c;可以看出用户对节日及大促节点的关注度&#xff0c;借势飙升的话题流量是大促营销的重要方式之一。数据显示&#xff0c;2023上半年的斋月期间&…

【Java 动态数据统计图】动态X轴二级数据统计图思路案例(动态,排序,动态数组(重点推荐:难))八(130)

需求&#xff1a; 1.有一组数据集合&#xff0c;数据集合中的数据为动态&#xff1b; 举例如下&#xff1a; [{province陕西省, city西安市}, {province陕西省, city咸阳市}, {province陕西省, city宝鸡市}, {province陕西省, city延安市}, {province陕西省, city汉中市}, {pr…

飞腾PSPA可信启动--2 数字签名证书

今天继续第二章&#xff0c;数字签名证书的介绍。 此章节录制了讲解视频&#xff0c;可以在B站进行观看&#xff1a;

PMP - 敏捷 3355

三个核心 产品负责人 负责最大化投资回报&#xff08;ROI&#xff09;&#xff0c;通过确定产品特性&#xff0c;把他们翻译成一个有优先级的列表 为下一个 sprint 决定在这个列表中哪些应该优先级最高&#xff0c;并且不断调整优先级以及调整这个列表 职责是定义需求、定义…

探索软件架构与网络通信协议:构建现代网络应用

文章目录 1. 软件架构&#xff1a;构建应用的基石1.1 C/S架构&#xff1a;客户端/服务器1.2 B/S架构&#xff1a;浏览器/服务器 2. 网络通信协议&#xff1a;构建连接的桥梁2.1 4层模型&#xff1a;数据的分层传输2.2 IP地址与端口号&#xff1a;标识和定位 3. TCP通信&#xf…

Java的数组是啥?

1.数组是啥&#xff1f; 数组是一块连续的内存&#xff0c;用来存储相同类型的数据 &#xff08;1&#xff09;如何定义数组&#xff1f; 1.int[] array {1,2,3,4} new int[]{1,2,3,4};//这里的new是一个关键字&#xff0c;用来创建对象 2.数组就是一个对象 动态初始化 …

[论文笔记]DSSM

引言 这是DSSM论文的阅读笔记,后续会有一篇文章来复现它并在中文数据集上验证效果。 本文的标题翻译过来就是利用点击数据学习网页搜索中深层结构化语义模型,这篇论文被归类为信息检索,但也可以用来做文本匹配。 这是一篇经典的工作,在DSSM之前,通常使用传统机器学习的…

【附安装包】Eplan2022安装教程

软件下载 软件&#xff1a;Eplan版本&#xff1a;2022语言&#xff1a;简体中文大小&#xff1a;1.52G安装环境&#xff1a;Win11/Win10/Win8/Win7硬件要求&#xff1a;CPU2.5GHz 内存4G(或更高&#xff09;下载通道①百度网盘丨64位下载链接&#xff1a;https://pan.baidu.co…

市场中做到低买高卖,其实很简单,FPmarkets澳福10秒教会

所有人都在告诉你&#xff0c;低买高卖就可以在市场大杀四方&#xff0c;但是没有人告诉你&#xff0c;如何在风云莫测的市场中做到低买高卖。其实很简单&#xff0c;FPmarkets澳福10秒钟教会&#xff0c;如果不好使&#xff0c;帮你账户充值1000块钱。 投资者都知道外汇报价在…

Metinfo6.0.0任意文件读取【漏洞复现】

文章目录 1.1、漏洞描述1.2、漏洞等级1.3、影响版本1.4、漏洞复现代码审计漏洞点 1.5、深度利用EXP编写 1.6、漏洞挖掘1.7修复建议 1.1、漏洞描述 漏洞名称&#xff1a;MetInfo任意文件读取 漏洞简介&#xff1a;MetInfo是一套使用PHP和MySQL开发的内容管理系统&#xff0c;其…

C++11 智能指针详解

C 程序设计中使用堆内存是非常频繁的操作&#xff0c;堆内存的申请和释放都由程序员自己管理。程序员自己 管理堆内存可以提高了程序的效率&#xff0c;但是整体来说堆内存的管理是麻烦的&#xff0c;C11 中引入了智能指针的 概念&#xff0c;方便管理堆内存。使用普通指针&…

耕地单目标语义分割实践——Deeplab3+语义分割

耕地单目标语义分割实践系列文章&#xff1a; [1*] 语义分割实践数据集制作—以Sentinel-2 MSI数据为例_doll &#xff5e;CJ的博客-CSDN博客 [2*] 耕地单目标语义分割实践——Pytorch网络过程实现理解_doll &#xff5e;CJ的博客-CSDN博客 [3*] 基于Pytorch的神经网络部分…

RocketMQ-(8-1)-EventBridge-EventBridge 核心概念

RocketMQ EventBridge 核心概念 理解EventBridge中的核心概念&#xff0c;能帮助我们更好的分析和使用EventBridge。本文重点介绍下EventBridge中包含的术语&#xff1a; EventSource&#xff1a;事件源。用于管理发送到EventBridge的事件&#xff0c;所有发送到EventBridge中…