序言
C++11 是 C++ 编程语言的一个重要版本,于 2011 年由国际标准化组织 (ISO) 和国际电工委员会 (IEC) 旗下的 C++ 标准委员会 (ISO/IEC JTC1/SC22/WG21) 正式公布,并于同年 9 月出版。其正式名称为 ISO/IEC 14882:2011 - Information technology – Programming languages – C++ 。C++11 是 C++98 发布后 13 年来的第一次重大修正,它引入了 140 多个新特性和改进,使得 C++ 语言更加现代化、易用和强大。
在这几篇文章中,笔者将介绍新特性中比较重要的那些,让大家感受到 C++11 为现代 C++ 编程带来的变革和增强。😆
1. 自动类型推导
1.1 auto 关键字
auto 允许编译器自动推导变量的类型,简化了变量声明。就比如:auto A = 1;
编译器会自动推导出 A
的类型是 int
,但是这种用法简直是大材小用😲,换一个复杂的场景,就要比如,我们现在有一个如下的 vector
:
std::vector<std::pair<std::string, int>> arr;
现在我们想要获取该 vector
的迭代器,正常流程应该是这样的吧:
std::vector<std::pair<std::string, int>>::iterator it = arr.begin();
但是现在有了 auto
之后,我们可以直接表示为:auto it = arr.begin();
。
虽然 auto
极大的便捷了我们的书写,但是会大大的降低代码的可读性😵,就比如:
auto Func(const int num){
std::vector<int> arr;
for(size_t i = 0; i < num; ++i){
arr.push_back(i);
}
return arr;
}
int main(){
auto ret = Func(5);
return 0;
}
原来我们一眼就能看出的返回值类型,现在需要需要到具体的函数查看大体细节。
总结起来就是,我们可以使用 auto
来便利我们的书写,但是我们不能依赖于他😖!
1.2 decltype 关键词
decltype
用于在编译时查询表达式的类型。decltype
可以避免显式地写出复杂的类型名称,特别是在模板编程、自动类型推导以及需要精确类型信息的场景中,就比如:
int main() {
int x = 42;
double y = 3.14;
// 使用 decltype 推导 x 的类型
decltype(x) z = 100; // z 的类型是 int
// 使用 decltype 推导表达式的类型
decltype(x + y) sum = x + y; // sum 的类型是 double
return 0
}
2. 范围for循环
范围 for 循环
是 C++11 引入的一种新的循环语法,它简化了对容器(如 std::vector、std::list 等)或数组遍历的代码编写。范围for循环
能够自动处理容器的迭代过程,使得遍历容器元素变得更加直观和简洁。格式如下:
// Container 是具体的容器结构
for(auto elem : Container){
// 对容器中元素的具体操作
}
在这里提一嘴:这个只能支持本身就支持可以遍历的容器,Stack,Queue等 容器本身不支持遍历的容器,是不支持的哈。
就比如我想要遍历我的 vector
中的元素,就可以表示为:
void test_1() {
vector<int> arr = { 1, 2, 3 ,4 };
for (auto e : arr) {
cout << e << " ";
}
cout << endl;
}
2.1 遍历的元素为深拷贝
如果我想要利用 范围 for 循环
将我的所有 vector
元素加一,那是这样的吗:
void test_2() {
vector<int> arr = { 1, 2, 3 ,4 };
for (auto e : arr) {
++e;
}
for (auto e : arr) {
cout << e << " ";
}
cout << endl;
}
你可以发现,元素并未发生变化,这是因为 e是vector中每一个元素的深拷贝
,这个要牢记哈。那怎么解决呢?很简单:
for (auto& e : arr) {
++e;
}
我们只需要加上引用就好啦😚。当我们遍历时也通常加上引用符号,这是因为,如果不引用,容器中的元素都是需要动态申请空间的话,那遍历时拷贝的代价就太大了。
2.2 本质是使用了迭代器
你可以简单的认为 范围 for 循环
可以转化为如下形式:
void test_3() {
vector<int> arr = { 1, 2, 3 ,4 };
auto it = arr.begin();
while (it != arr.end()) {
cout << *it << " ";
++it;
}
}
所以说如果你想要你的自定义容器也支持 范围 for 循环
,那就必须要如下前提:
- 你的容器实现了迭代器
- 你的迭代器支持,++,!=
3. 统一的列表初始化
3.1 {} 用法
在 C++98 版本中我们可以使用 {}
对数组或者是结构体进行初始化:
class Test {
public:
Test(int A, int B) {
_A = A;
_B = B;
}
private:
int _A;
int _B;
};
void test_4() {
int arr[] = { 1, 2, 3, 4 };
Test t1 = { 1, 2 };
}
在 C++11 中,{}
可以初始化的对象包括基本类型、复合类型(如结构体、类)以及 STL 容器等。这种语法提供了一种一致且清晰的初始化方式,避免了之前不同初始化方式可能带来的混淆和错误:
void test_5() {
// 对内置类型初始化
int A = { 1 };
int B{ 2 }; // 甚至可以去除 =
// 对容器初始化
vector<int> arr = {1, 2, 3, 4};
// 对自定义类型初始化
Test t2{ 1, 2 };
// 对 new 表达式初始化
int* ptr = new int[2] {1, 2};
delete[] ptr;
}
3.2 initializer_list — {} 背后的男人
{}
在背后是如何对容器进行初始化的呢?靠的是 initializer_list
。他具体是:
- 类型:
std::initializer_list<T>
是一个模板类,其中T
是列表中元素的类型。 - 用途:主要用于构造函数和函数调用的初始化列表中,允许以花括号
{}
包围的列表形式传递多个值。 - 特性:
initializer_list
是轻量级的,它不拥有它所包含的元素;它仅仅是对现有数据的引用。因此,使用initializer_list
时需要注意生命周期问题,确保initializer_list
引用的数据在initializer_list
被使用时仍然有效。
4. 关键字 override,final
override
override
修饰一个成员函数,代表你想要重写该函数,如果没有达到重写的条件就会报错。就比如:
final
当 final
修饰一个成员函数,代表该函数不可以被重写。就比如:
当 final
修饰一个类,代表该类不可以被继承。就比如:
5. 关键字 nullptr
在 C 语言中, NULL
的定义是:
#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)#endif
#endif
由于 C++ 中 NULL
被定义成字面量 0,这样就可能回带来一些问题,因为 0 既能指针常量,又能表示整形常量。所以出于清晰和安全的角度考虑,C++11 中新增了 nullptr
,仅用于表示空指针。
6 总结
还有些重要的新特性,将在接下来的时间慢慢更新,特别重要的特性会作为大章节,详细讲解。