C++ 函数式编程教程

简介: C++ 函数式编程学习

约定
在本书中,您将找到一些文本样式,用于区分不同类型的信息。以下是一些这些样式的示例及其含义的解释。

文本中的代码词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL 和用户输入显示如下:"auto关键字也可以应用于函数,以自动推断函数的返回类型。"
代码块设置如下:
int add(int i, int j)
{
return i + j;
}
当我们希望引起您对代码块的特定部分的注意时,相关行或项目将以粗体显示:
// Initializing a string variable
Name n = {"Frankie Kaur"};
cout << "Initial name = " << n.str;
cout << endl;
新术语和重要单词以粗体显示。
警告或重要提示会出现在这样的形式。
提示和技巧会出现在这样的形式。
第一章:深入现代 C++
自 1979 年发明以来,C++编程语言发生了巨大变化。在这个时代,有些人可能会有点害怕使用 C++语言编码,因为它不够用户友好。我们有时必须处理的内存管理有时会让人不愿意使用这种语言。幸运的是,自C++11--也被称为现代 C++,以及C++14和C++17--发布以来,已经引入了许多功能来简化我们在 C++语言中的代码。而且,最好的部分是 C++编程语言是任何项目的绝佳语言,从低级编程到 Web 编程,以及函数式编程。
这一章是我们在本书中开始旅程的最佳地方,因为它是为 C++程序员设计的,可以更新他们的知识,并将讨论以下主题:
理解现代 C++中的一些新功能
在现代 C++中实现 C++标准库
使用 Lambda 表达式和 C++ Lambda 中包含的所有功能
使用智能指针避免手动内存管理
使用元组处理多个返回值
深入了解现代 C++中的一些新功能
那么,现代 C++与旧版本相比有什么新功能?与旧版本相比,现代 C++中有很多变化,如果我们讨论所有这些变化,书页将大幅增加。然而,我们将讨论现代 C++中的新功能,我们应该了解这些功能,以使我们在编码活动中更加高效。我们将讨论几个新关键字,如auto、decltype和nullptr。我们还将讨论begin()和end()函数的增强,这些函数现在已成为非成员类函数。我们还将讨论对使用range-based for loop技术迭代集合的for-each技术的增强支持。
本章的接下来几个小节还将讨论现代 C++的新功能,即 Lambda 表达式、智能指针和元组,这些功能刚刚在 C++11 发布中添加。
使用auto关键字自动定义数据类型
在现代 C++之前,C++语言有一个名为auto的关键字,用于明确指定变量应具有自动持续时间。遵循变量的自动持续时间将在定义点创建变量(如果相关,则初始化),并在退出定义它们的块时销毁变量。例如,局部变量将在函数开始时定义并在程序退出包含局部变量的函数时销毁。
自 C++11 以来,auto关键字用于告诉编译器从其初始化程序推断出正在声明的变量的实际类型。自 C++14 以来,该关键字还可以应用于函数,以指定函数的返回类型,即尾随返回类型。现在,在现代 C++中,使用auto关键字指定自动持续时间已被废除,因为默认情况下所有变量都设置为自动持续时间。
以下是一个auto.cpp代码,演示了变量中使用auto关键字。我们将使用auto关键字定义四个变量,然后使用typeid()函数找出每个变量的数据类型。让我们来看一下:
/ auto.cpp /

#include <iostream>
#include <typeinfo>
int main()
  std::cout << "[auto.cpp]" << std::endl;
  // Creating several auto-type variables
  auto a = 1;
  auto b = 1.0;
  auto c = a + b;
  auto d = {b, c};
  // Displaying the preceding variables' type
  std::cout << "type of a: " << typeid(a).name() << std::endl;
  std::cout << "type of b: " << typeid(b).name() << std::endl;
  std::cout << "type of c: " << typeid(c).name() << std::endl;
  std::cout << "type of d: " << typeid(d).name() << std::endl;
  return 0;

正如我们在前面的代码中看到的,我们有一个将存储整数值的变量a,并且有一个将存储双精度值的变量b。我们计算a和b的加法,并将结果存储在变量c中。在这里,我们期望c将存储双精度对象,因为我们添加了整数和双精度对象。最后是将存储initializer_list数据类型的变量d。当我们运行前面的代码时,将在控制台上看到以下输出:
如前面的快照所示,我们只给出了数据类型的第一个字符,比如i代表整数,d代表双精度,St16initializer_listIdE代表initializer_list,最后一个小写的d字符代表双精度。
我们可能需要在编译器选项中启用运行时类型信息(RTTI)功能来检索数据类型对象。然而,GCC 已经默认启用了这个功能。此外,typeid()函数的使用输出取决于编译器。我们可能会得到原始类型名称,或者就像我们在前面的例子中所做的那样,只是一个符号。
此外,对于变量,正如我们之前讨论的那样,auto关键字也可以应用于函数,自动推断函数的返回类型。假设我们有以下名为add()的简单函数来计算两个参数的加法:
我们可以重构前面的方法来使用auto关键字,如下所示的代码行:
auto add(int i, int j)
与自动类型变量类似,编译器可以根据函数的返回值决定正确的返回类型。正如前面的代码所示,该函数确实返回整数值,因为我们只是添加了两个整数值。
现代 C++中使用auto关键字的另一个特性是尾返回类型语法。通过使用这个特性,我们可以指定返回类型,函数原型的其余部分,或函数签名。从前面的代码中,我们可以重构它以使用以下特性:
auto add(int i, int j) -> int
你可能会问我为什么我们在箭头符号(->)之后再次指定数据类型,即使我们已经使用了auto关键字。当我们在下一节讨论decltype关键字时,我们将找到答案。此外,通过使用这个特性,我们现在可以通过修改main()方法的语法来稍微重构前面的auto.cpp代码,而不是main()函数签名的以下语法:
// The body of the function
我们可以将签名语法改为以下代码行:
auto main -> int
现在,我们将看到本书中的所有代码都使用这个尾返回类型特性来应用现代 C++语法。
使用 decltype 关键字查询表达式的类型
我们在前面的部分讨论了auto关键字可以根据其存储的值的类型自动推断变量的类型。该关键字还可以根据其返回值的类型推断函数的返回类型。现在,让我们结合auto关键字和decltype关键字,获得现代 C++的功能。
在我们结合这两个关键字之前,我们将找出decltype关键字的用途--它用于询问对象或表达式的类型。让我们看一下以下几行简单的变量声明:
const int func1();
const int& func2();
int i;
struct X { double d; };
const X* x = new X();
现在,基于前面的代码,我们可以使用decltype关键字声明其他变量,如下所示:
// Declaring const int variable
// using func1() type
decltype(func1()) f1;
// Declaring const int& variable
// using func2() type
decltype(func2()) f2;
// Declaring int variable
// using i type
decltype(i) i1;
// Declaring double variable
// using struct X type
decltype(x->d) d1; // type is double
decltype((x->d)) d2; // type is const double&//代码效果参考:http://www.zidongmutanji.com/zsjx/286175.html

正如我们在前面的代码中所看到的,我们可以根据另一个对象的类型指定对象的类型。现在,假设我们需要重构前面的add()方法成为一个模板。没有auto和decltype关键字,我们将有以下模板实现:
template
K add(I i, J j)
幸运的是,由于auto关键字可以指定函数的返回类型,即尾返回类型,而decltype关键字可以根据表达式推断类型,我们可以将前面的模板重构如下:
template
auto add(I i, J j) -> decltype(i + j)
为了证明,让我们编译和运行以下的decltype.cpp代码。我们将使用以下模板来计算两种不同值类型--整数和双精度的加法:
/ decltype.cpp /
// Creating template
auto main() -> int
std::cout << "[decltype.cpp]" << std::endl;
// Consuming the template
auto d = add(2, 2.5);
std::cout << "result of 2 + 2.5: " << d << std::endl;
编译过程应该可以顺利进行,没有错误。如果我们运行前面的代码,我们将在屏幕上看到以下输出:
正如我们所看到的,我们成功地结合了auto和decltype关键字,创建了一个比现代 C++宣布之前通常更简单的模板。
指向空指针
现代 C++中的另一个新功能是一个名为nullptr的关键字,它取代了NULL宏来表示空指针。现在,在使用NULL宏表示零数字或空指针时不再存在歧义。假设我们在声明中有以下两个方法的签名:
void funct(const char );
void funct(int)
前一个函数将传递一个指针作为参数,后一个将传递整数作为参数。然后,我们调用funct()方法并将NULL宏作为参数传递,如下所示:
funct(NULL);
我们打算调用前一个函数。然而,由于我们传递了NULL参数,它基本上被定义为0,后一个函数将被调用。在现代 C++中,我们可以使用nullptr关键字来确保我们将传递一个空指针给参数。调用funct()方法应该如下:
funct(nullptr);
现在编译器将调用前一个函数,因为它将一个空指针传递给参数,这是我们期望的。不再存在歧义,将避免不必要的未来问题。
使用非成员 begin()和 end()函数返回迭代器
在现代 C++之前,要迭代一个序列,我们需要调用每个容器的begin()和end()成员方法。对于数组,我们可以通过迭代索引来迭代它的元素。自 C++11 以来,语言有一个非成员函数--begin()和end()--来检索序列的迭代器。假设我们有以下元素的数组:
int arr[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
当语言没有begin()和end()函数时,我们需要使用索引来迭代数组的元素,可以在下面的代码行中看到:
for (unsigned int i = 0; i < sizeof(arr)/sizeof(arr[0]); ++i)
// Do something to the array
幸运的是,使用begin()和end()函数,我们可以重构前面的for循环如下:
for (auto i = std::begin(arr); i != std::end(arr); ++i)
正如我们所看到的,使用begin()和end()函数创建了一个紧凑的代码,因为我们不需要担心数组的长度,因为begin()和end()的迭代器指针会为我们做这件事。为了比较,让我们看一下以下的begin_end.cpp代码:
/
begin_end.cpp /
std::cout << "[begin_end.cpp]" << std::endl;
// Declaring an array
int arr[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
// Displaying the array elements
// using conventional for-loop
std::cout << "Displaying array element using conventional for-
loop";
std::cout << std::endl;
for (unsigned int i = 0; i < sizeof(arr)/sizeof(arr[0]); ++i)
std::cout << arr[i] << " ";
// using non-member begin() and end()
std::cout << "Displaying array element using non-member begin()
and end()";
for (auto i = std::begin(arr); i != std::end(arr); ++i)
std::cout <<
i << " ";
为了证明前面的代码,我们可以编译代码,当我们运行它时,应该在控制台屏幕上显示以下输出:
正如我们在屏幕截图中看到的,当我们使用传统的for-loop或begin()和end()函数时,我们得到了完全相同的输出。
使用基于范围的 for 循环迭代集合
在现代 C++中,有一个新功能被增强,支持for-each技术来迭代集合。如果你想对集合或数组的元素做一些操作而不关心元素的数量或索引,这个功能就很有用。这个功能的语法也很简单。假设我们有一个名为arr的数组,我们想要使用range-based for loop技术迭代每个元素,我们可以使用以下语法:
for (auto a : arr)
// Do something with a
因此,我们可以重构我们之前的begin_end.cpp代码,使用range-based for loop,如下所示:
/ range_based_for_loop.cpp /
std::cout << "[range_based_for_loop.cpp]" << std::endl;
int arr[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
std::cout << "Displaying array element using range-based for
loop";
for (auto a : arr) std::cout << a << " ";
我们在前面的代码中看到的语法现在更简单了。如果我们编译前面的代码,应该不会出现错误,如果我们运行代码,应该在控制台屏幕上看到以下输出:
现在我们有了一种新的技术来迭代集合,而不必关心集合的索引。我们将在本书中继续使用它。
利用 C++语言与 C++标准库
C++标准库是一个强大的类和函数集合,具有创建应用程序所需的许多功能。它们由 C++ ISO 标准委员会控制,并受到标准模板库(STL)的影响,在 C++11 引入之前是通用库。标准库中的所有功能都在std 命名空间中声明,不再以.h结尾的头文件(除了 18 个 ISO C90 C 标准库的头文件,它们被合并到了 C++标准库中)。
C++标准库中包含了几个头文件,其中包含了 C++标准库的声明。然而,在这些小章节中几乎不可能讨论所有的头文件。因此,我们将讨论一些我们在日常编码活动中最常使用的功能。
将任何对象放入容器中
容器是用来存储其他对象并管理它所包含的对象的内存的对象。数组是 C++11 中添加的一个新特性,用于存储特定数据类型的集合。它是一个序列容器,因为它存储相同数据类型的对象并将它们线性排列。让我们看一下以下代码片段:
/ array.cpp /

#include <array>
  std::cout << "[array.cpp]" << std::endl;
  // Initializing an array containing five integer elements
  std::array<int, 10> arr = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
  // Displaying the original elements of the array
  std::cout << "Original Data : ";
  for(auto a : arr) std::cout << a << " ";
  // Modifying the content of
  // the 1st and 3rd element of the array
  arr[1] = 9;
  arr[3] = 7;
  // Displaying the altered array elements
  std::cout << "Manipulated Data: ";
 }//代码效果参考:http://www.zidongmutanji.com/zsjx/532307.html

正如我们在前面的代码中所看到的,我们实例化了一个名为arr的新数组,将其长度设置为10,并且只批准int元素。我们可以猜到,代码的输出是一行数字0到9,显示了原始数据,另一行将显示更改后的数据,如我们在以下截图中所看到的:
如果我们使用std::array声明一个数组,就不会有性能问题;我们在array.cpp代码中使用它,并将其与在begin_end.cpp代码中使用的普通数组进行比较。然而,在现代 C++中,我们有一个新的数组声明,它具有友好的值语义,因此可以按值传递给函数或从函数中返回。此外,这个新数组声明的接口使得更方便地找到大小,并与标准模板库(STL)风格的基于迭代器的算法一起使用。
使用数组作为容器是很好的,因为我们可以存储数据并对其进行操作。我们还可以对其进行排序,并查找特定元素。然而,由于数组是一个在编译时不可调整大小的对象,我们必须在最开始决定要使用的数组的大小,因为我们不能后来改变大小。换句话说,我们不能在现有数组中插入或删除元素。作为解决这个问题的方法,以及为了最佳实践使用容器,我们现在可以使用vector来存储我们的集合。让我们看一下以下代码:
/ vector.cpp /

#include <vector>
  std::cout << "[vector.cpp]" << std::endl;
  // Initializing a vector containing three integer elements
  std::vector<int> vect = { 0, 1, 2 };
  // Displaying the original elements of the vector
  for (auto v : vect) std::cout << v << " ";
  // Adding two new data
  vect.push_back(3);
  vect.push_back(4);
  // Displaying the elements of the new vector
  // and reverse the order
  std::cout << "New Data Added : ";
  // the 2nd and 4th element of the vector
  vect.at(2) = 5;
  vect.at(4) = 6;
  std::cout << "Manipulate Data: ";

现在,在我们之前的代码中有一个vector实例,而不是一个array实例。正如我们所看到的,我们使用push_back()方法为vector实例添加了一个额外的值。我们可以随时添加值。每个元素的操作也更容易,因为vector有一个at()方法,它返回特定索引的元素的引用。运行代码时,我们将看到以下截图作为输出:
当我们想要通过索引访问vector实例中的特定元素时,最好始终使用at()方法而不是[]运算符。这是因为,当我们意外地访问超出范围的位置时,at()方法将抛出一个out_of_range异常。否则,[]运算符将产生未定义的行为。
使用算法
我们可以对array或vector中的集合元素进行排序,以及查找特定内容的元素。为了实现这些目的,我们必须使用 C++标准库提供的算法功能。让我们看一下以下代码,以演示算法功能中排序元素的能力:
/ sort.cpp /

#include <algorithm>
bool comparer(int a, int b)
  return (a > b);
  std::cout << "[sort.cpp]" << std::endl;
  // Initializing a vector containing several integer elements
  std::vector<int> vect = { 20, 43, 11, 78, 5, 96 };
  for (auto v : vect)
  std::cout << v << " ";
  // Sorting the vector element ascending
  std::sort(std::begin(vect), std::end(vect));
  // Displaying the ascending sorted elements
  // of the vector
  std::cout << "Ascending Sorted : ";
  // Sorting the vector element descending
  // using comparer
  std::sort(std::begin(vect), std::end(vect), comparer);
  // Displaying the descending sorted elements
  std::cout << "Descending Sorted: ";

}
正如我们在前面的代码中看到的,我们两次调用了sort()方法。首先,我们只提供了我们想要排序的元素的范围。然后,我们添加了比较函数comparer(),以便将其提供给sort()方法,以获得更多灵活性。从前面的代码中,我们将在控制台上看到的输出如下:
从前面的截图中,我们可以看到一开始vector中有六个元素。然后,我们使用简单的sort()方法对向量的元素进行排序。然后,我们再次调用sort()方法,但现在不是简单的sort()方法,而是将comparer()提供给sort()方法。结果,向量元素将按降序排序,因为comparer()函数从两个输入中寻找更大的值。
现在,让我们转向算法特性具有的另一个功能,即查找特定元素。假设我们在代码中有Vehicle类。它有两个名为m_vehicleType和m_totalOfWheel的私有字段,我们可以从 getter 方法GetType()和GetNumOfWheel()中检索值。它还有两个构造函数,分别是默认构造函数和用户定义的构造函数。类的声明应该如下所示:
/ vehicle.h /

#ifndef __VEHICLE_H__
#define __VEHICLE_H__
#include <string>
class Vehicle
  private:
    std::string vehicleType;
    int totalOfWheel;
  public:
    Vehicle(
      const std::string &type,
      int _wheel);
    Vehicle();
    ~Vehicle();
    std::string GetType() const {return vehicleType;}
    int GetNumOfWheel() const {return totalOfWheel;}
};
#endif // End of __VEHICLE_H__

Vehicle类的实现如下:
/ vehicle.cpp /

#include "vehicle.h"
using namespace std;
// Constructor with default value for
// m_vehicleType and m_totalOfWheel
Vehicle::Vehicle() : m_totalOfWheel(0)
// Constructor with user-defined value for
Vehicle::Vehicle( const string &type, int wheel) :
 m_vehicleType(type),
 m_totalOfWheel(wheel)
// Destructor
Vehicle::~Vehicle()

我们将在vector容器中存储一组Vehicle,然后根据其属性搜索一些元素。代码如下:
/ find.cpp /

#include "../vehicle/vehicle.h"
bool TwoWheeled(const Vehicle &vehicle)
  return _vehicle.GetNumOfWheel() == 2 ? 
    true : false;
  cout << "[find.cpp]" << endl;
  // Initializing several Vehicle instances
  Vehicle car("car", 4);
  Vehicle motorcycle("motorcycle", 2);
  Vehicle bicycle("bicycle", 2);
  Vehicle bus("bus", 6);
  // Assigning the preceding Vehicle instances to a vector
  vector<Vehicle> vehicles = { car, motorcycle, bicycle, bus };
  // Displaying the elements of the vector
  cout << "All vehicles:" << endl;;
  for (auto v : vehicles)
    std::cout << v.GetType() << endl;
  cout << endl;
  // which are the two-wheeled vehicles
  cout << "Two-wheeled vehicle(s):" << endl;;
  auto tw = find_if(
                  begin(vehicles),
                  end(vehicles),
                  TwoWheeled);
  while (tw != end(vehicles))
  {
    cout << tw->GetType() << endl ;
    tw = find_if(++tw, end(vehicles), TwoWheeled);
  }
  // which are not the two-wheeled vehicles
  cout << "Not the two-wheeled vehicle(s):" << endl;;
  auto ntw = find_if_not(begin(vehicles),
                       end(vehicles),
                       TwoWheeled);
  while (ntw != end(vehicles))
    cout << ntw->GetType() << endl ;
    ntw = find_if_not(++ntw, end(vehicles), TwoWheeled);

正如我们所看到的,我们实例化了四个Vehicle对象,然后将它们存储在vector中。在那里,我们试图找到有两个轮子的车辆。find_if()函数用于此目的。我们还有TwoWheeled()方法来提供比较值。由于我们正在寻找两轮车辆,我们将通过调用GetNumOfWheel()方法来检查Vehicle类中的totalOfWheel变量。

相关文章
|
16天前
|
算法 安全 编译器
【C++进阶】模板进阶与仿函数:C++编程中的泛型与函数式编程思想
【C++进阶】模板进阶与仿函数:C++编程中的泛型与函数式编程思想
25 1
|
6天前
|
存储 编译器 开发工具
C++语言教程分享
C++语言教程分享
|
6天前
|
存储 编译器 C++
|
27天前
|
C++ 存储 索引
面向 C++ 的现代 CMake 教程(一)(5)
面向 C++ 的现代 CMake 教程(一)
45 0
|
27天前
|
缓存 存储 C++
面向 C++ 的现代 CMake 教程(一)(4)
面向 C++ 的现代 CMake 教程(一)
45 0
|
27天前
|
C++ 缓存 存储
面向 C++ 的现代 CMake 教程(一)(3)
面向 C++ 的现代 CMake 教程(一)
43 0
|
27天前
|
缓存 C++ Windows
面向 C++ 的现代 CMake 教程(一)(2)
面向 C++ 的现代 CMake 教程(一)
57 0
|
27天前
|
C++ 容器 Docker
面向 C++ 的现代 CMake 教程(一)(1)
面向 C++ 的现代 CMake 教程(一)
67 0
|
27天前
|
存储 算法 C++
面向 C++ 的现代 CMake 教程(五)(5)
面向 C++ 的现代 CMake 教程(五)
27 0
|
27天前
|
C++ 存储 JSON
面向 C++ 的现代 CMake 教程(五)(4)
面向 C++ 的现代 CMake 教程(五)
39 0