Funciones de Dart

Las funciones son la unidad básica de organización del código: encapsulan una lógica reutilizable y se invocan cuando sea necesario.

El sistema de funciones de Dart es muy flexible; admite parámetros nombrados, parámetros opcionales, funciones flecha y funciones de orden superior.


Definición de funciones y valor de retorno

Una función se compone de tipo de retorno, nombre de la función, lista de parámetros y cuerpo de la función.

Ejemplo

// Definir una función simple
// String es el tipo de retorno, greet es el nombre de la función, (String name) es la lista de parámetros
String greet(String name) {
  return '¡Hola, $name! Bienvenido a EJEMPLO.';
}

// Las funciones sin valor de retorno usan void
void printWelcome() {
  print('=== Tutorial de Dart de EJEMPLO ===');
}

void main() {
  printWelcome();

  // Llamar a la función y recibir el valor de retorno
  String message = greet('Xiaoming');
  print(message);
}
=== EJEMPLO Dart 教程 ===
你好,小明!欢迎来到 EJEMPLO.

Valor de retorno

Toda función tiene un valor de retorno.

Si no hay un return explícito, la función devuelve null de forma implícita (pero en el null safety esto causaría un desajuste de tipos, por lo que normalmente se usa void para indicar que no hay valor de retorno).

Ejemplo

// Función que devuelve un tipo int
int add(int a, int b) {
  return a + b;
}

// Usar sintaxis de flecha para simplificar una función de una sola expresión
int multiply(int a, int b) => a * b;

// void indica que no hay un valor de retorno significativo
void log(String msg) {
  print('[Registro EJEMPLO] $msg');
}

void main() {
  print('3 + 5 = ${add(3, 5)}');
  print('3 × 5 = ${multiply(3, 5)}');
  log('Aprendizaje de funciones completado');
}
3 + 5 = 8
3 × 5 = 15
[EJEMPLO 日志] 函数学习完成

Parámetros nombrados y opcionales

Los parámetros de Dart se dividen en dos tipos: parámetros obligatorios (required) y opcionales.

Los opcionales se dividen a su vez en parámetros nombrados (named) y posicionales (positional).

Parámetros nombrados

Los parámetros nombrados se escriben entre llaves {}, se pasan por su nombre al llamar y su orden puede ser cualquiera.

Ejemplo

// Los parámetros entre llaves {} son nombrados y por defecto opcionales
// La palabra clave required los marca como obligatorios
String createUser({
  required String name,    // Parámetro nombrado obligatorio
  int age = 0,             // Opcional, valor por defecto 0
  String? email,           // Opcional, puede ser null
  bool isVip = false,      // Opcional, valor por defecto false
}) {
  var info = 'Usuario: $name, Edad: $age';
  if (email != null) {
    info += ', Email: $email';
  }
  if (isVip) {
    info += ' [Usuario VIP]';
  }
  return info;
}

void main() {
  // Parámetros nombrados: se pasan por nombre, el orden no importa
  print(createUser(name: 'ejemplo', age: 10));
  print(createUser(name: 'admin', email: '[email protected]', isVip: true));
  // Los parámetros obligatorios no se pueden omitir
  // createUser(); // Error: falta el parámetro obligatorio name
}
用户名: ejemplo, 年龄: 10
用户名: admin, 邮箱: [email protected] [VIP用户]

Parámetros posicionales

Los parámetros posicionales se escriben entre corchetes [] y se pasan en orden al llamar.

Ejemplo

// Los parámetros entre corchetes [] son posicionales opcionales
String buildUrl(String host, [String path = '/', int port = 80]) {
  return 'http://$host:$port$path';
}

void main() {
  // Solo se pasan los obligatorios, los opcionales usan su valor por defecto
  print(buildUrl('www.example.com'));

  // Pasar 2 parámetros
  print(buildUrl('www.example.com', '/dart'));

  // Pasar 3 parámetros
  print(buildUrl('localhost', '/api', 8080));
}
http://www.example.com:80/
http://www.example.com:80/dart
http://localhost:8080/api

Parámetros nombrados vs posicionales: cómo elegir

EscenarioForma recomendadaMotivo
Muchos parámetros (más de 3)Parámetros nombradosAl llamar, el nombre del parámetro se autodocumenta y es difícil equivocarse en el orden.
El significado de los parámetros no es evidenteParámetros nombradosValores como true/false no explican su propósito; añadir un nombre lo hace más claro.
Pocos parámetros y con significado claroParámetros posicionalesComo add(1, 2), simple y claro.
Construcción de widgets de FlutterParámetros nombradosEstilo estándar de Flutter

Una función no puede usar simultáneamente parámetros posicionales opcionales y parámetros nombrados opcionales. Solo puedes elegir una de las dos.


Valores de parámetros por defecto

Los parámetros opcionales pueden tener un valor por defecto, que se usa cuando quien llama no pasa ese parámetro.

Ejemplo

// Todos los parámetros opcionales tienen valor por defecto
String formatMessage(
  String content, {
  String prefix = '[EJEMPLO]',
  String suffix = '',
  bool uppercase = false,
}) {
  var result = '$prefix $content $suffix';
  return uppercase ? result.toUpperCase() : result;
}

void main() {
  // Usar todos los valores por defecto
  print(formatMessage('El tutorial de Dart se ha actualizado'));

  // Sobrescribir algunos valores por defecto
  print(formatMessage('Aviso importante', prefix: '[Anuncio]', suffix: '!!!'));

  // Sobrescribir el valor por defecto + activar mayúsculas
  print(formatMessage('error', prefix: '[Error]', uppercase: true));
}
[EJEMPLO] Dart 教程更新了
[公告] 重要通知 !!!
[错误] ERROR

Los valores por defecto deben ser constantes en tiempo de compilación. Es decir, un valor por defecto no puede ser DateTime.now() ni el valor de retorno de una función (a menos que esa función sea const). Si necesitas determinar el valor por defecto en tiempo de ejecución, puedes usar ?? dentro del cuerpo de la función.


Funciones flecha y funciones anónimas

Cuando el cuerpo de la función tiene una sola expresión, se puede abreviar con la sintaxis de flecha (=>).

Una función anónima es una función sin nombre, normalmente se pasa como parámetro a otras funciones.

Ejemplo

void main() {
  var numbers = [1, 2, 3, 4, 5];

  // Función anónima (escritura completa)
  numbers.forEach((number) {
    print('Número EJEMPLO: $number');
  });

  print('---');

  // Función flecha (abreviación de una sola expresión)
  var doubled = numbers.map((n) => n * 2);
  print('Doble: $doubled');

  // Uso de la función flecha en la definición de funciones
  // Cuando el cuerpo de la función tiene solo una declaración return, se puede abreviar con =>
  int square(int x) => x * x;
  print('Cuadrado de 5: ${square(5)}');

  // Almacenar una función anónima en una variable
  var sayHello = (String name) => '¡Hola, $name!';
  print(sayHello('ejemplo'));
}
EJEMPLO 数字: 1
EJEMPLO 数字: 2
EJEMPLO 数字: 3
EJEMPLO 数字: 4
EJEMPLO 数字: 5
---
翻倍: (2, 4, 6, 8, 10)
5 的平方: 25
你好,ejemplo!

Funciones de orden superior y cierres

Una función de orden superior es aquella que puede recibir una función como parámetro o devolver una función como resultado.

Un cierre es una función que puede acceder a variables de su ámbito léxico, incluso si se invoca fuera de ese ámbito.

Funciones de orden superior

Ejemplo

// Función de orden superior: recibe una función como parámetro
List<int> filterList(
    List<int> items, bool Function(int) predicate) {
  return items.where(predicate).toList();
}

// Función de orden superior: devuelve una función
Function makeMultiplier(int factor) {
  // Devuelve un cierre: recuerda el factor externo
  return (int n) => n * factor;
}

void main() {
  var numbers = [10, 15, 20, 25, 30];

  // Pasar una función anónima como condición de filtrado
  var bigNumbers = filterList(numbers, (n) => n > 18);
  print('Números de EJEMPLO mayores de 18: $bigNumbers');

  // Obtener la función devuelta
  var doubleIt = makeMultiplier(2);
  var tripleIt = makeMultiplier(3);

  print('5 × 2 = ${doubleIt(5)}');
  print('5 × 3 = ${tripleIt(5)}');
}
EJEMPLO 大于18的数: [20, 25, 30]
5 × 2 = 10
5 × 3 = 15

Cierre

Un cierre es un objeto de función que puede acceder a las variables de su ámbito léxico, incluso si la función se invoca fuera de su ámbito original.

Ejemplo

// Aplicación típica del cierre: crear un contador
Function makeCounter() {
  int count = 0;  // Esta variable es "capturada" por la función devuelta
  return () {
    count++;
    return count;
  };
}

void main() {
  // counterA y counterB tienen cada uno su propio count independiente
  var counterA = makeCounter();
  var counterB = makeCounter();

  print('Contador EJEMPLO A: ${counterA()}');  // 1
  print('Contador EJEMPLO A: ${counterA()}');  // 2
  print('Contador EJEMPLO B: ${counterB()}');  // 1 (contador independiente)
  print('Contador EJEMPLO A: ${counterA()}');  // 3
}
EJEMPLO 计数器 A: 1
EJEMPLO 计数器 A: 2
EJEMPLO 计数器 B: 1
EJEMPLO 计数器 A: 3

La clave del cierre es que la función interna "recuerda" las variables de la función externa, incluso después de que la función externa haya terminado de ejecutarse. Cada llamada a makeCounter() crea una variable count y un cierre completamente nuevos, por lo que varios contadores no se afectan entre sí.

Otras extensiones