当前位置: 首页 > news >正文

C++常见面试题-3.C++11 及后续特性

三、C++11 及后续特性

3.1 C++11 新特性概述

  1. C++11 相比 C++98 有哪些主要改进?
    • 性能提升:移动语义、右值引用、完美转发、新的智能指针等;
    • 安全性增强:nullptr、强类型枚举、auto类型推导等;
    • 代码简化:auto类型推导、范围for循环、lambda表达式等;
    • STL扩展:unordered容器、正则表达式、线程库等;
    • 语言特性:constexpr、类型别名、委托构造函数等;
    • 内存模型:定义了多线程环境下的内存模型,支持原子操作。

3.2 类型推导与类型别名

  1. auto 关键字的作用?使用 auto 有哪些优缺点?

    • auto关键字的作用

      • 用于自动推导变量的类型,使代码更简洁;
      • 编译器在编译时根据初始化表达式推导变量类型;
      • 不能单独使用,必须有初始化表达式。
    • 优点

      • 代码简洁:避免冗长的类型声明,特别是对于复杂的STL容器迭代器;
      • 可维护性提高:当变量类型改变时,不需要手动修改声明;
      • 减少错误:避免手动输入类型时的拼写错误或类型不匹配。
    • 缺点

      • 可读性下降:过度使用auto可能导致代码难以理解,特别是对于复杂类型;
      • 类型信息丢失:开发者可能不清楚变量的确切类型;
      • 隐藏类型转换:可能隐藏一些隐式类型转换,导致意外行为。
      // auto的使用示例
      auto i = 42;  // i的类型为int
      auto d = 3.14;  // d的类型为double
      auto str = "hello";  // str的类型为const char*// 复杂类型示例
      std::vector<std::map<std::string, int>> data;
      auto it = data.begin();  // 不需要写很长的迭代器类型// 函数返回类型推导(C++14)
      auto add(int a, int b) { return a + b; }
      
  2. decltype 关键字的作用?与 auto 有何区别?

    • decltype关键字的作用

      • 用于获取表达式的类型,而不需要实际计算表达式的值;
      • 语法:decltype(expression) var;
      • 常用于模板编程、函数返回类型推导等场景。
    • 与auto的区别

      • 推导时机:auto在初始化时推导类型,decltype在编译时推导表达式类型;
      • 推导依据:auto根据初始化表达式的值推导类型,decltype根据表达式的形式和类型推导;
      • 引用处理:auto会忽略引用,decltype会保留引用;
      • const处理:auto会忽略顶层const,decltype会保留const。
      int a = 10;
      int& ref_a = a;
      const int const_a = 20;auto x = a;  // x的类型为int
      auto y = ref_a;  // y的类型为int(忽略引用)
      auto z = const_a;  // z的类型为int(忽略顶层const)decltype(a) dx = a;  // dx的类型为int
      decltype(ref_a) dy = a;  // dy的类型为int&(保留引用)
      decltype(const_a) dz = 30;  // dz的类型为const int(保留const)// 函数返回类型推导(C++11)
      template<typename T, typename U>
      auto add(T t, U u) -> decltype(t + u) {return t + u;
      }
      
  3. 类型别名(typedef 和 using)的区别?

    • typedef

      • C语言中就存在的关键字,用于创建类型的别名;
      • 语法:typedef 原类型 新类型名;
      • 在模板中使用时,需要使用typedef typename的组合。
    • using(类型别名声明,C++11)

      • C++11引入的新语法,也用于创建类型的别名;
      • 语法:using 新类型名 = 原类型;
      • 支持模板别名(类型别名模板)。
    • 区别

      • 语法不同:using使用等号,typedef不使用;
      • 模板支持:using可以直接定义模板别名,typedef需要使用typedef typename和嵌套类;
      • 可读性:using的语法更清晰,尤其是对于复杂类型;
      • 作用域:using可以在块作用域中使用,typedef也可以。
      // typedef示例
      typedef unsigned long ulong;
      typedef std::vector<int> IntVector;
      typedef std::map<std::string, std::vector<int>> StringToIntVectorMap;// using示例
      using ulong = unsigned long;
      using IntVector = std::vector<int>;
      using StringToIntVectorMap = std::map<std::string, std::vector<int>>;// 模板别名(类型别名模板)
      // typedef方式(需要嵌套类)
      template<typename T>
      struct Vec {typedef std::vector<T> type;
      };
      Vec<int>::type v1;  // 使用// using方式(更简洁)
      template<typename T>
      using Vec = std::vector<T>;
      Vec<int> v2;  // 直接使用
      

3.3 移动语义与智能指针

  1. 移动语义(Move Semantics)的作用?如何实现移动构造函数和移动赋值运算符?

    • 移动语义的作用

      • 允许资源(如堆内存)从一个对象转移到另一个对象,避免不必要的拷贝操作;
      • 提高程序性能,特别是对于临时对象和大对象;
      • 是C++11引入的重要特性,解决了C++98/03中的拷贝开销问题。
    • 移动构造函数

      • 用于从一个将亡值(rvalue)创建新对象;
      • 语法:Class(Class&& other) noexcept;
      • 实现:接管other的资源,并将other置于有效但不确定的状态(通常是nullptr)。
    • 移动赋值运算符

      • 用于将一个将亡值的资源转移给另一个已存在的对象;
      • 语法:Class& operator=(Class&& other) noexcept;
      • 实现:释放自身资源,接管other的资源,并将other置于有效但不确定的状态。
      class MyString {
      private:char* data;public:// 构造函数MyString(const char* str = nullptr) {if (str) {data = new char[strlen(str) + 1];strcpy(data, str);} else {data = new char[1];data[0] = '\0';}}// 拷贝构造函数(深拷贝)MyString(const MyString& other) {data = new char[strlen(other.data) + 1];strcpy(data, other.data);}// 移动构造函数MyString(MyString&& other) noexcept : data(other.data) {other.data = nullptr;  // 将other置于有效但不确定的状态}// 拷贝赋值运算符MyString& operator=(const MyString& other) {if (this != &other) {delete[] data;data = new char[strlen(other.data) + 1];strcpy(data, other.data);}return *this;}// 移动赋值运算符MyString& operator=(MyString&& other) noexcept {if (this != &other) {delete[] data;data = other.data;other.data = nullptr;}return *this;}// 析构函数~MyString() {delete[] data;}
      };void test() {MyString s1("hello");MyString s2 = std::move(s1);  // 调用移动构造函数,s1现在可能为nullptrMyString s3;s3 = std::move(s2);  // 调用移动赋值运算符,s2现在可能为nullptr
      }
      
  2. 左值引用和右值引用的区别?std::move 的作用?

    • 左值(Lvalue):表达式结束后依然存在的持久对象,可以出现在赋值语句的左侧。

    • 右值(Rvalue):表达式结束后就不再存在的临时对象,只能出现在赋值语句的右侧。

    • 左值引用(Lvalue Reference)

      • 指向左值的引用,用&表示;
      • 不能绑定到右值(除非是const左值引用)。
    • 右值引用(Rvalue Reference,C++11)

      • 指向右值的引用,用&&表示;
      • 可以绑定到右值,也可以通过std::move绑定到左值;
      • 主要用于实现移动语义和完美转发。
    • std::move的作用

      • 将左值转换为右值引用,使得可以对左值应用移动语义;
      • 本身不移动任何东西,只是改变值的属性;
      • 不保证被转换的左值仍然有效,通常转换后不应再使用原左值。
      int a = 10;  // a是左值
      int& ref_a = a;  // 左值引用,绑定到左值a
      // int& ref_temp = 20;  // 错误:左值引用不能绑定到右值
      const int& ref_const_temp = 20;  // 正确:const左值引用可以绑定到右值int&& ref_rvalue = 20;  // 右值引用,绑定到右值20
      // int&& ref_lvalue = a;  // 错误:右值引用不能直接绑定到左值
      int&& ref_moved = std::move(a);  // 正确:通过std::move将左值a转换为右值引用// 验证右值引用的作用
      MyString s1("hello");
      MyString s2 = std::move(s1);  // 调用移动构造函数
      // 注意:s1现在可能处于有效但不确定的状态,应避免使用
      
  3. std::forward 的作用?与 std::move 的区别?

    • std::forward的作用

      • 实现完美转发(Perfect Forwarding),即将参数原封不动地转发给其他函数;
      • 保持参数的值类别(左值或右值)不变;
      • 通常与模板参数T&&(万能引用,Universal Reference)配合使用。
    • 与std::move的区别

      • 目的不同:std::move用于将左值转换为右值引用,std::forward用于保持参数的值类别;
      • 使用场景不同:std::move用于移动语义,std::forward用于完美转发;
      • 参数要求不同:std::move接受任何类型的参数,std::forward通常需要配合模板参数。
      // 万能引用(Universal Reference):当T是模板参数且参数为T&&时
      template<typename T>
      void wrapper(T&& arg) {// std::forward保持arg的值类别func(std::forward<T>(arg));
      }void func(int& x) { std::cout << "Lvalue reference" << std::endl; }
      void func(int&& x) { std::cout << "Rvalue reference" << std::endl; }void test() {int a = 10;wrapper(a);  // 调用func(int&),输出"Lvalue reference"wrapper(20);  // 调用func(int&&),输出"Rvalue reference"
      }// std::forward与std::move的对比
      template<typename T>
      void move_vs_forward(T&& t) {// std::move总是返回右值引用use_rvalue(std::move(t));// std::forward根据传入的参数类型决定返回左值引用还是右值引用use_either(std::forward<T>(t));
      }
      
  4. 智能指针(std::shared_ptr、std::unique_ptr、std::weak_ptr)的区别和使用场景?

    • std::shared_ptr

      • 特点:共享所有权的智能指针,使用引用计数机制;
      • 工作原理:内部维护一个引用计数,当引用计数为0时,自动释放资源;
      • 使用场景:多个对象需要共享同一个资源的所有权;
      • 注意事项:避免循环引用,否则可能导致内存泄漏(需要使用std::weak_ptr解决)。
      void test_shared_ptr() {// 创建shared_ptrstd::shared_ptr<int> p1(new int(10));std::shared_ptr<int> p2 = p1;  // 引用计数增加到2std::shared_ptr<int> p3 = std::make_shared<int>(20);  // 更推荐的创建方式// 使用*p1 = 30;std::cout << *p2 << std::endl;  // 输出30,因为p1和p2指向同一资源// 引用计数std::cout << "p1 use count: " << p1.use_count() << std::endl;  // 输出2// 当所有shared_ptr离开作用域,资源会自动释放
      }
      
    • std::unique_ptr

      • 特点:独占所有权的智能指针;
      • 工作原理:同一时间只能有一个unique_ptr指向某个资源;
      • 使用场景:资源只能由一个对象独占,不需要共享所有权;
      • 注意事项:不能复制,只能移动;性能比shared_ptr好,因为不需要引用计数。
      void test_unique_ptr() {// 创建unique_ptrstd::unique_ptr<int> p1(new int(10));std::unique_ptr<int> p2 = std::make_unique<int>(20);  // C++14引入的更推荐方式// 不能复制// std::unique_ptr<int> p3 = p1;  // 错误// 可以移动std::unique_ptr<int> p4 = std::move(p1);  // p1现在为空,p4拥有资源// 当unique_ptr离开作用域,资源会自动释放
      }
      
    • std::weak_ptr

      • 特点:不增加引用计数的智能指针,用于解决shared_ptr的循环引用问题;
      • 工作原理:指向shared_ptr管理的资源,但不增加引用计数;
      • 使用场景:观察shared_ptr管理的资源,但不需要拥有所有权;
      • 注意事项:不能直接访问资源,需要先转换为shared_ptr(通过lock()方法)。
      void test_weak_ptr() {std::shared_ptr<int> sp = std::make_shared<int>(10);std::weak_ptr<int> wp = sp;  // 不增加引用计数// 检查资源是否存在if (!wp.expired()) {// 转换为shared_ptr以访问资源std::shared_ptr<int> sp2 = wp.lock();std::cout << *sp2 << std::endl;  // 输出10}// 解决循环引用示例struct Node {std::shared_ptr<Node> next;// 使用weak_ptr代替shared_ptr避免循环引用std::weak_ptr<Node> prev;};
      }
      
  5. std::make_shared 和 std::make_unique 的优势?

    • std::make_shared的优势

      • 内存分配更高效:一次性分配对象和控制块的内存,减少内存碎片;
      • 异常安全:避免在资源分配和智能指针构造之间发生异常导致内存泄漏;
      • 代码更简洁:语法更简洁,不需要显式使用new操作符;
      • 避免了裸指针暴露:减少了使用裸指针的机会,提高代码安全性。
    • std::make_unique的优势

      • 异常安全:避免在资源分配和智能指针构造之间发生异常导致内存泄漏;
      • 代码更简洁:语法更简洁,不需要显式使用new操作符;
      • 避免了裸指针暴露:减少了使用裸指针的机会,提高代码安全性;
      • 明确表达独占所有权:与unique_ptr的独占所有权语义一致。
      // 不使用make_shared的潜在问题
      void problematic() {// 如果process()抛出异常,new int(10)分配的内存可能泄漏std::shared_ptr<int> p(new int(10), std::default_delete<int>(), process());
      }// 使用make_shared的优势
      void better() {// 更高效、更安全、更简洁auto p = std::make_shared<int>(10);
      }// C++14引入make_unique
      void even_better() {auto p = std::make_unique<int>(10);
      }
      
  6. 智能指针的循环引用问题?如何解决?

    • 循环引用问题

      • 当两个或多个对象通过shared_ptr相互引用时,即使这些对象不再被外部引用,它们的引用计数也不会降为0,导致内存泄漏。
      struct A;
      struct B;struct A {std::shared_ptr<B> b_ptr;  // A持有B的shared_ptr~A() { std::cout << "A destroyed" << std::endl; }
      };struct B {std::shared_ptr<A> a_ptr;  // B持有A的shared_ptr~B() { std::cout << "B destroyed" << std::endl; }
      };void test_circular_reference() {{  // 作用域开始auto a = std::make_shared<A>();auto b = std::make_shared<B>();a->b_ptr = b;  // 循环引用形成b->a_ptr = a;}  // 作用域结束,但a和b的引用计数仍为1,不会调用析构函数,导致内存泄漏
      }
      
    • 解决方法

      • 使用std::weak_ptr:将其中一个shared_ptr改为weak_ptr,打破循环引用;
      • 手动断开连接:在不再需要引用时,手动将其中一个shared_ptr置为nullptr;
      • 重新设计数据结构:避免循环引用的设计,例如使用单向引用。
      // 使用std::weak_ptr解决循环引用
      struct A {std::shared_ptr<B> b_ptr;~A() { std::cout << "A destroyed" << std::endl; }
      };struct B {std::weak_ptr<A> a_ptr;  // 改为weak_ptr~B() { std::cout << "B destroyed" << std::endl; }
      };void test_solve_circular_reference() {{  // 作用域开始auto a = std::make_shared<A>();auto b = std::make_shared<B>();a->b_ptr = b;b->a_ptr = a;  // weak_ptr不会增加引用计数}  // 作用域结束,a的引用计数降为0,调用A的析构函数,A的析构函数释放b_ptr,b的引用计数降为0,调用B的析构函数// 输出:// A destroyed// B destroyed
      }
      

3.4 Lambda 表达式与函数对象

  1. Lambda 表达式的语法?捕获列表的作用?

    • Lambda表达式的语法

      [capture-list](parameters) -> return-type { body }
      
      • [capture-list]:捕获列表,指定如何捕获外部变量;
      • (parameters):参数列表,与普通函数类似;
      • -> return-type:返回类型,可以省略,编译器会自动推导;
      • { body }:函数体,包含Lambda表达式的执行代码。
    • 捕获列表的作用

      • 控制Lambda表达式如何访问外部作用域中的变量;
      • 捕获方式包括:按值捕获、按引用捕获、隐式捕获等。
      • 常用的捕获方式:
        • []:不捕获任何外部变量;
        • [=]:按值捕获所有外部变量;
        • [&]:按引用捕获所有外部变量;
        • [x]:按值捕获变量x;
        • [&x]:按引用捕获变量x;
        • [=, &x]:按值捕获所有外部变量,但x按引用捕获;
        • [&, x]:按引用捕获所有外部变量,但x按值捕获;
        • [this]:捕获当前对象的this指针;
        • [*this]:(C++17)捕获当前对象的副本。
      void test_lambda() {int a = 10;int b = 20;// 不捕获任何变量auto func1 = []() { std::cout << "Hello Lambda" << std::endl; };func1();// 按值捕获a和bauto func2 = [a, b]() { std::cout << "a + b = " << a + b << std::endl; };func2();// 按值捕获所有外部变量auto func3 = [=]() { std::cout << "a + b = " << a + b << std::endl; };func3();// 按引用捕获a,按值捕获bauto func4 = [&a, b]() { a++; std::cout << "a = " << a << ", b = " << b << std::endl; };func4();  // a变为11,b仍为20// 按引用捕获所有外部变量auto func5 = [&]() { a++; b++; std::cout << "a = " << a << ", b = " << b << std::endl; };func5();  // a变为12,b变为21// 带参数的Lambdaauto add = [](int x, int y) -> int { return x + y; };std::cout << add(5, 3) << std::endl;  // 输出8// 返回类型推导auto multiply = [](int x, int y) { return x * y; };std::cout << multiply(5, 3) << std::endl;  // 输出15
      }
      
  2. Lambda 表达式与函数对象的区别?

    • Lambda表达式

      • 是一种匿名函数对象,由编译器自动生成对应的函数对象类型;
      • 语法简洁,适合编写简短的、一次性使用的函数;
      • 可以通过捕获列表访问外部变量;
      • 可以隐式转换为函数指针(当没有捕获任何变量时);
      • 不能有默认参数(C++14开始允许);
      • 不能显式定义构造函数和析构函数。
    • 函数对象(Functor)

      • 是重载了函数调用运算符operator()的类的实例;
      • 可以有成员变量和成员函数,状态可以保持;
      • 可以有默认参数;
      • 可以显式定义构造函数和析构函数;
      • 语法相对复杂,适合编写复杂的、可重用的函数;
      • 不能直接捕获外部变量,但可以通过构造函数传递参数。
      // Lambda表达式示例
      void test_lambda_vs_functor() {// 使用Lambda表达式auto compare = [](int a, int b) { return a < b; };std::vector<int> vec = {3, 1, 4, 1, 5, 9};std::sort(vec.begin(), vec.end(), compare);// 使用函数对象struct Compare {bool operator()(int a, int b) const {return a < b;}};std::sort(vec.begin(), vec.end(), Compare());// 带状态的函数对象struct Counter {int count = 0;void operator()(int x) {count += x;}};Counter c;std::for_each(vec.begin(), vec.end(), c);std::cout << "Sum: " << c.count << std::endl;
      }
      
  3. mutable 关键字在 Lambda 表达式中的作用?

    • mutable关键字的作用

      • 允许Lambda表达式修改按值捕获的变量;
      • 默认情况下,Lambda表达式的函数调用运算符是const的,无法修改按值捕获的变量;
      • 添加mutable后,函数调用运算符不再是const的,可以修改按值捕获的变量。
    • 注意事项

      • mutable只影响按值捕获的变量,不影响按引用捕获的变量;
      • 修改的是按值捕获的变量的副本,不会影响原始变量。
      void test_lambda_mutable() {int x = 10;int y = 20;// 按值捕获x,默认情况下不能修改xauto func1 = [x]() {// x++;  // 错误:不能修改按值捕获的变量};// 使用mutable关键字,允许修改按值捕获的变量auto func2 = [x]() mutable {x++;  // 合法:修改的是x的副本std::cout << "Inside lambda: " << x << std::endl;  // 输出11};func2();std::cout << "Outside lambda: " << x << std::endl;  // 输出10,原始x未被修改// 按引用捕获y,可以直接修改(不需要mutable)auto func3 = [&y]() {y++;  // 合法:修改的是原始ystd::cout << "Inside lambda: " << y << std::endl;  // 输出21};func3();std::cout << "Outside lambda: " << y << std::endl;  // 输出21,原始y被修改
      }
      

3.5 constexpr、nullptr 与其他

  1. constexpr 的作用?与 const 的区别?

    • constexpr的作用

      • 声明可以在编译时计算的常量表达式;
      • 用于变量时,表示变量的值必须在编译时确定;
      • 用于函数时,表示函数可以在编译时被求值,前提是参数是常量表达式;
      • 用于类构造函数时,表示该构造函数可以在编译时创建常量对象。
    • 与const的区别

      • 初始化时机:const变量可以在运行时初始化,constexpr变量必须在编译时初始化;
      • 可计算性:constexpr变量的值必须是编译时常量表达式,const变量可以是运行时常量;
      • 优化潜力:constexpr比const有更强的优化潜力,可以在编译时完全求值;
      • 函数应用:constexpr可以用于函数,const不能;
      • 数组大小:只有constexpr变量才能用于定义数组大小(在C++11之前,const变量也可以,但需要是编译时常量)。
      // constexpr变量示例
      constexpr int MAX_SIZE = 100;  // 编译时常量
      int arr[MAX_SIZE];  // 合法const int size1 = 50;  // 可能是编译时常量
      int arr1[size1];  // 在C++11之前可能不合法,C++11及以后合法int n = 5;  // 运行时变量
      const int size2 = n;  // size2是运行时常量
      // int arr2[size2];  // 错误:size2不是编译时常量// constexpr函数示例
      constexpr int factorial(int n) {return n <= 1 ? 1 : n * factorial(n - 1);
      }constexpr int fact5 = factorial(5);  // 编译时计算为120
      int fact10 = factorial(10);  // 可以在运行时计算// constexpr构造函数示例
      class Point {
      public:constexpr Point(int x, int y) : x_(x), y_(y) {}constexpr int getX() const { return x_; }constexpr int getY() const { return y_; }
      private:int x_, y_;
      };constexpr Point origin(0, 0);  // 编译时创建常量对象
      constexpr int x = origin.getX();  // 编译时计算为0
      
  2. nullptr 的作用?与 NULL、0 的区别?

    • nullptr的作用

      • C++11引入的关键字,表示空指针常量;
      • 类型为std::nullptr_t;
      • 用于初始化指针或指针类型的引用,表示指针不指向任何对象。
    • 与NULL、0的区别

      • 类型安全:nullptr是一个明确的指针类型,NULL和0可能导致类型混淆;
      • 函数重载:nullptr可以正确匹配指针类型的重载函数,NULL和0可能匹配错误;
      • 定义不同:NULL通常是宏定义,在C++中可能被定义为0或reinterpret_cast<void*>(0),而nullptr是C++11的关键字;
      • 可读性:nullptr比NULL和0更清晰地表示空指针的意图。
      // 函数重载示例
      void func(int n) { std::cout << "int version" << std::endl; }
      void func(int* p) { std::cout << "pointer version" << std::endl; }void test_nullptr() {int* p1 = nullptr;  // 正确:nullptr初始化指针int* p2 = NULL;     // 正确:但可能有歧义int* p3 = 0;        // 正确:但可读性差// 函数重载问题func(0);        // 调用func(int),输出"int version"func(NULL);     // 可能调用func(int)或func(int*),取决于NULL的定义,有歧义func(nullptr);  // 调用func(int*),输出"pointer version"// nullptr的类型std::nullptr_t np = nullptr;  // 正确:nullptr_t是nullptr的类型// int n = nullptr;  // 错误:nullptr不能隐式转换为intbool b = (nullptr == 0);  // 正确:nullptr可以与0比较,b为true
      }
      
  3. 强类型枚举(enum class)与普通枚举(enum)的区别?

    • 普通枚举(enum)

      • 枚举值的作用域与枚举类型相同,可能导致命名冲突;
      • 枚举值可以隐式转换为整数类型;
      • 无法指定底层类型(C++11之前);
      • 枚举值的可见性较高,可能污染命名空间。
    • 强类型枚举(enum class,C++11)

      • 枚举值的作用域被限制在枚举类型内,避免命名冲突;
      • 枚举值不能隐式转换为整数类型,需要显式转换;
      • 可以指定底层类型(如int、char等);
      • 提供更好的类型安全和命名空间隔离。
      // 普通枚举的问题:命名冲突
      enum Color { RED, GREEN, BLUE };
      // enum Fruit { RED, APPLE, BANANA };  // 错误:RED已经被定义// 强类型枚举避免命名冲突
      enum class Color { RED, GREEN, BLUE };
      enum class Fruit { RED, APPLE, BANANA };  // 正确:RED在不同作用域void test_enum_class() {// 普通枚举的隐式转换Color color = RED;  // 正确int n = color;  // 正确:隐式转换为int,n=0// 强类型枚举需要显式使用作用域Color new_color = Color::RED;  // 正确// int m = new_color;  // 错误:不能隐式转换int m = static_cast<int>(new_color);  // 正确:显式转换,m=0// 指定底层类型enum class SmallEnum : char { A, B, C };  // 底层类型为char,节省空间std::cout << sizeof(SmallEnum) << std::endl;  // 输出1
      }
      
  4. 范围 for 循环的语法?与普通 for 循环的区别?

    • 范围for循环的语法

      for (declaration : range) {// 循环体
      }
      
      • declaration:声明一个变量,用于存储range中的元素;
      • range:可以是数组、容器(如vector、list等)、初始化列表或任何具有begin()和end()成员函数的类型。
    • 与普通for循环的区别

      • 语法简洁:不需要显式指定起始和结束条件,代码更简洁;
      • 可读性更好:更清晰地表达遍历整个范围的意图;
      • 不容易出错:避免了循环变量初始化、边界检查等错误;
      • 适用范围有限:只适用于遍历整个范围的场景,不适合需要自定义步进、反向遍历等复杂情况。
      void test_range_for() {std::vector<int> vec = {1, 2, 3, 4, 5};// 普通for循环遍历for (size_t i = 0; i < vec.size(); ++i) {std::cout << vec[i] << " ";}std::cout << std::endl;// 使用迭代器的for循环for (auto it = vec.begin(); it != vec.end(); ++it) {std::cout << *it << " ";}std::cout << std::endl;// 范围for循环(按值遍历)for (int value : vec) {std::cout << value << " ";}std::cout << std::endl;// 范围for循环(按引用遍历,可修改元素)for (int& value : vec) {value *= 2;  // 修改原容器中的元素}// 范围for循环(const引用遍历,提高效率)for (const auto& value : vec) {std::cout << value << " ";  // 输出2 4 6 8 10}std::cout << std::endl;
      }
      
  5. C++14、C++17、C++20 引入了哪些重要特性?

    • C++14 重要特性

      • 泛型Lambda表达式:Lambda表达式的参数可以使用auto;
      auto generic_lambda = [](auto x, auto y) { return x + y; };
      generic_lambda(5, 3);  // int相加
      generic_lambda(3.14, 2.71);  // double相加
      
      • 函数返回类型推导:函数返回类型可以使用auto,编译器自动推导;
      auto add(int a, int b) { return a + b; }  // 返回类型为int
      
      • std::make_unique:提供了make_unique函数,与make_shared类似;
      • 二进制字面量:支持二进制字面量,前缀为0b或0B;
      int binary = 0b1010;  // 二进制1010,等于十进制10
      
      • 数字分隔符:支持在数字中使用单引号作为分隔符,提高可读性;
      int large_number = 1'000'000;  // 等于1000000
      
    • C++17 重要特性

      • 结构化绑定:允许将复合对象的成员解包到单独的变量中;
      std::pair<int, std::string> p = {42, "hello"};
      auto [num, str] = p;  // num=42, str="hello"std::map<int, std::string> m = {{1, "one"}, {2, "two"}};
      for (const auto& [key, value] : m) {std::cout << key << ": " << value << std::endl;
      }
      
      • if/switch 初始化语句:允许在if和switch语句中声明和初始化变量;
      if (auto it = map.find(key); it != map.end()) {// 使用it
      }
      
      • std::variant:类型安全的联合体,可以存储不同类型的值;
      • std::optional:表示可能存在或不存在的值;
      • std::any:可以存储任何类型的值;
      • 折叠表达式:用于可变参数模板的展开;
      template<typename... Args>
      auto sum(Args&&... args) {return (args + ...);  // 折叠表达式,计算所有参数的和
      }
      
      • std::filesystem:提供了操作文件系统的标准库;
    • C++20 重要特性

      • 概念(Concepts):用于约束模板参数,提高编译错误信息的可读性;
      template<typename T>
      concept Integral = std::is_integral_v<T>;template<Integral T>  // 只接受整数类型
      T add(T a, T b) { return a + b; }
      
      • 范围库(Ranges):提供了更灵活的迭代器和算法;
      • 协程(Coroutines):支持异步编程;
      • 模块(Modules):替代头文件,提供更好的代码组织和编译性能;
      • 三路比较运算符(Spaceship Operator)<=>,用于简化比较操作;
      auto result = a <=> b;  // 返回一个比较类别,可以用于<, <=, >, >=, ==, !=
      
      • constexpr std::vector/string:vector和string可以在constexpr上下文中使用;
      • 初始化语句中的lambda:允许在初始化语句中使用lambda表达式。
http://www.dtcms.com/a/339828.html

相关文章:

  • 图像融合指标的代码
  • package.json详细字段解释
  • 基于 RxJava 构建强大的 Android 文件下载管理器
  • APM32芯得 EP.29 | 基于APM32F103的USB键盘与虚拟串口复合设备配置详解
  • 【库的操作】
  • linux控制其他程序使用cpu低于50%——笔记
  • 常见的对比学习的损失函数
  • LLM(大语言模型)的工作原理 图文讲解
  • 基于“R语言+遥感“水环境综合评价方法技术应用——水线提取、水深提取、水温提、水质提取、水环境遥感等
  • make stb style code: bin2c and reverse
  • 开发中使用——APP层级相关知识
  • bit-Agent正式接入GPT-5,九科信息智能体能力再升级!
  • 海滨浴场应急广播:守护碧海蓝天的安全防线
  • 皮带跑偏?智慧煤矿靠AI摄像机+检测算法实现实时预警
  • Docker学习--常用命令
  • vue3动态获取高度用在轮播图上
  • Diamond开发经验(1)
  • 代码随想录刷题Day37
  • C++的二叉搜索树(二叉排序树)
  • AC 应用控制技术
  • Vulkan笔记(十一)-渲染通道(RenderPass)详解
  • 厂区能源管理智能化改造物联网解决方案
  • 沈昕医编---因为上岸了医疗编,来说一下e类联考的强度
  • YggJS RLogin暗黑霓虹主题登录注册页面 版本:v0.1.1
  • js 怎么调用 deepseekAPI
  • 雷卯针对香橙派Orange Pi 5 Pro开发板防雷防静电方案
  • 从密度到聚类:DBSCAN算法的第一性原理解析
  • MyCAT2的主从配置
  • Ubuntu、CentOS、AlmaLinux 9.5的 rc.local实现 开机启动
  • 第2章 组件设计与架构