首页 > 其他分享 >智能指针怎么就智能了?

智能指针怎么就智能了?

时间:2024-09-10 12:53:30浏览次数:3  
标签:怎么 std 对象 内存 智能 引用 shared ptr 指针

在C++中,内存泄漏是一个不太容易发现但又有一定影响的问题,而且在引入了异常的机制之后,代码的走向就更加不可控,更容易发生内存泄露。【补充:内存泄露(Memory Leak)指的是在程序运行期间,动态分配的内存没有被释放或无法被回收,从而导致这些内存块一直被占用而无法再被使用的情况。】

比如这段代码:

int div()
{
	int a, b;
	cin >> a >> b;
	if (b == 0)
		throw invalid_argument("除0错误");
	return a / b;
}
void func()
{
	int* ptr = new int;
	cout << div() << endl;
	delete ptr;
}
int main()
{
	try
	{
		func();
	}
	catch (exception& e)
	{
		cout << e.what() << endl;
	}
	return 0;
}

div函数抛出异常(例如,b == 0时),func函数中的ptr指针(通过new分配的内存)将不会被释放,因为delete ptr;这一行在异常抛出后根本不会被执行。因此,ptr所指向的内存块将被遗留在内存中,导致内存泄露。

为了避免这种情况,一个常见的做法是使用 RAII(Resource Acquisition Is Initialization)原则来管理资源,这就引入了智能指针(如std::unique_ptr)来自动管理内存,或者确保在异常发生时总是能够释放已分配的内存。RAII是一种利用对象生命周期来控制程序资源(如内存、文件句柄、互斥量等等)的简单技术,即在对象构造时获取资源,在对象析构的时候释放资源。

智能指针的原理分析:通过对象来管理获取的资源,保证在对象结束生命周期时,可以自动调用析构函数避免获取的资源没有被释放。

template<class T>
class SmartPtr
{
public:
	SmartPtr(T* ptr)
		:_ptr(ptr)
	{}
	~SmartPtr()
	{
		cout << "delete: " << _ptr << endl;
		delete _ptr;
	}

private:
	T* _ptr;
};
int div()
{
	int a, b;
	cin >> a >> b;
	if (b == 0)
		throw invalid_argument("除0错误");
	return a / b;
}
void func()
{
	SmartPtr<int> sp(new int);
	cout << div() << endl;
}
int main()
{
	try
	{
		func();
	}
	catch (exception& e)
	{
		cout << e.what() << endl;
	}
	return 0;
}

1. auto_ptr

[不推荐使用]
std::auto_ptr有许多设计缺陷,并且已经在C++11中被弃用,在C++17中彻底移除。

问题

  1. 不安全的所有权转移:
    std::auto_ptr在赋值或拷贝时会转移所有权,即把内存的所有权从一个auto_ptr对象转移到另一个auto_ptr对象。这种行为很容易引起程序错误,特别是在函数参数传递和容器使用时。

    例如:

    std::auto_ptr<int> p1(new int(5));
    std::auto_ptr<int> p2 = p1; // p1的所有权被转移给p2
    // p1变为空悬指针,可能导致未定义行为
    

    这种所有权的自动转移可能在编程中引入难以发现的bug,尤其是在复杂代码中。

  2. 不支持标准容器:
    因为std::auto_ptr的所有权转移行为,不能将其放入STL容器中,如std::vectorstd::list等。标准容器要求其元素能够被复制,而std::auto_ptr的复制语义是移动语义,这违反了标准容器的要求。

  3. 不符合现代C++的智能指针语义:
    std::auto_ptr的语义与现代C++的资源管理思想(RAII和明确的所有权管理)不匹配。std::auto_ptr的行为不够直观,容易造成混淆和错误。

2. unique_ptr

std::unique_ptr被引入来解决std::auto_ptr的问题,并成为现代C++的首选智能指针。

优势

  1. 明确的唯一所有权:
    std::unique_ptr明确表示它拥有的对象具有唯一的所有权,因此不会有意外的所有权转移问题。在任何需要转移所有权的情况下,都必须显式地使用std::move,这样更清晰、直观,避免了不必要的错误。
    在这里插入图片描述

    例如:

    std::unique_ptr<int> p1(new int(5));
    // std::unique_ptr<int> p2(p1); // error 
    // p1所有权被转移给p2,p1被显式地std::move
    std::unique_ptr<int> p2 = std::move(p1); 
    
  2. 支持标准容器:
    std::unique_ptr的设计符合标准容器的要求,可以安全地用于容器中,提供更好的内存管理和性能。

  3. 更好的性能:
    std::unique_ptr不需要在复制时进行所有权的转移检查,因此在性能上更优。

  4. 现代C++标准支持:
    std::unique_ptr是C++11标准引入的现代智能指针类型,是当前及未来C++代码的首选。它的设计符合现代C++语言的最佳实践。

这里是使用std::unique_ptr对原先的代码进行改进:

#include <iostream>
#include <stdexcept>
#include <memory>
using namespace std;

int div()
{
    int a, b;
    cin >> a >> b;
    if (b == 0)
        throw invalid_argument("除0错误");
    return a / b;
}

void func()
{
    // 使用智能指针来管理内存
    unique_ptr<int> ptr = make_unique<int>();
    cout << div() << endl;
    // 不需要手动delete,智能指针会在超出作用域时自动释放内存
}

int main()
{
    try
    {
        func();
    }
    catch (exception& e)
    {
        cout << e.what() << endl;
    }
    return 0;
}

这样,即使在div函数中抛出异常,也不会发生内存泄露,因为std::unique_ptr会在func函数结束时自动释放内存。

总而言之

由于std::auto_ptr的所有权管理方式不直观且容易出错,在现代C++开发中,std::unique_ptr完全替代了std::auto_ptrstd::unique_ptr提供了更好的内存管理、更安全的所有权转移语义和更高效的性能,是管理动态资源时的最佳选择。

3. shared_ptr

std::shared_ptr 是 C++11 引入的另一种智能指针,与 std::unique_ptr 不同,它允许多个指针共享同一个对象的所有权。这使得 std::shared_ptr 特别适用于需要在多个地方使用相同对象并且对象的生命周期需要由多个使用者共同管理的场景。

主要特点和用途

  1. 共享所有权:
    std::shared_ptr 可以被多个指针共享。每个 shared_ptr 都维护一个指向相同对象的指针,并使用一个引用计数(reference count)来跟踪当前有多少个指针指向同一对象。当一个 shared_ptr 被拷贝或赋值时,引用计数增加;当一个 shared_ptr 被销毁或重置时,引用计数减少。当引用计数降为零时,表示没有指针再指向这个对象,这时对象才会被释放。

    例如:

    std::shared_ptr<int> p1 = std::make_shared<int>(10); // 创建一个共享指针
    std::shared_ptr<int> p2 = p1; // p1 和 p2 共享同一个对象
    
  2. 自动管理对象生命周期:
    std::shared_ptr 可以有效管理动态分配的对象生命周期,无需程序员手动释放内存。当最后一个 shared_ptr 指针超出作用域或被重置时,所管理的对象会自动释放。

  3. 循环引用检测:
    尽管 std::shared_ptr 能管理对象的生命周期,但它无法处理循环引用的问题(两个对象相互引用对方)。如果两个 std::shared_ptr 对象形成了循环引用(即 A 持有 B,B 持有 A),则它们的引用计数永远不会为零,从而导致内存泄露。

    为了解决这个问题,可以搭配 std::weak_ptr 使用(part4)。

使用场景

  1. 在多个地方共享对象:
    当你希望多个对象或函数共享同一个对象,并且需要自动管理该对象的生命周期时,std::shared_ptr 非常有用。例如,一个对象在多处被使用,而且这些使用者希望共享这个对象的所有权。

  2. 工厂函数或资源管理:
    当一个函数创建一个对象并将其返回给多个调用者时,std::shared_ptr 可以保证对象在不再被任何使用者使用时自动释放。例如,在工厂函数中返回一个对象的指针,多个调用者可以共享它:

    std::shared_ptr<MyClass> createObject() {
        return std::make_shared<MyClass>();
    }
    
  3. 多线程环境下的资源共享:
    std::shared_ptr 可以安全地在多线程环境中共享对象,因为其引用计数是线程安全的。在多线程环境中,如果多个线程需要访问共享资源,可以使用 std::shared_ptr 管理该资源。

unique_ptr 的对比

  • std::unique_ptr:用于表示唯一所有权,即对象只能由一个指针拥有,当指针超出作用域时,内存会自动释放。它通常用于不需要共享所有权的情况,并且具有更好的性能(因为它没有引用计数的开销)。

  • std::shared_ptr:用于表示共享所有权,即多个指针可以共同拥有一个对象。适用于需要多个对象或函数共享同一资源的场景,但它有引用计数的开销,因此在性能上稍逊于 std::unique_ptr

示例:

#include <iostream>
#include <memory>

class Example {
public:
    Example() { std::cout << "Example Constructor\n"; }
    ~Example() { std::cout << "Example Destructor\n"; }
    void sayHello() const { std::cout << "Hello, Shared Pointer!\n"; }
};

int main() {
    std::shared_ptr<Example> ptr1 = std::make_shared<Example>(); // 引用计数为 1
    {
        std::shared_ptr<Example> ptr2 = ptr1; // 引用计数为 2
        ptr2->sayHello();
    } // ptr2 超出作用域,引用计数减为 1

    ptr1->sayHello();
    // 当 main 结束时,ptr1 超出作用域,引用计数为 0,Example 对象被销毁
    return 0;
}

在上面的代码中,Example 对象在 ptr1ptr2 之间共享。当 ptr2 超出其作用域时,ptr1 仍然指向 Example 对象。当 ptr1 也超出作用域后,Example 对象才被销毁。

std::shared_ptr 提供了共享对象所有权的机制,适用于需要多个实体共享同一对象并自动管理对象生命周期的场景。尽管它有一定的性能开销(由于引用计数),但在需要共享资源的复杂系统中,它是非常有用且必不可少的工具。

引用计数问题

std::shared_ptr 将引用计数(reference count)放在堆上,而不是使用静态成员变量,这是为了正确管理对象的生命周期并确保其线程安全性和多样性使用场景。以下是详细的原因:

a. 支持多个实例指向不同对象

如果引用计数是静态成员变量,那么所有的 std::shared_ptr 对象都将共享同一个引用计数。这意味着无论多少个 shared_ptr 指向多少个不同的对象,它们都会共享相同的计数,这显然是不合理的。

例如:

std::shared_ptr<int> p1 = std::make_shared<int>(10);
std::shared_ptr<int> p2 = std::make_shared<int>(20);

如果引用计数是静态的,那么p1p2将共享同一个引用计数,这样无法区分两个不同的对象(1020)的生命周期。每个shared_ptr应该独立维护其指向的对象的引用计数。

b. 确保线程安全

std::shared_ptr是线程安全的,其引用计数是原子操作。将引用计数放在堆上(一个独立的内存块中)可以保证多个shared_ptr实例在不同线程中操作同一个对象时能够安全地增加或减少计数。

如果引用计数是静态成员变量,那么对引用计数的所有操作都会涉及同一个变量,会导致多个线程同时访问和修改同一个计数器,带来竞争条件(race condition)问题,需要额外的同步机制,这样会导致效率下降,并增加复杂性。

c. 避免内存泄漏和未定义行为

使用堆内存而不是静态成员变量有助于防止内存泄漏和未定义行为。当引用计数归零时,控制块及其管理的对象将被正确销毁。反之,如果引用计数是静态成员变量,那么当程序结束时,它可能仍然有残留的非零计数,导致资源未被释放。

d. 符合对象多态性和继承设计

堆上的控制块可以与对象一起动态分配并关联对象的多态行为(如虚析构函数调用等)。这种设计方式非常适合管理复杂对象的生命周期,例如基于继承的对象结构。使用静态成员变量的设计将无法灵活应对多态性和继承的场景,因为静态变量不依赖于具体对象实例。

总结

将引用计数放在堆上,使 std::shared_ptr 能够灵活管理不同对象的生命周期,支持多种使用场景,包括多线程环境下的安全性,动态对象管理,防止内存泄露等。这些特点使得 std::shared_ptr 成为一个强大且安全的智能指针类型,广泛应用于现代C++编程。

3.1 shared_ptr的定制删除器

std::shared_ptr 的定制删除器(Custom Deleter)允许开发者在智能指针销毁其所管理的对象时,自定义如何释放资源。这对于需要自定义资源管理的情况非常有用,例如管理动态分配的内存、文件句柄、网络连接、数据库连接等。

为什么使用定制删除器

默认情况下,std::shared_ptr 使用 delete 运算符来销毁它管理的对象。这对于大多数简单的动态分配对象(如通过 new 分配的对象)是足够的。但是,有时候我们需要更复杂的资源管理,比如:

  1. 与C库的兼容性:某些资源(如用 malloc 分配的内存)需要使用 free 而不是 delete 来释放。
  2. 管理非内存资源:如文件句柄、套接字、数据库连接等,需要特定的函数来释放资源。
  3. 调试或日志记录:在删除对象时记录日志或执行其他调试操作。
  4. 池化或缓存管理:将对象返回到对象池而不是直接删除。

如何使用 shared_ptr 的定制删除器

std::shared_ptr 的构造函数可以接受一个删除器(通常是一个函数指针、函数对象或 lambda 表达式)。当 std::shared_ptr 不再需要管理的对象时,它会调用这个删除器来释放资源。

示例:使用 lambda 表达式作为删除器

以下是一个使用 lambda 表达式作为定制删除器的示例:

#include <iostream>
#include <memory>

int main() {
    // 使用 malloc 分配内存
    int* p = (int*)std::malloc(sizeof(int));
    if (p == nullptr) {
        throw std::bad_alloc();
    }

    // 使用自定义删除器创建 shared_ptr
    std::shared_ptr<int> ptr(p, [](int* p) {
        std::cout << "Using custom deleter to free memory.\n";
        std::free(p); // 使用 free 释放内存
    });

    *ptr = 42; // 使用 shared_ptr 访问数据
    std::cout << "Value: " << *ptr << std::endl;

    // 当 ptr 超出作用域时,删除器将被调用,释放内存
    return 0;
}

在这个例子中,std::shared_ptr 使用 malloc 分配的内存,并提供了一个定制删除器(lambda 表达式),在内存释放时调用 free。这样确保了资源被正确释放。

示例:管理文件句柄

你可以使用 std::shared_ptr 来管理一个打开的文件句柄,并提供一个自定义删除器来关闭文件:

#include <iostream>
#include <memory>
#include <cstdio> // 使用 C 标准库的文件操作函数

int main() {
    // 打开一个文件
    std::shared_ptr<FILE> file(fopen("example.txt", "w"), [](FILE* fp) {
        if (fp) {
            std::cout << "Closing file.\n";
            fclose(fp); // 关闭文件
        }
    });

    if (!file) {
        std::cerr << "Failed to open file.\n";
        return 1;
    }

    // 使用文件句柄
    fprintf(file.get(), "Hello, world!\n");

    // 当 file 超出作用域时,文件将被自动关闭
    return 0;
}

在这个示例中,std::shared_ptr 管理一个 FILE* 指针(文件句柄),并在文件指针超出作用域时自动调用 fclose 关闭文件。

定制删除器的使用细节

  1. 定制删除器的存储std::shared_ptr 内部会存储删除器,因此这个删除器本身也会占用一定的内存空间。通常,删除器是一个小的函数对象(如 lambda 表达式),其开销可以忽略不计,但对于复杂的删除器对象,可能会增加一些内存使用。

  2. 删除器的类型:删除器的类型是 std::shared_ptr 类型的一部分。因此,不同的删除器会导致不同类型的 std::shared_ptr。例如:

    std::shared_ptr<int> p1(new int(10), [](int* p) { delete p; });
    std::shared_ptr<int> p2(new int(20), std::default_delete<int>());
    

    p1p2 的类型虽然都是 std::shared_ptr<int>,但它们的删除器类型不同,因此无法相互赋值。

  3. 使用 std::function 存储删除器:在需要将具有不同删除器的 std::shared_ptr 存储在一起时,可以使用 std::function

    std::vector<std::shared_ptr<void>> resources;
    resources.push_back(std::shared_ptr<void>(new int(10), [](void* p) { delete static_cast<int*>(p); }));
    resources.push_back(std::shared_ptr<void>(fopen("example.txt", "w"), [](void* fp) { fclose(static_cast<FILE*>(fp)); }));
    
  • 定制删除器允许在 std::shared_ptr 管理的对象销毁时执行自定义的资源释放逻辑,这对非内存资源的管理非常有用。
  • 可以使用函数指针、函数对象或 lambda 表达式作为定制删除器。
  • 定制删除器的使用确保了资源的正确释放,避免了内存泄漏和资源泄漏。
  • 定制删除器的灵活性使 std::shared_ptr 成为管理各种资源的理想选择。

4. weak_ptr

std::weak_ptr 是 C++11 引入的另一种智能指针,用来解决 std::shared_ptr 引用计数可能导致的循环引用问题。它提供了一种弱引用的方式来引用一个对象,而不影响该对象的引用计数。

主要作用

  1. 解决循环引用问题:
    当两个或多个对象相互引用对方的 std::shared_ptr 时,会形成循环引用。这种情况下,即使这些对象之间没有其他引用,它们的引用计数也永远不会变为零,导致内存泄漏。std::weak_ptr 可以打破这种循环引用的情况,因为它不会增加引用计数。

  2. 提供一种安全的方式检查对象是否存在:
    std::weak_ptr 可以检查一个对象是否已经被销毁而不尝试访问该对象。使用 std::weak_ptr 时,我们可以用它来创建一个 std::shared_ptr,只有在对象仍然存在的情况下,这样就可以安全地使用这个对象。

工作原理

  • std::weak_ptr 是一个指向由 std::shared_ptr 管理的对象的弱引用。它不会增加共享对象的引用计数(use_count),因此不会影响对象的生命周期。
  • 当你需要访问由 std::weak_ptr 引用的对象时,你需要将它转换为一个 std::shared_ptr。如果对象仍然存在(use_count > 0),这个操作将成功;否则,转换将产生一个空的 std::shared_ptr

使用场景

  1. 避免循环引用:

    在对象之间存在相互依赖的情况下,std::weak_ptr 可以打破循环引用。例如,在实现一个树状或图状数据结构时,父节点和子节点可以互相引用:

    #include <iostream>
    #include <memory>
    
    class Node {
    public:
        std::shared_ptr<Node> parent;
        std::vector<std::shared_ptr<Node>> children;
    
        Node() { std::cout << "Node created\n"; }
        ~Node() { std::cout << "Node destroyed\n"; }
    };
    
    int main() {
        std::shared_ptr<Node> parent = std::make_shared<Node>();
        std::shared_ptr<Node> child = std::make_shared<Node>();
    
        parent->children.push_back(child);
        child->parent = parent; // 循环引用
    
        // `parent`和`child`的引用计数相互增加,导致内存泄漏
        return 0;
    }
    

    在上面的例子中,parentchild相互引用,形成一个循环引用。当程序结束时,parentchild的引用计数都不会变为零,因此它们都不会被销毁,导致内存泄漏。

    为了解决这个问题,可以使用std::weak_ptr将父节点的引用变为弱引用:

    #include <iostream>
    #include <memory>
    #include <vector>
    
    class Node {
    public:
        std::weak_ptr<Node> parent; // 父节点使用弱引用
        std::vector<std::shared_ptr<Node>> children;
    
        Node() { std::cout << "Node created\n"; }
        ~Node() { std::cout << "Node destroyed\n"; }
    };
    
    int main() {
        std::shared_ptr<Node> parent = std::make_shared<Node>();
        std::shared_ptr<Node> child = std::make_shared<Node>();
    
        parent->children.push_back(child);
        child->parent = parent; // 弱引用,不增加引用计数
    
        // 正常情况下,`parent`和`child`的引用计数将达到零,内存会被释放
        return 0;
    }
    

    使用 std::weak_ptr 后,父节点的引用不再增加子节点的引用计数,这样当没有其他 std::shared_ptr 指向子节点时,子节点将会被销毁,从而避免了循环引用导致的内存泄漏。

  2. 临时访问共享对象:

    在某些情况下,你只需要短时间访问一个共享对象,而不希望延长它的生命周期。使用 std::weak_ptr 可以实现这种临时访问,避免不必要的引用计数增加。

如何使用

以下是 std::weak_ptr 的基本用法示例,大家可以自行跑一下:

#include <iostream>
#include <memory>

int main() {
    // 创建一个 std::shared_ptr
    std::shared_ptr<int> sp = std::make_shared<int>(42);

    // 创建一个 std::weak_ptr,指向同一个对象
    std::weak_ptr<int> wp = sp;

    // 检查对象是否仍然存在
    if (auto locked = wp.lock()) { // 使用 lock() 获得 std::shared_ptr
        std::cout << "对象仍然存在,值为: " << *locked << "\n";
    } else {
        std::cout << "对象已销毁\n";
    }

    sp.reset(); // 手动释放 shared_ptr,引用计数变为0,对象被销毁

    // 再次检查对象是否存在
    if (auto locked = wp.lock()) {
        std::cout << "对象仍然存在,值为: " << *locked << "\n";
    } else {
        std::cout << "对象已销毁\n";
    }

    return 0;
}

std::weak_ptr 通过提供一种不影响引用计数的弱引用方式,解决了 std::shared_ptr 循环引用导致的内存泄漏问题,并且允许安全地检查对象是否仍然存在。它在缓存、观察者模式和避免延长对象生命周期的场景中非常有用。

如果你能看到这里,给你点个赞,如果对你有帮助的话不妨点赞支持一下~

标签:怎么,std,对象,内存,智能,引用,shared,ptr,指针
From: https://blog.csdn.net/m0_73969414/article/details/141863969

相关文章

  • 0909人工智能教育技术学
    第一节课学会使用FILETYPE\SITE和INTITLE指令:(1)Filetype指令可以查询特定格式的文件,比如doc\txt\ppt\pdf,搜索格式为:关键词+空格+filetype:+文件格式,比如:初等数论filetype:doc,搜索结果均为与初等数论有关的doc文档。小学班级管理filetype:doc,搜索结果均为与初等数论有关的doc文档......
  • 记一次Java自动拆箱引发的空指针问题
    系统服务上线后,看代码运行正常,报警也已经提示,但是后台日志中仍记录打印出了NullPointerException代码段落如下,方法返回参数若为boolean时会造成空指针错误 原因分析如下1、412行报错,公共方法捕获了异常,发送消息报警通知2、异常处理完成后,会returnnull3、此时就会报错,方......
  • “谷歌大脑之父”吴恩达公布《AI转型指南》:引领企业步入人工智能时代
    前言吴恩达曾说过,人工智能(AI)必将像电力一样改变各行各业,企业越早开启转型,就越享受红利。但问题是,不是每家公司都能请到合适的高管人才,也不是谁都有帮助企业实现AI转型的经验。吴恩达在Medium专栏上po出一封公开信,正式公开免费地发布AITransformationPlaybook,中文译作《人工智能......
  • 中国智能网联汽车消费行为调查数据
    智能网联汽车是指利用车载传感器、控制器、执行器、通信装置等,实现环境感知、智能决策和/或自动控制、协同控制、信息交互等功能的汽车的总称。近年来,国务院、工信部、公安部、交通运输部等多部门都陆续印发了规范、引导、规划智能网联汽车行业的发展政策,内容涉及智能......
  • Python怎么发送邮件:基础步骤与详细教程?
    Python怎么发送邮件带附件?怎么使用Python发送邮件?无论是工作中的通知、报告,还是生活中的问候、邀请,电子邮件都扮演着不可或缺的角色。那么,Python怎么发送邮件呢?AokSend将详细介绍Python发送邮件的基础步骤。Python怎么发送邮件:必要的库常用的库包括smtplib和email。smtplib......
  • 华为OD机试真题-工号不够用了怎么办-2024年OD统一考试(E卷)
    题目描述3020年,空间通信集团的员工人数突破20亿人,即将遇到现有工号不够用的窘境,现在,请你负责调研新工号系统。继承历史传统,新的工号系统由小写英文字母Q(a-z)和数字(0-9)两部分构成。新工号由一段英文字母开头,之后跟随一段数字,比如"aaahw0001"“a12345","abcd1"”a00"。注意......
  • 09django基于Python的智能热门旅游景点数据分析可视化系统的设计与实现
    前言......
  • DrugAgent:多智能体系统,新药研发速度提升10倍
    DrugAgent:多智能体系统,新药研发速度提升10倍提出背景逻辑拆解全流程分析创意本文下一步论文:DRUGAGENT:EXPLAINABLEDRUGREPURPOSINGAGENTWITHLARGELANGUAGEMODEL-BASEDREASONING代码:https://anonymous.4open.science/r/DrugRepurposeLLM-E0B5/提出背景这篇论文提出的背景......
  • 10分钟让微信公众号成为智能客服
    只需10分钟即可将您的微信公众号(订阅号)变成AI智能客服,以便全天候(7x24)回应客户咨询,提升用户体验、增强业务竞争力。方案概览将微信公众号(订阅号)变成AI智能客服,只需4步:创建大模型问答应用:我们将先通过百炼创建一个大模型应用,并获取调用大模型应用API的相关凭证。......
  • 2024年Ai智能绘画Stable Diffusion软件+整合包+保姆式教程
    前言在2024年的科技浪潮中,一款名为StableDiffusion的AI智能绘画软件吸引了全球的目光。它不仅为艺术家和设计师提供了无限创意的可能,也让我们每个人都能轻松体验绘画的乐趣。那么,StableDiffusion究竟有何魅力?它又是如何工作的呢?让我们一起揭开这款神奇软件的神秘面纱!一......