【C++11】常见的c++11新特性(一)

06-18 1552阅读

文章目录

  • 1. C++11 简介
  • 2. 常见的c++11特性
  • 3.统一的列表初始化
    • 3.1initializer_list
    • 4. decltype与auto
      • 4.1decltype与auto的区别
      • 5.nullptr
      • 6.右值引用和移动语义
        • 6.1左值和右值
          • 6.1.1左值的特点
          • 6.1.2右值的特点
          • 6.1.3右值的进一步分类
          • 6.2左值引用和右值引用以及区别
            • 6.2.1左值引用
            • 6.2.2左值引用总结
            • 6.2.3右值引用
            • 6.2.4右值引用总结
            • 6.2.5move函数
            • 6.3右值引用的使用场景及其作用
              • 6.3.1移动语义
              • 6.3.2移动构造
              • 6.3.3移动赋值函数
              • 6.3.4右值本身也是一个左值
              • 6.3.5STL容器中的移动构造和移动赋值
              • 6.4完美转发
                • 6.4.1完美转发的介绍
                • 6.4.2forward函数的使用
                • 7.新的类功能
                  • 7.1c++11新增了两个默认成员函数
                  • 7.2delete和dault
                  • 7.3final和override关键字

                    1. C++11 简介

                    c++11,即2011年发布的新c++标准。相比于c++98和03,c++11带来了很多变化,其中包含了约140多个新特性,以及对c++03标准中大约600多个缺陷的修正。这使得c++11更像是一个从c++98/03孕育出来的一个新版本。对比于之前的版本,c++11能更好的用于系统开发和库开发、语法更加的泛华和简单化、更加稳定和安全,不仅功能更加强大,而且能提升程序员的开发效率。这也是我们学习c++11的重要原因。下面介绍常见的一些c++11特性。

                    2. 常见的c++11特性

                    • 列表初始化
                    • 变量类型推导
                      • auto
                      • decltype
                      • 范围for循环
                      • final与override
                      • 智能指针
                      • 新增加容器—静态数组array、forward_list以及unordered系列
                      • 默认成员函数控制
                      • 右值引用
                      • lambda表达式
                      • 包装器
                      • 线程库

                        3.统一的列表初始化

                        使用列表对数组或者结构体进行初始化在c++98就已经被支持使用了,例如:

                        struct Point
                        {
                         int _x;
                         int _y;
                        };
                        int main()
                        {
                         int array1[] = { 1, 2, 3, 4, 5 };
                         int array2[5] = { 0 };
                         Point p = { 1, 2 };
                         return 0;
                        }
                        

                        但是c++98的列表初始化局限于对数组和聚合类型的初始化,限制比较多,且不支持类型推导。c++11扩大了用{}(初始化列表)的使用范围,使得所有的内置类型和自定义类型都能以一种统一方式进行初始化,包括SL容器等。使用初始化列表时,可以添加等号也可以不添加,类似于构造声明。

                        struct Point
                        {
                         int _x;
                         int _y;
                        };
                        int main()
                        {
                         int x1 = 1;
                         int x2{ 2 };
                         int array1[]{ 1, 2, 3, 4, 5 };
                         int array2[5]{ 0 };
                         Point p{ 1, 2 };
                         // C++11中列表初始化也可以适用于new表达式中
                         int* pa = new int[4]{ 0 };
                         return 0;
                        }
                        

                        自定义类型能支持列表初始化的原因是因为,在其类体中重载了以初始化列表类模板为参数的构造函数。以至于我们能直接使用{}构造一个对象。

                        3.1initializer_list

                        initializer_list是一个C++11提供的一个轻量级容器,专门用来接收{}内的初始化列表。本质上是一个类模板,由于模板的特性,在构造initializer_list类时,会自动推导{}里的类型,从而完成对自定义类型的构造。这个容器其实在我之前的文章里有介绍过: initializer_list的介绍。

                        下面给出一个伪代码样例:

                        vector(initializer_list l)
                             {
                                 _start = new T[l.size()];
                                 _finish = _start + l.size();
                                 _endofstorage = _start + l.size();
                                 iterator vit = _start;
                                 typename initializer_list::iterator lit = l.begin();
                                 while (lit != l.end())
                                 {
                                     *vit++ = *lit++;
                                 }
                                 //for (auto e : l)
                                 //   *vit++ = e;
                             }
                        

                        上面自定义的vector类重载了以initializer_list模板为参数的一个构造函数,有了这个构造函数之后,就能使用{}的方式对自定义类型进行构造。分析下面代码构造过程:

                        vector a = { 1,2,3 };
                        
                        • 编辑器先是根据{}构造出一个initializer_list对象,然后再调用vector类的构造函数。

                          4. decltype与auto

                          decltype关键字是一个类型推导工具,可以将变量的类型声明为表达式指定的类型,比如:

                          【C++11】常见的c++11新特性(一)

                          decltype表达式的基本用法就是后面跟上一个括号,编译器会自动推导出括号里面表达式的类型,但不执行该表达式。

                          4.1decltype与auto的区别

                          auto也是一个c++11更新的一个特性,也能用来做类型的推导,但是和decltype有很多不一样的地方:

                          • auto 忽略表达式的引用性质,而是推导出一个干净的类型。
                          • decltype完全保留表达式的类型,包括const属性和引用属性。

                            思考以下代码:

                            【C++11】常见的c++11新特性(一)

                            我们发现,auto并没有推导出引用属性,所以变量b++之后并不会影响num,本质上b只是一个int类型。而decltype能推导出引用属性,所以变量c实际上是一个int&类型,自增之后会影响num。这样证明了decltype推导类型比auto更为准确。同样,const类型的推导也是如此:

                            【C++11】常见的c++11新特性(一)

                            5.nullptr

                            nullptr是c++11新出的一个专门用来表示空属性的一个关键字。关于nullptr与NULL的区别,我在之前的文章中有介绍过。这里就不过多介绍。

                            6.右值引用和移动语义

                            6.1左值和右值

                            左值(Lvalue)和右值(Rvalue)不单单是指某个变量的属性,而是可以用来描述表达式的属性。

                            • 左值的定义:左值是具有持久存储位置的表达式。换句话说,左值是指那些在内存中具有明确地址的对象,且这个地址持久不变。左值可以出现在赋值表达式的左侧或者右侧。最直观的理解就是左值能用&取出地址。
                            • 右值的定义: 右值和左值相反,是指不具有持久存储位置的表达式。 右值通常都是临时的,生命周期非常短,比如匿名对象等临时值。最直观的理解就是右值不能用&取出地址。

                              给出代码样例观察对左值和右值取地址:

                              【C++11】常见的c++11新特性(一)

                              我们可以看到,由于10是一个字面量即右值,不能对其取地址。编译器也提示&只能对左值取地址。

                              6.1.1左值的特点

                              • 可以用&取地址
                              • 通常表示对象的身份如变量名

                                6.1.2右值的特点

                                • 不能用&取出地址
                                • 只能在赋值表达式的右边
                                • 表示的是数据的值,而不是身份
                                • 通常是临时值、字面量等

                                  6.1.3右值的进一步分类

                                  右值又可以分为存右值和将亡值:

                                  • 纯右值:表示不对应任何对象的临时值,例如字面量或者表达式“1+2”这种。
                                  • 将亡值:表示即将被摧毁、可以被移动的对象。

                                    6.2左值引用和右值引用以及区别

                                    6.2.1左值引用

                                    左值引用使用一个&表示,通常只能绑定给左值。下面给出常见的左值引用左值的例子:

                                    int main()
                                    {
                                    // 以下的p、b、c、*p都是左值
                                    int* p = new int(0);
                                    int b = 1;
                                    const int c = 2;
                                    // 以下几个是对上面左值的左值引用
                                    int*& rp = p;
                                    int& rb = b;
                                    const int& rc = c;
                                    int& pvalue = *p;
                                    return 0;
                                    }
                                    

                                    6.2.2左值引用总结

                                    1. 左值引用只能引用左值,不能引用右值,比如

                                      【C++11】常见的c++11新特性(一)

                                    2. const左值引用可以引用右值

                                      【C++11】常见的c++11新特性(一)

                                    3. 不能延长临时对象的生命周期,可能会导致悬空引用。

                                    int& getTemporary() {
                                        int temp = 10;
                                        return temp; // 返回局部变量的引用
                                    }
                                    int main() {
                                        int& ref = getTemporary(); // 悬空引用
                                        std::cout 
                                    double x = 1.1, y = 2.2;
                                    // 以下几个都是常见的右值
                                    10;
                                    x + y;
                                    fmin(x, y);
                                    // 以下几个都是对右值的右值引用
                                    int&& rr1 = 10;
                                    double&& rr2 = x + y;
                                    double&& rr3 = fmin(x, y);
                                    // 这里编译会报错:error C2106: “=”: 左操作数必须为左值
                                    10 = 1;
                                    x + y = 1;
                                    fmin(x, y) = 1;
                                    return 0;
                                    }
                                    
                                     double x = 1.1, y = 2.2;
                                     int&& rr1 = 10;
                                     const double&& rr2 = x + y;
                                     rr1 = 20;
                                     rr2 = 5.5;  // 报错
                                     return 0;
                                    }
                                    
                                    public:
                                        size_t size;
                                        int* data;
                                        // 构造函数
                                        Buffer(size_t s=10) : size(s), data(new int[s]) {
                                            std::cout 
                                            std::memcpy(data, other.data, size * sizeof(int));
                                            std::cout 
                                            delete[] data;
                                            std::cout 
                                        // 对缓冲区进行处理
                                    }
                                    int main() {
                                        Buffer buf1(100);
                                        processBuffer(buf1); // 调用复制构造函数
                                        return 0;
                                    }
                                    
                                         other.size = 0;
                                         other.data = nullptr;
                                         std::cout 
                                        std::memcpy(data, other.data, size * sizeof(int));
                                        std::cout 
                                         if (this != &other) {
                                             delete[] data;//释放当前资源
                                             size = other.size;
                                             data = other.data;
                                             other.data = nullptr;
                                             other.size = 0;
                                             cout 
                                         if (this != &other) {
                                             Buffer temp(other);//构造一个临时对象
                                             delete[] data;//释放自己的资源
                                             swap(temp.data, data);
                                             swap(size, temp.size);
                                         }
                                         return *this;
                                     }
                                     cout  cout  cout  cout 
                                    	Fun(t);
                                    }
                                    int main()
                                    {
                                    	PerfectForward(10); //右值
                                    	int a;
                                    	PerfectForward(a); //左值
                                    	PerfectForward(std::move(a)); // 右值
                                    	const int b = 8;
                                    	PerfectForward(b); //const 左值
                                    	PerfectForward(std::move(b)); // const 右值
                                    	return 0;
                                    }
                                    
                                    	Fun(forward
                                    public:
                                     Person(const char* name = "", int age = 0)
                                     :_name(name)
                                     , _age(age)
                                     {}
                                     Person(const Person& p) = delete;//编译器不再自动生成拷贝构造函数
                                    private:
                                     bit::string _name;
                                     int _age;
                                    };
                                    
                                    public:
                                     Person(const char* name = "", int age = 0)
                                     :_name(name)
                                     , _age(age)
                                     {}
                                     Person(const Person& p) = default;//强制生成拷贝构造函数
                                    private:
                                     bit::string _name;
                                     int _age;
                                    };
                                    
VPS购买请点击我

文章版权声明:除非注明,否则均为主机测评原创文章,转载或复制请以超链接形式并注明出处。

目录[+]