先说说引用
C++中的引用可以理解为给一个变量起一个别名,就像我们都有自己的名字,同样也有很多人有自己的小名,外号等,就像张三的小名--二狗就是张三的一个引用,遇到张三无论是说嘿!张三!还是嘿!二狗!张三都可以知道有人在叫他的名字,这就引用
引用的特点
在说左值引用和右值引用的特点之前我们必须要时刻记清一个问题,无论是右值引用还是左值引用本质上都是引用类型,他们都具有引用的特点和作用,那就先说下引用的特点和作用。
特点:
- 引用必须在声明时被初始化,一旦初始化就不能改变它的指向。
- 引用本身不占用内存空间,他只是为已经存在的一个变量起了一个别名,因此使用时不需要进行额外的内存分配和复制。
- 引用可以作为函数的参数和返回值,可以避免拷贝大对象的开销(这里也是右值引用很大的作用之一),提高程序的效率。
- 引用可以简化代码,使得代码更加清晰易懂。
作用:
- 作为函数参数:引用可以作为函数的参数传递,可以避免函数对参数的拷贝,提高程序的效率。
- 作为函数返回值:引用可以作为函数的返回值,可以避免函数
-
返回大对象的拷贝,提高程序的效率。
-
简化代码:引用可以简化代码,使代码更加清晰和易读。
-
实现函数或运算符的重载:引用可以用于实现函数或运算符的重载,使代码更加灵活和可扩展。
-
引用作为对象的别名:引用可以作为对象的别名,可以用于修改对象的值,或者用于操作对象的成员。
右值引用
C++11引入了右值引用的概念,他是一种新的引用类型,用于引用临时对象。在我们了解右值引用之前我们最后还需要了解下右值的概念。
右值:
右值我们可以粗俗的理解为(只能)放在等号右边的值,即不能被取地址的临时对象或字面值常量。右值可以是基本数据类型也可以是对象,这里包括临时对象和函数返回值等。
右值具有以下特点:
-
右值是临时的:右值是在表达式中临时生成的,一旦表达式结束,就会被销毁。
-
右值不具备地址:右值没有具体的内存地址,因此不能被取地址。
-
右值可以在表达式中被移动:由于右值是临时的,因此可以在表达式中被移动,而不需要进行拷贝操作。
右值引用的作用
我们要了解右值引用首先要了解他出现的原因,而右值引用的出现,正是为了利用起来很难被正常使用的右值,避免空间的频繁创建与销毁,大大提高了程序的效率。我们可以看下下面这段代码的运行结果。
#include<iostream>
using namespace std;
class A
{
public:
int a;
A() {};
A(int num):a(num) {};
A test(A a)
{
a.a = 1;
return a;
}
A test2(A&& a)
{
cout << "test2";
return a;
}
~A()
{
cout << "销毁了一个A对象"<<endl;
}
};
void main()
{
A a;
a.test(a);
char c = getchar();
}
我们可以看到,当程序运行到getchar阻塞时,调用了两次析构函数,这里的两次就是函数传参和函数返回时发生的对象创建和销毁。
此时我们将这个程序修改为这样:
#include<iostream>
using namespace std;
class A
{
public:
int a;
A() {};
A(int num):a(num) {};
A test(A a)
{
a.a = 1;
return a;
}
A test2(A&& a)
{
cout << "test2";
return a;
}
~A()
{
cout << "销毁了一个A对象"<<endl;
}
};
void main()
{
A a;
A && b = a.test(move(a));//move函数将a这个左值转化为右值
char c = getchar();
}
此时再次运行,我们就会发现函数在getchar()阻塞之前不会调用析构函数,也就是说没有新的对象被创建销毁。
这样我们就节省了两次对象的创建销毁,如果A这个类变得很大很大,那么右值引用就为我们节省下程序的很多开销。这就是右值引用很大的优点。
右值引用的特点和作用:
特点:
-
右值引用使用&&符号来声明,例如int&& x = 10;。
-
右值引用只能绑定到右值,不能绑定到左值。
-
右值引用可以被赋值和拷贝,但是被拷贝的对象的状态会被转移,即被拷贝的对象变成了空对象。
-
右值引用可以用于实现移动语义(move),即将一个对象的资源所有权从一个对象转移到另一个对象,避免了不必要的拷贝和销毁操作。
-
右值引用可以用于实现完美转发(forward),即将一个函数的参数转发给另一个函数,使得被转发的参数类型和值都能保持不变。
-
右值引用可以用于优化代码,例如在返回临时对象时,可以使用右值引用来避免不必要的拷贝操作,提高程序的效率。
作用:
-
实现移动语义:通过右值引用可以实现移动语义,避免了不必要的拷贝和销毁操作,提高程序的效率。
-
实现完美转发:通过右值引用可以实现完美转发,避免了参数类型和值的改变,提高程序的灵活性和可扩展性。
-
优化代码:通过右值引用可以避免不必要的拷贝操作,提高程序的效率。
-
实现函数或运算符的重载:右值引用可以用于实现函数或运算符的重载,使代码更加灵活和可扩展。
move:
c++11添加了右值引用,却不能左值初始化右值引用,在一些特定的情况下免不了需要左值初始化右值引用(用左值调用移动构造),如果想要用左值初始化一个右值引用需要借助std::move() 函数。move() 函数可以将左值转换为右值。就像我上面演示的那样。
forward:
右值引用类型是独立于值的,一个右值引用作为函数参数的形参时,在函数内部转发该参数给内部其他函数时,他就变成了一个左值(当右值被命名是编译器会认为他是个左值),并不是原来的类型了。如果按照参数原来的类型转发到另一个函数,可以使用c++11的 std::forward()函数,该函数实现的功能称之为完美转发。
#include <iostream>
using namespace std;
template<typename T>
void printValue(T& t)
{
cout << "左值引用: " << t << endl;
}
template<typename T>
void printValue(T&& t)
{
cout << "右值引用:" << t << endl;
}
template<typename T>
void testForward(T&& v)
{
printValue(forward<T>(v));
cout << endl;
}
int main()
{
testForward(520);//右值类型,转化为右值引用
int num = 1314;
testForward(num);//左值引用,转化为左值
testForward(forward<int>(num));//forward将num转化为右值,再转发为右值
testForward(forward<int&>(num));//forward将num转化为左值引用,再转发为左值
testForward(forward<int&&>(num)); // forward将num转化为右值,再转发为右值
return 0;
}
可以看到运行结果:
此时这个testForward函数被称为完美转发函数。