Las estructuras de control de Go están relacionadas con las de C pero difieren en aspectos importantes. No hay bucle do o while, sólo un for ligeramente generalizado; switch es más flexible; if y switch aceptan una declaración de inicialización opcional como la de for; las declaraciones break y continue toman una etiqueta opcional para identificar qué interrumpir o continuar; y hay nuevas estructuras de control que incluyen un interruptor tipo y un multiplexor de comunicaciones multidireccional, select. La sintaxis también es ligeramente diferente: no hay paréntesis y los cuerpos siempre deben estar delimitados por llaves.
En Go, un if simple se ve así:
if x > 0 {
return y
}
Las llaves obligatorias alientan a escribir declaraciones if simples en varias líneas. Es un buen estilo hacerlo de todos modos, especialmente cuando el cuerpo contiene una declaración de control como return o break.
Dado que if y switch aceptan una declaración de inicialización, es común ver una utilizada para configurar una variable local.
if err := file.Chmod(0664); err != nil {
log.Print(err)
return err
}
En las bibliotecas Go, encontrará que cuando una declaración if no fluye hacia la siguiente declaración (es decir, el cuerpo termina en break, continue, goto o return), se omite el else innecesario.
f, err := os.Open(name)
if err != nil {
return err
}
codeUsing(f)
Este es un ejemplo de una situación común en la que el código debe protegerse contra una secuencia de condiciones de error. El código se lee bien si el flujo de control exitoso recorre la página, eliminando los casos de error a medida que surgen. Dado que los casos de error tienden a terminar en declaraciones de devolución, el código resultante no necesita else.
f, err := os.Open(name)
if err != nil {
return err
}
d, err := f.Stat()
if err != nil {
f.Close()
return err
}
codeUsing(f, d)
El último ejemplo demuestra un detalle, cómo funciona las declaración corta :=. La declaración que llama a os.Open dice:
f, err := os.Open(name)
Esta declaración declara dos variables, f y err. Unas líneas más tarde, la llamada a f.Stat dice:
d, err := f.Stat()
que parece como si declarara d y err. Observe, sin embargo, que err aparece en ambas afirmaciones. Esta duplicación es legal: err se declara en la primera declaración, pero solo se reasigna en la segunda. Esto significa que la llamada a f.Stat utiliza la variable err existente declarada anteriormente y simplemente le da un nuevo valor.
En una declaración := puede aparecer una variable v incluso si ya ha sido declarada, siempre que:
- esta declaración está en el mismo alcance que la declaración existente de v (si v ya está declarado en un alcance externo, la declaración creará una nueva variable),
- el valor correspondiente en la inicialización es asignable a v, y
- hay al menos otra variable creada por la declaración.
Esta propiedad inusual es puro pragmatismo, lo que facilita el uso de un único valor de error, por ejemplo, en una larga cadena if-else. Verás que se usa con frecuencia.
Vale la pena señalar aquí que en Go el alcance de los parámetros de la función y los valores de retorno es el mismo que el del cuerpo de la función, aunque aparecen léxicamente fuera de las llaves que encierran el cuerpo.
El bucle for es similar, pero no igual, al de C. Unifica for ,while y do-while. Hay tres formas, de las cuales sólo una tiene punto y coma.
// Like a C for
for init; condition; post { }
// Like a C while
for condition { }
// Like a C for(;;)
for { }
Las declaraciones breves facilitan la declaración de la variable de índice directamente en el bucle.
sum := 0
for i := 0; i < 10; i++ {
sum += i
}
Si está realizando un bucle sobre una matriz, un segmento, una cadena o un mapa, o leyendo desde un canal, una cláusula de rango puede gestionar el bucle.
for key, value := range oldMap {
newMap[key] = value
}
Si solo necesita el primer elemento del rango (la clave o índice), elimine el segundo:
for key := range m {
if key.expired() {
delete(m, key)
}
}
Si solo necesita el segundo elemento del rango (el valor), utilice el identificador en blanco, un guión bajo, para descartar el primero:
sum := 0
for _, value := range array {
sum += value
}
Para las cadenas, range , desglosa en caracteres código Unicode individuales analizando el UTF-8. Las codificaciones erróneas consumen un byte y producen la runa de reemplazo U+FFFD.
for pos, char := range "日本\x80語" { // \x80 is an illegal UTF-8 encoding
fmt.Printf("character %#U starts at byte position %d\n", char, pos)
}
esto imprime:
character U+65E5 '日' starts at byte position 0
character U+672C '本' starts at byte position 3
character U+FFFD '�' starts at byte position 6
character U+8A9E '語' starts at byte position 7
Finalmente, Go no tiene operador de coma y ++ y -- son declaraciones, no expresiones. Por lo tanto, si desea ejecutar múltiples variables en un for, debe usar la asignación paralela.
// Reverse a
for i, j := 0, len(a)-1; i < j; i, j = i+1, j-1 {
a[i], a[j] = a[j], a[i]
}
El switch de Go es más general que el de C. No es necesario que las expresiones sean constantes o incluso números enteros, los casos se evalúan de arriba a abajo hasta que se encuentra una coincidencia y, si el interruptor no tiene expresión, se activa como verdadero. Por lo tanto, es posible (e idiomático) escribir una cadena if-else-if-else como un switch.
func unhex(c byte) byte {
switch {
case '0' <= c && c <= '9':
return c - '0'
case 'a' <= c && c <= 'f':
return c - 'a' + 10
case 'A' <= c && c <= 'F':
return c - 'A' + 10
}
return 0
}
No hay clasificación automática, pero los casos se pueden presentar en listas separadas por comas.
func shouldEscape(c byte) bool {
switch c {
case ' ', '?', '&', '=', '#', '+', '%':
return true
}
return false
}
Aunque no son tan comunes en Go como en otros lenguajes similares a C, las declaraciones de break se pueden utilizar para finalizar un caso. A veces, sin embargo, es necesario salir de un bucle circundante, no del interruptor, y en Go eso se puede lograr colocando una etiqueta en el bucle y "rompiendo" esa etiqueta. Este ejemplo muestra ambos usos.
Loop:
for n := 0; n < len(src); n += size {
switch {
case src[n] < sizeOne:
if validateOnly {
break
}
size = 1
update(src[n])
case src[n] < sizeTwo:
if n+1 >= len(src) {
err = errShortInput
break Loop
}
if validateOnly {
break
}
size = 2
update(src[n] + src[n+1]<<shift)
}
}
Por supuesto, la instrucción continue también acepta una etiqueta opcional pero se aplica sólo a los bucles.
Aquí hay una rutina de comparación para segmentos de bytes que utiliza dos declaraciones de cambio:
// Comparar devuelve un número entero que compara los dos segmentos de bytes,
// lexicográficamente.
// El resultado será 0 si a == b, -1 si a < b, y +1 si a > b
func Compare(a, b []byte) int {
for i := 0; i < len(a) && i < len(b); i++ {
switch {
case a[i] > b[i]:
return 1
case a[i] < b[i]:
return -1
}
}
switch {
case len(a) > len(b):
return 1
case len(a) < len(b):
return -1
}
return 0
}
También se puede utilizar un modificador para descubrir el tipo dinámico de una variable de interfaz. Un cambio de tipo de este tipo utiliza la sintaxis de una aserción de tipo con la palabra clave tipo entre paréntesis. Si el modificador declara una variable en la expresión, la variable tendrá el tipo correspondiente en cada cláusula. También es idiomático reutilizar el nombre en tales casos, declarando de hecho una nueva variable con el mismo nombre pero de un tipo diferente en cada caso.
var t interface{}
t = functionOfSomeType()
switch t := t.(type) {
default:
fmt.Printf("unexpected type %T\n", t) // %T prints whatever type t has
case bool:
fmt.Printf("boolean %t\n", t) // t has type bool
case int:
fmt.Printf("integer %d\n", t) // t has type int
case *bool:
fmt.Printf("pointer to boolean %t\n", *t) // t has type *bool
case *int:
fmt.Printf("pointer to integer %d\n", *t) // t has type *int
}