我与c++的爱恋:c++11


外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

🔥个人主页guoguoqiang. 🔥专栏我与C++的爱恋

Alt

​1.右值引用

1.左值引用和右值引用
传统的C++语法中就有引用的语法,而C++11中新增了的右值引用语法特性,所以从现在开始我们之前学习的引用就叫做左值引用。无论左值引用还是右值引用,都是给对象取别名。

什么是左值?什么是左值引用?

左值是一个表示数据的表达式(如变量名或解引用的指针),我们可以获取它的地址+可以对它赋值,左值可以出现赋值符号的左边,右值不能出现在赋值符号左边。定义时const修饰符后的左值,不能给他赋值,但是可以取它的地址。左值引用就是给左值的引用,给左值取别名。

什么是右值?什么是右值引用?

右值也是一个表示数据的表达式,如:字面常量、表达式返回值,函数返回值(这个不能是左值引用返回)等等,右值可以出现在赋值符号的右边,但是不能出现出现在赋值符号的左边,右值不能取地址。右值引用就是对右值的引用,给右值取别名。

int main()
{
    // 以下的p、b、c、*p都是左值
    // 左值:可以取地址
    int* p = new int(0);
    int b = 1;
    const int c = b;
    *p = 10;
    string s("111111");
    s[0];
    cout << &c << endl;
    cout << &s[0] << endl;
 
//    // 右值:不能取地址
//    double x = 1.1, y = 2.2;
//    // 以下几个都是常见的右值,常量临时对象,匿名对象
//    10;
//    x + y;
//    fmin(x, y);
//    string("11111");
//
//    cout << &10 << endl;
//    cout << &(x+y) << endl;
//    cout << &(fmin(x, y)) << endl;
//    cout << &string("11111") << endl;
 
    return 0;
}
int main()
{
    double x = 1.1, y = 2.2;
    int&& rr1 = 10;
    const double&& rr2 = x + y;
    
    rr1 = 20;
    rr2 = 5.5; // 报错  rr2不能被修改
    
    return 0;
}

无论是左值引用还是右值引用,在字面上不占空间,在汇编、底层上是开空间的

2.左值引用与右值引用

左值引用总结:
左值引用只能引用左值,不能引用右值。

但是const左值引用既可引用左值,也可引用右值

int main(){
    // 左值引用只能引用左值,不能引用右值
    int a = 10;
    int& ra1 = a;// ra为a的别名
    //int& ra2 = 10; 编译失败,因为10是右值
    
    // const左值引用既可引用左值,也可引用右值。
    const int& ra3 = 10;
    const int& ra4 = a;
    return 0;
}

右值引用总结:

右值引用只能右值,不能引用左值。

但是右值引用可以move以后的左值。

move的本质是强制类型转换

int main()
{
    // 右值引用只能右值,不能引用左值。
    int&& r1 = 10;
    int a = 10;
//    int&& r2 = a;
    
    // 右值引用可以引用move以后的左值
    int&& r3 = std::move(a);
    
    
    string s("111111");//s是左值 “111111”是右值
    string&& rrx4 = move(s);//左值move后给右值引用
    // move的本质是强制类型转换
    string&& rrx5 = (string&&)s;//与上句等同
    return 0;
}

3.右值引用使用场景和意义

1.做返回值

//构造函数
string(const char* str = "")
        :_size(strlen(str))
        , _capacity(_size)
    {
        cout << "string(char* str)" << endl;
        _str = new char[_capacity + 1];
        strcpy(_str, str);
    }
// 赋值重载
    string& operator=(const string& s)
    {
        cout << "string& operator=(const string& s) -- 深拷贝" << endl;
        if (this != &s)
        {
            _str[0] = '\0';
            _size = 0;
 
            reserve(s._capacity);//开空间,深拷贝
            for (auto ch : s)
            {
                push_back(ch);
            }
        }
 
        return *this;
    }
 
gwq::string to_string(int value)
{
    bool flag = true;
    if (value < 0)
    {
        flag = false;
        value = 0 - value;
    }
    gwq::string str;//临时对象
 
    while (value > 0)
    {
        int x = value % 10;
        value /= 10;
        str += ('0' + x);
    }
 
    if (flag == false)
    {
        str += '-';
    }
 
    std::reverse(str.begin(), str.end());
 
    return str;
}
}
 
int main()
{
    gwq::string s1;
    s1 = bit::to_string(1234);
 
    return 0;
}
 

对于返回值,编译器会生成临时对象,再对临时对象进行拷贝构造,对于临时对象进行拷贝构造这不就浪费了吗?为了更进一步优化,C++11新引入移动构造,以及移动赋值

    // 移动构造
    // 临时创建的对象,不能取地址,用完就要消亡
    // 深拷贝的类,移动构造才有意义
    string(string&& s)
    {
        cout << "string(string&& s) -- 移动拷贝" << endl;
        swap(s);
    }
    // 移动赋值
    string& operator=(string&& s)
    {
        cout << "string& operator=(string&& s) -- 移动拷贝" << endl;
 
        swap(s);//转移资源
        return *this;
    }

深拷贝的类走移动构造和移动赋值效率大大提升。
右值引用的属性还是左值。
为什么呢? 只有右值引用本身的属性是左值才能转移他的资源。

注意:由于move以后本身的属性还是左值,所以只要使用到右值,我们都需要写一个右值版本,少一个都不行,比如这里的节点构造、push_back、insert

4.完美转发

模板中的&& 万能引用(引用折叠)

//函数模版中
template <class T>
void Func(T&& x)
{
    
}

对于void Func(T&& x)中的T&& x,如果我们进行传值

如果传的是一个右值类型,那么T&& x就是一个右值引用

如果传的是左值类型,那么T&& x就是一个左值引用

使用关键字forward可以保留原先的数据属性,传什么,就保留什么属性,本质上也是类型转换。
在这里插入图片描述

5.新的类功能

1.默认成员函数

原来C++类中,有6个默认成员函数:

构造函数

析构函数

拷贝构造函数

拷贝赋值重载

取地址重载

const 取地址重载

C++11 新增了两个:移动构造函数和移动赋值运算符重载。

针对移动构造函数和移动赋值运算符重载有一些需要注意的点如下:

如果你没有自己实现移动构造函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任意一个。那么编译器会自动生成一个默认移动构造。默认生成的移动构造函数,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动构造,如果实现了就调用移动构造,没有实现就调用拷贝构造。

如果你没有自己实现移动赋值重载函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任意一个,那么编译器会自动生成一个默认移动赋值。默认生成的移动赋值,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动赋值,如果实现了就调用移动赋值,没有实现就调用拷贝赋值。(默认移动赋值跟上面移动构造完全类似)

如果你提供了移动构造或者移动赋值,编译器不会自动提供拷贝构造和拷贝赋值。
注:一个函数如果需要显示写析构函数,说明有资源需要释放

说明需要显示写拷贝构造和赋值重载

说明需要显示写移动构造和移动赋值

自动生成的移动构造,对于Date这样的类,其实没有什么意义,因为它和拷贝构造的功能是一样的

自动生成的移动构造,对于Person这样的类是很有意义的,因为Person是右值时,他内部的string也是右值,string就可以走移动构造,提高效率

2.类成员变量初始化

C++11允许在类定义时给成员变量初始缺省值,默认生成构造函数会使用这些缺省值初始化,这个我们在类和对象默认就讲了,这里就不再细讲了。

3.强制生成默认函数的关键字default:

C++11可以让你更好的控制要使用的默认函数。假设你要使用某个默认的函数,但是因为一些原因这个函数没有默认生成。比如:我们提供了拷贝构造,就不会生成移动构造了,那么我们可以使用default关键字显示指定移动构造生成。

    Person(Person&& p) = default;
    Person& operator=(Person && p) = default;
    Person(const Person& p) = default;
    Person& operator=(const Person& p) = default;

4.禁止生成默认函数的关键字delete:

如果能想要限制某些默认函数的生成,在C++98中,是该函数设置成private,并且只声明补丁已,这样只要其他人想要调用就会报错。在C++11中更简单,只需在该函数声明加上=delete即可,该语法指示编译器不生成对应函数的默认版本,称=delete修饰的函数为删除函数。

class Person
{
public:
    Person(const char* name = "111111111111", int age = 0)
        :_name(name)
        , _age(age)
    {}
 
    // 只声明不实现,声明为私有
    // C++98的做法
//private:
//    Person(const Person& p);
//    Person& operator=(const Person & p);
 
    
    //C++11的做法
    Person(const Person& p) = delete;
    Person& operator=(const Person& p) = delete;
private:
    bit::string _name;
    int _age;
};

5.继承和多态中的final与override关键字

final 用来修饰类 、函数或者虚函数表示不能再被继承或者重写。
override是检查虚函数是否重写基类中的虚函数(在派生类后)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值