🧩 Dividir o programa en tarefas
Unha función agrupa instrucións para poder usalas varias veces. Pode recibir datos (parámetros) e devolver un resultado con return.
// A función recibe dúas notas e devolve a media.
double calcularMedia(double primeira, double segunda) {
return (primeira + segunda) / 2;
}
void main() {
double media = calcularMedia(7, 8); // Chamamos á función.
print(media);
}
double antes do nome indica o tipo do resultado. Se non precisamos devolver un resultado, usamos void, como en main().
return remata a función. Nunha función void podemos escribir return; para saír antes de chegar ao final.
📥 Parámetros posicionais e nomeados
🔢 Posicionais obrigatorios
Os valores asígnanse segundo a posición. En calcularMedia(7, 8), 7 é primeira e 8 é segunda. Hai que pasar os dous.
🏷️ Nomeados: moi habituais en Flutter
Defínense entre {}. Ao chamar á función indicamos o nome de cada argumento. Podemos marcar un como obrigatorio con required ou darlle un valor predeterminado.
void presentar({required String nome, String grupo = '2º DAM'}) {
print('$nome pertence a $grupo');
}
void main() {
presentar(nome: 'Iria'); // Usa o grupo predeterminado.
presentar(grupo: '2º DAM', nome: 'Roi'); // A orde non importa.
}
Un parámetro nomeado sen required é opcional. Se non ten valor predeterminado, debe admitir null, por exemplo {String? telefono}. Os valores predeterminados deben ser constantes.
required obriga a pasar o argumento; ? permite que o valor sexa nulo. Son cousas distintas.
📦 Posicionais opcionais
Defínense entre [] e pásanse sen nome. Verémolos menos ca os nomeados.
String saudar(String nome, [String saudo = 'Ola']) {
return '$saudo, $nome';
}
void main() {
print(saudar('Diego')); // Ola, Diego
print(saudar('Rosa', 'Bo día'));
}
Podemos combinar posicionais obrigatorios con nomeados ou con posicionais opcionais; non usamos [] e {} na mesma lista de parámetros.
📦 Devolver varios datos cun rexistro
Unha función devolve un único resultado, pero ese resultado pode ser un rexistro con varios campos. Así podemos devolver, por exemplo, a media e se o alumno aprobou:
({double media, bool aprobado}) avaliar(double primeira, double segunda) {
double media = (primeira + segunda) / 2;
return (media: media, aprobado: media >= 5); // Dous datos nun rexistro.
}
void main() {
final resultado = avaliar(7, 9);
print('Media: ${resultado.media}'); // 8.0
print('Aprobado: ${resultado.aprobado}'); // true
}
({double media, bool aprobado}) indica o tipo do resultado. Despois accedemos a cada campo polo seu nome. Tamén poderiamos devolver un rexistro posicional, pero os nomes axudan a entender que representa cada valor.
➡️ Sintaxe de frecha
Se a función contén unha única expresión, podemos usar =>. Non depende do número de liñas que ocupe.
// Devolve o resultado da comparación.
bool estaAprobado(int nota) => nota >= 5;
Se precisamos varias instrucións, usamos chaves e, cando corresponda, return.
📌 Funcións como obxectos
En Dart, as funcións son tamén obxectos. Isto significa que podemos traballar con elas como facemos con outros valores:
- gardalas nunha variable;
- pasalas como argumento a outra función;
- devolvelas como resultado dunha función.
📦 Gardar unha función nunha variable
Podemos gardar unha función xa existente nunha variable:
int dobre(int numero) {
return numero * 2;
}
void main() {
int Function(int) operacion = dobre;
print(operacion(5)); // 10
}
Neste exemplo, operacion contén unha referencia á función dobre.
É importante distinguir entre:
| Expresión | Que fai? |
|---|---|
dobre | Refírese á función, sen executala. |
dobre(5) | Executa a función co argumento 5. |
Ao escribir o nome sen parénteses, estamos a referirnos á función. Ao engadir os parénteses, executámola.
🏷️ Tipos de función
O tipo dunha función indica os parámetros que recibe e o resultado que devolve. Estas son notacións de tipos, non programas para executar:
| Tipo | Que describe? |
|---|---|
void Function() | Unha función sen parámetros nin resultado. |
int Function(int) | Recibe un enteiro e devolve outro. |
String Function(String) | Recibe un texto e devolve outro. |
En int Function(int) operacion = dobre;, o tipo indica que función podemos gardar en operacion.
✍️ Gardar unha función anónima
Unha función anónima non ten nome. Podemos gardala nunha variable e chamala a través desa variable:
void main() {
String Function(String) saudar = (String nome) {
return 'Ola, $nome!'; // Devolvemos o texto do saúdo.
};
print(saudar('Iria')); // Ola, Iria!
}
A mesma función cunha única expresión pode escribirse con frecha:
void main() {
String Function(String) saudar = (String nome) => 'Ola, $nome!';
print(saudar('Iria')); // O mesmo resultado que coa versión con bloque.
}
Nos primeiros exemplos indicamos os tipos para facilitar a lectura. Cando Dart poida deducilos, poderemos escribir (nome) en vez de (String nome).
🔔 Callbacks: pasar unha función como argumento
Un callback é unha función que se pasa como argumento a outra función para que esta a execute.
A función que recibe o callback decide cando e con que argumentos debe chamalo.
🔔 Callback sen parámetros
Creamos unha función que recibe outra función como argumento:
void executarAccion(void Function() accion) {
print('Comeza a acción');
accion();
print('Remata a acción');
}
Conserva a función executarAccion() anterior e engade este código para pasarlle unha función con nome:
void avisar() {
print('Acción realizada');
}
void main() {
executarAccion(avisar);
}
A saída será:
Comeza a acción
Acción realizada
Remata a acción
Fixémonos novamente na diferenza:
executarAccion(avisar); // Pasamos a función como argumento.
// executarAccion(avisar()); // Incorrecto: executa avisar e non entrega unha función.
O parámetro void Function() accion recibe a función que imos executar.
Isto indica que accion debe ser unha función que:
- non recibe argumentos;
- non devolve ningún valor.
Conserva executarAccion() e substitúe só main() para pasar unha función anónima:
void main() {
executarAccion(() {
print('Rosa revisou o aviso'); // Callback escrito directamente aquí.
});
}
Conservando tamén executarAccion(), podemos substituír main() por esta versión con frecha:
void main() {
executarAccion(() => print('Julia compartiu un exercicio'));
}
📥 Callback con parámetros e valor de retorno
Os callbacks tamén poden recibir parámetros e devolver resultados:
int aplicarOperacion(
int numero,
int Function(int) operacion,
) {
return operacion(numero);
}
int dobre(int numero) {
return numero * 2;
}
void main() {
print(aplicarOperacion(5, dobre)); // 10
print(
aplicarOperacion(5, (int numero) => numero * numero),
); // 25
}
Neste caso, aplicarOperacion() recibe:
- un número;
- unha función que recibe un
inte devolve outroint.
A función aplicarOperacion() chama o callback e devolve o resultado obtido.
📱 Unha primeira ollada a Flutter
📌 Este fragmento require Flutter e vai dentro dunha interface. Non é un programa completo de consola. No Tema 2.2 faremos unha ponte a Flutter; na UD 03 veremos os widgets e os eventos con detalle.
Os callbacks empréganse con frecuencia en Flutter para indicar o que debe suceder cando se produce un evento:
ElevatedButton(
onPressed: () {
print('Premiches o botón');
},
child: const Text('Premer'),
)
A función asignada a onPressed é un callback que Flutter executará cando a persoa usuaria prema o botón.
🔒 Closures: conservar o acceso ás variables do contorno
Unha closure é unha función que conserva o acceso ás variables do ámbito no que foi creada, mesmo cando se executa noutro lugar ou noutro momento.
Unha closure pode ser unha función con nome ou unha función anónima. Polo tanto, non debemos confundir estes conceptos:
- Unha función anónima é unha función que non ten nome.
- Un callback é unha función que se pasa como argumento a outra función.
- Unha closure é unha función que captura variables do seu contorno.
🔒 Capturar unha variable exterior
Observa o seguinte exemplo:
void main() {
String alumno = 'Xurxo';
final saudar = () {
print('Ola, $alumno'); // Usa a variable do contorno.
};
saudar(); // Ola, Xurxo
alumno = 'Iria';
saudar(); // Ola, Iria
}
A función saudar() utiliza a variable alumno, aínda que esta non foi declarada dentro da función nin se recibiu como parámetro.
Dicimos que saudar() captura a variable alumno.
Ademais, a closure non garda necesariamente unha copia do valor que tiña a variable cando se creou. Conserva o acceso á propia variable, polo que pode observar os seus cambios.
🔢 Conservar e modificar un estado
Unha función pode devolver outra función. Esa función devolta pode conservar unha variable e modificala entre chamadas:
int Function() crearContador() {
int conta = 0;
return () {
conta++;
return conta;
};
}
void main() {
final contador = crearContador();
print(contador()); // 1
print(contador()); // 2
print(contador()); // 3
}
A variable conta créase dentro de crearContador(). Porén, a función devolta continúa tendo acceso a ela despois de que remate a execución de crearContador().
int Function() antes de crearContador indica que devolve unha función sen parámetros que produce un enteiro. Por iso crearContador() devolve unha función e contador() devolve un número. A variable conta consérvase entre esas chamadas.
Cada contador mantén o seu propio estado. Conserva crearContador() e substitúe só main() por este código:
void main() {
final contadorA = crearContador();
final contadorB = crearContador();
print(contadorA()); // 1
print(contadorA()); // 2
print(contadorB()); // 1
print(contadorA()); // 3
print(contadorB()); // 2
}
contadorA e contadorB son funcións diferentes. Cada unha conserva a súa propia variable conta.
🧩 Un callback que tamén é closure
void executarAccion(void Function() accion) {
accion(); // Executamos a función recibida.
}
void main() {
String alumno = 'Iria';
executarAccion(() {
print('Gardouse a nota de $alumno'); // Accedemos á variable exterior.
});
}
É unha función anónima porque non ten nome, un callback porque se pasa a executarAccion() e unha closure porque captura alumno.
🧭 Diferenza entre os conceptos
| Concepto | Que significa? |
|---|---|
| Función como obxecto | Pode gardarse, pasarse como argumento ou devolverse. |
| Función anónima | É unha función que non ten nome. |
| Callback | É unha función que se pasa a outra función para que esta a execute. |
| Closure | É unha función que conserva o acceso ás variables do contorno no que foi creada. |
Unha mesma función pode pertencer a varias destas categorías ao mesmo tempo.
✏️ Exercicio de comprobación
Analiza o seguinte código antes de executalo:
int Function(int) crearSumador(int cantidade) {
return (int numero) => numero + cantidade;
}
void main() {
final sumarCinco = crearSumador(5);
final sumarDez = crearSumador(10);
print(sumarCinco(3));
print(sumarDez(3));
}
Preguntas:
Analiza o código e responde as seguintes preguntas:
- Que tipo de valor devolve
crearSumador()? - Que variable captura a función devolta?
- Cal será a saída do programa?
- A función devolta é anónima? É un callback? É unha closure?
- Como crearías unha función chamada
sumarVinte?
Respostas:
- Devolve unha función de tipo
int Function(int): recibe un enteiro e devolve outro. - Captura o parámetro
cantidade; cada chamada acrearSumador()crea un contorno independente. - Mostra
8e13, resultado de sumar 5 e 10 ao número 3. - É anónima e closure. Neste exemplo non é callback, porque non se pasa como argumento a outra función.
- Dentro do mesmo
main(), podemos engadir:
final sumarVinte = crearSumador(20);
print(sumarVinte(3)); // 23
📚 Consulta: closures — https://dart.dev/language/functions#lexical-closures.
🧪 Exemplo completo
🔗 Ver o código en DartPad — https://dartpad.dev/?id=be0f458633ba3c80b0344ff3bf672fe6
O comentario /// documenta a función: o editor pode mostralo cando a usamos.
📚 Consulta: funcións en Dart – https://dart.dev/language/functions.