变量

在C++中,变量是用来存储数据的命名存储单元。

  1. 变量声明和定义
    • 声明:告诉编译器变量的名称和类型,但不分配存储空间(通常用extern声明)。
    • 定义:为变量分配存储空间并初始化
      1
      2
      3
      4
      5
      // 声明
      extern int x;

      // 定义
      int x = 10;
  2. 变量的命名规则
    • 必须以字母或下划线_开头。
    • 只能包含字母、数字和下划线。
    • 不能是C++的关键字(如int、class等)。
    • 区分大小写(var和Var是不同的变量)。
  3. 变量的分类
    (1)按作用域分类

    • 局部变量:定义在函数或块中,只能在其所在作用域内使用。
    • 全局变量:定义在所有函数之外,作用域为整个程序。
    • 静态变量:用static修饰,局部变量的作用域仍在函数内,但生命周期贯穿整个程序。

      (2)按存储类型分类

    • 自动变量(Automatic Variables):默认存储期,生命周期随作用域结束。
    • 静态变量(Static Variables):用static修饰,生命周期贯穿程序。
    • 外部变量(External Variables):用extern声明,在其他文件中定义。
    • 线程局部变量(Thread-local Variables):用thread_local修饰,每个线程有独立副本。

      (3)按数据类型分类

      • 基本数据类型:

        • 整数类型:int、short、long、long long。
        • 浮点类型:float、double、long double。
        • 字符类型:char。
        • 布尔类型:bool。
      • 派生数据类型:

        • 数组:int arr[5];
        • 指针:int* ptr = &a;
        • 引用:int& ref = a;
        • 函数:返回值类型 + 参数列表。
      • 用户自定义类型:
        • 枚举类型(enum):一组命名常量。
        • 类类型(class):面向对象中的核心。
        • 结构体(struct):数据的集合。
        • 联合体(union):共享内存。
        • typedef:为类型定义别名。
  4. 变量的存储类型
    通过关键字调整存储、作用域和生命周期:

    • auto:自动推导变量类型(C++11)。
    • register:建议变量存储在寄存器(现代编译器忽略)。
    • static:静态存储期。
    • extern:声明外部变量。
    • mutable:允许修改const对象的成员。
    • thread_local:线程局部变量。
  5. 变量的初始化
    • 显式初始化:在定义时赋初值。
    • 默认初始化:
      • 局部变量:未初始化时包含垃圾值。
      • 全局变量和静态变量:会被默认初始化为0或对应类型的默认值。

数据类型

C++中的数据类型用于定义变量存储的类型和大小,帮助程序更高效地管理内存和操作数据。C++提供了多种基本数据类型,包括整型、浮点型、字符型、布尔型等。此外,C++还支持用户自定义数据类型,如结构体和类。

以下是C++中常用的基本数据类型:

  1. 整型:整型用于存储整数,包括有符号整型和无符号整型。常见的整型有int、short、long、long long等。其中,int表示整型,short表示短整型,long表示长整型,long long表示长长整型。无符号整型在前面加上unsigned关键字,如unsigned int、unsigned short等。
  2. 浮点型:浮点型用于存储小数,包括单精度浮点型float和双精度浮点型double。
  3. 字符型:字符型用于存储单个字符,包括char类型。char类型可以存储一个字符,如字母、数字、符号等。
  4. 布尔型:布尔型用于存储布尔值,包括bool类型。bool类型可以存储true和false两个值。
  5. 字符串型:字符串型用于存储字符串,包括char类型的数组。可以使用std::string类来表示字符串。
  6. 指针型:指针型用于存储变量的地址,包括int、float、char*等。
  7. 数组型:数组型用于存储一组相同类型的数据,包括int[]、float[]、char[]等。
  8. 枚举型:枚举型用于定义一组命名的常量,包括enum关键字。
  9. 结构体型:结构体型用于定义一组不同类型的数据,包括struct关键字。
  10. typedef:typedef用于为已有的数据类型定义一个新的名字,方便使用。
  11. 类:类用于定义一组数据和操作这些数据
  12. 空类型(Void Type):用于函数不返回任何值的情况。

常量

在C++中,常量是一种特殊的变量,其值在程序执行期间不能被修改。常量可以用于存储不会改变的值,如数学常数π、物理常数等。使用常量可以提高代码的可读性和可维护性,因为它们使代码中的硬编码值更加明确。
C++中有几种声明常量的方式:

  1. 使用const关键字:const关键字可以用于声明常量,其语法如下:
    1
    const 数据类型 常量名 = 值;
  2. 使用#define预处理指令:#define预处理指令可以用于定义宏,宏是一种常量的替代方式,其语法如下:
    1
    #define 宏名 值
  3. 使用constexpr关键字:constexpr关键字可以用于声明常量表达式,常量表达式是在编译时就可以确定其值的表达式,其语法如下:
    1
    constexpr 数据类型 常量名 = 值;
  4. 使用枚举类型:枚举类型可以用于定义一组常量,其语法如下:
    1
    2
    enum 枚举类型名 {	
    }

    指针

    指针基础

  5. 定义和使用
    指针是一个变量,其值为另一个变量的地址,即,内存位置的直接地址。就像其他变量或常量一样,必须在使用指针存储其他变量地址之前,对其进行声明。
    例如:

    1
    2
    3
    4
    5
    int  var = 20;   // 实际变量的声明
    int *ip; // 指针变量的声明

    ip = &var; // 在指针变量中存储 var 的地址。&取地址。

  6. 空指针

    1
    int *ptr = NULL;  //ptr的值是 0,避免野指针,确保指针在使用前初始化。
  7. 指针的算数运算
    指针是一个用数值表示的地址。因此,可以对指针进行四种算术运算:++、--、+、-

    • 递增或递减:在C++中,指针是一个变量,它存储一个内存地址。递增(递减)一个指针意味着将指针指向下一个(上一个)内存位置,这通常是指向下一个(上一个)数组元素。递增(递减)一个指针会根据指针所指向的数据类型自动调整指针的值。例如,若为int型指针,指针 ptr 会向前(向后)移动 4 个字节,指向下一个(上一个)整型元素的地址。
    • 加法运算:当一个指针p加上一个整数n时,结果是指针p向前移动n个元素的大小。例如,如果ptr是一个int类型的指针,每个int占4个字节,那么p + 1将指向p所指向的下一个int元素。
    • 减法运算:当一个指针p减去一个整数n时,结果是指针p向后移动n个元素的大小。例如,如果ptr是一个int类型的指针,每个int占4个字节,那么p - 1将指向p所指向的上一个int元素。
    • 指针与指针之间的减法运算:两个指针相减的结果是它们之间的元素数量。例如,如果ptr1和ptr2是指向相同类型的指针,并且ptr1在ptr2之前,那么ptr1 - ptr2将给出它们之间的元素数量。
  8. 指针的比较运算
    两个指针可以比较,以确定它们是否指向相同的内存位置。例如,如果ptr1和ptr2是指向相同类型的指针,并且它们指向相同的内存位置,那么ptr1 == ptr2将为真。

指针与数组

指针和数组是密切相关的,很多情况下,指针和数组在是可以互换的。
例如,一个指向数组开头(数组第一个元素)的指针,可以通过使用指针的算术运算或数组索引来访问数组。
例如

1
2
3
int arr[5] = {1, 2, 3, 4, 5};
int *p = arr; // 指向数组的第一个元素
*(p + 2); // 获取arr[2]

指针与字符串

字符串可以通过字符指针表示:

1
2
3
char str[] = "Hello";
char *p = str; // 指向字符串首地址
std::cout << *(p + 1); // 输出e

指针数组

指针数组是一个数组,其每个元素都是一个指针。常用于管理多个动态分配的对象或数据。
例如:

1
int *arr[5]; // 声明一个指针数组,包含5个int类型的指针。即arr是一个数组,数组中的每个元素都是int *类型的指针。

动态内存管理

通过指针动态分配和释放内存:

1
2
int *p = new int(42); // 动态分配一个int类型的内存空间,并将值初始化为42,并将地址赋值给指针p
delete p; // 释放p所指向的内存空间

动态数组:
1
2
int *arr = new int[n]; // 动态分配一个大小为n的int类型的数组,并将地址赋值给指针arr
delete[] arr; // 释放arr所指向的数组内存空间

使用 new 和 delete 动态分配和释放对象:
1
2
3
4
5
6
7
8
9
10
11
12
class Box {
public:
int width;
void display() {
std::cout << "Width: " << width << std::endl;
}
};
Box* b = new Box(); // 动态分配
b->width = 20;
b->display();

delete b; // 释放动态分配的对象

函数指针

函数指针是一个指向函数的指针,可以用来调用函数。函数指针可以用于回调函数、事件处理等场景。
例如:

1
2
3
4
5
6
int add(int a, int b) {
return a + b;
}

int (*p)(int, int) = add; // 声明一个函数指针p,指向add函数
int result = p(2, 3); // 调用add函数,并将结果赋值给result

函数参数传递

将实参的地址传递给形参,函数内部通过解引用指针访问和修改实参。
例如:

1
2
3
4
5
6
void increment(int* p) {
(*p)++;
}
int a = 10;
increment(&a);
std::cout << a; // 输出11

指针与结构体

指针可以用于指向结构体,从而方便地访问结构体的成员。
例如:

1
2
3
4
5
6
7
8
struct Person {
std::string name;
int age;
};

struePerson p = {"Tom", 20};
Person *ptr = &p; // 指向结构体p的指针
std::cout << ptr->name; // 输出Tom

指针与类

指针可以用于访问成员函数和变量。
通过箭头操作符(->)访问对象的成员:

1
2
3
4
5
6
7
8
9
10
11
12
class Box {
public:
int width;
void display() {
std::cout << "Width: " << width << std::endl;
}
};

Box box;
Box *ptr = &box;
ptr->width = 20;
ptr->display(); // 输出Width: 20

指针与多态

多态允许程序通过基类的指针或引用调用派生类的成员函数,从而实现运行时的动态行为。
原理:

  • 基类指针指向派生类对象:
    • 通过基类指针访问派生类的成员。
    • 基类中必须有虚函数,才能实现多态。
  • 动态绑定:
    • 虚函数的调用在运行时决定,而不是在编译时。
    • 动态绑定由虚函数表(vtable)和虚函数指针(vptr)实现。
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      #include <iostream>
      using namespace std;

      class Shape {
      public:
      virtual void draw() const { // 虚函数
      cout << "Drawing Shape" << endl;
      }
      virtual ~Shape() {} // 虚析构函数
      };

      class Circle : public Shape {
      public:
      void draw() const override { // 重写基类的虚函数
      cout << "Drawing Circle" << endl;
      }
      };

      class Rectangle : public Shape {
      public:
      void draw() const override {
      cout << "Drawing Rectangle" << endl;
      }
      };

      int main() {
      Shape* shape1 = new Circle(); // 基类指针指向派生类对象
      Shape* shape2 = new Rectangle(); // 基类指针指向派生类对象

      shape1->draw(); // 动态绑定,调用 Circle 的 draw
      shape2->draw(); // 动态绑定,调用 Rectangle 的 draw

      delete shape1; // 释放内存
      delete shape2;
      return 0;
      }
      // 输出:
      //Drawing Circle
      //Drawing Rectangle

指针与迭代器

STL中,迭代器本质是指针的抽象:

1
2
3
std::vector<int> vec = {1, 2, 3};
auto it = vec.begin();
std::cout << *it; // 输出1

指针与文件操作

指针可以用于读取和写入文件,通过文件指针(FILE*)操作文件。
例如:

1
2
3
4
5
6
7
8
9
10
11
12
FILE *file = fopen("example.txt", "r"); // 打开文件
if (file == NULL) {
std::cout << "Failed to open file" << std::endl;
return 1;
}

char buffer[100];
while (fgets(buffer, sizeof(buffer), file) != NULL) {
std::cout << buffer;
}

fclose(file); // 关闭文件

智能指针

C++11引入智能指针,用来自动管理动态内存,避免内存泄漏和悬挂指针问题。通过标准库提供的模板类,如 std::unique_ptr、std::shared_ptr 和 std::weak_ptr,可以替代原生指针的手动管理,提升代码的安全性和可读性。

  • unique_ptr:独占指针,不可复制,但可以移动。
  • shared_ptr:共享指针,多个shared_ptr可以指向同一个对象,通过引用计数管理对象的生命周期。
  • weak_ptr:弱指针,不增加引用计数,用于避免循环引用。

内存映射与硬件编程

指针直接操作内存地址,用于嵌入式开发和硬件编程:

1
2
int* reg = reinterpret_cast<int*>(0x40021000); // 假设一个硬件寄存器地址
*reg = 0x1; // 设置寄存器值

函数

函数基础

一个C++函数由以下部分组成:

  • 返回类型:函数返回值的类型,可以是 intvoiddouble 等。
  • 函数名:标识函数的名字,用于调用。
  • 参数列表:括号中的参数,用于传递数据。
  • 函数体:大括号 {} 包裹的代码块,是函数的具体实现。

语法:

1
2
3
4
返回类型 函数名(参数列表) {
// 函数体
return 返回值; // 如果返回类型是 void,则省略
}

示例:
1
2
3
int add(int a, int b) {
return a + b;
}

函数声明

函数声明会告诉编译器函数名称及如何调用函数。函数的实际主体可以单独定义。

函数参数

如果函数要使用参数,则必须声明接受参数值的变量。这些变量称为函数的形式参数。
形式参数就像函数内的其他局部变量,在进入函数时被创建,退出函数时被销毁。
当调用函数时,有三种向函数传递参数的方式:

  • 传值调用:将实际参数的值复制给形式参数,即复制一份参数的值。在这种情况下,修改函数内的形式参数对实际参数没有影响。
    1
    2
    3
    4
    int add(int a, int b) {
    a = a + b;
    return a;
    }
  • 指针调用:将实际参数的地址复制给形式参数,在函数内,该地址用于访问调用中要用到的实际参数。这意味着,修改形式参数会影响实际参数。
    1
    2
    3
    4
    int add(int *a, int *b) {
    *a = *a + *b;
    return *a;
    }
  • 引用调用:将实际参数的引用复制给形式参数,在函数内,该引用用于访问调用中要用到的实际参数。这意味着,修改形式参数会影响实际参数。
    1
    2
    3
    4
    int add(int &a, int &b) {
    a = a + b;
    return a;
    }

函数的调用

函数调用是通过函数名和参数列表完成的:

1
2
3
4
5
int main() {
int result = add(3, 5); //传值调用
std::cout << "Result: " << result << std::endl; // 输出 Result: 8
return 0;
}

函数重载

同名函数可以根据参数类型或个数的不同实现多种功能。

1
2
3
4
5
6
7
8
9
10
11
int add(int a, int b) {
return a + b;
}

double add(double a, double b) {
return a + b;
}

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

递归函数

函数调用自身,用于处理递归问题。

1
2
3
4
5
6
7
int factorial(int n) {
if (n == 0) {
return 1;
} else {
return n * factorial(n - 1);
}
}

内联函数 (inline)

C++ 中的 内联函数(inline function)是一种在编译阶段通过将函数调用替换为函数体来减少函数调用开销的机制。它通常用于那些频繁调用但函数体很小的函数,从而避免函数调用的额外开销。

内联函数的特点

  1. 通过关键字inline声明。
    1
    2
    3
    inline int add(int a, int b) {
    return a + b;
    }
  2. 减少函数调用开销:
    • 内联函数避免了普通函数调用时的压栈、跳转等开销。
    • 适合用于逻辑简单、频繁调用的场景。

内联函数的使用

  1. 小函数:函数体较小,且频繁调用。

    1
    2
    3
    inline int square(int x) {
    return x * x;
    }
  2. 模板代码:模板类的成员函数定义通常在头文件中内联。

    1
    2
    3
    4
    5
    6
    7
    template <typename T>
    class MyClass {
    public:
    inline T getValue() const { return value; }
    private:
    T value;
    };
  3. 性能关键代码:如数学运算。

函数指针

在C++中,函数指针是一种特殊的指针类型,用于存储函数的地址,并通过它调用该函数。函数指针非常灵活,常用于回调函数、动态函数选择等场景。

定义函数指针

函数指针的声明形式与函数的签名类似,只是在前面加上 * 表示指针。

1
返回类型 (*指针名)(参数列表);

示例:
1
2
3
4
5
6
int add(int a, int b) {
return a + b;
}

int (*funcPtr)(int, int) ; // 定义一个指向返回类型为 int,接收两个 int 参数的函数指针
funcPtr = add; // 将函数地址赋值给指针add;

使用函数指针

通过函数指针调用函数:

1
int result = funcPtr(3, 5);  // 使用指针调用函数,等价于 add(3, 4)

函数指针作为参数

函数指针可以作为函数的参数,用于实现动态调用。

1
2
3
4
5
6
7
8
9
void executeFunction(int (*funcPtr)(int, int), int a, int b) {
int result = funcPtr(a, b);
std::cout << "Result: " << result << std::endl;
}

int main() {
executeFunction(add, 3, 5); // 传入函数指针
return 0;
}

函数指针数组

函数指针可以存储在数组中,方便管理多个函数。函数指针数组是一种数组,其元素是指向函数的指针。

1
int (*funcPtrArray[3])(int, int);

类成员函数

在 C++ 中,类成员函数是类的组成部分,用于定义类对象可以执行的操作。成员函数可以访问类的成员变量,并能实现封装和操作逻辑。(这里不做过多介绍)

常量成员函数

在 C++ 中,“常量函数”通常是指常量成员函数,也就是使用 const 关键字修饰的成员函数。这类函数的特点是:它承诺不修改类的成员数据。

静态成员函数

在 C++ 中,static 关键字还可以用于类中的成员函数。静态成员函数属于类本身,而不是类的实例。因此,它不能访问类的非静态成员变量或成员函数,只能访问静态成员。

静态函数

static 修饰函数本身时,它的作用是限制该函数的作用范围。被 static 修饰的函数只能在定义它的源文件内使用,不能在其他源文件中调用。这通常用于将函数封装成只在文件内使用的工具,防止外部程序调用。这样的修饰符在进行模块化编程时非常有用,可以隐藏不需要暴露给外部的实现细节。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// file1.cpp
#include <iostream>
using namespace std;

static void hello() {
cout << "Hello from static function!" << endl;
}

int main() {
hello(); // 可以调用
return 0;
}

// file2.cpp
extern void hello(); // 编译错误:无法链接到 static 函数

在这个例子中,func() 函数在 file1.c 内部是可见的,但在 file2.c 中就无法访问。

函数模板

模板是泛型编程的基础,泛型编程即以一种独立于任何特定类型的方式编写代码。
所谓函数模板,实际上是建立一个通用函数,其函数类型和形参类型不具体指定,用一个虚拟的类型来代表。这个通用函数就称为函数模板。
模板函数定义的一般形式如下所示:

1
2
3
4
5
template < 类型形式参数表 >
类型 函数名 (形式参数表)
{
//语句序列
}

实例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <iostream>
#include <string>

using namespace std;

template <typename T>
T Max (T a, T b)
{
return a < b ? b:a;
}
int main ()
{

int i = 39;
int j = 20;
cout << "Max(i, j): " << Max(i, j) << endl;

double f1 = 13.5;
double f2 = 20.7;
cout << "Max(f1, f2): " << Max(f1, f2) << endl;

string s1 = "Hello";
string s2 = "World";
cout << "Max(s1, s2): " << Max(s1, s2) << endl;

return 0;
}

模板可以有多个类型参数:
1
2
3
4
template<typename T1, typename T2>
void display(T1 a, T2 b) {
cout << "a: " << a << ", b: " << b << endl;
}

Lambda表达式

Lambda表达式:是 C++11引入的一种函数对象,可以方便地创建匿名函数。与传统的函数不同,Lambda表达式可以在定义时直接嵌入代码,无需单独定义函数名称、参数和返回类型等信息。Lambda表达式通常用于需要定义一些简单的回调函数或者函数对象。

  1. 什么是 Lambda表达式
    Lambda表达式是一种在被调用的位置或作为参数传递给函数的位置定义匿名函数对象(闭包)的简便方法。Lambda表达式的基本语法如下:

    1
    [capture list] (parameter list) -> return type { function body }

    其中:

    • capture list 是捕获列表,用于指定 Lambda表达式可以访问的外部变量,以及是按值还是按引用的方式访问。捕获列表可以为空,表示不访问任何外部变量,也可以使用默认捕获模式 &= 来表示按引用或按值捕获所有外部变量,还可以混合使用具体的变量名和默认捕获模式来指定不同的捕获方式。
    • parameter list 是参数列表,用于表示 Lambda表达式的参数,可以为空,表示没有参数,也可以和普通函数一样指定参数的类型和名称,还可以在 c++14 中使用 `auto 关键字来实现泛型参数。
    • return type 是返回值类型,用于指定 Lambda表达式的返回值类型,可以省略,表示由编译器根据函数体推导,也可以使用 -> 符号显式指定,还可以在 c++14 中使用 auto 关键字来实现泛型返回值。
    • function body 是函数体,用于表示 Lambda表达式的具体逻辑,可以是一条语句,也可以是多条语句,还可以在 c++14 中使用 constexpr 来实现编译期计算。

      return type一般省略,所以lambda表达式一般式这个形式:

      1
      [capture list] (parameter list) { function body }
  2. 理解
    首先例如

    1
    2
    3
    4
    []
    {
    cout << "hello lambda" << endl;
    }

    这是一个最简单的lambda表达式,在它后面加上()就可以调用它。
    但是,不会这么用,给它一个名字,由于不知道什么类型,所以用auto

    1
    2
    3
    4
    auto L= []
    {
    cout << "hello lambda" << endl;
    };

    使用L()就可以调用它了。

  3. lambda表达式的捕获方法

    • 值捕获(capture by value):在捕获列表中使用变量名,表示将该变量的值拷贝到 Lambda 表达式中,作为一个数据成员。值捕获的变量在 Lambda 表达式定义时就已经确定,不会随着外部变量的变化而变化。值捕获的变量默认不能在 Lambda 表达式中修改,除非使用 mutable 关键字。
      例如:

      1
      2
      3
      4
      int x = 10;
      auto f = [x] (int y) { return x + y; }; // 值捕获 x
      x = 20; // 修改外部的 x
      cout << f(5) << endl; // 输出 15,不受外部 x 的影响
    • 引用捕获(capture by reference):在捕获列表中使用 &加变量名,表示将该变量的引用传递到 Lambda 表达式中,作为一个数据成员。引用捕获的变量在 Lambda 表达式调用时才确定,会随着外部变量的变化而变化。引用捕获的变量可以在 Lambda 表达式中修改,但要注意生命周期的问题,避免悬空引用的出现。

      1
      2
      3
      4
      int x = 10;
      auto f = [x] (int y) { return x + y; }; // 值捕获 x
      x = 20; // 修改外部的 x
      cout << f(5) << endl; // 输出 25,受外部 x 的影响
    • 隐式捕获(implicit capture):在捕获列表中使用 =&,表示按值或按引用捕获 Lambda 表达式中使用的所有外部变量。这种方式可以简化捕获列表的书写,避免过长或遗漏。隐式捕获可以和显式捕获混合使用,但不能和同类型的显式捕获一起使用。

      1
      2
      3
      4
      5
      6
      int x = 10;
      int y = 20;
      auto f = [=, &y] (int z) { return x + y + z; }; // 隐式按值捕获 x,显式按引用捕获 y
      x = 30; // 修改外部的 x
      y = 40; // 修改外部的 y
      cout << f(5) << endl; // 输出 55,不受外部 x 的影响,受外部 y 的影响
  4. lambda表达式的使用

    • 定义简单的匿名函数

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      #include <iostream>
      using namespace std;

      int main()
      {
      // 定义一个 Lambda表达式,计算两个数的和
      auto plus = [] (int a, int b) -> int { return a + b; };
      // 调用 Lambda表达式
      cout << plus(3, 4) << endl; // 输出 7

      // 定义一个 Lambda表达式,判断一个数是否为奇数
      auto is_odd = [] (int n) { return n % 2 == 1; };
      // 调用 Lambda表达式
      cout << is_odd(5) << endl; // 输出 1
      cout << is_odd(6) << endl; // 输出 0

      return 0;
      }
    • 捕获外部变量

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      #include <iostream>
      using namespace std;

      int main()
      {
      int x = 10;
      int y = 20;

      // 定义一个 Lambda表达式,按值捕获 x 和 y
      auto add = [x, y] () -> int { return x + y; };
      // 调用 Lambda表达式
      cout << add() << endl; // 输出 30

      // 修改 x 和 y 的值
      x = 100;
      y = 200;

      // 再次调用 Lambda表达式
      cout << add() << endl; // 输出 30,捕获的是 x 和 y 的副本,不受外部变化的影响

      // 定义一个 Lambda表达式,按引用捕获 x 和 y
      auto mul = [&x, &y] () -> int { return x * y; };
      // 调用 Lambda表达式
      cout << mul() << endl; // 输出 20000

      // 修改 x 和 y 的值
      x = 1000;
      y = 2000;

      // 再次调用 Lambda表达式
      cout << mul() << endl; // 输出 2000000,捕获的是 x 和 y 的引用,会反映外部变化的影响

      return 0;
      }
    • Lambda表达式作为函数参数

    • Lambda表达式作为函数返回值
  5. lambda表达式的实质
    Lambda表达式虽然是一种语法糖,但它本质上也是一种函数对象,也就是重载了 operator() 的类的对象。每一个 Lambda表达式都对应一个唯一的匿名类,这个类的名称由编译器自动生成,因此我们无法直接获取或使用。Lambda表达式的捕获列表实际上是匿名类的数据成员,Lambda表达式的参数列表和返回值类型实际上是匿名类的 operator() 的参数列表和返回值类型,Lambda表达式的函数体实际上是匿名类的 operator() 的函数体。
    例如

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    int x = 10;
    auto f = [x] (int y) -> int { return x + y; };

    //相当于定义了一个匿名类,类似于:

    int x = 10;
    class __lambda_1
    {
    public:
    __lambda_1(int x) : __x(x) {} // 构造函数,用于初始化捕获的变量
    int operator() (int y) const // 重载的 operator(),用于调用 Lambda表达式
    {
    return __x + y; // 函数体,与 Lambda表达式的函数体相同
    }
    private:
    int __x; // 数据成员,用于存储捕获的变量
    };
    auto f = __lambda_1(x); // 创建一个匿名类的对象,相当于 Lambda表达式
  6. lambda(C++14)

    • 初始化捕获(init capture):C++14 引入的一种新的捕获方式,它允许在捕获列表中使用初始化表达式,从而在捕获列表中创建并初始化一个新的变量,而不是捕获一个已存在的变量。这种方式可以使用 auto 关键字来推导类型,也可以显式指定类型。这种方式可以用来捕获只移动的变量,或者捕获 this 指针的值。
      1
      2
      3
      4
      int x = 10;
      auto f = [z = x + 5] (int y) -> int { return z + y; }; // 初始化捕获 z,相当于值捕获 x + 5
      x = 20; // 修改外部的 x
      cout << f(5) << endl; // 输出 20,不受外部 x 的影响
    • 泛型 Lambda:C++14 允许在 Lambda表达式的参数列表和返回值类型中使用 auto 关键字,从而实现泛型 Lambda,即可以接受任意类型的参数和返回任意类型的值的 Lambda表达式。
  7. lambda(C++17)

    • 捕获 this 指针:C++17 允许在 Lambda表达式的捕获列表中使用 *this,从而实现捕获 this 指针,即可以在 Lambda表达式中访问当前对象的成员变量和成员函数。
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      #include <iostream>
      using namespace std;

      // 定义一个类
      class Test
      {
      public:
      Test(int n) : num(n) {} // 构造函数,初始化 num
      void show() // 成员函数,显示 num
      {
      cout << num << endl;
      }
      void add(int x) // 成员函数,增加 num
      {
      // 定义一个 Lambda表达式,捕获 this 指针
      auto f = [*this] () { return num + x; };
      // 调用 Lambda表达式
      cout << f() << endl;
      }
      private:
      int num; // 成员变量,存储一个整数
      };

      int main()
      {
      Test t(10); // 创建一个 Test 对象
      t.show(); // 调用成员函数,输出 10
      t.add(5); // 调用成员函数,输出 15

      return 0;
      }