One place for hosting & domains

      Información

      Información sobre la lógica de booleanos en Go


      El tipo de datos “Boolean” (​​​​​​bool​​​​​​) puede ser uno de dos valores, ya sea true (verdadero) o false (falso). Los booleanos se utilizan en programación para realizar comparaciones y controlar el flujo de los programas.

      Los booleanos representan los valores de verdad que se asocian con la rama lógica de la matemática, que informa algoritmos en la Informática. La palabra “Boolean”, en honor al matemático George Boole, siempre comienza con B mayúscula.

      El tipo de datos en Go para “Boolean” es bool, que se escribe por completo en minúsculas. Los valores true y false siempre aparecerán con t y f minúsculas respectivamente, ya que son valores especiales en Go.

      En este tutorial, se abordarán los aspectos básicos que necesitará para comprender el funcionamiento del tipo de datos bool, que incluye la comparación booleana, los operadores lógicos y las tablas de verdad.

      Operadores de comparación

      En el ámbito de la programación, los operadores de comparación se utilizan para comparar valores y realizar evaluaciones hasta un único valor “Boolean” verdadero o falso.

      En la siguiente tabla se muestran operadores de comparación “Boolean”.

      OperadorQue significa
      ==Igual a
      ! =No es igual a
      <Menor que
      >Mayor que
      <=Menor o igual que
      >=Mayor o igual que

      Para entender el funcionamiento de estos operadores, asignaremos dos números enteros a dos variables en un programa de Go:

      x := 5
      y := 8
      

      En este ejemplo, dado que x tiene el valor 5, es inferior a y, que tiene el valor 8.

      Usando estas dos variables y sus valores asociados, repasemos los operadores de la tabla anterior. A través de este programa, solicitará a Go que realice impresiones si cada operador de comparación hace evaluaciones con verdadero o falso. Para ayudar a entender mejor este resultado, también solicitará a Go imprimir una cadena que muestre lo que evalúa:

      package main
      
      import "fmt"
      
      func main() {
          x := 5
          y := 8
      
          fmt.Println("x == y:", x == y)
          fmt.Println("x != y:", x != y)
          fmt.Println("x < y:", x < y)
          fmt.Println("x > y:", x > y)
          fmt.Println("x <= y:", x <= y)
          fmt.Println("x >= y:", x >= y)
      }
      

      Output

      x == y: false x != y: true x < y: true x > y: false x <= y: true x >= y: false

      Siguiendo la lógica matemática, Go evaluó lo siguiente de las expresiones:

      • ¿Es 5 (x) igual a 8 (y)? false
      • ¿5 no es igual a 8? true
      • ¿5 es menor que 8? true
      • ¿5 es mayor que 8? false
      • ¿5 es menor o igual que 8? true
      • ¿5 no es menor o igual que 8? false

      Si bien en este caso se utilizaron enteros, podría sustituirlos por valores flotantes.

      Las cadenas también pueden utilizarse con operadores “Boolean”. Se distinguen mayúsculas y minúsculas, a menos que utilice un método de cadena adicional.

      Podrá ver cómo se comparan las cadenas en la práctica:

      Sammy := "Sammy"
      sammy := "sammy"
      
      fmt.Println("Sammy == sammy: ", Sammy == sammy)
      

      Output

      Sammy == sammy: false

      La cadena Sammy no es igual a la cadena sammy, ya que su similitud no es absoluta; una comienza con S mayúscula y la otra con s minúscula. Sin embargo, si añade otra variable que tenga asignado el valor Sammy, entonces se evaluarán como iguales:

      Sammy := "Sammy"
      sammy := "sammy"
      alsoSammy := "Sammy"
      
      fmt.Println("Sammy == sammy: ", Sammy == sammy)
      fmt.Println("Sammy == alsoSammy", Sammy == alsoSammy)
      

      Output

      Sammy == sammy: false Sammy == alsoSammy true

      También puede usar los otros operadores de comparación, incluidos > y < para comparar dos cadenas. Go comparará estas cadenas de forma lexicográfica usando los valores ASCII de los caracteres.

      También puede evaluar valores “Boolean” con operadores de comparación:

      t := true
      f := false
      
      fmt.Println("t != f: ", t != f)
      

      Output

      t != f: true

      El bloque de código anterior evaluó que true no es igual a false.

      Observe la diferencia entre los dos operadores = y ==.

      x = y   // Sets x equal to y
      x == y  // Evaluates whether x is equal to y
      

      El primero = es el operador de asignación, que establecerá un valor igual a otro. El segundo, ==, es un operador de comparación y evaluará si dos valores son iguales.

      Operadores lógicos

      Existen dos operadores lógicos que se utilizan para comparar valores. Evalúan las expresiones hasta los valores “Boolean” y muestran true o false. Estos operadores son &&, || y !, y se definen en la siguiente lista:

      • && (x && y) es el operador and. Es verdadero si ambas instrucciones lo son.
      • || (x || y) es el operador or. Es verdadero si al menos una instrucción lo es.
      • ! (! x) es el operador not. Sólo es verdadero si la instrucción es falsa.

      Los operadores lógicos suelen utilizarse para evaluar si dos o más expresiones son verdaderas o no. Por ejemplo, pueden utilizarse para determinar si una calificación es aprobatoria y que el estudiante está registrado en el curso, y si ambos son verdaderos entonces se le asignará al alumno una calificación en el sistema. Otro ejemplo implicaría determinar si un usuario es un cliente activo válido de una tienda en línea,basándose en la posibilidad de que tenga crédito con la tienda o haya realizado una compra en los últimos 6 meses.

      Para entender el funcionamiento de los operadores lógicos, evaluaremos tres expresiones:

      fmt.Println((9 > 7) && (2 < 4))   // Both original expressions are true
      fmt.Println((8 == 8) || (6 != 6)) // One original expression is true
      fmt.Println(!(3 <= 1))            // The original expression is false
      

      Output

      true true true

      En el primer caso, fmt.Println((9 > 7) && (2 < 4)), tanto 9 > 7 como 2 < 4 debieron evaluarse como verdaderas, ya que se utilizó el operador and.

      En el segundo caso, fmt.Println((8 == 8) || (6 ! = 6)), ya que 8 == 8 se evaluó como verdadero; no marcó una diferencia que 6 != 6 se evalúe como falso porque se utilizó el operador or. Si hubiera utilizado el operador and​​​ , esto se evaluaría como falso.

      En el tercer caso, fmt.Println(!(3 <= 1)), el operador not niega el valor falso que 3 <=1 muestra.

      Sustituiremos los flotantes por enteros y buscaremos evaluaciones falsas:

      fmt.Println((-0.2 > 1.4) && (0.8 < 3.1))  // One original expression is false
      fmt.Println((7.5 == 8.9) || (9.2 != 9.2)) // Both original expressions are false
      fmt.Println(!(-5.7 <= 0.3))               // The original expression is true
      

      En este ejemplo:

      • Para and debe haber al menos una expresión falsa evaluada como falsa.
      • Para or, ambas expresiones deben evaluarse como falsas.
      • La expresión interna de ! debe ser verdadera para que la nueva expresión se evalúe como falsa.

      Si estos resultados le parecen confusos, revise algunas tablas de verdad para aclarar conceptos.

      También puede escribir instrucciones compuestas usando &&, || y !:

      !((-0.2 > 1.4) && ((0.8 < 3.1) || (0.1 == 0.1)))
      

      Primero, observe la expresión más interna: (0.8 < 3.1) || (0.1 == 0.1). Esta expresión se evalúa como true porque ambas instrucciones matemáticas son true.

      A continuación, Go utiliza el valor mostrado true y lo combina con la siguiente expresión interior: (-0.2 > 1.4) && (true). En este ejemplo se muestra false, ya que la instrucción matemática -0.2 > 1.4 es falsa, y (false) y (true) muestran false.

      Por último, se encuentra la expresión externa: !( false), que se evalúa como true, por lo que el valor mostrado al final, si imprimimos esta instrucción, es el siguiente:

      Output

      true

      Los operadores lógicos &&, || y ! evalúan las expresiones y muestran los valores “Boolean”.

      Tablas de verdad

      Hay mucho que aprender sobre la rama lógica de la matemática, pero puede aprender de forma selectiva algo de esto para mejorar su pensamiento algorítmico a la hora de crear programas.

      Las siguientes son tablas de verdad para el operador de comparación == y cada uno de los operadores lógicos &&, || y !. Aunque pueda ser capaz de encontrar una explicación para ellos, también puede resultar útil memorizarlos, ya que esto puede agilizar su proceso de toma de decisiones para la programación.

      Tabla de verdad de == (equal)

      x==yResultado
      true==truetrue
      true==falsefalse
      false==truefalse
      false==falsetrue

      Tabla de verdad de && (and)

      xandyResultado
      trueandtruetrue
      trueandfalsefalse
      falseandtruefalse
      falseandfalsefalse

      Tabla de verdad de || (or)

      xoryResultado
      trueortruetrue
      trueorfalsetrue
      falseortruetrue
      falseorfalsefalse

      Tabla de verdad de ! (not)

      notxResultado
      nottruefalse
      notfalsetrue

      Las tablas de verdad son tablas matemáticas comunes que se emplean en la lógica y resulta útil tomarlas en cuenta al crear algoritmos (instrucciones) en el campo de la programación informática.

      Uso de operadores “Boolean” para el control de flujo

      Para controlar el flujo y los resultados de un programa en la forma de instrucciones de control de flujo, puede usar una condición seguida de una* cláusula*.

      Una condición evalúa hasta un valor “Boolean” verdadero o falso y se presenta un punto en el que se toma una decisión en el programa. Es decir, una condición le mostraría si algo se evalúa a verdadero o falso.

      La cláusula es el bloque de código que sigue la condición y dicta el resultado del programa. Es decir, es la parte “hacer esto” de la construcción “Si x es true, hacer esto”.

      En el bloque de código que se muestra a continuación se muestra un ejemplo de operadores de comparación trabajando en equipo con instrucciones condicionales para controlar el flujo de un programa de Go:

      if grade >= 65 {                 // Condition
          fmt.Println("Passing grade") // Clause
      } else {
          fmt.Println("Failing grade")
      }
      

      Este programa evaluará si la calificación de cada estudiante es aprobatoria o reprobatoria. En el caso de un estudiante con una calificación de 83, la primera instrucción se evaluará a true y se activará la instrucción de impresión de Passing grade. En el caso de un estudiante con una calificación de 59, la primera instrucción se evaluará a false, por lo que el programa proseguirá con la ejecución de la instrucción de impresión vinculada a la expresión “else”: Failing grade.

      Los operadores “Boolean” presentan condiciones que pueden utilizarse para decidir el resultado final de un programa a través de instrucciones de control de flujo.

      Conclusión

      En este tutorial, repasamos la comparación y los operadores lógicos pertenecientes al tipo “Boolean”, así como las tablas de verdad y el uso de booleanos para el control de flujo de un programa.



      Source link

      Información sobre tipos de datos en Go


      Introducción

      En los tipos de datos se especifica el tipo de valores que se almacenarán en determinadas variables cuando escriba un programa. En ellos también se determinan las operaciones que se pueden realizar en los datos.

      En este artículo, repasaremos los tipos de datos importantes nativos de Go. No se trata de una investigación exhaustiva sobre tipos de datos, pero le permitirá familiarizarse con las opciones que tiene a su disposición en Go. Al comprender algunos tipos de datos básicos podrá escribir código más preciso que funcione de forma eficiente.

      Antecendentes

      Una alternativa para pensar en los tipos de datos es considerar los diferentes tipos de información que usamos en el mundo real. Un ejemplo de datos en el mundo real son los números: por ejemplo, podemos usar números naturales (0, 1, 2, etc.), enteros (…, -1, 0, 1, etc.,) e irracionales (π).

      Normalmente, en matemática, podemos combinar números de diferentes tipos y obtener algún tipo de respuesta. Es posible que queramos sumar 5 y π, por ejemplo:

      5 + π
      

      Podemos mantener la ecuación como a respuesta para explicar el número irracional, o redondear π a un número con posiciones decimales abreviadas, y luego sumar los números:

      5 + π = 5 + 3.14 = 8.14
      

      Sin embargo, si intentamos evaluar los números con otro tipo de datos, como las palabras, el sentido comienza a perderse. ¿Qué solución aplicaríamos a la siguiente ecuación?

      shark + 8
      

      En el caso de las computadoras, cada tipo de datos es bastante diferente, como las palabras y los números. Por lo tanto, debemos tener cuidado cuando usamos los diferentes tipos de datos para asignar valores y la forma en que los manipulamos a través de operaciones.

      Enteros

      Como en la matemática, los enteros en la programación informática son números enteros que pueden ser positivos, negativos o neutros (…, -1, 0 y 1). En Go, un entero se identifica como un int. Al igual que en otros lenguajes de programación, no debe usar comas en números de cuatro o más dígitos. Por lo tanto, cuando escriba “1.000” en su programa, ingrese “1000”.

      Podemos imprimir un entero de manera sencilla, como se muestra a continuación:

      fmt.Println(-459)
      

      Output

      -459

      También podemos declarar una variable, que en este caso es un símbolo del número que usamos o manipulamos. De esta manera:

      var absoluteZero int = -459
      fmt.Println(absoluteZero)
      

      Output

      -459

      También podemos realizar cálculos con enteros en Go. En el siguiente bloque de código, usaremos el operador de asignación := para declarar la variable​​​​​​ sum​​ y crear una instancia de ella:

      sum := 116 - 68
      fmt.Println(sum)
      

      Output

      48

      Como se muestra en el resultado, el operador matemático - restó el entero 68 de 116, lo que dio como resultado 48. Aprenderá más sobre la declaración de variables a través de la sección Declarar tipos de datos para variables.

      Los enteros pueden utilizarse de muchas formas en programas de Go. A medida que aprenda más acerca de Go, tendrá muchas oportunidades de trabajar con enteros y aprovechar su conocimiento sobre este tipo de datos.

      Números de punto flotante

      Un número de punto flotante o flotante se utiliza para representar números reales que no se pueden expresar como enteros. Los números reales incluyen todos los números racionales e irracionales y a causa de esto, los números de punto flotante pueden contener una parte fraccionaria, como 9,0 o -116,42. Para que pueda entender un flotante en un programa de Go, se trata de un número que contiene un punto decimal.

      Como hicimos con los enteros, podemos imprimir un número de punto flotante de manera sencilla, como se muestra a continuación:

      fmt.Println(-459.67)
      

      Output

      -459.67

      También se puede declarar una variable que representa un flotante, como en este caso:

      absoluteZero := -459.67
      fmt.Println(absoluteZero)
      

      Output

      -459.67

      Como en el caso de los enteros, también podemos realizar cálculos con flotantes en Go:

      var sum = 564.0 + 365.24
      fmt.Println(sum)
      

      Output

      929.24

      Con los números enteros y de punto flotante, es importante tener en cuenta que 3 ≠ 3,0, ya que 3 hace referencia a un número entero mientras que 3,0 hace referencia a un flotante.

      Tamaños de tipos numéricos

      Además de la distinción entre enteros y flotantes, Go tiene dos tipos de datos numéricos que se distinguen por la naturaleza estática o dinámica de sus tamaños. El primer tipo es independiente de la arquitectura. Esto significa que el tamaño de los datos en bits no cambia, sin importar el equipo en el que se ejecuta el código.

      La mayoría de las arquitecturas de sistemas actuales son de 32 o 64 bits. Por ejemplo, podría realizar desarrollos para una computadora portátil moderna con Windows, en la cual el sistema operativo se ejecute en una arquitectura de 64 bits. Sin embargo, si realiza desarrollos para un dispositivo como un reloj con pulsómetro, podría trabajar con una arquitectura de 32 bits. Si utiliza un tipo independiente de la arquitectura, como int32, sin importar la arquitectura para la que realiza compilaciones, el tipo tendrá un tamaño constante.

      El segundo tipo es específico de la implementación. En este tipo, el tamaño de los bits puede variar, según la arquitectura en la que se construya el programa. Por ejemplo, si usamos el tipo int cuando se compila en Go para una arquitectura de 32 bits, el tamaño del tipo de datos será de 32 bits. Si en el programa se compila para una arquitectura de 64 bits, la variable será de 64 bits.

      Además de los tipos de datos que tienen diferentes tamaños, los tipos como los enteros también vienen en dos formatos básicos: con firma y sin firma. Un int8 es un entero con firma y puede tener un valor entre -128 y 127. Un uint8 es un entero sin firma y solo puede tener un valor positivo entre 0 y 255.

      Los rangos se basan en el tamaño de bits. Para los datos binarios, 8 bits pueden representar un total de 256 valores diferentes. Debido a que un tipo int debe admitir valores positivos y negativos, un número entero de 8 bits (int8) tendrá un rango de -128 a 127, para un total de 256 valores únicos posibles.

      Go tiene los siguientes tipos de enteros independientes de la arquitectura:

      uint8       unsigned  8-bit integers (0 to 255)
      uint16      unsigned 16-bit integers (0 to 65535)
      uint32      unsigned 32-bit integers (0 to 4294967295)
      uint64      unsigned 64-bit integers (0 to 18446744073709551615)
      int8        signed  8-bit integers (-128 to 127)
      int16       signed 16-bit integers (-32768 to 32767)
      int32       signed 32-bit integers (-2147483648 to 2147483647)
      int64       signed 64-bit integers (-9223372036854775808 to 9223372036854775807)
      

      Los números flotantes y complejos también vienen en diferentes tamaños:

      float32     IEEE-754 32-bit floating-point numbers
      float64     IEEE-754 64-bit floating-point numbers
      complex64   complex numbers with float32 real and imaginary parts
      complex128  complex numbers with float64 real and imaginary parts
      

      También existen varios alias de tipos de números, que asignan nombres útiles a tipos de datos específicos:

      byte        alias for uint8
      rune        alias for int32
      

      El propósito del alias byte es dejar claro el momento en que el programa usa bytes a modo de medición informática común en elementos de cadenas de caracteres, en contraposición a enteros pequeños no relacionados con la medición de datos de byte. Aunque byte y uint8 son idénticos una vez que se compila el programa, byte se utiliza a menudo para representar datos de caracteres en formato numérico, mientras que uint8 está diseñado para ser un número en su programa.

      El alias rune es un poco diferente. Si bien byte y uint8 contienen exactamente los mismos datos, un rune puede ser de un solo byte o de cuatro, un rango determinado por int32. Un rune se utiliza para representar un carácter Unicode, mientras que solo los caracteres ASCII se pueden representar únicamente con un tipo de datos int32.

      Además, Go tiene los siguientes tipos específicos de la implementación:

      uint     unsigned, either 32 or 64 bits
      int      signed, either 32 or 64 bits
      uintptr  unsigned integer large enough to store the uninterpreted bits of a pointer value
      

      El tamaño de los tipos específicos de la implementación se definirá a través de la arquitectura para la que se compile el programa.

      Seleccionar tipos de datos numéricos

      La selección del tamaño correcto normalmente tiene que ver más con el rendimiento de la arquitectura de destino para la que realiza tareas de programación que con el tamaño de los datos con los que trabaja. Sin embargo, sin necesidad de conocer las ramificaciones específicas de rendimiento para su programa, puede seguir algunas de estas directrices básicas cuando dé los primeros pasos.

      Como se mencionó antes en este artículo, existen tipos independientes de la arquitectura y tipos específicos de la implementación. Para los datos enteros, en Go es común utilizar los tipos de implementación como int o uint en lugar de int64 o uint64. Normalmente, esto dará como resultado una velocidad de procesamiento más alta para su arquitectura de destino. Por ejemplo, si utiliza un int64 y realiza compilaciones en una arquitectura de 32 bits, necesitará al menos el doble de tiempo para procesar esos valores porque se requieren ciclos de CPU adicionales para mover los datos a través de la arquitectura. Si en su lugar utiliza un int, en el programa se definirá como uno de 32 bits de tamaño para una arquitectura de 32 bits y se podría procesar a una velocidad considerablemente mayor.

      Si sabe que no superará un rango de tamaño específico, la elección de un tipo independiente de la arquitectura puede aumentar la velocidad y disminuir el uso de memoria. Por ejemplo, si sabe que sus datos no superarán el valor de 100 y solo representarán un número positivo, la elección de uint8 hará que su programa sea más eficiente porque requerirá menos memoria.

      Ahora que examinamos algunos de los posibles rangos para los tipos de datos numéricos, veamos qué sucederá si superamos esos rangos en nuestro programa.

      Desbordamiento vs. ajuste

      Go tiene el potencial tanto de desbordar un número como de ajustarlo cuando intente almacenar un valor más grande que el tipo de datos que su diseño permite almacenar, según el valor se calcule en el tiempo de compilación o de ejecución. Un error de tiempo de compilación sucede cuando en el programa se encuentra un error a medida que se intenta compilar dicho programa. Un error de tiempo de ejecución se produce después de la compilación del programa, justo mientras se encuentra en ejecución.

      En el siguiente ejemplo, fijamos maxUint32 en su valor máximo:

      package main
      
      import "fmt"
      
      func main() {
          var maxUint32 uint32 = 4294967295 // Max uint32 size
          fmt.Println(maxUint32)
      }
      

      Se realizará la compilación y ejecución, y producirá el siguiente resultado:

      Output

      4294967295

      Si añadimos 1 al valor en tiempo de ejecución, se ajustará a 0:

      Output

      0

      Por otro lado, cambiaremos el programa para añadir 1 a la variable cuando lo asignemos, antes del tiempo de compilación:

      package main
      
      import "fmt"
      
      func main() {
          var maxUint32 uint32 = 4294967295 + 1
          fmt.Println(maxUint32)
      
      }
      

      En el tiempo de compilación, si en el compilador se determina que un valor será demasiado grande para contenerlo en el tipo de datos especificado, producirá un error overflow. Esto significa que el valor calculado es demasiado grande para el tipo de datos que especificó.

      Debido a que en el compilador se puede determinar que se desbordará el valor, ahora generará un error:

      Output

      prog.go:6:36: constant 4294967296 overflows uint32

      Comprender los límites de sus datos le permitirá evitar posibles errores en su programa en el futuro.

      Ahora que abarcamos los tipos numéricos, analizaremos la forma de almacenar valores booleanos.

      Booleanos

      El tipo de datos booleano puede ser uno de dos valores, ya sea true o false, y se define como bool al declararlo como un tipo de datos. Los booleanos se utilizan para representar los valores de verdad que se asocian con la rama lógica de la matemática, que informa algoritmos en el ámbito de la informática.

      Los valores true y false siempre aparecerán con t y f minúsculas respectivamente, ya que son identificadores declarados previamente en Go.

      Muchas operaciones matemáticas nos proporcionan respuestas que se evalúan en “true” o “false”:

      • mayor que
        • 500 > 100 true
        • 1 > 5 false
      • menor que
        • 200 < 400 verdadero
        • 4 < 2 falso
      • igual a
        • 5 = 5 verdadero
        • 500 = 400 false

      Al igual que con los números, podemos almacenar un valor booleano en una variable:

      myBool := 5 > 8
      

      Luego, podemos imprimir el valor booleano invocando la función fmt.Println():

      fmt.Println(myBool)
      

      Debido a que 5 no es mayor que 8, obtendremos el siguiente resultado:

      Output

      false

      A medida que escriba más programas en Go, se familiarizará más con el funcionamiento de los booleanos y con la forma en que las diferentes funciones y operaciones que se evalúan en true o false pueden cambiar el curso del programa.

      Cadenas

      Una cadena es una secuencia de uno o más caracteres (letras, números y símbolos) que puede ser una constante o una variable. Los cadenas existen dentro de comillas invertidas ”“o dobles”` en Go y tienen diferentes características según las que utilice.

      Si utiliza comillas invertidas, creará un literal de cadena sin formato. Si utiliza comillas dobles, creará un literal de cadena interpretado.

      Literales de cadena sin formato

      Los literales de cadena sin formato son secuencias de caracteres entre comillas inversas, a menudo conocidas como tildes inversas. Dentro de las comillas, cualquier carácter aparecerá como se muestra entre las comillas inversas, a excepció del propio carácter de comilla inversa.

      a := `Say "hello" to Go!`
      fmt.Println(a)
      

      Output

      Say "hello" to Go!

      Normalmente, las barras diagonales inversas se utilizan para representar caracteres especiales en cadenas. Por ejemplo, en una cadena interpretada, n representaría una nueva línea en una cadena. Sin embargo, las barras diagonales inversas no tienen un significado especial dentro de los literales de cadena sin formato:

      a := `Say "hello" to Go!n`
      fmt.Println(a)
      

      Debido a que las barras diagonales inversas no tienen un significado especial en un literal de cadena, en lugar de hacer una nueva línea en realidad imprimirá el valor de n:

      Output

      Say "hello" to Go!n

      Los literales de cadena sin formato también pueden utilizarse para crear cadenas de varias líneas:

      a := `This string is on
      multiple lines
      within a single back
      quote on either side.`
      fmt.Println(a)
      

      Output

      This string is on multiple lines within a single back quote on either side.

      En los bloques de código anteriores, las nuevas líneas fueron literalmente transferidas de la entrada al resultado.

      Literales de cadena interpretados

      Los literales de cadena interpretados son secuencias de caracteres entre comillas dobles, como en “bar”. Dentro de las comillas, cualquier carácter puede aparecer con excepción de las comillas de nueva línea y las comillas dobles sin escapes. Para mostrar las comillas dobles en una cadena interpretada, puede usar la barra diagonal inversa como un carácter de escape, como se muestra a continuación:

      a := "Say "hello" to Go!"
      fmt.Println(a)
      

      Output

      Say "hello" to Go!

      Casi siempre usará literales de cadena interpretados porque permiten caracteres de escape dentro de ellas. Para obtener más información sobre cómo trabajar con cadenas, consulte Introducción al uso de cadenas en Go.

      Cadenas con caracteres UTF-8

      UTF-8 es un esquema de codificación que se utiliza para codificar caracteres de ancho variable en uno a cuatro bytes. En Go se admiten caracteres UTF-8 desde el principio, sin ningún tipo de configuración, biblioteca o paquetes especiales. Los caracteres romanos como la letra A pueden representarse a través de un valor ASCII, como en el caso del número 65. Sin embargo, con caracteres especiales como el internacional , se requeriría UTF-8. Go utiliza el tipo de alias rune para los datos de UTF-8.

      a := "Hello, 世界"
      

      Puede usar la palabra clave range en un bucle for para realizar indexaciones a través de cualquier cadena en Go, incluso una cadena UTF-8. Los bucles for y range se abordarán en detalle más adelante en la serie; por ahora, es importante saber que podemos usar esto para contar los bytes en una cadena determinada:

      package main
      
      import "fmt"
      
      func main() {
          a := "Hello, 世界"
          for i, c := range a {
              fmt.Printf("%d: %sn", i, string(c))
          }
          fmt.Println("length of 'Hello, 世界': ", len(a))
      }
      

      En el bloque de código anterior, declaramos la variable a y le asignamos el valor de Hello, 世界. El texto asignado tiene caracteres UTF-8.

      Luego usamos un bucle for estándar y la palabra clave range. En Go, la palabra clave range se indexará a través de una cadena que muestra un carácter a la vez, así como el índice de bytes en el que se encuentra el carácter en la cadena.

      Usando la función fmt.Printf, proporcionamos una cadena de formato de %d: %sn. %d es el verbo de impresión para un dígito (en este caso, un entero) y %s es el verbo de impresión para una cadena. Luego proporcionamos los valores de i, que es el índice actual del bucle for, y c, que es el carácter actual en el bucle for.

      Por último, imprimimos la variable a en toda su extensión utilizando la función len integrada.

      Anteriormente, mencionamos que un rune es un alias para int32 y puede estar compuesto de uno a cuatro bytes. El carácter ocupa tres bytes para su definición y el índice se mueve en consecuencia cuando se desplaza a través de la cadena UTF-8. Esta es la razón por la que i no es secuencial cuando se imprime.

      Output

      0: H 1: e 2: l 3: l 4: o 5: , 6: 7: 世 10: 界 length of 'Hello, 世界': 13

      Como puede observar, la extensión supera el número de veces que tardó en recorrer la cadena.

      No siempre trabajará con cadenas UTF-8, pero cuando lo haga, comprenderá por qué son “runes” y no solo un int32.

      Declarar tipos de datos para variables

      Ahora que conoce los diferentes tipos de datos primitivos, repasaremos la forma de asignar estos tipos a variables en Go.

      En Go, podemos definir una variable con la palabra clave var seguida del nombre de la variable y el tipo de datos deseado.

      En el siguiente ejemplo, declararemos una variable con el nombre pi del tipo float64.

      La palabra clave var es lo primero que se declara:

      var pi float64
      

      A esta le sigue el nombre de nuestra variable, pi:

      var pi float64
      

      Por último, el tipo de datos float64:

      var pi float64
      

      Opcionalmente también se puede especificar un valor inicial, como 3.14:

      var pi float64 = 3.14
      

      Go es un lenguaje tipificado estáticamente. Esto significa que cada instrucción en el programa se verifica en el tiempo de compilación. También significa que el tipo de datos está ligado a la variable, mientras que en los lenguajes vinculados de forma dinámica este está ligado al valor.

      Por ejemplo, en Go el tipo se declara al declarar una variable:

      var pi float64 = 3.14
      var week int = 7
      

      Cada una de estas variables podría ser de un tipo de datos diferente si las declaró de forma diferente.

      Esto es diferente en comparación con un lenguaje como PHP, en el que se asocia el tipo de datos al valor:

      $s = "sammy";         // $s is automatically a string
      $s = 123;             // $s is automatically an integer
      

      En el bloque de código anterior, el primer $s es una cadena porque se le asigna el valor “sammy” y el segundo es un entero porque tiene el valor 123.

      A continuación, veremos tipos de datos más complejos como las matrices.

      Matrices

      Una matriz es una secuencia ordenada de elementos. La capacidad de una matriz se define en el momento de su creación. Una vez que se asigna el tamaño a una matriz, este ya no se puede cambiar. Debido a que el tamaño de una matriz es estático, significa que se asigna memoria solo una vez. Esto hace que el trabajo con matrices resulte un tanto rígido, pero aumenta el rendimiento de su programa. Debido a esto, las matrices suelen utilizarse al optimizar programas. Los segmentos, que veremos a continuación, son más flexibles y constituyen lo que se podría considerar como matrices en otros lenguajes.

      Las matrices se definen al declarar el tamaño de estas y luego el tipo de datos con los valores definidos entre llaves { }.

      Una matriz de cadenas tiene el siguiente aspecto:

      [3]string{"blue coral", "staghorn coral", "pillar coral"}
      

      Podemos almacenar una matriz en una variable e imprimirla:

      coral := [3]string{"blue coral", "staghorn coral", "pillar coral"}
      fmt.Println(coral)
      

      Output

      [blue coral staghorn coral pillar coral]

      Como ya se mencionó anteriormente, los segmentos son similares a las matrices, pero son mucho más flexibles. Veamos este tipo de datos mutable.

      Segmentos

      Un segmento es una secuencia ordenada de elementos cuya extensión puede cambiar. El tamaño de los segmentos puede aumentar de forma dinámica. Cuando añada nuevos elementos a un segmento, si este no cuenta con memoria suficiente para almacenar los nuevos elementos, se solicitará más memoria al sistema según sea necesario. Debido a que se pueden ampliar para añadir más elementos cuando sea necesario, los segmentos se utilizan con mayor frecuencia que las matrices.

      Los segmentos se definen declarando el tipo de datos precedidos por un corchete de apertura y cierre [], y disponiendo los valores entre llaves { }.

      Un segmento de enteros tiene el siguiente aspecto:

      []int{-3, -2, -1, 0, 1, 2, 3}
      

      Un segmento de flotantes tiene el siguiente aspecto:

      []float64{3.14, 9.23, 111.11, 312.12, 1.05}
      

      Un segmento de cadenas tiene el siguiente aspecto:

      []string{"shark", "cuttlefish", "squid", "mantis shrimp"}
      

      Definiremos nuestro segmento de cadenas como seaCreatures:

      seaCreatures := []string{"shark", "cuttlefish", "squid", "mantis shrimp"}
      

      Podemos imprimirlos invocando a la variable:

      fmt.Println(seaCreatures)
      

      El resultado tendrá un aspecto exactamente igual a la lista que creamos:

      Output

      [shark cuttlefish squid mantis shrimp]

      Podemos usar la palabra clave append para añadir un elemento a nuestro segmento. Con el siguiente comando se agregará el valor de cadena seahorse al segmento:

      seaCreatures = append(seaCreatures, "seahorse")
      

      Puede verificar que se agregó imprimiéndolo:

      fmt.Println(seaCreatures)
      

      Output

      [shark cuttlefish squid mantis shrimp seahorse]

      Como puede observar, si necesita administrar un tamaño desconocido de elementos, un segmento será mucho más versátil que una matriz.

      Mapas

      El mapa es el tipo de hash o de diccionario incorporado de Go. Los mapas utilizan claves y valores como un par para almacenar datos. Esto es útil en la programación para buscar rápidamente valores a través de un índice o, en este caso, una clave. Por ejemplo, podría querer mantener un mapa de usuarios, indexados por su ID de usuario. La clave sería el ID del usuario y el objeto del usuario sería el valor. Un mapa se construye usando la palabra clave map seguida por el tipo de datos de clave entre corchetes [ ] y luego por el tipo de datos de valor y los pares clave-valor entre llaves.

      map[key]value{}
      

      Un mapa normalmente se usa para guardar datos relacionados, como la información contenida en un ID, y tiene el siguiente aspecto:

      map[string]string{"name": "Sammy", "animal": "shark", "color": "blue", "location": "ocean"}
      

      Observará que, además de las llaves, hay también dos puntos en varias partes del mapa. Las palabras que se hallan a la izquierda de los dos puntos son las claves. Las claves pueden ser de cualquier tipo comparable en Go. Los tipos comparables son primitivos, como strings e ints, entre otros. Un tipo primitivo se define por el lenguaje y no se construye a partir de la combinación de cualquier otros tipos. Aunque puede haber tipos definidos por el usuario, se considera que una buena práctica es procurar que sean sencillos para evitar errores de programación. Las claves en el diccionario anterior son: name, animal, color y location.

      Las palabras a la derecha de los dos puntos representan los valores. Los valores pueden comprender cualquier tipo de datos. Los valores en el diccionario anterior son: Sammy, shark, blue y ocean.

      Almacenaremos el mapa dentro de una variable y lo imprimiremos:

      sammy := map[string]string{"name": "Sammy", "animal": "shark", "color": "blue", "location": "ocean"}
      fmt.Println(sammy)
      

      Output

      map[animal:shark color:blue location:ocean name:Sammy]

      Si queremos aislar el color de Sammy, podemos hacerlo invocando a sammy["color"]. Lo imprimiremos:

      fmt.Println(sammy["color"])
      

      Output

      blue

      Debido a que los mapas ofrecen pares clave-valor para almacenar datos, pueden ser elementos importantes en su programa de Go.

      Conclusión

      En este punto, comprenderá mejor algunos de los tipos de datos principales que están disponibles para que utilizarlos en Go. Cada uno de estos tipos de datos cobrará importancia a medida que usted desarrolle proyectos de programación en el lenguaje Go.

      Una vez que conozca bien los tipos de datos disponibles en Go, podrá aprender a convertir tipos de datos para tener la posibilidad de cambiarlos según la situación.



      Source link

      Información sobre defer en Go


      Introducción

      Go tiene muchas de las palabras claves de flujo de control comunes que se encuentran en otros lenguajes de programación, como if, switch y for, entre otras. Una palabra clave que no tienen la mayoría de los otros lenguajes de programación es defer, y aunque es menos común, pronto verá la utilidad de esta palabra en sus programas.

      Uno de los principales usos de una instrucción defer es el de limpiar recursos como archivos abiertos, conexiones de red y controladores de bases de datos. Cuando su programa termine con estos recursos, es importante cerrarlos para evitar agotar los límites del programa y permitir que otros programas accedan a esos recursos. defer aporta más claridad a nuestro código y reduce su propensión a experimentar errores mediante la conservación de las invocaciones para cerrar archivos y recursos cerca de las invocaciones abiertas.

      En este articulo, aprenderá a usar de forma adecuada la instrucción defer para limpiar recursos y también verá algunos errores comunes que se cometen cuando se utiliza defer.

      Qué es una instrucción defer

      Una instrucción defer añade la invocación de la función después de la palabra clave defer en una pila. Todas las invocaciones de la pila en cuestión se invocan cuando regresa la función en la que se añadieron. Debido a que las invocaciones se disponen en una pila, se llaman en el orden “último en entrar” y “primero en salir”.

      Veremos la forma en que defer funciona imprimiendo un texto:

      main.go

      package main
      
      import "fmt"
      
      func main() {
          defer fmt.Println("Bye")
          fmt.Println("Hi")
      }
      

      En la función main hay dos instrucciones. La primera comienza con la palabra clave defer y le sigue una afirmación print que imprime Bye. La siguiente línea imprime Hi.

      Si ejecutamos el programa, veremos el siguiente resultado:

      Output

      Hi Bye

      Observe que Hi se imprimió primero. Esto es porque cualquier instrucción precedida por la palabra clave defer no se invoca hasta el final de la función en la cual se utilizó defer.

      Echaremos otro vistazo al programa y esta vez añadiremos algunos comentarios para ayudar a ilustrar lo que está sucediendo:

      main.go

      package main
      
      import "fmt"
      
      func main() {
          // defer statement is executed, and places
          // fmt.Println("Bye") on a list to be executed prior to the function returning
          defer fmt.Println("Bye")
      
          // The next line is executed immediately
          fmt.Println("Hi")
      
          // fmt.Println*("Bye") is now invoked, as we are at the end of the function scope
      }
      

      La clave para comprender defer es que cuando se ejecuta la instrucción defer, los argumentos para la función diferida se evalúan de inmediato. Cuando defer se ejecuta, dispone la instrucción después de sí en una lista para que se invoque antes del regreso de la función.

      Aunque este código ilustra el orden en el cual se ejecutaría defer, no es una alternativa habitual para usarla cuando se escribe un programa de Go. Es más probable que utilicemos defer para limpiar un recurso, como el controlador de un archivo. Veremos la forma de hacer eso a continuación.

      Utilizar defer para limpiar recursos

      En Go, es muy común usar defer para limpiar recursos. Primero, veremos un programa que escribe una cadena en un archivo, pero no utiliza defer para gestionar la limpieza del recurso:

      main.go

      package main
      
      import (
          "io"
          "log"
          "os"
      )
      
      func main() {
          if err := write("readme.txt", "This is a readme file"); err != nil {
              log.Fatal("failed to write file:", err)
          }
      }
      
      func write(fileName string, text string) error {
          file, err := os.Create(fileName)
          if err != nil {
              return err
          }
          _, err = io.WriteString(file, text)
          if err != nil {
              return err
          }
          file.Close()
          return nil
      }
      

      En este programa, existe una función llamada write que primero intentará crear un archivo. Si tiene un error, lo mostrará y cerrará la función. A continuación, intenta escribir la cadena This is a readme file en el archivo especificado. Si recibe un error, lo mostrará y cerrará la función. A continuación, la función intentará cerrar el archivo y liberar el recurso de vuelta para el sistema. Finalmente, la función muestra nil para indicar que se ejecutó sin errores.

      Aunque este código funciona, hay un error sutil. Si falla la invocación de io.WriteString, la función volverá sin cerrar el archivo ni liberar el recurso de vuelta para el sistema.

      Podríamos solucionar el problema añadiendo otra instrucción file.Close(), método con el cual probablemente resolvería esto en un lenguaje sin defer:

      main.go

      package main
      
      import (
          "io"
          "log"
          "os"
      )
      
      func main() {
          if err := write("readme.txt", "This is a readme file"); err != nil {
              log.Fatal("failed to write file:", err)
          }
      }
      
      func write(fileName string, text string) error {
          file, err := os.Create(fileName)
          if err != nil {
              return err
          }
          _, err = io.WriteString(file, text)
          if err != nil {
              file.Close()
              return err
          }
          file.Close()
          return nil
      }
      

      Ahora, incluso si la invocación de io.WriteString falla, cerraremos el archivo de todos modos. Aunque este era un error relativamente fácil de detectar y solucionar, con una función más complicada, es posible que se haya pasado por alto.

      En vez de añadir la segunda invocación a file.Close(), podemos usar una instrucción defer para garantizar que independientemente de las secciones que se tomen durante la ejecución, siempre invoquemos Close().

      Aquí está la versión que utiliza la palabra clave defer:

      main.go

      package main
      
      import (
          "io"
          "log"
          "os"
      )
      
      func main() {
          if err := write("readme.txt", "This is a readme file"); err != nil {
              log.Fatal("failed to write file:", err)
          }
      }
      
      func write(fileName string, text string) error {
          file, err := os.Create(fileName)
          if err != nil {
              return err
          }
          defer file.Close()
          _, err = io.WriteString(file, text)
          if err != nil {
              return err
          }
          return nil
      }
      

      Esta vez, añadimos la línea de código: defer file.Close(). Esto indica al compilador que debería ejecutar file.Close antes de cerrar la función write.

      Ahora, nos hemos asegurado de que, incluso si añadimos más código y creamos otra ramificación que cierre la función en el futuro, siempre limpiaremos y cerraremos el archivo.

      Sin embargo, hemos introducido un error más al añadir defer. Ya no comprobaremos el error potencial que puede mostrarse desde el método Close. Esto se debe a que cuando usamos defer no hay forma de comunicar valores de retorno a nuestra función.

      En Go, se considera una práctica segura y aceptada invocar Close() más de una vez sin que esto afecte al comportamiento de su programa. Si Close() muestra un error, lo hará la primera vez que se invoque. Esto nos permite invocarlo explícitamente en la ruta de ejecución correcta de nuestra función.

      Veamos cómo podemos aplicar defer a la invocación para Close y, de todas formas, notificar un error si encontramos uno.

      main.go

      package main
      
      import (
          "io"
          "log"
          "os"
      )
      
      func main() {
          if err := write("readme.txt", "This is a readme file"); err != nil {
              log.Fatal("failed to write file:", err)
          }
      }
      
      func write(fileName string, text string) error {
          file, err := os.Create(fileName)
          if err != nil {
              return err
          }
          defer file.Close()
          _, err = io.WriteString(file, text)
          if err != nil {
              return err
          }
      
          return file.Close()
      }
      

      El único cambio en este programa es la última línea en la que mostramos file.Close(). Si la invocación a Close genera un error, este ahora se mostrará como previsto para la función de invocación. Tenga en cuenta que nuestra instrucción defer file.Close() también se ejecutará después de la instrucción return. Esto significa que file.Close() posiblemente se invoque dos veces. Aunque esto no es lo ideal, es una práctica aceptable porque no debería tener efectos colaterales en su programa.

      Si, sin embargo, vemos un error previamente en la función, como cuando invocamos WriteString; la función mostrará ese error y también intentará invocar a file.Close porque se difirió. Aunque file.Close puede mostrar un error (y probablemente lo haga) también, esto ya no nos importa porque vemos un error que probablemente nos indique el problema.

      Hasta ahora, vimos la forma en que podemos usar un único defer para asegurarnos de limpiar nuestros recursos correctamente. A continuación, veremos la manera en que podemos usar varias instrucciones defer para limpiar más de un recurso.

      Varias instrucciones defer

      Es normal que haya más de una instrucción defer en una función. Crearemos un programa que solo tenga instrucciones defer para ver qué sucede cuando introducimos varias instrucciones defer:

      main.go

      package main
      
      import "fmt"
      
      func main() {
          defer fmt.Println("one")
          defer fmt.Println("two")
          defer fmt.Println("three")
      }
      

      Si ejecutamos el programa, veremos el siguiente resultado:

      Output

      three two one

      Observe que el orden es el opuesto al que empleamos para invocar las instrucciones defer. Esto se debe a que cada instrucción diferida que se invoca se apila sobre la anterior y luego se invoca a la inversa cuando la función sale del ámbito (Last In, First Out).

      Puede tener tantas invocaciones diferidas como sea necesario en una función, pero es importante recordar que todas se invocarán en el orden opuesto en el que se ejecutaron.

      Ahora que comprendemos el orden en el cual se ejecutarán varias instrucciones defer, veremos la forma de usar varias instrucciones defer para limpiar varios recursos. Crearemos un programa que abra un archivo, realice tareas de escritura en él y luego lo abra de nuevo para copiar el contenido a otro archivo.

      main.go

      package main
      
      import (
          "fmt"
          "io"
          "log"
          "os"
      )
      
      func main() {
          if err := write("sample.txt", "This file contains some sample text."); err != nil {
              log.Fatal("failed to create file")
          }
      
          if err := fileCopy("sample.txt", "sample-copy.txt"); err != nil {
              log.Fatal("failed to copy file: %s")
          }
      }
      
      func write(fileName string, text string) error {
          file, err := os.Create(fileName)
          if err != nil {
              return err
          }
          defer file.Close()
          _, err = io.WriteString(file, text)
          if err != nil {
              return err
          }
      
          return file.Close()
      }
      
      func fileCopy(source string, destination string) error {
          src, err := os.Open(source)
          if err != nil {
              return err
          }
          defer src.Close()
      
          dst, err := os.Create(destination)
          if err != nil {
              return err
          }
          defer dst.Close()
      
          n, err := io.Copy(dst, src)
          if err != nil {
              return err
          }
          fmt.Printf("Copied %d bytes from %s to %sn", n, source, destination)
      
          if err := src.Close(); err != nil {
              return err
          }
      
          return dst.Close()
      }
      

      Añadimos una nueva función llamada fileCopy. En esta función, primero abrimos nuestro archivo de origen desde el que realizaremos la copia. Comprobaremos si se mostró un error al abrir el archivo. Si es así, aplicaremos return al error y cerraremos la función. De lo contrario, aplicaremos defer al cierre del archivo de origen que acabamos de abrir.

      A continuación, crearemos un archivo de destino. De nuevo, comprobaremos si aparece un error al crear el archivo. Si esto sucede, aplicaremos return a ese error y cerraremos la función. De lo contrario, también aplicaremos defer a Close() para el archivo de destino. Ahora tenemos dos funciones defer que se invocarán cuando la función cierre su ámbito.

      Ahora que ambos archivos están abiertos, aplicaremos Copy() a los datos del archivo de origen al de destino. Si esto se realiza correctamente, intentaremos cerrar ambos archivos. Si observamos un error al intentar cerrar cualquiera de los archivos, aplicaremos return al error y cerraremos el ámbito de la función.

      Observe que invocamos de forma explícita a Close() para cada archivo, aunque defer también invoque a Close(). Esto es para garantizar que notifiquemos el error si hay un error al cerrar un archivo. También garantiza que si, por cualquier motivo, la función se cierra antes de tiempo con un error, por ejemplo, si no pudimos realizar una copia entre los dos archivos, cada uno de ellos intentará cerrarse de forma adecuada a partir de las invocaciones diferidas.

      Conclusión

      En este artículo, incorporó conocimientos sobre la instrucción defer y la forma en que puede usarse para verificar que se hayan limpiado correctamente los recursos del sistema en nuestro programa. Limpiar correctamente los recursos del sistema hará que su programa utilice menos memoria y funcione mejor. Para obtener más información acerca de las aplicaciones de defer, lea el artículo sobre el manejo de Panics o consulte nuestra serie Cómo realizar codifcaciones en Go.



      Source link