【C++11】C++的新特性(详解)


在这里插入图片描述

1.C++11简介

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

想要深入了解C++11的可以到C++11官网

2.统一的列表初始化

2.1{}初始化

  • 在C++98中,标准允许使用花括号{}对数组或者结构体元素进行统一的列表初始值设定。
  • C++11扩大了用大括号括起的列表(初始化列表)的使用范围,使其可用于所有的内置类型和用户自定义的类型,使用初始化列表时,可添加等号(=),也可不添加。
  • 创建对象时也可以使用列表初始化方式调用构造函数初始化

有如下使用方法:

#include<iostream>
#include<vector>
#include<list>
#include<map>
using namespace std;
struct Point
{
	int _x;
	int _y;
};
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()
{
	int x1 = 1;
	int x2 = { 2 };
	//可以省略赋值符号
	int x3{ 3 };

	int arr1[] = { 1,2 };
	int array1[]{ 1, 2, 3, 4, 5 };
	int arr2[5] = { 0 };
	int array2[5]{ 0 };


	Point p1 = { 1, 2 };
	Point P2{ 1,2 };

	// C++11中列表初始化也可以适用于new表达式中
	int* pa = new int[4]{ 0 };
	int* pb = new int[10]{ 1, 2, 3 };

	Point* p3 = new Point[2]{ { 1,1 },{ 2,2 } };

	//一般调用构造函数创建对象的方式
	Date d1(2022, 8, 29);
	//创建对象时也可以使用列表初始化方式调用构造函数初始化
	Date d2 = { 2022, 8, 30 }; //可添加等号
	Date d3{ 2022, 8, 31 };    //可不添加等号


	return 0;
}

2.2 std::initializer_list

C++11中新增了initializer_list容器,并且该容器没有提供过多的成员函数。std::initializer_list的官方详细文档:
https://cplusplus.com/reference/initializer_list/initializer_list/

那么std::initializer_list是什么类型呢?从下图的运行结果可以看出可以看出编译器会将大括号识别为std::itializer_list类型。

int main()
{
	auto il = { 10, 20, 30 };
	cout << typeid(il).name() << endl;//typeid是用于获取变量类型的函数
	return 0;
}

在这里插入图片描述
initializer_list是C++11提供的新类型,定义在头文件中。
用于表示某种特定类型的值的数组,和vector一样,initializer_list也是一种模板类型。能够处理不同数量实参(但是类型相同),与vector不同的是initializer_list对象中的元素永远是常量值,我们无法改变initializer_list对象中元素的值。在进行函数调用的时候需要使用花括号将所有的参数括起来。
在这里插入图片描述

std::initializer_list使用场景:

std::initializer_list一般是作为构造函数的参数,没有提供对应的增删查改等接口,因为initializer_list并不是专门用于存储数据的,而是为了让其他容器支持列表初始化的。C++11对STL中的不少容器就增加std::initializer_list作为参数的构造函数,这样初始化容器对象就更方便了。也可以作为operator=的参数,这样就可以用大括号赋值。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

#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;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	initializer_list<int> i3 = { 10,20,30 };
	initializer_list<int>::iterator it3 = i3.begin();
	cout << it3 << endl;

	vector<int> v1 = { 1,2,3,4,5 };
	vector<int> v2 = { 10,20,30 };

	list<int> lt1 = { 1,2,3,4,5 };
	list<int> lt2 = { 10,20,30 };

	//auto自动识别initializer_list类型
	auto i1 = { 10,20,30,1,1,2,2,2,2,2,2,1,1,1,1,1,1,1,1,2,1,1,2 };
	auto i2 = { 10,20,30 };
	cout << typeid(i1).name() << endl;
	cout << typeid(i2).name() << endl;
	initializer_list<int>::iterator it1 = i1.begin();
	initializer_list<int>::iterator it2 = i2.begin();
	cout << it1 << endl;
	cout << it2 << endl;

	map<string, string> dict1 = { {"sort", "排序"},{"string", "字符串"},{"Date", "日期"} };
	map<string, string> dict2{ make_pair("sort", "排序"), { "insert", "插入" } };//也可以省略赋值符号
	pair<string, string> kv1 = { "Date", "日期" };

	Date d1(2023, 5, 20);
	Date d2(2023,5,21);
	// initializer_list<Date>
	vector<Date> vd1 = {d1, d2};
	vector<Date> vd2 = { Date(2023,5,20), Date(2023,5,21) };
	vector<Date> vd3 = { {2023,5,20}, {2023,5,20} };

	return 0;
}

让模拟实现的vector也支持{}初始化和赋值

namespace bit
{
template<class T>
class vector {
public:
  typedef T* iterator;
  vector(initializer_list<T> l)
  {
    _start = new T[l.size()];
    _finish = _start + l.size();
    _endofstorage = _start + l.size();
    iterator vit = _start;
    //必须加typename,声明iterator是个类型,不然编译器不知道它是类型还是
    //静态变量
    typename initializer_list<T>::iterator lit = l.begin();
    //将已经装载数据的initializer_list<T> l
    //中的每个元素通过迭代器搞到vector中
    while (lit != l.end())
    {
      *vit++ = *lit++;
    }
    //for (auto e : l)
    //  *vit++ = e;
  }
  //赋值运算函数的现代写法
  vector<T>& operator=(initializer_list<T> l) {
    vector<T> tmp(l);
    std::swap(_start, tmp._start);
    std::swap(_finish, tmp._finish);
    std::swap(_endofstorage, tmp._endofstorage);
    return *this;
  }
private:
  iterator _start;
  iterator _finish;
  iterator _endofstorage;
};
}

3.声明

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

3.1 auto

C++11中auto可以自动推断实现类型,要求必须进行显示初始化,让编译器将定义对象的类型设置为初始化值的类型。

使用场景:

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", "插入"} };
	//有的时候迭代器类型表表示太长了,我们就可以直接用auto
	//map<string, string>::iterator it = dict.begin();
	auto it = dict.begin();
	return 0;
}

在这里插入图片描述

3.2 decltype

decltype,在C++中,作为操作符用于查询表达式的数据类型。decltype在C++11标准制定时引入,主要是为泛型编程而设计,以解决泛型编程中,由于有些类型由模板参数决定,而难以(甚至不可能)表示之的问题。

decltype的使用场景

#include<iostream>
#include<vector>
using namespace std;

template<class T1,class T2>
void func(T1 t1, T2 t2)
{
	decltype(t1*t2) ret; //也可以推导模板参数的类型
	cout << typeid(ret).name() << endl;
}
void* GetMemory(size_t size)
{
	return malloc(size);
}
int main()
{
	const int x = 1;
	double y = 2.2;

	//decltype可以推演表达式的类型
	decltype(x*y) ret; //ret的类型是double
	ret = 2.2; 
	decltype(&x) p; //p的类型是int*

	cout << typeid(ret).name() << endl;
	cout << typeid(p).name() << endl;

	func(1, 'a');

	//vector存储的类型跟x*y表达式返回值类型一致
	//decltype推导表达式类型,用这个类型实例化模板参数或者定义对象
	vector<decltype(x*y)> v;

	//decltype还可以推演函数返回值的类型
	//1.如果没有带参数,则推导函数类型
	cout << typeid(decltype(GetMemory)).name() << endl;
	//2.如果没有带参数,则推导函数类型
	cout << typeid(decltype(GetMemory(0))).name() << endl;

	return 0;

}

运行结果:
在这里插入图片描述

  • 虽然通过typeid(变量名).name()的方式可以获取一个变量类型,但无法用这个函数获取到变量的类型取去定义变量。
  • auto可以自动推导类型,但是auto的使用前提是必须要对auto声明的类型进行初始化,否则编译器无法推导出auto的实际类型。
  • decltype不仅可以将变量的类型声明为表达式指定的类型,还可以推导函数类型或函数返回值的类型。

3.3 nullptr

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

#ifndef NULL
#ifndef __cpluscplus
#define NULL    0
#else
#define NULL    ((void*)0)
#endif
#endif

有关空指针,野指针的博客:空指针 野指针

4.范围for

传统for语句

#include<iostream>
#include<vector>
using namespace std;
int main()
{
	vector<int> v;
	for (int i = 0; i < 10; i++)
	{
		v.push_back(i);
	}
	//遍历打印
	for (auto it = v.begin(); it != v.end(); it++)
	{
		cout << *it << " ";
	}
	cout << endl;
	//或
	int arr[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
	//将数组元素值全部乘以2
	for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
	{
		arr[i] *= 2;
	}
	//打印数组中的所有元素
	for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
	{
		cout << arr[i] << " ";
	}
	cout << endl;
	return 0;
}

范围for的使用方式

int main()
{
	string strs[] = { "苹果", "香蕉", "草莓", "橘子" };
	for (const auto& e : strs)
	{
		cout << e << " ";
	}
	cout << endl;
	system("pause");
	return 0;
}

auto :自动推演出范围变量的数据类型。
关于引用&:范围for在遍历的时候,把数组的每一个元素的值拷贝给e,拷贝需要开辟空间,一定程度上降低了效率,加引用后可以节省空间,提高效率;而且若想要修改数组的内容,不加引用就无法修改,因为拷贝的值只是数组的一个副本,而加了引用之后,就是变量的别名,也就是变量本身,就可以进行修改操作。

  • 可以使用continue或break;
  • 范围for的底层实际上是一个迭代器,编译器会自动去调用迭代器从而实现范围for。所以使用范围for,那么这个对象必须要支持++和减减操作;
  • 范围for循环迭代的范围必须是确定的。对应普通数组,就是第一个元素到最后一个元素;对于STL的容器就是begin()到end()。

5.STL中一些变化

新增容器

C++11中新增了四个容器,分别是array、forward_list、unordered_map和unordered_set。

5.1 array

数组array是一个大小固定的序列容器,本质是一个静态数组,容器中保存着特定数量的元素,元素按照严格的线性序列保存。它是一个封装了固定数量元素的数组的聚合类型。因此,它不能动态的添加或删除元素(类似的大小可扩展的容器参见vector)。

int main()
{
	array<int, 10> a1;   //定义一个可存储10个int类型元素的array容器
	array<double, 5> a2; //定义一个可存储5个double类型元素的array容器
	return 0;
}
  1. array容器支持通过[]访问指定下标的元素,同样也支持使用范围for遍历数组元素,并且创建后数组的大小不可改变。
  2. array与普通数组的不同之处主要在于:array会严格的进行越界检查,用下标访问操作符[]访问元素采用断言检查,调用at成员函数采用抛异常检查。

5.2 forward_list容器

forward_list容器本质是一个单链表,只支持头插头删,不支持尾插尾删。因为单链表在进行尾插尾删需要先找尾,时间复杂度为O(N),尾插尾删的效率不高。

比起list而言,forward_list每个结点可以节省一个指针的空间,头删头插效率不错,但是日常中我们一般不缺内存,还是使用list容器更香。

5.3 unordered_map和unordered_set容器

unordered_map和unordered_set容器底层采用的都是哈希表。这也是C++11中新增的最有价值的容器。关于这两个容器,我之前的博客由详细介绍,可以点击以下链接进行跳转学习:
【C++】unordered_map与unordered_set(系列关联式容器)
底层哈希:【C++】哈希/散列详细解析

容器中的一些新方法

如果我们再细细去看会发现基本每个容器中都增加了一些C++11的方法。
比如提供了cbegin和cend方法返回const迭代器,将内置类型转换成string类型统一调用的to_string函数,将string类型转换成内置类型的函数,一个以initializer_list作为参数的构造函数,用于支持列表初始化。提供了emplace系列方法,并在容器原有插入方法的基础上重载了一个右值引用版本的插入函数,用于提高向容器中插入元素的效率。

有关C++11新增的:1.右值引用和移动语义;2.lambda表达式;3.包装器;4.线程库5.可变参数模板等重要知识博主会将后序继续更新详细解析的文章。

  • 7
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 7
    评论
### 回答1: 11-tie源码是一个用C语言实现的简单且高效的哈希表结构,可以用来实现键-值对的存储和查找。以下是对11-tie源码的详细解释。 11-tie源码主要由三个关键部分组成:哈希表结构、哈希函数和碰撞解决方法。 首先是哈希表结构。11-tie源码中使用了一个固定大小的数组作为哈希表来存储键-值对。数组的大小由用户在创建哈希表时指定,并具有较好的素数特性,以减少碰撞的发生。哈希表中的每个元素(bucket)是一个指向键-值对链表的指针。如果出现碰撞,新的键-值对将被添加到链表的头部。 然后是哈希函数。11-tie源码中使用了一个简单且高效的哈希函数,它会根据键的特征将其映射到数组的索引位置。哈希函数使得不同的键被均匀地分布在数组中,从而减少碰撞的发生。该哈希函数通常基于键的类型和特性,但也可以根据特定需求进行自定义。 最后是碰撞解决方法。当多个键映射到数组的同一个索引位置时,就会发生碰撞。11-tie源码中使用了链表来解决碰撞问题。当发生碰撞时,新的键-值对将被添加到链表的头部。这种解决方法简单且有效,但当哈希表中的元素数量较大时,链表的遍历会导致性能下降。 总结起来,11-tie源码是一个使用C语言实现的简单高效的哈希表结构。通过哈希函数将键映射到数组的索引位置,使用链表解决碰撞问题。这种结构可以用来存储和查找键-值对,适用于快速查询和插入数据的场景。 ### 回答2: c 11 tie 源码详解是指对 C++ 11 中的 `std::tie` 函数进行解析。`std::tie` 是一个模板函数,用于将多个值绑定到一个元组中。 `std::tie` 的源码实现如下: ```cpp namespace std { template <typename... Types> tuple<Types&...> tie(Types&... args) noexcept { return tuple<Types&...>(args...); } } ``` `std::tie` 函数是一个模板函数,接受任意数量的参数,并将这些参数作为引用传递给 `std::tuple`,然后返回这个 `std::tuple`。 `std::tuple` 是一个模板类,用于保存一组不同类型的值。`std::tuple<Types&...>` 的含义是保存参数 Types&... 的引用。 利用 `std::tie` 函数,可以将多个变量绑定到一个 `std::tuple` 中,并且可以通过解构绑定的方式获取这些变量。 例如,假设有两个变量 `int a` 和 `double b`,可以使用 `std::tie` 将它们绑定到一个元组中,并通过解构绑定方式获取它们的值: ```cpp int a = 1; double b = 2.0; std::tuple<int&, double&> t = std::tie(a, b); std::get<0>(t) = 10; std::get<1>(t) = 20.0; std::cout << a << ", " << b << std::endl; ``` 在上面的代码中,通过 `std::tie(a, b)` 将变量 `a` 和 `b` 绑定到一个元组 `t` 中,然后通过 `std::get<0>(t)` 和 `std::get<1>(t)` 获取元组中第一个和第二个值,并将它们分别赋值为 10 和 20.0。最后输出结果为 `10, 20`。 `std::tie` 的源码实现简单明了,通过将多个参数作为引用传递给 `std::tuple`,实现了将多个变量绑定到一个元组中的功能。这个功能在一些情况下非常方便,可以减少代码的复杂性和重复性。 ### 回答3: c 11 tie 是 C++ 11 标准中新增的一个标准库函数,用于将多个输出流(ostream)绑定到一个流对象上。通过将多个输出流绑定在一起,可以在输出时同时向多个流对象输出数据,提高代码的易读性和简洁性。 使用 c 11 tie 首先需要包含 `<tuple>` 头文件,并且可以接受任意个数的流对象作为参数。例如 `std::tie(stream1, stream2)` 表示将 stream1 和 stream2 绑定在一起。 在绑定之后,输出到绑定对象的数据会自动发送到所有绑定的流对象中。例如 `std::cout << "Hello World";`,如果之前使用 `std::tie(std::cout, fileStream)` 进行了绑定,那么输出的 "Hello World" 既会在控制台上显示,也会同时写入到文件流对象中,实现了同时输出到两个流对象的效果。 需要注意的是,绑定只在绑定操作发生时生效,之后对流对象的修改不会影响绑定。因此,如果在绑定之后修改了流对象,需要重新进行绑定操作。 c 11 tie 的使用可以简化代码,提高开发效率。通过同时输出到多个流对象,可以实现在不同目的地同时记录相同的输出信息,提供了一种方便的日志记录功能。此外,绑定的流对象可以是任意的输出流,不限于标准输出流和文件流,也可以是用户自定义的流对象。 总结来说,c 11 tie 是 C++ 11 标准中新增的一个标准库函数,用于将多个输出流绑定在一个流对象上,实现同时输出到多个流对象的功能。它提高了代码的可读性和简洁性,并且可以应用于日志记录等多种场景。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

_麦子熟了

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值