C++ 11【右值引用】

news2024/10/6 20:31:17

💓博主CSDN主页:麻辣韭菜💓

⏩专栏分类:C++修炼之路⏪

🚚代码仓库:C++高阶🚚

🌹关注我🫵带你学习更多C++知识
  🔝🔝

1.C++ 11 简介

目录

1.C++ 11 简介

2. 统一的列表初始化  

2.1 {}初始化  

 2.2 initializer_list

3.1 auto 

3.2 decltype

 4.右值引用和移动语义

 4.2 左值引用与右值引用比较

4.3 右值引用使用场景和意义

4.4 右值引用引用左值及其一些更深入的使用场景分析  

5.完美转化

5.1模板中的&& 万能引用(引用折叠)

 5.2完美转发实际中的使用场景:


C++11是C++编程语言的一个主要更新版本,它在2011年被国际标准化组织(ISO)正式批准为ISO/IEC 14882:2011标准。这个版本引入了许多新特性和改进,旨在提高语言的表达能力、性能和易用性。以下是C++11的一些关键特性:

  1. 自动类型推断(auto关键字):允许编译器自动推断变量的类型,简化了代码编写,尤其是在模板元编程中。

  2. 基于范围的for循环:提供了一种更简洁的方式来遍历容器。

  3. lambda表达式:允许在代码中创建匿名函数,这在算法编程中非常有用。

  4. 智能指针:如std::unique_ptrstd::shared_ptr,提供了自动内存管理,减少了内存泄漏的风险。

  5. 右值引用和移动语义:允许更高效的资源管理,特别是对于临时对象。

  6. 变长模板参数:允许模板函数和类接受任意数量的模板参数。

  7. 委托构造函数:允许构造函数之间相互委托。

  8. 继承构造函数:允许派生类继承基类的构造函数。

  9. 线程库:引入了一个新的线程库,支持多线程编程。

  10. 原子操作和内存模型:提供了对多线程编程的支持,包括原子操作和内存顺序控制。

  11. 用户定义的字面量:允许用户定义新的字面量后缀。

  12. 静态断言:可以在编译时检查条件。

  13. 类型特征和类型遍历:提供了类型信息查询和类型操作的能力。

  14. 统一的初始化列表:简化了对象的初始化。

  15. 新的字符串字面量:如u8"",支持UTF-8编码。

  16. 匿名结构体和类:允许在声明中嵌套结构体和类。

  17. 属性(属性修饰符):如[[deprecated]],允许标记某些特性为已弃用。

  18. 正则表达式库:提供了正则表达式的处理能力。

C++11的这些特性使得C++语言更加强大和灵活,同时也使得编写的代码更加简洁和易于维护。

2. 统一的列表初始化  

2.1 {}初始化  

#include <iostream>


using namespace std;


struct Point
{
	int _x;
	int _y;
};
int main()
{
	int x1 = 1;
	int x2 = { 2 };
	// int x4(1);

	// 可以省略赋值符号
	int x3{ 3 };
	int array1[]{ 1, 2, 3, 4, 5 };
	int array2[5]{ 0 };
	Point p{ 1, 2 };
	// C++11中列表初始化也可以适用于new表达式中
	int* pa = new int[4]{ 0 };

	return 0;
}

​ 

C++11 扩大了用大括号括起的列表 ( 初始化列表 ) 的使用范围,使其可用于所有的内置类型和用户自定义的类型,使用初始化列表时,可添加等号 (=) ,也可不添加

我个人感觉还是添加等号(=),比较好。

class Date
{
public:
	//explicit Date(int year, int month, int day)
	Date(int year, int month, int day)
		:_year(year)
		, _month(month)
		, _day(day)
	{
		cout << "Date(int year, int month, int day)" << endl;
	}

private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1(2022, 1, 1); // old style

	// C++11支持的列表初始化,这里会调用构造函数初始化
	Date d2 = { 2024, 5, 20 };
	Date d3 { 2024, 5, 21 };

	return 0;
}

 创建对象时也可以使用列表初始化方式调用构造函数初始化

​ 

 2.2 initializer_list

它提供了一种便捷的方式来接收和使用初始化列表(initializer lists)。初始化列表是一组用花括号 {} 包围的初始化值。

在 C++11 之前,当你需要初始化一个容器或对象时,你通常需要使用单独的参数或者构造函数的多个参数。std::initializer_list 的引入使得你可以一次性传递一个值的列表,这在初始化数组、容器或自定义类型时非常有用。

#include <initializer_list>
#include <iostream>

class MyClass {
public:
    MyClass(std::initializer_list<int> init) {
        for (auto value : init) {
            values.push_back(value);
        }
    }

    void print() const {
        for (auto value : values) {
            std::cout << value << " ";
        }
        std::cout << std::endl;
    }

private:
    std::vector<int> values;
};

int main() {
    MyClass obj1 = {1, 2, 3, 4, 5}; // 使用列表初始化 MyClass
    obj1.print(); // 输出: 1 2 3 4 5

    MyClass obj2{6, 7, 8, 9, 10}; // 直接初始化 MyClass
    obj2.print(); // 输出: 6 7 8 9 10

    return 0;
}

 std::initializer_list作为参数的构造函数,这样初始化容器对象就更方便了。也可以作为operator=

的参数,这样就可以用大括号赋值

class Date
{
public:
	//explicit Date(int year, int month, int day)
	Date(int year, int month, int day)
		:_year(year)
		, _month(month)
		, _day(day)
	{
		cout << "Date(int year, int month, int day)" << endl;
	}

private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	vector<int> v1 = { 1,2,3,4,5 };
	vector<int> v2 = { 10,20,30 };
	vector<int> v3 = { 10,20,30,1,1,2,2,2,2,2,2,1,1,1,1,1,1,1,1,2,1,1,2 };

	list<int> lt1 = { 1,2,3,4,5 };
	list<int> lt2 = { 10,20,30 };

	auto i1 = { 10,20,30,1,1,2,2,2,2,2,2,1,1,1,1,1,1,1,1,2,1,1,2 };
	auto i2 = { 10,20,30 };
	cout << typeid(i1).name() << endl;
	cout << typeid(i2).name() << endl;

	initializer_list<int>::iterator it1 = i1.begin();
	initializer_list<int>::iterator it2 = i2.begin();
	cout << it1 << endl;
	cout << it2 << endl;
	 //*it1 = 1;

	initializer_list<int> i3 = { 10,20,30 };
	initializer_list<int>::iterator it3 = i3.begin();
	cout << it3 << endl;

	Date d1(2023,5,20);
	Date d2(2023,5,21);
	// initializer_list<Date>
	vector<Date> vd1 = {d1, d2};
	vector<Date> vd2 = { Date(2023,5,20), Date(2023,5,21) };
	vector<Date> vd3 = { {2023,5,20}, {2023,5,20} };

	map<string, string> dict = { {"sort", "排序"},{"string", "字符串"},{"Date", "日期"} };
	pair<string, string> kv1 = { "Date", "日期" };
	pair<string, string> kv2 { "Date", "日期" };

	return 0;
}

 

3.1 auto 

C++98 auto 是一个存储类型的说明符,表明变量是局部自动存储类型,但是局部域中定义局
部的变量默认就是自动存储类型,所以 auto 就没什么价值了。 C++11 中废弃 auto 原来的用法,将
其用于实现自动类型腿断。这样要求必须进行显示初始化,让编译器将定义对象的类型设置为初
始化值的类型。
int main()
{
    int i = 10;
    auto p = &i;
    auto pf = strcpy;
    cout << typeid(p).name() << endl;
    cout << typeid(pf).name() << endl;
    map<string, string> dict = { {"sort", "排序"}, {"insert", "插入"} };
    //map<string, string>::iterator it = dict.begin();
    auto it = dict.begin();
    return 0;
}

3.2 decltype

 关键字decltype将变量的类型声明为表达式指定的类型。

int main()
{
	const int x = 1;
	double y = 2.2;

	cout << typeid(x * y).name() << endl;

	decltype(x * y) ret; // ret的类型是double
	decltype(&x) p;      // p的类型是const int*
	cout << typeid(ret).name() << endl;
	cout << typeid(ret).name() << endl;
	cout << typeid(p).name() << endl;

	// vector存储的类型跟x*y表达式返回值类型一致
	// decltype推导表达式类型,用这个类型实例化模板参数或者定义对象
	vector<decltype(x* y)> v;

	return 0;
}

 

 4.右值引用和移动语义

传统的 C++ 语法中就有引用的语法,而 C++11 中新增了的右值引用语法特性,所以我们之前学习的引用就叫做 左值引用 无论左值引用还是右值引用,都是给对象取别名

 什么是左值?什么是左值引用?

左值是一个表示数据的表达式 ( 如变量名或解引用的指针 ) 我们可以获取它的地址 + 可以对它赋
值,左值可以出现赋值符号的左边,右值不能出现在赋值符号左边 。定义时 const 修饰符后的左
值,不能给他赋值,但是可以取它的地址。左值引用就是给左值的引用,给左值取别名。

int main()
{
    // 以下的p、b、c、*p都是左值
    int* p = new int(0);
    int b = 1;
    const int c = 2;
    // 以下几个是对上面左值的左值引用
    int*& rp = p;
    int& rb = b;
    const int& rc = c;
    int& pvalue = *p;
    return 0;
}
什么是右值?什么是右值引用?
右值也是一个表示数据的表达式,如:字面常量、表达式返回值,函数返回值 ( 这个不能是左值引
用返回 ) 等等, 右值可以出现在赋值符号的右边,但是不能出现出现在赋值符号的左边,右值不能
取地址 。右值引用就是对右值的引用,给右值取别名。

int main()
{
    double x = 1.1, y = 2.2;
    // 以下几个都是常见的右值
    10;
    x + y;
    fmin(x, y);
    // 以下几个都是对右值的右值引用
    int&& rr1 = 10;
    double&& rr2 = x + y;
    double&& rr3 = fmin(x, y);
    // 这里编译会报错:error C2106: “=”: 左操作数必须为左值
    10 = 1;
    x + y = 1;
    fmin(x, y) = 1;
    return 0;
}
需要注意的是右值是不能取地址的,但是给右值取别名后,会导致右值被存储到特定位置,且可
以取到该位置的地址,也就是说例如:不能取字面量10的地址,但是rr1引用后,可以对rr1取地
址,也可以修改rr1
int main()
{
     double x = 1.1, y = 2.2;
     int&& rr1 = 10;
     const double&& rr2 = x + y;
     rr1 = 20;
     rr2 = 5.5;  // 报错
     return 0;
}

 4.2 左值引用与右值引用比较

左值引用总结:
1. 左值引用只能引用左值,不能引用右值。
2. 但是const左值引用既可引用左值,也可引用右值

int main()
{
    // 左值引用只能引用左值,不能引用右值。
    int a = 10;
    int& ra1 = a;   // ra1为a的别名
    //int& ra2 = 10;   // 编译失败,因为10是右值
    // const左值引用既可引用左值,也可引用右值。
    const int& ra3 = 10;
    const int& ra4 = a;
    return 0;
}

右值引用总结:
1. 右值引用只能右值,不能引用左值。
2. 但是右值引用可以move以后的左值。

int main()
{
     // 右值引用只能右值,不能引用左值。
     int&& r1 = 10;
 
     // error C2440: “初始化”: 无法从“int”转换为“int &&”
     // message : 无法将左值绑定到右值引用
     int a = 10;
     int&& r2 = a;
     // 右值引用可以引用move以后的左值
     int&& r3 = std::move(a);
     return 0;
}

4.3 右值引用使用场景和意义

既然左值引用可以引用左值和引用右值,那C++11搞出右值引用岂不是多此一举?不要忘了左值引用还有短板没有解决。

我们通过代码来演示右值引用的意义

通过上面的代码示例我们可以看到 传值a+b会调用参数是右值引用的func函数。当然有人觉得这不能说明右值引用的意义。那如果传值是个自定义类型的参数比如string这样的容器又或者unordered_map 这样的?

比如下面这种场景

#pragma once
#include <iostream>
#include <list>
#include <vector>
#include <cassert>
#include <map>

using namespace std;
namespace gx
{
	class string
	{
	public:
		typedef char* iterator;
		iterator begin()
		{
			return _str;
		}
		iterator end()
		{
			return _str + _size;
		}
		string(const char* str = "")
			:_size(strlen(str))
			, _capacity(_size)
		{
			//cout << "string(char* str)" << endl;
			_str = new char[_capacity + 1];
			strcpy(_str, str);
		}
		// s1.swap(s2)
		void swap(string& s)
		{
			::swap(_str, s._str);
			::swap(_size, s._size);
			::swap(_capacity, s._capacity);
		}
		// 拷贝构造
		string(const string& s)
			:_str(nullptr)
		{
			cout << "string(const string& s) -- 深拷贝" << endl;
			string tmp(s._str);
			swap(tmp);
		}
		// 赋值重载
		string& operator=(const string& s)
		{

			cout << "string& operator=(string s) -- 深拷贝" << endl;
			string tmp(s);
			swap(tmp);
			return *this;
		}
		
		~string()
		{
			delete[] _str;
			_str = nullptr;
		}
		char& operator[](size_t pos)
		{
			assert(pos < _size);
			return _str[pos];
		}
		void reserve(size_t n)
		{
			if (n > _capacity)
			{
				char* tmp = new char[n + 1];
				strcpy(tmp, _str);
				delete[] _str;
				_str = tmp;
				_capacity = n;
			}
		}
		void push_back(char ch)
		{
			if (_size >= _capacity)
			{
				size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;
				reserve(newcapacity);
			}
			_str[_size] = ch;
			++_size;
			_str[_size] = '\0';
		}
		//string operator+=(char ch)
		string& operator+=(char ch)
		{
			push_back(ch);
			return *this;
		}
		string operator+(char ch)
		{
			string tmp(*this);
			tmp += ch;
			return tmp;
		}
		const char* c_str() const
		{
			return _str;
		}
	private:
		char* _str;
		size_t _size;
		size_t _capacity; // 不包含最后做标识的\0
	};
}
#include "String.h"
int main()
{
	gx::string s1("hello world");
	gx::string s2(s1);
	gx::string s3 = (s2 + '!');


	return 0;
}

s2+'!' 是个右值 右值又称为将亡值。将亡值也就意味着执行完这条语句。s2+'!'所在的空间会被析构函数回收。那我们这时再拷贝一个和它一样的大的空间岂不是浪费了?能不能把它的空间直接给s3? 

当然可以 我们在头文件String.h增加这段代码

// 移动构造
 string(string&& s)
 :_str(nullptr)
 ,_size(0)
 ,_capacity(0)
 {
 cout << "string(string&& s) -- 移动构造" << endl;
 swap(s);
 }

 移动构造代价就比深拷贝的代价低了很多。效率就变高了!

之前的学习我们知道左值引用使用场景:

一是引用传参二是引用返回

但是引用返回如果是函数内的局部对象,是不能用引用返回的。

这就意味局部对象返回时是要拷贝的。那如果返回的局部对象是一颗红黑树?那拷贝就极大降低了效率。 

举个例子 string 的to_string这个函数。

gx::string to_string(int value)
	{
		bool flag = true;
		if (value < 0)
		{
			flag = false;
			value = 0 - value;
		}

		gx::string str;
		while (value > 0)
		{
			int x = value % 10;
			value /= 10;

			str += ('0' + x);
		}

		if (flag == false)
		{
			str += '-';
		}

		std::reverse(str.begin(), str.end());
		return str;
	}

 

那如果是C++11之后的编译器呢?

 

编译器直接优化成移动构造 

右值引用和移动语义解决上述问题:
gx ::string 中增加移动构造, 移动构造本质是将参数右值的资源窃取过来,占位已有,那么就不
用做深拷贝了,所以它叫做移动构造,就是窃取别人的资源来构造自己
移动构造中没有新开空间,拷贝数据,所以效率提高了。  
不仅仅有移动构造,还有移动赋值:
gx::string 类中增加移动赋值函数,再去调用gx ::to_string(1234) ,不过这次是将
gx::to_string(1234) 返回的右值对象赋值给 ret1 对象,这时调用的是移动构造。
// 移动赋值
string& operator=(string&& s)
{
cout << "string& operator=(string&& s) -- 移动语义" << endl;
swap(s);
return *this;
}

 

这里运行后,我们看到调用了一次移动构造和一次移动赋值。因为如果是用一个已经存在的对象
接收,编译器就没办法优化了。gx::to_string函数中会先用str生成构造生成一个临时对象,但是
我们可以看到,编译器很聪明的在这里把str识别成了右值,调用了移动构造。然后在把这个临时
对象做为gx::to_string函数调用的返回值赋值给ret1,这里调用的移动赋值

 

 C++11后STL中的容器都是增加了移动构造和移动赋值:

这里就列举移动构造感兴趣的可以去cplusplus官网看看。

4.4 右值引用引用左值及其一些更深入的使用场景分析  

按照语法,右值引用只能引用右值,但右值引用一定不能引用左值吗?因为:有些场景下,可能
真的需要用右值去引用左值实现移动语义。 当需要用右值引用引用一个左值时,可以通过 move
函数将左值转化为右值 C++11 中, std::move() 函数 位于 头文件中,该函数名字具有迷惑性,
并不搬移任何东西,唯一的功能就是将一个左值强制转化为右值引用,然后实现移动语义
int main()
{
     gx::string s1("hello world");
     // 这里s1是左值,调用的是拷贝构造
     gx::string s2(s1);
     // 这里我们把s1 move处理以后, 会被当成右值,调用移动构造
     // 但是这里要注意,一般是不要这样用的,因为我们会发现s1的
     // 资源被转移给了s3,s1被置空了。
     gx::string s3(std::move(s1));
     return 0;
}

STL容器插入接口函数也增加了右值引用版本:

以链表为例

 从上面运行结果来看,减少了深拷贝。那就意味着效率就提高了。当然move以后也有风险。s1就悬空了没有了。

一般来说我们写法不会用move而是下面这种 

 

那如果是C++98 那就是拷贝构造。而11之后就是移动构造。

总结:

左值引用减少拷贝,提高效率
右值引用也是减少拷贝,提高效率
但是他们的角度不同,左值引用是直接减少拷贝
右值引用是间接减少拷贝,识别出是左值还是右值,如果是右值,则不再深拷贝,直接移动拷贝!提高效率。

5.完美转化

5.1模板中的&& 万能引用(引用折叠)

 模板中的&&不代表右值引用,而是万能引用,其既能接收左值又能接收右值。
 模板的万能引用只是提供了能够接收同时接收左值引用和右值引用的能力,
void Fun(int &x){ cout << "左值引用" << endl; }
void Fun(const int &x){ cout << "const 左值引用" << endl; }
void Fun(int &&x){ cout << "右值引用" << endl; }
void Fun(const int &&x){ cout << "const 右值引用" << endl; }

template<typename T>
void PerfectForward(T&& t)
{
     Fun(t);
}
int main()
{
     PerfectForward(10);           // 右值
     int a;
     PerfectForward(a);            // 左值
     PerfectForward(std::move(a)); // 右值
     const int b = 8;
     PerfectForward(b);      // const 左值
     PerfectForward(std::move(b)); // const 右值
     return 0;
}

先看上面一段代码的 按照常理 PerfectForward(std::move(a));  PerfectForward(std::move(b));

是会调用void Fun(int&& x) { cout << "右值引用" << endl; }和void Fun(const int&& x) { cout << "const 右值引用" << endl; }函数

 

运行结果却都是左值引用?这是为什么?

因为右值本身就具有常性不可修改 PerfectForward这个函数参数是万能引用 当识别是右值时,编译器发现是右值时,会开辟一段空间,而这个开辟的空间会和传参的值进行资源转换。而在转换的过程中会出现属性丢失。也就是说原本的右值属性变成了左值属性。而这时在fun这函数进行传参t时,而这个t就变成左值。那调用时就会调用参数是左值的fun

那如何解决这个问题?

那就需要用到完美转发 forward<>()

 std::forward<T>(t)在传参的过程中保持了t的原生类型属性。

 5.2完美转发实际中的使用场景:

就比如list 我们使用push_back 时,我们后来又实现了insert这个插入函数

如果push_back函数套用insert这个函数就会出现上面的情况。传参时出现属性丢失。

原本是右值的移动构造就会变成拷贝构造。 效率就降低了。

 

template<class T>
struct ListNode
{
	ListNode* _next = nullptr;
	ListNode* _prev = nullptr;
	T _data;
};
template<class T>
class List
{
	typedef ListNode<T> Node;
public:
	List()
	{
		_head = new Node;
		_head->_next = _head;
		_head->_prev = _head;
	}
	void PushBack(T&& x)
	{
		//Insert(_head, x);
		Insert(_head, std::forward<T>(x));
	}
	void PushFront(T&& x)
	{
		//Insert(_head->_next, x);
		Insert(_head->_next, std::forward<T>(x));
	}
	void Insert(Node* pos, T&& x)
	{
		Node* prev = pos->_prev;
		Node* newnode = new Node;
		newnode->_data = std::forward<T>(x); // 关键位置
		// prev newnode pos
		prev->_next = newnode;
		newnode->_prev = prev;
		newnode->_next = pos;
		pos->_prev = newnode;
	}
	void Insert(Node* pos, const T& x)
	{
		Node* prev = pos->_prev;
		Node* newnode = new Node;
		newnode->_data = x; // 关键位置
		// prev newnode pos
		prev->_next = newnode;
		newnode->_prev = prev;
		newnode->_next = pos;
		pos->_prev = newnode;
	}
private:
	Node* _head;
};
int main()
{
	List<gx::string> lt;
	lt.PushBack("1111");
	lt.PushFront("2222");
	return 0;
}

如果我们把任意一个关键位置的forward的去掉,都会是拷贝构造。 

 右值引用非常的绕,希望大家下去可以多看几遍。有什么不懂的可以私信我。关注我带你学习更多C++知识。下节预告可变参数模板、lambda。也是非常重要!!!

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

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

相关文章

JVM之【类的生命周期】

首先&#xff0c;请区分Bean的声明周期和类的声明周期。此处讲的是类的声明周期 可以同步观看另一篇文章JVM之【类加载机制】 概述 在Java中数据类型分为基本数据类型和引用数据类型 基本数据类型由虚拟机预先定义&#xff0c;引用数据类型则需要进行类的加载 按照]ava虚拟机…

如何打造不一样的景区文旅VR体验馆项目?

近年来影院类产品迅速火爆&#xff0c;市面上的产品越来越多&#xff0c;投资者可以说是挑花了眼。为了助力投资者实现持续盈利&#xff0c;今天来给大家分析目前普乐蛙大爆新品悬空球幕飞行影院与其他5D/7D影院有哪些区别&#xff0c;给大家的创业投资之路避避雷~ 那我们正式开…

Android精通值Fragment的使用 —— 不含底层逻辑(五)

文章目录 1. Fragment1.1 Fragment的特性1.2 Fragment的基本使用步骤1.3 动态添加Fragment基本步骤1.4 Fragment与Activity的通信原生方案&#xff1a;Bundle类深入方案&#xff1a;java类与类通信的方案&#xff1a;接口Activity从Fragment获取消息Fragment从Activity获取消息…

德国80%的统计学教授都会答错的6个与P值有关的问题!

小编阅读了一篇发表于2002年关于P值的一项问卷调查研究 [1]&#xff0c;作者在6所德国大学中邀请了3组不同的受试者&#xff0c;分别为: 心理学专业的学生(n 44)&#xff1b;主要从事科学研究但不进行统计相关教学的教授和讲师(n 39)&#xff1b;进行统计相关教学的教授和讲师…

05-控制流(分支结构)

05-控制流(分支结构) 一、二路分支 程序中某一段代码需要满足一定的条件才会被执行。 if 语句&#xff1a;用于表达一种条件&#xff0c;如果条件满足则执行某个代码块。if-else 语句&#xff1a;用于表达一种条件&#xff0c;如果条件满足则执行某个代码块&#xff0c;否则…

微信小程序bindgetphonenumber获取手机号阻止冒泡触发

问题&#xff1a;点击手机号弹出微信的手机号验证组件&#xff0c;这是可以的。但是我点击车牌号&#xff0c;也弹出来了&#xff0c;这就郁闷了。 以下是解决方法 点击手机号时&#xff0c;弹出选择手机号 解决&#xff1a; <view style"display: flex;justify-conte…

Facebook开户|Facebook广告设计与测试优化

早上好家人们~今天Zoey给大家伙带来的是Facebook广告设计与测试优化&#xff0c;需要的家人们看过来啦&#xff01; 一、避免复杂用图和过多的文字 根据Facebook的数据显示&#xff0c;用户平均浏览一个贴文的时间在手机上仅花1.7秒、在电脑上则为2.5秒。因此&#xff0c;广告…

Java1.8 vue版家政服务系统成品源码 家政管家系统源码 家政月嫂系统源码 家政保洁系统源码 在线派单,师傅入驻全套商业源码

Java1.8 vue版家政服务系统成品源码 家政管家系统源码 家政月嫂系统源码 家政保洁系统源码 在线派单&#xff0c;师傅入驻全套商业源码 一、系统定义 家政上门服务系统是一种利用互联网技术&#xff0c;将家政服务需求与专业的家政服务人员进行高效匹配的平台。它允许用户通过…

LeetCode-704. 二分查找【数组 二分查找】

LeetCode-704. 二分查找【数组 二分查找】 题目描述&#xff1a;解题思路一&#xff1a;注意开区间和闭区间背诵版&#xff1a;解题思路三&#xff1a; 题目描述&#xff1a; 给定一个 n 个元素有序的&#xff08;升序&#xff09;整型数组 nums 和一个目标值 target &#xf…

Partially Spoofed Audio Detection论文介绍(ICASSP 2024)

An Efficient Temporary Deepfake Location Approach Based Embeddings for Partially Spoofed Audio Detection 论文翻译名&#xff1a;一种基于部分欺骗音频检测的基于临时深度伪造位置方法的高效嵌入 摘要&#xff1a; 部分伪造音频检测是一项具有挑战性的任务&#xff0…

水电表自动抄表系统

1.简述 水电表自动抄表系统是一种现代化智能化管理系统&#xff0c;它利用先进的物联网&#xff0c;完成了远程控制、即时、零接触的水电表读值收集&#xff0c;大大提升了公共事业服务项目的效率和准确性。该系统不仅减少了人工抄表工作量&#xff0c;还避免了人为失误&#…

【NOIP2020普及组复赛】题3:方格取数

题3&#xff1a;方格取数 【题目描述】 设有 nm 的方格图&#xff0c;每个方格中都有一个整数。现有一只小熊&#xff0c;想从图的左上角走到右下角&#xff0c;每一步只能向上、向下或向右走一格&#xff0c;并且不能重复经过已经走过的方格&#xff0c;也不能走出边界。小熊…

神经网络搭建(1)----nn.Sequential

神经网络模型构建 采用CIFAR10中的数据&#xff0c;并对其进行简单的分类。以下图为例 输入&#xff1a;3通道&#xff0c;3232 ( 经过一个55的卷积) → 变成32通道&#xff0c;3232的图像 (经过22的最大池化) → 变成32通道&#xff0c;1616的图像 ( 经过一个55的卷积) → 变…

电商售后常见的客服快捷语

在电商行业&#xff0c;优质的客户服务体验是留住顾客、建立品牌信誉的关键。面对多样化的售后请求&#xff0c;如何高效、准确地回应顾客&#xff0c;成为每个客服团队必须面对的挑战。今天&#xff0c;我给大家分享一些电商售后常见的客服快捷语&#xff0c;帮助客服人员提高…

AIGC 011-SAM第一个图像分割大模型-分割一切!

AIGC 011-SAM第一个图像分割大模型-分割一切&#xff01; 文章目录 0 论文工作1论文方法2 效果 0 论文工作 这篇论文介绍了 Segment Anything (SA) 项目&#xff0c;这是一个全新的图像分割任务、模型和数据集。SA 项目是一个具有里程碑意义的工作&#xff0c;它为图像分割领域…

网络安全:https劫持

文章目录 参考https原理https窃听手段SSL/TLS降级原理难点缺点 SSL剥离原理发展缺点前端劫持 MITM攻击透明代理劫持 参考 https原理 SNI 浏览器校验SSL证书 https降级 https握手抓包解析 lets encrypt申请证书 https原理 步骤如下&#xff1a; 客户端向服务器发送https请求。…

Java大文件上传、分片上传、多文件上传、断点续传、上传文件minio、分片上传minio等解决方案

一、上传说明 文件上传花样百出&#xff0c;根据不同场景使用不同方案进行实现尤为必要。通常开发过程中&#xff0c;文件较小&#xff0c;直接将文件转化为字节流上传到服务器&#xff0c;但是文件较大时&#xff0c;用普通的方法上传&#xff0c;显然效果不是很好&#xff0c…

docker 拉取不到镜像的问题:拉取超时

error pulling image configuration: download failed after attempts6: dial tcp 31.13.94.10:443: i/o timeout 首先设置国内的镜像源&#xff1a;复制下面直接执行 sudo mkdir -p /etc/docker sudo tee /etc/docker/daemon.json <<-EOF{"registry-mirrors"…

k8s学习--Secret详细解释与应用

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 Secret什么是Secret?Secret四种类型及其特点Secret应用案例&#xff08;1&#xff09;将明文密码进行base64编码&#xff08;2&#xff09;编写创建secret的YAML文…

【人工智能003】图像识别算法模型常见术语简单总结(已更新)

1.熟悉、梳理、总结数据分析实战中的AI图像识别等实战研发知识体系&#xff0c;这块领域很大&#xff0c;需要耗费很多精力&#xff0c;逐步总结、更新到位&#xff0c;&#xff0c;&#xff0c; 2.欢迎点赞、关注、批评、指正&#xff0c;互三走起来&#xff0c;小手动起来&am…