跳转至

10 函数、Lambda 表达式⚓︎

1024 个字 111 行代码 预计阅读时间 7 分钟

函数、Lambda 表达式⚓︎

引入

这是一个函数模板的例子,它能够计算任何类型的容器内单个指定元素的个数。

思考这个函数模板能够计算多个指定元素的个数?比如将 *iter == target 换成 isVowel(*iter)(是否为元音字母)

template <typename InputIt, typename DataType>
int count_occurrences(InputIt begin, InputIt end, DataType target)
{
    int count = 0;
    for (auto iter = begin; iter != end; ++iter)
        if (*iter == target) count++;
    return count;
}

// ...
std::string str = "Tchaikovsky";
cout << "Occurrence of the letter k in Tchaikovsky: "
    << count_occurrence(str.begin(), str.end(), 'k') << endl;

谓词函数⚓︎

返回值类型为布尔(boolean)类型的函数称为谓词(predicate,谓词可以有任何数量的参数,比如:

// predicate 1
bool isVowel(char c)
{
    std::string vowels = "aeiou";
    return vowels.find(c) != std::string::npos;
}

// predicate 2
bool isMoreThan(int num, int limit)
{
    return num > limit;
}

现在将前面那个函数模板的 target 参数改为一个含单参的谓词函数,修改后如下所示:

template <typename InputIt, typename UniPred>
int count_occurrences(InputIt begin, InputIt end, UniPred pred)
{
    int count = 0;
    for (auto iter = begin; iter != end; ++iter)
        if (pred(*iter)) count++;
    return count;
}

// ...
std::string str = "Tchaikovsky";
count_occurrence(str.begin(), str.end(), isVowel) ;

函数指针⚓︎

上面函数模板中的 UniPred 是一个函数指针(function pointer,它们既可以被看作一般指针,也可以作为参数传递,还可以像函数一样被调用。

上面的函数模板并不是一个很好的泛型编程,因为它那个作为参数的谓词函数是单参的,那么就必然有一定的局限性。如果尝试将单参换成双参:

template <typename InputIt, typename BinPred>
int count_occurrences(InputIt begin, InputIt end, BinPred pred)
{
    int count = 0;
    for (auto iter = begin; iter != end; ++iter)
        if (pred(*iter, ???)) count++;
    return count;
}

可以发现不能直接这么做,因为我们无法传递谓词函数的第二个参数——这时就需要用到 Lambda 表达式。

Lambda 表达式⚓︎

它是一种内联、匿名的函数,能够发现与它位于同一范围内的变量(即使变量在函数的外部。捕获这些变量(的值或引用)的东西称为捕获从句(capture clauses

注:建议在函数主体相对较为简单的时候使用,主体过于复杂时还是用函数指针吧。

基本格式:

auto var = [capture-clause] (auto param) -> bool
{
    // ...
}

各种捕获从句:

  • []:不捕获东西
  • [limit]:捕获变量 limit 的值
  • [&limit]:捕获变量 limit 的引用
  • [&limit, upper]:捕获变量 limit 的引用和变量 upper 的值
  • [&, limit]:捕获除变量 limit 之外的变量的引用
  • [&]:捕获所有变量的引用
  • [=]:捕获所有变量的值

举个 🌰

int limit = 5;
auto isMoreThan = [limit](int n){ return n > limit };
isMoreThan(6); // true

利用 Lambda 表达式,我们修改原来的函数模板,如下所示:

template <typename InputIt, typename UniPred>
int count_occurrences(InputIt begin, InputIt end, UniPred pred)
{
    int count = 0;
    for (auto iter = begin; iter != end; ++iter)
        if (pred(*iter)) count++;
    return count;
}

int limit = 5;
auto isMoreThan = [limit] (int n) { return n > limit; }
std::vector<int> nums = {3, 5, 6, 7, 9, 13};

count_occurrences(num.begin(), nums.end(), isMoreThan);

函数对象⚓︎

目前不是很理解,待补充

函数对象(functor)是一种提供运算符(operator())实现的类。它能够创建“自定义”函数的闭包(closure(指的是某个函数的实例化Lambda 表达式便是其中一种。

举个 🌰

class functor
{
    public:
        int operator() (int arg) const
        {
            return num + arg;
        }
    private:
        int num;
}

int num = 0;
auto lambda = [&num] (int arg) { num += arg; };
lambda(5);

STL 中专门提供了一种标准的函数对象:std::function<return_type(param_types)> func;,之前提到的 Lambda 表达式、函数对象和函数指针均可被投射到这一标准函数,而后者相比前三者更大,成本也更加高。

虚拟函数⚓︎

在介绍继承的最后稍微讲到了虚拟函数(virtual functions,它允许我们用子类的函数覆写基类的同名函数,使用时只需在基类的函数前加上关键词 virtual 即可。

例子

// 基类
class Animal
{
    void speak()
    {
        std::cout << "I'm an animal!" << std::endl;
    }
}

// 子类
class Dog : Animal
{
    void speak()
    {
        std::cout << "I'm a dog!" << std::endl;
    }
}


void func(Animal * animal)
{
    animal->speak();
}

int main()
{
    Animal * myAnimal = new Animal;
    Dog * myDog = new Dog;
    func(myAnimal);
    func(myDog);
}

在这个例子中,func() 函数接收基类的指针,因此它不会使用子类的函数。同样的问题出现在指向子类对象的基类指针上。如果想要用子类的函数覆写基类的函数,只需在父类的函数 speak() 前加关键词 virtual,即 virtual void speak() 即可。

算法⚓︎

编程世界中经常提到的一条原则:绝不重复造轮子!于是 C++ STL 很贴心地为开发者们提供了许多轮子,这里我们简单介绍其中的一个轮子——算法库。使用前需在添加 #include <algorithm> 即可。这些算法都是泛型函数、模板函数,包含了一些高效查找、排序、复杂数据结构的运算、智能指针等等的功能,它们都是工作在迭代器之上的。

部分算法:

  • any_of(), all_of(), none_of():检查所有元素是否满足某一条件
  • for_each():将一个函数用于容器内所有元素
  • find(), search():找到特定元素 / 一系列元素
  • copy():从一个容器内复制,删除、增加元素到另一个容器内

前面提到的函数模板 count_occurrences() 实际上就是 STL count_if() 算法。

评论区

如果大家有什么问题或想法,欢迎在下方留言~