目录
[2.1.auto 和 decltype](#2.1.auto 和 decltype)
[2.2.范围 for 循环](#2.2.范围 for 循环)
[2.3.nullptr 与nullptr_t](#2.3.nullptr 与nullptr_t)
[2.4.= default](#2.4.= default)
[2.5.= delete](#2.5.= delete)
[2.10.constexpr 说明符](#2.10.constexpr 说明符)
[4.1.lambda 表达式](#4.1.lambda 表达式)
6.4.std::unordered_map和std::unordered_set
1.引言
最近也在面试,问C++11里面的某个具体内容倒不多,反而问 " C++11和C++98有哪些不同?" 的比较多。其实C++11从2011年发展到现在已经很多年了,普及率还是不高,以至于很多C++11的特性,开发者都不知道。今天这里就抛砖引玉,缩略的总结一下C++11增加的大部分内容。

2.语法特性
2.1.auto 和 decltype
auto: 引入了 auto
关键字,编译器可根据初始化表达式自动推导变量的类型,简化代码编写。如:
cpp
// C++98
std::map<int, std::string>::iterator it = myMap.begin();
// C++11
auto it = myMap.begin();
decltype:用于推导表达式类型,这里只用于编译器分析表达式的类型,表达式实际不会进行运算。(decltypde是不需要推导变量初始化的,根据的是表达式对变量的类型就可以推导。)如:
cpp
auto varname = value;
decltype(exp) varname = value;
decltype(10.8) x; //x 被推导成了 double
2.2.范围 for 循环
在C++11中,引入了范围for循环(Range-based for loop),它提供了一种简洁而直观的方式来遍历容器、数组、字符串和其他可迭代对象。
cpp
#include <vector>
#include <iostream>
int main() {
std::vector<int> vec = {1, 2, 3, 4, 5};
// C++98
for (std::vector<int>::iterator it = vec.begin(); it != vec.end(); ++it) {
std::cout << *it << " ";
}
std::cout << std::endl;
// C++11
for (auto num : vec) {
std::cout << num << " ";
}
return 0;
}
2.3.nullptr 与nullptr_t
- nullptr 是一个指针空值常量 , nullptr_t是一个指针空值**类型,**一个是常量,一个是类型。既然nullptr_t是一个类型,则说明两件事情:1)指针空值并非仅有nullptr一个,可以通过nullptr_t来声明一个指针空值类型的变量(用途不大)。2)nullptr_t是一个类型,不能直接使用,需要先声明一个实例。
- nullptr 是一个关键字,不需要包含任何头文件,使用nullptr_t的时候需 包含cstddef。
2.4.= default
在C++中声明自定义的类,编译器会默认帮助程序员生成一些他们未显示定义的成员函数,包括构造函数、拷贝构造函数、拷贝赋值函数(operator =)、移动构造函数、移动拷贝函数、析构函数。一旦程序员实现了这些函数的自定义版本,则编译器不会再为该类自动生成默认版本。例如,声明了带参数的构造函数版本,则必须声明不带参数的版本以完成无参的变量初始化,而这会导致对应的类不再是POD的(非POD类型,意味着编译器失去了优化这样简单的数据类型的可能),通过=default 可以使其重新成为POD类型,见如下几个例子。
cpp
#include <type_traits>
#include <iostream>
class A{
private:
int data;
};
class B{
public:
B(int i)
:data(i)
{}
private:
int data;
};
class C{
public:
C()
{}
C(int i)
:data(i)
{}
private:
int data;
};
class D{
public:
D() = default;
D(int i)
:data(i)
{}
private:
int data;
};
int main()
{
std::cout << std::is_pod<A>::value << std::endl;// 打印1
std::cout << std::is_pod<B>::value << std::endl;// 打印0
std::cout << std::is_pod<C>::value << std::endl;// 打印0
std::cout << std::is_pod<D>::value << std::endl;// 打印1
}
2.5.= delete
以前,程序员若希望限制一些默认函数的生成,例如,单件类的实现需要阻止其生成拷贝构造函数和拷贝赋值函数,可以将拷贝构造函数和拷贝赋值函数声明为private成员,并且不提供函数实现,C++11 标准给出了非常简洁的方法,即在函数的定义或者声明加上"= delete"。
cpp
#include <iostream>
class NoCopyConstructor
{
public:
NoCopyConstructor() = default;
NoCopyConstructor(int i)
:data(i)
{
}
private:
NoCopyConstructor(const NoCopyConstructor&);
private:
int data;
};
class NoCopyConstructor2
{
public:
NoCopyConstructor2() = default;
NoCopyConstructor2(int i)
:data(i)
{
}
NoCopyConstructor2(const NoCopyConstructor2&) = delete;
private:
int data;
};
int main()
{
NoCopyConstructor a;
NoCopyConstructor b(a);// 编译不过,报error: calling a private constructor of class 'NoCopyConstructor'
NoCopyConstructor2 a2;
NoCopyConstructor2 b2(a2);// 编译不过, 报error: call to deleted constructor of 'NoCopyConstructor2'
}
2.6.override
通过在派生类的成员函数声明中使用override
关键字(C++11及以后)来显式地指出该函数重写了基类中的虚函数。如果不使用override
关键字,编译器也会隐式地检查,但使用override
可以提高代码的可读性和安全性。
2.7.左值,右值
- 将亡值是C++11新增的跟右值引用相关的表达式,通常是要被移动的对象。右值引用,对右值进行引用的类型,因为右值通常没有名字,所以只能用引用的方式找到他的存在,举例,T&& a = ReturnRvalue(); ReturnRvalue()必须返回一个右值,不能返回左值。右值引用和移动语义紧密联系,可以延续右值的生命周期,并且可以对右值进行修改; 虽然可以定义常量右值引用,但是意义不大。
- 一般来说,左值引用不能接受一个右值,不过常量的左值引用是能接受一个右值的,比如,函数的引用传递,const T&, 可以减少临时对象的拷贝。
- is_rvalue_reference, is_lvalue_reference, is_reference 可以判断是左值引用、右值引用、引用。举例,is_revalue_reference<string &&>::value , value取值true。
- std::move , 可以强制将一个左值转化为右值,以便可以通过右值引用使用该值,从而实现移动语义。对堆内存、文件句柄等资源成员使用move,如果成员支持移动构造,则可以实现移动语义,若成员没有移动构造函数,参数类型为常量左值引用的构造函数版本也会轻松的实现拷贝构造;
cpp
#include <iostream>
#include <utility>
#include <vector>
#include <string>
using namespace std;
int main()
{
string str = "Hello World!";
vector<string> v;
// 使用 push_back(const T&) , 复制str的内容,不是移动
v.push_back(str);
// 打印如下:After copy, str is "Hello World!"
cout << "After copy, str is \"" << str << "\"\n";
// 使用右值引用 push_back(T&&) ,移动str的内容,不是复制,
v.push_back(move(str));
// 打印如下:After move, str is ""
cout << "After move, str is \"" << str << "\"\n";
// 打印如下(包含第一次复制进来的,总共两个):The contents of the vector are "Hello World!", "Hello World!"
cout << "The contents of the vector are \"" << v[0] << "\", \"" << v[1] << "\"\n";
}
2.8.初始化列表
C++11新增了列表初始化方式,方便了代码编写。声明一个以initializer_list<T> 模板类为参数的构造函数,自定义的类可以使用列表初始化方式。
cpp
int b[]{2,4,5};
vector<int> c{1,3,5};
map<int, float> d = {{1,1.0f}, {2, 2.0f}, {5, 3.2f}};
int a = {3+4};
int a{3+4};
int a(3+4);
vector<T>::vector<T>(initializer_list<T> elements)
{
......
}
std::vector<int> vec= {1,2,3,4,5};
2.9.final
指定某个虚函数不能在派生类中被覆盖,或者某个类不能被派生。如:
cpp
struct Base
{
virtual void foo();
};
struct A : Base
{
void foo() final; // Base::foo 被覆盖而 A::foo 是最终覆盖函数
void bar() final; // 错误:bar 非虚,因此它不能是 final 的
};
struct B final : A // struct B 为 final
{
void foo() override; // 错误:foo 不能被覆盖,因为它在 A 中是 final 的
};
struct C : B {}; // 错误:B 是 final 的
2.10.constexpr
说明符
constexpr
是 C++11 引入的说明符,用于告诉编译器某个函数、变量或类构造函数可以在编译期求值,从而将计算提前到编译阶段,提高程序效率并增强类型安全性。如:
cpp
template <int N>
struct Fibonacci {
constexpr static int value = Fibonacci<N-1>::value + Fibonacci<N-2>::value;
};
template <>
struct Fibonacci<0> { constexpr static int value = 0; };
template <>
struct Fibonacci<1> { constexpr static int value = 1; };
constexpr int fib_10 = Fibonacci<10>::value; // 编译期计算为 55
2.11.用户定义字面量
通过定义用户定义的后缀,允许整数、浮点数、字符及字符串字面量产生用户定义类型的对象。如:
cpp
long double operator ""_w(long double);
std::string operator ""_w(const char16_t*, size_t);
unsigned operator ""_w(const char*);
int main()
{
1.2_w; // 调用 operator ""_w(1.2L)
u"one"_w; // 调用 operator ""_w(u"one", 3)
12_w; // 调用 operator ""_w("12")
"two"_w; // 错误:没有适用的字面量运算符
}
3.内存管理
3.1.智能指针
引入了 std::unique_ptr
、std::shared_ptr
和 std::weak_ptr
三种智能指针。std::unique_ptr
实现独占式拥有,std::shared_ptr
实现共享式拥有,std::weak_ptr
则用于解决 std::shared_ptr
的循环引用问题。
cpp
#include <memory>
// C++11
std::unique_ptr<int> uniquePtr = std::make_unique<int>(42);
std::shared_ptr<int> sharedPtr = std::make_shared<int>(42);
3.2.右值引用
C++ 中的右值引用(Rvalue reference)是一种引用类型,它用于绑定到临时对象或将要被移动的对象(右值)。通过右值引用,我们可以对右值进行有效的操作,如移动语义和完美转发。
右值引用的语法是在类型后面加上 &&
,例如 int&&
表示一个右值引用到 int 类型的对象。右值引用只能绑定到右值,不能绑定到左值。
右值引用主要有两个重要的应用场景:移动语义和完美转发。
移动语义 : 右值引用使得我们可以实现高效的资源管理,尤其是在处理动态分配的内存或大型对象时。通过移动语义,我们可以将资源从一个对象转移到另一个对象,避免了不必要的拷贝开销。
通过定义移动构造函数和移动赋值运算符,并使用右值引用参数,可以实现对资源的高效转移。移动构造函数用于在构造对象时从临时或将要被销毁的对象中"窃取"资源,移动赋值运算符用于在对象已存在时将资源从右值赋值给对象。这样,在资源转移完成后,原始对象就不再拥有该资源,而新对象拥有该资源,避免了多余的内存分配和拷贝操作。
完美转发 : 完美转发是指在函数模板中保持参数的值类别(左值或右值)并将其转发到其他函数,以实现泛型编程中的通用参数传递。通过使用右值引用和模板参数推导,可以实现参数类型的自动推导和类型保持。
在函数模板中使用右值引用参数可以接收右值和左值,并保持参数的原始类型。结合 std::forward
可以实现完美转发,将参数以原始类型转发到其他函数。这样,在调用模板函数时,参数的值类别被保留,从而选择正确的函数进行处理。
右值引用在 C++11 中引入,它的出现在很大程度上优化了资源管理和提升了代码的性能。它为移动语义和完美转发提供了重要的基础,并在现代 C++ 开发中广泛应用。
4.改进对泛型编程的支持
4.1.lambda 表达式
支持创建匿名函数对象,方便在算法调用等场景中定义临时函数,例如:
cpp
std::sort(myList.begin(), myList.end(), [](int a, int b) { return a < b; });
4.2.变参模板
C++11 引入了变参模板(Variadic Template),它允许函数或类模板接受任意数量的参数。这使得我们能够定义更加灵活的函数和类模板,支持可变数量的参数。
在 C++11 中,使用 ...
表示变参模板。下面是一个简单示例:
cpp
#include <iostream>
// 使用变参模板实现递归打印函数
void print() {
std::cout << std::endl;
}
template<typename T, typename... Args>
void print(T first, Args... args) {
std::cout << first << " ";
print(args...);
}
int main() {
print(1, 2, 3, "Hello", 4.5); // 调用变参模板函数 print
return 0;
}
除了函数模板,变参模板也可以应用于类模板。通过对模板参数包使用 ...
,可以接受任意数量和类型的模板参数,从而实现更加灵活的类模板定义。
4.3.模板别名
使用using
关键字定义模板别名,简化复杂模板类型的声明,如:
cpp
using MapIntStr = std::map<int, std::string>;
4.4.std::function
std::function
是 C++ 标准库(<functional>
头文件)中的一个通用可调用对象包装器,能够封装普通函数、Lambda 表达式、函数指针、成员函数指针、仿函数(函数对象)等任何可调用对象,使其具有统一的调用接口。它是 C++ 泛型编程和回调机制的重要工具,支持类型擦除,允许将不同类型的可调用对象存储在同一种类型中。如:
cpp
struct Subtract {
int operator()(int a, int b) const { return a - b; }
};
std::function<int(int, int)> func = Subtract();
int result = func(10, 3); // 调用结果为 7
5.并发编程支持
5.1.线程库
标准库新增<thread>
、<mutex>
、<condition_variable>
等头文件,提供跨平台的线程创建、同步与通信机制,开发者可方便地编写多线程程序,如:
cpp
std::thread myThread(myFunction);
5.2.原子操作
<atomic>
头文件提供原子类型和操作,确保多线程环境下数据访问的原子性,避免数据竞争问题。
6.标准库容器和算法
6.1.std::tuple
6.2.std::array
固定大小数组,比原生数组更安全
6.3.std::forward_list
单向链表
6.4.std::unordered_map
和std::unordered_set
基于哈希表,提供快速查找
6.5.新算法
- std::all_of、std::any_of、std::none_of、
- std::find_if_not、
- std::copy_if、std::copy_n、
- std::move、std::move_backward、
- std::random_shuffle、std::shuffle、
- std::is_partitioned、std::partition_copy、std::partition_point、
- std::is_sorted、std::is_sorted_until、
- std::is_heap、std::is_heap_until、
- std::minmax、std::minmax_element、
- std::is_permutation、
- std::iota、
- std::uninitialized_copy_n
6.6.迭代器改进
7.时间库(std::chrono
)
综上所述,C++11 在语法、内存管理、并发编程、模板特性以及标准库等方面都有重大改进,使 C++ 语言更加现代化、高效和易用。
推荐阅读: