【C++11】右值引用的深度解析(什么是右值引用?它有什么作用?能应用在那些场景?)

news2024/11/26 10:40:43

目录

一、前言

二 、什么是左值什么是右值?

🔥左值🔥

🔥右值 🔥

 三、什么是右值引用?  

💧左右引用的“引出”💧

💧左值引用 💧

💧右值引用 💧

💢右值引用的理解 

💢std::move() 

💧左值引用 vs 右值引用  💧

四、右值引用的应用场景 

🥝 移动语义

🍇 完美转发

五、右值引用有什么作用? 

六、共勉 


一、前言

        在C++的世界中,每一个临时对象的诞生都伴随着资源的消耗与转移。但你是否曾想过,这些临时对象的生命周期可以更加高效,它们的资源可以无缝地转移到需要它们的地方?这就是右值引用的魅力所在。

       右值引用,一个在C++11中闪耀登场的革命性特性,它不仅仅是一个语言的扩展,更是对资源管理哲学的一次深刻反思。它允许我们以一种前所未有的方式,对临时对象进行资源的“移动”,而不是简单的“复制”。
        下文,我们将一起揭开右值引用的神秘面纱,探索它如何改变我们对C++编程的认知,以及如何利用这一特性,编写出更加高效、优雅的代码。

二 、什么是左值什么是右值?

理解左值(Lvalue)右值(Rvalue)是掌握C++中引用和内存管理的基础。让我们从概念上和实际代码中来解析它们的区别。 

  • 这张图形化地展示了左值(Lvalue)右值(Rvalue)在C++中的区别。左值被表示为一个稳定的盒子,指向一个内存地址,表明它是持久的,有一个明确的存储位置。而右值则被表示为一个临时的云状形状,代表它是一个临时值,没有持久的内存地址。 

🔥左值🔥

左值:最初指的是可出现在赋值语句左边的实体,引入了const关键字之后,常规变量和 const 变量都可视为左值, 因为可通过地址访问它们,但常规变量属于可修改的左值, 而 const 变量属于不可修改的左值。

  • 左值的本质:是可以操作的一块内存区域,一般可以通过&取到该内存起始地址,这块内存的值可以被修改(const对象除外)。除了const对象的其他左值都可以出现在赋值语句左边。 

常见的左值有:

1、变量(对象);
2、const 变量(对象)
3、对指针解引用,*(指针)
4、数组元素,a[1]
5、结构体成员、类成员,s.m_aps->ma; 

int main()
{
	int i = 0;			
	i = 1;				// i是变量(对象),左值
	
	const int ci = 5;	
	//ci = 6;错误			// ci是const变量(对象),是左值但不能出现在赋值号左边
	
	int *pi = &i;
	*pi = 1;			// *pi 对指针解引用,左值

	int arr[5] = {0};
	arr[0] = 1;			// arr[0] 是数组元素,左值

	struct {
		int m_a;
		int m_b;
	} st, *pst=&st;
	st.m_a = 1;			// 结构体成员,左值
	pst->m_b = 2;

	return 0;
}

🔥右值 🔥

右值:一般是没有明确的内存位置无法使用&获取地址,值不可被修改。例如:立即数、常量、字面值、&(变量)。

  • 右值本质:指一种表达式,其结果是值而非值所在的位置。一般是没有明确的内存位置,无法使用&获取地址,值不可被修改的。例如:立即数、常量、字面值、&(变量)。另外,很多时候左值可以当右值使用。
  • 关于右值,有一个重要的原则,是在需要右值的地方 (除了移动构造函数) 可以用左值来代替,但是不能把右值当成左值(也就是位置)使用。当一个左值被当成右值使用时,实际使用的是它的内容(值)。

常见的右值有:

  1. 字面常量(立即数),引号括起的字符串除外(它们由其地址表示),42、'a'
  2. 算术运算符(+、-、*、/、%、正号、负号)的求值结果,1+2
  3. 逻辑运算符(&&、||、!)的求值结果,a!=10
  4. 关系运算符(>、>=、<、<=、==、!=)的求值结果,a>10 && a<100
  5. 函数的非引用返回值。这种返回值位于临时内存单元中,运行到下一条语句时,它们可能不再存在;
  6. 当条件运算符的两个表达式都是左值或者能转换成同一种左值类型时,运算的结果是左值;否则运算的结果是右值。
int get100()
{
	return 100;
}

int main()
{
	int i = 0, c=0;			
	i = 42;				// 字面值42,右值
	c = 'a';			// 字面值'a',右值
	
	i = 1+2;			// 算术运算符的求值结果(1+2),右值
	
	i = (c!='a');		// 逻辑运算符的求值结果 (c!='a'),右值
	
	i = (c >= 'a'); 	// 关系运算符的求值结果(c >= 'a'),右值
	
	i = get100();		// 函数的非引用返回值get100(),右值
	
	i>0?i:c = i>0?1:0;	// 条件运算符的两个表达式都是左值,则可以做左值,本例的 i>0?i:c
						// 条件运算符的两个表达式都是右值,则为右值,本例的 i>0?1:0;
						
	i = c;				// c是左值,左值的值可以当右值使用

	return 0;
}

 三、什么是右值引用?  

在学习 右值引用 之前,需要先来看看 左值引用(其实就是我们之前的 ----- 引用)引用 是 C++ 相对于 C语言 的升级点之一,引用 既能像指针那样获取资源的地址,直接对资源进行操纵,也不必担心多重 引用 问题,对于绝大多数场景来说,引用 比 指针 好用得多 

💧左右引用的“引出”💧

想象一下,你正在搬家,而你有一辆车用来运送你的物品。为了方便起见,我们可以将“搬家”看作是一次资源的转移过程。

左值引用的情况

  • 如果你想把你的家具从旧家搬到新家,通常你会自己一件件地把家具搬到车上,然后开车把它们带到新家。这有点像“复制”家具,因为你在原地“复制”了这些家具,然后把它们运到了新家。
  • 在编程中,这种情况就像是传统的左值引用,你只是通过复制(深拷贝)的方式,将资源从一个对象传递给另一个对象,这可能会导致性能开销增大。

右值引用的情况 

  • 现在,假设你有一位朋友,他刚好也在搬家,而且他的新家离你的新家很近。他不想要他的旧家具了,而你正好需要这些家具。那么,他可以直接把这些家具“交给”你,而不需要你再自己搬运或复制。
  • 这就类似于右值引用。在这个过程中,你的朋友并没有复制家具,而是直接将他的资源(家具)“转移”给了你。你得到了家具,而他不再拥有这些家具。这种“资源转移”非常高效,避免了不必要的复制操作。

💧左值引用 💧

        左值引用是一个很好用的工具,它有像指针指向一块空间的能力,同时它不像指针那样危险、复杂,换句话说,引用是指针的改进版,在C++的学习中,有80%的场景都会用到引用而非指针,由于之前已经详细的讲过指针啦,这里就不再深究,只是帮助大家简单的回忆一下,如果对引用不了解,可以去看这篇文章: 引用详解

  • 怎样声明、定义左值引用:左值引用的定义需要使用&,格式一般是类型 &变量名 = 变量。左值引用定义必须初始化,且初始化后无法改变该引用关联的对象。下面例子介绍了左值引用的定义、const左值引用初始化为左值、const左值引用初始化为右值。
int a = 10;
int* pa = &a;  // 指针
int& r = a;   // 引用
  •  引用的底层依然是 ---- 指针

  • 我们之前使用的所有引用都称为 左值引用,主要用于引用各种 变量,如果想引用 常量,需要使用 const 修饰
int get100()
{
	return 100;
}
int main()
{
	int i = 0;
	int &ri = i;		// 左值引用
//	int &*pri = &ri;	// 不能定义引用的指针。报错:cannot declare pointer to ‘int&’
//	int &&rri = ri;		// 不能定义引用的引用。在C++11标准里,这是一个右值引用
	
	int *pi = &i;
	int *&rpi = pi;		// pi是左值,rpi是指针的引用
	int &r_pi = *pi;	// *pi是左值,可以初始化左值引用
	
	int arr[5] = {0};
	int &rarr = arr[0];	// arr[0]是左值,可以初始化左值引用
	
	// const 左值引用可以被初始化为一些左值
	const int &cri = i;		// const 左值引用
//	const int *&crpi = pi;	// 报错:用 ‘int*’ 初始化 ‘const int*&’ 无效
	const int &cr_pi = *pi;	// const 左值引用
	const int &crarr= arr[0];
	
	// const 左值引用可以被初始化为右值
	const int &ri0 = 42;
	const int &ri1 = 'a';
	const int &ri2 = 1+2;
	const int &ri3 = (ri1 != 'a');
	const int &ri4 = (ri1 >= 'a');
	const int &ri5 = get100();		// 函数的非引用返回值,右值
	const int &ri6 = i>0?1:0;	
//	ri0 = 1;	// 报错,const引用是只读的,其值不能修改
	return 0;
}

 另外,应尽可能将引用形参声明为 const ,有以下三个理由:

  • 使用 const 可以避免无意中修改数据的编程错误;
  • 使用 const 使函数能够处理 const 和非 const 实参, 否则将只能接受非 const 数据;
  • 使用 const 引用使函数能够正确生成并使用临时变量。

💧右值引用 💧

💢右值引用的理解 

在 C++11 中,新增了 右值引用 的概念,就是将 左值引用 中的 & 变为 &&右值引用 可以直接引用 左值引用 中需要加 const 引用的值;也可以通过函数 move 引用 左值引用 直接引用的值

  • 怎样定义右值引用?右值引用使用&&来定义,也是必须初始化,初始化后无法改变其绑定的对象。定义右值引用格式:类型 &&引用名称 = 右值;。定义一个右值引用可以参考下面代码: 
int &&rl = 13;  // 13是一个临时变量,进行右值引用
int *pi = &rl;
  • 将 【右值】 关联到【右值引用】导致该【右值】被存储到特定的位置,且可以获取该位置的地址。也就是说,虽然不能将运算符&用于 13,但可将其用于 rl。通过将数据与特定的地址关联,使得可以通过右值引用来访问该数据。 

右值引用只能绑定到一个右值,右值要么是字面常量,要么是在表达式求值过程中创建的临时对象; 而临时对象有两个特点:

  • 该对象将要被销毁;
  • 该对象没有其他用户再使用它。

这就意味着,右值引用的代码是最后使用这个对象的了,可以自由地接管所引用的对象的资源。 

如上图,变量a、b相加之后会产生一个值这个值就是临时量,但它在内存中肯定是存在某个地址的,没有右值引用之前,这个值使用完就会被销毁,我们也不会知道它的内存地址。

  • 现在,这块内存可以被右值引用关联,关联后,右值引用甚至可以改变内存的内容,等右值引用使用完再销毁。 

 看看下面关于右值引用的例子,加深理解:

#include <iostream>
using namespace std;
int get100()
{
	return 100;
}

void fun(int &&rri)// 右值引用作为函数形参
{
	rri = 0;
}

int main()
{
	// 1、右值引用必须被初始化为右值
	int &&ri0 = 42;		// 将 42 存到一个临时量,然后引用这个临时量
	int &&ri1 = 'a';	// 将 'a' 存到一个临时量,然后引用这个临时量
	int &&ri2 = 1+2;	// 将 1+2 存到一个临时量,然后引用这个临时量
	int &&ri3 = (ri1 != 'a');
	int &&ri4 = (ri1 >= 'a');
	int &&ri5 = get100();	// 函数的非引用返回值,右值
	int &&ri6 = ri0>0?1:0;
	
	// 2、右值引用的内容可以被修改
	ri0 = 1;
	cout << "ri0=" << ri0 << endl;  // 输出:ri0 = 1
	
	// 3、虽然没办法获取右值的地址,但可以获取右值引用的地址,并改变该地址的值
	int *pi = &ri0;
	*pi = 2;
	cout << "pi=" << pi << ", *pi=" << *pi << ", ri0=" << ri0 << endl;

	// 4、传入右值
	fun(1+2);
	return 0;
}

注意: 

  1. 左值引用 可以通过其他手段引用 右值,比如加 const右值引用 也可以通过其他手段引用 左值,比如 move 函数
  2. 赋值语句左边的一定是 左值,但右边的不一定是 右值,比如 int a = b

💢std::move() 

无论是 左值引用 还是 右值引用,本质上都是在给 资源 起别名,当 左值引用 引用 左值 时,是直接指向 资源,从而对 左值 进行操作;当 右值引用 引用 右值 时,则是先将 常量 等即将被销毁的临时资源 “转移” 到特定位置,然后指向该位置中的 资源,对 右值 进行操作 

int a = 10;

// 左值引用 引用 左值
int& ra = a;

// 右值引用 引用 右值
int&& rr = 10;

虽然不能将左值绑定在一个右值应用上, 但我们可以显式地将一个左值转换为对应的右值引用类型。通过调用 std::move 的标准库函数可以获取绑定到左值上的右值引用,此函数定义在 utility 头文件中。

  • 对于 「常量 / 临时对象 / 表达式结果」 等 右值,编译器会直接转移资源,但对于用户自定义的 左值,编译器不敢轻举妄动,只敢给用户提供一个 转移变量资源 的函数 move,有了 move 之后,右值引用 就能引用 左值 了 
int a = 10;

// 左值引用 引用 右值
const int& r = 10;

// 右值引用 引用 左值
int&& rr = move(a);

  •  语法还支持给 右值引用 加 const,这样做的含义是 不能修改右值引用后的值
int main() 
{
	int a = 10;

	const int&& crr = 10;
	const int&& crra = move(a);

	++crr; // 【报错】
	++crra; // 【报错】

	return 0;
}
  • 一般情况下是不会这样干的,右值引用 是为了移走资源,加了 const 还不如直接改用 const 左值引用 

不要轻易使用 move 函数左值 中的资源可能会被转走,在 C++11 之后,几乎所有的 STL 容器都增加了一个 移动构造 函数,其中就用到了 右值引用 

  • 如果此时我们直接将 左值 move 后构造一个新对象,会导致原本左值中的 资源 丢失 
// move 转移资源
int main()
{
	string str = "Hello World!";
	cout << "str: " << str << endl;

	// 使用 move 函数后
	string tmp = move(str);
	cout << "str: " << str << endl;

	return 0;
}

  •  所以一般情况下不要轻易使用 move 移动函数,除非你确定该资源后续不再使用

💧左值引用 vs 右值引用  💧

在 C++11 之前,使用 const 左值引用 也可以引用 右值,并且在我们之前的学习中只使用 左值引用 也没什么大问题啊,那为什么还要搞出一个 右值引用 呢?

  •  答案是 右值引用可以提高资源的利用率,进而提高整体效率
  • 有了右值引用之后,之前只能 【读取】、【拷贝】的临时资源变得更有价值了,可以在右值引用后进行操作,也可以将资源转移以减少拷贝 

 下面是 左值引用 与 右值引用 的对比图

特征左值引用右值引用
语法Type& lvalueRef = variable;Type&& rvalueRef = std::move(variable);
绑定对象现有对象临时对象或可移动对象
典型对象函数参数、返回类型移动语义、完美转发
示例int x = 10; int& ref = x;int&& rref = 10;
可重新赋值
可为nullptr是(需谨慎使用)
可以折叠(C++11)Type&& && 折叠为 Type&&
生命周期延长否(延长临时对象的生命周期)是(绑定到临时对象,如果绑定到右值则延长生命周期)

四、右值引用的应用场景 

右值引用的主要作用:

  1. 移动语义:允许临时对象的资源(如内存、文件句柄等)被“移动”到另一个对象中,而不是进行拷贝。这可以避免不必要的资源复制,提高程序效率。

  2. 完美转发:在模板编程中,能够将函数的参数以左值或右值的形式完美转发给另一个函数。

🥝 移动语义

允许临时对象的资源(如内存、文件句柄等)被“移动”到另一个对象中,而不是进行拷贝。这可以避免不必要的资源复制,提高程序效率。

场景描述

我们有一个简单的类MyString,它管理一个字符串的资源(动态分配的字符数组)。我们将演示在没有右值引用的情况下,如何进行复制,以及如何通过右值引用来优化这个过程。

#include <iostream>
#include <cstring>

class MyString {
public:
    char* data;

    // 构造函数
    MyString(const char* str) 
    {
        data = new char[strlen(str) + 1];
        strcpy(data, str);
        std::cout << "构造函数调用,分配资源:" << data << std::endl;
    }

    // 拷贝构造函数(深拷贝)
    MyString(const MyString& other) 
    {
        data = new char[strlen(other.data) + 1];
        strcpy(data, other.data);
        std::cout << "拷贝构造函数调用,复制资源:" << data << std::endl;
    }

    // 析构函数
    ~MyString() 
    {
        std::cout << "析构函数调用,释放资源:" << data << std::endl;
        delete[] data;
    }
};

int main() 
{
    MyString str1("Hello");
    MyString str2 = str1;  // 触发拷贝构造函数
    return 0;
}

解释:

  • 构造函数:分配内存并复制传入的字符串。
  • 拷贝构造函数:当我们复制一个MyString对象时,这个函数会被调用,它会分配新内存并复制字符串。
  • 析构函数:释放动态分配的内存。

输出结果:

在这个例子中,当我们复制str1str2时,触发了拷贝构造函数,执行了一次深拷贝,这意味着我们复制了整个字符串,占用了额外的内存,并且进行了不必要的复制操作。


使用右值引用(转移语义)的情况 

#include <iostream>
#include <cstring>

class MyString {
public:
    char* data;

    // 构造函数
    MyString(const char* str) 
    {
        data = new char[strlen(str) + 1];
        strcpy(data, str);
        std::cout << "构造函数调用,分配资源:" << data << std::endl;
    }

    // 拷贝构造函数(深拷贝)
    MyString(const MyString& other) 
    {
        data = new char[strlen(other.data) + 1];
        strcpy(data, other.data);
        std::cout << "拷贝构造函数调用,复制资源:" << data << std::endl;
    }

    // 移动构造函数(右值引用)
    MyString(MyString&& other) noexcept 
    {
        data = other.data;  // 直接“偷取”资源
        other.data = nullptr;  // 清空源对象的指针
        std::cout << "移动构造函数调用,转移资源:" << data << std::endl;
    }

    // 析构函数
    ~MyString() 
    {
        if (data) 
        {
            std::cout << "析构函数调用,释放资源:" << data << std::endl;
            delete[] data;
        } 
        else 
        {
            std::cout << "析构函数调用,资源已被转移,无需释放" << std::endl;
        }
    }
};

int main() 
{
    MyString str1("Hello");
    MyString str2 = std::move(str1);  // 触发移动构造函数
    return 0;
}

解释:

  • 移动构造函数:当我们使用std::movestr1的资源“转移”给str2时,这个函数会被调用。移动构造函数不会复制字符串,而是直接将指针“偷取”过来,避免了内存分配和复制的开销。原来的对象str1的指针被置为nullptr,表示资源已经被转移。

输出结果: 


对比说明

  • 不使用右值引用:在复制MyString对象时,触发了拷贝构造函数,进行了深拷贝,占用了额外的内存,并增加了不必要的复制操作。
  • 使用右值引用:在资源转移时,移动构造函数直接“偷取”资源,避免了深拷贝,提高了效率,同时也避免了内存资源的重复释放。
  •  这个例子展示了右值引用如何通过转移语义避免不必要的资源复制,从而优化代码的性能。对于初学者,记住:右值引用的核心思想就是“转移”而不是“复制”。

🍇 完美转发

 “完美转发”是指在C++中能够将参数原封不动地传递给另一个函数,不管它是左值还是右值。通过使用右值引用std::forward,我们可以实现这一点。下面我会用一个简单的代码例子来说明完美转发,并展示如果不用右值引用会发生什么情况。

#include <iostream>
#include <utility> // 包含 std::forward

// 一个简单的函数,接受左值引用
void process(int& x) {
    std::cout << "左值引用函数调用, 值: " << x << std::endl;
}

// 一个简单的函数,接受右值引用
void process(int&& x) {
    std::cout << "右值引用函数调用, 值: " << x << std::endl;
}

// 一个接受任意参数的模板函数,并完美转发给 process 函数
template <typename T>
void forwardToProcess(T&& arg) {
    process(std::forward<T>(arg)); // 完美转发
}

int main() {
    int a = 5;

    // 调用时传入左值
    forwardToProcess(a);

    // 调用时传入右值
    forwardToProcess(10);

    return 0;
}

 代码解释

  • process(int& x):这是一个接收左值引用的函数。如果传入的是一个左值(如变量a),这个函数会被调用。

  • process(int&& x):这是一个接收右值引用的函数。如果传入的是一个右值(如字面量10),这个函数会被调用。

  • forwardToProcess(T&& arg):这是一个模板函数,使用了T&&,它可以接受任何类型的参数(无论是左值还是右值)。这个函数使用std::forward<T>(arg)来将参数转发给process函数,从而实现完美转发。

    • 如果arg是一个左值,std::forward<T>(arg)会把它当作左值传递。
    • 如果arg是一个右值,std::forward<T>(arg)会把它当作右值传递。

输出结果:

  • 假设我们不使用std::forward而是直接调用process(arg),如下所示: 
template <typename T>
void forwardToProcess(T&& arg) {
    process(arg); // 不使用 std::forward
}
  • 那么无论传入的是左值还是右值,arg都会被当作左值处理,因为argforwardToProcess函数内部是一个左值。这就意味着无论传入的是a还是10,都会调用process(int& x)函数。 

 总结

  • 完美转发:通过使用std::forward,可以将参数的原始类型(左值或右值)完美地传递给另一个函数。这在泛型编程中非常重要,因为它避免了不必要的拷贝或不正确的引用。
  • 没有使用std::forward:所有传入的参数都会被当作左值处理,即使传入的是右值,也会失去右值的特性,导致潜在的性能损失或不正确的行为。

五、右值引用有什么作用? 

【右值引用】是C++11引入的一个新特性用于实现移动语义和完美转发,其作用主要包括以下几点: 

  • 实现移动语义:右值引用可以绑定到临时对象(右值),通过将资源的所有权从一个对象转移到另一个对象,避免了不必要的复制和销毁操作,提高程序效率。移动语义在大规模数据结构中尤为重要,例如std::vector、std::string等。
  • 支持完美转发:右值引用还可以用于函数模板的完美转发,即将参数以原始的形式传递给下一个函数,避免了不必要的复制和类型转换,提高了程序效率。
  • 避免内存泄漏:右值引用可以使用std::move()函数将对象强制转换为右值,使得该对象的所有权可以被移交,从而避免了内存泄漏的问题。

总之,右值引用是C++中一项非常重要的特性,通过实现移动语义、完美转发等功能,能够提高程序效率、避免内存泄漏,并在标准库中得到了广泛的应用。 

六、共勉 

以下就是我对 【C++11】右值引用 的理解,如果有不懂和发现问题的小伙伴,请在评论区说出来哦,同时我还会继续更新对 【C++11】 的理解,请持续关注我哦!!!    

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

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

相关文章

C# 不一样的洗牌算法---Simd指令

洗牌算法&#xff0c;以随机打乱数组中元素的位置 测试数据创建 int[] _data; Random rng new Random(); protected override void CreateData() {_data new int[_size];for (int i 0; i < _data.Length; i){_data[i] i;} } 普通打乱数组元素位置 protected overrid…

VBA学习(27):在筛选数据中复制可见单元格

在筛选数据中复制数据时&#xff0c;可以按原结构粘贴所复制的数据。具体如下文&#xff1a; 下图所示为示例数据。 我们对列C进行筛选&#xff0c;如下图所示 复制单元格区域B2:B10&#xff0c;然后粘贴到以单元格E2开始的区域&#xff0c;结果如下图所示。正如所见&#xff…

LLM之RLHF:Karpathy视角来对比RLHF技术和RL技术—RLHF is just barely RL(RLHF只是勉强算作强化学习)

LLM之RLHF&#xff1a;Karpathy视角来对比RLHF技术和RL技术—RLHF is just barely RL(RLHF只是勉强算作强化学习) 导读&#xff1a;2024年8月8日&#xff0c;Karpathy发表了一个有意思的观点&#xff0c;RLHF is just barely RL. 强化学习从人类反馈(RLHF)训练方法只是勉强属于…

怎么将音乐转为mp3格式?7种简单的手机音频转换方法!

怎么将音乐转为mp3格式&#xff1f; mp3是一种运用比较广泛的格式&#xff0c;几乎所有的音频设备、播放器和操作系统都可以播放MP3音乐。如果你的音频文件不是mp3格式&#xff0c;那么可以通过专业的mp3格式转换器进行转换。这样就不会出现格式不兼容&#xff0c;无法正常使用…

第四范式发布AI+5G视频营销产品 助力精准获客与高效转化

产品上新 Product Release 今天&#xff0c;第四范式AI5G视频电话互动营销产品全新发布。 相较于以往销效率低、互动差、转化差的传统电话外呼和短信营销方式&#xff0c;视频电话互动营销基于AI、5G等技术&#xff0c;可让用户接听电话时观看个性化视频广告并实时互动&#xf…

Fortify三种扫描模式有什么区别?分别怎么用?

一、通过“Audit Workbench”进行测试 “Audit Workbench”支持Java语言源代码的测试。 二、通过“Scan Wizard”进行测试 “Scan Wizard”支持Java、Python、C/C、.Net、Go、PHP、Flex、Action Script、HTML、XML、JavaScript、TypeScript、Kotlin、SQL、ABAP、ColdFusion语言…

基于asp.net的简单的BBS论坛管理系统源码

今天给大家分享一套c#的webform框架的简单的BBS论坛管理系统源码&#xff0c;老规矩&#xff0c;下载链接我放在了最后面了&#xff0c;需要的自行下载&#xff0c;喜欢的小伙伴记得点个赞哦&#xff01; 1.主要功能 这个系统是比较简单的BBS的论坛管理系统源码&#xff0c;包…

C++ //练习 17.2 定义一个tuple,保存一个string、一个vector<string>和一个pair<string, int>。

C Primer&#xff08;第5版&#xff09; 练习 17.2 练习 17.2 定义一个tuple&#xff0c;保存一个string、一个vector和一个pair<string, int>。 环境&#xff1a;Linux Ubuntu&#xff08;云服务器&#xff09; 工具&#xff1a;vim 代码块 /**********************…

GD32 MCU内部温度传感器如何使用,以及适合哪种应用场景?

GD32 MCU全系列产品均内置温度传感器&#xff0c;内部温度传感器可以用于测量器件周围温度及变化&#xff0c;传感器的输出电压连接到ADC的第16通道&#xff0c;可被ADC转换成数字量。 温度传感器输出电压随温度线性变化&#xff0c;由于生产过程中的多样化&#xff0c;温度变…

索引使用规则以及注意失效的场景

目录 一、联合索引遵循最左前缀法则 1.1 执行带着唯一索引和联合索引情况: 1.2 执行不带最左侧字段情况: 1.3 执行跳过中间列情况&#xff1a; 1.4 使用联合索引查找是否跟建立索引的顺序有关&#xff1f; 二、范围查询 三、避免某些操作导致索引失效 3.1 索引列运算 3…

(计算机网络)数据链路层

目录 一.基本概述 二.基本术语 三.基本功能 四.CRC差错检验 五.点对点协议 六.PPP帧格式 七.以太网介绍 一.基本概述 结点&#xff1a;可以相当于一个实体。能够具备接收数据&#xff0c;发送数据的实体 物理层不关心介质问题&#xff0c;发送端和接收端数据不一致&…

docker配置国内镜像加速

docker配置国内镜像加速 由于国内使用docker拉取镜像时&#xff0c;会经常出现连接超时的网络问题&#xff0c;所以配置Docker 加速来使用国内 的镜像加速服务&#xff0c;以提高拉取 Docker 镜像的速度。 1、备份docker配置文件 cp /etc/docker/daemon.json /etc/docker/da…

Datawhale AI 夏令营——从零入门多模态大模型数据合成——Task1学习笔记

天池Better Synth 官方赛事链接&#xff1a;天池Better Synth多模态大模型数据合成挑战赛_算法大赛_天池大赛-阿里云天池的赛制 一、内容提要 天池Better Synth多模态大模型数据合成挑战赛 是 Datawhale 2024 年 AI 夏令营第四期的学习活动—— 适合想 入门并实践 多模态大模型…

java 实现-使用OpenFeign 实现远程调用外部接口(调用swgager)接口

首先根据提供的swagger路径&#xff0c;在网页中查看相关接口方法 打开接口后我们可以看见&#xff0c;接口的路径/online/list 以及可以看见入参和出参 根据所有提供的参考方法和路径&#xff0c;拼接出整体路径URL&#xff08;http://ip:端口/online/list&#xff09; 注意…

一键生成PPT、漫画、长文:橙篇让创意一触即发

​ 橙篇 如何快速高效地创作优质内容&#xff0c;成为了每个创作者都在思考的问题。而今天&#xff0c;我们要介绍的这款神器——橙篇&#xff0c;或许就是解开这个难题的钥匙。它不仅能让你的创意一触即发&#xff0c;更能将你的灵感瞬间化为现实。 橙篇 多样化功能 橙篇不仅仅…

21款奔驰GLS450升级原厂香氛负离子系统,提升车内空气质量

奔驰原厂香氛系统激活原车自带系统&#xff0c;将香气加藏储物盒中&#xff0c;通过系统调节与出风口相结合&#xff0c;再将香味传达至整个车厢&#xff0c;达到净化车厢空气的效果&#xff0c;让整个车厢更加绿色健康&#xff0c;清新淡雅。 产品功能&#xff1a;香氛负离子…

Java JDVC编程

1、Java的数据库编程&#xff1a;JDBC JDBC&#xff08;Java Database Connectivity&#xff09;是Java中用来与数据库进行交互的一种标准API&#xff08;应用程序编程接口&#xff09;。它为开发人员提供了一种统一的方式来访问各种类型的数据库管理系统&#xff08;DBMS&…

火锅店管理系统

TOC springboot260火锅店管理系统 第1章 绪论 1.1选题动因 当前的网络技术&#xff0c;软件技术等都具备成熟的理论基础&#xff0c;市场上也出现各种技术开发的软件&#xff0c;这些软件都被用于各个领域&#xff0c;包括生活和工作的领域。随着电脑和笔记本的广泛运用&am…

(24)(24.3) MSP OSD(二)

文章目录 前言 3 OSD面板项目配置 4 使用SITL测试OSD 5 使用任务规划器配置布局 6 视频 前言 ArduPilot 支持 MSP OSD 协议&#xff0c;该协议允许在 DJI 护目镜上显示飞行数据&#xff0c;就像许多自动驾驶仪中的外部 MAVLink OSD 或内部集成模拟 OSD 一样。如果配置了 …

世界顶级思想家起名大师颜廷利:金融离不开银行,灵魂离不开引航

在《升命学说》一书中,山东济南的杰出人物、被广泛认为是最有影响力的哲学家之一的颜廷利教授,提出了一系列深奥而又实用的生命哲学。这些理念不仅是对传统文化的传承,也是对现代社会的深刻洞察。 世界伟大人民的哲学家思想家教育家,山东籍当代文化名人颜廷利教授提出,生命的本…