函数基础

一个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;
      }