文章目录
- C++11简介
- 列表初始化
- std::initializer_list
- 变量类型推导
- nullptr
- 范围for循环
- STL中的一些变化
C++11简介
- 在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能更好地用于系统开发和库开发、语法更加泛华和简单化、更加稳定和安全,不仅功能更强大,而且能提升程序员的开发效率,公司实际项目开发中也用得比较多,所以我们要作为一个重点去学习.
小故事:
1998年是C++标准委员会成立的第一年,本来计划以后每5年视实际需要更新一次标准C++国际标准委员会在研究C++ 03的下一个版本的时候,一开始计划是2007年发布,所以最初这个标准叫C++ 07。但是到06年的时候,官方觉得2007年肯定完不成C++ 07,而且官方觉得2008年可能也完不成。最后干脆叫C++ 0x。x的意思是不知道到底能在07还是08还是09年完成。结果2010年的时候也没完成,最后在2011年终于完成了C++标准。所以最终定名为C++11.
列表初始化
在C++98中,标准允许使用花括号{}对数组和结构体元素进行统一的列表初始值设定.比如:
struct Point
{
int _x;
int _y;
};
int main()
{
int array1[] = { 1, 2, 3, 4, 5 }; //使用花括号对数组进行初始化.
int array2[5] = { 0 };
struct Point p = { 1, 2 }; //使用花括号对结构体进行初始化.
return 0;
}
C++11扩大了用大括号括起的列表(初始化列表)的适用范围,使其可用于所有内置类型和用户自定义的类型,使用初始化列表时,可以添加等于号(=),也可以不添加.
对于内置类型:
struct Point
{
int _x;
int _y;
};
int main()
{
int array1[] = { 1, 2, 3, 4, 5 };
int array2[5] = { 0 };
struct Point p = { 1, 2 };
int* p1 = new int[6]{ 0 }; //C++11中{}也可以作用域new表达式中
int* p2 = new int[6]{ 1,2,3,4,5,6 };
return 0;
}
对于自定义类型:
C++11支持的列表初始化,对于自定义类型会调用构造函数初始化.
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(2022,1,1); //C++98
Date d2{2022,1,2 }; //C++11
Date d3{2022,1,3}; //C++11
}
std::initializer_list
C++中新增了begin和end容器,其本质为一个静态数组,主要成员函数如下:
1: 提供了begin()和end()函数,用于支持迭代器遍历.
2. 还提供了size()函数,用于获取容器数据个数.
其中,{ }也是一种模板类型,类型便为initializer_list类模板
STL中的所有容器支持使用initializer_list函数构造,所以STL容器中都支持使用列表初始化.当我们使用列表初始化时( {} ),实则是将initializer_list类模板具体类型传给initializer_list构造函数对应参数,此时,编译器便会调用该构造函数进行初始化.
怎么在我们模拟实现的vector中支持列表初始化?
我们可以在模拟实现的vector中添加initializer_list构造函数,当我们使用初始化列表初始化时,实则是将花括号中所有数据都放进了initializer类型对象的静态数组中,然后我们可以通过遍历该静态数组的数据,将数据一 一尾插到vector容器中.
vector(initializer_list<T> il)
:_start(nullptr)
, _finish(nullptr)
, _end_of_storage(nullptr)
{
resize(il.size());
for (auto& e : il)
{
push_back(e);
}
}
变量类型推导
decltype
这里与我们熟悉的auto有一些差异:
关键字decltype可以将变量的类型声明为表达式指定的类型
auto关键字是编译器通过表达式右边的数据类型来推出左边的数据类型.
int main()
{
int x = 10;
decltype(x) y1 = 20.22; //根据x推倒y
auto y2 = 20.22; //根据20.22推导y2.
cout << y1 << endl; //20
cout << y2 << endl; //20.22
return 0;
}
注意:
通过typeid(变量名).name()只能获取一个变量类型的字符串,但无法根据它获取到的这个类型去定义变量.
nullptr
由于C++中NULL可以被定义成整型常量0,这样就可能会带来一些问题,因为0既能表示答指针常量,又能表示整型常量0.
所以,出于清晰和安全的角度考虑,C++11中系中新增了nullptr,用于表示空指针.
以下为C++11中针对NULL定义类型:
/* Define NULL pointer value */
#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else /* __cplusplus */
#define NULL ((void *)0)
#endif /* __cplusplus */
#endif /* NULL */
当然,在大部分场景下,使用NULL和nullptr没什么差异,但是如果在一些极端情况的传参场景中可能会导致我们所不希望的结果.
void f(int ret)
{
cout << "void f(int ret)" << endl;
}
void f(int* arg)
{
cout << "void f(int* ret)" << endl;
}
int main()
{
f(NULL); //void f(int ret)
f(nullptr); //void f(int* ret)
return 0;
}
在我们的观念中,NULL和nullptr代表的都是空指针,所以我们都希望能够调用的是形参为int*的重载函数,可是,因为NULL又可以代表整型常量0,所以编译器就会去调用形参为int的重载函数.
void f(int arg)
{
cout << "void f(int arg)" << endl;
}
void f(int* arg)
{
cout << "void f(int* arg)" << endl;
}
int main()
{
f(NULL); //void f(int arg)
f(nullptr); //void f(int* arg)
return 0;
}
范围for循环
在C++98中,我们遍历数组常常通过以下方式:
int main()
{
int arr[] = { 1,2,3,4,5,6 };
for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); ++i)
{
arr[i] += 1;
}
for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); ++i)
{
cout << arr[i] << endl;
}
return 0;
}
可是,但是对于以上方式,不但麻烦,还容易将判断条件写错,基于以上情况,C++11引入了范围for, 冒号( : ) 左边表示每一次遍历目标数组(对象)中所需要的变量,冒号( : ),右边则表示需要遍历的数组(或者对象).
int main()
{
int arr[] = { 1 , 2, 3, 4, 5, 6 };
for (auto& e : arr)
{
e += 2;
}
for (auto& e : arr)
{
cout << e << endl;
}
}
STL中的一些变化
新容器
C++11中新增了四个容器,分别是array,forward_list,unordered_map,unordered_set. 其中array,forward容器的作用比较鸡肋,unorder_map和unordered_set是一个亮点.
一: array容器
array的本质就是一个静态的顺序表经过包装成了容器.
array容器与数组最大的差别就是对于越界的检测严格很多.
int main()
{
const size_t N = 5;
int a1[N];
a1[N]; //越界读vsuai stdio测不出来.
a1[N] = 1; //越界写可以测试出来.
array<int, N> a2;
//越界读写都可以被检查出来.
a2[N]
a2[N] = 2;
}
注意:
1: array之所以对越界检测很严格,因为它类似于vector容器,在使用[]访问元素时会进行针对于越界的断言检查.
2: array相对于其他容器来说,array是在栈上开辟空间的,不适合开辟特别大的数组,否则容易导致栈溢出.
二:forward_list
forward_list本质来讲就是一个单链表.
forward_list很少被人使用,原因如下:
(1): 相对于list双向链表来说,结点少存储了一个指针,但是只针对于大量数据处理节省空间效果较大.
(2): forward_list 只支持在头插头删,不支持尾插尾删.因为单链表在进行尾插尾删的时候需要找尾,时间复杂度为O(N);
(3): forward_list的插入函数(insert_after) 表示在指定元素的后面插入一个元素,而不是像其他容器在指定元素的前面插入一个元素(这点对于删除函数(erase_after)类似),这对于我们平常的使用来说有点违和.
三:unordered_map和unordered_set
unordered_map和unordered_set的本质来说都是哈希表.
对于这两个容器,博主已经为其作了详细的讲解:
(1) 哈希表(底层结构剖析-- 上)
(2)哈希表(底层结构剖析–下)
(3) C++STL详解(十) – 使用哈希表封装unordered_set和unordered_map
内部变化
(1): STL容器都支持initializer_list构造,用来支持列表初始化.
(2): 提供了cbegin,cend方法,用于返回const迭代器,但是是比较鸡肋的,因为原本就支持普通对象调用普通迭代器,cons对象调用const迭代器就已经很方便了.
(3 ) 移动构造和移动赋值,提高效率,节省空间.
(4): 右值引用参数的插入.
说明:
(3),(4)也是C++11中的重点内容,以下系列博客中会进行详细讲解.