Por que a expressão Lambda?
Considere a seguinte declaração:
intmyInt= 52;Aqui, myInt é um identificador, um lvalue. 52 é um literal, um prvalue. Hoje, é possível codificar uma função especialmente e colocá-la na posição de 52. Essa função é chamada de expressão lambda. Considere também o seguinte programa curto:
#incluir
usando namespacehoras;
intfn(intAtravés dos)
{
intresponder=Através dos+ 3;
Retornaresponder;
}
inta Principal()
{
fn(5);
Retorna 0;
}
Hoje é possível codificar uma função especialmente e colocá-la na posição do argumento de 5, da chamada de função, fn (5). Essa função é chamada de expressão lambda. A expressão lambda (função) nessa posição é um prvalue.
Qualquer literal, exceto o literal de string, é um prvalue. A expressão lambda é um design de função especial que caberia como um literal no código. É uma função anônima (sem nome). Este artigo explica a nova expressão primária C ++, chamada de expressão lambda. Conhecimento básico em C ++ é um requisito para entender este artigo.
Conteúdo do Artigo
- Ilustração da Expressão Lambda
- Partes da Expressão Lambda
- Capturas
- Esquema de função de retorno de chamada clássico com expressão lambda
- O tipo de retorno à direita
- Fecho
- Conclusão
Ilustração da Expressão Lambda
No programa a seguir, uma função, que é uma expressão lambda, é atribuída a uma variável:
#incluir
usando namespacehoras;
autofn= [](intparam)
{
intresponder=param+ 3;
Retornaresponder;
};
inta Principal()
{
autovariab=fn(2);
custo <<variab<< ' n';
Retorna 0;
}
O resultado é:
5Fora da função main (), existe a variável, fn. Seu tipo é automático. Nesta situação, Auto significa que o tipo real, como int ou float, é determinado pelo operando certo do operador de atribuição (=). À direita do operador de atribuição está uma expressão lambda. Uma expressão lambda é uma função sem o tipo de retorno anterior. Observe o uso e a posição dos colchetes, []. A função retorna 5, um int, que determinará o tipo de fn.
Na função main (), existe a declaração:
autovariab=fn(2);Isso significa que fn fora de main () acaba sendo o identificador de uma função. Seus parâmetros implícitos são aqueles da expressão lambda. O tipo de variab é automático.
Observe que a expressão lambda termina com ponto-e-vírgula, assim como a definição de classe ou estrutura, termina com ponto-e-vírgula.
No programa a seguir, uma função, que é uma expressão lambda que retorna o valor 5, é um argumento para outra função:
#incluirusando namespacehoras;
vaziootherfn(intno1,int (*ptr)(int))
{
intno2= (*ptr)(2);
custo <<no1<< '' <<no2<< ' n';
}
inta Principal()
{
otherfn(4,[](intparam)
{
intresponder=param+ 3;
Retornaresponder;
});
Retorna 0;
}
O resultado é:
Quatro cincoExistem duas funções aqui, a expressão lambda e a função otherfn (). A expressão lambda é o segundo argumento de otherfn (), chamado em main (). Observe que a função lambda (expressão) não termina com um ponto e vírgula nesta chamada porque, aqui, é um argumento (não uma função autônoma).
O parâmetro da função lambda na definição da função otherfn () é um ponteiro para uma função. O ponteiro tem o nome, ptr. O nome, ptr, é usado na definição otherfn () para chamar a função lambda.
A declaração,
intno2= (*ptr)(2);Na definição otherfn (), ele chama a função lambda com um argumento de 2. O valor de retorno da chamada, '(* ptr) (2)' da função lambda, é atribuído a no2.
O programa acima também mostra como a função lambda pode ser usada no esquema de função de retorno de chamada do C ++.
Partes da Expressão Lambda
As partes de uma função lambda típica são as seguintes:
[] () {}- [] é a cláusula de captura. Pode ter itens.
- () é para a lista de parâmetros.
- {} é para o corpo da função. Se a função estiver isolada, ela deve terminar com um ponto e vírgula.
Capturas
A definição da função lambda pode ser atribuída a uma variável ou usada como argumento para uma chamada de função diferente. A definição para tal chamada de função deve ter como parâmetro um ponteiro para uma função, correspondendo à definição da função lambda.
A definição da função lambda é diferente da definição da função normal. Ele pode ser atribuído a uma variável no escopo global; esta função atribuída a variável também pode ser codificada dentro de outra função. Quando atribuído a uma variável de escopo global, seu corpo pode ver outras variáveis no escopo global. Quando atribuído a uma variável dentro de uma definição de função normal, seu corpo pode ver outras variáveis no escopo da função apenas com a ajuda da cláusula de captura, [].
A cláusula de captura [], também conhecida como introdutor lambda, permite que as variáveis sejam enviadas do escopo circundante (função) para o corpo da função da expressão lambda. Diz-se que o corpo da função da expressão lambda captura a variável quando recebe o objeto. Sem a cláusula de captura [], uma variável não pode ser enviada do escopo circundante para o corpo da função da expressão lambda. O programa a seguir ilustra isso, com o escopo da função main (), como o escopo circundante:
#incluirusando namespacehoras;
inta Principal()
{
intEu iria= 5;
autofn= [Eu iria]()
{
custo <<Eu iria<< ' n';
};
fn();
Retorna 0;
}
A saída é 5 . Sem o nome, id, dentro de [], a expressão lambda não teria visto a variável id do escopo da função main ().
Capturando por Referência
O uso do exemplo acima da cláusula de captura é a captura por valor (veja os detalhes abaixo). Na captura por referência, a localização (armazenamento) da variável, por exemplo, id acima, do escopo circundante, é disponibilizada dentro do corpo da função lambda. Portanto, mudar o valor da variável dentro do corpo da função lambda mudará o valor dessa mesma variável no escopo circundante. Cada variável repetida na cláusula de captura é precedida pelo e comercial (&) para isso. O programa a seguir ilustra isso:
#incluirusando namespacehoras;
inta Principal()
{
intEu iria= 5; flutuadorft= 2,3; CaracteresCH= 'PARA';
autofn= [EEu iria,Eft,ECH]()
{
Eu iria= 6;ft= 3,4;CH= 'B';
};
fn();
custo <<Eu iria<< ',' <<ft<< ',' <<CH<< ' n';
Retorna 0;
}
O resultado é:
6, 3,4, BConfirmando que os nomes das variáveis dentro do corpo da função da expressão lambda são para as mesmas variáveis fora da expressão lambda.
Capturando por valor
Na captura por valor, uma cópia da localização da variável, do escopo circundante, é disponibilizada dentro do corpo da função lambda. Embora a variável dentro do corpo da função lambda seja uma cópia, seu valor não pode ser alterado dentro do corpo a partir de agora. Para conseguir a captura por valor, cada variável repetida na cláusula de captura não é precedida por nada. O programa a seguir ilustra isso:
#incluirusando namespacehoras;
inta Principal()
{
intEu iria= 5; flutuadorft= 2,3; CaracteresCH= 'PARA';
autofn= [id, ft, ch]()
{
// id = 6; ft = 3,4; ch = 'B';
custo <<Eu iria<< ',' <<ft<< ',' <<CH<< ' n';
};
fn();
Eu iria= 6;ft= 3,4;CH= 'B';
custo <<Eu iria<< ',' <<ft<< ',' <<CH<< ' n';
Retorna 0;
}
O resultado é:
5, 2,3, A6, 3,4, B
Se o indicador de comentário for removido, o programa não será compilado. O compilador emitirá uma mensagem de erro de que as variáveis dentro da definição do corpo da função da expressão lambda não podem ser alteradas. Embora as variáveis não possam ser alteradas dentro da função lambda, elas podem ser alteradas fora da função lambda, como mostra a saída do programa acima.
Mistura de capturas
A captura por referência e a captura por valor podem ser misturadas, como mostra o seguinte programa:
#incluirusando namespacehoras;
inta Principal()
{
intEu iria= 5; flutuadorft= 2,3; CaracteresCH= 'PARA'; boolbl= verdade;
autofn= [id, ft,ECH,Ebl]()
{
CH= 'B';bl= falso;
custo <<Eu iria<< ',' <<ft<< ',' <<CH<< ',' <<bl<< ' n';
};
fn();
Retorna 0;
}
O resultado é:
5, 2,3, B, 0Quando todos são capturados, são por referência:
Se todas as variáveis a serem capturadas são capturadas por referência, então apenas um & será suficiente na cláusula de captura. O programa a seguir ilustra isso:
#incluirusando namespacehoras;
inta Principal()
{
intEu iria= 5; flutuadorft= 2,3; CaracteresCH= 'PARA'; boolbl= verdade;
autofn= [E]()
{
Eu iria= 6;ft= 3,4;CH= 'B';bl= falso;
};
fn();
custo <<Eu iria<< ',' <<ft<< ',' <<CH<< ',' <<bl<< ' n';
Retorna 0;
}
O resultado é:
6, 3,4, B, 0Se algumas variáveis devem ser capturadas por referência e outras por valor, então um & representará todas as referências, e o resto não será precedido por nada, como mostra o seguinte programa:
usando namespacehoras;inta Principal()
{
intEu iria= 5; flutuadorft= 2,3; CaracteresCH= 'PARA'; boolbl= verdade;
autofn= [E, id, ft]()
{
CH= 'B';bl= falso;
custo <<Eu iria<< ',' <<ft<< ',' <<CH<< ',' <<bl<< ' n';
};
fn();
Retorna 0;
}
O resultado é:
5, 2,3, B, 0Observe que & sozinho (ou seja, & não seguido por um identificador) deve ser o primeiro caractere na cláusula de captura.
Quando todos capturados, são por valor:
Se todas as variáveis a serem capturadas devem ser capturadas por valor, então apenas um = será suficiente na cláusula de captura. O programa a seguir ilustra isso:
#incluirusando namespacehoras;
inta Principal()
{
intEu iria= 5; flutuadorft= 2,3; CaracteresCH= 'PARA'; boolbl= verdade;
autofn= [=]()
{
custo <<Eu iria<< ',' <<ft<< ',' <<CH<< ',' <<bl<< ' n';
};
fn();
Retorna 0;
}
O resultado é:
5, 2,3, A, 1Observação : = é somente leitura, a partir de agora.
Se algumas variáveis devem ser capturadas por valor e outras por referência, então um = representará todas as variáveis copiadas somente leitura, e o resto terá cada um &, como mostra o programa a seguir:
#incluirusando namespacehoras;
inta Principal()
{
intEu iria= 5; flutuadorft= 2,3; CaracteresCH= 'PARA'; boolbl= verdade;
autofn= [=,ECH,Ebl]()
{
CH= 'B';bl= falso;
custo <<Eu iria<< ',' <<ft<< ',' <<CH<< ',' <<bl<< ' n';
};
fn();
Retorna 0;
}
O resultado é:
5, 2,3, B, 0Observe que = sozinho deve ser o primeiro caractere na cláusula de captura.
Esquema de função de retorno de chamada clássico com expressão lambda
O programa a seguir mostra como um esquema de função de retorno de chamada clássico pode ser feito com a expressão lambda:
#incluirusando namespacehoras;
Caracteres *saída;
autocba= [](CaracteresFora[])
{
saída=Fora;
};
vaziomainFunc(Caracteresentrada[],vazio (*para)(Caracteres[]))
{
(*para)(entrada);
custo<<'para função principal'<<' n';
}
vaziofn()
{
custo<<'Agora'<<' n';
}
inta Principal()
{
Caracteresentrada[] = 'para função de retorno de chamada';
mainFunc(entrada, cba);
fn();
custo<<saída<<' n';
Retorna 0;
}
O resultado é:
para função principalAgora
para função de retorno de chamada
Lembre-se de que quando uma definição de expressão lambda é atribuída a uma variável no escopo global, seu corpo de função pode ver variáveis globais sem empregar a cláusula de captura.
O tipo de retorno à direita
O tipo de retorno de uma expressão lambda é auto, o que significa que o compilador determina o tipo de retorno da expressão de retorno (se houver). Se o programador realmente deseja indicar o tipo de retorno, ele o fará como no seguinte programa:
#incluirusando namespacehoras;
autofn= [](intparam) -> int
{
intresponder=param+ 3;
Retornaresponder;
};
inta Principal()
{
autovariab=fn(2);
custo <<variab<< ' n';
Retorna 0;
}
A saída é 5. Após a lista de parâmetros, o operador de seta é digitado. Isso é seguido pelo tipo de retorno (int neste caso).
Fecho
Considere o seguinte segmento de código:
estruturaCla{
intEu iria= 5;
CaracteresCH= 'para';
}obj1, obj2;
Aqui, Cla é o nome da classe de estrutura. Obj1 e obj2 são dois objetos que serão instanciados a partir da classe struct. A expressão lambda é semelhante na implementação. A definição da função lambda é um tipo de classe. Quando a função lambda é chamada (invocada), um objeto é instanciado a partir de sua definição. Este objeto é chamado de fechamento. É o fechamento que faz o trabalho que se espera que o lambda faça.
No entanto, codificar a expressão lambda como a estrutura acima terá obj1 e obj2 substituídos pelos argumentos dos parâmetros correspondentes. O programa a seguir ilustra isso:
#incluirusando namespacehoras;
autofn= [](intparam1,intparam2)
{
intresponder=param1+param2;
Retornaresponder;
} (2,3);
inta Principal()
{
autoOnde=fn;
custo <<Onde<< ' n';
Retorna 0;
}
A saída é 5. Os argumentos são 2 e 3 entre parênteses. Observe que a chamada de função da expressão lambda, fn, não aceita nenhum argumento, pois os argumentos já foram codificados no final da definição da função lambda.
Conclusão
A expressão lambda é uma função anônima. É dividido em duas partes: classe e objeto. Sua definição é uma espécie de classe. Quando a expressão é chamada, um objeto é formado a partir da definição. Este objeto é chamado de fechamento. É o fechamento que faz o trabalho que se espera que o lambda faça.
Para que a expressão lambda receba uma variável de um escopo de função externo, ela precisa de uma cláusula de captura não vazia em seu corpo de função.