【C++ Primer】 第十三章 拷贝控制_13.6 对象移动_13.6.1 右值引用

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;
}
  • 6
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

冷凝女子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值