C++11常用的一部分新特性(上)

简介: C++11常用的一部分新特性(上)

统一的列表初始化

{}初始化

C++11扩大了用大括号括起的列表(初始化列表)的使用范围,使其可用于所有的内置类型和用户自

定义的类型,使用初始化列表时,可添加等号(=),也可不添加。

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

#include<iostream>
using namespace std;
struct Point
{
 int _x;
 int _y;
};
int main()
{
  Point* p1 = new Point[2]{ {1,1},{2,2} };
  return 0;
}

#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;
  }
private:
  int _year;
  int _month;
  int _day;
};
int main()
{
  //这三个效果是相同的
  Date d1(2023, 5, 25);
  Date d2{ 2023, 5, 25 };
  Date d3 = { 2023, 5, 25 };
  return 0;
}

也就是说这里用花括号进行初始化调用的是类的构造。

也就是说,C++11几乎可以一切都可以用花括号初始化,包括变量(但是不建议这样)。

std::initializer_list

来看下面这段代码:

#include<iostream>
#include<vector>
#include<list>
using namespace std;
int main()
{
  vector<int> arr{1, 2, 3, 4, 5, 6};//这里的初始化为什么可以随意改变元素数量呢?
  auto a = { 10,20,30 };//来看看这个花括号初始化成了什么类型
  cout << typeid(a).name() << endl;//这里拿到的是类型的字符串
  return 0;
}

这是initializer_list类型的使用文档https://cplusplus.com/reference/initializer_list/initializer_list/

这个类似一个常量数组,有两个指针指向数组的开始和结束(其实也是迭代器)。

并且这个vector可以利用这个类型进行初始化的。

其实就相当于将initializer_list类型中的数据遍历然后push_back()到vector里面。

这种类型的实用处就是:

//这里就不用初始化一个pair类型的然后在插入map中了,因为里面是匿名对象的初始化
map<string, string> str = { {"字符串","string"},{"排序","sort"} };//里面的两个小花括号也可以理解为一个pair类型的initializer_list数组

声明

auto

这个经常用,自动推导左边对象类型。

在C++98中auto是一个存储类型的说明符,表明变量是局部自动存储类型,但是局部域中定义局

部的变量默认就是自动存储类型,所以auto就没什么价值了。C++11中废弃auto原来的用法,将

其用于实现自动类型腿断。这样要求必须进行显示初始化,让编译器将定义对象的类型设置为初

始化值的类型。

decltype

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

#include<iostream>
using namespace std;
int main()
{
  int x = 0;
  cout << typeid(x).name() << endl;
  decltype(x) y;
  cout << typeid(y).name() << endl;
  return 0;
}

那么decltype使用的地方在哪里呢?

#include<iostream>
using namespace std;
template<class T1, class T2>
void F(T1 t1, T2 t2)
{
  decltype(t1 * t2) ret = t1 * t2;//这里万一涉及到整型提升,不知道提升到哪个类型就可以自动推导,不至于丢失精度
  cout << typeid(ret).name() << endl;
  cout << ret << endl;
}
int main()
{
  F(1, 2);
  F(1, 2.2);
  return 0;
}

nullptr

这个之前也经常用。

由于C++中NULL被定义成字面量0,这样就可能回带来一些问题,因为0既能指针常量,又能表示

整形常量。所以出于清晰和安全的角度考虑,C++11中新增了nullptr,用于表示空指针。

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

STL中一些变化

新容器

array

https://legacy.cplusplus.com/reference/array/array/

这个新容器和数组的功能没什么区别,不如vector好用,比普通数组多一个越界检查的报错。

forward_list

https://legacy.cplusplus.com/reference/forward_list/forward_list/

这是个单链表。

这里的区别就是,每个节点少一个指针的大小,并且没有头插头删(并不是那么好用)

已有容器的新接口

这里以vector举例:

这四个其实就是上面的正迭代器和反迭代器,c只是为了显示是const版本的而已,看起来更容易辨别。

这个接口是缩容的接口,如果空间浪费的实在是太大,可以用一下(用时间换空间)。

还有这两个接口,与右值引用和可变模板参数有关,下面会结合这个接口讲解。

右值引用和移动语义

左值引用和右值引用

什么是左值?

左值是一个表示数据的表达式(如变量名或解引用的指针),我们可以获取它的地址+可以对它赋值,左值可以出现赋值符号的左边,右值不能出现在赋值符号左边。

定义时const修饰符后的左值,不能给他赋值,但是可以取它的地址。左值引用就是给左值的引用,给左值取别名。

什么是右值?

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

右值引用就是对右值的引用,给右值取别名。

注意:

  1. 左值引用只能引用左值,不能引用右值。
  2. 但是const左值引用既可引用左值,也可引用右值。
  3. 右值引用只能右值,不能引用左值。
  4. 但是右值引用可以move以后的左值。
#include<iostream>
using namespace std;
int main()
{
  // 左值引用只能引用左值,不能引用右值。
  int a = 10;
  int& ra1 = a;//ra为a的别名
  //int& ra2 = 10;//编译失败,因为10是右值
  //const左值引用既可引用左值,也可引用右值。
  const int& ra3 = 10;
  const int& ra4 = a;
  int a = 10;
  int&& r2 = a;
  // error C2440: “初始化”: 无法从“int”转换为“int &&”
  // message : 无法将左值绑定到右值引用
  // 右值引用可以引用move以后的左值
  int&& r3 = std::move(a);
  return 0;
}

需要注意的是右值是不能取地址的,但是给右值取别名后,会导致右值被存储到特定位置,且可

以取到该位置的地址,也就是说例如:不能取字面量10的地址,但是rr1引用后,可以对rr1取地

址,也可以修改rr1。如果不想rr1被修改,可以用const int&& rr1 去引用。

右值引用使用场景和意义

左值引用最大的意义就是函数传参,返回值,减少拷贝。

那么左值引用的缺点是什么?

看下面代码:

template<class T>
T func(const int x)
{
  T ret;
  return ret;//这里ret是局部变量,出作用域就会销毁,所以是一个传值返回
}

右值引用的价值之一就是补齐这最后一块短板。

相关文章
|
3月前
|
编译器 程序员 定位技术
C++ 20新特性之Concepts
在C++ 20之前,我们在编写泛型代码时,模板参数的约束往往通过复杂的SFINAE(Substitution Failure Is Not An Error)策略或繁琐的Traits类来实现。这不仅难以阅读,也非常容易出错,导致很多程序员在提及泛型编程时,总是心有余悸、脊背发凉。 在没有引入Concepts之前,我们只能依靠经验和技巧来解读编译器给出的错误信息,很容易陷入“类型迷路”。这就好比在没有GPS导航的年代,我们依靠复杂的地图和模糊的方向指示去一个陌生的地点,很容易迷路。而Concepts的引入,就像是给C++的模板系统安装了一个GPS导航仪
140 59
|
2月前
|
安全 编译器 C++
【C++11】新特性
`C++11`是2011年发布的`C++`重要版本,引入了约140个新特性和600个缺陷修复。其中,列表初始化(List Initialization)提供了一种更统一、更灵活和更安全的初始化方式,支持内置类型和满足特定条件的自定义类型。此外,`C++11`还引入了`auto`关键字用于自动类型推导,简化了复杂类型的声明,提高了代码的可读性和可维护性。`decltype`则用于根据表达式推导类型,增强了编译时类型检查的能力,特别适用于模板和泛型编程。
27 2
|
3月前
|
存储 编译器 C++
【C++】面向对象编程的三大特性:深入解析多态机制(三)
【C++】面向对象编程的三大特性:深入解析多态机制
|
3月前
|
存储 编译器 C++
【C++】面向对象编程的三大特性:深入解析多态机制(二)
【C++】面向对象编程的三大特性:深入解析多态机制
|
3月前
|
编译器 C++
【C++】面向对象编程的三大特性:深入解析多态机制(一)
【C++】面向对象编程的三大特性:深入解析多态机制
|
3月前
|
存储 安全 编译器
【C++】C++特性揭秘:引用与内联函数 | auto关键字与for循环 | 指针空值(一)
【C++】C++特性揭秘:引用与内联函数 | auto关键字与for循环 | 指针空值
|
4月前
|
编译器 C++ 计算机视觉
C++ 11新特性之完美转发
C++ 11新特性之完美转发
62 4
|
4月前
|
Java C# C++
C++ 11新特性之语法甜点1
C++ 11新特性之语法甜点1
39 4
|
3月前
|
C++
C++ 20新特性之结构化绑定
在C++ 20出现之前,当我们需要访问一个结构体或类的多个成员时,通常使用.或->操作符。对于复杂的数据结构,这种访问方式往往会显得冗长,也难以理解。C++ 20中引入的结构化绑定允许我们直接从一个聚合类型(比如:tuple、struct、class等)中提取出多个成员,并为它们分别命名。这一特性大大简化了对复杂数据结构的访问方式,使代码更加清晰、易读。
46 0
|
4月前
|
安全 程序员 编译器
C++ 11新特性之auto和decltype
C++ 11新特性之auto和decltype
51 3