[C++11] contenedor de expresiones lambda


1 expresión lambda

1.1 Referencias

En C++98, si desea ordenar los elementos de una colección de datos, puede utilizar el método std::sort:

#include <algorithm>
#include <functional>
int main()
{
    
    
	int array[] = {
    
     4,1,8,5,3,7,0,9,2,6 };
	// 默认按照小于比较,排出来结果是升序
	std::sort(array, array + sizeof(array) / sizeof(array[0]));
	// 如果需要降序,需要改变元素的比较规则
	std::sort(array, array + sizeof(array) / sizeof(array[0]), greater<int>());
	return 0;
}

Si los elementos a ordenar son de un tipo personalizado, el usuario debe definir las reglas de comparación para la clasificación:

struct Goods
{
    
    
	string _name;  // 名字
	double _price; // 价格
	int _evaluate; // 评价
	Goods(const char* str, double price, int evaluate)
		:_name(str)
		, _price(price)
		, _evaluate(evaluate)
	{
    
    }
};
struct ComparePriceLess
{
    
    
	bool operator()(const Goods& gl, const Goods& gr)
	{
    
    
		return gl._price < gr._price;
	}
};
struct ComparePriceGreater
{
    
    
	bool operator()(const Goods& gl, const Goods& gr)
	{
    
    
		return gl._price > gr._price;
	}
};
int main()
{
    
    
	vector<Goods> v = {
    
     {
    
     "苹果", 2.1, 5 }, {
    
     "香蕉", 3, 4 }, {
    
     "橙子", 2.2,
   3 }, {
    
     "菠萝", 1.5, 4 } };
	sort(v.begin(), v.end(), ComparePriceLess());
	sort(v.begin(), v.end(), ComparePriceGreater());
	return 0;
}

Si el nombre del funtor está más estandarizado, como el método de nombre anterior, estará bien. Si encuentra un método de nombre como cmp1 cmp2 cmp3... y no hay comentarios, puede ser molesto y debe encontrar el correspondiente. Código fuente para implementarlo. Y si hay muchos códigos en un proyecto, el costo de búsqueda será relativamente alto, por lo que C ++ 11 ha introducido una nueva sintaxis llamada expresión lambda .

1.2 Sintaxis básica de expresiones lambda

Formato de escritura de expresiones lambda:[capture-list] (parameters) mutable -> return-type { statement }

Descripción de cada parte de la expresión lambda:

  • [lista de captura]: Lista de captura. Esta lista siempre aparece al principio de la función lambda. El compilador usa [] para determinar si el siguiente código es una función lambda. La lista de captura puede capturar variables en el contexto para su uso por la función lambda.
  • (parámetros): lista de parámetros. De acuerdo con la lista de parámetros de una función ordinaria, si no es necesario pasar parámetros, se puede omitir junto con ().
  • mudable: De forma predeterminada, una función lambda es siempre una función constante y mutable puede cancelar su constancia. Cuando se utiliza este modificador, la lista de parámetros no se puede omitir (incluso si el parámetro está vacío).
  • ->tipo de retorno:Tipo de valor de retorno. Utilice el formulario de tipo de retorno de seguimiento para declarar el tipo de valor de retorno de la función. Esta parte se puede omitir si no hay un valor de retorno. Si el tipo de valor de retorno es claro, también se puede omitir y el compilador deducirá el tipo de retorno.
  • {declaración}: Cuerpo funcional. Dentro del cuerpo de la función, además de sus parámetros, están disponibles todas las variables capturadas.

Aviso:

En la definición de la función lambda, la lista de parámetros y el tipo de valor de retorno son partes opcionales , mientras que la lista de captura y el cuerpo de la función no se pueden omitir y pueden estar vacíos . Entonces, la función lambda más simple en C++ 11 es:[]{}; Esta función lambda no puede hacer nada.

La lista de captura describe qué datos del contexto puede utilizar la lambda y si se pasan por valor o por referencia.

  • [ var ]: Indica que el método de transferencia de valor captura la variable var
  • [ = ]: Indica que el método de paso de valor captura todas las variables en el ámbito principal (incluida esta)
  • [ &var ]: Indica que la variable de captura var se pasa por referencia
  • [ & ]: Indica que la transferencia de referencia captura todas las variables en el ámbito principal (incluida esta)
  • [ this ]: indica que el método de transferencia de valor captura el puntero this actual

Podemos implementar un complemento simple para verificar:

int main()
{
    
    
	int x, y;
	cin >> x >> y;
	auto add = [=]()
	{
    
    
		return x + y;
	};
	cout << add() << endl;
	return 0;
}

Una expresión lambda en realidad puede entenderse como una función sin nombre, que no se puede llamar directamente. Si desea llamarla directamente, puede asignarla a una variable con la ayuda de auto. Al igual que el complemento anterior, incluso puedes escribir:cout<< [=](){return x + y;}()<< endl;

Podemos echar un vistazo a los escenarios de aplicación de mutable, como el siguiente código:

int main()
{
    
    
	int x = 10,y = 20;
	auto swapInt = [=] {
    
    int tmp = x; x = y; y = tmp; };
	swapInt();
	return 0;
}

Cuando compilamos, se informará directamente un error: ¿
Insertar descripción de la imagen aquí
por qué? Debido a que capturamos la variable usando la captura de valor, y la variable capturada es una copia, y usted no puede modificarla de forma predeterminada (puede entenderse como agregar un atributo constante), cuando modifique la variable, ser directamente Se informa un error, ¿y si queremos modificarlo? nosotros podemos usarmudable(que significa mutable):
Insertar descripción de la imagen aquí

Aviso:

  1. El alcance principal se refiere al bloque de instrucciones que contiene la función lambda.
  2. Sintácticamente, una lista de captura puede constar de varios elementos de captura, separados por comas. Por ejemplo: [=, &a, &b]: captura las variables a y b por referencia, y captura todas las demás variables por valor. [&, a, this]: captura las variables a y this por valor, y captura otras variables por referencia. .
  3. Las listas de captura no permiten que las variables se pasen repetidamente; de ​​lo contrario, se producirán errores de compilación. Por ejemplo: [=, a]: = ha capturado todas las variables mediante transferencia de valor y captura la repetición de a.
  4. Las listas de captura de funciones Lambda fuera del alcance del bloque deben estar vacías.
  5. Una función lambda en un alcance de bloque solo puede capturar variables locales en el alcance principal. La captura de cualquier variable que no sea de alcance o no local resultará en un error de compilación.
  6. Las expresiones Lambda no se pueden asignar entre sí, incluso si parecen ser del mismo tipo.

Las notas anteriores son fáciles de entender, podemos comprobar la última nota:

void (*PF)();
int main()
{
    
    
 auto f1 = []{
    
    cout << "hello world" << endl; };
 auto f2 = []{
    
    cout << "hello world" << endl; };

 //f1 = f2;   // 编译失败--->提示找不到operator=()
 // 允许使用一个lambda表达式拷贝构造一个新的副本
 auto f3(f2);
 f3();
 // 可以将lambda表达式赋值给相同类型的函数指针
 PF = f2;
 PF();
 return 0;
}

Nota: Hay comentarios en el código.
En cuanto a por qué no se permite la asignación, lo explicaremos más adelante cuando expliquemos los principios de las expresiones lambda.

1.3 Los principios subyacentes de las expresiones lambda.

El objeto función, también conocido como funtor, es un objeto que se puede utilizar como una función, es un objeto de clase que sobrecarga el operador operador () en la clase.

Escribamos un fragmento de código para verificarlo:

class Rate
{
    
    
public:

	Rate(double rate) : _rate(rate)
	{
    
    }
	double operator()(double money, int year)
	{
    
    
		return money * _rate * year;
	}

private:
	double _rate;
};

int main()
{
    
    
	//  函数对象
	double rate = 0.49;
	Rate r1(rate);
	r1(10000, 2);
	//  lamber
	auto r2 = [=](double monty, int year)->double {
    
     return monty * rate * year;};
	r2(10000, 2);
	return 0;
}

Insertar descripción de la imagen aquíDesde la perspectiva del ensamblaje, no es difícil encontrar que las expresiones lambda también se llaman operatorpara implementarse en el nivel inferior, entonces, ¿por qué las expresiones lambda no pueden asignarse valores entre sí? La esencia es que la denominación subyacente de las expresiones lambda utiliza uuidun método para generar nombres de clase únicos, por lo que a objetos de diferentes tipos no se les pueden asignar valores de forma natural.

De hecho, el compilador subyacente maneja expresiones lambda exactamente como objetos de función, es decir,
si se define una expresión lambda, el compilador generará automáticamente una clase en la que el operador está sobrecargado ().

Luego pruebe a todos: ¿cuántos bytes tiene el tamaño de un objeto lambda?
La respuesta es realmente obvia. Dado que la capa inferior de la expresión lambda se implementa mediante un funtor, y el functor es una clase (clase vacía) sin miembro incorporado variables, el tamaño es 1 byte. ¿Estás en lo cierto?


2 envoltorios

Los contenedores de funciones también se denominan adaptadores. La función en C++ es esencialmente una plantilla de clase y un contenedor.

Antes de usar el contenedor, debemos introducir el archivo de encabezado. #include <functional>
El prototipo de la plantilla de clase es el siguiente:

// 类模板原型如下
template <class T> function;     // undefined
template <class Ret, class... Args>
class function<Ret(Args...)>;

Entonces, ¿cómo utilizamos los envoltorios todos los días?

// 使用方法如下:
#include <functional>
int f(int a, int b)
{
    
    
	return a + b;
}
struct Functor
{
    
    
public:
	int operator() (int a, int b)
	{
    
    
		return a + b;
	}
};

int main()
{
    
    
	// 函数名(函数指针)
	std::function<int(int, int)> func1 = f;
	cout << func1(1, 2) << endl;
	// 函数对象
	std::function<int(int, int)> func2 = Functor();
	cout << func2(1, 2) << endl;
	// lamber表达式
	std::function<int(int, int)> func3 = [](const int a, const int b)
	{
    
    return a + b; };
	cout << func3(1, 2) << endl;
	return 0;
}

Podemos usar un contenedor para aceptarlo 函数指针 仿函数 lambda , de modo que podamos usar un tipo unificado para aceptar diferentes parámetros para lograr el propósito de crear una instancia de solo una copia.

Sin embargo, al llamar a funciones miembro no estáticas (excluyendo functores) en una clase, se debe prestar especial atención al formato de sintaxis de la función:
por ejemplo, de la siguiente manera:

class Plus
{
    
    
public:
	static int plusi(int a, int b)
	{
    
    
		return a + b;
	}
	double plusd(double a, double b)
	{
    
    
		return a + b;
	}
};
int main()
{
    
    
	// 类的成员函数
	std::function<int(int, int)> func4 = &Plus::plusi;
	cout << func4(1, 2) << endl;
	std::function<double(Plus, double, double)> func5 = &Plus::plusd;
	cout << func5(Plus(), 1.1, 2.2) << endl;
	return 0;
}

Sabemos que las funciones miembro estáticas no incluyen thispunteros, por lo que no hay problema al usar la sintaxis anterior, pero como las funciones miembro tienen estos punteros, tenemos queDar un objeto de parámetro adicional(Generalmente nos gusta llamar a objetos anónimos) y llamar a las funciones miembro internas a través del objeto de parámetro. Y al especificar el dominio de clase, se debe agregar &, que es un requisito rígido de la gramática.
Insertar descripción de la imagen aquíPero preste atención al siguiente método de llamada:
Insertar descripción de la imagen aquítambién podemos usar punteros de objetos para llamar, pero en este momento no podemos usar objetos anónimos, porque los objetos anónimos son valores, lo cual no es posible &, pero en circunstancias normales no elegiremos este método. .


3 enlazar

La función std::bind se define en el archivo de encabezado y es una plantilla de función. Es como un contenedor de funciones (adaptador), que acepta un objeto invocable y genera un nuevo objeto invocable para "adaptar" el objeto original.

En términos generales, utilizamos bind en las dos situaciones siguientes:

  • 1️⃣Cambiar el orden de los parámetros
  • 2️⃣Cambiar la cantidad de parámetros

Rara vez se usa cambiar el orden de los parámetros, pero cambiar el número de parámetros es muy interesante, veámoslos uno por uno:
por ejemplo, el siguiente programa:

void Print(int x, int y)
{
    
    
	cout << x << ":" << y << endl;
}

Suponiendo que no cambiamos la implementación de la función Imprimir, sino que intercambiamos el orden de los parámetros al imprimir los resultados, ¿qué podemos hacer?
Podemos bindusarlo para manejar:

int main()
{
    
    
	int x = 10, y = 20;
	Print(x, y);
	auto RPrint = bind(Print, placeholders::_2, placeholders::_1);
	RPrint(x, y);
	return 0;
}

_1 _2 ¿Qué diablos hay aquí ? Esto es en realidad placeholdersun marcador de posición encapsulado en el espacio de nombres. Como entendemos directamente, _1 _2... representa el primer parámetro y el segundo parámetro... respectivamente, creemos que las posiciones de los parámetros a intercambiar pueden intercambiarse directamente intercambiando el orden de los marcadores de posición.

El uso de intercambiar el orden de los parámetros es en realidad bastante inútil. Generalmente no lo usamos con mucha frecuencia, pero creo que el escenario de cambiar el número de parámetros es bastante interesante. Echemos un vistazo a esta situación:

void mul(double x, double y)
{
    
    
	cout<< x * y<<endl;
}

struct fun
{
    
    

	fun(double rate)
		:_rate(rate)
	{
    
    }

	void mulR(double x, double y)
	{
    
    
		cout << x * y * _rate << endl;
	}

	double _rate;
};


int main()
{
    
    
	int x = 10, y = 20;
	function<void(double, double)> f1 = mul;
	function<void(double, double)> f2 = [=](double x,double y) {
    
    cout<< x * y<<endl; };
	return 0;
}

Cuando requerimos el mismo formato que los parámetros anteriores para aceptar mulR por diversión, si lo escribimos directamente, se informará un error directamente. Hemos explicado el principio en detalle cuando explicamos la función anterior, por lo que no entraremos en detalles. aquí. Entonces podemos manejarlo a través de bind:

function<void(double, double)> f3 = bind(&fun::mulR,f, placeholders::_1, placeholders::_2);

Podemos hacer el procesamiento de enlace de la manera anterior, codificar el enlace del primer parámetro y luego solo podemos usar un contenedor de dos parámetros para aceptarlo. ¿No es maravilloso? Por supuesto, no solo podemos vincular el primer parámetro, sino que también podemos vincular los segundos tres n parámetros mediante vinculación. El pequeño detalle que vale la pena señalar es que no importa qué parámetro vinculemos, nuestros otrosLos parámetros independientes solo pueden seguir aumentando desde _1


Supongo que te gusta

Origin blog.csdn.net/m0_68872612/article/details/131031897
Recomendado
Clasificación