左值引用、右值引用

● 请你回答一下C++类内可以定义引用数据成员吗?
可以,必须通过成员函数初始化列表初始化。
常量和引用初始化必须赋值。所以上面的构造函数的写法只是简单的赋值,并不是初始化。
采用初始化列表实现了对常量和引用的初始化。采用括号赋值的方法,括号赋值只能用在变量的初始化而不能用在定义之后的赋值。

凡是有引用类型的成员变量或者常量类型的变量的类,不能有缺省构造函数。默认构造函数没有对引用成员提供默认的初始化机制,也因此造成引用未初始化的编译错误。并且必须使用初始化列表进行初始化const对象、引用对象。

#include <iostream>
using std::cout;
using std::endl;
class A{
public:
    A(int &b):a(b){}
    int &a;
};
int main()
{
    int b = 10;
    A a(b);
    cout << a.a <<endl;
    return 0;
}

● 请你回答一下什么是右值引用,跟左值又有什么区别?
右值引用是C++11中引入的新特性 , 它实现了转移语义和精确传递。它的主要目的有两个方面:

  1. 消除两个对象交互时不必要的对象拷贝,节省运算存储资源,提高效率。
  2. 能够更简洁明确地定义泛型函数。
    左值和右值的概念:
    左值:能对表达式取地址、或具名对象/变量。一般指表达式结束后依然存在的持久对象。
    右值:不能对表达式取地址,或匿名对象。一般指表达式结束就不再存在的临时对象。
    右值引用和左值引用的区别:
  3. 左值可以寻址,而右值不可以。
  4. 左值可以被赋值,右值不可以被赋值,可以用来给左值赋值。
  5. 左值可变,右值不可变(仅对基础类型适用,用户自定义类型右值引用可以通过成员函数改变)。
    扩展:我们平时经常使用的引用就是左值引用,通过 & 获取左值引用。为了支持移动操作,新标准引入 右值引用 (rvalue reference)。顾名思义,右值引用就是将引用绑定到右值。可以通过 && 来获取右值引用比如:
int i = 42; 
int &&r = i * 2;  // 将 i * 2 的结果绑定到 r 上
//要注意只能将右值绑定到右值引用上,并且左值引用不能绑定右值,以下几种用法都是错误的:
int i = 42;
int &&rr = i;     // 错误,i 是左值
int &r = i * 2;   // 错误,i * 2 是右值

//但是右值可以绑定到 const 的左值引用上:
int i = 42;
const int &r = i * 42;  // 正确,可以将 const 引用绑定到右值上

以下两种情况下,可以使用右值引用:

  • 所引用的对象将要被销毁时
  • 该对象没有其他使用者
    并且所有的变量都是左值(包括右值引用类型的变量),因此不能将右值引用直接绑定到一个变量上。

虽然不能将一个右值引用绑定到左值上,但我们可以显式地将一个左值转换为对应的右值引用类型。这就是 move 的作用,move 定义在头文件 utility 中。可以用如下方式使用 move(注意,为了避免潜在的名字冲突,使用 move 时不应使用 using 声明):

int&& rr1 = 42;  // 虽然 rr1 是右值引用类型,但其任然是变量,所以还是左值
int&& rr2 = std::move(rr1);  // 使用 std::move() 而不是 move()

调用 move 就相当于告诉编译器:我们有一个左值,但现在要像右值一样处理这个左值,并且调用 move 就意味着:除了对 rr1 赋值或销毁意外,我们将不再使用它。再调用 move 后,我们不能再对 rr1 的值做任何假设。也就是说,对于 rr1,我们可以赋予它新值,也可以销毁它,但是不能再使用其值了。

string gethello(){
    return {"hello,world"};
}
const char*getc_str(const std::string&str){
    return str.c_str();
}
int main(){
    int i;
    int *o;
    int &i1=i;//
    //c++中具有地址的值叫做左值,可以通过取地址的方式将地址取出来。
    //右值是不具有地址的,
    cout<<"hello,world!"<<endl; //字面值常量:无地址。
    cout<<gethello()<<endl; //函数返回值,返回左值引用除外
    cout<<string("hello world")<<endl;  //构造无名对象。
    //对于右值,这些类型都是临时的,即将消亡的,生命周期很短,可以成为将亡值;
    //同时没有地址,不可以被取地址,更不可以被左值引用,右值还有一个特点就是无法修改。
    T=int;
    T1=T&;
    T2=T&&;
    T3=T1&
    T4=T1&
    T5=T2&;
}

为什么需要右值引用

一个典型的例子就是copy构造,我们发生深拷贝一个对象开销不小,但是如果是右值的传入,我们也许不需要重新开辟空间和copy数据,反正都要将亡,那么我们直接占用就好了, 但是很可惜我们无法修改const引用,所有c++添加了右值引用规则。
对比左值的copy这样优化了不少。

一个类,如果我们需要 拷贝和复制,那么我们需要写出如下版本的函数。
如果我们不需要,使用 delete 阻止编译器给我们添加默认的版本。
buffer&operator=(buffer const&other)=delete;
1.拷贝构造函数 buffer(const buffer & other)
2.移动构造函数 buffer(buffer && other)
3.拷贝赋值函数 buffer & operator=(const buffer&other)
4.移动拷贝赋值函数 buffer & operator=(buffer&&other)

但是明显会有代码的重复,
使用 swap&copy 模式可以减少一个函数的代码,我们声明了一个swap函数,就是为了交换两个buffer

//为什么需要右值引用。
class buffer{
public:
    explicit buffer(int capacity):capacity(capacity),len(0),buf(new unsigned char[capacity]{0}){}
    //explicit指明了这个构造函数智能被显示调用,不能被隐式调用。
    ~buffer(){
        delete[]buf;
    }
    buffer(const buffer&other){
        cout<<"拷贝构造\n";
        this->capacity=other.capacity;
        this->len=other.len;
        this->buf=new unsigned char[capacity]{};

        copy(other.buf,last:other.buf+other.capacity,this->buf);        
    }
    //右值引用的构造器。移动构造器。
    //对于一些将亡值,本来就是要被销毁的,何必重新赋值一份呢,
    buffer(buff&&other){
        cout<<"move拷贝构造\n";
        this->capacity=other.capacity;
        this->len=other.len;
        this->buf=other.buf;
        other.buf=nullptr;
    }
    buffer&operator=(buffer const&other)=delete;
private:
    int capacity;
    int len;
    unsigned char*buf;
};
void dothing(buffer b){
    //值传递,会调用拷贝构造函数,
    //开销很大
}

void dothing(buffer&b){
    //传引用。只传入地址,不会调用拷贝构造函数。
    //左值引用不能引用右值。
}
void dothing(buffer&&b){
}
//用const修饰引用,也是可以进行右值引用。
//右值不能被修改。
void dothing(const buffer&b){
}
int main(){
    buffer buf(capacity:5);
    dothing(buf);
    //右值
    dothing(b:buffer(capacity:10));
}

值传递,会调用拷贝构造函数
传引用。只传入地址,不会调用拷贝构造函数。

class buffer {
public:
    explicit buffer(int capacity):capacity(capacity),len(0),buf(new unsigned char[capacity]{0}){}
    ~buffer(){
        delete[]buf;
    }
    //拷贝构造
    buffer(buffer&buffer):
        capacity(buffer.capacity),len(buffer.len),
        buf(capacity?new unsigned char[capacity]{0}:nullptr)
    {
        if(capacity){
            copy(buffer.buf,buffer.buf+buffer.capacity,this->buf);
        }
        cout<<"调用拷贝构造函数"<<*this<<endl;
    }
    //拷贝赋值函数
    buffer&operator=(buffer other)noexcept
    {
        cout<<"调用拷贝赋值函数"<<other<<endl;
        Swap(*this,other);
        return *this;
    }
//传入的本来就是一个将亡值,
buffer(buffer&&buffer):noexcept:capacity(0),len(0),buf(nullptr)
{
    Swap(*this,buffer);
    cout<<"调用移动构造器"<<*this<<endl;
}
//静态成员函数
static void Swap(buffer& lhs,buffer&rhs)noexcept
{
    swap(lhs.buf,rhs.buf);
    swap(lhs.capacity,rhs.capacity);
    swap(lhs.len,rhs.len);
}
private:
    int capacity;
    int len;
    unsigned char*buf;
};


  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值