● 请你回答一下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中引入的新特性 , 它实现了转移语义和精确传递。它的主要目的有两个方面:
- 消除两个对象交互时不必要的对象拷贝,节省运算存储资源,提高效率。
- 能够更简洁明确地定义泛型函数。
左值和右值的概念:
左值:能对表达式取地址、或具名对象/变量。一般指表达式结束后依然存在的持久对象。
右值:不能对表达式取地址,或匿名对象。一般指表达式结束就不再存在的临时对象。
右值引用和左值引用的区别: - 左值可以寻址,而右值不可以。
- 左值可以被赋值,右值不可以被赋值,可以用来给左值赋值。
- 左值可变,右值不可变(仅对基础类型适用,用户自定义类型右值引用可以通过成员函数改变)。
扩展:我们平时经常使用的引用就是左值引用,通过 & 获取左值引用。为了支持移动操作,新标准引入 右值引用 (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© 模式可以减少一个函数的代码,我们声明了一个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;
};