7. C++11
范围for
使用格式
vector<int> v = {
1,2,3,4,5 };
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
底层原理,使用迭代器
vector<int> v = {
1,2,3,4,5 };
auto it = v.begin();
while (it != v.end())
{
cout << *it << " ";
++it;
}
cout << endl;
右值引用的移动语义
什么是左值,什么又是右值?
左值,可以取地址,可以对内容进行修改
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。如果不想rr1被修改,可以用const int&& rr1 去引用
把字面量存储在了栈上,后面使用时取地址就是栈空间上的地址,修改也是
这是一种特性
- 左值引用只能引用左值,不能引用右值
- 但是const左值引用既可引用左值,也可引用右值
- 右值引用只能右值,不能引用左值
- 但是右值引用可以move以后的左值
当需要用右值引用引用一个左值时,可以通过move函数将左值转化为右值,move本质是一个函数,唯一的功能就是将一个左值强制转化为右值引用,然后实现移动语义
右值引用的使用场景,如何提高效率
左值引用的使用场景:做参数和做返回值都可以提高效率。(对于自定义类型)
左值引用的短板:但是当函数返回对象是一个局部变量,出了函数作用域就不存在了,就不能使用左值引用返回, 只能传值返回
例如: string operator+(const string& str)
传值返回就会导致,两次拷贝构造,在编译器优化后可能会优化掉第一次的拷贝构造,但是返回值做为参数的拷贝构造是少不了的
这个返回值是一个临时对象,也就是右值,将亡值,这个临时对象在拷贝之后也是要析构的,可不可以利用一下这个资源呢?
通过右值引用和移动语义解决上述问题
移动构造和移动赋值
在bit::string中增加移动构造,移动构造本质是将参数右值的资源窃取过来,占位已有,那么就不用做深拷贝了,所以它叫做移动构造,就是窃取别人的资源来构造自己。
不仅仅有移动构造,还有移动赋值
namespace kele
{
class string
{
public:
string(const char* str = "")//构造
:_size(strlen(str))
{
cout << "构造" << endl;
_capacity = _size == 0 ? 3 : _size;
_str = new char[_capacity + 1];
strcpy(_str, str);
}
string(const string& str)//拷贝构造
:_str(nullptr)
{
cout << "string(const string & str)//拷贝构造" << endl;
string tmp(str._str);
swap(tmp);
}
string(string&& str)//移动构造
:_str(nullptr),
_size(0),
_capacity(0)
{
cout << "string(string&& str)//移动构造" << endl;
swap(str);
}
string& operator=(string& str)//赋值重载
{
if (this != &str)
{
cout << "string& operator=(string str)//赋值重载" << endl;
string tmp(str._str);
swap(tmp);
return *this;
}
}
string& operator=(string&& str)//移动赋值重载
{
if (this != &str)
{
cout << "string& operator=(string&& str)//移动赋值重载" << endl;
swap(str);
return *this;
}
}
void reserve(size_t n = 0)
{
if (n > _capacity)
{
char* tmp = new char[n + 1];
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
_capacity = n;
}
}
string& operator+=(const string& s)
{
size_t n = s._size;
if (_size + n > _capacity)
{
reserve(_capacity + n);
}
strcpy(_str + _size, s._str);
_size += n;
return *this;
}
string operator+(const string& str)
{
string tmp(_str);
tmp += str;
return tmp;
}
~string()
{
delete[] _str;
_size = _capacity = 0;
}
void swap(string& s)
{
std::swap(_str, s._str);
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}
private:
char* _str;
size_t _size;
size_t _capacity;
};
}
完美转发
模板中的&& 万能引用:
模板中的&&不代表右值引用,而是万能引用,其既能接收左值又能接收右值
模板的万能引用只是提供了能够接收同时接收左值引用和右值引用的能力
但是引用类型的唯一作用就是限制了接收的类型,后续使用中都退化成了左值
因为如果要进行移动构造需要对其内容做修改(必须是左值)
我们希望能够在传递过程中保持它的左值或者右值的属性, 就需要用我们下面学习的完美转发(forward)
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; }
template<typename T>
void PerfectForward(T&& t)
{
//Fun(t);
Fun(