【C++】C++11——右值引用和移动语义、左值引用和右值引用、右值引用使用场景和意义、完美转发、新的类功能

news2024/12/25 12:54:58

文章目录

  • C++11
    • 5.右值引用和移动语义
      • 5.1左值引用和右值引用
      • 5.2左值引用与右值引用比较
      • 5.3右值引用使用场景和意义
      • 5.4右值引用引用左值及其一些更深入的使用场景分析
      • 5.5完美转发
    • 6.新的类功能

C++11

在这里插入图片描述

5.右值引用和移动语义

  右值引用是C++11引入的一个新特性,用于支持移动语义和完美转发。

  在C++中,左值和右值是根据它们在表达式中的位置来定义的。左值是指在表达式中位于赋值符号左侧的对象,而右值是指位于赋值符号右侧的对象。例如,在表达式 a = b 中,a 是左值,b 是右值。

  在C++11之前,左值和右值都是使用普通的引用(T&)来处理的。但是,这在处理某些情况时会造成一些不便,例如在实现移动构造函数和移动赋值运算符时。

  为了解决这个问题,C++11引入了右值引用(T&&)的概念。右值引用是一种特殊的引用类型,只能绑定到右值上。这使得我们可以实现移动语义,即将资源从一个对象移动到另一个对象,而不是进行深拷贝。

  右值引用还可以用于实现完美转发,即在函数模板中将参数以原样传递给其他函数,保持其原始的左值或右值性质不变。这需要使用 std::forward 函数来实现。

            

5.1左值引用和右值引用

  什么是左值什么是右值?

  左值和右值是根据它们在表达式中的位置来定义的。

  左值是指可以位于赋值符号左侧的对象,它表示一个对象的身份(例如,一个具有名称的变量)。左值必须在内存中有实体,可以使用取地址符&获取其地址。

  右值指的是位于赋值符号右侧的对象,它表示一个对象的值。右值可以是临时对象(临时变量),也可以是在内存或CPU寄存器中的值。当一个对象被用作右值时,使用的是它的内容(值),而不是它的身份。

  简单来说,左值和右值的区别在于:左值表示一个对象的身份,而右值表示一个对象的值。左值可以取地址,右值不能取地址。

  

  左值示例:

int a = 10;  // a 是左值,因为它是一个具有名称的变量,可以在赋值符号左侧使用  
int b = a;   // a 是左值,因为它在赋值符号左侧

  右值示例:

  在这个例子中,a + b 的结果是一个临时值,没有具体的身份(变量名),只能在赋值符号右侧使用,因此它是右值。

int a = 10;  
int b = 20;  
int c = a + b;  // a + b 的结果是右值,因为它是一个临时值,没有具体的身份(变量名)

  

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

  左值引用(lvalue reference)就是对一个左值进行引用的类型。 具体来说,它是一个具有名称的变量的别名,可以通过该别名访问和修改该变量的值。左值引用使用 T& 表示,其中 T 是被引用的变量的类型。

  右值引用(rvalue reference)就是对一个右值进行引用的类型。 由于右值通常不具有名字,我们也只能通过引用的方式找到它的存在。右值引用使用 T&& 表示,其中 T 是被引用的变量的类型。

  
  左值引用示例:

  左值是一个表示数据的表达式(如变量名或解引用的指针),我们可以获取它的地址+可以对它赋值,左值可以出现赋值符号的左边,右值不能出现在赋值符号左边。 定义时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;
}

            

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

  左值引用总结:

  (1)左值引用只能引用左值,不能引用右值。

  (2)但是const左值引用既可引用左值,也可引用右值

int main()
{
    // 左值引用只能引用左值,不能引用右值。
    int a = 10;
    int& ra1 = a;   // ra为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;
}

            

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

  左值引用的场景:

(1)做参数:

  当函数需要传递大的对象或复杂的数据结构时,可以使用左值引用来做参数传递,以避免对象的复制。这样可以提高函数的执行效率,并减少内存占用。例如:

  在这个例子中,ComplexObject 是一个复杂的数据结构,使用左值引用来做参数传递可以避免对象的复制。

void foo(const ComplexObject& obj) {  
    // ...  
}

(2)做返回值:

  当函数需要返回一个大的对象或复杂的数据结构时,可以使用左值引用来做返回值,以避免对象的复制。这样可以提高函数的执行效率,并减少内存占用。例如:

  在这个例子中,bar 函数返回一个 ComplexObject 类型的左值引用,以避免对象的复制。由于返回的是一个引用,调用者可以直接访问原始对象,而不是一个复制的副本。

ComplexObject& bar() {  
    static ComplexObject obj;  
    return obj;  
}

  综上,无论是做参数还是做返回值,它们都可以减少拷贝,提高效率。

  
  以String的实现为例, 左值引用被用于实现字符串类的拷贝构造函数和赋值重载运算符。

  在拷贝构造函数中,左值引用被用来接收一个传入的字符串对象(const string& s),这样可以避免对象的复制。通过创建一个临时对象(string tmp(s._str)),然后使用 swap 函数将临时对象的资源交换到当前对象,实现了深拷贝的效果。这里的左值引用作为参数传递方式,使得我们可以直接访问传入的字符串对象的成员变量。

  在赋值重载运算符中,左值引用同样被用来接收一个传入的字符串对象(const string& s)。这里的左值引用的作用是使得我们可以将返回的对象赋值给当前对象。通过创建一个临时对象(string tmp(s)),然后使用 swap 函数将临时对象的资源交换到当前对象,实现了深拷贝的效果。最后,返回当前对象的引用(return *this),使得赋值操作可以连续进行。

  综上,在这个代码片段中,左值引用被用于实现深拷贝的拷贝构造函数和赋值重载运算符,它使得我们可以直接访问传入的对象,避免对象的复制,提高了程序的性能。

// 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;
}

  
左值引用的短板:

  但是当函数返回对象是一个局部变量,出了函数作用域就不存在了,就不能使用左值引用返回,只能传值返回。

  下面是一个代码示例,演示了左值引用的短板:

class String {  
public:  
    String(const char* str = "")  
        : _str(nullptr)  
    {  
        std::cout << "String(const char* str) -- 深拷贝" << std::endl;  
        _str = new char[strlen(str) + 1];  
        strcpy(_str, str);  
    }  
  
    String(const String& other)  
        : _str(nullptr)  
    {  
        std::cout << "String(const String& other) -- 深拷贝" << std::endl;  
        _str = new char[strlen(other._str) + 1];  
        strcpy(_str, other._str);  
    }  
  
    String& operator=(const String& other)  
    {  
        std::cout << "String& operator=(const String& other) -- 深拷贝" << std::endl;  
        if (this != &other) {  
            delete[] _str;  
            _str = new char[strlen(other._str) + 1];  
            strcpy(_str, other._str);  
        }  
        return *this;  
    }  
  
    ~String()  
    {  
        delete[] _str;  
    }  
  
private:  
    char* _str;  
};  
  
void printString(const String& str)  
{  
    std::cout << "printString(const String& str)" << std::endl;  
    std::cout << str._str << std::endl;  
}  
  
int main()  
{  
    String s1("Hello");  
    String s2 = "World";  
    printString(s1 + s2);  // 这里会创建一个临时对象,无法用左值引用捕获  
    return 0;  
}

  在上面的代码中,我们定义了一个简单的 String 类,并实现了拷贝构造函数、赋值重载运算符和析构函数。在 main 函数中,我们创建了两个 String 对象 s1 和 s2,然后调用 printString 函数打印它们的拼接结果。

  然而,在 printString 函数中,我们尝试通过左值引用来捕获传入的字符串对象,但是这里的传入参数实际上是一个临时对象(由 s1 + s2 创建),无法用左值引用捕获。这就是左值引用的短板之一,它无法绑定到临时对象或右值上,导致我们无法直接访问这些对象的资源。为了解决这个问题,C++11引入了右值引用的概念。
  

右值引用和移动语义解决上述问题:

  使用右值引用和移动语义可以解决上述问题。具体做法是在 String 类中实现移动构造函数和移动赋值运算符,利用右值引用来捕获临时对象,并将其资源移动到当前对象中,避免深拷贝。

  下面是修改后的代码示例:

  我们在 String 类中实现了移动构造函数和移动赋值运算符,使用右值引用来捕获临时对象,并将其资源移动到当前对象中。在 main 函数中,临时对象就可以使用右值引用捕获,以便调用移动构造函数和移动赋值运算符。这样就可以避免深拷贝,提高程序的性能。

class String {  
public:  
    String(const char* str = "")  
        : _str(nullptr)  
    {  
        std::cout << "String(const char* str) -- 深拷贝" << std::endl;  
        _str = new char[strlen(str) + 1];  
        strcpy(_str, str);  
    }  
  
    String(const String& other)  
        : _str(nullptr)  
    {  
        std::cout << "String(const String& other) -- 深拷贝" << std::endl;  
        _str = new char[strlen(other._str) + 1];  
        strcpy(_str, other._str);  
    }  
  
    String(String&& other) 
        : _str(nullptr)  
    {  
        std::cout << "String(String&& other) -- 移动构造" << std::endl;  
        _str = other._str;  
        other._str = nullptr;  
    }  
  
    String& operator=(const String& other)  
    {  
        std::cout << "String& operator=(const String& other) -- 深拷贝" << std::endl;  
        if (this != &other) {  
            delete[] _str;  
            _str = new char[strlen(other._str) + 1];  
            strcpy(_str, other._str);  
        }  
        return *this;  
    }  
  
    String& operator=(String&& other) 
    {  
        std::cout << "String& operator=(String&& other) -- 移动赋值" << std::endl;  
        if (this != &other) {  
            delete[] _str;  
            _str = other._str;  
            other._str = nullptr;  
        }  
        return *this;  
    }  
  
    ~String()  
    {  
        delete[] _str;  
    }  
  
private:  
    char* _str;  
};  
  
void printString(const String& str)  
{  
    std::cout << "printString(const String& str)" << std::endl;  
    std::cout << str._str << std::endl;  
}  
  
int main()  
{  
    String s1("Hello");  
    String s2 = "World";  
    printString(s1 + s2);  // 临时对象可以使用右值引用捕获  
    return 0;  
}

            

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

  按照语法,右值引用只能引用右值,但右值引用一定不能引用左值吗?

  但是有些场景下,可能真的需要用右值去引用左值实现移动语义。当需要用右值引用引用一个左值时,可以通过move函数将左值转化为右值。 C++11中,std::move()函数位于 头文件中,该函数可以将一个左值强制转化为右值引用,然后实现移动语义。

template<class _Ty>
inline typename remove_reference<_Ty>::type&& move(_Ty&& _Arg) _NOEXCEPT
{
	// forward _Arg as movable
	return ((typename remove_reference<_Ty>::type&&)_Arg);
}

int main()
{
	bit::string s1("hello world");
	// 这里s1是左值,调用的是拷贝构造
	
	bit::string s2(s1);
	// 这里我们把s1 move处理以后, 会被当成右值,调用移动构造
	// 但是这里要注意,一般是不要这样用的,因为我们会发现s1的
	// 资源被转移给了s3,s1被置空了。
	bit::string s3(std::move(s1));
	return 0;
}

  move函数使用示例:

void push_back (value_type&& val);
int main()
{
	list<bit::string> lt;
	bit::string s1("1111");
	// 这里调用的是拷贝构造
	lt.push_back(s1);
	
	// 下面调用都是移动构造
	lt.push_back("2222");
	lt.push_back(std::move(s1));
	return 0;
}

// string(const string& s) -- 深拷贝
// string(string&& s) -- 移动语义
// string(string&& s) -- 移动语义

            

5.5完美转发

  完美转发(Perfect Forwarding)是C++11引入的一种特性,它允许函数模板按照参数原来的形式(类型、值类别)将参数转发给其他函数,从而实现更灵活的编程。

  完美转发的核心在于使用 std::forward 函数模板,它可以将左值转换为左值引用,将右值转换为右值引用,从而实现参数的完美转发。下面是一个简单的示例:

template<typename F, typename T1, typename T2>  
void flip(F f, T1&& t1, T2&& t2)  
{  
    f(std::forward<T2>(t2), std::forward<T1>(t1));  
}

  在这个示例中,flip 函数模板接受一个函数对象 f 和两个参数 t1 和 t2,并将它们转发给函数对象 f,但是转发的顺序与原始顺序相反。通过使用 std::forward,我们可以保证参数的类型和值类别得到正确的保留和转发。

  完美转发在很多情况下都非常有用,比如在实现代理函数、工厂函数、回调函数等场景中,它可以让我们更灵活地处理参数,并避免不必要的拷贝或移动操作。

  完美转发在传参的过程中保留对象原生类型属性:

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; }

// std::forward<T>(t)在传参的过程中保持了t的原生类型属性。
template<typename T>
void PerfectForward(T&& t)
{
	Fun(std::forward<T>(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;
}

  完美转发的示例:

void print(int&& i) {  
    std::cout << "rvalue: " << i << std::endl;  
}  
  
void print(const int& i) {  
    std::cout << "lvalue: " << i << std::endl;  
}  
  
template<typename T>  
void forwardPrint(T&& t) {  
    print(std::forward<T>(t));  
}  
  
int main() {  
    int i = 42;  
    forwardPrint(i);          // lvalue: 42  
    forwardPrint(std::move(i)); // rvalue: 42  
    return 0;  
}

            

6.新的类功能

  默认成员函数:

  原来C++类中,有6个默认成员函数:

  (1)构造函数    (2)析构函数

  (3)拷贝构造函数  (4)拷贝赋值重载

  (5)取地址重载   (6)const 取地址重载

  C++11 新增了两个:移动构造函数和移动赋值运算符重载。

  针对移动构造函数和移动赋值运算符重载有一些需要注意的点如下:

  (1)如果你没有自己实现移动构造函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任意一个。那么编译器会自动生成一个默认移动构造。 默认生成的移动构造函数,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动构造,如果实现了就调用移动构造,没有实现就调用拷贝构造。

  (2)如果你没有自己实现移动赋值重载函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任意一个,那么编译器会自动生成一个默认移动赋值。 默认生成的移动构造函数,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动赋值,如果实现了就调用移动赋值,没有实现就调用拷贝赋值。(默认移动赋值跟上面移动构造完全类似)

  (3)如果你提供了移动构造或者移动赋值,编译器不会自动提供拷贝构造和拷贝赋值。
  

  强制生成默认函数的关键字default:

  C++11可以让你更好的控制要使用的默认函数。假设你要使用某个默认的函数,但是因为一些原因这个函数没有默认生成。比如:我们提供了拷贝构造,就不会生成移动构造了,那么我们可以使用default关键字显示指定移动构造生成。

class Person
{
public:
	Person(const char* name = "", int age = 0)
		:_name(name)
		, _age(age)
	{}
	
	Person(const Person& p)
		:_name(p._name)
		,_age(p._age)
	{}
	
	Person(Person&& p) = default;
	
private:
	bit::string _name;
	int _age;
};

int main()
{
	Person s1;
	Person s2 = s1;
	Person s3 = std::move(s1);
	return 0;
}

  
  禁止生成默认函数的关键字delete:

  如果能想要限制某些默认函数的生成,在C++98中,是该函数设置成private,并且只声明补丁,这样只要其他人想要调用就会报错。在C++11中更简单,只需在该函数声明加上=delete即可,该语法指示编译器不生成对应函数的默认版本,称=delete修饰的函数为删除函数。

class Person
{
public:
	Person(const char* name = "", int age = 0)
		:_name(name)
		, _age(age)
	{}
	
	Person(const Person& p) = delete;
	
private:
	bit::string _name;
	int _age;
};

int main()
{
	Person s1;
	Person s2 = s1;
	Person s3 = std::move(s1);
	return 0;
}

            

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

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

相关文章

冯诺依曼体系结构与进程的初步理解

目录 一&#xff0c;冯诺依曼体系结构 1.是什么&#xff1f;特点 2.为什么&#xff1f; 二&#xff0c;操作系统 三&#xff0c;进程 1.什么是进程&#xff1f; 2.查看进程 3.进程的管理 4.fork()创建子进程 1.fork()简介 2.fork()干了啥 3.fork()为什么会有两个返回…

【Java】微服务——Ribbon负载均衡(跟进源码分析原理)

添加LoadBalanced注解&#xff0c;即可实现负载均衡功能&#xff0c;这是什么原理 1.负载均衡原理 SpringCloud底层其实是利用了一个名为Ribbon的组件&#xff0c;来实现负载均衡功能的。 2.源码跟踪 为什么我们只输入了service名称就可以访问了呢&#xff1f;之前还要获取…

mstsc无法保存RDP凭据, 100%生效

问题 即使如下两项都打勾&#xff0c;其还是无法保存凭据&#xff0c;特别是连接Ubuntu (freerdp server)&#xff1a; 解决方法 网上多种复杂方法&#xff0c;不生效&#xff0c;其思路是修改后台配置&#xff0c;以使mstsc跟平常一样自动记住凭据。最后&#xff0c;如下的…

Python无废话-办公自动化Excel写入操作

Python 办公自动化-Excel写入 创建并保存Excel文件 import openpyxl workbookopenpyxl.Workbook() #创建空Excel文件 sheetworkbook.active #获取活动的工作表 sheet.title“测试“ #修改sheet工作表名称为测试 workbook.save(“data\input\Test.xlsx”) #保存Excel文件 …

R中的min()函数 和max()函数

通过min()函数和max()函数产生Inf 数值空集的最小值和最大值是Inf和–Inf(按此顺序&#xff01;)这确保了传递性&#xff0c;例如min(x1&#xff0c;min(x2)) min(x1&#xff0c;x2)。对于数值x&#xff0c;每当length (x) 0时&#xff0c;max(x) - Inf和min(x) Inf(如果需…

C#餐饮收银系统

一、引言 餐饮收银系统是一种用于管理餐馆、咖啡厅、快餐店等餐饮业务的计算机化工具。它旨在简化点餐、结账、库存管理等任务&#xff0c;提高运营效率&#xff0c;增强客户体验&#xff0c;同时提供准确的财务记录。C# 餐饮收银系统是一种使用C#编程语言开发的餐饮业务管理软…

SDK Vitis记录

文章目录 SDK记录SDK中报错“undefined reference to sqrt”的解决方法通过XML文件导入工程的include路径方法说明 其他设置编译选项设置某些文件/文件夹不编译单独设置文件的编译选项 向存储区中导入/导出数据通过GUI操作使用命令行操作 产生C代码的MAP文件在Xilinx SDK 工程的…

Golang 中的调试技巧

掌握有效的策略和工具&#xff0c;实现顺畅的开发 调试是每位开发人员都必须掌握的关键技能。它是识别、隔离和解决代码库中问题的过程。在 Golang 的世界中&#xff0c;掌握有效的调试技巧可以显著提升您的开发工作流程&#xff0c;并帮助您创建更可靠和健壮的应用程序。在本…

C语言 —— 函数栈帧的创建和销毁

在我们之前学习函数的时候&#xff0c;我们可能有很多困惑? 比如: 局部变量是怎么创建的?为什么局部变量的值是随机值?函数是怎么传参的?传参的顺序是怎样的?形参和实参是什么关系?函数调用是怎么做的?函数调用是结束后怎么返回的? 那么要解决这些问题, 我们就需要知道…

Raspberry Pi 5 新平台 新芯片组

Raspberry Pi 5 的 CPU 和 GPU 性能提高了两到三倍&#xff1b;内存和 I/O 带宽大约是两倍&#xff1b;并且是首款采用英国剑桥内部设计的芯片的 Raspberry Pi 计算机&#xff0c;4GB 型号的售价为 60 美元&#xff0c;8GB 版本的售价为 80 美元 主要特点包括&#xff1a; 2.4…

Zama的fhEVM:基于全同态加密实现的隐私智能合约

1. 引言 Zama的fhEVM定位为&#xff1a; 基于全同态加密实现的隐私智能合约 解决方案 开源代码见&#xff1a; https://github.com/zama-ai/fhevm&#xff08;TypeScript Solidity&#xff09; Zama的fhEVM协议中主要包含&#xff1a; https://github.com/zama-ai/tfhe-…

Windows11+VS2022+OCCT7.6.0安装配置记录

Windows11VS2022OCCT7.6.0安装配置记录 工具及源码准备VS2022以及CMake下载OCCT源码下载第三方库 CMake修改occt_toolkit.cmake进行CMake Visual Studio环境配置配置包含目录配置库目录配置链接器设置系统环境变量配置项目调试环境环境测试 其他方法 主要参考此文&#xff0c;在…

自然语言处理的分类

动动发财的小手&#xff0c;点个赞吧&#xff01; 简介 作为理解、生成和处理自然语言文本的有效方法&#xff0c;自然语言处理&#xff08;NLP&#xff09;的研究近年来呈现出快速传播和广泛采用。鉴于 NLP 的快速发展&#xff0c;获得该领域的概述并对其进行维护是很困难的。…

Golang 语言学习 01 包含如何快速学习一门新语言

Golang方向 区块链 go服务器端 (后台流量支撑程序) 支撑主站后台流量&#xff08;排序&#xff0c;推荐&#xff0c;搜索等&#xff09;&#xff0c;提供负载均衡&#xff0c;cache&#xff0c;容错&#xff0c;按条件分流&#xff0c;统计运行指标 (qps&#xff0c; latenc…

java飞机大战

一、 概述 1.1 项目简介 本次Java课程设计是做一个飞机大战的游戏&#xff0c;应用Swing编程&#xff0c;完成一个界面简洁流畅、游戏方式简单&#xff0c;玩起来易于上手的桌面游戏。该飞机大战项目运用的主要技术即是Swing编程中的一些窗口类库、事件监听以及贴图技术。 1…

微信小程序WebSocket实现stream流式聊天对话功能

要在微信小程序实现聊天对话功能&#xff0c;回话是流式应答&#xff0c;这里使用了WebSocket技术。WebSocket大家应该都很熟悉&#xff0c;使用wx.connectSocket就可以了。这里可能需要注意下的是流式应答&#xff0c;后端如何发送&#xff0c;前端如何接收。直接上代码&#…

【1】c++设计模式——>UML类图的画法

UML介绍 UML:unified modeling language 统一建模语言 面向对象设计主要就是使用UML类图&#xff0c;类图用于描述系统中所包含的类以及他们之间的相互关系&#xff0c;帮助人们简化对系统的理解&#xff0c;他是系统分析和设计阶段的重要产物&#xff0c;也是系统编码和测试的…

小程序 用户反馈 与 客服对话 使用说明

在开发小程序时&#xff0c;通过翻阅官方文档&#xff0c;会发现 button 的 open-type 属性有很多值可以选。因此&#xff0c;我们就可以实现相应的按钮功能。 微信开发文档-表单组件-buttonhttps://developers.weixin.qq.com/miniprogram/dev/component/button.html contact…

嵌入式学习笔记(44)S5PV210的SD卡启动实战

8.5.1任务&#xff1a;大于16KB的bin文件使用SD卡启动 (1)总体思路&#xff1a;将我们的代码分为2部分&#xff0c;第一部分BL1小于等于16KB&#xff0c;第二部分为任意大小&#xff0c;iROM代码执行完成后从SD卡启动会自动读取BL1到iRAM中执行&#xff1b;BL1执行时负责初始化…

ChatGPT启蒙之旅:弟弟妹妹的关键概念入门

大家好,我是herosunly。985院校硕士毕业,现担任算法研究员一职,热衷于机器学习算法研究与应用。曾获得阿里云天池比赛第一名,CCF比赛第二名,科大讯飞比赛第三名。拥有多项发明专利。对机器学习和深度学习拥有自己独到的见解。曾经辅导过若干个非计算机专业的学生进入到算法…