C++ 面试准备 八股(二)C++11新特性、STL

type right by Thomas Alan 光风霁月023 .XDU
发现一个方便看的小工具 如何生成目录索引

一、C++11

C++11是C++语言的一个重要版本,它引入了许多新特性和语言扩展,使得C++变得更加现代化、安全和高效。下面是一些C++11的新特性:

1. 自动类型推导(auto):可以使用auto关键字进行类型推导,使得代码更加简洁,减少重复代码。

  使用auto可以简化代码,提高代码的可读性和可维护性。常见的使用场景包括:
  1) 迭代器类型:可以用auto定义迭代器类型,避免显式地指定类型,减少代码量,提高可读性。

vector vec = {1, 2, 3, 4, 5};
for (auto it = vec.begin(); it != vec.end(); ++it) {
    cout << *it << endl;
}

  2) 函数返回类型:可以用auto作为函数返回类型,编译器会自动推导返回值类型。

auto sum(int a, int b) {
    return a + b;
}

  3) 模板编程:可以用auto作为模板参数,编译器会根据实际类型推导出对应的类型。

template 
auto add(T t, U u) -> decltype(t + u) {
    return t + u;
}

2. 声明式循环语句(range-based for loop):可以通过类似foreach的语法遍历数组、容器等集合类型的元素。

for (auto& element : container) {
    // 循环体
}

  其中,auto 表示使用类型推导来确定变量的类型,element 表示容器中的元素,container 表示需要遍历的容器。

  使用 range-based for 循环时,无需通过下标或迭代器来访问容器中的元素,也无需手动计数或判断循环终止条件,循环体内部直接使用 element 变量来访问当前元素即可。例如,使用 range-based for 循环遍历一个 std::vector 容器:

std::vector vec {1, 2, 3, 4, 5};
for (auto& element : vec) {
    std::cout << element << " ";
}

  使用 range-based for 循环可以简化代码,同时也更加安全,因为它可以自动遍历容器中的所有元素,而不会越界。

3. 智能指针(smart pointer):包括unique_ptr、shared_ptr和weak_ptr,用于管理动态分配的内存,减少内存泄漏的风险。

  C++11引入了智能指针(smart pointers),用于自动管理内存,避免手动管理内存带来的错误和繁琐。智能指针的主要目的是通过自动管理内存来解决内存泄漏和悬挂指针等问题。
  智能指针是一个对象,它的行为类似于一个指针,但它会自动管理内存,当指针不再使用时,它会自动释放资源。C++11中提供了三种智能指针:
  1)unique_ptr:独占所有权的智能指针,不能被复制或共享,当其所指对象被删除时,它所拥有的指针也会被自动删除。
  2)shared_ptr:共享所有权的智能指针,可以被多个指针共享,当最后一个指向所拥有的对象的 shared_ptr 被销毁时,对象才会被销毁。
  3)weak_ptr:弱引用智能指针,是一种特殊的 shared_ptr,它并不会增加引用计数,所以它不会阻止所指对象的销毁,一般用于解决 shared_ptr 循环引用的问题。
  shared_ptr 循环引用 指的是在使用 shared_ptr 时,两个或多个对象之间相互持有对方的 shared_ptr,导致资源无法释放的情况。这种情况也被称为循环依赖或循环引用。例如,对象 A 持有对象 B 的 shared_ptr,对象 B 同时也持有对象 A 的 shared_ptr,那么 A 和 B 就会形成一个循环引用,它们的引用计数永远不会到达零,资源无法释放,导致内存泄漏。为避免这种情况发生,可以使用 weak_ptr 来打破循环引用。weak_ptr 是一种弱引用,它可以指向 shared_ptr 指向的对象,但不会增加其引用计数。如果需要使用 shared_ptr 操作所指向的对象,可以使用 weak_ptr 的 lock() 方法获得一个指向对象的 shared_ptr,这个 shared_ptr 不会增加对象的引用计数,但是可以使用 shared_ptr 操作对象,当 shared_ptr 被销毁时,对象的引用计数会相应减一。使用 weak_ptr 可以避免循环引用的问题,同时确保资源正常释放。
  智能指针的使用可以简化内存管理,避免内存泄漏和悬挂指针等问题,同时也减少了代码量和程序复杂度。
  注: auto_ptr 是最早的智能指针,被 C++11 标准废弃了。它实现了简单的资源管理功能,但有一些缺点,比如不能共享所有权,且容易造成悬空指针等问题。悬空指针(dangling pointer)是指指向已经被释放或者不存在的内存区域的指针。当一个指针被释放或者它所指向的内存区域被释放后,如果没有将指针置为空或者重新指向一个新的内存区域,那么这个指针就会成为悬空指针。使用悬空指针会导致程序崩溃或者产生其他的不可预测行为,因此需要避免出现悬空指针。

4. 右值引用(rvalue reference):支持将右值引用作为函数参数,避免了不必要的内存拷贝,提高程序效率。

  1) 什么是左值右值
  左值(Lvalues)是指在表达式结束后依然存在的持久化对象或函数。简单来说,左值就是有名字的变量或者对象。
  右值(Rvalues)是指在表达式结束时就被销毁的临时对象或值,它们没有持久化的身份。简单来说,右值就是没有名字的临时变量或者常量。

int x = 10;     // x是左值
int y = x + 5;  // x + 5是右值表达式
int* p = &x;    // &x是左值表达式
int a[] = {1, 2, 3}; // a是左值
int b = a[0];   // a[0]是右值

  2) 右值引用的作用
  i) 移动语义(Move Semantics)
  见5. 移动语义

  ii) 完美转发(Perfect Forwarding)
  完美转发是指在模板函数中将参数以原有的方式传递给其他函数,保留参数的左值或右值属性。在模板函数中,如果使用左值引用作为参数类型,会把所有传递给该函数的参数都视为左值,而如果使用右值引用作为参数类型,则会把所有传递给该函数的参数都视为右值。通过右值引用和完美转发,我们可以保留参数的左值或右值属性,从而更加灵活地处理函数参数。

  3) 右值引用的例子

// 1. 将一个右值引用绑定到一个临时对象上,例如:
std::string getName()
{
    return "John Doe";
}
std::string&& name = getName();
// 在这个例子中,getName()返回一个临时的 std::string 对象,可以将其用一个右值引用 name 来绑定。

// 2. 将一个右值引用绑定到一个临时对象上,例如:
void someFunction(std::string&& str)
{
    // ...
}
someFunction(std::string("hello"));
// 在这个例子中,str 参数是一个右值引用,可以使用一个临时的 std::string 对象来调用该函数:

// 3. 在移动语义中使用右值引用,例如:
std::unique_ptr ptr1(new int(42));
std::unique_ptr ptr2(std::move(ptr1));
// 在这个例子中,std::move() 函数返回一个右值引用,它将 ptr1 转换为一个右值引用,这使得可以使用移动语义将 ptr1 的所有权转移到 ptr2 中。

5. 移动语义(move semantics):支持将对象的所有权从一个对象移动到另一个对象,提高程序效率。

  在 C++11 之前,对象的复制通常是通过拷贝构造函数和赋值操作符来完成的,这会导致大量的内存复制和动态内存分配,效率很低。而右值引用的出现,使得我们可以通过移动构造函数和移动赋值操作符来实现对象之间的数据转移,从而避免了大量的内存复制和动态内存分配,提高了程序的效率。
  移动构造函数和移动赋值操作符都是以右值引用作为参数的,它们接受一个右值引用参数,将参数对象的资源转移到当前对象中,并将参数对象置为空。移动构造函数和移动赋值操作符通常会在对象被返回、传递临时对象时被调用。

6. Lambda表达式(Lambda expression):类似于匿名函数,用于快速定义一些简单的函数对象,方便程序设计。

  Lambda表达式是C++11引入的一种新的语法,它允许我们在代码中定义一个匿名的函数对象,可以方便地进行一些函数式编程的操作。
  Lambda表达式的语法结构是:[capture list] (params list) mutable exception-> return type { function body },其中:
  [capture list]:表示Lambda表达式的捕获列表,用于捕获外部变量。捕获列表可以为空,也可以包含一个或多个捕获项,每个捕获项可以是值捕获、引用捕获或者隐式捕获。
  (params list):表示函数参数列表,与普通函数的参数列表一样。如果不需要参数,可以留空。
  mutable:表示Lambda表达式是否可变。默认情况下,Lambda表达式是不可变的,即不能修改捕获的变量。如果需要修改捕获的变量,需要使用mutable关键字。
  exception:表示异常规格说明,可以省略。
  return type:表示Lambda表达式的返回值类型,可以省略,编译器会自动推断。
  {function body}:表示Lambda表达式的函数体,与普通函数的函数体一样。
  Lambda表达式可以作为函数对象使用,可以直接调用,也可以作为函数的参数或返回值,还可以作为STL算法的谓词。
  下面是一些Lambda表达式的例子:

// 例子1:Lambda表达式作为函数参数
std::vector vec{1, 2, 3, 4, 5};
std::for_each(vec.begin(), vec.end(), [](int n){ std::cout << n << " "; }); // 输出1 2 3 4 5

// 例子2:Lambda表达式捕获外部变量
int a = 1, b = 2;
auto f = [a, &b]{ return a + b; };
std::cout << f() << std::endl; // 输出3

// 例子3:Lambda表达式作为返回值
auto g = [](int n) -> std::function {
    return [=](int x){ return n + x; };
};
auto f2 = g(10);
std::cout << f2(5) << std::endl; // 输出15

  Lambda表达式是C++11中非常强大的一个特性,可以方便地实现许多函数式编程的功能,如map、reduce等。

7. 线程库(Thread library):包括std::thread、std::mutex、std::condition_variable等,用于多线程编程,提高程序效率。

8. 新的容器类型(New container types):如unordered_map、unordered_set等,支持高效的查找、插入和删除操作。

9. constexpr:支持编译时计算,提高程序效率。constexpr 主要用于编译时计算,因此适用于非动态场景下,比如数组大小的定义、模板参数、常量表达式等。

  1) 常量表达式函数

constexpr int factorial(int n) {
    return n == 0 ? 1 : n * factorial(n-1);
}

int main() {
    static_assert(factorial(5) == 120, "Factorial of 5 should be 120");
    return 0;
}

  2) 常量表达式类

class Rectangle {
public:
    constexpr Rectangle(int w, int h) : width(w), height(h) {}
    constexpr int getArea() const { return width * height; }
private:
    int width;
    int height;
};

int main() {
    constexpr Rectangle rect(3, 4);
    static_assert(rect.getArea() == 12, "Area of rectangle should be 12");
    return 0;
}

  3) 指定常量表达式的返回值

constexpr int fib(int n) {
    if (n == 0 || n == 1) {
        return n;
    }
    return fib(n-1) + fib(n-2);
}

int main() {
    constexpr int fib5 = fib(5);
    static_assert(fib5 == 5, "Fibonacci sequence of 5 should be 5");
    return 0;
}

10. 新的语言特性(New language features):如静态断言(static_assert)、委托构造函数(delegating constructor)等,用于提高代码的可读性和可维护性。

见C++11 引入的新的构造函数和语言特性

这些都是C++11引入的一些重要特性,使得C++语言更加现代化、简洁、高效。

二、STL

  以下是C++11标准库中的全部STL:
  1) 序列式容器(Sequence containers):vector、deque、list、forward_list、array、string
  2) 关联式容器(Associative containers):set、multiset、map、multimap、unordered_set、unordered_multiset、unordered_map、unordered_multimap
  3) 容器适配器(Container adapters):stack、queue、priority_queue
  4) 迭代器(Iterators):迭代器类别、迭代器操作、算法、使用范例
  5) 算法(Algorithms):for_each、transform、find、sort、binary_search、min/max_element等等
  6) 数值(Numeric):iota、accumulate、inner_product、adjacent_difference、partial_sum
  7) 随机数(Random Number):随机数引擎、随机数分布
  8) 输入/输出流(Input/output):iostream、fstream、stringstream、bitset
  9) 并发编程(Concurrency):线程、mutex、future、condition_variable、atomic、lock_guard、unique_lock等等
  需要注意的是,C++11标准库中的STL并不包含所有常见的数据结构和算法,还有一些其他的第三方库,如Boost,也提供了许多扩展的数据结构和算法。

  C++11对STL的函数方法补充主要包括以下几个方面:
  1) 新容器类型:C++11引入了array、forward_list和unordered_xxx等容器类型。
  2) 容器类型增强:vector和map容器新增了emplace_back和emplace等函数,可以直接在容器尾部或者指定位置构造对象。set和map容器新增了emplace_hint函数,可以在指定位置插入元素。
  3) 智能指针:unique_ptr和shared_ptr是C++11中新增的智能指针类型,可以自动管理内存,避免内存泄漏等问题。
  4) 并发编程:C++11中新增了atomic、mutex和condition_variable等并发编程相关的类,可以实现线程同步和互斥。
  5) 正则表达式:C++11中新增了头文件,支持正则表达式的匹配和查找操作。
  6) 随机数:C++11中新增了头文件,支持生成随机数和随机序列等操作。
  7) 其他新增的函数和算法:C++11中还新增了一些函数和算法,如unique、reverse、is_partitioned等。
  总的来说,C++11对STL的增强主要是为了提高编程效率和方便性,使得C++编程更加容易和简洁。

你可能感兴趣的:(C++ 面试准备 八股(二)C++11新特性、STL)