1.为了支持移动操作,新标准引入
新的引用类型----
右值引用.
2.右值引用:绑定到右值的引用,通过&&获得右值引用.
3.右值引用重要性质:只能绑定到一个将要销毁的对象.
4.我们不能将左值引用(即常规引用)绑定到要求转换的表达式,字面常量或是返回右值的表达式.
5.我们可以将一个右值引用绑定到这类表达式(第4条所说的表达式)上,但不能将一个右值引用直接绑定到一个左值上.
int i = 42;//i是左值,赋值是返回左值表达式的例子
int &r = i;//r引用i
int &&rr = i;//错误:不能将一个右值引用绑定到一个左值上
int &r2 = i*42;//错误:i*42是一个右值,不能将一个右值直接绑定在一个左值上
const int &r3 = i*42;//正确:我们可以将一个const的引用绑定到一个右值上
int &&rr2 = i*42;//正确:将rr2绑定到乘法结果上,可以将一个右值引用绑定到返回右值的表达式上.
返回左值引用的函数
,连同赋值,下标,解引用和前置递增/递减运算符,都是返回左值表达式的例子.我们可以将左值绑定到这类表达式的结果上.
返回非引用类型的函数
,连同算术,关系,位以及后置递增/递减运算符,都生成右值.我们不能将一个左值引用绑定到这类表达式上,但我们可以将一个const的左值引用或将一个右值引用绑定到这类表达式上.
左值持久;右值短暂
由于右值引用只能绑定到临时对象,我们得知:
-
所引用的对象将要被销毁
-
该对象没有其他用户
变量是左值
变量表达式都是左值,我们不能将一个右值引用绑定到一个右值引用类型的变量上。
int &&rr1=42;//正确:字面常量是右值
int &&rr2 =rr1;//错误:表达式rr1是左值,不能将一个右值引用绑定到一个右值引用类型的变量上
标准库move函数
我们虽不能将一个右值引用直接绑定到一个左值上,但我们可以显式地将一个左值转换为对应的右值引用类型。
调用move的新标准库函数来获得绑定到左值上的右值引用,move函数定义在utility头文件中。
注意:我们可以销毁一个移后源对象,也可以赋予它新值,但不能使用一个移后源对象的值。
//调用了move函数,意味着除了对rr1赋值或销毁它外,我们将不再使用它。
int &&rr3 = std::move(rr1);//正确
【练习13.45】解释
右值引用和左值引用的区别
所谓右值引用就是必须绑定到右值的引用,通过&&获得。右值引用只能绑定到一个将要销毁的对象上,因此可以自由的移动其资源。
左值引用,也就是”常规引用“,不能绑定到要转换的表达式、字面常量或返回右值的表达式。而右值引用恰好相反,可以绑定到这类表达式,但不能绑定到一个左值上。
返回左值的表达式包括
返回左值引用的函数及赋值、下标、解引用和前置递增/递减运算符,返回右值的包括
返回非引用类型的函数及算术、关系、位和后置递增/递减运算符。可以看到,左值的特点是持久的状态,而右值则是短暂的。
【练习13.46】什么类型的引用可以绑定到下面的初始化器上?
【出题思路
】理解左值引用和右值引用
int f();//f是返回非引用类型的函数,返回值是一个右值
vector<int>vi(100);
int &&r1 = f();//f返回非引用类型的函数都生成右值,我们不能将一个左值引用绑定到这类表达式上。
int &r2 =vi[0];//vi[0]下标返回的是左值表达式
int &r3 = r1;//r1是一个变量,而变量是左值,
int &&r4 =vi[0]*f();//算术运算表达式返回的是右值
【练习13.47】对你在练习13.44(13.5节,第470页)中定义的String类,为它的拷贝构造函数和拷贝赋值运算符添加一条语句,在每次函数执行时打印一条信息。
【练习13.48】定义一个vector<String>并在其上多次调用push_back。运行你的程序,并观察String被拷贝了多少次。
【出题思路
】理解拷贝何时发生
//String.h文件
/*
* 练习13.44:编写标准库string类的简化版本,命名为String。
你的类应该至少有一个默认构造函数和一个接受C风格字符串指针参数的构造函数。
使用allocator为你的String类分配所需内存。
*/
#pragma once
#include<iostream>
#include<memory>
#include<string>
using namespace std;
class String
{
friend String operator+(const String&, const String&);
//输出<<运算符
friend ostream& operator<<(ostream& os, const String&);
friend istream& operator>>(istream& in, String&);
public:
//构造函数,成员进行初始化
String() :elements(nullptr), first_free(nullptr), cap(nullptr) {}
//接受一个initializer_list<char>参数的构造函数
String(initializer_list<char>);
//接受C风格字符串指针参数的构造函数
String(const char*);
//拷贝构造函数
String(const String&);
//拷贝赋值运算符
String& operator=(const String&);
//析构函数
~String();
//添加元素
void push_back(const char&);
//返回数组元素个数
size_t size()const { return first_free - elements; }
//返回数组最多可容纳元素的个数
size_t capacity()const { return cap - elements; }
//分配至少能容纳n个元素的内存空间
void reserve(const size_t& n);
//修改resize
//void resize(const size_t& n);
void resize(const size_t& n, const char& c = '\0');
//返回指向数组首元素的指针
char* begin() const { return elements; }
//返回指向数组尾后元素的指针
char* end()const { return first_free; }
//打印所有string
void printAll()const;
private:
allocator<char> chars;
char* elements; //指向数组首元素的指针
char* first_free; //指向数组第一个空闲元素(最后一个构造对象的元素之后的位置)的指针//
char* cap;//指向数组尾后位置的指针
void free(); //销毁元素并释放内存
void reallocate(const size_t n = 0); //获得更多的内存并拷贝已有元素
//添加元素时检查数组空间是否够用并进行扩容
void chk_n_alloc()
{
if (size() == capacity())
reallocate();
}
//工具函数,被拷贝构造函数、赋值运算符和析构函数所使用
pair<char*, char*> alloc_n_copy(const char*, const char*);
};
String operator+(const String&, const String&);
ostream& operator<<(ostream& os, const String&);
istream& operator>>(istream& in, String&);
//String.cpp
#include "String.h"
#include<algorithm>
/*********************public成员函数:**************************/
//接受一个initializer_list<string>参数的构造函数
String::String(initializer_list<char> il)
{
auto newset = alloc_n_copy(il.begin(), il.end());
this->elements = newset.first;
this->first_free = newset.second;
this->cap = newset.second;
}
//接受C风格字符串指针参数的构造函数
String::String(const char* cp)
{
cout << "in String::String(const char* cp)" << endl;
auto len = strlen(cp);
elements = first_free = chars.allocate(len);
first_free = uninitialized_copy(cp, cp + len, first_free);
cap = elements + len;
}
//拷贝构造函数
String::String(const String& c)
{
cout << "in String::String(const String &c) " << endl;
auto newset = alloc_n_copy(c.begin(), c.end());
this->elements = newset.first;
this->first_free = newset.second;
this->cap = newset.second;
}
//拷贝赋值运算符
String& String::operator=(const String& c)
{
cout << "in operator=" << endl;
auto newset = alloc_n_copy(c.begin(), c.end());//拷贝一份,可以处理自赋值
this->free();
this->elements = newset.first;
this->first_free = newset.second;
this->cap = newset.second;
return *this;
}
//析构函数
String::~String()
{
this->free();
}
//添加元素
void String::push_back(const char& c)
{
chk_n_alloc();//确保有空间容纳新元素
chars.construct(first_free++, c);//向第一个空闲内存添加元素,fires_free向后移动一个元素
}
//分配至少能容纳n个元素的内存空间
void String::reserve(const size_t& n)
{
if (n <= capacity())//如果请求的n个内存空间小于等于当前分配的内存空间
{
//不做处理
}
else
{
//重新分配内存空间
reallocate(n);
}
}
//修改resize
void String::resize(const size_t& n, const char& s)
{
//cout << "in resize()" << endl;
if (n < this->size())
{
//cout << *elements << endl;
//cout << "int resize() --> in if(n<this->size())" << endl;
//如果请求大小小于当前元素个数,将多余的元素删除并释放空闲内存空间
auto new_first_free = elements + n;//执行我们预期的最后一个有效元素的后一个位置
while (first_free != new_first_free)
{
//cout << "执行while (first_free!=new_first_free)函数体一次" << endl;
chars.destroy(--first_free);//从后向前逐个销毁多余的元素
}
}
else if (n == this->size())
{
//cout << "int resize() --> in else if (n == this->size())" << endl;
//不做处理
}
else if (n > this->size())
{
//cout << "int resize() --> in else if (n > this->size())" << endl;
if (n <= capacity())//如果n<=最大容量大小
{
//使用默认string填充的空闲内存直到有n个元素
while (size() != n)
{
chars.construct(first_free++, s);
}
}
else if (n > capacity())//如果请求元素数量大于当前容量,先扩容到n个容量再填充
{
reserve(n);//扩容到capacity=n
//使用默认string填充的空闲内存直到有n个元素
while (size() != n)
{
chars.construct(first_free++, s);
}
}
}
}
//打印所有char
void String::printAll()const
{
auto beg = begin();
for (auto beg = begin(); beg != end(); ++beg)
cout << *beg;
cout << endl;
}
/************************private成员函数:********************************/
/*
void String::free() //销毁元素并释放内存
{
if (elements)//如果allocator数组不为空
{
auto n = cap - elements;//记住原数组的capacity()
for_each(elements, first_free, [this](char& c) {chars.destroy(&c); });
chars.deallocate(elements, n); //释放内存
}
}
*/
void String::free() //销毁元素并释放内存
{
if (elements)//如果allocator数组不为空
{
auto n = cap - elements;//记住原数组的capacity()
for_each(elements, first_free, [this](char& c) {chars.destroy(&c); });
chars.deallocate(elements, n); //释放内存
elements = first_free = cap = nullptr;//新增
}
}
void String::reallocate(const size_t n) //获得更多的内存并拷贝已有元素
{
char* newelements = nullptr;
size_t newcapacity = 0;
if (n == 0) //用于push_back(s)时的扩容规则
{
newcapacity = this->size() ? (size() * 2) : 1;//如果数组元素个数为0,capacity设置为1,否则扩容为原来的2倍
newelements = this->chars.allocate(newcapacity);//分配新内存
}
else //用于reserve(n)的扩容规则
{
newcapacity = n;
newelements = this->chars.allocate(n);//分配新内存
}
//将数据从旧内存移动到新内存
auto newdest = newelements; //将一直指向新数组中的第一个空闲位置,
auto oldelem = elements; //将指向旧数组中下一个元素位置
for (size_t i = 0; i != this->size(); ++i)
chars.construct(newdest++, std::move(*oldelem++));//std::remove表示希望使用string的移动构造函数,而不是拷贝
//更新数据结果,执行新元素
free();//移动完毕,释放旧内存空间
this->elements = newelements;
this->first_free = newdest;
this->cap = elements + newcapacity;
}
//工具函数,被拷贝构造函数、赋值运算符和析构函数所使用
pair<char*, char*> String::alloc_n_copy(const char* b, const char* e)
{
//cout << "in alloc_n_copy(const char* b, const char* e)" << endl;
//分配空间保存给定范围中的元素
auto data = chars.allocate(e - b);//data即elements*
//初始化并返回一个pair,该pair由data和unintialized_copy的返回值构造
return { data,uninitialized_copy(b,e,data) };//unintialized_copy的返回值=指向第一个空闲元素的位置
}
//类外///
String operator+(const String& s1, const String& s2)
{
auto len = s1.size() + s2.size();
String s;
s.elements = s.first_free = s.chars.allocate(len);//分配内存空间
//拷贝s1
s.first_free = uninitialized_copy(s1.begin(), s1.end(), s.first_free);
//拷贝s2
s.first_free = uninitialized_copy(s2.begin(), s2.end(), s.first_free);
s.cap = s.elements + len;
return s;
}
//输出<<运算符
ostream& operator<<(ostream& os, const String& s)
{
os.clear();
auto beg = s.begin();
for (auto beg = s.begin(); beg != s.end(); ++beg)
os << *beg;
return os;
}
//输入>>运算符
istream& operator>>(istream& in, String& s)
{
s.free();
char c;
while (in.get(c))
{
if (c == 9 || c == 10 || c == 11 || c == 12 || c == 13 || c == 32)
{
break;
}
s.push_back(c);
}
return in;
}
//main.cpp
#include<iostream>
#include<fstream>
#include<vector>
using namespace std;
#include"String.h"
int main(int argc, char** argv)
{
/*=============【练习13.47】对你在练习13.44(13.5节,第470页)中定义的String类,为它的拷贝构造函数和拷贝赋值运算符添加一条语句,在每次函数执行时打印一条信息。===========*/
#if 0
/**********理解拷贝构造函数和拷贝赋值运算符何时调用***********/
//使用operator>>
cout << "测试operator>>; 请输入String s以空格或回车符结束:" << endl;
String s;
cin >> s;
//使用operator<<
cout << "测试operator<<:" << endl;
cout << "s= " << s << endl;
//测试接受一个const char*参数的构造函数//
cout << "测试接受一个const char*参数的构造函数" << endl;
String s2 = "hello world";
cout << "s2=" << s2 << endl;
//测试operator=///
cout << "测试operator= s3=s2:" << endl;
String s3;
s3 = s2;
cout << "s3=" << s3 << endl;
//测试接受一个initializer_list<c>参数的构造函数//
cout << "测试接受一个initializer_list<c>参数的构造函数" << endl;
String s4 = { 'q','a','z' };
cout << "s4= " << s4 << endl;;
//测试operator+
cout << "测试operator+: s5=s4+\" \"s3" << endl;
String s5;
s5 = s4 + " " + s3;
cout << s5 << endl;
//测试reserve///
cout << endl << "//测试reserve///" << endl;
cout << "当前s的capacity大小为:" << s.capacity() << endl;
s.reserve(1);//修改容器小于当前容量
cout << "s.reserve(1);当前s的capacity大小为:" << s.capacity() << endl;
s.reserve(20);//修改容器大于当前容量
cout << "s.reserve(20);当前s的capacity大小为:" << s.capacity() << endl;
return 0;
#endif
/*===【练习13.48】定义一个vector<String>并在其上多次调用push_back。运行你的程序,并观察String被拷贝了多少次。===*/
#if 0
/**********理解拷贝何时发生*************/
vector<String> vStr;
String S;
while (cin >> S)
{
vStr.push_back(S);
}
cout << "打印输出:" << endl;
for (const auto& s : vStr)
cout << s << " ";
cout << endl;
return 0;
#endif
/**********理解拷贝何时发生*************/
String s1("One"), s2("Two");
cout << s1 << " " << s2 << endl << endl;
String s3(s2);
cout << s1 << " " << s2 << " " << s3 << endl << endl;
s3 = s1;
cout << s1 << " " << s2 << " " << s3 << endl << endl;
s3 = String("Three");
cout << s1 << " " << s2 << " " << s3 << endl << endl;
vector<String>vs;
//vs.reserve(4);
vs.push_back(s1);
vs.push_back(std::move(s2));
vs.push_back(String("Three"));
vs.push_back(String("Four"));
for_each(vs.begin(), vs.end(), [](const String& s) {cout << s << " "; });
cout << endl;
return 0;
}