序言:
- 从本期开始,我将会带大家学习的是关于C++11 新增的相关知识!废话不多说,我们直接开始今天的学习。
(一)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能更好地用于系统开发和库开发、语法更加泛华和简单化、更加稳定和安全,不仅功能更强大,而且能提升程序员的开发效率,公司实际项目开发中也用得比较多,所以我们要作为一个重点去学习。C++11增加的语法特性非常篇幅非常多,我们这里没办法一 一讲解,所以本节课程主要讲解实际中比较实用的语法。
- 大家在学习过程中也可以参考文档:C++11 - cppreference.com
【知识拓展】
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++11 的第一个新增特性——有关统一的列表初始化的相关知识!
1、{}初始化
使用大括号 {} 进行初始化可以在不同的情况下提供一致的行为,并且具有一些特殊的初始化规则和语义。
下面是一些 C++11 中使用大括号进行初始化的示例:
- 直接初始化:
int main() { int x{ 42 }; // 使用大括号直接初始化 x 为 42 string str{ "Hello" }; // 使用大括号直接初始化字符串对象 cout << x << endl; cout << str << endl; return 0; }
输出展示:
- 默认初始化:
int main() { int x{}; // 使用大括号进行默认初始化,x 的值为 0 string str{}; // 使用大括号进行默认初始化,str 为空字符串 cout << x << endl; cout << str << endl; return 0; }
输出展示:
- 数组或结构体初始化
在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++11扩大了用大括号括起的列表(初始化列表)的使用范围,使其可用于所有的内置类型和用户自定义的类型,使用初始化列表时,可添加等号(=),也可不添加。
代码展示:
int main() { int x1 = 1; int x2{ 2 }; cout << x1 << endl; cout << x2 << endl; return 0; }
输出展示:
因此,对于上述结构体和数组的初始化操作,在C++11中可以转化为下面这样:
struct Point { int _x; int _y; }; int main() { //c++98 /*int array1[] = { 1, 2, 3, 4, 5 }; int array2[5] = { 0 }; Point p = { 1, 2 };*/ //c++11 int array1[]{ 1, 2, 3, 4, 5 }; int array2[5]{ 0 }; Point p{ 1, 2 }; return 0; }
输出展示:
- 创建对象时也可以使用列表初始化方式调用构造函数初始化
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() { // old style Date d1(2022, 1, 1); // C++11支持的列表初始化,这里会调用构造函数初始化 Date d2{ 2022, 1, 2 }; Date d3 = { 2022, 1, 3 }; return 0; }
输出展示:
【解释说明】
- 这里的本质是构造+拷贝构造,经过优化后。如果不想编译器进行优化设置,可以加上 explicit即可。
【小结】
- 使用大括号初始化在很多情况下都比传统的赋值或括号初始化更加安全和灵活;
- 它可以用于解决窄化转换问题,提供统一的初始化语法,并且可以进行更严格的类型检查。
2、std::initializer_list
std::initializer_list 是 C++11 引入的标准库类型,用于方便地表示初始化列表。它是一个轻量级的容器类,用于存储一组相同类型的值。
使用 std::initializer_list 可以将一组值作为参数传递给函数或构造函数,或者用于对象的初始化。它的语法类似于大括号 {}
初始化,但提供了更灵活的传递和使用方式。
下面是一些 std::initializer_list 的常见用法:
- 作为参数传递给函数
void Point(initializer_list<int> values) { for (const auto& value : values) { cout << value << " "; } cout << endl; } int main() { Point({ 1, 2, 3, 4 }); // 使用初始化列表作为参数 return 0; }
输出展示:
- 作为对象的构造函数参数
class MyClass { public: MyClass(initializer_list<int> values) { // 在构造函数中使用初始化列表 for (const auto& value : values) { // 处理每个值 cout << value << " "; } } }; int main() { MyClass obj({ 1, 2, 3, 4 }); // 使用初始化列表作为构造函数参数 return 0; }
输出展示:
- 在范围基于循环中使用
int main() { initializer_list<int> values = { 1, 2, 3, 4 }; for (const auto& value : values) { // 处理每个值 cout << value << " "; } return 0; }
输出展示:
std::initializer_list 的主要优点是它提供了一种简洁、直观的方式来传递初始化列表,特别适用于带有不确定数量参数的函数或构造函数。它允许以类似于标准容器的方式进行遍历和访问其中的值。
那是因为在 C++11 中,学从vector 提供了以下构造函数:
注意:std::initializer_list 是一个只读容器,不能进行元素的插入、删除或修改。它的元素类型是常量,因此通常会使用 const auto&
来遍历元素。
1.插入报错
2.删除报错
3.修改报错
【小结】
- 总之,std::initializer_list 提供了一种简洁而方便的方式来处理初始化列表,并且在 C++11 及更高版本中广泛使用;
- 它使我们能够以一种更直观、类型安全的方式处理初始化列表,并简化了函数和构造函数的参数传递。
(三)声明
c++11提供了多种简化声明的方式,尤其是在使用模板时。
1、auto
在C++98中auto是一个存储类型的说明符,表明变量是局部自动存储类型,但是局部域中定义局部的变量默认就是自动存储类型,所以auto就没什么价值了。C++11中废弃auto原来的用法,将其用于实现自动类型推断。这样要求必须进行显示初始化,让编译器将定义对象的类型设置为初始化值的类型。
下面是一些关于 auto
的使用示例:
- 自动类型推断
int main() { auto num = 42; // 推断 num 的类型为 int auto str = "Hello"; // 推断 str 的类型为 const char* auto arr = std::vector<int>(); // 推断 vec 的类型为 std::vector<int> return 0; }
输出展示:
在这些示例中,auto
关键字根据变量的初始化表达式推断出变量的类型,并相应地声明变量。
- 与范围基于循环结合使用(这个我们一直都在用就不多说了)
- 函数返回类型推断
auto add(int a, int b) { return a + b; }
在这个示例中,auto
用于推断函数的返回类型。根据返回表达式的类型,编译器会自动推断出函数的返回类型。
【小结】
auto
推断的类型是在编译时确定的,而不是运行时。这意味着使用auto
声明的变量在编译时会根据初始化表达式的类型进行类型推断,然后在后续的代码中被视为具有确定的类型。- 另外,由于
auto
是根据初始化表达式推断类型,因此在一些情况下可能会导致意外的类型推断结果或不明确的类型。在这些情况下,可以使用显式的类型声明或其他更具体的类型推断工具(例如类型转换操作符)来消除歧义。
2、decltype
关键字decltype将变量的类型声明为表达式指定的类型。
代码举例:
int main() { const int x = 1; double y = 2.2; decltype(x * y) ret; // ret的类型是double decltype(&x) p; // p的类型是 const int* cout << typeid(ret).name() << endl; cout << typeid(p).name() << endl; return 0; }
输出展示:
3、nullptr
由于C++中NULL被定义成字面量0,这样就可能回带来一些问题,因为0既能指针常量,又能表示
整形常量。所以出于清晰和安全的角度考虑,C++11中新增了nullptr,用于表示空指针。
#ifndef NULL #ifdef __cplusplus #define NULL 0 #else #define NULL ((void *)0) #endif #endif
(四)范围for循环
这个我们在前面的课程中已经进行了非常详细的讲解,这里就不进行讲解了。
(五)STL中一些变化
新容器
用橘色圈起来是C++11中的一些几个新容器,但是实际最有用的是unordered_map和
unordered_set。这两个我们前面已经进行了非常详细的讲解,其他的大家了解一下即可
总结
以上便是本期关于 C++11 新特性的介绍。以上内容在校招中属于了解内容,大家知道会用即可!