跳至主要內容

C++11新特性

CodeShouhu大约 24 分钟使用指南Markdown

C++11新特性

1,C++11新特性-auto关键字

auto 是 C++11 引入的一个关键字,它可以自动推断变量的类型。这在某些情况下可以使代码更简洁、更易读。下面是一些使用 auto 的案例:

  1. 基本使用
#include <iostream>
#include <vector>

int main() {
    std::vector<int> v = {1, 2, 3, 4, 5};
    
    // 使用 auto 推断迭代器的类型
    for (auto it = v.begin(); it != v.end(); ++it) {
        std::cout << *it << ' ';
    }
    
    return 0;
}
  1. 与范围for循环结合使用
#include <iostream>
#include <vector>

int main() {
    std::vector<int> v = {1, 2, 3, 4, 5};
    
    // 使用 auto 和范围for循环遍历容器
    for (auto i : v) {
        std::cout << i << ' ';
    }
    
    return 0;
}
  1. 与lambda函数结合使用
#include <iostream>
#include <algorithm>
#include <vector>

int main() {
    std::vector<int> v = {5, 4, 3, 1, 2};
    
    // 使用 auto 创建一个lambda函数,用于排序
    auto compare = [](int a, int b) { return a < b; };
    std::sort(v.begin(), v.end(), compare);
    
    for (auto i : v) {
        std::cout << i << ' ';
    }
    
    return 0;
}
  1. 类型推导与模板结合
#include <iostream>
#include <vector>

template<typename T>
void printElements(const std::vector<T>& v) {
    for (auto element : v) { // 这里使用 auto 可以避免重复提到 T 类型
        std::cout << element << ' ';
    }
    std::cout << '\n';
}

int main() {
    std::vector<int> v1 = {1, 2, 3, 4, 5};
    std::vector<double> v2 = {1.1, 2.2, 3.3, 4.4, 5.5};
    printElements(v1); // 输出:1 2 3 4 5 
    printElements(v2); // 输出:1.1 2.2 3.3 4.4 5.5 
    return 0;
}

速记

2,C++11新特性-auto关键字

decltype 是 C++11 引入的一个关键字,它可以用来推断表达式的类型。这在某些情况下可以使代码更简洁、更易读,还可以与模板结合使用来进行更复杂的类型推断。下面是一些使用 decltype 的案例:

  1. 基本使用
#include <iostream>

int main() {
    int a = 10;
    decltype(a) b = 20; // b 的类型为 int,因为 a 的类型是 int
    std::cout << "b is " << b << " and its type is " << typeid(b).name() << '\n';
    return 0;
}
  1. 与复杂表达式结合使用
#include <iostream>
#include <vector>

int main() {
    std::vector<int> v = {1, 2, 3, 4, 5};
    for (auto it = v.begin(); it != v.end(); ++it) {
        decltype(*it) copy = *it; // copy 的类型为 int,因为 *it 的类型是 int
        std::cout << "copy is " << copy << " and its type is " << typeid(copy).name() << '\n';
    }
    return 0;
}
  1. 与模板结合使用
#include <iostream>
#include <vector>

template<typename T, typename U>
void printElements(const std::vector<T>& v, U f) {
    for (decltype(v)::size_type i = 0; i != v.size(); ++i) { // 使用 decltype 推断容器的 size_type 类型
        std::cout << f(v[i]) << ' ';
    }
    std::cout << '\n';
}

int main() {
    std::vector<int> v = {1, 2, 3, 4, 5};
    printElements(v, [](int x) { return x * x; }); // 输出:1 4 9 16 25 
    return 0;
}
  1. 推断函数返回类型

decltype 还可以用来推断函数的返回类型,这在编写模板函数或需要根据输入参数类型确定返回类型的函数时非常有用。例如:

#include <iostream>
#include <vector>
#include <algorithm>

template<typename T>
auto findLargest(const std::vector<T>& v) -> decltype(v[0]) { // 使用 decltype 推断返回类型
    auto it = std::max_element(v.begin(), v.end());
    return *it;
}

int main() {
    std::vector<int> v1 = {1, 2, 3, 4, 5};
    std::vector<double> v2 = {1.1, 2.2, 3.3, 4.4, 5.5};
    std::cout << "Largest element in v1: " << findLargest(v1) << '\n'; // 输出:Largest element in v1: 5
    std::cout << "Largest element in v2: " << findLargest(v2) << '\n'; // 输出:Largest element in v2: 5.5
    return 0;
}

为什么要有decltype

因为 auto 并不适用于所有的自动类型推导场景,在某些特殊情况下 auto 用起来非常不方便,甚至压根无法使用,所以 decltype 关键字也被引入到 C++11 中。

auto 和 decltype 关键字都可以自动推导出变量的类型,但它们的用法是有区别的:

auto varname = value; decltype(exp) varname = value;

其中,varname 表示变量名,value 表示赋给变量的值,exp 表示一个表达式。

auto 根据"="右边的初始值 value 推导出变量的类型,而 decltype 根据 exp 表达式推导出变量的类型,跟"="右边的 value 没有关系。

另外,auto 要求变量必须初始化,而 decltype 不要求。这很容易理解,auto 是根据变量的初始值来推导出变量类型的,如果不初始化,变量的类型也就无法推导了。decltype 可以写成下面的形式:

decltype(exp) varname;

3,C++11新特性-追踪返回类型

C++11引入了一种新的函数声明语法,称为"追踪返回类型"(trailing return type),也称为"后置返回类型"。这种语法允许在函数参数列表之后指定函数的返回类型,而不是在函数名之前。

使用追踪返回类型可以使代码更简洁、更易读,特别是在函数模板和lambda表达式中。下面是一些使用追踪返回类型的案例:

  1. 基本使用
#include <iostream>
#include <vector>
#include <algorithm>

template<typename T>
auto findLargest(const std::vector<T>& v) -> T { // 使用追踪返回类型
    auto it = std::max_element(v.begin(), v.end());
    return *it;
}

int main() {
    std::vector<int> v = {1, 2, 3, 4, 5};
    std::cout << "Largest element: " << findLargest(v) << '\n'; // 输出:Largest element: 5
    return 0;
}
  1. 与auto关键字结合使用

当使用auto关键字作为函数的返回类型时,追踪返回类型语法尤其有用。这允许在函数体内部根据实际需求推断返回类型。

#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>

template<typename T, typename Compare>
auto findLargest(const std::vector<T>& v, Compare comp) -> T { // 使用追踪返回类型和auto关键字
    auto it = std::max_element(v.begin(), v.end(), comp);
    return *it;
}

int main() {
    std::vector<int> v = {1, 2, 3, 4, 5};
    std::cout << "Largest element using default comparator: " << findLargest(v) << '\n'; // 输出:Largest element using default comparator: 5
    std::cout << "Largest element using custom comparator: " << findLargest(v, std::greater<int>()) << '\n'; // 输出:Largest element using custom comparator: 1
    return 0;
}
  1. 在lambda表达式中使用

追踪返回类型在lambda表达式中也非常有用,特别是当lambda表达式的返回类型依赖于捕获的类型时。

#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>

int main() {
    std::vector<int> v = {1, 2, 3, 4, 5};
    auto sum = [](const std::vector<int>& vec) -> int { // 使用追踪返回类型在lambda表达式中
        int total = 0;
        for (const auto& num : vec) {
            total += num;
        }
        return total;
    };
    std::cout << "Sum of elements: " << sum(v) << '\n'; // 输出:Sum of elements: 15
    return 0;
}

4,C++11新特性-类内成员初始化

C++11引入了类内成员初始化的新特性,允许在类定义中直接初始化成员变量。这一特性使代码更简洁,同时提高了代码的可读性和可维护性。

在C++11之前,成员变量的初始化通常需要在类的构造函数中进行。这在某些情况下可能导致代码重复和混乱。类内成员初始化允许在声明成员变量的同时对其进行初始化,从而减少了这些问题。

下面是一个使用类内成员初始化的简单示例:

#include <iostream>
#include <string>

class Person {
public:
    Person(const std::string& name, int age) : name(name), age(age) { // 使用初始化列表初始化成员变量
        // 构造函数体可以为空,也可以执行其他操作
    }
    
    void introduce() const {
        std::cout << "Hello, my name is " << name << " and I am " << age << " years old.\n";
    }
    
private:
    std::string name = "Unknown"; // 使用类内成员初始化初始化成员变量
    int age = 0;                  // 使用类内成员初始化初始化成员变量
};

int main() {
    Person person("Alice", 25);
    person.introduce(); // 输出:Hello, my name is Alice and I am 25 years old.
    return 0;
}

在这个示例中,Person类的nameage成员变量在类定义中进行了初始化。构造函数的初始化列表也使用了这些初始值。这避免了在构造函数体中对每个成员变量进行重复的初始化。

需要注意的是,类内成员初始化只适用于静态成员变量、常量成员变量和非常量引用成员变量。对于其他类型的成员变量,如普通成员变量或指针成员变量,仍然需要在构造函数中进行初始化。

5,C++11新特性-静态断言

C++11引入了静态断言(static_assert)的特性,它允许在编译时进行断言检查,以确保某些条件在编译时得到满足。这有助于在编译期间捕获错误,从而提高代码的质量和可靠性。

静态断言的语法如下:

static_assert(condition, message);

其中,condition是一个编译时常量表达式,表示要进行检查的条件。message是一个字符串,用于描述断言失败时的错误信息。

下面是一个简单的示例,演示如何使用静态断言来检查模板参数的类型:

#include <iostream>
#include <type_traits>

template <typename T>
void printSize() {
    static_assert(std::is_integral<T>::value, "Template parameter T must be an integral type.");
    std::cout << "Size of T: " << sizeof(T) << " bytes\n";
}

int main() {
    printSize<int>(); // 输出:Size of T: 4 bytes
    // printSize<double>(); // 编译错误:Template parameter T must be an integral type.
    return 0;
}

在这个示例中,printSize函数模板使用static_assert来检查模板参数T是否是一个整数类型。如果T不是整数类型,编译器将在编译期间产生错误,并显示提供的错误消息。

静态断言非常有用,可以用于检查模板参数的类型、约束条件、常量表达式的结果等。它可以帮助在编译期间捕获潜在的错误,提高代码的可读性和可维护性。

6,C++11新特性-noexcept

C++11引入了noexcept关键字,它用于指定函数是否抛出异常。noexcept关键字可以与函数声明或定义一起使用,表示该函数不会抛出任何异常。

在C++中,异常处理是一种重要的错误处理机制。然而,在某些情况下,我们可能希望确保某些函数不会抛出异常。这可以通过在函数声明或定义中添加noexcept关键字来实现。

noexcept关键字的语法如下:

return_type function_name(arguments) noexcept;

或者:

return_type function_name(arguments) throw(); // C++11之前的语法,等同于noexcept

下面是一个简单的示例,演示如何使用noexcept关键字:

#include <iostream>
#include <stdexcept>

void foo() noexcept {
    std::cout << "Function foo() called.\n";
    // 这个函数不会抛出异常
}

void bar() {
    std::cout << "Function bar() called.\n";
    throw std::runtime_error("An error occurred in bar()."); // 这个函数会抛出异常
}

int main() {
    try {
        foo(); // 调用不会抛出异常的函数
        bar(); // 调用会抛出异常的函数
    } catch (const std::exception& e) {
        std::cerr << "Caught exception: " << e.what() << '\n';
    }
    return 0;
}

在这个示例中,foo()函数被声明为noexcept,表示它不会抛出任何异常。而bar()函数没有noexcept关键字,它会抛出一个std::runtime_error异常。在main()函数中,我们调用这两个函数,并使用try-catch块捕获异常。

需要注意的是,如果noexcept函数确实抛出了异常,程序将调用std::terminate()函数,导致程序终止。因此,在使用noexcept关键字时,应确保函数不会抛出任何异常。

7,C++11新特性-NULL和nullptr区别

在C++11之前,NULL宏被广泛应用于表示指针的“空”或“无”状态。然而,NULL实际上是一个整数类型的宏定义,通常为0。这可能导致一些类型安全问题和不一致的行为。为了解决这个问题,C++11引入了nullptr关键字,作为一种更安全和一致的表示指针空值的方法。

下面是NULL和nullptr之间的主要区别:

  1. 类型安全性:nullptr是一种指针类型,而NULL是一个整数类型。这意味着使用nullptr可以确保类型安全,因为它只能用于指针类型的赋值或比较。而使用NULL可能会导致类型错误,尤其是在模板和重载函数的情况下。
  2. 一致性:nullptr关键字更清晰地表示其意图,即表示一个空指针。相比之下,NULL宏可能会让人误解为整数值0。
  3. 兼容性:nullptr是C++11及更高版本的标准特性,而NULL宏在C和C++中都存在。因此,如果你的代码需要在旧的C++编译器或C环境中运行,你可能需要继续使用NULL。

下面是一个简单的示例,演示如何使用nullptr:

#include <iostream>

void printValue(int* ptr) {
    if (ptr == nullptr) {
        std::cout << "Pointer is nullptr.\n";
    } else {
        std::cout << "Pointer value: " << *ptr << '\n';
    }
}

int main() {
    int x = 42;
    int* p1 = &x;
    int* p2 = nullptr;

    printValue(p1); // 输出:Pointer value: 42
    printValue(p2); // 输出:Pointer is nullptr
    return 0;
}

在这个示例中,我们使用了nullptr来表示一个空指针,并将其与另一个指向实际值的指针进行比较。

8,C++11新特性-强类型枚举

C++11引入了强类型枚举(strongly typed enumerations)的特性,也称为作用域枚举(scoped enumerations)。这一特性使枚举类型更安全、更具表达性。

在C++11之前,传统的枚举类型存在一些问题。例如,枚举值在作用域中是全局的,这可能导致命名冲突。此外,传统枚举的值可以被隐式转换为整数,这可能导致意外的类型转换和错误。强类型枚举解决了这些问题,为枚举值提供了更清晰的作用域,并禁止了隐式类型转换。

下面是强类型枚举的基本语法:

enum class EnumerationName {
    Enumerator1,
    Enumerator2,
    // ...
};

与传统枚举不同,强类型枚举使用enum class关键字,并在枚举名称前加上作用域解析运算符::来访问枚举值。这意味着强类型枚举的值不会污染全局作用域。

强类型枚举还具有以下特点:

  1. 枚举值的作用域限定在其所在的枚举中。这意味着在不同的枚举中可以存在同名的枚举值,而不会引起冲突。
  2. 强类型枚举的值不会被隐式转换为整数。如果需要将其转换为整数,可以使用静态转换static_cast<int>()
  3. 强类型枚举的前置声明是有效的。这使得在不同文件中使用相同的枚举类型变得更加容易。

下面是一个简单的示例,演示如何使用强类型枚举:

#include <iostream>

enum class Color {
    Red,
    Green,
    Blue
};

int main() {
    Color myColor = Color::Red;
    // myColor = 1; // 错误:无法将整数隐式转换为强类型枚举的值

    switch (myColor) {
        case Color::Red:
            std::cout << "Red color\n";
            break;
        case Color::Green:
            std::cout << "Green color\n";
            break;
        case Color::Blue:
            std::cout << "Blue color\n";
            break;
    }
    return 0;
}

在这个示例中,我们定义了一个名为Color的强类型枚举,并在switch语句中使用它。注意,我们必须使用作用域解析运算符::来访问枚举值。

9,C++11新特性-常量表达式

C++11引入了常量表达式的概念,这是一种在编译时可以被计算的表达式。常量表达式在编译时求值,并且结果是一个常量。这种特性使得程序员可以在编译时执行一些计算,从而提高代码的性能和可读性。

常量表达式可以使用constexpr关键字进行声明。在C++11中,constexpr可以用于声明变量和函数。这意味着这些变量和函数在编译时可以被计算。

下面是常量表达式的一些特点:

  1. 常量表达式的值在编译时确定,并且不能被修改。
  2. 常量表达式只能使用其他常量表达式作为操作数。
  3. 常量表达式可以使用一些特定的运算符和函数,如算术运算符、比较运算符、逻辑运算符、位运算符等。
  4. 常量表达式的计算结果必须在编译时可以确定。

下面是一个简单的示例,演示如何使用constexpr声明常量表达式:

#include <iostream>

constexpr int add(int a, int b) {
    return a + b;
}

int main() {
    constexpr int x = 10;
    constexpr int y = 20;
    constexpr int z = add(x, y);
    std::cout << "The sum of " << x << " and " << y << " is " << z << '\n';
    return 0;
}

在这个示例中,我们定义了一个constexpr函数add,用于计算两个整数的和。我们还声明了三个constexpr整数变量xyz,其中z是通过调用add函数计算的。由于这些值都是在编译时计算的,因此输出结果是固定的,不会受到运行时环境的影响。

需要注意的是,C++11中的constexpr有一些限制,比如函数体只能包含一条语句,并且只能访问其他constexpr变量或函数等。然而,随着C++标准的演进,这些限制已经被逐渐放宽。

10,C++11新特性-常量表达式

C++11引入了原生字符串字面值(Raw String Literals)的特性,这是一种方便表示包含反斜杠或双引号的字符串的方式。原生字符串字面值以R(大小写均可)为前缀,后面紧跟一对双引号。在这对双引号之间的内容将被视为原始字符串,不会对其中的反斜杠或双引号进行转义。

下面是一个简单的示例,演示如何使用原生字符串字面值:

#include <iostream>

int main() {
    std::string path = R"(C:\Users\Username\Documents\file.txt)";
    std::cout << "Path: " << path << '\n';

    std::string json = R"({"name": "John", "age": 30})";
    std::cout << "JSON: " << json << '\n';

    return 0;
}

在这个示例中,我们使用了原生字符串字面值来表示包含反斜杠的文件路径和包含双引号的JSON字符串。由于使用了原生字符串字面值,我们不需要对这些特殊字符进行转义,使代码更加易读。

需要注意的是,原生字符串字面值的语法是R"(...)",其中...表示原始字符串的内容。在(...)之间的任何字符都将被视为原始字符,不会被转义。如果需要在原生字符串中使用双引号,可以使用一对双引号来表示一个双引号字符。例如,R"("Hello, World!")"将表示一个包含文本"Hello, World!"的原生字符串。

11,C++11新特性-继承构造

C++11引入了一项名为继承构造(Inherited Constructors)的新特性,它允许派生类继承基类的构造函数。这意味着,如果基类定义了某个构造函数,派生类可以直接使用该构造函数,而无需显式地重新定义它。

在C++11之前,派生类必须显式地定义所有构造函数,即使它们与基类的构造函数相同。这导致了大量的代码重复,并增加了出错的可能性。继承构造的特性解决了这个问题,使得代码更加简洁和易于维护。

要使用继承构造,只需在派生类的构造函数列表中使用using关键字,并指定要继承的基类构造函数。例如:

class Base {
public:
    Base(int x) { /* ... */ }
    Base(double y) { /* ... */ }
};

class Derived : public Base {
public:
    using Base::Base;  // 继承基类的构造函数
};

在上面的例子中,Derived类继承了Base类的所有构造函数。这意味着Derived对象可以使用Base类的任何构造函数进行初始化,就像它们是Derived类自己的构造函数一样。

需要注意的是,如果派生类定义了与基类构造函数具有相同参数的构造函数,那么该构造函数将隐藏基类的构造函数。在这种情况下,如果想要使用基类的构造函数,可以使用using关键字将其引入到派生类的构造函数列表中。

继承构造是一个非常有用的特性,它可以减少代码重复,提高代码的可读性和可维护性。

12,C++11新特性-委托构造函数

C++11引入了委托构造函数(Delegating Constructors)的新特性,允许一个构造函数委托给同一类中的另一个构造函数来执行初始化工作。这可以帮助减少代码重复,并使构造函数更具可读性和可维护性。

在C++11之前,如果一个类有多个构造函数,它们通常会包含一些共同的初始化代码。为了避免代码重复,程序员通常会将这些共同代码提取到一个私有成员函数中,并在每个构造函数中调用该函数。然而,这种方法有时会导致代码不够清晰和直观。

委托构造函数允许一个构造函数将初始化工作委托给同一类中的另一个构造函数。这可以通过在构造函数的初始化列表中调用同一类的另一个构造函数来实现。例如:

class MyClass {
public:
    MyClass(int x) : MyClass(x, 0) {}  // 委托给另一个构造函数
    MyClass(int x, int y) { /* ... */ }
};

在上面的例子中,MyClass类有两个构造函数。第一个构造函数接受一个int类型的参数x,并将其委托给另一个接受两个int类型参数的构造函数。委托构造函数的初始化列表中调用了同一类的另一个构造函数,并在括号中传递了所需的参数。

需要注意的是,委托构造函数必须在构造函数的初始化列表中调用另一个构造函数,而不能在构造函数的主体中调用。这是因为构造函数的主体在对象已经完全初始化之后执行,而在初始化列表中执行委托可以确保在对象完全初始化之前执行所需的初始化工作。

委托构造函数是一个非常有用的特性,可以帮助减少代码重复,并使构造函数更具可读性和可维护性。它可以使构造函数更加清晰和直观,同时减少了私有成员函数的使用。

13,C++11新特性-final和override关键字

C++11引入了finaloverride这两个关键字,用于更明确地控制类的继承和多态行为。

  1. final关键字:

final关键字可以用于类或者类的成员函数,表示该类不能被继承,或者该成员函数不能在派生类中被重写。

示例:

class Base {
public:
    virtual void foo() { }
};

class Derived : public Base {
public:
    void foo() final { } // 使用final关键字,表示该函数不能被进一步重写
};

class Derived2 : public Derived {
public:
    // void foo() { } // 编译错误,因为Derived类的foo()函数已经被标记为final
};

在上面的例子中,Derived类继承自Base类,并重写了foo()函数,并使用final关键字标记该函数不能被进一步重写。在Derived2类中尝试重写foo()函数会导致编译错误。

  1. override关键字:

override关键字用于类的成员函数,表示该函数旨在重写基类中的虚函数。使用override关键字可以帮助检查代码的正确性,确保函数的签名与基类中的虚函数匹配。

示例:

class Base {
public:
    virtual void foo(int x) { }
};

class Derived : public Base {
public:
    void foo(int x) override { } // 使用override关键字,表示该函数旨在重写基类中的foo()函数
};

在上面的例子中,Derived类继承自Base类,并重写了foo()函数,并使用override关键字表示该函数旨在重写基类中的foo()函数。这样做的好处是,如果基类的foo()函数的签名被修改,编译器会在编译时提示错误,因为Derived类的foo()函数的签名不再匹配。这可以帮助避免潜在的运行时错误。

14,C++11新特性-内存泄漏问题与解决

内存泄漏(memory leak) 是指由于疏忽或错误造成了程序未能释放掉不再使用的内存的情况。内存泄漏并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,失去了对该段内存的控制,因而造成了内存的浪费。

(1)堆内存泄漏 (Heap leak)。对内存指的是程序运行中根据需要分配通过 malloc,realloc new 等从堆中分配的一块内存,再是完成后必须通过调用对应的 free 或者 delete 删掉。如果程序的设计的错误导致这部分内存没有被释放,那么此后这块内存将不会被使用,就会产生Heap Leak 。

(2)系统资源泄露(Resource Leak)。主要指程序使用系统分配的资源比如 Bitmap,handle ,SOCKET 等没有使用相应的函数释放掉,导致系统资源的浪费,严重可导致系统效能降低,系统运行不稳定。解决内存泄漏最有效的办法就是使用智能指针(Smart Pointer)。使用智能指针就不用担心这个问题了,因为智能指针可以自动删除分配的内存。智能指针和普通指针类似,只是不需要手动释放指针,而是通过智能指针自己管理内存的释放,这样就不用担心内存泄漏的问题了。

15,C++11新特性-Lambda表达式的参数捕获有哪几种情况:

C++11引入了Lambda表达式,它是一种匿名函数对象,可以在代码中定义并使用。Lambda表达式的一个重要特性是其参数捕获方式,即Lambda表达式内部如何访问外部变量的。Lambda表达式的参数捕获有以下几种情况:

  1. 值捕获(Capture by Value):

默认情况下,Lambda表达式使用值捕获方式。这意味着在Lambda表达式内部使用外部变量的副本,对副本的修改不会影响外部变量的值。

示例:

int x = 10;
auto f = [x] { return x + 5; };
x = 20;
std::cout << f() << std::endl; // 输出 15,Lambda表达式内部使用的是x的副本
  1. 引用捕获(Capture by Reference):

使用&符号可以指定Lambda表达式使用引用捕获方式。这意味着在Lambda表达式内部直接使用外部变量,对外部变量的修改会影响Lambda表达式内部的值。

示例:

int x = 10;
auto f = [&x] { x += 5; return x; };
x = 20;
std::cout << f() << std::endl; // 输出 25,Lambda表达式内部使用的是x的引用
  1. 隐式捕获(Implicit Capture):

如果不明确指定捕获方式,Lambda表达式会根据需要使用值捕获或引用捕获。对于在Lambda表达式中使用的外部变量,如果它们是可修改的(非常量),则使用引用捕获;否则,使用值捕获。

示例:

int x = 10;
const int y = 20;
auto f = [=] { return x + y; }; // 隐式捕获,x使用值捕获,y使用值捕获
x = 20;
std::cout << f() << std::endl; // 输出 30,x在Lambda表达式内部使用的是副本,y在Lambda表达式内部使用的是副本
  1. 混合捕获(Mixed Capture):

可以在Lambda表达式中同时使用值捕获和引用捕获。可以明确指定某些变量使用值捕获,其他变量使用引用捕获。

示例:

int x = 10;
const int y = 20;
auto f = [&, x] { x += 5; return x + y; }; // 混合捕获,x使用值捕获,y使用引用捕获
x = 20;
std::cout << f() << std::endl; // 输出 25,x在Lambda表达式内部使用的是副本,y在Lambda表达式内部使用的是引用

16,C++11中的lambda表达式格式,怎么写

C++11中的lambda表达式的基本格式如下:

[捕获列表] (参数列表) -> 返回类型 {函数体}

让我们详细解释一下每个部分:

  1. 捕获列表:定义了lambda表达式可以从其封闭作用域中访问的变量。捕获列表可以有以下形式:

    • []:不捕获任何外部变量。
    • [&]:以引用方式捕获所有外部变量。
    • [=]:以值方式捕获所有外部变量。
    • [a, &b]:以值方式捕获a,以引用方式捕获b。
  2. 参数列表:定义了lambda表达式的参数。这与普通函数的参数列表类似,例如 (int a, int b)。如果没有参数,可以使用空括号 ()

  3. 返回类型:定义了lambda表达式的返回类型。这是可选的,如果省略,编译器将自动推断返回类型。例如,-> int 表示该lambda表达式将返回一个整数。

  4. 函数体:定义了lambda表达式的主体。这与普通函数的主体类似,可以包含一系列语句和表达式。函数体必须被花括号 {} 包围。

下面是一个简单的lambda表达式的例子:

auto lambda = [](int a, int b) -> int { return a + b; };
int result = lambda(10, 20); // result现在是30

在这个例子中,我们定义了一个lambda表达式,它接受两个整数参数并返回它们的和。我们使用 auto 关键字来声明一个变量 lambda,该变量的类型是编译器自动推断的lambda函数的类型。然后我们调用这个lambda函数,并将结果存储在 result 变量中。