C++: Objetos de Função STL
Na lição 37 aprendemos sobre iteradores.
Agora, vamos aprender sobre a "alma" dos algoritmos STL — objetos de função.
Algoritmos são o esqueleto, objetos de função são a carne. Somente quando combinados eles liberam o verdadeiro poder da STL.
1. Visão Geral de Objetos de Função
(1) 1.1 O que são Objetos de Função?
Objetos de função (Funtores) são objetos que podem ser chamados como funções.
Três tipos de objetos de função:
- Ponteiros de função
- Classes de objeto de função (sobrecarga de
operator()) - Expressões lambda (C++11)
(2) 1.2 Por que Precisamos de Objetos de Função?
Os algoritmos STL são genéricos, mas operações específicas variam conforme a necessidade. Objetos de função permitem que você personalize operações.
Analogia do mundo real:
- Algoritmo = máquina de lavar (genérica)
- Objeto de função = detergente (personalizável: aroma fresco / uso pesado / amaciante)
2. Ponteiros de Função
(1) 2.1 Uso Básico
Exemplo: Ordenação personalizada com ponteiros de função (Dificuldade ⭐⭐)
▶ Exemplo 2: Uso de Contêiner STL (Dificuldade ⭐)
#include <iostream>
#include <vector>
#include <algorithm>
// Custom comparison function
bool compareDesc(int a, int b) {
return a > b; // Descending
}
int main() {
std::vector<int> v = {3, 1, 4, 1, 5, 9, 2, 6};
// Use function pointer
std::sort(v.begin(), v.end(), compareDesc);
for (int x : v) {
std::cout << x << " ";
}
std::cout << std::endl;
return 0;
}
Saída:
3 1 4 1 5 9 2 6
Resultado:
9 6 5 4 3 2 2 1 1
💡 Dica:
- Ponteiros de função são herança da linguagem C; C++ recomenda objetos de função ou Lambda
3. Classes de Objeto de Função
(1) 3.1 O que são Classes de Objeto de Função?
Classes de objeto de função são classes que sobrecarregam operator(), cujas instâncias podem ser chamadas como funções.
Exemplo: Comparador personalizado (Dificuldade ⭐⭐)
#include <iostream>
#include <vector>
#include <algorithm>
#include <string>
// Function object class: sort by string length
struct CompareByLength {
bool operator()(const std::string& a, const std::string& b) const {
return a.length() < b.length();
}
};
int main() {
std::vectorstd::string words = {"apple", "banana", "cat", "dog"};
// Use function object
std::sort(words.begin(), words.end(), CompareByLength());
for (const auto& w : words) {
std::cout << w << " ";
}
std::cout << std::endl;
return 0;
}
Resultado:
cat dog apple banana
(2) 3.2 Vantagens dos Objetos de Função
| Comparação | Ponteiro de Função | Classe de Objeto de Função |
|---|---|---|
| Estado | Sem estado | Pode ter estado (variáveis membro) |
| Desempenho | Pode não ser inline | Pode ser inline, mais rápido |
| Flexibilidade | Baixa | Alta (pode ser template) |
(3) 3.3 Objetos de Função com Estado
Exemplo: Objeto de função contador (Dificuldade ⭐⭐⭐)
#include <iostream>
#include <algorithm>
#include <vector>
// Function object: count elements satisfying a condition
struct Counter {
int threshold; // Threshold (state)
Counter(int t) : threshold(t) {}
bool operator()(int x) const {
return x > threshold; // Count elements greater than threshold
}
};
int main() {
std::vector<int> v = {1, 5, 10, 15, 20};
// Create function object, set threshold to 10
Counter counter(10);
// Count elements greater than 10
int count = std::count_if(v.begin(), v.end(), counter);
std::cout << "Elements greater than 10: " << count << std::endl; // Output: 2
return 0;
}
4. Expressões Lambda
(1) 4.1 O que são Expressões Lambda?
Expressões lambda são funções anônimas introduzidas no C++11 que podem ser definidas inline ondequer que uma função seja necessária.
Sintaxe básica:
[capture](parameters) -> return_type { body }
| Parte | Descrição |
|---|---|
capture |
Lista de captura (capturar variáveis externas) |
parameters |
Lista de parâmetros |
return_type |
Tipo de retorno (pode ser omitido) |
body |
Corpo da função |
(2) 4.2 Exemplo Básico
Exemplo: Ordenação com Lambda (Dificuldade ⭐)
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
std::vector<int> v = {3, 1, 4, 1, 5, 9, 2, 6};
// Sort with Lambda expression (descending)
std::sort(v.begin(), v.end(), (int a, int b) {
return a > b;
});
for (int x : v) {
std::cout << x << " ";
}
std::cout << std::endl;
return 0;
}
Saída:
3 1 4 1 5 9 2 6
(3) 4.3 Lista de Captura
A lista de captura determina quais variáveis externas uma Lambda pode acessar.
| Método de Captura | Descrição |
|---|---|
| `` | Não captura nada |
[x] |
Captura x por valor |
[&x] |
Captura x por referência |
[=] |
Captura todas as variáveis por valor |
[&] |
Captura todas as variáveis por referência |
[this] |
Captura o ponteiro this (usado dentro de uma classe) |
Exemplo: Lambda com estado (Dificuldade ⭐⭐)
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
std::vector<int> v = {1, 5, 10, 15, 20};
int threshold = 10;
// Capture threshold by value
int count = std::count_if(v.begin(), v.end(),
[threshold](int x) {
return x > threshold;
});
std::cout << "Elements greater than " << threshold << ": " << count << std::endl;
return 0;
}
5. Objetos de Função Predefinidos da STL
(1) 5.1 Objetos de Função Aritméticos
O cabeçalho functional fornece objetos de função comuns:
| Objeto de Função | Finalidade | Exemplo |
|---|---|---|
std::plusT |
Adição | std::plusint() |
std::minusT |
Subtração | std::minusint() |
std::multipliesT |
Multiplicação | std::multipliesint() |
std::dividesT |
Divisão | std::dividesint() |
std::negateT |
Negação | std::negateint() |
Exemplo: Dobrando com multiplies (Dificuldade ⭐)
#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>
int main() {
std::vector<int> v = {1, 2, 3, 4, 5};
// Double all elements
std::transform(v.begin(), v.end(), v.begin(),
std::bind(std::multipliesint(), std::placeholders::_1, 2));
for (int x : v) {
std::cout << x << " ";
}
std::cout << std::endl;
return 0;
}
(2) 5.2 Objetos de Função de Comparação
| Objeto de Função | Finalidade |
|---|---|
std::equal_toT |
Igual a |
std::not_equal_toT |
Diferente de |
std::greaterT |
Maior que |
std::lessT |
Menor que |
std::greater_equalT |
Maior ou igual a |
std::less_equalT |
Menor ou igual a |
(3) 5.3 Objetos de Função Lógicos
| Objeto de Função | Finalidade |
|---|---|
std::logical_andT |
E lógico |
std::logical_orT |
OU lógico |
std::logical_notT |
NÃO lógico |
6. Exemplo Abrangente
▶ Exemplo 1: Processador de Notas (Dificuldade ⭐⭐⭐)
#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>
struct Student {
std::string name;
int score;
};
int main() {
std::vectorStudent students = {
{"Zhang San", 85},
{"Li Si", 92},
{"Wang Wu", 78}
};
// 1. Sort by score descending
std::sort(students.begin(), students.end(),
(const Student& a, const Student& b) {
return a.score > b.score;
});
// 2. Find the highest score
auto max_it = std::max_element(students.begin(), students.end(),
(const Student& a, const Student& b) {
return a.score < b.score;
});
std::cout << "Highest score: " << max_it->name << " " << max_it->score << std::endl;
// 3. Count passing students
int passed = std::count_if(students.begin(), students.end(),
(const Student& s) {
return s.score >= 60;
});
std::cout << "Students passed: " << passed << std::endl;
return 0;
}
Saída:
Highest score:
Students passed:
❓ Perguntas Frequentes
P: Qual é melhor — Lambda ou classes de objeto de função? R:- Operações simples → Lambda (código conciso) - Operações complexas / precisa de reuso → Classes de objeto de função (melhor manutenibilidade)
P:
autopode deduzir o tipo de uma Lambda? R: O tipo de uma Lambda é um tipo anônimo único; só pode ser deduzido comauto, não escrito como um tipo concreto.
auto func = (int x) { return x * 2; };
// std::function<int(int)> func = ... // Also works, but with performance overhead
P: Quando devo usar
std::function? R: Usestd::functionquando precisar armazenar objetos de função (como variáveis membro, valores de retorno, etc.).
▶ Exemplo 3: Expressão Lambda (Dificuldade ⭐)
#include <iostream>
#include <algorithm>
#include <vector>
int main() {
std::vector<int> v = {1, 2, 3, 4, 5};
int sum = 0;
std::for_each(v.begin(), v.end(), [&sum](int x) {
sum += x;
});
std::cout << "Total: " << sum << std::endl;
return 0;
}
Saída:
1 2 3 4 5
[capture](parameters) { body }. [&sum] captura por referência, permitindo modificação de variáveis externas.
| Tópico | Pontos-chave |
|---|---|
| Ponteiros de função | Simples mas funcionalidade limitada |
| Classes de objeto de função | Operações personalizáveis, podem carregar estado |
| Lambda | Funções anônimas, concisas e poderosas |
| Objetos de função predefinidos | std::plus etc., em functional |
| Lista de captura | Como Lambda acessa variáveis externas |
📖 Resumo
- Objetos de função: classes que sobrecarregam
operator(), chamáveis como funções - Lambda: funções anônimas, sintaxe
[capture](parameters) { body } - std::function: invólucro de função genérico, pode armazenar qualquer objeto chamável
- std::bind: vincula argumentos de função, gerando novos objetos chamáveis
📝 Exercícios
-
Básico (Dificuldade ⭐): Crie um funtor (uma classe sobrecarregando operator()) que "compara dois inteiros", e teste-o com std::sort.
-
Intermediário (Dificuldade ⭐⭐): Use std::function para armazenar diferentes tipos de objetos chamáveis (funções regulares, lambdas, funtores) e chamá-los uniformemente.
-
Desafio (Dificuldade ⭐⭐⭐): Use std::bind para vincular argumentos parciais, gerando novos objetos chamáveis. Implemente um adaptador de função de "pré-preenchimento de argumentos".
- Objetos de função (funtores): classes sobrecarregando operator()
- Objetos de função podem manter estado; funções regulares não podem
- std::function: invólucro chamável com apagamento de tipo
- bind vincula argumentos parciais para gerar novos objetos chamáveis
- Lambda é açúcar sintático para objetos de função
Próxima lição: Adaptadores STL (#39)