C++基础面试题 | C++11中有哪些常用的新特性?

江碧鸟逾白,山青花欲燃。 - 《绝句二首》(杜甫)

回答重点

1. C++11简介

2. 统一的列表初始化

3. std::initializer_list

4. auto和decltype

5. nullptr

6. 范围for循环

7. 智能指针

8. 新增加的容器

9. 默认成员函数控制

10. 右值引用、移动语义、完美转发和bind绑定

右值引用和移动语义

完美转发/万能引用

bind绑定

11. lambda表达式

12. 线程库


1. C++11简介

在2003年,C++标准委员会提交了一份技术勘误表(TC1),使得C++03成为C++98之后的最新标准。然而,C++03主要是对C++98标准中的漏洞进行修复,语言的核心部分并没有大的改动。因此,人们通常将这两个标准合并称为C++98/03标准。从C++0x到C++11,C++标准经过了长达十年的发展,带来了约140个新特性和对C++03标准中约600个缺陷的修正。这使得C++11更像是从C++98/03中孕育出的一种新语言。

2. 统一的列表初始化

在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扩大了初始化列表的使用范围,使其适用于所有内置类型和用户自定义类型。使用初始化列表时,可以添加等号=,也可以不添加。

Point p{1, 2}; // 用户自定义类型对象的初始化 OK

3. std::initializer_list

std::initializer_list是C++11中引入的一个模板类,元素数组的引用,用于封装初始化列表的语法结构,使得初始化容器对象更加方便。

#include <initializer_list>

class Matrix {
public:
    Matrix(std::initializer_list<double> init) {
        for (auto value : init) {
            // 处理初始化数据
        }
    }
};

int main() {
    Matrix m = {{1.0, 2.0, 3.0}, {4.0, 5.0, 6.0}};
}

注意1:C++11后,STL中的容器,如vectorlistmap等都增加了接受std::initializer_list作为参数的构造函数。

int main() {
    vector<int> v = {1, 2, 3, 4};
    list<int> lt = {1, 2};
    map<string, string> dict = { {"sort", "排序"}, {"insert", "插入"} };
    return 0;
}

注意2:std::initializer_list类型不能使用引用传参,因为内部实现已经使用引用提升效率

Matrix(std::initializer_list<double>& init) { // <- 错误!
        for (auto value : init) {
            // 处理初始化数据
        }
    }

4. auto和decltype

C++98中,auto用于声明具有自动存储期的变量,但是局部变量默认就是具有自动存储期的,因此auto关键字在C++98中的作用并不大。

C++11废除了C++98中auto的定义,引入了新的autodecltype两个关键字,用于简化类型声明。

  • auto用于自动类型推导,要求必须进行显示初始化。(在STL复杂变量声明中好用)
  • decltype将变量的类型声明为表达式指定的类型。
int main() {
    int i = 10;
    auto p = &i;  // p的类型是int*
    list<int> l = {1,2,3,4};
    auto pl = l.begin();// pl的类型是list迭代器类型
    decltype(i) j = i;  // j的类型是int
    return 0;
}

5. nullptr

NULL 在C++中的问题主要源于它与整数 0 的等价性,这可能导致类型混淆和逻辑错误。为了解决NULL被定义成字面量0带来的问题,C++11引入了nullptr,用于表示空指针。

int main() {
    int* p = nullptr;
    return 0;
}        

6. 范围for循环

范围for循环是C++11中引入的一种新的循环方式,它可以直接迭代容器中的元素。

int main() {
    vector<int> v = {1, 2, 3, 4};
    for (auto& e : v)
    {
        cout << e << ' ';
    }
    
    return 0;
}

笔记:范围for底层由迭代器实现,自定义类型实现迭代器后也可以使用范围for遍历内部元素。

// 范围for的内部实现
for (auto it = v.begin(); it != v.end(); ++it) 
{
    cout << *it << endl;
}

7. 智能指针

C++11引入了智能指针,用于自动管理动态分配的内存。智能指针主要有三种:unique_ptrshared_ptrweak_ptr

int main() {
    unique_ptr<int> p1(new int(10));
    shared_ptr<int> p2(new int(20));
    weak_ptr<int> p3(p2);
    return 0;
}

单独写了篇博客~链接如下:

C++ | 一文读懂C++11中的智能指针(附模拟实现):C++内存管理的现代解决方案-CSDN博客

8. 新增加的容器

C++11增加了一些新的STL容器,如arrayforward_listunordered_map等。

int main() {
    array<int, 5> arr = {1, 2, 3, 4, 5};
    forward_list<int> fl = {1, 2, 3};
    unordered_map<string, int> um = {{"one", 1}, {"two", 2}};
    return 0;
}

附上接口链接~:

http://www.cplusplus.com/reference/vector/vector/emplace_back/

http://www.cplusplus.com/reference/vector/vector/push_back/

http://www.cplusplus.com/reference/map/map/insert/

http://www.cplusplus.com/reference/map/map/emplace/

9. 默认成员函数控制

C++11允许开发者更好地控制默认成员函数的生成。可以使用default关键字强制生成默认函数,使用delete关键字禁止生成默认函数。

class Person {
public:
    Person(const char* name = "", int age = 0) : _name(name), _age(age) {}
    Person(const Person& p) = default;
    Person& operator=(const Person& p) = delete;
private:
    bit::string _name;
    int _age;
};

笔记:C++11前进行特殊类设计,可以将函数声明为private达到delete的效果,然后提供public接口获取相关资源。

C++ | 深入理解C++中的特殊类设计和单例模式(懒汉模式、饿汉模式)-CSDN博客

10. 右值引用、移动语义、完美转发和bind绑定
右值引用和移动语义

C++11引入了右值引用和移动语义,用于提高资源的利用效率。

  • 右值引用允许对即将被销毁的对象进行引用,从而实现资源的转移。
  • 移动构造函数和移动赋值运算符允许在对象间转移资源,而不是进行复制。
class string {
public:
    string(string&& s) : _str(s._str), _size(s._size), _capacity(s._capacity) {
        s._str = nullptr;
        s._size = 0;
        s._capacity = 0;
    }
    string& operator=(string&& s) {
        _str = s._str;
        _size = s._size;
        _capacity = s._capacity;
        s._str = nullptr;
        s._size = 0;
        s._capacity = 0;
        return *this;
    }
private:
    char* _str;
    size_t _size;
    size_t _capacity;
};

完美转发/万能引用

std::forward 完美转发在传参的过程中保留对象原生类型属性

#include<iostream>
using namespace std;

void Fun(int& x) { cout << "左值引用" << endl; }
void Fun(const int& x) { cout << "const 左值引用" << endl; }
void Fun(int&& x) { cout << "右值引用" << endl; }
void Fun(const int&& x) { cout << "const 右值引用" << endl; }

// 模板中的&&不代表右值引用,而是万能引用,其既能接收左值又能接收右值。
template<typename T>
void PerfectForward(T && t) // 可以接收任何类型的变量
{
    Fun(forward<T>(t)); // 根据传入参数的类型,决定调用哪个版本的Fun
}


int main()
{
    int a = 10;
    PerfectForward(10);
    PerfectForward(a);// 左值
    PerfectForward(std::move(a)); // 右值
    const int b = 8;
    PerfectForward(b);// const 左值
    PerfectForward(std::move(b)); // const 右值
    return 0;
}

解释:

1、模板中的&&不代表右值引用,而是万能引用,其既能接收左值又能接收右值。

2、接收变量t后 t坍塌为左值。

3、完美转发forward自动识别t的类型,如果t原本就是左值不做处理,如果为右值则move之后传递给下层

bind绑定

std::bind 是 C++ 标准库中 <functional> 头文件提供的一个模板函数,它用于创建一个新的可调用对象 —— 通常是一个函数对象或 lambda 表达式 —— 的绑定版本。这个绑定版本可以固定原函数对象的某些参数,从而产生一个新的函数对象,这个新函数对象可以接受剩余的参数。

定义:std::bind(fn, arg1, arg2, ..., argN)

  • fn 是要绑定的可调用对象,可以是普通函数、成员函数、函数对象或 lambda 表达式。
  • arg1argN 是要绑定的参数,它们将被固定在新创建的函数对象中。
  • std::bind 还使用特殊的参数占位符 _1, _2, _3, ... 来代表新函数对象将要接收的参数。这些占位符在调用新函数对象时,会被实际的参数替换。
#include<iostream>
#include<functional> // bind函数头文件
#include<cmath>

using namespace std;
using placeholders::_1;
using placeholders::_2;
using placeholders::_3;
using placeholders::_4;

int main()
{
    auto fun1 = [](int x, double money, double percent){
         return money*pow((percent+1), x);
    };

    // 使用 std::bind 绑定函数参数
    // _1表示这个参数是fun_1year的第一个参数
    auto fun_1year = std::bind(fun1, 1, _1, 0.01);
    auto fun_3year = std::bind(fun1, 3, _1, 0.015);
    auto fun_5year = std::bind(fun1, 5, _1, 0.02);
    auto fun_10year = std::bind(fun1, 10, _1, 0.025);

    // 计算并输出不同年限的投资增长
    cout << "1年后: " << fun_1year(10000) << endl;
    cout << "3年后: " << fun_3year(10000) << endl;
    cout << "5年后: " << fun_5year(10000) << endl;
    cout << "10年后: " << fun_10year(10000) << endl;
    return 0;
}

11. lambda表达式

C++11引入了lambda表达式,允许在代码中定义匿名函数。

Lambda 表达式的一般语法是:

[capture](parameters) mutable -> return_type {
    // function body
}
  • [capture]:捕获子句,用于定义从包围作用域中捕获哪些变量,以及如何捕获(通过值或引用)。
  • (parameters):参数列表,与普通函数参数列表类似。
  • mutable:可选关键字,允许在 Lambda 表达式内部修改捕获的变量。
  • -> return_type:返回类型,如果 Lambda 表达式返回值,则需要指定返回类型;如果 Lambda 表达式不返回值,则可以省略。
  • {...}:函数体,包含 Lambda 表达式的代码。
  • 根本看不懂?... 没关系!!!看用法就明白了~~
#include<iostream>
using namespace std;
int main()
{
    // 定义一个用于整形加法的匿名函数add
    auto add = [](int x, int y)->int{return x + y;};
    cout << add(1,2) << endl;
    // 输出结果:3
    return 0;
}

lambda函数在很多时候比仿函数好用(比如自定义sort比较函数)。

int main() {
    vector<Goods> v = {{"苹果", 2.1, 5}, {"香蕉", 3, 4}, {"橙子", 2.2, 3}, {"菠萝", 1.5, 4}};
    sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {
        return g1._price < g2._price;
    });
    return 0;
}

12. 线程库

C++11引入了对多线程的支持,使得并行编程更加容易。

int main() {
    thread t1([]() { cout << "Thread1" << endl; });
    thread t2([]() { cout << "Thread2" << endl; });
    t1.join();
    t2.join();
    return 0;
}

  • 6
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值