Saltar al contenido

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:

main.go
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.

fmt.Print imprime sin terminar la línea:

main.go
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:

main.go
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:

  • %s imprime un string: el primer hueco recibe producto, cuaderno.
  • %d imprime un entero: el segundo hueco recibe stock, 24.
  • %v imprime cualquier valor en su formato por defecto, el mismo que usa Println: precio sale como 2.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.

main.go
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?

main.go
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:

  • %f imprime un float64 siempre con seis decimales: 2.500000.
  • %.2f imprime dos. El .2 entre el % y la f es la precisión: cuántos decimales. El último se redondea: 4.1666… sale como 4.17.
  • %.0f no 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:

main.go
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:

main.go
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%

  • %q imprime 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. codigo es un string vacío: con %q sale "", y con %s no saldría nada.
  • %T imprime el tipo del valor, no el valor: string, int y float64. Sirve para comprobar qué tipo dedujo Go con :=.
  • %% imprime un signo %. Como % empieza un verbo, para imprimir el signo se escribe dos veces: %d%% imprime 15%.

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:

main.go
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:

main.go
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 %d recibió un valor que no le sirve. Entre paréntesis dice cuál: un string que vale cuaderno. %d es para enteros; para un string, usa %s.
  • %!d(MISSING): al verbo %d no 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”): un float64 que vale 2.5 no 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:

main.go
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:

main.go
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:14 es el lugar: archivo main.go, línea 7, columna 14, donde empieza fmt.Printf.
  • assignment mismatch significa “la asignación no cuadra”.
  • 1 variable but fmt.Printf returns 2 values significa “hay 1 variable, pero fmt.Printf devuelve 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:

main.go
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:

main.go
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:

main.go
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.Println imprime sus valores separados por espacios y termina la línea. fmt.Print no la termina: para eso está \n, el salto de línea dentro de un string.
  • fmt.Printf imprime un formato con verbos: %v (cualquier valor), %d (entero), %s (string), %f y %.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.Sprintf arma 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.

  main.go

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)
}
 

¿Ver la solución?

La solución reemplazará tu código en el editor. Antes de verla, prueba con una pista: se aprende más cuando lo resuelves tú.