Valores por defecto e inout
Al terminar: Dar valores por defecto a los parámetros y modificar un valor con inout.
Al terminar tendrás en Puntos.swift dos funciones más: encabezadoDeJornada, con un parámetro que tiene valor por defecto, y sumarGol, que cambia la variable de quien la llama con inout. Los 5 tests de FuncionesTests las comprueban, y main.swift imprime Fecha 1 de 3 y Goles de Ana: 2.
Importa porque un valor por defecto te ahorra escribir en cada llamada lo que casi siempre es igual, y porque inout es la forma de Swift de decir «esta función cambia mi variable», con un & que se ve desde fuera. Verás las dos ideas en la biblioteca estándar y en las APIs de Apple.
Temas: valores por defecto de parámetros e inout.
Un parámetro con valor por defecto
Un valor por defecto es el valor que tiene un parámetro cuando quien llama no lo da. Se escribe con = después del tipo, y al llamar la función puedes omitir ese argumento. Es como el formulario de una tienda en línea con el país ya rellenado con «España»: si no cambias nada, se queda ese; si quieres otro, lo escribes.
| Cómo se declara | Cómo se llama | Qué pasa |
|---|---|---|
func f(goles: Int = 1) | f() o f(goles: 2) | Valor por defecto: se puede omitir |
inout: un parámetro que sale cambiado
Un parámetro normal es una constante: n += 1 dentro de la función da left side of mutating operator isn't mutable: 'n' is a 'let' constant. Con inout, el valor de la variable de quien llama entra en la función, la función lo cambia y el valor nuevo vuelve a la variable al terminar. Quien llama pone & delante para que se vea. Solo se puede pasar una variable, no un número escrito directamente (como 5) ni un let: con un let el compilador dice cannot pass immutable value as inout argument: 'reloj' is a 'let' constant.
En Kotlin no existe: sus parámetros nunca se pueden cambiar, y para sacar un valor cambiado de una función escribías return con el valor nuevo y quien llamaba lo guardaba (goles = sumarGol(goles)).
Las dos ideas con otros datos, el saludo de una tienda y sus cajas en el almacén:
// `a nombre`: etiqueta a, nombre interno nombre.
// `= "!"`: valor por defecto, así que con: se puede omitir.
func saludar(a nombre: String, con signo: String = "!") -> String {
"Hola, \(nombre)\(signo)"
}
// `inout`: el valor entra, la función lo cambia y el valor nuevo vuelve a la variable.
func reponer(_ existencias: inout Int, unidades: Int = 1) {
existencias += unidades // sin inout, esto no compilaría: sería una constante
}
print(saludar(a: "Lucía")) // Hola, Lucía!
print(saludar(a: "Lucía", con: ".")) // Hola, Lucía.
var cajas = 10 // tiene que ser var: la función la va a cambiar
reponer(&cajas) // 11: unidades vale 1 si no se dice
reponer(&cajas, unidades: 5) // 16
print(cajas) // 16Lo que imprime:
Hola, Lucía!
Hola, Lucía.
16Kotlin frente a Swift
| Kotlin (tu código de Kotlin) | Swift |
|---|---|
encabezadoDeJornada(numero = 1, totalDeJornadas = 3) | encabezadoDeJornada(numero: 1, de: 3) |
prefijo: String = "Jornada" | prefijo: String = "Jornada" |
| Un parámetro nunca se cambia | inout y & al llamar |
Reto · Ejercicio: el reloj del partido
En tu Playground, escribe sumarMinutos, que reciba una variable de minutos y le sume, en la propia variable, los minutos que le digas; si no le dices cuántos, suma 5. Y cartel, que reciba un minuto con la etiqueta en y devuelva «Minuto 25» para el 25. Con una variable que empiece en 0, llama a sumarMinutos una vez sin decir cuántos y otra con 20, e imprime su cartel:
Minuto 25Después intenta llamar a sumarMinutos con una constante en lugar de la variable y lee el mensaje del compilador.
Ver solución
// inout: la variable de quien llama sale cambiada. cuantos vale 5 si no se dice.
func sumarMinutos(_ minuto: inout Int, cuantos: Int = 5) {
minuto += cuantos
}
// Etiqueta en (fuera) y nombre minuto (dentro).
func cartel(en minuto: Int) -> String {
"Minuto \(minuto)"
}
var reloj = 0
sumarMinutos(&reloj) // 5
sumarMinutos(&reloj, cuantos: 20) // 25
print(cartel(en: reloj)) // Minuto 25Con let reloj = 0 y sumarMinutos(&reloj), el compilador dice cannot pass immutable value as inout argument: 'reloj' is a 'let' constant.
Paso · encabezadoDeJornada y sumarGol
Objetivo: ampliar Puntos.swift con las dos funciones nuevas y main.swift con sus llamadas. Al terminar, xcrun swift run torneo-cli imprime Fecha 1 de 3 y Goles de Ana: 2, y xcrun swift test da 16 tests.
Abre una Terminal y escribe cd ~/Marcador-Swift/Torneo. Vas en dos tramos, y compilas y ejecutas al terminar cada uno.
Tramo 1 · encabezadoDeJornada: etiquetas y valor por defecto
Intenta escribirlo tú, al final de Puntos.swift: encabezadoDeJornada, una función pública que recibe el número de jornada con la etiqueta numero, el total con la etiqueta de y, opcionalmente, un prefijo (si no se da, «Jornada»), y devuelve un texto como «Jornada 1 de 3».
Intenta primero. Cuando termines, compara con la solución.Mostrar el código
// `de total` es etiqueta `de` y nombre interno `total`.
// `prefijo` tiene valor por defecto: se puede omitir al llamar.
public func encabezadoDeJornada(numero: Int, de total: Int, prefijo: String = "Jornada") -> String {
"\(prefijo) \(numero) de \(total)"
}En main.swift, sustituye el bucle de jornadas (el for que imprimía Jornada \(jornada) de \(totalDeJornadas)). Intenta escribirlo tú: el mismo bucle, que ahora imprime cada encabezado con encabezadoDeJornada, y después una línea más, la jornada 1 de 3 con el prefijo «Fecha». Es un fragmento: el resto del fichero no cambia. Lo que debes ver tras Torneo: Copa Barrio:
Jornada 1 de 3
Jornada 2 de 3
Jornada 3 de 3
Fecha 1 de 3Intenta primero. Cuando termines, compara con la solución.Mostrar el código
for jornada in 1...totalDeJornadas {
// Las etiquetas `numero:` y `de:` son obligatorias y van en este orden.
print(encabezadoDeJornada(numero: jornada, de: totalDeJornadas))
}
// Con el tercer argumento, el prefijo deja de ser "Jornada".
print(encabezadoDeJornada(numero: 1, de: 3, prefijo: "Fecha"))Ejecuta xcrun swift run torneo-cli: tras Torneo: Copa Barrio debe salir Jornada 1 de 3, Jornada 2 de 3, Jornada 3 de 3 y Fecha 1 de 3, y luego los nueve marcadores de siempre.
Tramo 2 · sumarGol: inout
Pieza nueva: inout en la declaración del parámetro y & delante del argumento al llamar. El patrón completo, comentado, es reponer(_:unidades:) del ejemplo mínimo de arriba: vuelve a él si dudas.
Intenta escribirlo tú, al final de Puntos.swift: sumarGol, una función pública que recibe con la etiqueta a un marcador y le suma un gol, de modo que quien la llama ve su variable cambiada.
Intenta primero. Cuando termines, compara con la solución.Mostrar el código
// `inout`: el valor entra, la función lo cambia y el valor nuevo vuelve a quien llamó.
public func sumarGol(a marcador: inout Int) {
marcador += 1
}En main.swift, después del fragmento de los titulares (los siete marcadores de la lección anterior) y antes del comentario // Una lista de tres jugadores, intenta escribir tú unas líneas que guarden los goles de Ana empezando en 0, le sumen dos goles con sumarGol y los impriman. Lo que debes ver:
Goles de Ana: 2Intenta primero. Cuando termines, compara con la solución.Mostrar el código
var golesDeAna = 0 // tiene que ser `var`: la función la va a cambiar
sumarGol(a: &golesDeAna) // el `&` obliga a que se vea que puede cambiar
sumarGol(a: &golesDeAna)
print("Goles de Ana: \(golesDeAna)") // Goles de Ana: 2Ejecuta de nuevo: tras los siete titulares debe salir Goles de Ana: 2, y después, lo de la lección de opcionales.
El error del & olvidado
Si llamas sumarGol(a: golesDeAna) sin el &, el compilador se detiene con este mensaje, precedido por la ruta y la línea de tu main.swift:
error: passing value of type 'Int' to an inout parameter requires explicit '&'El & no es decoración: obliga a que quien llama vea que su variable puede cambiar.
Los tests
Los cinco tests de FuncionesTests.swift no los escribes: son comprobaciones de encabezadoDeJornada y de inout, funciones que ya has escrito. Rellénalos con el botón Copiar fichero (copia al portapapeles el fichero ya escrito, como en guard let y textos que no son números):
- Crea
Tests/TorneoTests/FuncionesTests.swift: clic derecho sobreTests → TorneoTests→ New File from Template… → Swift File → Next, escribeFuncionesTestsy pulsa Create. O, en la Terminal,touch Tests/TorneoTests/FuncionesTests.swift. - Pulsa el botón de abajo.
- Haz clic dentro del fichero, selecciona todo con Cmd+A y pega con Cmd+V.
Uno enseña algo nuevo: prueba inout comprobando que la variable de quien llama cambia.
@Test func inoutCambiaLaVariableDeQuienLlama() {
var goles = 0
sumarGol(a: &goles)
sumarGol(a: &goles)
#expect(goles == 2)
}Punto de control
- Desde
~/Marcador-Swift/Torneo,xcrun swift buildtermina conBuild complete!. - Desde
~/Marcador-Swift/Torneo,xcrun swift run torneo-cliimprimeFecha 1 de 3, los siete titulares (de0-0: Sin golesa1-3: Gana el visitante, con5-1: Goleadaentre ellos) yGoles de Ana: 2, antes del bloque de opcionales. - Desde
~/Marcador-Swift/Torneo,xcrun swift testtermina conTest run with 16 tests in 3 suites passed(ignora la líneaExecuted 0 testsde XCTest): los 11 anteriores (4 dePuntosTestsy 7 deJugadorTests) y los 5 deFuncionesTestsque copiaste. - Sabes decir para qué sirve el
&al llamar a una función coninouty por qué la variable que le pasas tiene que servar.
Comprueba lo que entendiste
5 preguntas · necesitas 4 para completar la lección
1.Tienes
func sumarGol(a marcador: inout Int)yvar goles = 0. ¿Cuál es la llamada correcta?2.Una función se declara
func saludar(a nombre: String, con signo: String = "!"). ¿Cuál de estas llamadas es válida?3.Tienes
func reponer(_ existencias: inout Int)y escribeslet cajas = 10yreponer(&cajas). ¿Qué pasa?4.Dentro de
func f(_ n: Int), escribesn += 1. ¿Qué pasa?5.En Kotlin escribías
encabezadoDeJornada(numero = 1, totalDeJornadas = 3). ¿Qué diferencia tiene la llamada en Swift,encabezadoDeJornada(numero: 1, de: 3)?