Especificación del lenguaje de programación GO
Versión del 20 de Marzo, 2015
Puedes ver el documento original aquí
- Introducción
- Notación
- Representación de código fuente
- Elementos léxicos
- Constantes
- Variables
- Tipos
- Propiedades de tipos y valores
- Bloques
- Declaraciones y ámbito
- Expresiones
- Operandos
- Identificadores cualificados
- Literales compuestos
- Funciones literales
- Expresiones primarias
- Selectores
- Expresiones método
- Valores método
- Expresiones índice
- Expresiones sector
- Tipos aserción
- Llamadas
- Pasando argumentos a parámetros
...
- Operadores
- Precedencia de operadores
- Operadores aritméticos
- Desbordamiento de enteros
- Operadores de comparación
- Operadores lógicos
- Operadores de dirección
- Operador de recepción
- Conversiones
- Expresiones constantes
- Orden de evaluación
- Instrucciones
- Instrucción de terminación
- Instrucciones vacías
- Instrucciones etiquetadas
- Expresiones instrucción
- Instrucciones de envío
- Instrucciones IncDec
- Asignaciones
- Instrucciones
if
- Instrucciones
switch
- Instrucciones
for
- Instrucciones
go
- Instrucciones
select
- Instrucciones
return
- Instrucciones
break
- Instrucciones
continue
- Instrucciones
goto
- Instrucciones
fallthrough
- Instrucciones
defer
- Funciones integradas
- Paquetes
- Iniciación y ejecución de programa
- Errores
- Pánicos en tiempo de ejecución
- Consideraciones del sistema
Introducción
Este es el manual de referencia para el lenguaje de programación Go. Para más información y otros documentos, ve [golang.org][golang].
Go es un lenguaje de propósito general diseñado con la programación de sistemas en mente. Es fuertemente tipado, cuenta con su propio recolector de basura y apoyo explícito a la programación concurrente. Los programas se construyen a partir de paquetes, cuyas propiedades permiten una eficiente gestión de las dependencias. Las implementaciones existentes utilizan el tradicional modelo de compilación/enlace para generar los binarios ejecutables.
La gramática es compacta y regular, lo que facilita el análisis de las herramientas automáticas tal como los entornos de desarrollo integrado.
Notación
La sintaxis se especifica utilizando la Forma Backus-Naur Extendida (EBNF por Extended Backus-Naur Form):
Producción = nombre_producción "=" [ Expresión ] "." .
Expresión = Alternativa { "|" Alternativa } .
Alternativa = Término { Término } .
Término = nombre_producción | símbolo [ "…" símbolo ] | Grupo | Opción
| Repetición .
Grupo = "(" Expresión ")" .
Opción = "[" Expresión "]" .
Repetición = "{" Expresión "}" .
Las producciones son expresiones construidas a partir de términos y los siguientes operadores, en orden incremental de precedencia:
| alternancia
() agrupación
[] opción (0 o 1 veces)
{} repetición (de 0 a n veces)
Los nombres de producción en minúsculas se usan para identificar símbolos léxicos. Los no terminales están en MayúsculasIntercaladas. Los símbolos léxicos se encierran entre comillas dobles "" o comillas inversas ``.
La forma a … b
representa el conjunto de caracteres de a
hasta b
como alternativas. Los puntos suspensivos horizontales …
también se
utilizan en otras partes de la especificación para denotar informalmente
diversas enumeraciones o fragmentos de código que no se especifican
más. El carácter …
(a diferencia de los tres caracteres ...
) no es un
símbolo del lenguaje Go.
Representación de código fuente
El código fuente es texto Unicode codificado en UTF-8. El texto no se canoniza, por lo que un único carácter acentuado es distinto del mismo carácter construido a partir de la combinación de un acento y una letra; estos son tratados como dos caracteres. Para simplificar, en este documento se utilizará el término no calificado carácter para referirse a un carácter Unicode en el texto fuente.
Cada carácter es distinto; por ejemplo, las letras mayúsculas y minúsculas son caracteres diferentes.
Restricción de implementación: Por compatibilidad con otras herramientas, un compilador puede rechazar el carácter NUL (U+0000) en el texto fuente.
Restricción de implementación: Por compatibilidad con otras herramientas, un compilador puede ignorar un byte de marca de orden (U+FEFF) codificado en UTF-8 si es el primer carácter Unicode en el texto fuente. Un byte de marca de orden se puede rechazar en cualquier otro lugar en la fuente.
Caracteres
Los siguientes términos se utilizan para referirse a clases específicas de caracteres Unicode:
nuevalínea = /* el carácter Unicode U+000A */ .
carácter_unicode = /* un carácter Unicode arbitrario excepto nuevalínea */ .
letra_unicode = /* un carácter Unicode clasificado como "Letra" */ .
dígito_unicode = /* un carácter Unicode clasificado como "Dígito decimal" */ .
El Estándar Unicode 7.0, en la Sección 4.5 "Categoría general" define un conjunto de categorías de caracteres. Go trata los caracteres en la categoría Lu, Ll, Lt, Lm o Lo como letras Unicode y los de la categoría Nd como dígitos Unicode.
Letras y dígitos
El carácter de subrayado _
(U+005F) se considera una letra.
letra = letra_unicode | "_" .
dígito_decimal = "0" … "9" .
dígito_octal = "0" … "7" .
dígito_hex = "0" … "9" | "A" … "F" | "a" … "f" .
Elementos léxicos
Comentarios
Hay dos formas de comentarios:
- Comentarios de una línea comienzan con la secuencia de caracteres
//
y terminan al final de la línea. Una línea de comentario actúa como una nuevalínea. - Comentarios generales comienzan con la secuencia de caracteres
/*
y continúan hasta alcanzar la secuencia de caracteres*/
. Un comentario general que contiene uno o más saltos de línea actúa como un salto de línea, de lo contrario, actúa como un espacio.
Los comentarios no se anidan.
Símbolos
Los símbolos forman el vocabulario del lenguaje Go. Hay cuatro clases: identificadores, palabras clave, operadores/delimitadores y literales. El espacio en blanco, formado a partir de espacios (U+0020), tabuladores horizontales (U+0009), retornos de carro (U+000D) y saltos de línea (U+000A), se ignoran salvo los que separan a los símbolos que de otro modo se combinarían en un único símbolo. Además, un salto de línea o el final del archivo pueden desencadenar la inserción de un punto y coma. Aunque la entrada se divide en símbolos, el siguiente símbolo es la secuencia de caracteres más larga que forma un símbolo válido.
Puntos y comas
La gramática formal utiliza un punto y coma ";"
como terminador en una
serie de resultados. Los programas Go pueden omitir la mayoría de esos
puntos y comas utilizando las dos siguientes reglas:
Cuando la entrada se divide en símbolos, automáticamente se inserta un punto y coma en la cadena de componentes léxicos al final de una línea que no esté en blanco si el símbolo final de la línea es:
- un identificador
- un entero,
número de coma flotante,
[número imaginario](#imaginarios-literales), [rune](#rune-literales) o una [cadena de caracteres](#cadenas-literales) literal
- una de las palabras clave
break
,continue
,fallthrough
oreturn
- uno de los operadores y delimitadores
++
,--
,)
,]
o}
- Para permitir que las declaraciones complejas ocupen una sola línea, se
puede omitir un punto y coma antes de un
")"
o una"}"
de cierre.
Para reflejar el uso idiomático, el código de los ejemplos de este documento omite el punto y coma utilizando estas reglas.
Identificadores
Los identificadores nombran entidades de programas tal como variables y tipos. Un identificador es una secuencia de una o más letras y dígitos. El primer carácter de un identificador debe ser una letra.
identificador = letra { letra | dígito_unicode } .
a
_x9
EstaVariableEsExportada
αβ
Algunos identificadores son predeclarados.
Palabras clave
Las siguientes palabras clave están reservadas y no se pueden utilizar como identificadores.
break default func interface select
case defer go map struct
chan else goto package switch
const fallthrough if range type
continue for import return var
Operadores y delimitadores
Las siguientes secuencias de caracteres representan operadores, delimitadores y otros símbolos especiales:
+ & += &= && == != ( )
- | -= |= || < <= [ ]
* ^ *= ^= <- > >= { }
/ << /= <<= ++ = := , ;
% >> %= >>= -- ! ... . :
&^ &^=
Enteros literales
Un entero literal es una secuencia de dígitos que representa una
constante entera. Un prefijo opcional establece una base
no decimal: 0
para octal, 0x
o 0X
para hexadecimal. En
hexadecimales literales, las letras a-f
y A-F
representan valores del
10 al 15.
ent_lit = decimal_lit | octal_lit | hex_lit .
decimal_lit = ( "1" … "9" ) { dígito_decimal } .
octal_lit = "0" { dígito_octal } .
hex_lit = "0" ( "x" | "X" ) dígito_hex { dígito_hex } .
42
0600
0xBadFace
170141183460469231731687303715884105727
Números de coma flotante literales
Un número de coma flotante literal es una representación decimal de una
constante de coma flotante. Tiene una parte entera, un
punto decimal, una parte fraccional y una parte exponente. Las partes
entera y fraccional comprenden los dígitos decimales; la parte del
exponente es una e
o E
seguida de un exponente decimal opcionalmente
con signo. Se puede omitir la parte entera o la parte fraccional; o bien
se puede omitir el punto decimal o el exponente.
comaflotante_lit = decimales "." [ decimales ] [ exponente ] |
exponente decimal |
"." decimales [ exponente ] .
decimales = dígito_decimal { dígito_decimal } .
exponente = ( "e" | "E" ) [ "+" | "-" ] decimales .
0.
72.40
072.40 // == 72.40
2.71828
1.e+0
6.67428e-11
1E6
.25
.12345E+5
Imaginarios literales
Un imaginario literal es una representación decimal de la parte imaginaria
de una constante compleja. Consiste de un
número de coma flotante literal o
un entero decimal seguido de la letra i
minúscula.
imaginario_lit = (decimales | comaflotante_lit) "i" .
0i
011i // == 11i
0.i
2.71828i
1.e+0i
6.67428e-11i
1E6i
.25i
.12345E+5i
Rune literales
Un rune literal representa una constante rune, un valor entero que identifica un carácter Unicode. Un rune literal se expresa como uno o más caracteres entre comillas simples. Dentro de las comillas, puede aparecer cualquier carácter excepto la comilla simple y el salto de línea. Un solo carácter entrecomillado representa el valor Unicode del carácter en sí mismo, mientras que las secuencias de varios caracteres que comienzan con una barra inversa codifican valores en varios formatos.
La forma más simple representa el único carácter dentro de las comillas;
debido a que el texto fuente de Go son caracteres Unicode codificados en
UTF-8, múltiples bytes codificados en UTF-8 pueden representar un único
valor entero. Por ejemplo, la 'a'
literal contiene un solo byte que
representa una a
literal, el valor Unicode U+0061, el valor 0x61
,
mientras que 'ä'
tiene dos bytes (0xc3
0xa4
) que representan una
a
-dieresis literal, U+00E4, el valor 0xe4
.
Varias barras inversas permiten escapar valores arbitrarios para que sean
codificados como texto ASCII. Hay cuatro formas de representar el valor
entero como una constante numérica: \x
seguida de exactamente dos
dígitos hexadecimales; \u
seguida de exactamente cuatro dígitos
hexadecimales, \U
seguida de exactamente ocho dígitos hexadecimales y
una barra inversa \
seguida exactamente por tres dígitos octales. En
cada caso el valor del literal es el valor representado por los dígitos en
la base correspondiente.
Aunque todas estas representaciones resultan en un número entero, tienen
diferentes rangos válidos. Los escapes octales deben representar un valor
entre 0 y 255 inclusive. Los escapes hexadecimales satisfacen esta
condición por su construcción. Los escapes \u
y \U
representan
caracteres Unicode de modo que dentro de ellos algunos valores son
ilegales, en particular los de arriba de 0x10FFFF
y mitades sucedáneas.
Después de una barra inversa, algunos escapes de un solo carácter representan valores especiales:
\a U+0007 alerta o campana
\b U+0008 retroceso
\f U+000C avance de página
\n U+000A avance de línea o nueva línea
\r U+000D retorno de carro
\t U+0009 tabulación horizontal
\v U+000b tabulación vertical
\\ U+005c barra inversa
\' U+0027 comilla simple (escape válido solo dentro de runes literales)
\" U+0022 comilla doble (escape válido solo dentro de cadenas literales)
Todas las demás secuencias que comienzan con una barra inversa son literales ilegales dentro de rune.
rune_lit = "'" ( valor_unicode | valor_byte ) "'" .
valor_unicode = carácter_unicode | valor_u_bajo | valor_u_alto | carácter_escapado .
valor_byte = byte_valor_octal | byte_valor_hex .
byte_valor_octal = `\` dígito_octal dígito_octal dígito_octal .
byte_valor_hex = `\` "x" dígito_hex dígito_hex .
valor_u_bajo = `\` "u" dígito_hex dígito_hex dígito_hex dígito_hex .
valor_u_alto = `\` "U" dígito_hex dígito_hex dígito_hex dígito_hex
dígito_hex dígito_hex dígito_hex dígito_hex .
carácter_escapado = `\` ( "a" | "b" | "f" | "n" | "r" | "t" | "v" | `\` | "'" | `"` ) .
'a'
'ä'
'本'
'\t'
'\000'
'\007'
'\377'
'\x07'
'\xff'
'\u12e4'
'\U00101234'
'aa' // ilegal: demasiados caracteres
'\xa' // ilegal: muy pocos dígitos hexadecimales
'\0' // ilegal: muy pocos dígitos octales
'\uDFFF' // ilegal: medio sustituto
'\U00110000' // ilegal: carácter Unicode no válido
Cadenas literales
Una cadena literal representa una cadena constante obtenida a partir de la concatenación de una secuencia de caracteres. Hay dos formas: cadenas literales crudas y cadenas literales interpretadas. Las cadenas literales crudas son secuencias de caracteres entre comillas inversas ``. Dentro de las comillas, es lícito cualquier carácter excepto la comilla inversa. El valor de una cadena de texto literal es la cadena compuesta por los caracteres no interpretados (codificados implícitamente en UTF-8) entre comillas; en particular, las barras inversas no tienen un significado especial y la cadena puede contener saltos de línea. Los caracteres de retorno de carro ('\r') dentro de las cadenas literales se descartan del valor de la cadena cruda.
Las cadenas literales interpretadas son secuencias de caracteres entre
comillas dobles ""
. El texto entre las comillas, que por
cierto, no puede contener saltos de línea, forma el valor literal, con
barras inversas escapa caracteres interpretados como son los
rune literales (excepto que \'
es ilegal y \"
es
legal), con las mismas restricciones. Los tres dígitos octales (\
nnn)
y dos dígitos hexadecimales (\x
nn) representan bytes de escape
individuales de la cadena resultante; todos los demás escapes representan
la (posiblemente multi-byte) codificación UTF-8 de caracteres
individuales. De este modo dentro de una cadena literal \377
y \xFF
representan un solo byte de valor 0xFF
=255, mientras ÿ
, \u00FF
,
\U000000FF
y \xc3\xbf
representan los dos bytes 0xc3
y 0xbf
de la
codificación UTF-8 del carácter U+00FF.
cadena_lit = cadena_lit_cruda | cadena_lit_interpretada .
cadena_lit_cruda = "`" { carácter_unicode | nuevalínea } "`" .
cadena_lit_interpretada = `"` { valor_unicode | valor_byte } `"` .
`abc` // misma que "abc"
`\n
\n` // misma que "\\n\n\\n"
"\n"
""
"¡Hola, mundo!\n"
"日本語"
"\u65e5本\U00008a9e"
"\xff\u00FF"
"\uD800" // ilegal: medio sustituto
"\U00110000" // ilegal: carácter Unicode no válido
Todos estos ejemplos representan la misma cadena:
"日本語" // UTF-8 texto ingresado
`日本語` // UTF-8 texto ingresado como un
// literal crudo
"\u65e5\u672c\u8a9e" // los caracteres Unicode explicitos
"\U000065e5\U0000672c\U00008a9e" // los caracteres Unicode explícitos
"\xe6\x97\xa5\xe6\x9c\xac\xe8\xaa\x9e" // los bytes UTF-8 explícitos
Si el código fuente representa un carácter como dos caracteres, tal como implica una forma de combinar un acento y una letra, el resultado será un error si lo colocas en un rune literal (no es un único carácter) y aparecerá como dos caracteres si se coloca en una cadena literal.
Constantes
Hay constantes lógicas, constantes rune, constantes enteras, constantes de coma flotante, constantes complejas y constantes de cadenas de caracteres. Las constantes rune, enteras, de coma flotante y complejas se denominan colectivamente constantes numéricas.
Un valor constante está representado por un rune,
número entero, de
coma flotante,
imaginario o cadena
literal, un identificador denotando una constante, una
expresión constante, una
conversión con un resultado que es una constante o el
valor resultante de alguna de las funciones integradas tal como
unsafe.Sizeof
aplicada a cualquier valor, cap
o len
aplicada a
algunas expresiones, real
e imag
aplicada a
una constante compleja y complex
aplicada a constantes numéricas. Los
valores lógicos de verdad están representados por las constantes
predeclaradas true
y false
. El identificador predeclarado iota
denota una constante entera.
En general, las constantes complejas son una forma de expresión constante y se explican en esa sección.
Las constantes numéricas representan valores de precisión arbitraria y no se desbordan.
Las constantes pueden ser tipadas o sin tipo. Las constantes
literales, true
, false
, iota
y ciertas
expresiones constantes conteniendo solo
operandos constantes sin tipo son sin tipo.
A una constante se le puede dar un tipo explícitamente por medio de una
declaración de constante o
conversión o implícitamente cuando se utiliza en una
declaración de variable o una
asignación o como un operando en una
expresión. Es un error si el valor constante no se puede
representar como un valor del tipo respectivo. Por ejemplo, a 3.0
se le
puede dar cualquier tipo numérico tal como entero o de coma flotante,
mientras que a 2147483648.0
(igual a 1<<31
) se le pueden dar los
tipos float32
, float64
o Uint32
pero no int32
o string
.
Una constante sin tipo tiene un tipo predeterminado, que es el tipo al
que la constante se convierte implícitamente en contextos donde se
requiere un valor tipado, por ejemplo, en una
declaración corta de variable tal como
i := 0
, donde no hay ningún tipo explícito. El tipo predeterminado de
una constante sin tipo es bool
, rune
, int
, float64
, complex128
o
cadena
, respectivamente, dependiendo de si se trata de un valor lógico,
rune, número entero, de coma flotante, complejo o cadena constante.
No hay constantes que denoten los valores infinitos IEEE-754 ni
not-a-number
, pero las funciones Inf,
NaN, IsInf e
IsNaN del paquete math
devuelven y
prueban estos valores en tiempo de ejecución.
Restricción de implementación: A pesar de que en el lenguaje las constantes numéricas tienen precisión arbitraria, un compilador la puede implementar usando una representación interna con precisión limitada. Dicho esto, cada implementación debe:
- Representar constantes enteras con al menos 256 bits.
- Representar constantes de coma flotante, incluyendo las partes de una constante compleja, con una mantisa de al menos 256 bits y un exponente con signo de al menos 32 bits.
- Dar un error si no puede representar con precisión una constante entera.
- Dar un error si no puede representar una constante de coma flotante o compleja debido a desbordamiento.
- Redondear a la constante representable más cercana si no puede representar una constante de coma flotante o compleja debido a los límites de precisión.
Estos requisitos se aplican tanto a las constantes literales como al resultado de la evaluación de expresiones constantes.
Variables
Una variable es una ubicación de almacenamiento para contener un valor. El conjunto de valores permisibles lo determina el tipo de la variable.
Una declaración de variable o, para los
parámetros de función y resultados, la firma de una
declaración de función o
función literal reserva almacenamiento para una
variable nombrada. Llamar a la función incorporada new
o
tomar la dirección de un literal compuesto
reserva almacenamiento para una variable en tiempo de ejecución. Para
referirse a tal variable anónima se usa una (posiblemente implícita)
indirección de puntero.
Las variables estructuradas de tipo arreglo, sector y estructura tienen elementos y campos que se pueden encontrar individualmente. Cada uno de esos elemento actúa como una variable.
El tipo estático (o simplemente tipo) de una variable es el tipo
determinado en su declaración, el tipo provisto en la llamada a new
o al
literal compuesto, o el tipo de un elemento de una variable estructurada.
Las variables de tipo interfaz también tienen un tipo dinámico distinto,
que es el tipo concreto del valor asignado a la variable en tiempo de
ejecución (a menos que el valor sea el identificador predeclarado nil
,
mismo que no tiene tipo). El tipo dinámico puede variar durante la
ejecución, pero los valores almacenados en variables interfaz siempre son
asignables al tipo estático de la variable.
var x interface{} // x es nil y tiene una interfaz de tipo estático
var v *T // v tiene un valor nil, tipo estático *T
x = 42 // x tiene valor 42 y el tipo dinámico int
x = v // x tiene un valor (*T)(nil) y el tipo dinámico *T
El valor de una variable se recupera haciendo referencia a la variable en una expresión; este es el valor más reciente asignado a la variable. Si una variable aún no tiene asignado un valor, su valor es el valor cero para su tipo.
Tipos
Un tipo determina el conjunto de valores y operaciones específicas para los valores de ese tipo. Los tipos podrán ser nombrados o anónimos. Los tipos con nombre se especifican mediante una (posiblemente cualificada) declaración type nombre; los tipos anónimos se especifican usando un tipo literal, que compone un nuevo tipo de los tipos existentes.
Tipo = NombreTipo | TipoLit | "(" Tipo ")" .
NombreTipo = identificador | IdentiCualificado .
TipoLit = TipoArreglo | TipoEstructura | TipoPuntero | TipoFunción | TipoInterfaz |
TipoSector | TipoMapa | TipoCanal .
Las instancias nombradas de tipos lógicos, numéricos y cadena son predeclaradas. Los tipos compuestos —arreglo, estructura, puntero, función, interfaz, sector, mapa y canal— se pueden construir mediante tipos literales.
Cada tipo T
tiene un tipo subyacente: Si T
es uno de los tipos
predeclarados lógico, numérico, cadena o un tipo literal, el
correspondiente tipo subyacente es T
en sí mismo. De lo contrario, el
tipo T
subyacente es el tipo subyacente del tipo al que T
se refiere
en su declaración de tipo.
type T1 string
type T2 T1
type T3 []T1
type T4 T3
El tipo subyacente de string
, T1
y T2
es string
. El tipo
subyacente de []T1
, T3
y T4
es []T1
.
Conjunto de métodos
Un tipo puede tener un conjunto de métodos asociado a él. El conjunto
de métodos de un tipo interfaz es su interfaz. El
conjunto de métodos de cualquier otro tipo T
se compone de todos los
métodos declarados con el tipo receptor T
. El
conjunto de métodos del puntero al tipo correspondiente
a *T
es el conjunto de todos los métodos declarados con el receptor *T
o T
(es decir, que también contiene el conjunto de métodos de T
). Las
nuevas reglas se aplican a estructuras que contienen campos anónimos, como
se describe en la sección sobre tipos estructura.
Cualquier otro tipo tiene un conjunto de métodos vacío. En un conjunto de
métodos, cada método debe tener un
nombre de método único no
blanco.
El conjunto de métodos de un tipo determina las interfaces que el tipo implementa y los métodos que se pueden llamar mediante un receptor de ese tipo.
Tipos lógicos
Un tipo lógico representa el conjunto de valores lógicos de verdad
indicados por las constantes predeclaradas true
y false
. El tipo
lógico predeclarado es bool
.
Tipos numéricos
Un tipo numérico representa conjuntos de valores enteros o de coma flotante. Los tipos numéricos predeclarados independientes de la arquitectura son:
uint8 el conjunto de todos los enteros sin signo de 8 bits (0 a 255)
uint16 el conjunto de todos los enteros sin signo de 16 bits (0 a
65535)
uint32 el conjunto de todos los enteros sin signo de 32 bits (0 a
4294967295)
uint64 el conjunto de todos los enteros sin signo de 64 bits
(0-18446744073709551615)
int8 el conjunto de todos los enteros con signo de 8 bits (-128 a
127)
int16 el conjunto de todos los enteros con signo de 16 bits (-32768
a 32767)
int32 el conjunto de todos los enteros con signo de 32 bits
(-2147483648 a 2147483647)
int64 el conjunto de todos los enteros con signo de 64 bits
(-9223372036854775808 a 9223372036854775807)
float32 el conjunto de todos los números IEEE-754 de coma flotante de
32 bits
float64 el conjunto de todos los números IEEE-754 de coma flotante de
64 bits
complex64 el conjunto de todos los números complejos float32 con partes
real e imaginaria
complex128 el conjunto de todos los números complejos float64 con partes
real e imaginaria
byte alias para uint8
rune alias para int32
El valor de un entero n-bit es n bits de ancho y se representa usando aritmética de complemento a dos.
También hay una serie de tipos numéricos predeclarados con tamaños específicos de la implementación:
uint entre 32 o 64 bits
int mismo tamaño que uint
uintptr un entero sin signo suficientemente grande para almacenar
los bits no interpretados de un valor puntero
Para evitar problemas de portabilidad todos los tipos numéricos son
distintos a excepción de byte
, que es un alias para uint8
y rune
,
que es un alias para int32
. Se necesita una conversión cuando
diferentes tipos numéricos se mezclan en una expresión o asignación. Por
ejemplo, int32
e int
no son del mismo tipo a pesar de que pueden tener
el mismo tamaño en una arquitectura particular.
Tipos cadena de caracteres (string)
Un tipo cadena representa el conjunto de valores de cadena de
caracteres. Un valor de cadena es una (posiblemente vacía) secuencia de
bytes. Las cadenas son inmutables: una vez creadas, no es posible cambiar
el contenido de una cadena. El tipo predeclarado de cadena es string
.
La longitud de una cadena s
(su tamaño en bytes) se puede descubrir
usando la función incorporada len
. La longitud
es una constante en tiempo de compilación si la cadena es una constante.
Se puede acceder a los bytes de una cadena por
índices enteros desde 0 hasta len(s)-1
. Es
ilegal tomar la dirección de tal elemento; si s[i]
es el i
enésimo byte
de una cadena, &s[i]
no es válido.
Tipos arreglo
Un arreglo o vector es una secuencia numerada de elementos de un solo
tipo, llamado el tipo del elemento. Al número de elementos se le conoce
como longitud
y nunca es negativo.
TipoArreglo = "[" LongitudArreglo "]" TipoElemento .
LongitudArreglo = Expresión .
TipoElemento = Tipo .
La longitud es parte del tipo arreglo; se debe evaluar a una
constante no negativa representable por un valor de tipo
int
. La longitud del arreglo a
se puede descubrir usando la función
incorporada len
. Los elementos se pueden
encontrar por índices enteros desde 0 hasta
len(a)-1
. Los tipos arreglo siempre son de una sola dimensión, pero
pueden estar compuestos para formar tipos multidimensionales.
[32]byte
[2*N] struct { x, y int32 }
[1000]*float64
[3][5]int
[2][2][2]float64 // igual que [2]([2]([2]float64))
Tipos sector
Un sector es un descriptor de un segmento contiguo de un arreglo
subyacente y proporciona acceso a una secuencia numerada de elementos de
ese arreglo. Un tipo sector denota el conjunto de todos los sectores de
arreglos del tipo de elemento. El valor de un sector sin iniciar es
nil
.
TipoSector = "[" "]" TipoElemento .
Al igual que los arreglos, los sectores son indexables y tienen una
longitud. La longitud de un sector s
se puede descubrir por medio de la
función incorporada len
; a diferencia de los
arreglos puede cambiar durante la ejecución. Los elementos se pueden
encontrar por medio de índices enteros desde 0 hasta
len(s)-1
. El índice de un elemento dado del sector puede ser menor que
el índice del mismo elemento del arreglo subyacente.
Un sector, una vez iniciado, siempre está asociado con un arreglo subyacente que mantiene sus elementos. Por lo tanto, un sector comparte almacenamiento con su arreglo y con otros sectores del mismo arreglo; por el contrario, distintos arreglos siempre representan almacenamiento distinto.
El arreglo subyacente a un sector se puede extender más allá del final del
sector. La capacidad es una medida de esa extensión: es la suma de la
longitud del sector y la longitud del arreglo más allá del sector; puedes
crear un sector de mayor longitud que la capacidad
secccionando uno nuevo desde el sector original.
La capacidad de un sector a
se puede descubrir usando la función
incorporada cap(a)
.
Un nuevo valor de sector iniciado, para un tipo de elemento T
dado se
crea usando la función integrada
make
, que toma un tipo sector y
parámetros especificando la longitud y opcionalmente la capacidad. Un
sector creado con make
siempre asigna un nuevo arreglo oculto al cual el
valor del sector devuelto refiere. Es decir, al ejecutar:
make([]T, longitud, capacidad)
produce el mismo sector como si asignaras un arreglo y después lo seccionaras, por lo tanto estas dos expresiones son equivalentes:
make([]int, 50, 100)
new([100]int)[0:50]
Como los arreglos, los sectores siempre son de una sola dimensión, pero pueden estar compuestos para construir objetos de mayores dimensiones. Con arreglos de arreglos, los arreglos internos siempre son, por construcción, de la misma longitud; sin embargo con los sectores de sectores (o arreglos de sectores), las longitudes internas pueden variar dinámicamente. Por otra parte, los sectores internos se deben iniciar individualmente.
Tipos estructura
Una estructura es una secuencia de elementos nombrados, llamados campos, cada uno de los cuales tiene un nombre y un tipo. Los nombres de campo se pueden especificar explícitamente (ListaIdentificador) o implícitamente (CampoAnónimo). En una estructura, no vacía los nombres de los campos deben ser únicos.
TipoEstructura = "estructura" "{" { DeclaraCampo ";" } "}" .
DeclaraCampo = ( ListaIdentificador Tipo | CampoAnónimo ) [ Etiqueta ] .
CampoAnónimo = ["*"] NombreTipo .
Etiqueta = cadena_lit .
// Una estructura vacía.
struct {}
// Una estructura con 6 campos.
struct {
x, y int
u float32
_ float32 // relleno
A *[]int
F func()
}
Un campo declarado con un tipo pero no un nombre de campo explícito es un
campo anónimo, también llamado campo incrustado o una incrustación del
tipo en la estructura. Un tipo de campo se tiene que especificar como un
nombre de tipo T
o como un puntero a un nombre de tipo no interfaz *T
y T
en sí mismo no puede ser un tipo puntero. El nombre de tipo no
cualificado actúa como el nombre del campo.
// Una estructura con cuatro campos anónimos de tipo T1, *T2,
// P.T3 y *P.T4
struct {
T1 // el nombre del campo es T1
*T2 // el nombre del campo es T2
P.T3 // el nombre del campo es T3
*P.T4 // el nombre del campo es T4
x, y int // los nombres de campo son x e y
}
La siguiente declaración es ilegal ya que los nombres de campo deben ser únicos en un tipo estructura:
struct {
T // conflicto con los campos anónimos *T y *P.T
*T // conflicto con los campos anónimos T y *P.T
*P.T // conflicto con los campos anónimos T y *T
}
Un campo o método f
de un campo anónimo en
una estructura x
se dice que fue promovido si x.f
es un
selector legal que denota ese campo o método f
.
Los campos promovidos actúan como campos ordinarios de una estructura, excepto que no se pueden utilizar como nombres de campo en los literales compuestos de la estructura.
Dado un tipo estructura S
y un tipo llamado T
, los métodos promovidos
se incluyen en el conjunto de métodos de la estructura de la siguiente
manera:
- Si
S
contiene un campo anónimoT
, el conjunto de métodos de ambosS
y*S
incluyen métodos promovidos con receptorT
. El conjunto de métodos de*S
también incluye métodos promovidos con receptor*T
. - Si
S
contiene un campo anónimo*T
, ambos conjuntos de métodos deS
y*S
incluyen métodos promovidos con receptorT
o*T
.
Una declaración de campo puede estar seguida por una cadena literal opcional que hará las veces de etiqueta, que se convierte en un atributo para todos los campos en la declaración del campo correspondiente. Las etiquetas se hacen visibles a través de una interfaz de reflexión y toman parte en la identidad de tipo para estructuras pero de lo contrario se omiten.
// Una estructura que corresponde al protocolo TimeStamp con búfer.
// Las cadenas etiqueta definen el protocolo búfer para el número
// de campos.
struct {
microsec uint64 "campo 1"
servidorIP6 uint64 "campo 2"
proceso string "campo 3"
}
Tipos puntero
Un tipo puntero denota el conjunto de todos los punteros a las
variables de un determinado tipo, llamado el tipo base del
puntero. El valor de un puntero no iniciado es nil
.
TipoPuntero = "*" TipoBase .
TipoBase = Tipo .
*Punto
*[4]int
Tipos función
Un tipo función indica el conjunto de todas las funciones con los mismos
parámetros y tipo de resultados. El valor de una variable no iniciada de
tipo función es nil
.
TipoFunción = "func" Firma .
Firma = Parámetros [ Resultado ].
Resultado = Parámetros | Tipo .
Parámetros = "(" [ ListaDeParámetros [ "," ] ] ")".
ListaDeParámetros = DeclaParámetro {"," DeclaParámetro }.
DeclaParámetro = [ ListaIdentificador ] ["..."] Tipo .
Dentro de una lista de parámetros o resultados, los nombres (ListaIdentificador), deberían estar todos presentes o todos ausentes. Si están presentes, cada nombre se refiere a un elemento (parámetro o resultado) del tipo especificado y todos los nombres no vacíos de la firma deben ser únicos. Si esta ausente, cada tipo representa un elemento de ese tipo. Las listas de parámetros y resultados siempre están entre paréntesis excepto si hay exactamente un resultado anónimo que se pueda escribir como un tipo sin paréntesis.
El parámetro final en una firma de función puede tener un tipo prefijado
con ...
. Una función con tal parámetro se llama variadica y se puede
invocar con cero o más argumentos para ese parámetro.
func()
func(x int) int
func(a, _ int, z float32) bool
func(a, b int, z float32) (bool)
func(prefijo string, valores ...int)
func(a, b int, z float64, opcional ...interface{}) (éxitoso bool)
func(int, int, float64) (float64, *[]int)
func(n int) func(p *T)
Tipos interfaz
Un tipo interfaz especifica un conjunto de métodos
conocido como su interfaz. Una variable de tipo interfaz puede
almacenar un valor de cualquier tipo con un conjunto de métodos que es
algún superconjunto de la interfaz. Tal tipo se dice que implementa la
interfaz. El valor de una variable no iniciada de tipo interfaz es
nil
.
TipoInterfaz = "interface" "{" { EspecificaMétodo ";" } "}" .
EspecificaMétodo = NombreMétodo Firma | NombreTipoInterfaz .
NombreMétodo = identificador .
NombreTipoInterfaz = NombreTipo .
Al igual que con todos los conjuntos de métodos, en un tipo interfaz, cada método debe tener un nombre único no vacío.
// Una sencilla interfaz Documento
interface {
Lee(b Buffer) bool
Escribe(b Buffer) bool
Cierra()
}
Más de un tipo puede implementar una interfaz. Por ejemplo, si dos tipos
S1
y S2
tienen el conjunto de métodos:
func (p T) Lee(b Buffer) bool { return … }
func (p T) Escribe(b Buffer) bool { return … }
func (p T) Cierra() { … }
(Donde T
representa ya sea a S1
o a S2
), entonces la interfaz
Documento
la implementan tanto S1
como S2
, independientemente de
cuales otros métodos puedan tener o compartir S1
y S2
.
Un tipo implementa cualquier interfaz que comprenda algún subconjunto de sus métodos y por lo tanto puede implementar varias interfaces distintas. Por ejemplo, todos los tipos implementan la interfaz vacía:
interface{}
Del mismo modo, considera esta especificación de interfaz que aparece
dentro de una declaración de tipo para definir una
interfaz llamada Archivero
:
type Archivero interface {
Bloquea()
Desbloquea()
}
Si S1
y S2
también implementan
func (p T) Bloquea() { … }
func (p T) Desbloquea() { … }
implementan la interfaz Archivero
así como la interfaz Documento
.
Una interfaz T
puede utilizar un (posiblemente cualificado) nombre de
tipo interfaz E
en lugar de una especificación de método. A esto se le
conoce como incrustación de la interfaz E
en T
; esta añade todos los
métodos (exportados y no exportados) de E
a la interfaz T
.
type LectorEscritor interface {
Lee(b Buffer) bool
Escribe(b Buffer) bool
}
type Documento interface {
LectorEscritor // igual que añadir los métodos de LectorEscritor
Archivero // igual que añadir los métodos de Archivero
Cierra()
}
type DocumentoArchivado interface {
Archivero
Documento // ilegal: Archivero, Desbloquea no es único
Bloquea() // ilegal: Bloquea no es único
}
Un tipo interfaz T
no se puede incrustar a sí mismo o a cualquier tipo
interfaz que incorpore T
, recursivamente.
// ilegal: Mal no se puede incrustar
type Mal interface {
Mal
}
// ilegal: Mal1 no se puede incrustar a sí misma usando Mal2
type Mal1 interface {
Mal2
}
type Mal2 interface {
Mal1
}
Tipos mapa
Un mapa es un grupo no ordenado de elementos de un tipo, conocido como el
tipo del elemento, indexado por un conjunto de claves únicas de otro
tipo, llamada la clave del tipo. El valor de un mapa sin iniciar es
nil
.
TipoMapa = "map" "[" TipoClave "]" TipoElemento .
TipoClave = Tipo .
Los operadores de comparación ==
y !=
deben estar completamente definidos como operandos del tipo clave; por lo
que, el tipo clave no debe ser una función, mapa o sector. Si el tipo
clave es un tipo interfaz, estos operadores de comparación los deben
definir los valores de las claves dinámicas; un fallo provocará
pánico en tiempo de ejecución.
map[string]int
map[*T]struct{ x, y float64 }
map[string]interfaz{}
El número de elementos del mapa se conoce como su longitud. En un mapa
m
su longitud se puede descubrir usando la función incorporada
len
y puede cambiar durante la
ejecución. Puedes añadir elementos durante la ejecución utilizando
asignaciones y se recuperan con
expresiones índice; se pueden remover con la
función incorporada delete
.
Un nuevo valor de mapa vacío se crea usando la función integrada
make
, que toma el tipo mapa y un
indicio de capacidad opcional como argumentos:
make(map[string]int)
make(map[string]int, 100)
La capacidad inicial no inmoviliza su tamaño: los mapas crecen para
acomodar el número de elementos almacenados en ellos, a excepción de los
mapas nil
. Un mapa nil
es equivalente a un mapa vacío salvo que no se
le pueden añadir elementos.
Tipos canal
Un canal proporciona un mecanismo para
ejecutar funciones concurrentemente para comunicarse
enviando y recibiendo
valores de un tipo de elemento especificado. El valor de un canal sin
iniciar es nil
.
TipoCanal = ("chan" | "chan" "<-" | "<-" "chan") TipoElemento .
El operador opcional <-
especifica la dirección del canal, enviar
o recibir. Si no se da una dirección, el canal es bidireccional. Un
canal puede estar limitado por conversión o
asignación solo para enviar o solo para recibir.
chan T // se puede utilizar para enviar y recibir valores
// de tipo T
chan<- float64 // solo se puede utilizar para enviar float64
<-chan int // solo se puede utilizar para recibir ints
El operador <-
posibilita la asociación con el canal
de la izquierda:
chan<- chan int // igual que chan<- (chan int)
chan<- <-chan int // igual que chan<- (<-chan int)
<-chan <-chan int // igual que <-chan (<-chan int)
chan (<-chan int)
Puedes crear un nuevo valor de canal iniciado usando la función integrada
make
, que toma como argumentos el
tipo canal y una capacidad opcional:
make(chan int, 100)
La capacidad, en número de elementos, establece el tamaño del búfer en el
canal. Si la capacidad es cero o está ausente, el canal es sin búfer y la
comunicación tiene éxito solo cuando ambos, el remitente y el receptor
están listos. De lo contrario, el canal se almacenará en un búfer y la
comunicación será éxitosa sin bloqueo si el búfer no está lleno (envía) o
no está vacío (recibe). Un canal nil
nunca está listo para
comunicación.
Un canal se puede cerrar con la función incorporada close
. La
forma de asignación de múltiples valores del
operador receptor informa si un valor recibido
fue enviado antes de que se cerrara el canal.
Un solo canal se puede utilizar en
instrucciones de envío,
operaciones de recepción y llamadas a las
funciones incorporadas cap
y
len
por una serie de rutinasgo sin más
sincronización. Los canales actúan como colas PEPS
(primero-en-entrar-primero-en-salir). Por ejemplo, si una rutinago
envía los valores en un canal y una segunda rutinago los recibe, los
valores se reciben en el orden enviado.
Propiedades de tipos y valores
Identidad de tipo
Dos tipos son o bien idénticos o diferentes.
Dos tipos nombrados son idénticos cuando sus nombres de tipo se originan en la misma EspeciTipo. Un tipo nombrado y uno anónimo siempre son diferentes. Dos tipos anónimos son idénticos si los tipos literales correspondientes son idénticos, es decir, si tienen la misma estructura literal y los componentes correspondientes tienen tipos idénticos. En detalle:
- Dos tipos arreglo son idénticos si tienen elementos de tipos idénticos y los arreglos tiene la misma longitud.
- Dos tipos sector son idénticos si el tipo de sus elementos es idéntico.
- Dos tipos estructura son idénticos si tienen la misma secuencia de campos y si los campos correspondientes tienen los mismos nombres, tipos y etiquetas idénticas. Dos campos anónimos se considera que tienen el mismo nombre. Nombres de campo en minúsculas de diferentes paquetes siempre son diferentes.
- Dos tipos puntero son idénticos si tienen tipos base idénticos.
- Dos tipos función son idénticos si tienen el mismo número de parámetros y valores de resultado, los tipos de parámetros y resultados correspondientes son idénticos y, o bien ambas funciones son variadicas o ninguna de las dos. Los nombres de parámetros y resultados no es necesario que coincidan.
- Dos tipos interfaz son idénticos si tienen el mismo conjunto de métodos con los mismos nombres y tipos función idénticos. Los nombres de método en minúsculas de diferentes paquetes siempre son diferentes. El orden de los métodos es irrelevante.
- Dos tipos mapa son idénticos si tienen tipos de claves y valores idénticos.
- Dos tipos canal son idénticos si tienen tipos de valores idénticos y la misma dirección.
Dadas las declaraciones:
type (
T0 []string
T1 []string
T2 struct{ a, b int }
T3 struct{ a, c int }
T4 func(int, float64) *T0
T5 func(x int, y float64) *[]string
)
estos tipos son idénticos:
T0 y T0
[]int e []int
struct{ a, b *T5 } y struct{ a, b *T5 }
func(x int, y float64) *[]string y func(int, float64) (resultado *[]string)
T0
y T1
son diferentes porque tienen nombres de tipo con declaraciones
distintas; func(int, float64) *T0
y func(x int, y float64) *[]string
son diferentes porque T0
es diferente de []string
.
Asignabilidad
Un valor x
es asignable a una variable de tipo T
("x
es asignable a T
") en cualquiera de estos casos:
- el tipo
x
es idéntico aT
. - los tipos
x
,V
yT
tienen idénticos tipos subyacentes y al menos uno deV
oT
no es un tipo nombrado. T
es un tipo interfaz yx
implementa aT
.x
es un valor de canal bidireccional,T
es un tipo canal, los tiposx
,V
yT
tienen tipos de elemento idénticos y al menos uno deV
oT
no es un tipo nombrado.x
es el identificador predeclaradonil
yT
es un tipo puntero, función, sector, mapa, canal o interfaz.x
es una constante sin tipo representable por un valor de tipoT
.
Bloques
Un bloque es una secuencia posiblemente vacía de declaraciones e instrucciones entre llaves.
Bloque = "{" ListaInstrucciones "}" .
ListaInstrucciones = { Declaración ";" } .
Además de los bloques explícitos en el código fuente, hay bloques implícitos:
- El bloque universal abarca todo el texto fuente Go.
- Cada paquete tiene un bloque de paquete que contiene todo el texto fuente Go para ese paquete.
- Cada archivo tiene un bloque de archivo que contiene todo el texto fuente Go en ese archivo.
- Cada declaración "if", "for" y "switch" se considera que están en su propio bloque implícito.
- Cada cláusula de una declaración "switch" o "select" actúa como un bloque implícito.
Bloques anidados e influencia y determinación de ámbito.
Declaraciones y ámbito
Una declaración vincula a un identificador no blanco con una constante, tipo, variable, función, etiqueta o paquete. Cada identificador en un programa se debe declarar. Ningún identificador se puede declarar dos veces en el mismo bloque y ningún identificador se puede declarar en ambos el bloque de archivo y el bloque de paquete.
El identificador blanco se puede utilizar como
cualquier otro identificador en una declaración, pero no introduce un
vínculo y por tanto no se ha declarado. En el bloque de paquete, el
identificador init
solo lo pueden utilizar las declaraciones de
función init
e igual que el identificador
blanco este no introduce un nuevo vínculo.
Declaración = DeclaConst | DeclaTipo | DeclaVar .
DeclaNivelSuperior = Declaración | DeclaFunción | DeclaMétodo .
El ámbito de un identificador declarado es la extensión del texto fuente en el que el identificador denota la constante, tipo, variable, función, etiqueta o paquete especificado.
Go tiene ámbito léxico utilizando bloques:
- El alcance de un identificador predeclarado es el bloque universal.
- El ámbito de un identificador que denota una constante, tipo, variable o función (pero no método) declarado en el nivel superior (fuera de cualquier función) es el bloque del paquete.
- El alcance del nombre del paquete de un paquete importado es el bloque de archivo del archivo que contiene la declaración de importación.
- El ámbito de un identificador que denota un método receptor, parámetro de función o variable de resultado es el cuerpo de la función.
- El ámbito del identificador de una constante o variable declarada dentro de una función se inicia al final de la EspeConst o EspeVar (DeclaVarCorta para declaraciones cortas de variables) y termina al final de la más interna que contiene el bloque.
- El alcance de un identificador de tipo declarado dentro de una función comienza en la EspeciTipo del identificador y termina al final del bloque más interno.
Un identificador declarado en un bloque se puede volver a declarar en un bloque interno. Mientras que el identificador de la declaración interna esté a su alcance, este se refiere a la entidad declarada por la declaración interna.
La cláusula package no es una declaración; el nombre del paquete no aparece en ningún ámbito. Su propósito es identificar los archivos pertenecientes a un mismo paquete y especificar el nombre del paquete predeterminado para las declaraciones de importación.
Ámbito de etiquetas
Las etiquetas son declaradas por las instrucciones etiquetadas y se utilizan en las instrucciones "break", "continue" y "goto". Es ilegal definir una etiqueta que nunca se utiliza. En contraste con otros identificadores, las etiquetas no se restringen a bloques y no entran en conflicto con los identificadores que no son etiquetas. El ámbito de una etiqueta es el cuerpo de la función en la que se declara y excluye el cuerpo de cualquier función anidada.
Identificador blanco
El identificador blanco está representado por el carácter de subrayado
_
. Sirve como un marcador de posición anónimo en lugar de un
identificador regular (no blanco) y tiene un significado especial en
declaraciones, como un operando y
en las asignaciones.
Identificadores predeclarados
Los siguientes identificadores están declarados implícitamente en el bloque universal:
Tipos:
bool byte error complex128 complex64 float32 float64
int int8 int16 int32 int64 runa string
uint uint8 uint16 uint32 uint64 uintptr
Constantes:
true false iota
Valor cero:
nil
Funciones:
append cap close complex copy delete imag len
make new panic print println real recover
Identificadores exportados
Un identificador se puede exportar para permitir el acceso al mismo desde otro paquete. Un identificador se exporta si se cumplen las siguientes condiciones:
- El primer carácter del nombre del identificador es una letra Unicode en mayúscula (Unicode clase "Lu"); y
- El identificador se declara en el bloque de paquete o se trata de un nombre de campo o un nombre de método.
Todos los otros identificadores no se exportan.
Unicidad de identificadores
Dado un conjunto de identificadores, un identificador se llama único si es diferente de todos los otros en el conjunto. Dos identificadores son diferentes si se escriben diferente o si aparecen en diferentes paquetes y no se exportan. De lo contrario, son los mismos.
Declaración de constantes
Una declaración de constante vincula una lista de identificadores (los nombres de las constantes) a los valores de una lista de expresiones constantes. El número de identificadores debe ser igual al número de expresiones y el enésimo identificador de la izquierda está unido al valor de la enésima expresión de la derecha.
DeclaConst = "const" ( EspeConst | "(" { EspeConst ";" } ")" ) .
EspeConst = ListaIdentificador [ [ Tipo ] "=" ListaExpresión ] .
ListaIdentificador = identificador { "," identificador } .
ListaExpresión = Expresión { "," Expresión } .
Si el tipo está presente, todas las constantes toman el tipo especificado y las expresiones deben ser asignables a ese tipo. Si se omite el tipo, las constantes toman los tipos individuales de las expresiones correspondientes. Si los valores de la expresión sin tipo son constantes, las constantes declaradas permanecen sin tipo y los identificadores constantes denotan los valores constantes. Por ejemplo, si la expresión es un literal de coma flotante, el identificador constante denota una constante de coma flotante, incluso si la parte fraccionaria literal es cero.
const Pi float64 = 3.14159265358979323846
const cero = 0.0 // constante de coma flotante sin tipo
const (
tamaño int64 = 1024
fda = -1 // constante entera sin tipo
)
const a, b, c = 3, 4, "foo" // a = 3, b = 4, c = "foo", constantes
// enteras sin tipo y cadena
const u, v float32 = 0, 3 // u = 0.0, v = 3.0
Dentro de paréntesis la lista de declaración const
y la lista de
expresiones se puede omitir en cualquiera menos la primera declaración.
Tal lista vacía es equivalente a la sustitución textual de la primer lista
de expresiones no vacía anterior y su tipo si los hubiere. La omisión de
la lista de expresiones es equivalente a la repetición de la lista
anterior. El número de identificadores debe ser igual al número de
expresiones en la lista anterior. Junto con el
generator de constantes iota
este mecanismo permite la
declaración ligera de valores secuenciales:
const (
Domingo = iota
Lunes
Martes
Miércoles
Jueves
Viernes
DíaDelPartido
númeroDeDías // esta constante no se exporta
)
Iota
Dentro de una declaración de constante, el
identificador predeclarado iota
representa constantes
enteras sucesivas sin tipo. Se restablece a 0 cada vez que la palabra
reservada const
aparece en la fuente y se incrementa después de cada
EspeConst. Se puede utilizar para construir un conjunto de
constantes relacionadas:
const ( // iota se restablece a 0
c0 = iota // c0 == 0
c1 = iota // c1 == 1
c2 = iota // c2 == 2
)
const (
a = 1 << iota // a == 1 (iota se ha restablecido)
b = 1 << iota // b == 2
c = 1 << iota // c == 4
)
const (
u = iota * 42 // u == 0 (constante entera sin tipo)
v float64 = iota * 42 // v == 42.0 (constante float64)
w = iota * 42 // w == 84 (constante entera sin tipo)
)
const x = iota // x == 0 (iota se ha restablecido)
const y = iota // y == 0 (iota se ha restablecido)
Dentro de una ListaExpresión, el valor de cada iota
es el mismo, ya que
solo se incrementa después de cada EspeConst:
const (
bit0, mask0 = 1 << iota, 1<<iota - 1 // bit0 == 1, mask0 == 0
bit1, mask1 // bit1 == 2, mask1 == 1
_, _ // salta iota == 2
bit3, mask3 // bit3 == 8, mask3 == 7
)
Este último ejemplo explota la repetición implícita de la lista de expresiones no vacía anterior.
Declaración de tipos
Una declaración de tipo vincula un identificador, el nombre de tipo, a un nuevo tipo que tiene el mismo tipo subyacente que un tipo existente y las operaciones definidas para el tipo existente también están definidas para el nuevo tipo. El nuevo tipo es diferente del tipo existente.
DeclaTipo = "type" ( EspeciTipo | "(" { EspeciTipo ";" } ")" ) .
EspeciTipo = identificador de Tipo .
type ArregloEntero [16]int
type (
Punto struct{ x, y float64 }
Polar Punto
)
type ÁrbolDeNodo struct {
izquierda, derecha *ÁrbolDeNodo
valor *Comparable
}
type Bloque interface {
TamañoBloque() int
Cifra(fnt, dst []byte)
Descifra(fnt, dst []byte)
}
El tipo declarado no hereda ningún método ligado al tipo existente, pero el conjunto de métodos de un tipo interfaz o de elementos de un tipo compuesto se mantiene sin cambios:
// Un Excluyente es un tipo de dato con dos métodos, Bloquea y Desbloquea.
type struct Excluyente { /* Campos de exclusión mutua (mutex) */ }
func (m *Excluyente) Bloquea() { /* Implementación de Bloquea */ }
func (m *Excluyente) Desbloquea() { /* Implementación de Desbloquea */ }
// NuevoExcluyente tiene la misma composición que un objeto de
// exclusión mutua pero su conjunto de métodos está vacío.
type NuevoExcluyente Excluyente
// El conjunto de métodos del tipo base de PuntExcluyente permanece sin
// cambios, pero el conjunto de métodos está vacío.
type PuntExcluyente *Excluyente
// El conjunto de métodos de *ExcluyenteImprimible contiene los métodos
// Bloquea y Desbloquea unido a su campo anónimo Excluyente.
type ExcluyenteImprimible struct {
Excluyente
}
// MiBloque es un tipo interfaz que tiene el mismo conjunto
// de métodos que Bloque.
type MiBloque Bloque
Puedes utilizar una declaración de tipo para definir un diferente valor lógico, numérico o tipo cadena y conectarle métodos:
type ZonaHoraria int
const (
EST ZonaHoraria = -(5 + iota)
CST
MST
PST
)
func (zh ZonaHoraria) String() string {
return fmt.Sprintf("GMT+%dh", zh)
}
Declaración de variables
Una declaración de variable crea una o más variables, vinculándolas a sus correspondientes identificadores, dándoles un tipo y valor inicial a cada una.
DeclaVar = "var" ( EspeVar | "(" { EspeVar ";" } ")" ) .
EspeVar = ListaIdentificador ( Tipo [ "=" ListaExpresión ] | "=" ListaExpresión ) .
var i int
var U, V, W float64
var k = 0
var x, y float32 = -1, -2
var (
i int
u, v, s = 2.0, 3.0, "bar"
)
var re, im = RaízCompleja(-1)
var _, encontrada = entradas[nombre] // consulta mapa; solo está
// interesado en "encontrada"
Si se da una lista de expresiones, las variables se inician con las expresiones siguiendo las reglas para asignaciones. De lo contrario, cada variable se inicia a su valor cero.
Si un tipo está presente, a cada variable se le da ese tipo. De lo
contrario, a cada variable se le da el tipo del valor inicial
correspondiente en la asignación. Si ese valor es una constante sin tipo,
primero se convierte a su
tipo predeterminado; si es un valor lógico sin tipo,
primero se convierte al tipo bool
. El valor predeclarado nil
no se
puede utilizar para iniciar una variable sin tipo explícito.
var d = math.Sin(0.5) // d es float64
var i = 42 // i es int
var t, ok = x.(T) // t es T, ok es bool
var n = nil // ilegal
Restricción de implementación: Un compilador puede hacer que sea ilegal declarar una variable dentro del cuerpo de la función si la variable no se usa nunca.
Declaración corta de variables
Una declaración corta de variables utiliza la sintaxis:
DeclaVarCorta = ListaIdentificador ":=" ListaExpresión .
Esta es la forma abreviada de una declaración de variables con expresión iniciadora pero sin tipos:
"var" ListaIdentificador = ListaExpresión .
i, j := 0, 10
f := func() int { return 7 }
ch := make(chan int)
r, w := os.Pipe(fd) // os.Pipe() devuelve dos valores
_, y, _ := coord(p) // coord() devuelve tres valores; solo está
// interesado en la coordenada y
A diferencia de las declaraciones de variable regulares, una declaración corta de variable puede redeclarar variables siempre y cuando se hayan declarado originalmente antes en el mismo bloque y con el mismo tipo y por lo menos una de las variables no blanca sea nueva. Como consecuencia, una redeclaración solo puede aparecer en una declaración multivariable corta. La redeclaración no introduce una nueva variable; simplemente asigna un nuevo valor a la original.
campo1, desplazamiento := siguienteCampo(cadena, 0)
campo2, desplazamiento := siguienteCampo(str, desplazamiento) // redeclara
// desplazamiento
a, a := 1, 2 // ilegal: doble declaración de a o no nueva variable si
// a fue declarada en otra parte
Las declaraciones cortas de variable solo pueden aparecer en el interior de funciones. En algunos contextos, como en los iniciadores de las instrucciones "if", "for" o "switch" se pueden utilizar para declarar variables locales temporales.
Declaración de funciones
Una declaración de función vincula un identificador, el nombre de la función, a una función.
DeclaFunción = "func" NombreFunción ( Función | Firma ) .
NombreFunción = identificador .
Función = Firma CuerpoFunción .
CuerpoFunción = Bloque .
Si la firma de la función declara parámetros de resultado, la lista de instrucciones del cuerpo de la función debe terminar en una instrucción de terminación.
func encuentraMarcador(c <-chan int) int {
for i := range c {
if x := <-c; esMarcador(x) {
return x
}
}
// No válido: falta la instrucción de retorno.
}
Una declaración de función puede omitir el cuerpo. Tal declaración provee la firma para una función implementada fuera de Go, tal como una rutina de ensamblador.
func min(x int, y int) int {
if x < y {
return x
}
return y
}
func vacíaCaché(inicio, fin uintptr) // implementada externamente
Declaración de métodos
Un método es una función con un receptor. Una declaración de método vincula a un identificador, el nombre del método, con un método y asocia el método con el tipo base del receptor.
DeclaMétodo = "func" Receptor NombreMétodo ( Función | Firma ) .
Receptor = Parámetros .
El receptor se especifica a través de una sección de parámetros extra
precediendo al nombre del método. Esa sección de parámetros debe declarar
un solo parámetro, el receptor. Su tipo debe ser de la forma T
o *T
(posiblemente usando paréntesis) donde T
es un nombre de tipo. El tipo
denotado por T
se conoce como el tipo base del receptor; no debe ser
un tipo puntero o interfaz y se debe declarar en el mismo paquete que el
método. El método se dice que está unido al tipo base y el nombre del
método es visible solo dentro de selectores para ese tipo.
Un identificador receptor no blanco debe ser único en la firma del método. Si no se hace referencia al valor del receptor dentro del cuerpo del método, su identificador se puede omitir en la declaración. Lo mismo se aplica en general a los parámetros de funciones y métodos.
Para un tipo base, los nombres no blancos de métodos vinculados a él deben ser únicos. Si el tipo base es un tipo estructura, el método y los nombres de campos no blancos deben ser distintos.
Dado el tipo Punto
, las declaraciones
func (p *Punto) Longitud() float64 {
return math.Sqrt(p.x * p.x + p.y * p.y)
}
func (p *Punto) Escala(factor float64) {
p.x *= factor
p.y *= factor
}
vincula a los métodos Longitud
y Escala
con el tipo receptor *Punto
,
de tipo base Punto
.
El tipo de un método es el tipo de una función con el receptor como primer
argumento. Por ejemplo, el método Escala
tiene el tipo:
func(p *Punto, factor float64)
Sin embargo, una función declarada de esta manera no es un método.
Expresiones
Una expresión especifica el cálculo de un valor mediante la aplicación de operadores y funciones a operandos.
Operandos
Los operandos denotan los valores elementales de una expresión. Un operando puede ser un literal, un (posiblemente cualificado) identificador no blanco que denota una constante, variable o función, una expresión método produciendo una función o una expresión entre paréntesis.
El identificador blanco puede aparecer como un operando solo en el lado izquierdo de una asignación.
Operando = Literal | NombreOperando | ExpreMétodo | "(" Expresión ")" .
Literal = LitBásico | LitCompuesto | FunciónLit .
LitBásico = ent_lit | float_lit | imaginario_lit | rune_lit | cadena_lit .
NombreOperando = identificador | IdentiCualificado.
Identificadores cualificados
Un identificador cualificado es un identificador prefijado con un nombre de paquete. Tanto el nombre del paquete como el identificador no deben estar en blanco.
IdentiCualificado = NombrePaquete "." identificador .
Un identificador cualificado accede a un identificador en un paquete diferente, que se debe importar. El identificador se debe exportar y estar declarado en el bloque de paquete de ese paquete.
math.Sin // denota la función Sin en el paquete math
Literales compuestos
Los literales compuestos construyen valores para estructuras, arreglos, sectores, mapas y crean un nuevo valor cada vez que se evalúan. Estos consisten en el tipo del valor seguido de una lista encerrada entre llaves de elementos compuestos. Un elemento puede ser una sola expresión o un par clave-valor.
LitCompuesto = TipoLiteral ValorLiteral .
TipoLiteral = TipoEstructura | TipoArreglo | "[" "..." "]" TipoElemento |
TipoSector | TipoMapa | NombreTipo .
ValorLiteral = "{" [ ListaElementos [ "," ] ] "}" .
ListaElementos = Elemento { "," Elemento } .
Elemento = [ Clave ":" ] Valor .
Clave = NombreCampo | ÍndiceElemento .
NombreCampo = identificador .
ÍndiceElemento = Expresión .
Valor = Expresión | ValorLiteral .
El TipoLiteral debe ser un tipo estructura, arreglo, sector o mapa (la gramática impone esta restricción, excepto cuando el tipo se da como un NombreTipo). Los tipos de las expresiones deben ser asignables al campo respectivo, elemento y tipo clave del TipoLiteral; no hay conversión adicional. La clave se interpreta como un nombre de campo para las estructuras literales, un índice de arreglo y sectores literales, y una clave para mapas literales. Para mapas literales, todos los elementos deben tener una clave. Es un error especificar múltiples elementos con el mismo nombre de campo o valor clave constante.
Para estructuras literales se aplican las siguientes reglas:
- Una clave debe ser un nombre de campo declarado en el TipoLiteral.
- Una lista de elementos que no contiene ninguna clave debe incluir un
elemento para cada campo de estructura en el orden en que se declaran
los campos.
- Si algún elemento tiene una clave, cada elemento debe tener una clave.
- Una lista de elementos que contiene claves no necesita tener un elemento
para cada campo de la estructura. Los campos omitidos obtienen el
valor cero para ese campo.
- Un literal puede omitir la lista de elementos; dichos literales evalúan al valor cero para su tipo.
- Es un error especificar un elemento de un campo no exportado de una estructura que pertenece a un paquete diferente.
Teniendo en cuenta las declaraciones
type Punto3D struct { x, y, z float64 }
type Línea struct { p, q Punto3D }
uno puede escribir
origen := Punto3D{} // valor cero para Punto3D
línea := Línea{origen, Punto3D{y: -4, z: 12.3}} // valor cero para línea.q.x
Para los arreglos y sectores literales se aplican las siguientes reglas:
- Cada elemento tiene un índice entero asociado marcando su posición en el arreglo.
- Un elemento con una clave utiliza la clave como su índice; la clave debe ser una expresión constante entera.
- Un elemento sin una clave usa el índice del elemento anterior más uno.
Si el primer elemento no tiene clave, su índice es cero.
Tomar la dirección de un literal compuesto genera un puntero a una única variable iniciada con el valor del literal.
var puntero *Punto3D = &Punto3D{y: 1000}
La longitud de un arreglo literal es la longitud especificada en el
TipoLiteral. Si en el literal se proporcionan menos elementos que la
longitud, los elementos que faltan se establecen al valor cero para el
tipo de elemento del arreglo. Es un error proporcionar elementos con
valores de índice fuera del rango índice del arreglo. La notación ...
especifica una longitud de arreglo igual al índice máximo de elementos más
uno.
búfer := [10]string{} // len(búfer) == 10
setEnt := [6]int{1, 2, 3, 5} // len(setEnt) == 6
días := [...]string{"Sáb", "Dom"} // len(días) == 2
Un sector literal describe todo el arreglo literal subyacente. Así, la longitud y capacidad de un sector literal son el índice del máximo elemento más uno. un sector literal tiene la forma
[]T{x1, x2, … xn}
y es la forma abreviada de una operación sector aplicada a un arreglo:
temp := [n]T{x1, x2, … xn}
temp[0 : n]
Dentro de un literal compuesto de arreglo, sector o mapa de tipo T
, en
que los elementos o claves de mapa en sí mismos son literales compuestos
puedes omitir el tipo literal correspondiente si es idéntico al tipo de
elemento o clave de mapa de T
. Del mismo modo, los elementos que son
direcciones de literales compuestos pueden omitir la &T
cuando el tipo del
elemento o clave es *T
.
Dentro de un literal compuesto de elementos arreglo, sector o mapa de tipo
T
, en que ellos mismos son literales compuestos puedes omitir el tipo
literal correspondiente si es idéntico al tipo de elemento T
. Del mismo
modo, los elementos que son direcciones de literales compuestos pueden
omitir la &T
cuando el tipo del elemento es *T
.
[...]Punto{{1.5, -3.5}, {0, 0}} // igual que [...]Punto{Punto{1.5, -3.5},
// Punto{0, 0}}
[][]int{{1, 2, 3}, {4, 5}} // igual que [][]int{[]int{1, 2, 3},
// []int{4, 5}}
[][]Punto{{{0, 1}, {1, 2}}} // igual que [][]Punto{[]Punto{Punto{0, 1},
// Punto{1, 2}}}
map[string]Punto{"orig": {0, 0}} // igual que map[string]Punto{"orig":
// Punto{0, 0}}
[...]*Punto{{1.5, -3.5}, {0, 0}} // igual que [...]*Punto{&Punto{1.5, -3.5},
// &Punto{0, 0}}
map[Punto]string{{0, 0}: "orig"} // igual que map[Punto]string{Punto{0, 0}:
// "orig"}
Surge un conflicto de ambigüedad cuando un literal compuesto usando la forma NombreTipo del TipoLiteral aparece como un operando entre la palabra clave y la llave de apertura del bloque de una declaración "if", "for" o "switch" y el literal compuesto no está entre paréntesis, corchetes o llaves. En este raro caso, la llave de apertura del literal es interpretado erróneamente como la introducción del bloque de instrucciones. Para resolver la ambigüedad, el literal compuesto debe aparecer entre paréntesis.
if x == (T{a,b,c}[i]) { … }
if (x == T{a,b,c}[i]) { … }
Ejemplos de literales válidos de arreglo, sector y mapa:
// Lista de números primos
primos := []int{2, 3, 5, 7, 9, 2147483647}
// vocales[v] es true si v es una vocal
vocales := [128]bool{'a': true, "e": true, "i": true, "o": true, "u": true, 'y': true}
// el arreglo [10]float32{-1, 0, 0, 0, -0.1, -0.1, 0, 0, 0, -1}
filtro := [10]float32{-1, 4: -0.1, -0.1, 9: -1}
// Frecuencias en Hz de la escala temperada (A4 = 440Hz)
frecuenciaNota := map[string]float32{
"C0": 16.35, "D0": 18.35, "E0": 20.60, "F0": 21.83,
"G0": 24.50, "A0": 27.50, "B0": 30.87,
}
Funciones literales
Un función literal representa una función anónima.
FunciónLit = "func" Función .
func(a, b int, z float64) bool { return a*b < int(z) }
Una función literal se puede asignar a una variable o invocarse directamente.
f := func(x, y int) int { return x + y }
func(ch chan int) { ch <- ACK }(canalRespuesta)
Las funciones literales son cierres: se pueden referir a variables definidas en funciones circundantes. Las variables se comparten entre las funciones circundantes y la función literal, además de que sobreviven siempre y cuando sean accesibles.
Expresiones primarias
Las expresiones primarias son los operandos de expresiones unarias y binarias.
ExprePrimaria =
Operando |
Conversión |
ExprePrimaria Selector |
ExprePrimaria Índice |
ExprePrimaria Sector |
ExprePrimaria TipoAserción |
ExprePrimaria Argumentos .
Selector = "." identificador .
Índice = "[" Expresión "]" .
Sector = "[" ( [ Expresión ] ":" [ Expresión ] ) |
( [ Expresión ] ":" Expresión ":" Expresión )
"]" .
TipoAserción = "." "(" Tipo ")" .
Argumentos = "(" [ ( ListaExpresión | Tipo [ "," ListaExpresión ] )
[ "..." ] [ "," ] ] ")" .
x
2
(s + ".txt")
f(3.1415, true)
Punto{1, 2}
m["foo"]
s[i : j + 1]
obj.color
f.p[i].x()
Selectores
Para una expresión primaria x
que no es un
nombre de paquete, la expresión selectora
x.f
denota el campo o método f
del valor de x
(o, a veces *x
; véase más
adelante). El identificador de f
se conoce como el (campo o método)
selector; no debe ser el identificador blanco.
El tipo de la expresión selectora es el tipo de f
. Si x
es el nombre
del paquete, consulta la sección sobre
identificadores cualificados.
Un selector f
puede denotar un campo o método f
de un tipo T
, o bien
se puede referir a un campo o método f
anidado en un
campo anónimo de T
. El número de campos anónimos
atravesados para alcanzar f
se conoce como su profundidad en T
. La
profundidad de un campo o método f
declarada en T
es cero. La
profundidad de un campo o método f
declarado en un campo anónimo A
en
T
es la profundidad de f
en A
más uno.
Las siguientes reglas se aplican a los selectores:
- Para un valor
x
de tipoT
o*T
dondeT
no es un puntero o tipo interfaz,x.f
denota el campo o método a la profundidad mínima enT
donde hay talf
. Si no hay exactamente unaf
con menor profundidad, la expresión selectora es ilegal. - Para un valor
x
de tipoI
, dondeI
es un tipo interfaz,x.f
denota el método real con el nombref
del valor dinámico dex
. Si no hay ningún método con el nombref
en el conjunto de métodos deI
, la expresión selectora es ilegal. - Como excepción, si el tipo de
x
es un tipo puntero nombrado y(*x).f
es una expresión selectora válida que denota un campo (pero no un método),x.f
es la abreviatura de(*x).f
. - En todos los demás casos,
x.f
es ilegal. - Si
x
es de tipo puntero y tiene el valornil
yx.f
denota un campo de estructura, asignando o evaluando ax.f
provoca pánico en tiempo de ejecución. - Si
x
es de tipo interfaz y tiene el valornil
, llamar o evaluar el métodox.f
provoca pánico en tiempo de ejecución.
Por ejemplo, dadas las declaraciones:
type T0 struct {
x int
}
func (*T0) M0()
type T1 struct {
y int
}
func (T1) M1()
type T2 struct {
z int
T1
*T0
}
func (*T2) M2()
type Q *T2
var t T2 // con t.T0 != nil
var p *T2 // con p != nil y (*p).T0 != nil
var q Q = p
uno puede escribir:
t.z // t.z
t.y // t.T1.y
t.x // (*t.TO).x
p.z // (*p).z
p.y // (*p).T1.y
p.x // (*(*p).T0).x
q.x // (*(*q).T0).x (*q).x es un campo selector válido
p.M2() // p.M2() M2 espera receptor de *T2
p.M1() // ((*p).T1).M1() M1 espera receptor de T1
p.M0() // ((&(*p).T0)).M0() M0 espera receptor de *T0, ve
// la sección de Llamadas
pero lo siguiente no es válido:
q.M0() // (*q).M0 es válido pero no un campo selector
Expresiones método
Si M
está en el conjunto de métodos del tipo
T
, T.M
es una función que se puede llamar como una función regular con
los mismos argumentos que M
prefijados por un argumento adicional que es
el receptor del método.
ExpreMétodo = TipoReceptor "." NombreMétodo .
TipoReceptor = NombreTipo | "(" "*" NombreTipo ")" | "(" TipoReceptor ")" .
Considera un tipo estructura T
con dos métodos, Mv
, cuyo receptor es
de tipo T
y Mp
, cuyo receptor es de tipo *T
.
type T struct {
a int
}
func (tv T) Mv(a int) int { return 0 } // receptor del valor
func (tp *T) Mp(f float32) float32 { return 1 } // puntero al receptor
var t T
La expresión
T.Mv
produce una función equivalente a Mv
pero con un receptor explícito como
primer argumento; el cuál tiene la firma
func(tv T, a int) int
Esa función se puede invocar normalmente con un receptor explícito, por lo que estas cinco llamadas son equivalentes:
t.Mv(7)
T.Mv(t, 7)
(T).Mv(t, 7)
f1 := T.Mv; f1(t, 7)
f2 := (T).Mv; f2(t, 7)
Del mismo modo, la expresión
(*T).Mp
produce un valor función representando a Mp
con la firma
func(tp *T, f float32) float32
Para un método con un receptor de valor, se puede derivar una función con un puntero receptor explícito, por lo tanto
(*T).Mv
produce un valor función que representa a Mv
con la firma
func(tv *T, a int) int
Tal función direcciona al receptor para crear un valor a pasar como el receptor para el método subyacente; el método no sobrescribe el valor cuya dirección se pasa en la llamada a la función.
El caso final, una función receptor-valor para un método receptor-puntero, es ilegal porque los métodos receptor-puntero no están en el conjunto de métodos del tipo valor.
Los valores función derivados de los métodos se invocan con la sintaxis de
llamada a función; el receptor se proporciona como el primer argumento de
la llamada. Es decir, dada f := T.Mv
, f
es invocada como f(t, 7)
no
t.f(7)
. Para construir una función que se vincule al receptor, usa una
función literal o un
valor método.
Es legal derivar un valor función desde un método de un tipo interfaz. La función resultante tiene un receptor explícito de ese tipo interfaz.
Valores método
Si la expresión x
tiene tipo estático T
y M
está en el
conjunto de métodos del tipo T
, x.M
se
conoce como un valor método. El valor método x.M
es un valor de
función que es invocable con los mismos argumentos que una llamada al
método x.M
. La expresión x
se evalúa y se guarda durante la
evaluación del valor método; la copia guardada se utiliza entonces como el
receptor en cualquier llamada que se pueda ejecutar después.
El tipo T
puede ser un tipo interfaz o no interfaz.
Como en la explicación de la expresión método
anterior, considera un tipo estructura T
con dos métodos, Mv
, cuyo
receptor es de tipo T
y Mp
, cuyo receptor es de tipo *T
.
type T struct {
a int
}
func (tv T) Mv(a int) int { return 0 } // receptor del valor
func (tp *T) Mp(f float32) float32 { return 1 } // puntero al receptor
var t T
var pt *T
func makeT() T
La expresión
T.Mv
obtiene un valor de tipo función
func(int) int
Estas dos invocaciones son equivalentes:
t.Mv(7)
f := t.Mv; f(7)
Del mismo modo, la expresión
pt.Mp
obtiene un valor de tipo función
func(float32) float32
Como con los selectores, una referencia a un método no
interfaz con un receptor de valor utilizando un puntero automáticamente
desreferencia ese puntero: pt.Mv
es equivalente a (*pt).Mv
.
Al igual que con las llamadas a método, una referencia a un
método no interfaz con un receptor puntero utilizando un valor
direccionable automáticamente tomará la dirección de ese valor: t.Mp
es
equivalente a (&t).Mp
.
f := t.Mv; f(7) // como t.Mv(7)
f := pt.Mp; f(7) // como pt.Mp(7)
f := pt.Mv; f(7) // como (*pt).Mv(7)
f := t.Mp; f(7) // como (&t).Mp(7)
f := makeT().Mp // no válido: el resultado de makeT() no es direccionable
Aunque los ejemplos anteriores utilizan tipos no interfaz, también es legal crear un valor método desde un valor de tipo interfaz.
var i interface { M(int) } = miValor
f := i.M; f(7) // como i.M(7)
Expresiones índice
Una expresión primaria de la forma
a[x]
denota el elemento del arreglo, puntero a arreglo, sector, cadena o mapa
a
indexado por x
. El valor x
se conoce cómo el índice del arreglo
o clave del mapa, respectivamente. Se aplican las siguientes reglas:
Si a
no es un mapa:
- el índice
x
debe ser de tipo entero o sin tipo; estar en el rango si0 <= x < len(a)
, de lo contrario está fuera de rango - una constante índice no debe ser negativa y representable
por un valor de tipo
int
Para a
de tipo arreglo A
:
- una constante índice debe estar en rango
- si
x
está fuera de rango en tiempo de ejecución, se produce pánico en tiempo de ejecución a[x]
es el elemento del arreglo en el índicex
y el tipo dea[x]
es el tipo del elementoA
Para el puntero a
al tipo arreglo:
a[x]
es la abreviatura de(*a)[x]
Para a
de tipo sector S
:
- si
x
está fuera de rango en tiempo de ejecución, se produce pánico en tiempo de ejecución a[x]
es el elemento del sector en el índicex
y el tipo dea[x]
es el tipo del elementoS
Para a
de tipo cadena:
- una constante índice debe estar en rango si la cadena
a
también es constante - si
x
está fuera de rango en tiempo de ejecución, se produce pánico en tiempo de ejecución a[x]
es el valor byte no constante en el índicex
y el tipo dea[x]
esbyte
- nada se puede asignar a
a[x]
Para a
de tipo mapa M
:
- el tipo de
x
debe ser asignable al tipo de la claveM
- si el mapa contiene una entrada con clave
x
,a[x]
es el valor del mapa con clavex
y el tipo dea[x]
es el valor del tipoM
- si el mapa es
nil
o no contiene dicha entrada,a[x]
es el valor cero para el valor del tipoM
De lo contrario a[x]
es ilegal.
Una expresión índice en un mapa a
de tipo map[K]V
utilizado en una
asignación o iniciación de la forma especial
v, ok = a[x]
v, ok := a[x]
var v, ok = a[x]
adicionalmente arroja un valor lógico sin tipo. El valor de ok
es true
si la clave x
está presente en el mapa y false
de lo contrario.
Asignar a un elemento de un mapa nil
provoca
pánico en tiempo de ejecución.
Expresiones sector
Las expresiones sector construyen una subcadena o un sector a partir de una cadena, arreglo, puntero a arreglo o sector. Hay dos variantes: una forma simple que especifica una cota baja y alta, y una forma completa que también especifica un límite en capacidad.
Expresiones sector simples
Para una cadena, arreglo, puntero a arreglo o sector a
, la expresión
primaria
a[bajo : alto]
construye una subcadena o un sector. Los índices bajo
y alto
seleccionan los elementos del operando a
que aparecen en el
resultado. El resultado tiene índices que comienzan en 0 y una longitud
igual a alto
- bajo
. Después de seccionar el arreglo a
a := [5]int{1, 2, 3, 4, 5}
s := a[1:4]
el sector s
tiene el tipo []int
, longitud 3, capacidad 4 y los
elementos:
s[0] == 2
s[1] == 3
s[2] == 4
Por conveniencia, cualquiera de los índices se puede omitir. A falta del
índice bajo
el valor predeterminado es cero; a falta del índice alto
el predeterminado es la longitud del operando seccionado:
a[2:] // igual que a[2 : len(a)]
a[:3] // igual que a[0 : 3]
a[:] // igual que a[0 : len(a)]
Si a
es un puntero a un arreglo, a[bajo : alto]
es la abreviatura de
(*a)[bajo : alto]
.
Para arreglos o cadenas, los índices están en rango si 0
<= bajo
<=
alto
<= len(a)
, de lo contrario estan fuera de rango. Para
sectores, el índice del límite superior es la capacidad del sector
cap(a)
en lugar de la longitud. Una constante índice no
debe ser negativa y representable por un valor de tipo int
; para
arreglos o cadenas constantes, las constantes índice también deben estar
en rango. Si ambos índices son constantes, deben satisfacer bajo <=
alto
. Si los índices están fuera de rango en tiempo de ejecución, se
produce pánico en tiempo de ejecución.
Excepto para las cadenas sin tipo, si el operando
seccionado es una cadena o un sector, el resultado de la operación de
seccionado es un valor no constante del mismo tipo que el operando. Para
operandos cadena sin tipo el resultado es un valor no constante de tipo
cadena
. Si el operando seccionado es un arreglo, debe ser
direccionable y el resultado de la operación de
seccionado es un sector con el mismo tipo de elemento que el arreglo.
Si el operando seccionado de una expresión sector válida es un sector
nil
, el resultado es un sector nil
. De lo contrario, el resultado
comparte su arreglo subyacente con el operando.
Expresiones sector complejas
Para un arreglo, puntero a arreglo o sector a
(pero no una cadena), la
expresión primaria
a[bajo : alto : máx]
construye un sector del mismo tipo y con la misma longitud y elementos que la simple expresión sector a[bajo : alto]
. Además, esta controla la capacidad del sector resultante estableciéndola en máx - bajo
. Únicamente se puede omitir el primer índice; su valor predeterminado es 0.
Después de seccionar el arreglo a
a := [5]int{1, 2, 3, 4, 5}
t := a[1:3:5]
el sector t
tiene el tipo []int
, longitud 2, capacidad 4 y los
elementos
t[0] == 2
t[1] == 3
Cómo para las expresiones sector simples, si a
es un puntero a un
arreglo, a[bajo : alto : máx]
es la abreviatura de
(*a)[bajo : alto : máx]
. Si el operando seccionado es un arreglo, debe
ser direccionable.
Los índices están en rango si 0 <= bajo <= alto <= máx <= cap(a)
, si
no están fuera de rango. Una constante índice no debe
ser negativa y representable por un valor de tipo int
; para arreglos,
los índices constantes también deben estar en rango. Si varios índices
son constantes, las constantes que están presentes deben estar en rango
relativo de uno al otro. Si los índices están fuera de rango en tiempo de
ejecución, se produce
pánico en tiempo de ejecución.
Tipos aserción
Para una expresión x
de tipo interfaz y un tipo T
,
la expresión primaria
x.(T)
afirma que x
no es nil
y que el valor almacenado en x
es de tipo
T
. La notación x.(T)
se llama tipo aserción.
Exactamente, si T
no es un tipo interfaz, x.(T)
afirma que el tipo
dinámico de x
es idéntico al tipo de T
. En este
caso, T
debe implementar la (interfaz) del tipo
x
; de lo contrario el tipo aserción no es válido ya que no es posible
para x
almacenar un valor de tipo T
. Si T
es un tipo interfaz,
x.(T)
afirma que el tipo dinámico de x
implementa la interfaz T
.
Si el tipo aserción se mantiene, el valor de la expresión es el valor
almacenado en x
y su tipo es T
. Si el tipo aserción es falso, se
produce pánico en tiempo de ejecución.
En otras palabras, aunque el tipo dinámico de x
es conocido solo en
tiempo de ejecución, el tipo de x.(T)
es conocido por ser T
en un
programa correcto.
var x interface{} = 7 // x tiene tipo dinámico int y valor 7
i := x.(int) // i tiene tipo int y valor 7.
type I interface { m() }
var y I
s := y.(string) // ilegal: "string" no implementa a I (falta el
// método m)
r := y.(io.Reader) // r tiene tipo io.Reader e y debe implementar
// ambas I e io.Reader
Un tipo aserción usado en una asignación o iniciación de la forma especial
v, ok = x.(T)
v, ok := x.(T)
var v, ok = x.(T)
adicionalmente arroja un valor lógico sin tipo. El valor de ok
es true
si se mantiene la aserción. De lo contrario, es false
y el valor de v
es el valor cero para el tipo T
. En este caso no
ocurre ningún pánico en tiempo de ejecución.
Llamadas
Dada una expresión f
de tipo función F
:
f(a1, a2, … an)
invoca a f
con argumentos a1, a2, … an
. A excepción de un caso
especial, los argumentos deben ser expresiones de un solo valor
asignable a los tipos de parámetros de F
y son
evaluados antes de llamar a la función. El tipo de la expresión es el
tipo de resultado de F
. Una invocación a método es similar, pero el
método en sí se especifica como un selector sobre un valor del tipo
receptor para el método.
math.Atan2(x, y) // invoca a la función
var pt *Punto
pt.Scale(3.5) // llamada a método con pt como receptor
En una llamada a función, el valor de la función y los argumentos se evalúan en el orden habitual. Después de evaluarlos, los parámetros de la llamada se pasan por valor a la función y la función llamada comienza su ejecución. Los parámetros de retorno de la función de nuevo se pasan por valor a la función llamada cuando la función regresa.
Llamar a un valor de función nil
provoca
pánico en tiempo de ejecución.
Como caso especial, si los valores de retorno de una función o método g
son iguales en número e individualmente asignables a los parámetros de
otra función o método f
, entonces la llamada f(g(parámetros-de-g))
invocará a f
después de vincular en orden los valores de retorno de g
a los parámetros de f
. La llamada a f
no debe contener ningún otro
parámetro salvo la llamada a g
y g
debe tener por lo menos un valor
de retorno. Si f
tiene un parámetro ...
final, este se asigna el
valor de retorno de g
que permanece después de la asignación de los
parámetros normales.
func Divide(s string, pos int) (string, string) {
return s[0:pos], s[pos:]
}
func Junta(s, t string) string {
return s + t
}
if Junta(Divide(valor, len(valor)/2)) != valor {
log.Panic("prueba fallida")
}
Una llamada al método x.m()
es válida si el
conjunto de métodos de (el tipo de) x
contiene
m
y la lista de argumentos se puede asignar a la lista de parámetros de
m
. Si x
es direccionable y el conjunto de
métodos de &x
contiene a m
, x.m()
es una abreviatura de (&x).m()
:
var p Punto
p.Escala(3.5)
No hay ningún tipo método distinto y no hay métodos literales.
Pasando argumentos a parámetros ...
Si f
es variadica con un parámetro final p
de tipo
...T
, entonces dentro de f
de tipo p
es equivalente al tipo
[]T
. Si f
se invoca sin argumentos reales de p
, el valor pasado a
f
es nil
. De lo contrario, el valor pasado es un nuevo sector de tipo
[]T
con un nuevo arreglo subyacente cuyos elementos sucesivos son los
argumentos reales, todos ellos deben ser asignables a
T
. Por tanto, la longitud y capacidad del sector es el número de
argumentos ligados a p
y pueden ser diferentes para cada sitio de la
llamada.
Dada la función y llamadas
func Saluda(prefijo string, quién ...string)
Saluda("nadie")
Saluda("hola", "José", "Ana", "Elena")
dentro de Saluda
, quién
tendrá el valor nil
en la primera llamada y
[]string{"José", "Ana", "Elena"}
en la segunda.
Si el argumento final es asignable a un tipo sector []T
, se puede pasar sin cambios como el valor de un parámetro ...T
si el argumento está seguido por ...
. En este caso no se crea un nuevo sector.
Dado el sector s
y la llamada
s := []string{"Jaime", "Jazmín"}
Saluda("adiós:", s...)
dentro de Saluda
, quién
tendrá el mismo valor que s
con el mismo
arreglo subyacente.
Operadores
Los operadores combinan operandos en expresiones.
Expresión = ExprUnaria | Expresión op_binario ExprUnaria .
ExprUnaria = ExprePrimaria | op_unario ExprUnaria .
op_binario = "||" | "&&" | op_rel | op_adi | op_mul .
op_rel = "==" | "!=" | "<" | "<=" | ">" | ">=" .
op_adi = "+" | "-" | "|" | "^" .
op_mul = "*" | "/" | "%" | "<<" | ">>" | "&" | "&^" .
op_unario = "+" | "-" | "!" | "^" | "*" | "&" | "<-" .
Las comparaciones se dilucidan en otro lugar. Para otros operadores binarios, los tipos de los operandos deben ser idénticos a menos que la operación involucre desplazamientos o constantes sin tipo. Para las operaciones que solo implican constantes, consulta la sección sobre expresiones constantes.
Excepto para las operaciones de desplazamiento, si un operando es una constante sin tipo y el otro operando no, la constante se convierte al tipo del otro operando.
El operando de la derecha en una expresión de desplazamiento debe tener tipo entero sin signo o ser una constante sin tipo que se pueda convertir al tipo entero sin signo. Si el operando de la izquierda de una expresión de desplazamiento no constante es una constante sin tipo, el tipo de la constante es lo que sería si la expresión de desplzamiento fuera reemplazada solo por su operando izquierdo.
var s uint = 33
var i = 1<<s // 1 tiene tipo int
var j int32 = 1<<s // 1 tiene tipo int32; j == 0
var k = uint64(1<<s) // 1 tiene tipo uint64; k == 1<<33
var m int = 1.0<<s // 1.0 tiene tipo int
var n = 1.0<<s != i // 1.0 tiene tipo int; n == false si los enteros
// son de 32 bits
var o = 1<<s == 2<<s // 1 y 2 tiene tipo int; o == true si los enteros
// son de 32 bits
var p = 1<<s == 1<<33 // ilegal si los enteros son de 32 bits: 1 tiene
// tipo int, pero 1<<33 desborda el int
var u = 1.0<<s // ilegal: 1.0 tiene tipo float64, no se puede
// desplazar
var u1 = 1.0<<s != 0 // ilegal: 1.0 tiene tipo float64, no se puede
// desplazar
var u2 = 1<<s != 1.0 // ilegal: 1.0 tiene tipo float64, no se puede
// desplazar
var v float32 = 1<<s // ilegal: 1 tiene tipo float64, no se puede
// desplazar
var w int64 = 1.0<<33 // 1.0<<33 es una expresión de desplazamiento
// constante
Precedencia de operadores
Los operadores unarios tienen la precedencia más alta. Puesto que ++
y
--
forman declaraciones de operadores, no expresiones, caen fuera de la
jerarquía del operador. Por consecuencia, la declaración *p++
es la
misma que (*p)++
.
Hay cinco niveles de precedencia en los operadores binarios. Los
operadores de multiplicación se vinculan fuertemente, seguidos de los
operadores de adición, operadores de comparación &&
(AND
lógico) y
finalmente ||
(OR
lógico):
Precedencia Operador
5 * / % << >> & &^
4 + - | ^
3 == != < <= > >=
2 &&
1 ||
Los operadores binarios de la misma precedencia se asocian de izquierda a
derecha. Por ejemplo, x / y * z
es lo mismo que (x / y) * z
.
+x
23 + 3*x[i]
x <= f()
^a >> b
f() || g()
x == y+1 && <-chanPtr > 0
Operadores aritméticos
Los operadores aritméticos se aplican a los valores numéricos y producen
un resultado del mismo tipo que el primer operando. Los cuatro operadores
aritméticos estándar (+
, -
, *
y /
) se aplican a tipos numéricos
enteros, de coma flotante y complejos; +
también se aplica a cadenas de
caracteres. Todos los demás operadores aritméticos solo se aplican a
enteros.
+ suma valores numéricos enteros, de coma flotante,
complejos y cadenas
- diferencia valores numéricos enteros, de coma flotante,
complejos
* producto valores numéricos enteros, de coma flotante,
complejos
/ cociente valores numéricos enteros, de coma flotante,
complejos
% residuo enteros
& AND a nivel de bits enteros
| OR a nivel de bits enteros
^ XOR a nivel de bits enteros
&^ limpieza de bit (y NOT) enteros
<< desplaza a la izquierda entero << entero sin signo
>> desplaza a la derecha entero >> entero sin signo
Las cadenas se pueden concatenar usando el operador +
o el operador de
asignación +=
:
s := "hola" + string(c)
s += " y adiós"
La adición de cadenas crea una nueva cadena concatenando los operandos.
Para dos valores enteros x
e y
, el cociente entero q = x / y
y
residuo r = x % y
satisfacen las siguientes relaciones:
x = q*y + r and |r| < |y|
con x / y
truncado hacia cero
("división truncada").
x y x / y x % y
5 3 1 2
-5 3 -1 -2
5 -3 -1 2
-5 -3 1 -2
Como una excepción a esta regla, si el dividendo x
es el valor negativo
para el tipo int de x
, el cociente q = x / -1
es igual a x
(y r =
0
).
x, q
int8 -128
int16 -32768
int32 -2147483648
int64 -9223372036854775808
Si el divisor es una constante, esta no debe ser cero. Si
el divisor es cero en tiempo de ejecución, se produce
pánico en tiempo de ejecución. Si el
dividendo no es negativo y el divisor es una constante potencia de 2, la
división se puede reemplazar por un desplazamiento a la derecha y el
cálculo del residuo se podrá sustituir por una operación AND
bit a bit:
x x / 4 x % 4 x >> 2 x & 3
11 2 3 2 3
-11 -2 -3 -3 1
Los operadores de desplazamiento desplazan el operando de la izquierda por
el valor de desplazamiento especificado por el operando de la
derecha. Estos implementan desplazamiento aritmético si el operando de la
izquierda es un entero con signo y los desplazamientos lógicos si se trata
de un entero sin signo. No hay un límite superior para el valor del
desplazamiento. Los desplazamientos se comportan como si el operando de la
izquierda se desplazara n
veces por 1 para un valor de desplazamiento
n
. Como resultado, x << 1
es lo mismo que x*2
y x >> 1
es lo
mismo que x/2
, pero truncado hacia el infinito negativo.
Para operandos enteros, los operadores unarios +
, -
y ^
se definen
de la siguiente manera:
+x es 0 + x
-x negación es 0 - x
^x complemento bit a bit es m ^ x dónde m = "todos los bits puestos a 1"
para x sin signo
y m = -1 para x con signo
Para números de coma flotante y complejos, +x
es lo mismo que x
,
mientras que -x
es la negación de x
. El resultado de una división de
coma flotante o compleja entre cero no se especifica más allá del estándar
IEEE-754; cuando se produce
pánico en tiempo de ejecución es
específico de la implementación.
Desbordamiento de enteros
Para valores enteros sin signo, las operaciones +
, -
, *
y <<
se
calculan módulo 2 n , donde n es el número de bits del tipo
entero sin signo. En términos generales, estas
operaciones de enteros sin signo descartan bits altos sobre
desbordamiento y los programas pueden depender de ``aproximaciones''.
Para enteros con signo, las operaciones +
, -
, *
y <<
legalmente
pueden desbordar y si el valor resultante existe y está definido
deterministamente por la representación del entero con signo, la operación
y sus operandos. No se lanza una excepción como resultado de
desbordamiento. Un compilador no puede optimizar el código bajo el
supuesto de que no se produce desbordamiento. Por ejemplo, no puede
suponer que x < x + 1
siempre sea cierto.
Operadores de comparación
Los operadores de comparación comparan dos operandos y producen un valor lógico sin tipo.
== igual
!= no igual
< menor
<= menor o igual
> mayor
>= mayor o igual
En cualquier comparación, el primer operando debe ser asignable al tipo del segundo operando o viceversa.
Los operadores de igualdad ==
y !=
se aplican a los operandos que son
comparables. Los operadores de ordenamiento <
, <=
, >
y >=
se
aplican a los operandos que son ordenados. Estos términos y el
resultado de las comparaciones se definen de la siguiente manera:
- Los valores lógicos son comparables. Dos valores lógicos son iguales si
ambos son
true
o ambos sonfalse
. - Los valores enteros son comparables y ordenados, de la forma habitual.
- Los valores de coma flotante son comparables y ordenados, como lo define la norma IEEE-754.
- Los valores complejos son comparables. Dos valores complejos
u
yv
son iguales si ambos sonreal(u) == real(v)
yimag(u) == imag(v)
. - Los valores de cadena son comparables y ordenados, léxicamente bit a bit.
- Los valores puntero son comparables. Dos valores puntero son iguales si
apuntan a la misma variable o si ambos tienen valor
nil
. Los punteros a distinta variable de tamaño cero pueden o no ser iguales. - Los valores de canal son comparables. Dos valores de canal son iguales
si fueron creados por la misma llamada a
make
o si ambos tienen valornil
. - Los valores de interfaz son comparables. Dos valores de interfaz son
iguales si tienen idénticos tipos dinámicos y
valores dinámicos iguales o si ambos tienen valor
nil
. - Un valor
x
de tipo no interfazX
y un valort
de tipo interfazT
son comparables cuando los valores del tipoX
son comparables yX
implementa aT
. Ellos son iguales si el tipo dinámico det
es idéntico aX
y el valor dinámico det
es igual ax
. - Los valores estructura son comparables si todos sus campos son comparables. Dos valores estructura son iguales si sus correspondientes campos no blancos son iguales.
- Los valores de arreglo son comparables si el tipo de los valores de los elementos del arreglo son comparables. Dos valores de arreglo son iguales si sus correspondientes elementos son iguales.
Una comparación de dos valores interfaz con tipos dinámicos idénticos provoca pánico en tiempo de ejecución si los valores de ese tipo no son comparables. Este comportamiento se aplica no solo a las comparaciones de valores interfaz directos sino también al comparar arreglos de valores interfaz o estructuras con campos de valor interfaz.
Los valores de sector, mapa y función no son comparables. Sin embargo,
como un caso especial, un valor de sector, mapa o función se puede
comparar con el identificador predeclarado nil
. La comparación de
valores puntero, canal e interfaz a nil
también se permite y sigue las
reglas generales anteriores.
const c = 3 < 4 // c es true, la constante lógica sin tipo
type MiBooleano bool
var x, y int
var (
// el resultado de una comparación es un bool sin tipo.
// Aplican las reglas de asignación habituales.
b3 = x == y // b3 es de tipo bool
b4 bool = x == y // b4 es de tipo bool
b5 MiBooleano = x == y // b5 es de tipo MiBooleano
)
Operadores lógicos
Los operadores lógicos se aplican a valores booleanos y producen un resultado del mismo tipo que los operandos. El operando de la derecha se evalúa de forma condicional.
&& AND condicional p && q es "si p entonces q si no false"
|| OR condicional p || q es "si p entonces true si no q"
! NOT !p es "no p"
Operadores de dirección
Para un operando x
de tipo T
, la operación de dirección &x
genera un
puntero de tipo *T
a x
. El operando debe ser direccionable, es
decir, ya sea una variable, indirección de puntero o la operación de
indexación de un sector; o un selector de campo de un operando
direccionable a estructura; o una operación de indexación de arreglo de un
arreglo direccionable. Como excepción al requisito de direccionamiento,
x
también puede ser un (posiblemente encerrado entre paréntesis)
literal compuesto. Si la evaluación de x
pudiera provocar
pánico en tiempo de ejecución, la
evaluación de x
también lo haría.
Para un operando x
de tipo puntero *T
, la indirección de puntero *x
denota la variable de tipo T
apuntada por x
. Si x
es
nil
, un intento de evaluar *x
provocará
pánico en tiempo de ejecución.
&x
&a[f(2)]
&Punto{2, 3}
*p
*pf(x)
var x *int = nil
*x // provoca pánico en tiempo de ejecución
&*x // provoca pánico en tiempo de ejecución
Operador de recepción
Para un operando ch
de tipo canal, el valor de la
operación de recepción <-ch
es el valor recibido desde el canal ch
. La
dirección del canal debe permitir operaciones de recepción y el tipo de la
operación de recepción es el tipo de elemento del canal. Los bloques de
expresión hasta un cierto valor están disponibles. Recibir desde un canal
nil
lo bloquea por siempre. Una operación de recepción en un canal
cerrado siempre se puede proceder inmediatamente, dando el tipo
de elemento de valor cero después de que se hayan
recibido todos los valores previamente enviados.
v1 := <-ch
v2 = <-ch
f(<-ch)
<-estrobo // Espera hasta un pulso del reloj
// y desecha el valor recibido
Una expresión de recepción utilizada en una asignación o iniciación de la forma especial
x, ok = <-ch
x, ok := <-ch
var x, ok = <-ch
Adicionalmente arroja un resultado lógico sin tipo informando si la
comunicación tuvo éxito. El valor de ok
es true
si el valor recibido
fue entregado por una operación de envío correcta en el canal, o false
si se trata de un valor cero generado debido a que el canal está cerrado y
vacío.
Conversiones
Las conversiones son expresiones de la forma T(x)
, donde T
es un tipo
y x
es una expresión que se puede convertir al tipo T
.
Conversión = Tipo "(" Expresión [ "," ] ")" .
Si el tipo comienza con el operador *
o <-
, o si el tipo comienza con
la palabra clave func
y no tiene una lista de resultados, este se debe
encerrar entre paréntesis cuando sea necesario para evitar ambigüedades:
*Punto(p) // igual que *(Punto(p))
(*Punto)(p) // p se convierte a *Punto
<-chan int(c) // igual que <-(chan int(c))
(<-chan int)(c) // c se convierte a <-chan int
func()(x) // firma de la función func() x
(func())(x) // x se convierte a func()
(func() int)(x) // x se convierte a func() int
func() int(x) // x se convierte a func() int (sin ambigüedades)
Un valor constante x
se puede convertir al tipo T
en
cualquiera de estos casos:
x
es representable por un valor de tipoT
.x
es una constante de coma flotante,T
es un tipo de coma flotante yx
es representable por medio de un valor de tipoT
después de redondeado usando las reglas de redondeo al par IEEE 754. La constanteT(x)
es el valor redondeado.x
es una constante entera yT
es un tipo cadena. En este caso, aplica la misma regla que para lax
no constante.
La conversión de una constante produce una constante tipada como resultado.
uint(iota) // valor iota de tipo uint
float32(2.718281828) // 2.718281828 de tipo float32
complex128(1) // 1.0 + 0.0i de tipo complex128
float32(0.49999999) // 0.5 de tipo float32
string('x') // "x" de tipo string
string(0x266c) // "♬" de tipo string
MiCadena("foo" + "bar") // "foobar" de tipo MiCadena
string([]byte{'a'}) // no es una constante: []byte{'a'} no es una
// constante
(*int)(nil) // no es una constante: nil no es una constante,
// *int no es un tipo booleano, numérico o string
int(1.2) // ilegal: 1.2 no se puede representar como un int
string(65.0) // ilegal: 65.0 no es una constante entera
Un valor no constante x
se puede convertir al tipo T
en cualquiera de
estos casos:
x
es asignable aT
.x
yT
tienen idénticos tipos subyacentes.x
yT
son tipos puntero sin nombre y sus tipos puntero base tienen tipos subyacentes idénticos.- ambos
x
yT
son tipos enteros o de coma flotante. - ambos
x
yT
son tipos complejos. x
es un número entero o un sector de bytes o runes yT
es un tipo cadena.x
es una cadena yT
es un sector de bytes o runes.
Se aplican normas específicas para conversiones (no constantes) entre
tipos numéricos o desde y hacia un tipo cadena. Estas conversiones pueden
cambiar la representación de x
e incurrir en un costo en tiempo de
ejecución. Todas las demás conversiones solo cambian el tipo pero no la
representación de x
.
No hay ningún mecanismo lingüístico para convertir entre punteros y
números enteros. Los paquetes inseguros
implementan
esta funcionalidad en circunstancias restringidas.
Conversiones entre tipos numéricos
Para la conversión de valores numéricos no constantes, se aplican las siguientes reglas:
- Cuando conviertas entre tipos enteros, si el valor es un entero con
signo, este signo se extiende implícitamente a precisión infinita; de
lo contrario, se extiende a cero. Entonces se trunca para encajar en
el tamaño del tipo de resultado. Por ejemplo, si
v := uint16(0x10F0)
, entonces,uint32(int8(v)) == 0xFFFFFFF0
. La conversión siempre produce un valor válido; no hay ninguna indicación de desbordamiento. - Cuando se convierte un número de coma flotante a entero, la fracción se descarta (truncando a cero).
- Al convertir un número entero o de coma flotante a un tipo de coma
flotante, o un número complejo a otro tipo complejo, el valor del
resultado se redondea a la precisión especificada por el tipo destino.
Por ejemplo, el valor de una variable
x
de tipofloat32
se puede almacenar usando precisión adicional más allá de la de un número IEEE-754 de 32 bits, perofloat32(x)
representa el resultado de redondear el valor dex
a precisión de 32 bits. Del mismo modo,x + 0.1
puede utilizar más de 32 bits de precisión, perofloat32(x + 0.1)
no lo hace.
En todas las conversiones no constantes involucrando valores de coma flotante o complejos, si el tipo del resultado no puede representar el valor de la conversión es éxitosa, pero el valor del resultado depende de la implementación.
Conversiones a y desde un tipo cadena
- La conversión de un valor entero con o sin signo a un tipo cadena
produce una cadena que contiene la representación UTF-8 del entero. Los
valores fuera de rango de caracteres Unicode válidos se convierten a
"\uFFFD"
.
string('a') // "a"
string(-1) // "\ufffd" == "\xef\xbf\xbd"
string(0xf8) // "\u00f8" == "ø" == "\xc3\xb8"
type MiCadena string
MiCadena(0x65e5) // "\u65e5" == "日" == "\xe6\x97\xa5"
- La conversión de un sector de bytes a un tipo cadena produce una cadena cuyos bytes sucesivos son los elementos del sector.
string([]byte{'h', '\xc3', '\xb8', 'l', 'a'}) // "høla"
string([]byte{}) // ""
string([]byte(nil)) // ""
type MisBytes []byte
string(MisBytes{'h', '\xc3', '\xb8', 'l', 'a'}) // "høla"
- La conversión de un sector de runes a un tipo cadena produce una cadena que es la concatenación de los valores runes individuales convertidos a cadenas.
string([]rune{0x767d, 0x9d6c, 0x7fd4}) // "\u767d\u9d6c\u7fd4" == "白鵬翔"
string([]rune{}) // ""
string([]rune(nil)) // ""
type MisRunes []rune
string(MisRunes{0x767d, 0x9d6c, 0x7fd4}) // "\u767d\u9d6c\u7fd4" == "白鵬翔"
- La conversión de un valor de tipo cadena a un sector de tipo bytes produce un sector cuyos elementos sucesivos son los bytes de la cadena.
[]byte("høla") // []byte{'h', '\xc3', '\xb8', 'l', 'a'}
[]byte("") // []byte{}
MisBytes("høla") // []byte{'h', '\xc3', '\xb8', 'l', 'a'}
- La conversión de un valor de tipo cadena a un sector de tipo runes produce un sector que contiene los caracteres Unicode individuales de la cadena.
[]rune(MiCadena("白鵬翔")) // []rune{0x767d, 0x9d6c, 0x7fd4}
[]rune("") // []rune{}
MisRunes("白鵬翔") // []rune{0x767d, 0x9d6c, 0x7fd4}
Expresiones constantes
Las expresiones constantes pueden contener solo operandos constantes y se evalúan en tiempo de compilación.
Las constantes booleanas sin tipo, numéricas y cadena se pueden utilizar como operandos siempre que sea legal el uso de un operando lógico, numérico o de tipo cadena, respectivamente. A excepción de las operaciones de desplazamiento, si los operandos de una operación binaria son diferentes clases de constantes sin tipo, la operación y, para operaciones no lógicas, el resultado utiliza el tipo que aparece más adelante en esta lista: número entero, rune, de coma flotante o complejo. Por ejemplo, una constante entera sin tipo dividida entre una constante compleja sin tipo produce una constante compleja sin tipo.
Una comparación constante siempre produce una constante lógica sin tipo. Si el operando de la izquierda de una expresión de desplazamiento constante es una constante sin tipo, el resultado es una constante entera; de lo contrario es una constante del mismo tipo que el operando de la izquierda, mismo que debe ser de tipo entero. La aplicación de todos los demás operadores a constantes sin tipo resultan en una constante sin tipo de la misma clase (es decir, una constante booleana, entera, de coma flotante, compleja o cadena).
const a = 2 + 3.0 // a == 5.0 (constante de coma flotante sin tipo)
const b = 15 / 4 // b == 3 (constante entera sin tipo)
const c = 15 / 4.0 // c == 3.75 (constante de coma flotante sin tipo)
const Θ float64 = 3/2 // Θ == 1.0 (tipo float64, 3/2 es una división
// entera)
const Π float64 = 3/2. // Π == 1.5 (tipo float64, 3/2. es la división de
// coma flotante)
const d = 1 << 3.0 // d == 8 (constante entera sin tipo)
const e = 1.0 << 3 // e == 8 (constante entera sin tipo)
const f = int32(1) << 33 // ilegal (constante 8589934592 desbordamiento
// int32)
const g = float64(2) >> 1 // ilegal (float64(2) es una constante de coma
// flotante con tipo)
const h = "foo" > "bar" // h == true (constante lógica sin tipo)
const j = true // j == true (constante lógica sin tipo)
const k = 'w' + 1 // k == 'x' (constante rune sin tipo)
const l = "hi" // l == "hi" (constante cadena sin tipo)
const m = string(k) // m == "x" (tipo cadena)
const Σ = 1 - 0.707i // (constante compleja sin tipo)
const Δ = Σ + 2.0e-4 // (constante compleja sin tipo)
const Φ = iota*1i - 1/1i // (constante compleja sin tipo)
La aplicación de la función incorporada complex
a un número entero sin
tipo, rune o a constantes de coma flotante produce una constante compleja
sin tipo.
const ic = complex(0, c) // ic == 3.75i (constante compleja sin tipo)
const iΘ = complex(0, Θ) // iΘ == 1i (tipo complex128)
Las expresiones constantes siempre se evalúan con exactitud; los valores intermedios y las constantes en sí mismas pueden requerir precisión significativamente mayor que la compatible con cualquier tipo predeclarado en el lenguaje. Las siguientes son declaraciones lícitas:
const Enorme = 1 << 100 // Enorme == 1267650600228229401496703205376
// (constante entera sin tipo)
const Cuatro int8 = Enorme >> 98 // Cuatro == 4 (tipo int8)
El divisor de una operación de división o residuo constante no debe ser cero:
3.14 / 0.0 // ilegal: división entre cero
Los valores de constantes tipadas siempre se deben representar con toda precisión como valores del tipo constante. Las siguientes expresiones constantes son ilegales:
uint(-1) // -1 no se puede representar como uint
int(3.14) // 3.14 no se puede representar como uint
int64(Enorme) // 1267650600228229401496703205376 no se puede representar
// como int64
Cuatro * 300 // operando 300 no se puede representar como int8 (tipo de
// Cuatro)
Cuatro * 100 // producto 400 no se puede representar como int8 (tipo de
// Cuatro)
La máscara utilizada por el operador complemento unario bit a bit ^
coincide con la regla para los no constantes: la máscara es toda 1s para
las constantes sin signo y -1 para las constantes con signo y sin tipo.
^1 // número entero constante sin tipo, igual a -2
uint8(^1) // ilegal: lo mismo que uint8(-2), -2 no se puede representar
como uint8
^uint8(1) // constante uint8 con tipo, igual que
0xFF ^ uint8(1) = uint8(0xFE)
int8(^1) // igual que int8(-2)
^int8(1) // igual que -1 ^ int8(1) = -2
Restricción de implementación: Un compilador puede utilizar redondeo al calcular coma flotante sin tipo o expresiones constantes complejas; ve la restricción de implementación en la sección de constantes. Este redondeo puede causar una expresión constante de coma flotante para ser válida en el contexto de un número entero, incluso si sería integral cuando se calcula usando una precisión infinita.
Orden de evaluación
A nivel de paquete, la iniciación de dependencias determina el orden de evaluación de expresiones de iniciación individuales en las declaraciones de variables. De lo contrario, al evaluar los operandos de una expresión, asignación o instrucción return, todas las llamadas a función, las llamadas a métodos y operaciones de comunicación se evalúan en orden léxico de izquierda a derecha.
Por ejemplo, en la asignación (función local)
y[f()], ok = g(h(), i()+x[j()], <-c), k()
y [f ()], ok = g (h (), i () + `x` [j ()],
a := 1
f := func() int { a++; return a }
x := []int{a, f()} // x puede ser [1, 2] o [2, 2]: el orden de
// evaluación entre a y f() no está
// especificado
m := map[int]int{a: 1, a: 2} // m puede ser {2: 1} o {2: 2}: el orden de
// evaluación entre la asignación de los
// dos mapas no está especificado
n := map[int]int{a: f()} // n puede ser {2: 3} o {3: 3}: el orden de
// evaluación entre la clave y el valor no
// está especificado
A nivel de paquete, la iniciación de dependencias redefine la regla de izquierda a derecha para las expresiones de iniciación individuales, pero no para operandos dentro de cada expresión:
var a, b, c = f() + v(), g(), sqr(u()) + v()
func f() int { return c }
func g() int { return a }
func sqr(x int) int { return x*x }
// las funciones u y v son independientes de todas las otras variables
// y funciones
Las llamadas a funciones suceden en el orden u()
, sqr()
, v()
, f()
,
v()
y g()
.
Operaciones de coma flotante en una sola expresión se evalúan de acuerdo a
la asociatividad de los operadores. Los paréntesis explícitos afectan a
la evaluación por razones imperiosas de la asociatividad predeterminada.
En la expresión x + (y + z)
la adición y + z
se realiza antes de sumar
x
.
Instrucciones
Control de ejecución de instrucciones.
Instrucción =
Declaración | DeclaEtiquetada | SimpleInstru |
InstruGo | InstruReturn | InstruBreak | InstruContinue | InstruGoto |
InstruFallthrough | Bloque | InstruIf | InstruSwitch | InstruSelect |
InstruFor | InstruDefer .
SimpleInstru = InstruVacía | DeclaExpresión | InstruEnvío | InstruIncDec |
Asignación | DeclaVarCorta .
Instrucción de terminación
Una instrucción de terminación es una de las siguientes:
- Una instrucción "return" o "goto".
- Una llamada a la función integrada
panic
. - Un bloque en el cual la lista de declaraciones termina en una declaración de terminación.
- Una instrucción "if" en la que:
- la rama "else" está presente y
- ambas ramas tienen instrucciones de terminación.
- Una instrucción "for" en la que:
- no hay instrucciones "break" refiriéndose a la instrucción "for" y
- la condición del bucle está ausente.
- Una instrucción "switch" en la que:
- ho hay instrucciones "break" refiriéndose a la instrucción "switch",
- hay un caso
default
y - la lista de instrucciones en cada caso, incluyendo a
default
, terminan en una instrucción de terminación o una posiblemente etiquetada instrucción "fallthrough".
- Una instrucción "select" en la que:
- no hay declaraciones "break" refiriéndose a la instrucción "select" y
- las listas de instrucciones en cada caso, incluyendo a
default
si está presente, terminan en una instrucción de terminación.
- Una instrucción etiquetada vinculada a una instrucción de terminación.
Todas las demás declaraciones no están terminando.
Una lista de instrucciones termina en una instrucción de terminación si la lista no está vacía y su instrucción final está terminando.
Instrucciones vacías
La instrucción vacía no hace nada.
InstruVacía = .
Instrucciones etiquetadas
Una instrucción etiquetada puede ser el destino de una instrucción goto
,
break
o continue
.
InstruEtiquetada = Etiqueta ":" Declaración .
Etiqueta = identificador .
Error: log.Panic("encontré un error")
Expresiones instrucción
A excepción de funciones integradas específicas, las llamadas a función, métodos y operaciones de recepción pueden aparecer en el contexto de una instrucción. Tales instrucciones pueden estar entre paréntesis.
InstruExpresión = Expresión .
Las siguientes funciones incorporadas no están permitidas en el contexto de una instrucción:
append cap complex imag len make new real
unsafe.Alignof unsafe.Offsetof unsafe.Sizeof
h(x+y)
f.Close()
<-ch
(<-ch)
len("foo") // ilegal si len es la función integrada
Instrucciones de envío
Una instrucción de envío transmite un valor en un canal. La expresión canal debe ser de tipo canal, la dirección del canal debe permitir las operaciones de envío y el tipo de valor a enviar debe ser asignable al tipo de elemento del canal.
InstruEnvío = Canal "<-" Expresión .
Canal = Expresión .
Tanto el canal como la expresión de valor se evalúan antes de que comience
la comunicación. Se bloquea la comunicación hasta que pueda proceder el
envío. Un envío en un canal sin búfer puede proceder si un receptor está
listo. Un envío en un canal con búfer puede proceder si hay espacio en el
búfer. Un envío en un canal cerrado procede provocando
pánico en tiempo de ejecución. Un envío
a un canal nil
se bloquea para siempre.
ch <- 3 // envía el valor 3 al canal ch
Instrucciones IncDec
Las instrucciones "++" y "--" incrementan o disminuyen sus operandos por
la constante sin tipo 1
. Al igual que con una asignación,
el operando debe ser direccionable o una
expresión de índice de mapa.
InstruIncDec = Expresión ( "++" | "--" ) .
Las siguientes instrucciones de asignación son semánticamente equivalentes:
Instrucción IncDec Asignación
x++ x += 1
x-- x -= 1
Asignaciones
asignación = ListaExpresión op_asignación ListaExpresión .
op_asignación = [ op_adi | op_mul ] "=" .
Cada operando del lado izquierdo debe ser
direccionable, una expresión de índice de mapa
o (solo para asignaciones =
) el
identificador blanco. Los operandos pueden estar
entre paréntesis.
x = 1
*p = f()
a[i] = 23
(k) = <-ch // lo mismo que: k = <-ch
Una operación de asignación x
op=
y
donde op es una operación
aritmética binaria es equivalente a x
=
x
op y
sino que solo
evalúa a x
una vez. La construcción op=
es un solo símbolo. En las
operaciones de asignación, tanto la lista de expresiones izquierda como la
derecha deben contener exactamente una expresión de valor único y la
expresión de la izquierda no debe ser el identificador blanco.
a[i] <<= 2
i &^= 1<<n
Una tupla de asignación establece los elementos individuales de una
operación de varios valores a una lista de variables. Hay dos formas. En
la primera, el operando de la derecha es una sola expresión de varios
valores, tal como una llamada a función, una operación de
canal o mapa, o un
tipo aserción. El número de operandos en el lado de la
mano izquierda debe coincidir con el número de valores. Por ejemplo, si
f
es una función que devuelve dos valores:
x, y = f()
asigna el primer valor a x
y el segundo a y
. En la segunda forma, el
número de operandos de la izquierda debe ser igual al número de
expresiones de la derecha, cada uno de los cuales debe ser de un solo
valor y la enésima expresión de la derecha se asigna al enésimo
operando de la izquierda:
uno, dos, tres = '一', '二', '三'
El identificador blanco proporciona una manera de ignorar los valores del lado derecho en una asignación:
_ = x // evalúa x pero lo descarta
x, _ = f() // evalúa f() pero desecha el valor del resultado
La asignación procede en dos fases. En primer lugar, los operandos de las expresiones de índice e indirecciones de puntero (incluyendo indirecciones de puntero implícitas en selectores) a la izquierda y las expresiones de la derecha todas se evalúan en el orden habitual. En segundo lugar, las tareas se llevan a cabo en orden de izquierda a derecha.
a, b = b, a // intercambia a y b
x := []int{1, 2, 3}
i := 0
i, x[i] = 1, 2 // fija i = 1, x[0] = 2
i = 0
x[i], i = 2, 1 // fija x[0] = 2, i = 1
x[0], x[0] = 1, 2 // fija x[0] = 1, luego x[0] = 2 (por tanto x[0] == 2
// al final)
x[1], x[3] = 4, 5 // fija x[1] = 4, luego ajusta el pánico x[3] = 5.
type Punto struct { x, y int }
var p *Punto
x[2], p.x = 6, 7 // fija x[2] = 6, luego ajusta el pánico p.x = 7
i = 2
x = []int{3, 5, 7}
for i, x[i] = range x { // fija i, x[2] = 0, x[0]
break
}
// después de este bucle, i == 0 y x == []int{3, 5, 3}
En las asignaciones, cada valor debe ser asignable al tipo del operando al que se le asigna, con los siguientes casos especiales:
- Cualquier valor tipado se puede asignar al identificador blanco.
- Si se asigna una constante sin tipo a una variable de tipo interfaz o al identificador blanco, la constante primero se convierte a su tipo predeterminado.
- Si se asigna un valor lógico sin tipo a una variable de tipo interfaz o
al identificador blanco, esta primero se convierte al tipo
bool
.
Instrucciones if
Las instrucciones "if" especifican la ejecución condicional de dos ramas de acuerdo con el valor de una expresión booleana. Si la expresión se evalúa como verdadera, la rama "if" se ejecuta, de lo contrario, si está presente, se ejecuta la rama "else".
InstruIf = "if" [ SimpleInstru ";" ] Bloque Expresión [ "else" ( InstruIf |
Bloque ) ] .
if x > máx {
x = máx
}
La expresión puede estar precedida por una declaración simple, que se ejecuta antes de evaluar la expresión.
if x := f(); x < y {
return x
} else if x > z {
return z
} else {
return y
}
Instrucciones switch
Las instrucciones "switch" proporcionan la ejecución múltivía. Una expresión o especificador de tipo se compara con los "casos" en el interior del "switch" para determinar qué rama se ejecuta.
InstruSwitch = InstruExpreSwitch | InstruTipoSwitch .
Hay dos formas: expresiones switch y tipos switch. En una expresión switch, los casos contienen expresiones que se comparan con el valor de la expresión switch. En un tipo switch, los casos contienen tipos que se comparan contra el tipo de una expresión switch especialmente anotada.
Expresiones switch
En una expresión switch, se evalúa la expresión switch y las expresiones
de casos, que no necesitan ser constantes, se evalúan de izquierda a
derecha y de arriba hacia abajo; el primero que es igual a la expresión
switch desencadena la ejecución de las instrucciones del caso asociado;
los demás casos se omiten. En el supuesto de que no haya coincidencia se
ejecutan las instrucciones del caso "default
". Como máximo puede haber
un caso default
y puede aparecer en cualquier parte de la instrucción
"switch". Una expresión switch faltante es equivalente al valor lógico
true
.
InstruExpreSwitch = "switch" [ SimpleInstru ";" ] [ Expresión ] "{" {
ExpreCláusulaCase } "}" .
ExpreCláusulaCase = ExpreCasoSwitch ":" ListaInstrucciones .
ExpreCasoSwitch = "case" ListaExpresión | "default" .
En una cláusula case
o default
, la última instrucción no vacía puede
ser una (posiblemente etiquetada)
instrucción "fallthrough" para indicar que
el control debe fluir desde el final de esta cláusula a la primera
declaración de la siguiente cláusula. De lo contrario, el control fluye
al final de la instrucción "switch". Una instrucción "fallthrough
" puede
aparecer como la última declaración de todas, pero la última cláusula de
una expresión switch.
La expresión puede estar precedida por una instrucción simple, que se ejecuta antes de evaluar la expresión.
switch etiqueta {
default: s3()
case 0, 1, 2, 3: s1()
case 4, 5, 6, 7: s2()
}
switch x := f(); { // falta expresión switch significa "true"
case x < 0: return -x
default: return x
}
switch {
case x < y: f1()
case x < z: f2()
case x == 4: f3()
}
Tipos switch
Un tipo switch compara tipos en lugar de valores. Por lo demás es similar
a una expresión switch. Se caracteriza por una expresión switch especial
que tiene la forma de un tipo aserción utilizando la
palabra reservada type
en lugar de un tipo real:
switch x.(tipo) {
// casos
}
Entonces los casos coinciden los tipos T
reales contra el tipo dinámico
de la expresión x
. Al igual que con los tipos aserción, x
debe ser de
tipo interfaz y cada tipo no interfaz T
enumerado en
un caso debe implementar el tipo de x
.
InstruTipoSwitch = "switch" [ SimpleInstru ";" ] ProtecTipoSwitch "{" {
TipoCláusulaCase } "}" .
ProtecTipoSwitch = [ identificador ":=" ] ExprePrimaria "." "(" "type" ")" .
TipoCláusulaCase = TipoCasoSwitch ":" ListaInstrucciones .
TipoCasoSwitch = "case" ListaTipo | "default" .
ListaTipo = Tipo { "," Tipo } .
El ProtecTipoSwitch puede incluir una declaración corta de variable. Cuando se utiliza esta forma, la variable se declara al comienzo del bloque implícito en cada cláusula. En las cláusulas con un caso enumerando exactamente un tipo, la variable tiene ese tipo; de lo contrario, la variable tiene el tipo de la expresión en el ProtecTipoSwitch.
El tipo en un caso puede ser nil
; ese
caso se utiliza cuando la expresión en el ProtecTipoSwitch es un valor
interfaz nil
.
Dada una expresión x
de tipo interface{}
, el siguiente tipo switch:
switch i := x.(tipo) {
case nil:
printString("x es nil") // el tipo de i es el tipo de
// x(interface{})
case int:
printInt(i) // el tipo de i es int
case float64:
printFloat64(i) // el tipo de i es float64
case func(int) float64:
printFunction(i) // el tipo de i es func(int) float64
case bool, string:
printString("tipo es bool o string") // el tipo de i es el tipo de
// x(interface{})
default:
printString("deconozco el tipo") // el tipo de i es el tipo de
// x(interface{})
}
Se podría reescribir de la siguiente manera:
v := x // x se evalúa exactamente una vez
if v == nil {
i := v // el tipo de i es el tipo de
// x(interface{})
printString("x es nil")
} else if i, isInt := v.(int); isInt {
printInt(i) // el tipo de i es int
} else if i, isFloat64 := v.(float64); isFloat64 {
printFloat64(i) // el tipo de i es float64
} else if i, isFunc := v.(func(int) float64); isFunc {
printFunction(i) // el tipo de i es func(int)
// float64
} else {
_, isBool := v.(bool)
_, isString := v.(string)
if isBool || isString {
i := v // el tipo de i es el atipo de
// x(interface{})
printString("tipo es bool o string")
} else {
i := v // el tipo de i es el tipo de
// x(interface{})
printString("desconozco el tipo")
}
}
El protector del tipo switch puede estar precedido por una declaración simple, que se ejecuta antes de que se evalúe el protector.
La declaración "fallthrough" no está permitida en un tipo switch.
Instrucciones for
Una instrucción "for" especifica la ejecución repetida de un bloque. La iteración es controlada por una condición, una cláusula "for" o una cláusula "range".
InstruFor = "for" [ Condición | CláusulaFor | CláusulaRange ] Bloque .
Condición = Expresión .
En su forma más simple, una instrucción "for" especifica la ejecución
repetida de un bloque, siempre que una condición booleana se evalúe como
verdadera. La condición se evalúa antes de cada iteración. Si la
condición está ausente, es equivalente al valor lógico true
.
for a < b {
a *= 2
}
Una instrucción "for" con una CláusulaFor también es controlada por su condición, pero además, como asignación puede especificar una expresión inicio y una destino, así como una declaracion de incremento o decremento. La expresión de inicio puede ser una declaración corta de variables, pero la declaración destino no debe. Las variables declaradas por la declaración de inicio se vuelven a utilizar en cada iteración.
CláusulaFor = [ ExpreInicio ] ";" [ Condición ] ";" [ ExpreDestino ] .
ExpreInicio = SimpleInstru .
ExpreDestino = SimpleInstru .
for i := 0; i < 10; i++ {
f(i)
}
Si no está vacía, la declaración de inicio se ejecuta una vez antes de
evaluar la condición en la primera iteración; la declaración destino se
evalúa después de cada ejecución del bloque (y únicamente si el bloque se
ejecuta). Cualquier elemento de la CláusulaFor puede estar vacío pero los
puntos y comas son obligatorios a menos que solo haya
una condición. Si la condición está ausente, es equivalente al valor
lógico true
.
for cond { S() } es lo mismo que for ; cond ; { S() }
for { S() } es lo mismo que for true { S() }
Una instrucción "for" con una cláusula "range" itera a través todas las entradas de un arreglo, sector, cadena, mapa o los valores recibidos en un canal. Por cada entrada esta asigna valores de iteración a las variables de iteración correspondientes si están presentes y luego ejecuta el bloque.
CláusulaRange = [ ListaExpresión "=" | ListaIdentificador ":=" ]
"range" Expresión .
La expresión de la derecha en la cláusula "range" se conoce como la expresión de rango, que bien puede ser un arreglo, puntero a un arreglo, sector, cadena, mapa o canal que permite operaciones de recepción. Al igual que con una asignación, si están presentes los operandos de la izquierda deben ser direccionables o expresiones índice de mapa; estos denotan las variables de iteración. Si la expresión rango es un canal, por lo menos se permite una variable de iteración, de lo contrario puede haber un máximo de dos. Si la última variable de iteración es el identificador blanco, la cláusula rango es equivalente a la misma cláusula sin ese identificador.
La expresión de rango se evalúa una vez antes de comenzar el bucle, con una excepción: si la expresión rango es un vector o un puntero a un arreglo y como máximo está presente una variable de iteración, sólo se evalúa la longitud de la expresión de rango; si esta longitud es constante, por definición no se evaluará la expresión en si misma.
Las llamadas a función de la izquierda son evaluadas una vez por iteración. Por cada iteración, los valores de iteración se producen como sigue si las respectivas variables de iteración están presentes:
Expresión range 1er valor 2do valor
arreglo o sector a [n]E, *[n]E, o []E índice i int a[i] E
cadena s tipo string índice i int ve rune abajo
mapa m map[K]V clave k K m[k] V
canal c chan E, <-chan E elemento e E
- Para un arreglo, puntero a arreglo o valor del sector
a
, los valores índice de iteración se producen en orden creciente, comenzando en el índice del elemento 0. Si a lo sumo está presente una variable de iteración, el bucle de iteración range produce valores de 0 hastalen(a)-1
y no tiene índice en el arreglo o sector en sí mismo. Para un sectornil
, el número de iteraciones es 0. - Para un valor de cadena, la cláusula "range" itera sobre los caracteres
Unicode en la cadena a partir del índice del byte 0. En sucesivas
iteraciones, el valor del índice será el índice del primer byte de los
sucesivos caracteres codificación UTF-8 en la cadena y el segundo
valor, de tipo
rune
, será el valor del carácter correspondiente. Si la iteración se encuentra con una secuencia UTF-8 no válida, el segundo valor será0xFFFD
, el carácter Unicode de reemplazo y la siguiente iteración avanzará un solo byte en la cadena. - La orden de iteración sobre mapas no se especifica y no se garantiza
que sea la misma de una iteración a la siguiente. Si se eliminan
entradas correlacionadas que aún no se han alcanzado durante la
iteración, no se producirán los valores de iteración
correspondientes. Si se crean entradas en el mapa durante la iteración,
esa entrada se puede producir durante la iteración o se puede
omitir. La elección puede variar para cada entrada creada y de una
iteración a la siguiente. Si el mapa es
nil
, el número de iteraciones es 0. - Para canales, los valores de iteración producidos son los valores
sucesivos enviados por el canal hasta que el canal esté
cerrado. si el canal es
nil
, la expresión range se bloquea para siempre.
Los valores de iteración se asignan a las respectivas variables de iteración como en una instrucción de asignación.
Las variables de iteración se pueden declarar por cláusula "range"
utilizando una forma de
declaración corta de variables (:=
).
En este caso, sus tipos se fijan a los tipos de los respectivos valores de
iteración y su ámbito es el bloque de la
declaración "for"; estas se vuelven a utilizar en cada iteración. Si las
variables de iteración se declaran fuera de la declaración "for", después
de la ejecución sus valores serán los de la última iteración.
var datosdeprueba *struct {
a *[7]int
}
for i, _ := range datosdeprueba.a {
// datosdeprueba.a nunca se evalúa; len(datosdeprueba.a) es constante
// i rangos desde 0 hasta 6
f(i)
}
var a [10]string
for i, s := range a {
// el tipo de i es int
// el tipo de s es string
// s == a[i]
g(i, s)
}
var clave string
var val interface {} // el tipo del valor de m es asignable a val
m := map[string]int{"lun":0, "mar":1, "mie":2, "jue":3, "vie":4,
"sáb":5, "dom":6}
for clave, val = range m {
h(clave, val)
}
// clave == última clave encontrada en la iteración del mapa
// val == map[clave]
var ch chan Trabajo = productor()
for w := range ch {
hazTrabajo(w)
}
// canal vacío
for range ch {}
Instrucciones go
Una instrucción "go
" comienza la ejecución de una llamada a función como
un hilo de control concurrente independiente, o rutinago, dentro del
mismo espacio de direcciones.
InstruGo = "go" Expresión .
La expresión debe ser una llamada a función o método; no puede estar entre paréntesis. Las llamadas a funciones integradas están restringidas en cuanto a las declaraciones de expresión.
El valor de la función y los parámetros se evalúan como de costumbre en la llamada a la rutinago, pero a diferencia de una llamada regular, la ejecución del programa no espera a que la función invocada termine. En lugar de ello, la función comienza a ejecutarse de forma independiente en una nueva rutinago. Cuando la función termina, su rutinago también termina. Si la función tiene algúnos valores de retorno, serán descartados en cuanto la función termine.
go Server()
go func(ch chan<- bool) { for { sleep(10); ch <- true; }} (c)
Instrucciones select
Una instrucción "select" elige cual de un conjunto de posibles operaciones de envío o recepción procederá. Es similar a una declaración "switch" pero en la cual todos los casos se refieren a operaciones de comunicación.
InstruSelect = "select" "{" { CláusulaComunicación } "}" .
CláusulaComunicación = CasoComunicación ":" ListaInstrucciones .
CasoComunicación = "case" ( InstruEnvío | InstruRecep ) | "default" .
InstruRecep = [ ListaExpresión "=" | ListaIdentificador ":=" ] ExpreRecep .
ExpreRecep = Expresión .
Un caso con una InstruRecep puede asignar el resultado de una ExpreRecep a
una o dos variables, las cuales se pueden declarar utilizando una
declaración corta de variables. La
ExpreRecep debe ser una (posiblemente entre paréntesis) operación de
recepción. Como máximo puede haber un caso default
y puede aparecer en
cualquier parte de la lista de casos.
La ejecución de una declaración "select" procede en varias etapas:
- Para todos los casos en la declaración, los operandos de canales de operaciones de recepción y el canal y las expresiones de envío del lado de la mano derecha son evaluadas exactamente una vez, en el orden de la fuente, al entrar en la declaración "select". El resultado es un conjunto de canales para recibir desde o enviar a, y los valores correspondientes a enviar. Cualquier efecto secundario en la evaluación se producirá independientemente de que (si las hay) se seleccione la operación de comunicación para proceder. Las expresiones en el lado izquierdo de una InstruRecep con una declaración corta de variables o asignación aún no se han evaluado.
- Si una o más de las comunicaciones puede proceder, se elige una sola, la que pueda proceder a través de una selección pseudoaleatoria uniforme. De lo contrario, si hay un caso predeterminado, se elige ese caso. Si no hay ningún caso predeterminado, la declaración "Select" se bloquea hasta que al menos una de las comunicaciones pueda continuar.
- A menos que el caso seleccionado sea el caso predeterminado, se ejecuta la operación de comunicación respectiva.
- Si el caso seleccionado es una InstruRecep con una declaración corta de variable o es una asignación, las expresiones de la parte izquierda se evalúan y el valor (o valores) recibidos serán asignados.
- Se ejecuta la lista de instrucciones del caso seleccionado.
Puesto que la comunicación en canales nil
nunca puede proceder, una
"select" con únicamente canales nil
y sin un caso predeterminado se
bloquea por siempre.
var a []int
var c, c1, c2, c3, c4 chan int
var i1, i2 int
select {
case i1 = <-c1:
print("recibí ", i1, " desde c1\n")
case c2 <- i2:
print("envié ", i2, " a c2\n")
case i3, ok := (<-c3): // igual que: i3, ok := <-c3
if ok {
print("recibí ", i3, " desde c3\n")
} else {
print("c3 está cerrado\n")
}
case a[f()] = <-c4:
// mismo que:
// case t := <-c4
// a[f()] = t
default:
print("no hay comunicación\n")
}
for { // envía secuencias aleatorias de bits a c
select {
case c <- 0: // observa: no hay instrucción,
// no hay fallthrough,
// no hay repliegue de casos
case c <- 1:
}
}
select {} // bloquea por siempre
Instrucciones return
Una instrucción "return" en una función F
termina la ejecución de F
y
opcionalmente proporciona uno o más valores como resultado. Cualquier
función diferida por F
se ejecuta antes de que
F
regrese a su invocador.
InstruReturn = "return" [ ListaExpresión ] .
En una función sin un tipo de resultado, una instrucción "return" no deberá especificar ningún valor de resultado.
func sinResultado() {
return
}
Hay tres formas de devolver valores de una función con un tipo de resultado:
El valor o valores de retorno se pueden enumerar explícitamente en la instrucción "return". Cada expresión debe ser de un solo valor y asignable al elemento correspondiente del tipo de resultado de la función.
func fSimple() int { return 2 } func fCompleja() (re float64, im float64) { return -7.0, -4.0 }
La lista de expresiones en la instrucción "return" puede ser una sola llamada a una función multivalores. El efecto es como si cada valor devuelto por la función se asignara a una variable temporal con el tipo del valor respectivo, seguido de una instrucción "return" enumerando estas variables, momento en que se aplican las reglas del caso anterior.
func fCompleja2() (re float64, im float64) { return fCompleja() }
La lista de expresiones puede estar vacía si el tipo de resultado de la función especifica nombres para sus parámetros de resultado. Los parámetros de resultado actúan como variables locales ordinarias y la función le puede asignar valores cuando sea necesario. La instrucción "return" devuelve los valores de esas variables.
func fCompleja3() (re float64, im float64) { re = 7.0 im = 4.0 return } func (devnull) Escribe(p []byte) (n int, _ error) { n = len(p) return }
Independientemente de la forma en que se declaren, todos los valores de los resultados se inician a los valores cero para su tipo al entrar a la función. Una instrucción "return" que especifica los resultados establece los parámetros de resultado antes de ejecutar cualquiera de las funciones diferidas.
Restricción de implementación: Un compilador podrá invalidar una lista de expresiones vacía en una instrucción "return" si una entidad diferente (constante, tipo o variable) con el mismo nombre que un parámetro de resultado está en el ámbito del lugar de la devolución.
func f(n int) (res int, err error) {
if _, err := f(n-1); err != nil {
return // func f (n int) (res int, err error) {
if _, err: = f (n-1); err! = nil {
return // instrucción return no válida: err es ensombrecido
}
return
}
Instrucciones break
Una instrucción "break" termina la ejecución de la instrucción "for", "switch" o "select" más interna dentro de la misma función.
InstruBreak = "break" [ Etiqueta ] .
Si hay una etiqueta, debe ser la de una instrucción envolvente "for", "switch" o "select", que es en dónde se tiene que terminar la ejecución.
BucleExterno:
for i = 0; i < n; i++ {
for j = 0; j < m; j++ {
switch a[i][j] {
case nil:
estado = Error
break BucleExterno
case item:
estado = Encontrado
break BucleExterno
}
}
}
Instrucciones continue
Una instrucción "continue", comienza la siguiente iteración del bucle "for" más interno en su posterior instrucción. El bucle "for" debe estar dentro de la misma función.
InstruContinue = "continue" [ Etiqueta ] .
Si hay una etiqueta, debe ser la de una instrucción "for" envolvente y que sea en la que tiene que avanzar la ejecución.
BucleRenglón:
for y, renglón := range renglones {
for x, datos := range renglón {
if datos == finDeRenglón {
continue BucleRenglón
}
renglón[x] = datos + parcial(x, y)
}
}
Instrucciones goto
Una instrucción "goto" transfiere el control a la declaración con la etiqueta correspondiente dentro de la misma función.
InstruGoto = "goto" Etiqueta .
goto Error
La ejecución de la instrucción "goto" no debe provocar que ninguna variable
entre en el ámbito que no estaba ya en su
alcance en el punto de la goto
. Por ejemplo, veamos esta declaración:
goto L // MAL
v := 3
L:
es errónea porque salta a la etiqueta L
omitiendo la creación de v
.
Una instrucción "goto" declarada fuera de un bloque no puede saltar a una etiqueta dentro de este bloque. Por ejemplo, veamos esta declaración:
if n%2 == 1 {
goto L1
}
for n > 0 {
f()
n--
L1:
f()
n--
}
es errónea porque la etiqueta L1
está dentro del bloque de la
instrucción "for", pero la instrucción "goto
" no.
Instrucciones fallthrough
Una instrucción "fallthrough" transfiere el control a la primera declaración de la siguiente cláusula caso en una expresión "switch". Solo se puede utilizar como la instrucción no vacía final en dicha cláusula.
InstruFallthrough = "fallthrough" .
Instrucciones defer
Una instrucción "defer" invoca a una función cuya ejecución se difiere hasta momentos antes de que la función regrese, ya sea porque la función envolvente ejecutó una instrucción return, se alcanzó el final del cuerpo de la función o porque la rutinago correspondiente está entrando en pánico.
InstruDefer = "defer" Expresión .
La expresión debe ser una llamada a función o método; no puede estar entre paréntesis. Las llamadas a funciones integradas están restringidas en cuanto a las declaraciones de expresión.
Cada vez que se ejecuta una declaración "defer", el valor de la función y
los parámetros de la llamada se evalúan de la manera usual y
se guarda de nuevo, pero la función real no se invoca. En su lugar, las
funciones diferidas se invocan inmediatamente antes de que la función
envolvente regrese, en orden inverso al que fueron diferidas. Si un valor
de función diferida evalúa a nil
, se entra en
pánico cuando la función se invoca, no cuando se
ejecute la instrucción "defer".
Por ejemplo, si la función diferida es una función literal y la función que la rodea tiene parámetros nombrados como resultado esos están en el ámbito de la función literal, la función diferida puede acceder y modificar los parámetros de resultado antes de que sean devueltos. Si la función diferida no tiene ningún valor de retorno, estos serán descartados al terminar la función. (Ve también la sección sobre el manejo de pánico).
bloquea(l)
defer desbloquea(l) // el desbloqueo ocurre antes que la
// función envolvente regrese
// imprime 3 2 1 0 antes de que la función envolvente regrese
for i := 0; i <= 3; i++ {
defer fmt.Print(i)
}
// f devuelve 1
func f() (result int) {
defer func() {
result++
}()
return 0
}
Funciones integradas
Las funciones incorporadas son predeclaradas. Se llaman como cualquier otra función, pero algunas de ellas aceptan un tipo en lugar de una expresión como primer argumento.
Las funciones incorporadas no tienen tipos estándar Go, por lo que sólo pueden aparecer en expresiones de llamada; no se pueden utilizar como valores de función.
close
Para un canal c
, la función incorporada close(c)
registra que no más
valores serán enviados en el canal. Es un error si c
únicamente es un
canal de recepción. Enviar a o cerrar un canal cerrado provoca
pánico en tiempo de ejecución. Cerrar el
canal nil
también provoca
pánico en tiempo de ejecución. Después
de llamar a close
y después de que se hayan recibido todos los valores
enviados anteriormente, las operaciones de recepción devolverán el valor
cero para el tipo de canal sin bloquearlo. Las
operaciones de recepción multivalor devuelven un
valor recibido junto con una indicación de si el canal está cerrado.
Longitud y capacidad
Las funciones incorporadas len
y cap
toman argumentos de varios tipos
y devuelven un resultado de tipo int
. La implementación garantiza que
el resultado siempre encaja en un int
.
Llamada Tipo de argumento Resultado
len(s) tipo string longitud de la cadena en bytes
[n]T, *[n]T longitud del arreglo (== n)
[]T longitud del sector
map[K]T longitud del mapa (el número de claves definido)
chan T número de elementos en cola en el búfer del canal
cap(s) [n]T, *[n]T Longitud del arreglo (== n)
[]T capacidad del sector
chan T capacidad del búfer del canal
La capacidad de un sector es el número de elementos para los cuales hay espacio reservado en el arreglo subyacente. En cualquier momento mantiene la siguiente relación:
0 <= len(s) <= cap(s)
La longitud de un sector nil
, mapa o canal es 0. La capacidad de un
sector o canal nil
es 0.
La expresión len(s)
es constante si s
es una cadena
constante. Las expresiones len(s)
y cap(s)
son constantes si el tipo
de s
es un arreglo o puntero a un arreglo y la expresión s
no contiene
llamadas a función en un
canal receptor o (no constante); en ese caso, s
no se evalúa. De lo contrario, las invocaciones de len
y cap
no son
constantes y s
se evalúa.
const (
c1 = imag(2i) // imag(2i) = 2.0 es una constante
c2 = len([10]float64{2}) // [10]float64{2} no contiene
// llamadas a función
c3 = len([10]float64{c1}) // [10]float64{c1} no contiene
// llamadas a función
c4 = len([10]float64{imag(2i)}) // imag(2i) es una constante y no
// se emite llamada a función
c5 = len([10]float64{imag(z)}) // no válida: imag(z) es una
// llamada a función (no constante)
)
var z complex128
Asignación
La función incorporada new
toma un tipo T
, reserva almacenamiento para
una variable de ese tipo en tiempo de ejecución y devuelve
un valor de tipo *T
que apunta a ella misma. La
variable se inicia como se describe en la sección sobre los
valores iniciales.
new(T)
Por ejemplo:
type S struct { a int; b float64 }
new(S)
reserva almacenamiento para una variable de tipo S
, lo inicia (a=0
,
b=0.0
) y devuelve un valor de tipo *S
que contiene la dirección de esa
ubicación.
Creando sectores, mapas y canales
La función integrada make
toma un tipo T
, el cual debe ser un tipo
sector, mapa o canal, opcionalmente seguido de una lista de expresiones de
tipos específicos. Devuelve un valor de tipo T
(no *T
). La memoria
se inicia como se describe en la sección sobre los
valores iniciales.
Llamada Tipo de T Resultado
make(T, n) sector sector de tipo T con longitud n y capacidad n
make(T, n, m) sector sector de tipo T con longitud n y capacidad m
make(T) mapa mapa de tipo T
make(T, n) mapa mapa de tipo T con espacio inicial para n elementos
make(T) canal canal sin búfer de tipo T
make(T, n) canal canal con búfer de tipo T, tamaño del búfer n
Los argumentos de tamaño n
y m
deben ser de tipo entero o sin tipo.
Un argumento constante para tamaño debe no ser negativo y
representable por un valor de tipo int
. Si se proporcionan ambos n
y
m
y son constantes, entonces n
debe ser mayor que m
. Si n
es
negativo o mayor que m
en tiempo de ejecución, se produce
pánico en tiempo de ejecución.
s := make([]int, 10, 100) // sector con len(s) == 10, cap(s) == 100
s := make([]int, 1e3) // sector con len(s) == cap(s) == 1000
s := make([]int, 1<<63) // ilegal: len(s) no es representable por un valor de tipo int
s := make([]int, 10, 0) // ilegal: len(s) > cap(s)
c := make(chan int, 10) // canal con un búfer de tamaño 10
m := make(map[string]int, 100) // mapa con espacio inicial para 100 elementos
Anexando a y copiando sectores
Las funciones incorporadas append
y copy
ayudan en las operaciones de
sector comunes. Para ambas funciones, el resultado es independiente de si
la memoria referenciada por los argumentos se superpone.
La función variadica append
añade cero o más valores
x
a s
de tipo S
, el cual debe ser un tipo de sector y devolver el
sector resultante, también de tipo S
. Los valores x
se pasan a un
parámetro de tipo ...T
dónde T
es el tipo de elemento
de S
y se aplican las respectivas
reglas para el paso de parámetros.
Como caso especial, append
también acepta un primer argumento asignable
al tipo []byte
con un segundo argumento de tipo cadena seguido por
...
. Esta forma agrega los bytes de la cadena.
append(s S, x ...T) S // T es el tipo de elemento de S
Si la capacidad de s
no es lo suficientemente grande como para ajustarse
a los valores adicionales, append
asigna un nuevo arreglo subyacente lo
suficientemente grande para que se ajuste tanto a los elementos existentes
del sector como a los valores adicionales. De lo contrario, append
reutiliza el arreglo subyacente.
s0 := []int{0, 0}
s1 := append(s0, 2) // agrega un único elemento
// s1 == []int{0, 0, 2}
s2 := append(s1, 3, 5, 7) // agrega múltiples elementos
// s2 == []int{0, 0, 2, 3, 5, 7}
s3 := append(s2, s0...) // agrega un sector
// s3 == []int{0, 0, 2, 3, 5, 7, 0, 0}
s4 := append(s3[3:6], s3[2:]...) // agrega superponiendo un sector
// s4 == []int{3, 5, 7, 2, 3, 5, 7, 0, 0}
var t []interface{}
t = append(t, 42, 3.1415, "foo") // t == []interface{}{42, 3.1415, "foo"}
var b []byte
b = append(b, "bar"...) // añade contenido a la cadena
// b == []byte{'b', 'a', 'r' }
La función copy
copia elementos del sector desde una fuente src
a un
destino dst
y devuelve el número de elementos copiados. Ambos
argumentos deben tener idéntico tipo de elemento T
y deben ser asignables a un sector de tipo []T
. El
número de elementos copiados es el mínimo de len(src)
y len(dst)
.
Como caso especial, copy
también acepta un argumento destino asignable
al tipo []byte
con un argumento fuente de tipo cadena. Esta forma copia
los bytes de la cadena al sector de byte.
copy(dst, src []T) int
copy(dst []byte, src string) int
Ejemplos:
var a = [...]int{0, 1, 2, 3, 4, 5, 6, 7}
var s = make([]int, 6)
var b = make([]byte, 5)
n1 := copy(s, a[0:]) // n1 == 6, s == []int{0, 1, 2, 3, 4, 5}
n2 := copy(s, s[2:]) // n2 == 4, s == []int{2, 3, 4, 5, 4, 5}
n3 := copy(b, "¡Hola, mundo!") // n3 == 5, b == []byte("¡Hola")
Eliminando elementos de mapa
La función incorporada delete
elimina el elemento con la clave k
de un
mapa m
. El tipo de k
debe ser
asignable al tipo de la clave de m
.
delete(m, k) // elimina el elemento m[k] del mapa m
Si el mapa m
es nil
o el elemento m[k]
no existe, delete
no es
operativo.
Manipulando números complejos
Tres funciones para montar y desmontar números complejos. La función
incorporada complex
construye un valor complejo a partir de una parte
real de coma flotante y una parte imaginaria, mientras que real
e imag
extraen las partes real e imaginaria de un valor complejo.
complex(parteReal, parteImaginaria floatT) tipoComplejo
real(tipoComplejo) floatT
imag(tipoComplejo) floatT
El tipo de los argumentos y valor de retorno corresponde. Para complex
,
los dos argumentos deben ser del mismo tipo, coma flotante y el tipo de
retorno es de tipo complejo con los correspondientes componentes de coma
flotante: complex64
para float32
, complex128
para float64
. Juntas
las funciones real
e imag
forman la inversa, por lo que para un
complejo de valor de z
, z
==
complex(real(z),
imag(z))
.
Si todos los operandos de estas funciones son constantes, el valor de retorno es una constante.
var a = complex(2, -2) // complex128
var b = complex(1.0, -1.4) // complex128
x := float32(math.Cos(math.Pi/2)) // float32
var c64 = complex(5, -x) // complex64
var im = imag(b) // float64
var rl = real(c64) // float32
Manejando pánicos
Dos funciones incorporadas, panic
y recover
, ayudan en la presentación
de informes y manipulación de
pánicos en tiempo de ejecución y las
condiciones de error definidas por el programa.
func panic(interface{})
func recover() interface{}
Durante la ejecución de una función F
, una llamada explícita a panic
o
un pánico en tiempo de ejecución
finaliza la ejecución de F
. Cualquier función
diferida por F
se ejecuta entonces de la manera
usual. A continuación, las funciones diferidas a cargo del llamador F's
se ejecutan y así sucesivamente hasta cualquier diferida por la función
de nivel superior en la rutinago en ejecución. En ese momento, el
programa se termina y se reporta la condición de error, incluyendo el
valor del argumento para entrar en pánico
. Esta secuencia de
terminación se conoce como entrar en pánico (panicking
en inglés).
panic(42)
panic("inalcanzable")
panic(Error("no se puede interpretar"))
La función recover
permite que un programa maneje el comportamiento de
una rutinago entrando en pánico. Supongamos que una función G
difiere
una función D
que llama a recover
y se produce pánico en una función
en la misma rutinago en la que se está ejecutando G
. Cuando el
funcionamiento natural de las funciones diferidas alcanza a D
, el valor
de retorno de D
en la llamada a recover
será el valor que se pase a la
llamada de panic
. Si D
regresa normalmente, sin necesidad de iniciar
un nuevo pánico
, la secuencia de pánico se detiene. En ese caso, el
estado de las funciones llamadas entre G
y la llamada a pánico
se
descartan y se reanuda la ejecución normal. Cualquier función diferida
por G
antes de D
entonces se ejecutan y la ejecución de G
termina
volviendo a su llamador.
El valor de retorno de recover
es nil
si se cumple alguna de las
siguientes condiciones:
- el argumento para
panic
eranil
; - la rutinago no es presa del pánico;
recover
no fue llamado directamente por una función diferida.
La función protege
en el siguiente ejemplo invoca al argumento de la
función g
y protege a los llamadores de
pánico en tiempo de ejecución
planteados por g
.
func protege(g func()) {
defer func() {
log.Println("hecho") // Println ejecuta normalmente incluso
// si hay un pánico
if x := recover(); x != nil {
log.Printf("pánico en tiempo de ejecución: %v", x)
}
}()
log.Println("inicio")
g()
}
Proceso de arranque
Las implementaciones actuales proporcionan varias funciones incorporadas
útiles durante el proceso de arranque (bootstrapping
en inglés). Estas
funciones están completamente documentadas pero no se garantiza que
permanezcan en el lenguaje. No devuelven un resultado.
Función Comportamiento
print imprime todos los argumentos; el formato de los argumentos es
específico de la implementación
println como print pero imprime espacios entre argumentos y una nueva
línea al final
Paquetes
Los programas Go se construyen enlazando paquetes. Un paquete a su vez, se construye a partir de uno o más archivos fuente que juntos declaran constantes, tipos, variables y funciones propias del paquete y que son accesibles en todos los archivos del mismo paquete. Estos elementos se pueden exportar y utilizar en otros paquetes.
Organización de archivos fuente
Cada archivo fuente consta de una cláusula package
que define el paquete
al que pertenece, seguida de una serie posiblemente vacía de declaraciones
de importación que declaran los paquetes que desea utilizar, seguida de
una serie posiblemente vacía de declaraciones de constantes, tipos,
variables y funciones.
ArchivoFuente = CláusulaPackage ";" { InstruImport ";" } { DeclaNivelSuperior ";" } .
Cláusula package
Una cláusula package
comienza cada archivo fuente y define el paquete al
que pertenece el archivo.
CláusulaPackage = "package" NombrePaquete .
NombrePaquete = identificador .
El NombrePaquete no debe ser el identificador blanco.
package math
Un conjunto de archivos que comparten el mismo NombrePaquete forman la implementación de un paquete. Una implementación puede requerir que todos los archivos fuente para un paquete habiten en el mismo directorio.
Instrucciones import
Una instrucción import
indica que el archivo fuente conteniendo la
declaración depende de la funcionalidad de la importación del paquete
importado
(inicio y ejecución del programa) y
permite el acceso a los
identificadores exportados de ese paquete.
La import
nombra un identificador (NombrePaquete) que se utilizará para
acceder y una RutaImportación que especifica el paquete a ser importado.
InstruImport = "import" ( EspecImport | "(" { EspecImport ";" } ")" ) .
EspecImport = [ "." | NombrePaquete ] RutaImportación .
RutaImportación = cadena_lit .
El NombrePaquete se utiliza en
identificadores cualificados para acceder
a los identificadores exportados del paquete dentro del archivo fuente
importado. Se declara en el bloque de archivo. Si se omite
el NombrePaquete, toma como predeterminado el identificador especificado
en la cláusula package
del paquete importado. Si
aparece un punto explícito (.
) en lugar de un nombre, todos los
identificadores exportados del paquete declarados en ese
bloque de paquete serán declarados en el bloque de archivo del
archivo fuente importador y se debe acceder a ellos sin un calificador.
La interpretación de la RutaImportación depende de la implementación, pero típicamente es una subcadena del nombre de archivo completo del paquete compilado y puede ser relativa a un repositorio de paquetes instalados.
Restricción de implementación: Un compilador puede restringir las RutaImportación a cadenas no vacías utilizando sólo caracteres pertenecientes a Unicode categorías generales L, M, N, P y S (los caracteres gráficos sin espacios) y también puede excluir los caracteres
!"#$%&'()*,:;<=>?[\]^
{|}` y el carácter de reemplazo Unicode U+FFFD.
Supongamos que hemos compilado un paquete que contiene la cláusula de
paquete package math
, que exporta la función Sin
y el paquete
compilado está instalado en el archivo identificado por "lib/math"
.
Esta tabla demuestra cómo se accede a Sin
en los archivos que importan
el paquete después de los distintos tipos de declaración de importación.
Declaración de importación Nombre local de Sin
import "lib/math" math.Sin
import m "lib/math" m.Sin
import . "lib/math" Sin
Una declaración de importación declara una relación de dependencia entre el importador y el paquete importado. Es ilegal que un paquete directa o indirectamente se importe a sí mismo o importar directamente un paquete sin hacer referencia a cualquiera de sus identificadores exportados. Para importar un paquete únicamente por sus efectos secundarios (iniciación), utiliza el identificador blanco como nombre de paquete explícito:
import _ "lib/math"
Un paquete de ejemplo
Aquí tienes un paquete Go completo que implementa un tamiz primo concurrente.
package main
import "fmt"
// Envía la secuencia 2, 3, 4, … al canal 'ch'.
func genera(ch chan<- int) {
for i := 2; ; i++ {
ch <- i // Envía 'i' al canal 'ch'.
}
}
// Copia los valores del canal «src» al canal 'dst',
// quitando los divisibles por 'primo'.
func filtra(src <-chan int, dst chan<- int, primo int) {
for i: = range src { // Bucle sobre los valores recibidos de «src».
if i%primo != 0 {
dst <- i // Envía 'i' al canal 'dst'.
}
}
}
// El tamiz primo: filtra la conexión en cadena y los procesa juntos.
func tamiz() {
ch := make(chan int) // Crea un nuevo canal.
go genera(ch) // Inicia genera() como un subproceso.
for {
primo := <-ch
fmt.Print(primo, "\n")
ch1 := make(chan int)
go filtra(ch, ch1, primo)
ch = ch1
}
}
func main() {
tamiz()
}
Iniciación y ejecución de programa
El valor cero
Cuando se asigna almacenamiento a una variable, ya sea a
través de una declaración o una llamada a new
o cuando se crea un nuevo
valor, bien a través de un literal compuesto o por medio de una llamada a
make
y no se proporciona iniciación explícita, se da a la variable o
valor un valor predeterminado. Cada elemento de una variable o valor tal
se establece al valor cero para su tipo: false
para lógicos, 0
para
números enteros, 0.0
para número de coma flotante, ""
para cadenas y
nil
para punteros, funciones, interfaces, sectores, canales y mapas.
Esta iniciación se realiza de forma recursiva, por lo que, por ejemplo,
cada elemento de un arreglo de estructuras tendrá sus campos a cero si no
se especifica ningún valor.
Estas dos simples declaraciones son equivalentes:
var i int
var i int = 0
Después de:
type T struct { i int; f float64; siguiente *T }
t := new(T)
se cumple lo siguiente:
t.i == 0
t.f == 0.0
t.siguiente == nil
Lo mismo sería cierto después de:
var t T
Iniciación del paquete
Dentro de un paquete, las variables a nivel de paquete se inician en orden de declaración pero después de cualquiera de las variables de que dependen.
Específicamente, una variable a nivel de paquete se considera lista para iniciación si aún no se ha iniciado y, o bien no tiene ninguna expresión de iniciación o su expresión de iniciación no tiene ninguna dependencia de variables sin iniciar. La iniciación procede por iniciar repetidamente la siguiente variable a nivel de paquete que se declaró más temprano y está lista para iniciación, hasta que no haya más variables listas para iniciación.
Si algunas variables están todavía sin iniciar cuando termine este proceso, esas variables son parte de uno o más ciclos de iniciación y el programa no es válido.
El orden de la declaración de variables declaradas en varios archivos es determinado por el orden en que los archivos se presentan al compilador: Las variables declaradas en el primer archivo se declaran antes de cualquiera de las variables declaradas en el segundo archivo y así sucesivamente.
El análisis de dependencia no se basa en los valores reales de las
variables, sólo en referencias léxicas a ellos en la fuente, analizados
transitivamente. Por ejemplo, si la expresión de iniciación para una
variable x
se refiere a una función cuyo cuerpo se refiere a la variable
y
entonces x
depende de y
. Específicamente:
- Una referencia a una variable o función es un identificador que denota esa variable o función.
- Una referencia a un método
m
es un valor método o expresión método de la format.m
, donde el tipo (estático) det
no es un tipo interfaz y el métodom
está en el conjunto de métodos det
. Es irrelevante que el valor resultante de la funciónt.m
sea invocado. - Una variable, función o método
x
depende de una variabley
si la expresión de iniciación dex
o el cuerpo (para funciones y métodos) contiene una referencia ay
o a una función o método que dependa dey
.
El análisis de dependencias se realiza por paquete; únicamente se consideran referencias a variables, funciones y métodos declarados en el paquete actual.
Por ejemplo, dadas las declaraciones:
var (
a = c + b
b = f()
c = f()
d = 3
)
func f() int {
d++
return d
}
el orden de iniciación es d
, b
, c
, a
.
Las variables también se pueden iniciar utilizando funciones con nombre
init
declaradas en el bloque del paquete, sin argumentos y sin
parámetros de resultado.
func init() { … }
Se pueden definir múltiples funciones como esta, incluso dentro de un solo
archivo fuente. El identificador init
no está
declarado y por lo tanto las funciones init
no
se pueden referir desde cualquier parte de un programa.
Un paquete sin importaciones se inicia mediante la asignación de valores
iniciales a todas sus variables a nivel de paquete seguida por una llamada
a todas las funciones init
en el orden en que aparecen en la fuente,
posiblemente en varios archivos, tal como se presentan al compilador. Si
un paquete tiene importaciones, los paquetes importados se inician antes
de iniciar el propio paquete. Si varios paquetes importan un paquete, el
paquete importado se iniciará únicamente una vez. La importación de
paquetes, por construcción, garantiza que no puede haber ninguna
dependencia de iniciación cíclica.
La iniciación de variables del paquete y la invocación a las funciones
init
ocurre secuencialmente en una sola rutinago, un paquete a la vez.
Una función init
puede lanzar otras rutinagos, mismas que se pueden
ejecutar simultáneamente con el código de iniciación. Sin embargo, la
iniciación siempre secuencia las funciones init
: no invocará a la
siguiente hasta que la anterior haya regresado.
Para garantizar un comportamiento de iniciación reproducible, se anima a construir sistemas para presentar varios archivos pertenecientes a un mismo paquete con el fin de nombrar archivo léxicos a un compilador.
Ejecución del programa
Un programa completo se crea enlazando un solo paquete no importado
llamado el package main con todos los paquetes que importa,
transitivamente. El paquete principal debe tener el nombre de paquete
main
y declarar una función main
que no tiene argumentos y no devuelve
nada.
func main() { … }
La ejecución del programa comienza iniciando el paquete principal y luego
invocando a la función main
. Cuando esa invocación a función regresa,
el programa se cierra. No espera a que otras rutinasgo (no main
)
terminen.
Errores
El tipo predeclarado error
se define como:
type error interface {
Error() string
}
Es la interfaz convencional para representar una condición de error, el
valor nil
representa la ausencia de errores. Por ejemplo, una función
para leer datos de un archivo se puede definir así:
func Lee(f *File, b []byte) (n int, err error)
Pánicos en tiempo de ejecución
Errores de ejecución como el intento de indexar un arreglo fuera de
límites desencadena un
pánico en tiempo de ejecución equivalente
a llamar a la función incorporada panic
con un
valor de tipo interfaz runtime.Error
definido en la implementación. Ese
tipo se ajusta al tipo interfaz error
predeclarado. Los
valores de error exactos que representan condiciones de error en distintos
tiempo de ejecución no están especificados.
package runtime
type Error interface {
error
// y tal vez otros métodos
}
Consideraciones del sistema
Paquete unsafe
El paquete integrado unsafe
, conocido por el compilador, ofrece
facilidades para la programación de bajo nivel incluyendo las operaciones
que violan el sistema de tipos. Un paquete usando unsafe
debe
revisar manualmente la seguridad de tipos y puede no ser portátil. El
paquete proporciona la siguiente interfaz:
package unsafe
type ArbitraryType int // abreviatura de un tipo Go arbitrario; no es
// un tipo real
type Pointer *ArbitraryType
func Alignof(variable ArbitraryType) uintptr
func Offsetof(selector ArbitraryType) uintptr
func Sizeof(variable ArbitraryType) uintptr
Un Pointer
es un tipo puntero pero un valor Pointer
no se puede desreferenciar. Cualquier
puntero o valor del tipo subyacente uintptr
se puede convertir
a un tipo Pointer
y viceversa. El efecto de convertir entre Pointer
y
uintptr
está definido por la implementación.
var f float64
bits = *(*uint64)(unsafe.Pointer(&f))
type ptr unsafe.Pointer
bits = *(*uint64)(ptr(&f))
var p ptr = nil
Las funciones Alignof
y Sizeof
toman una expresión x
de cualquier
tipo y devuelven la alineación o tamaño, respectivamente, de una
hipotética variable v
como si v
hubiese sido declarada a través de
var v = x
.
La función Offsetof
toma un (posiblemente entre paréntesis)
selector s.f
, que denota un campo s
de la estructura
denotada por s
o *s
y devuelve el desplazamiento del campo en bytes
relativo a la estructura dirección
. Si f
es un
campo incrustado, este debe ser accesible sin
indirecciones de puntero a través de los campos de la estructura. Para
una estructura s
con el campo f
:
uintptr(unsafe.Pointer(&s)) + unsafe.Offsetof(s.f) == uintptr(unsafe.Pointer(&s.f))
La arquitectura de las computadoras puede requerir que las direcciones de
memoria estén alineadas; es decir, que las direcciones de una variable
sean un múltiplo de un factor, el tipo alineación de la variable. La
función Alignof
toma una expresión que denota una variable de cualquier
tipo y devuelve la alineación (del tipo) de la variable en bytes. Para
una variable x
:
uintptr(unsafe.Pointer(&x)) % unsafe.Alignof(x) == 0
Las llamadas a Alignof
, Offsetof
y Sizeof
son expresiones constantes
de tiempo de compilación de tipo uintptr
.
Garantías de tamaño y alineación
Para los tipos numéricos, los siguientes tamaños están garantizados:
tipo tamaño en bytes
byte, uint8, int8 1
uint16, int16 2
uint32, int32, float32 4
uint64, int64, float64, complex64 8
complex128 16
Las siguientes propiedades mínimas de alineación están garantizadas:
- Para una variable
x
de cualquier tipo:unsafe.Alignof(x)
es al menos 1. - Para una variable
x
de tipo estructura:unsafe.Alignof(x)
es el más grande de todos los valoresunsafe.Alignof(x.f)
para cada campof
dex
, pero al menos 1. - Para una variable
x
de tipo arreglo:unsafe.Alignof(x)
es el mismo queunsafe.Alignof(x[0])
, pero por lo menos 1.
Una estructura o tipo arreglo tiene tamaño cero si no contiene campos (o elementos, respectivamente) que tengan un tamaño mayor que cero. Dos distintas variables de tamaño cero pueden tener la misma dirección en memoria.
En su mayor parte este libro se reproduce a partir del trabajo creado y compartido por Google traducido al Español y se usa de acuerdo a los términos descritos en la Licencia Creative Commons 3.0 Attribution.