Lección 5 · Primeros pasos
Salida y entrada con fmt
Desde la primera lección usas fmt.Println para imprimir. Al terminar esta lección vas a saber qué
hace, vas a poder darle formato a la salida con fmt.Printf, por ejemplo un precio con dos decimales, y
vas a leer datos de la entrada con fmt.Scan. También vas a saber leer los avisos que imprime fmt
cuando un formato no calza con sus valores.
Println: espacios y salto de línea
La Papelería Central imprime un recibo sencillo:
package main
import "fmt"
func main() {
producto := "cuaderno"
cantidad := 3
fmt.Println("Papelería Central")
fmt.Println(producto, cantidad, "unidades")
fmt.Println()
fmt.Println("¡Gracias por tu compra!")
}Salida:
Papelería Central cuaderno 3 unidades ¡Gracias por tu compra!
fmt.Println hace dos cosas. Primero imprime los valores que le pasas, en orden y con un espacio entre
uno y otro: cuaderno, 3 y unidades salen como cuaderno 3 unidades. Después termina la línea, así
que lo siguiente que imprimas empieza en la línea de abajo.
fmt.Println(), sin valores, solo termina la línea: por eso aparece una línea vacía antes del saludo. El
nombre lo dice: print line, “imprimir una línea”.
Println acepta valores de cualquier tipo, mezclados: strings, int, float64 o bool. Cada uno sale
en su formato por defecto, el más simple: 3, 2.5, true.
Print y el salto de línea \n
fmt.Print imprime sin terminar la línea:
package main
import "fmt"
func main() {
fmt.Print("Papelería ")
fmt.Print("Central")
fmt.Print("\n")
fmt.Print("Cuadernos: ", 24, "\n")
fmt.Print(24, 36, "\n")
}Salida:
Papelería Central Cuadernos: 24 24 36
El segundo Print sigue en la misma línea que el primero: Papelería Central. Para pasar a la línea
siguiente, el programa imprime "\n". Dentro de un string, \n (una barra invertida y una n) es el
salto de línea: se escribe con dos caracteres, pero Go lo guarda como uno solo, el que termina la
línea. Funciona en cualquier texto entre comillas dobles, también con Println.
Print tampoco separa siempre con espacios: agrega uno solo entre dos valores que no son strings, como
24 y 36. En Cuadernos: 24, el espacio está dentro del string "Cuadernos: ".
La regla: Println separa con espacios y termina la línea; Print no termina la línea y casi no pone espacios. Por eso Println es el que más vas a usar de los dos.
Printf: un formato con huecos
Cuando quieres decidir exactamente cómo se ve una línea, usa fmt.Printf:
package main
import "fmt"
func main() {
producto := "cuaderno"
stock := 24
precio := 2.5
fmt.Printf("%s: %d unidades\n", producto, stock)
fmt.Printf("Precio: %v\n", precio)
}Salida:
cuaderno: 24 unidades Precio: 2.5
La f de Printf es de “formato”. Su primer valor es el formato: un string con huecos. Los valores
que siguen van en esos huecos, en orden. Cada hueco empieza con % y una letra, el verbo, que dice
cómo imprimir el valor que le toca:
%simprime un string: el primer hueco recibeproducto,cuaderno.%dimprime un entero: el segundo hueco recibestock,24.%vimprime cualquier valor en su formato por defecto, el mismo que usa Println:preciosale como2.5.
El resto del formato sale tal cual: los dos puntos, los espacios y la palabra unidades.
Printf no termina la línea: si no escribes \n al final del formato, la línea siguiente sale pegada.
package main
import "fmt"
func main() {
fmt.Printf("Cuadernos: %d", 24)
fmt.Printf("Lápices: %d", 36)
}Salida:
Cuadernos: 24Lápices: 36
Es el olvido más común con Printf. Si en un ejercicio dos líneas de tu salida aparecen juntas, revisa que
cada formato termine en \n.
Si vienes de JavaScript, Python o Java…
Go no tiene interpolación de strings: no hay nada como las f-strings de Python ni las plantillas entre comillas invertidas de JavaScript.
print(f"{producto}: {stock} unidades")En Go, esa línea se escribe con un formato y sus valores:
fmt.Printf("%s: %d unidades\n", producto, stock)Si vienes de Java, ya conoces los verbos: System.out.printf usa %s, %d y %.2f igual. Lo nuevo es
%v, que sirve para un valor de cualquier tipo.
Decimales con %f y %.2f
Un paquete de 3 cuadernos cuesta 12.5. ¿Cuánto cuesta cada cuaderno?
package main
import "fmt"
func main() {
precio := 2.5
porCuaderno := 12.5 / 3
fmt.Println(precio, porCuaderno)
fmt.Printf("%f\n", precio)
fmt.Printf("%.2f %.2f\n", precio, porCuaderno)
fmt.Printf("%.0f\n", porCuaderno)
}Salida:
2.5 4.166666666666667 2.500000 2.50 4.17 4
Println muestra un float64 con los decimales que hacen falta: uno para 2.5 y quince para
4.166666666666667. Para un precio, ninguna de las dos formas sirve. Con Printf eliges cuántos
decimales salen:
%fimprime unfloat64siempre con seis decimales:2.500000.%.2fimprime dos. El.2entre el%y lafes la precisión: cuántos decimales. El último se redondea:4.1666…sale como4.17.%.0fno imprime decimales, y también redondea:4.
Un detalle del redondeo: 2.675 parece estar justo en la mitad, pero %.2f da 2.67 y no 2.68.
Es lo que viste con 0.1 en la lección Constantes e iota: el float64 guarda un
número apenas menor, y ese número se redondea hacia abajo.
Números grandes: 1.25e+06
Con un float64 grande, Println cambia de forma:
package main
import "fmt"
func main() {
ventas := 1250000.0
fmt.Println("Ventas del año:", ventas)
fmt.Printf("Ventas del año: %.2f\n", ventas)
}Salida:
Ventas del año: 1.25e+06 Ventas del año: 1250000.00
1.25e+06 es notación científica: e+06 significa “multiplica por 10 seis veces”, es decir, corre
el punto seis lugares a la derecha. 1.25 se vuelve 1250000. Println y %v la usan con los float64
de un millón o más (también en negativo) y con los muy cercanos a cero, como 0.00001, que sale
1e-05: con e-, el punto se corre hacia la izquierda. Si quieres ver todas las cifras, usa %.2f o
%.0f.
Esa notación también vale en el código: 1e6 es un millón. Como todo número escrito con e, es un
número con decimales, así que meta := 1e6 declara un float64.
Más verbos: %q, %T y %%
Tres verbos más, que vas a usar a menudo:
package main
import "fmt"
func main() {
producto := "lápiz azul"
codigo := ""
descuento := 15
precio := 0.5
fmt.Printf("Producto: %q\n", producto)
fmt.Printf("Código: %q\n", codigo)
fmt.Printf("%T %T %T\n", producto, descuento, precio)
fmt.Printf("Descuento: %d%%\n", descuento)
}Salida:
Producto: "lápiz azul" Código: "" string int float64 Descuento: 15%
%qimprime un string entre comillas dobles (la q es de quote, “comillas” en inglés). Así ves dónde empieza y dónde termina el texto, con sus espacios.codigoes un string vacío: con%qsale"", y con%sno saldría nada.%Timprime el tipo del valor, no el valor:string,intyfloat64. Sirve para comprobar qué tipo dedujo Go con:=.%%imprime un signo%. Como%empieza un verbo, para imprimir el signo se escribe dos veces:%d%%imprime15%.
Un % solo no se imprime: Printf lo toma como el comienzo de un verbo y escribe un aviso %!. Si va
justo antes del \n, el salto de línea queda como si fuera el verbo:
package main
import "fmt"
func main() {
descuento := 15
fmt.Printf("Descuento: %d%\n", descuento)
}Salida:
Descuento: 15%! (MISSING)
El aviso sale partido en dos líneas, porque el “verbo” es el salto de línea. Qué significa MISSING
lo ves en la sección siguiente. La corrección es escribir %%: "Descuento: %d%%\n".
Consejo
Cuando un valor no sale como esperas, imprímelo con %v y, en el mismo Printf, su tipo con %T. Si
es un string, usa %q en lugar de %v: un espacio de más o un texto vacío se ven al instante.
Cuando un verbo no calza
Cada verbo espera un valor, y del tipo adecuado. Si no lo recibe, Printf no se detiene:
package main
import "fmt"
func main() {
producto := "cuaderno"
stock := 24
precio := 2.5
fmt.Printf("Stock: %d\n", producto)
fmt.Printf("%s: %d unidades\n", producto)
fmt.Printf("Stock: %d\n", stock, precio)
}Salida:
Stock: %!d(string=cuaderno) cuaderno: %!d(MISSING) unidades Stock: 24 %!(EXTRA float64=2.5)
El programa compila y termina bien: no es un error de compilación. Go no revisa el formato al compilar.
Printf lo revisa al ejecutar y, cuando algo no calza, en lugar de detener el programa escribe un aviso en
la salida. Los avisos empiezan con %!. Léelos así:
%!d(string=cuaderno): el verbo%drecibió un valor que no le sirve. Entre paréntesis dice cuál: unstringque valecuaderno.%des para enteros; para un string, usa%s.%!d(MISSING): al verbo%dno le llegó ningún valor (missing significa “falta”). El formato tiene dos verbos, y la llamada pasa un solo valor.%!(EXTRA float64=2.5): sobra un valor (extra, “de más”): unfloat64que vale2.5no tiene verbo. Go lo agrega al final, después del\n, por eso sale en una línea aparte.
Para corregirlo, cuenta: un verbo por cada valor, en el mismo orden, y cada verbo del tipo de su valor.
%v acepta cualquier tipo, pero igual necesita un valor.
Consejo
En tu computadora, go vet, una herramienta de Go que busca errores comunes en el código, revisa los
formatos de Printf antes de ejecutar. Para el primer caso dice
fmt.Printf format %d has arg producto of wrong type string: el formato %d recibe producto, que es
de otro tipo, string. Los editores con soporte para Go muestran el mismo aviso mientras escribes.
Sprintf: el texto formateado, sin imprimirlo
A veces no quieres imprimir el texto, sino guardarlo para usarlo después. Para eso está fmt.Sprintf:
package main
import (
"fmt"
"strings"
)
func main() {
producto := "cuaderno"
precio := 2.5
etiqueta := fmt.Sprintf("%s: %.2f", producto, precio)
fmt.Println(etiqueta)
fmt.Println(strings.ToUpper(etiqueta))
}Salida:
cuaderno: 2.50 CUADERNO: 2.50
fmt.Sprintf arma el texto igual que Printf, con el mismo formato y los mismos verbos, pero no lo
imprime: lo devuelve como un string (la S es de string). etiqueta guarda cuaderno: 2.50, y la
usas como cualquier otro string: la imprimes o se la pasas a strings.ToUpper.
El formato no lleva \n: el texto es para guardarlo, y Println ya termina cada línea al imprimirlo. Si
llamas a Sprintf sin guardar lo que devuelve, el programa compila, pero no imprime nada.
Lo que no funciona es guardar en una variable lo que devuelve Printf:
package main
import "fmt"
func main() {
precio := 2.5
etiqueta := fmt.Printf("Precio: %.2f", precio)
fmt.Println(etiqueta)
}Salida:
./main.go:7:14: assignment mismatch: 1 variable but fmt.Printf returns 2 values
Lee el mensaje por partes:
./main.go:7:14es el lugar: archivomain.go, línea 7, columna 14, donde empiezafmt.Printf.assignment mismatchsignifica “la asignación no cuadra”.1 variable but fmt.Printf returns 2 valuessignifica “hay 1 variable, perofmt.Printfdevuelve 2 valores”.
Printf imprime el texto, y lo que devuelve no es ese texto, sino dos valores que casi nunca se usan:
cuánto escribió y un error. Es una función que devuelve varios valores, como
strings.Cut en la lección Variables y valores cero. Si lo que querías era guardar el texto, cambia fmt.Printf por
fmt.Sprintf.
Leer la entrada con fmt.Scan
Hasta ahora, los datos estaban escritos en el código. Un programa también puede recibirlos al ejecutarse, por la entrada: el texto que alguien escribe en la terminal o, en algunos ejercicios, el que el sitio le pasa a tu programa. Este programa lee un producto y su stock:
package main
import "fmt"
func main() {
var producto string
var stock int
fmt.Scan(&producto, &stock)
fmt.Printf("%s: %d unidades\n", producto, stock)
}Entrada:
cuadernos 24
Salida:
cuadernos: 24 unidades
Debajo del código está la entrada que recibió el programa y, después, lo que imprimió.
Primero se declaran las variables, con var y su tipo: empiezan con su valor cero. Después,
fmt.Scan lee la entrada y la separa en palabras. La primera palabra la guarda en producto y la
segunda, en stock, en el orden en que pasas las variables.
Las palabras se separan con espacios o con saltos de línea: para Scan, un salto de línea cuenta como un
espacio. Aquí cuadernos y 24 llegan en dos líneas; con cuadernos 24 en una sola, la salida sería la
misma.
Scan convierte cada palabra al tipo de su variable: stock es un int, así que el texto 24 se guarda
como el entero 24. Si la palabra no es un número, como veinte, Scan no puede convertirla y stock
se queda con su valor cero.
Delante de cada variable va un &. &producto le dice a Scan dónde está la variable, para que pueda
guardar ahí lo que lee. Cómo funciona & lo veremos en la lección “Punteros”; por ahora, en Scan, cada
variable lleva su &.
Sin &, Scan no cambia nada
Si olvidas los &, el programa compila y corre igual, pero no lee nada:
package main
import "fmt"
func main() {
var producto string
var stock int
fmt.Scan(producto, stock)
fmt.Printf("%s: %d unidades\n", producto, stock)
}Entrada:
cuadernos 24
Salida:
: 0 unidades
Sin &, Scan recibe una copia de los valores (el texto vacío y el 0), no las variables, así que no
tiene dónde guardar lo que lee. Las variables se quedan con su valor cero, y no aparece ningún mensaje
de error.
Scan avisa lo que pasó con un error que devuelve, pero por ahora no lo revisamos: lo veremos en la
lección “Errores como valores”. Mientras tanto, si después de leer una variable sigue en su valor cero,
revisa los &.
Un texto con espacios son varias palabras
Ahora el producto es un lápiz azul:
package main
import "fmt"
func main() {
var producto string
var stock int
fmt.Scan(&producto, &stock)
fmt.Printf("%s: %d unidades\n", producto, stock)
}Entrada:
lápiz azul 12
Salida:
lápiz: 0 unidades
Scan guardó lápiz en producto. Después intentó leer la palabra siguiente, azul, como el int de
stock, y no pudo: stock se quedó en 0, y el 12 ni siquiera se leyó.
Con Scan, cada palabra va a una variable. Si un dato tiene espacios, son varias palabras: léelas en
variables separadas, como producto y color. Para leer una línea completa, con sus espacios, la
biblioteca estándar tiene otras herramientas, como bufio.Scanner, que no veremos en este bloque.
En los ejercicios que leen la entrada, el enunciado dice qué datos llegan y en qué orden. El sitio se los pasa a tu programa cuando lo ejecutas.
Si vienes de JavaScript, Python o Java…
En Python, input() lee una línea completa como string, y la conviertes tú: int(input()). El Scanner
de Java se parece más a Scan: nextInt() y next() leen palabras separadas por espacios. En Go, Scan
lee varias palabras de una vez y convierte cada una al tipo de su variable.
En resumen
fmt.Printlnimprime sus valores separados por espacios y termina la línea.fmt.Printno la termina: para eso está\n, el salto de línea dentro de un string.fmt.Printfimprime un formato con verbos:%v(cualquier valor),%d(entero),%s(string),%fy%.2f(decimales),%q(string entre comillas),%T(el tipo) y%%(el signo%). No termina la línea: el formato lleva\n.- Un verbo que no calza no impide compilar: Printf escribe en la salida avisos como
%!d(string=cuaderno),%!d(MISSING)o%!(EXTRA …). fmt.Sprintfarma el mismo texto que Printf, pero lo devuelve en lugar de imprimirlo.fmt.Scan(&producto, &stock)lee palabras separadas por espacios y las guarda en las variables, convertidas a su tipo. Sin&, las variables no cambian.
Ejercicios
Cambia el último fmt.Println por un fmt.Printf que imprima Cuadernos: 24 unidades con las variables producto y stock, debajo de Inventario.
Para salir del editor con el teclado, pulsa Escape y luego Tab.
package main
import "fmt"
func main() {
producto := "Cuadernos"
stock := 24
fmt.Println("Inventario")
// Escribe aquí: cambia esta línea por un fmt.Printf.
fmt.Println(producto, stock)
}