面试题:C++11在C++98基础上增加了哪些内容?

目录

1.引言

2.语法特性

[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.6.override

2.7.左值,右值

2.8.初始化列表

2.9.final

[2.10.constexpr 说明符](#2.10.constexpr 说明符)

2.11.用户定义字面量

3.内存管理

3.1.智能指针

3.2.右值引用

4.改进对泛型编程的支持

[4.1.lambda 表达式](#4.1.lambda 表达式)

4.2.变参模板

4.3.模板别名

4.4.std::function

5.并发编程支持

5.1.线程库

5.2.原子操作

6.标准库容器和算法

6.1.std::tuple

6.2.std::array

6.3.std::forward_list

6.4.std::unordered_map和std::unordered_set

6.5.新算法

6.6.迭代器改进

7.时间库(std::chrono)


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

C/C++中decltype关键字用法总结-CSDN博客

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;
}

C++实现自定义对象支持Range-based循环语法

2.3.nullptr 与nullptr_t

  • nullptr 是一个指针空值常量 , nullptr_t是一个指针空值**类型,**一个是常量,一个是类型。既然nullptr_t是一个类型,则说明两件事情:1)指针空值并非仅有nullptr一个,可以通过nullptr_t来声明一个指针空值类型的变量(用途不大)。2)nullptr_t是一个类型,不能直接使用,需要先声明一个实例。
  • nullptr 是一个关键字,不需要包含任何头文件,使用nullptr_t的时候需 包含cstddef。

有了NULL,为什么C++还需要nullptr?

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可以提高代码的可读性和安全性。

C++ 中的 override 和 overload的区别

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};

C/C++中{}的用法总结(全)
C++之std::initializer_list详解

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_ptrstd::shared_ptrstd::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);

C++ std::unique_ptr的使用及源码分析

3.2.右值引用

C++ 中的右值引用(Rvalue reference)是一种引用类型,它用于绑定到临时对象或将要被移动的对象(右值)。通过右值引用,我们可以对右值进行有效的操作,如移动语义和完美转发。

右值引用的语法是在类型后面加上 &&,例如 int&& 表示一个右值引用到 int 类型的对象。右值引用只能绑定到右值,不能绑定到左值。

右值引用主要有两个重要的应用场景:移动语义和完美转发。

移动语义 : 右值引用使得我们可以实现高效的资源管理,尤其是在处理动态分配的内存或大型对象时。通过移动语义,我们可以将资源从一个对象转移到另一个对象,避免了不必要的拷贝开销。

通过定义移动构造函数和移动赋值运算符,并使用右值引用参数,可以实现对资源的高效转移。移动构造函数用于在构造对象时从临时或将要被销毁的对象中"窃取"资源,移动赋值运算符用于在对象已存在时将资源从右值赋值给对象。这样,在资源转移完成后,原始对象就不再拥有该资源,而新对象拥有该资源,避免了多余的内存分配和拷贝操作。

C++之std::move(移动语义)-CSDN博客

完美转发 : 完美转发是指在函数模板中保持参数的值类别(左值或右值)并将其转发到其他函数,以实现泛型编程中的通用参数传递。通过使用右值引用和模板参数推导,可以实现参数类型的自动推导和类型保持。

在函数模板中使用右值引用参数可以接收右值和左值,并保持参数的原始类型。结合 std::forward 可以实现完美转发,将参数以原始类型转发到其他函数。这样,在调用模板函数时,参数的值类别被保留,从而选择正确的函数进行处理。

C++之std::forward(完美转发)-CSDN博客

右值引用在 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;
}

除了函数模板,变参模板也可以应用于类模板。通过对模板参数包使用 ...,可以接受任意数量和类型的模板参数,从而实现更加灵活的类模板定义。

深入理解可变参数(va_list、std::initializer_list和可变参数模版)

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);

std::thread使用及实现原理精讲(全)

5.2.原子操作

<atomic>头文件提供原子类型和操作,确保多线程环境下数据访问的原子性,避免数据竞争问题。

C++11原子操作

6.标准库容器和算法

6.1.std::tuple

C++之std::tuple(一) : 使用精讲(全)

6.2.std::array

固定大小数组,比原生数组更安全

6.3.std::forward_list

单向链表

6.4.std::unordered_mapstd::unordered_set

基于哈希表,提供快速查找

6.5.新算法

6.6.迭代器改进

C++标准库之std::begin、std::end、std::pre和std::next

7.时间库(std::chrono)

C++日期和时间库

综上所述,C++11 在语法、内存管理、并发编程、模板特性以及标准库等方面都有重大改进,使 C++ 语言更加现代化、高效和易用。

推荐阅读

https://zh.cppreference.com/w/cpp/11

相关推荐
努力学习的小廉1 分钟前
【C++】 —— 笔试刷题day_27
开发语言·c++
双叶83617 分钟前
(51单片机)LCD显示红外遥控相关数字(Delay延时函数)(LCD1602教程)(Int0和Timer0外部中断教程)(IR红外遥控模块教程)
c语言·数据库·c++·单片机·嵌入式硬件·mongodb·51单片机
无聊的小坏坏18 分钟前
【C++】string类
c++
周努力.20 分钟前
设计模式之状态模式
设计模式·状态模式
蜗牛沐雨22 分钟前
Rust 中的 Pin 和 Unpin:内存安全与异步编程的守护者
服务器·开发语言·rust
Despacito0o35 分钟前
QMK键盘固件配置详解
c++·计算机外设·mfc
python算法(魔法师版)38 分钟前
JavaScript性能优化实战,从理论到落地的全面指南
开发语言·性能优化·前端框架·代理模式
凌祈丶微光1 小时前
【C++】【数据结构】【API列表】标准库数据结构
数据结构·c++·标准库
shmily麻瓜小菜鸡1 小时前
vue3使用tailwindcss报错问题
开发语言·前端·javascript·vue.js
爱吃程序猿的喵1 小时前
STM32硬件I2C驱动OLED屏幕
c++·stm32·单片机·嵌入式硬件·c·硬件·i2c