从C语言到C++_33(C++11_上)initializer_list+右值引用+完美转发+移动构造/赋值(上)

简介: 从C语言到C++_33(C++11_上)initializer_list+右值引用+完美转发+移动构造/赋值

       在2003年C++标准委员会曾经提交了一份技术勘误表(简称TC1),使得C++03这个名字已经取代了C++98称为C++11之前的最新C++标准名称。不过由于C++03(TC1)主要是对C++98标准中的漏洞进行修复,语言的核心部分则没有改动,因此人们习惯性的把两个标准合并称为C++98 / 03标准。从C++0x到C++11,C++标准10年磨一剑,第二个真正意义上的标准珊珊来迟。


       相比于C++98 / 03,C++11则带来了数量可观的变化,其中包含了约140个新特性,以及对C++03标准中约600个缺陷的修正,这使得C++11更像是从C++98 / 03中孕育出的一种新语言。相比较而言,C++11能更好地用于系统开发和库开发、语法更加泛华和简单化、更加稳定和安全,不仅功能更强大,而且能提升程序员的开发效率,公司实际项目开发中也用得比较多,所以我们要作为一个重点去学习。C++11增加的语法特性非常篇幅非常多,这里没办法壹壹讲解,所以本博客主要讲解实际中比较实用的语法。

1. 列表初始化initializer_list

  • 列表:花括号:{ }就被叫做列表。

之前可以使用列表来初始化数组,初始化结构体变量,初始化元素类型为结构体变量的数组等等。

  • C++11扩大了用大括号括起的列表(初始化列表)的使用范围,使其可用于所有的内置类型和用户自定义的类型,使用初始化列表时,可添加等号(=),也可不添加
#include <iostream>
using namespace std;
 
class Date
{
public:
  Date(int year, int month, int day)
    :_year(year)
    , _month(month)
    , _day(day)
  {
    cout << "Date(int year, int month, int day)" << endl;
  }
 
protected:
  int _year = 1;
  int _month = 1;
  int _day = 1;
};
 
int main()
{
  int x1 = 1;
 
  int x2 = { 2 }; // 要能看懂,但是不建议使用
  int x3{ 2 };
 
  Date d1(2023, 1, 1); // 都是在调用构造函数
 
  Date d2 = { 2023, 2, 2 }; // 要能看懂,但是不建议使用
  Date d3{ 2023, 3, 3 };
 
  return 0;
}

可以不加等号进行初始化,如上图代码所示,但是强烈不建议使用

       这其实很鸡肋,没有什么价值,继续使用C++98中的方式就挺好的,而且容易理解,C++11中的方式反而不太好理解了。C++中这种鸡肋的语法被很多人吐槽,理性看待。

可以不加等号进行初始化,如上图代码所示,但是强烈不建议使用

       这其实很鸡肋,没有什么价值,继续使用C++98中的方式就挺好的,而且容易理解,C++11中的方式反而不太好理解了。C++中这种鸡肋的语法被很多人吐槽,理性看待。

列表初始化真正有意义的地方是用于初始化STL中的容器:

       之前提到:vector和list以及map等STL中的容器也可以像普通数组一样使用初始化列表来初始化了。这是因为列表初始化本身就是一个类模板:

       如上图所示,这是C++11才有的一个类型,该类型叫做列表初始化,而且还有自己的成员函数,包括构造函数,计算列表大小的接口,获取列表迭代器位置。(但几乎都不用)


       C++11为这些容器提供了新的构造函数,该构造函数是使用列表来初始化对象的,它的形参就是initializer_list,所以列表初始化才可以初始化STL中的容器。


赋值运算符重载函数也有一个列表的重载版本:

#include <iostream>
#include <vector>
#include <list>
#include <map>
using namespace std;
 
class Date
{
public:
  Date(int year, int month, int day)
    :_year(year)
    , _month(month)
    , _day(day)
  {
    cout << "Date(int year, int month, int day)" << endl;
  }
 
protected:
  int _year = 1;
  int _month = 1;
  int _day = 1;
};
 
int main()
{
  int x1 = 1;
 
  int x2 = { 2 }; // 要能看懂,但是不建议使用
  int x3{ 2 };
 
  Date d1(2023, 1, 1); // 都是在调用构造函数
 
  Date d2 = { 2023, 2, 2 }; // 要能看懂,但是不建议使用
  Date d3{ 2023, 3, 3 };
 
  // 调用支持list (initializer_list<value_type> il)类似这样的构造函数
  vector<int> v1 = { 1, 2, 3, 4, 5, 6 };
  vector<int> v2 { 1, 2, 3, 4, 5, 6 };
 
  list<int> lt1 = { 1, 2, 3, 4, 5, 6 };
  list<int> lt2{ 1, 2, 3, 4, 5, 6 };
 
  auto x = { 1, 2, 3, 4, 5, 6 };
  cout << typeid(x).name() << endl; // 打印初始化列表的类型
 
  vector<Date> v3 = {d1, d2, d3};
  vector<Date> v4 = { { 2022, 1, 1 }, {2022, 11, 11} };
 
  string s1 = "11111";
 
  map<string, string> dict = { { "sort", "排序" }, { "insert", "插入" } }; // 构造
 
  initializer_list<pair<const string, string>> kvil = { { "left", "左边" }, { "right", "右边" } }; // 赋值重载
  dict = kvil; // 上面的类型就不能用auto推导,编译器不知道那里是一个pair
 
  return 0;
}


2. 前面提到的一些知识点

2.1 小语法

C++11提供了一些新的小语法,很多我们都接触过甚至是使用过,这里系统讲讲。


c++11提供了多种简化声明的方式,尤其是在使用模板时。这里讲auto和decltype


auto:这个关键字我们已经使用过很多了


       在C++98中auto是一个存储类型的说明符,表明变量是局部自动存储类型,但是局部域中定义局部的变量默认就是自动存储类型,所以auto就没什么价值了。C++11中废弃auto原来的用法,将其用于实现自动类型推断。这样要求必须进行显示初始化,让编译器将定义对象的类型设置为初始化值的类型。

#define _CRT_SECURE_NO_WARNINGS 1
 
#include <iostream>
#include <map>
using namespace std;
 
int main()
{
  int i = 10;
  auto p = &i;
  auto pf = strcpy;
  cout << typeid(p).name() << endl;
  cout << typeid(pf).name() << endl;
  map<string, string> dict = { {"sort", "排序"}, {"insert", "插入"} };
  //map<string, string>::iterator it = dict.begin();
  auto it = dict.begin();
 
  return 0;
}


decltype:

关键字decltype将变量的类型声明为表达式指定的类型。

使用typeid().name()只能打印出类型的名称,并不能用这个名称继续创建变量,而decltype可以:

template<class T1, class T2>
void F(T1 t1, T2 t2)
{
  decltype(t1 * t2) ret;
  cout << typeid(ret).name() << endl;
}
 
int main()
{
  const int x = 1;
  double y = 2.2;
  decltype(x * y) ret; // ret的类型是double
  decltype(&x) p; // p的类型是int*
  cout << typeid(ret).name() << endl;
  cout << typeid(p).name() << endl;
  F(1, 'a');
  return 0;
}

       使用decltype可以自动推演类型,并且可以用推演出的结果继续创建变量,如上图所示,对于一些不同类型直接的运算结果,decltype有奇效。


nullptr:

       由于C++中NULL被定义成字面量0,这样就可能回带来一些问题,因为0既能指针常量,又能表示整形常量。所以出于清晰和安全的角度考虑,C++11中新增了nullptr,用于表示空指针。

在C++中存在条件编译:(以后用nullptr就行了)这算是修复了一个bug

#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL  ((void*)0)
#endif
#endif

范围for循环

       范围for我们也一直都在使用,这是C++11提供的语法糖,使用起来非常方便,它的底层就是迭代器,只是编译器给自动替换了,这里就不再详细讲解了。一般是这么用的:

#include<iostream>
using namespace std;
int main()
{
    int arr[] = { 1, 2, 3, 4, 5 };
    for (auto& e : arr)
    {
        cout << e << " ";
    }
    cout << endl;
    return 0;
}


2.2 STL中的一些变化

新容器:

       红色框中的是C++11增加的新容器,基本只有unordered_map和unordered_set有用,其他很鸡肋。容器array对标的是静态数组,array也是一个静态的,也就是在栈区上的,大小是通过一个非类型模板参数确定的。容器forward_list是一个单链表,也很鸡肋,因为绝大部分场景双链表都可以满足要求,而且更加方便,唯一使用到单链表的地方就是哈希桶中。前面都提到过。


       至于unordered_map和unordered_set,这两个容器的底层是哈希桶,虽然不能实现排序,但是可以降重。而且在查找时具有其他容器无法比拟的效率。这两个容器是非常实用的,而且也是我们经常使用的。


容器中的使用新方法:


1. 使用列表构造

在前面就讲解过了,几乎每个容器都增加了新的接口,使用std::initializer_list类型来构造。


2. 移动构造和移动赋值

在下面讲解了右值引用就可以明白了。


3. emplace_xxx插入接口或者右值引用版本的插入接口。

同样在后面才能学习到。

3. 右值和右值引用

       传统的C++语法中就有引用的语法,而C++11中新增了的右值引用语法特性, 之前学习的引用就叫做左值引用。无论左值引用还是右值引用,都是给对象取别名。

3.1 右值和右值引用概念

什么是左值?什么是右值?

  • 左值:一个表示数据的表达式,如变量名或者指针解引用。
  • 特点:可以对左值取地址 + 可以对左值赋值。

上图代码中所示的变量都属于左值,要牢记左值可以取地址这一个特性。

  • 定义时const修饰符后的左值,不能给它赋值,但是可以取它的地址。
  • 左值可以出现在赋值符号的左边,也可以出现在赋值符号的右边。

  • 右值:也是一个表示数据的表达式。如:字面常量,表达式返回值,函数返回值,类型转换时的临时变量等等。
  • 特点:右值不可以取地址,不可以赋值。

要牢记右值特性:不能取地址不能赋值。

  • 右值可以出现在赋值符号的右边,但是不能出现出现在赋值符号的左边。

什么是右值引用?

左值引用是给左值取别名,右值引用显而易见就是给右值取别名。

  • 右值引用使用两个&符号。

上图代码中的rr1,rr2,rr3就是三个右值的别名,也就是右值引用。

3.2 右值引用类型的左值属性

  • 右值是不能取地址的,但是给右值取别名后,会导致右值被存储到特定位置,且可以取到该位置的地址。

       对于内置类型的右值,如字面常量,一旦右值引用以后,就会被存储到特定的位置,并且可以取到该地址,而且还可以修改。

int main()
{
  int&& rr1 = 10;
  cout << rr1 << endl;
 
  rr1 = 5;
  cout << rr1 << endl;
 
  const double&& rr2 = (1.1 + 2.2);
  //rr2 = 5.5; // 不能修改
 
  return 0;
}

       字面常量10原本是不可以被修改的,但是右值引用以后,在特定的位置开辟了变量来存放10,所以就可以被修改了。


       表达式或者函数的返回值,会有一个临时变量来存放返回值,我们知道这样的临时变量具有常性,也是右值。对于这种右值引用,编译器会修改它的属性,将常性修改,并且存储在特定位置。注意const类型的右值,即便开辟了变量存放该右值也是不可以被修改的,因为被const修饰了。


内置类型的右值被称为纯右值。


自定义类型的右值被称为将亡值。


       对于自定义类型的右值,如容器的临时变量,它确确实实会被销毁,而不会被存放。自定义类型的右值才能体现出右值存在的意义,后面会详细讲解。


右值引用是右值的别名,它所指向的右值是不可以被修改的。

但是右值引用本身也是一种类型,并且它的属性是左值,可以取地址,可以赋值。

从C语言到C++_33(C++11_上)initializer_list+右值引用+完美转发+移动构造/赋值(中):https://developer.aliyun.com/article/1522391

目录
相关文章
|
3月前
|
安全 C++
【c++】继承(继承的定义格式、赋值兼容转换、多继承、派生类默认成员函数规则、继承与友元、继承与静态成员)
本文深入探讨了C++中的继承机制,作为面向对象编程(OOP)的核心特性之一。继承通过允许派生类扩展基类的属性和方法,极大促进了代码复用,增强了代码的可维护性和可扩展性。文章详细介绍了继承的基本概念、定义格式、继承方式(public、protected、private)、赋值兼容转换、作用域问题、默认成员函数规则、继承与友元、静态成员、多继承及菱形继承问题,并对比了继承与组合的优缺点。最后总结指出,虽然继承提高了代码灵活性和复用率,但也带来了耦合度高的问题,建议在“has-a”和“is-a”关系同时存在时优先使用组合。
197 6
|
4月前
|
算法 C++ 容器
模拟实现c++中的list模版
模拟实现c++中的list模版
|
6月前
|
编译器 C语言 C++
【c++丨STL】list模拟实现(附源码)
本文介绍了如何模拟实现C++中的`list`容器。`list`底层采用双向带头循环链表结构,相较于`vector`和`string`更为复杂。文章首先回顾了`list`的基本结构和常用接口,然后详细讲解了节点、迭代器及容器的实现过程。 最终,通过这些步骤,我们成功模拟实现了`list`容器的功能。文章最后提供了完整的代码实现,并简要总结了实现过程中的关键点。 如果你对双向链表或`list`的底层实现感兴趣,建议先掌握相关基础知识后再阅读本文,以便更好地理解内容。
115 1
|
6月前
|
算法 C语言 C++
【c++丨STL】list的使用
本文介绍了STL容器`list`的使用方法及其主要功能。`list`是一种双向链表结构,适用于频繁的插入和删除操作。文章详细讲解了`list`的构造函数、析构函数、赋值重载、迭代器、容量接口、元素访问接口、增删查改操作以及一些特有的操作接口如`splice`、`remove_if`、`unique`、`merge`、`sort`和`reverse`。通过示例代码,读者可以更好地理解如何使用这些接口。最后,作者总结了`list`的特点和适用场景,并预告了后续关于`list`模拟实现的文章。
176 7
|
6月前
|
存储 编译器 C++
C++ initializer_list&&类型推导
在 C++ 中,`initializer_list` 提供了一种方便的方式来初始化容器和传递参数,而右值引用则是实现高效资源管理和移动语义的关键特性。尽管在实际应用中 `initializer_list&&` 并不常见,但理解其类型推导和使用方式有助于深入掌握现代 C++ 的高级特性。
66 4
|
8月前
|
存储 搜索推荐 C++
【C++篇】深度剖析C++ STL:玩转 list 容器,解锁高效编程的秘密武器2
【C++篇】深度剖析C++ STL:玩转 list 容器,解锁高效编程的秘密武器
156 2
【C++篇】深度剖析C++ STL:玩转 list 容器,解锁高效编程的秘密武器2
|
6月前
|
算法 编译器 C语言
【C语言】C++ 和 C 的优缺点是什么?
C 和 C++ 是两种强大的编程语言,各有其优缺点。C 语言以其高效性、底层控制和简洁性广泛应用于系统编程和嵌入式系统。C++ 在 C 语言的基础上引入了面向对象编程、模板编程和丰富的标准库,使其适合开发大型、复杂的软件系统。 在选择使用 C 还是 C++ 时,开发者需要根据项目的需求、语言的特性以及团队的技术栈来做出决策。无论是 C 语言还是 C++,了解其优缺点和适用场景能够帮助开发者在实际开发中做出更明智的选择,从而更好地应对挑战,实现项目目标。
261 0
|
8月前
|
存储 算法 C++
【C++打怪之路Lv10】-- list
【C++打怪之路Lv10】-- list
58 1
|
8月前
|
C语言 C++
C 语言的关键字 static 和 C++ 的关键字 static 有什么区别
在C语言中,`static`关键字主要用于变量声明,使得该变量的作用域被限制在其被声明的函数内部,且在整个程序运行期间保留其值。而在C++中,除了继承了C的特性外,`static`还可以用于类成员,使该成员被所有类实例共享,同时在类外进行初始化。这使得C++中的`static`具有更广泛的应用场景,不仅限于控制变量的作用域和生存期。
177 10
|
8月前
|
存储 C++ 容器
【C++篇】深度剖析C++ STL:玩转 list 容器,解锁高效编程的秘密武器1
【C++篇】深度剖析C++ STL:玩转 list 容器,解锁高效编程的秘密武器
133 5

热门文章

最新文章