C++新特性~摘录lambda

Lambda函数与表示式[编辑]

C++11标准提供了匿名函数的支持,在《ISO/IEC 14882:2011》(C++11标准文档)中叫做lambda表达式[10]。一个lambda表达式有如下的形式:

[capture] (parameters) mutable exception attribute -> return_type { body }

必须用方括号括起来的capture列表来开始一个lambda表达式的定义。

lambda函数的形参表比普通函数的形参表多了3条限制:

  1. 参数不能有缺省值
  2. 不能有可变长参数列表
  3. 不能有无名参数

如果lambda函数没有形参且没有mutable、exception或attribute声明,那么参数的空圆括号可以省略。但如果需要给出mutable、exception或attribute声明,那么参数即使为空,圆括号也不能省略。

如果函数体只有一个return语句,或者返回值类型为void,那么返回值类型声明可以被省略:

[capture](parameters){body}

一个lambda函数的例子如下:

[](int x, int y) { return x + y; } // 從return語句中隱式獲得的返回值類型
[](int& x) { ++x; }   // 沒有return語句 -> lambda函數的返回值為void
[]() { ++global_x; }  // 沒有參數,僅僅是訪問一個全局變量
[]{ ++global_x; }     // 與前者相同,()可以被省略

这个无名函数的返回值是decltype(x+y) (在上面的第一个例子中)。如果lambda函数体的形式是return expression,或者什么也每返回,或者所有返回语句用decltype都能检测到同一类型,那么返回值类型可以被省略。

在标准 C++,特别是当使用 C++ 标准程序库算法函数诸如 sort 和 find,使用者经常希望能够在算法函数调用的附近定义一个临时的述部函数(又称谓词函数,predicate function)。由于语言本身允许在函数内部定义类,可以考虑使用函数对象,然而这通常既麻烦又冗赘,也阻碍了代码的流程。此外,标准 C++ 不允许定义于函数内部的类被用于模板,所以前述的作法是不可行的。

C++11 对 lambda 的支持可以解决上述问题。

一个 lambda 函数可以用如下的方式定义:

[](int x, int y) { return x + y; }

这个不具名函数的回返类型是 decltype(x+y)。只有在 lambda 函数符合"return expression"的形式下,它的回返类型才能被忽略。在前述的情况下,lambda 函数仅能为一个述句。

在一个更为复杂的例子中,回返类型可以被明确的指定如下(返回值类型可以显式指定):

[](int x, int y) -> int { int z = x + y; return z + x; }

本例中,一个临时的参数 z 被创建用来存储中间结果。如同一般的函数,z 的值不会保留到下一次该不具名函数再次被调用时(中间值在调用的前后并不存在)。

如果 lambda 函数没有传回值(例如 void ),其回返类型可被完全忽略(无需显式指定返回值,没有必要写-> void代码。)。

定义在与 lambda 函数相同作用域的参数引用也可以被使用。这种的参数集合一般被称作 closure (闭包)。

lambda函数可以捕获lambda函数外的具有automatic storage duration的变量。函数体与这些变量的集合合起来称做闭包。这些外部变量在声明lambda表达式时列在在方括号[]中。空的方括号表示没有外界变量被capture。这些变量被传值捕获或者引用捕获。对于传值捕获的变量,默认为只读。修改这些变量将导致编译报错。但在lambda表达式的参数表的圆括号后面使用mutable关键字,就允许lambda函数体内的语句修改传值引用的变量,这些修改与lambda表达式(实际上是用函数对象实现)有相同的生命期,但不影响被传值捕获的外部变量的值。lambda函数可以直接使用具有static存储期的变量。如果在lambda函数的捕获列表中给出了static存储期的变量,编译时会给出警告,仍然按照lambda函数直接使用这些外部变量来处理。因此具有static存储期的变量即使被声明为传值捕获,修改该变量实际上直接修改了这些外部变量。编译器生成lambda函数对应的函数对象时,不会用函数对象的数据成员来保持被“捕获”的static存储期的变量。示例:

[]  // 沒有定義任何變數。使用未定義變數會導致錯誤。
[x, &y] // x 以傳值方式傳入(預設),y 以傳參考方式傳入。
[&]   // 任何被使用到的外部變數皆隱式地以參考方式加以引用。
[=]   // 任何被使用到的外部變數皆隱式地以傳值方式加以引用。
[&, x]   // x 顯示地以傳值方式加以引用。其餘變數以參考方式加以引用。
[=, &z]   // z 顯示地以參考方式加以引用。其餘變數以傳值方式加以引用。

closure 被定义与使用如下:

std::vector<int> someList;
int total = 0;
std::for_each(someList.begin(), someList.end(), [&total](int x) {
  total += x;
});
std::cout << total;

上例可计算 someList 元素的总和并将其印出。 参数 total 是 lambda 函数 closure 的一部分,同时它以引用方式被传递入谓词函数, 因此它的值可被 lambda 函数改变。

若不使用引用的符号&,则代表参数以传值的方式传入 lambda 函数。 让使用者可以用这种表示法明确区分参数传递的方法:传值,或是传引用。 由于 lambda 函数可以不在被声明的地方就地使用(如置入 std::function 对象中); 这种情况下,若参数是以传引用的方式链接到 closure 中,是无意义甚至是危险的行为。

若 lambda 函数只在定义的作用域使用, 则可以用 [&] 声明 lambda 函数, 代表所有引用到 stack 中的参数,都是以引用的方式传入, 不必一一显式指明:

std::vector<int> someList;
int total = 0;
std::for_each(someList.begin(), someList.end(), [&](int x) {
  total += x;
});

参数传入 lambda 函数的方式可能随实现有所变化,一般期望的方法是 lambda 函数能保留其作用域函数的 stack 指针,借此访问区域参数。

若使用 [=] 而非 [&],则代表所有的引用的参数都是传值使用。

对于不同的参数,传值或传引用可以混和使用。 比方说,使用者可以让所有的参数都以传引用的方式使用,但带有一个传值使用的参数:

int total = 0;
int value = 5;
[&, value](int x) { total += (x * value); };

total 是传引用的方式传入 lambda 函数,而 value 则是传值。

在类的非静态成员函数中定义的lambda表达式可以显式或隐式捕捉this指针,从而可以引用所在类对象的数据成员与函数成员。lambda函数的函数体中,可以访问下述变量:

  • 函数参数
  • 局部声明的变量
  • 类数据成员(当函数声明在类中)
  • 具有静态存储期的变量(如全局变量)
  • 被捕获的外部变量
    • 显式捕获的变量
    • 隐式捕获的变量,使用默认捕获模式(传值或引用)来访问。

lambda函数的数据类型是函数对象,保存时必须用std::function模板类型或auto关键字,或用auto关键字。例如:

#include <functional>
#include <vector>
#include <iostream>
 
double eval(std::function <double(double)> f, double x = 2.0)
{
	return f(x);
}
 
int main()
{
	std::function<double(double)> f0    = [](double x){return 1;};
	auto                          f1    = [](double x){return x;};
	decltype(f0)                  fa[3] = {f0,f1,[](double x){return x*x;}};
	std::vector<decltype(f0)>     fv    = {f0,f1};
	fv.push_back                  ([](double x){return x*x;});
	for(int i=0;i<fv.size();i++)
		std::cout << fv[i](2.0) << std::endl;
	for(int i=0;i<3;i++)
		std::cout << fa[i](2.0) << std::endl;
	for(auto &f : fv)
		std::cout << f(2.0) << std::endl;
	for(auto &f : fa)
		std::cout << f(2.0) << std::endl;
	std::cout << eval(f0) << std::endl;
	std::cout << eval(f1) << std::endl;
	std::cout << eval([](double x){return x*x;}) << std::endl;
	return 0;
}

一个lambda函数的捕捉表达式为空,则可以用普通函数指针存储或调用。例如:

auto a_lambda_func = [](int x) { /*...*/ };
void (* func_ptr)(int) = a_lambda_func;
func_ptr(4); //calls the lambda.

若一个 lambda 函数被定义于某类的成员函数中,则可以使用该类对象的引用,并且能够访问其内部的成员。

[](SomeType *typePtr) { typePtr->SomePrivateMemberFunction(); };

这只有当该 lambda 函数创建的作用域是在 SomeType 的成员函数内部时才能运作。

在成员函数中指涉对象的 this 指针,必须要显式的传入 lambda 函数, 否则成员函数中的 lambda 函数无法使用任何该对象的参数或函数。

[this]() { this->SomePrivateMemberFunction(); };

若是 lambda 函数使用 [&] 或是 [=] 的形式,this在 lambda 函数即为可见。

lambda 函数是编译器从属类型的函数对象; 这种类型名称只有编译器自己能够使用。如果使用者希望将 lambda 函数作为参数传入,该类型必须是模版类型,或是必须创建一个std::function 去获取 lambda 的值。使用 auto 关键字让我们能够存储 lambda 函数:

auto myLambdaFunc = [this]() { this->SomePrivateMemberFunction(); };
auto myOnheapLambdaFunc = new auto([=] { /*...*/ });

在使用Python来安装geopandas包时,由于geopandas依赖于几个其他的Python库(如GDAL, Fiona, Pyproj, Shapely等),因此安装过程可能需要一些额外的步骤。以下是一个基本的安装指南,适用于大多数用户: 使用pip安装 确保Python和pip已安装: 首先,确保你的计算机上已安装了Python和pip。pip是Python的包管理工具,用于安装和管理Python包。 安装依赖库: 由于geopandas依赖于GDAL, Fiona, Pyproj, Shapely等库,你可能需要先安装这些库。通常,你可以通过pip直接安装这些库,但有时候可能需要从其他源下载预编译的二进制包(wheel文件),特别是GDAL和Fiona,因为它们可能包含一些系统级的依赖。 bash pip install GDAL Fiona Pyproj Shapely 注意:在某些系统上,直接使用pip安装GDAL和Fiona可能会遇到问题,因为它们需要编译一些C/C++代码。如果遇到问题,你可以考虑使用conda(一个Python包、依赖和环境管理器)来安装这些库,或者从Unofficial Windows Binaries for Python Extension Packages这样的网站下载预编译的wheel文件。 安装geopandas: 在安装了所有依赖库之后,你可以使用pip来安装geopandas。 bash pip install geopandas 使用conda安装 如果你正在使用conda作为你的Python包管理器,那么安装geopandas和它的依赖可能会更简单一些。 创建一个新的conda环境(可选,但推荐): bash conda create -n geoenv python=3.x anaconda conda activate geoenv 其中3.x是你希望使用的Python版本。 安装geopandas: 使用conda-forge频道来安装geopandas,因为它提供了许多地理空间相关的包。 bash conda install -c conda-forge geopandas 这条命令会自动安装geopandas及其所有依赖。 注意事项 如果你在安装过程中遇到任何问题,比如编译错误或依赖问题,请检查你的Python版本和pip/conda的版本是否是最新的,或者尝试在不同的环境中安装。 某些库(如GDAL)可能需要额外的系统级依赖,如地理空间库(如PROJ和GEOS)。这些依赖可能需要单独安装,具体取决于你的操作系统。 如果你在Windows上遇到问题,并且pip安装失败,尝试从Unofficial Windows Binaries for Python Extension Packages网站下载相应的wheel文件,并使用pip进行安装。 脚本示例 虽然你的问题主要是关于如何安装geopandas,但如果你想要一个Python脚本来重命名文件夹下的文件,在原始名字前面加上字符串"geopandas",以下是一个简单的示例: python import os # 指定文件夹路径 folder_path = 'path/to/your/folder' # 遍历文件夹中的文件 for filename in os.listdir(folder_path): # 构造原始文件路径 old_file_path = os.path.join(folder_path, filename) # 构造新文件名 new_filename = 'geopandas_' + filename # 构造新文件路径 new_file_path = os.path.join(folder_path, new_filename) # 重命名文件 os.rename(old_file_path, new_file_path) print(f'Renamed "{filename}" to "{new_filename}"') 请确保将'path/to/your/folder'替换为你想要重命名文件的实际文件夹路径。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值