Skip to content

Latest commit

 

History

History
919 lines (646 loc) · 25.3 KB

strings.md

File metadata and controls

919 lines (646 loc) · 25.3 KB

1. Strings en Go

Los strings en go merecen especial atención porque se implementan de manera muy diferente en go en comparación con otros lenguajes.

Un string es una porción de bytes en Go. Se pueden crear strings encerrando un conjunto de caracteres entre comillas dobles " ".

Los strings se definen entre comillas dobles "..." y no entre comillas simples, a diferencia de JavaScript. Los strings en go están codificadas en UTF-8 de forma predeterminada.

Como UTF-8 admite el juego de caracteres ASCII, no necesita preocuparse por la codificación en la mayoría de los casos.

Para definir una variable vacía de tipo string, utilice la palabra clave string.

Veamoslo en un ejemplo:

package main

import "fmt"

func main() {
  var s string

  s = "Hello World"

  fmt.Println(s)
}
Hello World

Ejemplo en vivo

Strings en go son Unicode compliant y son UTF-8 Encoded.

1.1 Longitud de un string

Para obtener la longitud de un string, puede usar la función len. La función len está disponible en tiempo de ejecución en go, por lo que no es necesario importarla desde ningún paquete.

package main

import "fmt"

func main() {
  s := "Hello World"

  fmt.Println(len(s))
}
11

Ejemplo en vivo

Important

len es una función universal para encontrar la longitud de cualquier tipo de datos, no es exclusiva de strings.

En el programa anterior, len(s) imprimirá 11 en la consola ya que la cadena s tiene 11 caracteres, incluido un carácter de espacio.

Pero tambien la función RuneCountInString(s string) (n int) del paquete utf8 se puede utilizar para encontrar la longitud del string. Este método toma un string como argumento y devuelve el número de runas que contiene.

Como comentamos anteriormente, len(s) se usa para encontrar el número de bytes en el string y no devuelve la longitud del mismo. Como ya comentamos, algunos caracteres unicode tienen code points que ocupan más de 1 byte. Usar len para averiguar la longitud de esas cadenas devolverá la longitud de cadena incorrecta.

package main

import (
	"fmt"
	"unicode/utf8"
)

func main() {
	word1 := "Señor"
	fmt.Printf("String: %s\n", word1)
	fmt.Printf("Length: %d\n", utf8.RuneCountInString(word1))
	fmt.Printf("Number of bytes: %d\n", len(word1))

	fmt.Printf("\n")
	word2 := "Pets"
	fmt.Printf("String: %s\n", word2)
	fmt.Printf("Length: %d\n", utf8.RuneCountInString(word2))
	fmt.Printf("Number of bytes: %d\n", len(word2))
}

Ejemplo en vivo

La salida impresa de este ejemplo seria

String: Señor
Length: 5
Number of bytes: 6

String: Pets
Length: 4
Number of bytes: 4

Ye esta salida impresa confirma que, len(s) y RuneCountInString(s) retornan valores diferentes.

1.2 Accediendo a bytes individuales de un string

Todos los caracteres de la cadena Hello World son caracteres ASCII válidos, por lo que esperamos que cada carácter ocupe solo un byte en la memoria (ya que los caracteres ASCII en UTF-8 ocupan 8 bits o 1 byte).

Dado que una cadena es un slice de bytes, es posible acceder a cada byte de un string.

Verifiquemos eso usando un bucle for en la cadena s.

package main

import "fmt"

func main() {
  s := "Hello World"

  for i := 0; i < len(s); i++ {
    fmt.Print(s[i], " ")
  }

  fmt.Println()
}
72 101 108 108 111 32 87 111 114 108 100

Ejemplo en vivo

Supongo que esperabas que s[i] fuera una letra en el string s donde i es el índice del carácter en el string que comienza en 0. Entonces, ¿qué es esto? Bueno, estos son los valores decimales de los caracteres ASCII/UTF-8 en el string Hello World (consulte la tabla).

H - 72
e - 101
l - 108
l - 108
o - 111
  - 32
W - 87
o - 111
r - 114
l - 108
d - 100

En go, un string es, de hecho, un slice de bytes de solo lectura. Por ahora, imagina que un slice es como una matriz simple.

En el ejemplo anterior, estamos iterando sobre un slice de bytes (valores de la matriz uint8). Por lo tanto, s[i] imprime el valor decimal del byte que contiene el carácter. Pero para ver caracteres individuales, puede usar la cadena de formato %c en la declaración Printf. También puede usar la cadena de formato %v para ver el valor del byte y %T para ver el tipo de datos del valor.

package main

import "fmt"

func main() {
  s := "Hello World"
  fmt.Println("len(s)", len(s))

  for i := 0; i < len(s); i++ {
    fmt.Printf("%c ", s[i])
  }

  fmt.Println("")

  for i := 0; i < len(s); i++ {
    fmt.Printf("%v ", s[i])
  }

  fmt.Println("")

  for i := 0; i < len(s); i++ {
    fmt.Printf("%x ", s[i])
  }

  fmt.Println("")
          for i := 0; i < len(s); i++ {
    fmt.Printf("%T ", s[i])
  }

  fmt.Println("")
}
len(s) 11
H e l l o   W o r l d
72 101 108 108 111 32 87 111 114 108 100
48 65 6c 6c 6f 20 57 6f 72 6c 64
uint8 uint8 uint8 uint8 uint8 uint8 uint8 uint8 uint8 uint8 uint8

Ejemplo en vivo

Como puedes ver, cada letra muestra un número decimal que contiene 8 bits o 1 byte de memoria en el tipo uint8.

Como sabemos, los caracteres UTF-8 se pueden definir en un tamaño de memoria desde 1 byte (compatible con ASCII) hasta 4 bytes. Por lo tanto, en go, todos los caracteres se representan en el tipo de datos int32 (tamaño de 4 bytes). Una unidad de código es el número de bits que utiliza una codificación para una sola celda unitaria. Entonces, UTF-8 usa 8 bits y UTF-16 usa 16 bits para una unidad de código, eso significa que UTF-8 necesita un mínimo de 8 bits o 1 byte para representar un carácter.

Pero la pregunta más importante es, si todos los caracteres en UTF-8 están representados en int32, entonces ¿por qué obtenemos el tipo uint8 en el ejemplo anterior? Como se dijo anteriormente, en go, un string es una porción de bytes de solo lectura. Cuando usamos la función len en un string, calcula la longitud de ese slice.

Cuando usamos el bucle for, recorre el slice y devuelve un byte a la vez o una unidad de código a la vez. Como hasta ahora, todos nuestros caracteres estaban en el conjunto de caracteres ASCII, el byte proporcionado por el bucle for era un carácter válido o una unidad de código era, de hecho, un code point.

Por lo tanto, %c en la declaración Printf podría imprimir un carácter válido de ese valor de byte. Pero como sabemos, el code point UTF-8 o el valor de carácter se pueden representar mediante series de uno o más bytes (máximo 4 bytes). ¿Qué pasará en el bucle for que vimos antes si introducimos caracteres que no sean ASCII?

Reemplacemos o en Hola por õ letra o minúscula latina con tilde que tiene una representación unicode U+00F5 y está representado por 2 unidades de código (2 bytes) c3 b5 (representación hexadecimal). Entonces, en lugar de 6f para el carácter o, deberíamos esperar c3 b5 para el carácter õ.

package main

import (
 "fmt"
)

func main() {
  s := "Hellõ World"
  fmt.Println("len(u)", len(s))

  for i := 0; i < len(s); i++ {
  fmt.Printf("%c ", s[i])
  }

  fmt.Println("")

  for i := 0; i < len(s); i++ {
  fmt.Printf("%v ", s[i])
  }

  fmt.Println("")

  for i := 0; i < len(s); i++ {
  fmt.Printf("%x ", s[i])
  }

  fmt.Println("")
}
len(s) 12
H e l l à µ   W o r l d
72 101 108 108 195 181 32 87 111 114 108 100
48 65 6c 6c c3 b5 20 57 6f 72 6c 64

Ejemplo en vivo

Del resultado anterior obtuvimos c3 b5 en lugar de 6f pero los caracteres de Hellõ World no se imprimieron muy bien. También vemos que len(s) devuelve 12 porque len cuenta el número de bytes en una cadena y esto genero este problema.

Como indexar un string (usando un bucle for) se accede a bytes individuales, no a caracteres. Por lo tanto, c3 (195 decimal) en UTF-8 representa à y b5 (181 decimal) representa µ.

Para evitar el caos anterior, go introduce el tipo de datos rune que es un alias de int32 y les explicaba (pero aún no lo he demostrado) que go representa un carácter en el tipo de datos int32.

Note

Una respuesta interesante sobre por qué runa es int32 y no uint32 (ya que el valor del code point de carácter no puede ser negativo y el tipo de datos int32 puede contener valores tanto negativos como positivos) está aquí.

Entonces, en lugar de slice de bytes, necesitamos convertir un string en un slice de runas.

package main

import "fmt"

func main() {
  s := "Hellõ World"
  r := []rune(s)

  fmt.Println("len(r)", len(r))

  for i := 0; i < len(r); i++ {
    fmt.Printf("%c ", r[i])
  }

  fmt.Println("")

  for i := 0; i < len(r); i++ {
    fmt.Printf("%v ", r[i])
  }

  fmt.Println("")

  for i := 0; i < len(r); i++ {
    fmt.Printf("%x ", r[i])
  }

  fmt.Println("")

  for i := 0; i < len(r); i++ {
    fmt.Printf("%T ", r[i])
  }

  fmt.Println("")
}
H e l l õ   W o r l d
72 101 108 108 245 32 87 111 114 108 100
48 65 6c 6c f5 20 57 6f 72 6c 64
int32 int32 int32 int32 int32 int32 int32 int32 int32 int32 int32

Ejemplo en vivo

Convertimos una cadena en una porción de runas mediante conversión de tipos. Observe f5 en el resultado anterior en lugar de c3 b5.

Esto sucedió porque al convertir la cadena s en un slice de runas, c3 b5 se convirtió a f5 ya que c3 b5 representa colectivamente el carácter õ y el code point de õ en la tabla UTF es f5 (por lo tanto, la representación del code point Unicode U+00F5) o decimal 245 (consultar aquí).

Además, obtuvimos la longitud 11 de la cadena s, lo cual es correcto, porque hay 11 runas en el segmento (o 11 code point o 11 caracteres). Y también demostramos que un code point o un carácter en Go está representado por el tipo de datos int32.

1.3 Usando un loop for/range en un string

Si usa range dentro de un bucle for, range devolverá una rune y el indice del byte del carácter.

package main

import "fmt"

func main() {
  s := "Hellõ World"

  for index, char := range s {
    fmt.Printf("character at index %d is %c\n", index, char)
  }
}
character at index 0 is H
character at index 1 is e
character at index 2 is l
character at index 3 is l
character at index 4 is õ
character at index 6 is
character at index 7 is W
character at index 8 is o
character at index 9 is r
character at index 10 is l
character at index 11 is d

Ejemplo en vivo

En el programa anterior, perdimos el índice 5 porque el quinto byte es la segunda code unit del carácter õ. Si no necesitas el valor del índice, puedes ignorarlo usando _ (blank identifier) en su lugar.

Podemos ver otro ejemplo

package main

import (
  "fmt"
)

func printBytes(s string) {
  fmt.Printf("Bytes: ")
  for i := 0; i < len(s); i++ {
    fmt.Printf("%x ", s[i])
  }
}

func printChars(s string) {
  fmt.Printf("Characters: ")
  for i := 0; i < len(s); i++ {
    fmt.Printf("%c ", s[i])
  }
}

func main() {
  name := "Hello World"
  fmt.Printf("String: %s\n", name)
  printChars(name)
  fmt.Printf("\n")
  printBytes(name)
}
String: Hello World
Characters: H e l l o   W o r l d
Bytes: 48 65 6c 6c 6f 20 57 6f 72 6c 64

Ejemplo en vivo

En la línea número 17 del programa anterior, el selector de formato %c se utiliza para imprimir los caracteres del string en el método printChars.

Aunque el programa del ejemplo anterior parece una forma legítima de acceder a los caracteres individuales de una cadena, tiene el mismo error grave que ya hemos comentado en ejemplos anteriores. Averigüemos cuál es ese error.

package main

import (
  "fmt"
)

func printBytes(s string) {
  fmt.Printf("Bytes: ")
  for i := 0; i < len(s); i++ {
    fmt.Printf("%x ", s[i])
  }
}

func printChars(s string) {
  fmt.Printf("Characters: ")
  for i := 0; i < len(s); i++ {
    fmt.Printf("%c ", s[i])
  }
}

func main() {
  name := "Hello World"

  fmt.Printf("String: %s\n", name)
  printChars(name)

  fmt.Printf("\n")
  printBytes(name)

  fmt.Printf("\n\n")
  name = "Señor"

  fmt.Printf("String: %s\n", name)
  printChars(name)

  fmt.Printf("\n")
  printBytes(name)
}

Output

String: Hello World
Characters: H e l l o   W o r l d
Bytes: 48 65 6c 6c 6f 20 57 6f 72 6c 64

String: Señor
Characters: S e à ± o r
Bytes: 53 65 c3 b1 6f 72

En la línea no. 30 del programa anterior, estamos intentando imprimir los caracteres de Señor y el output que obtenemos S e à ± o r, lo cual es incorrecto. ¿Por qué se rompe este programa para Señor cuando funciona perfectamente bien para Hola Mundo? La razón es que el code point Unicode de ñ es U+00F1 y su codificación UTF-8 ocupa 2 bytes c3 y b1. Estamos intentando imprimir caracteres asumiendo que cada code point tendrá una longitud de un byte, lo cual es incorrecto. En la codificación UTF-8, un code point puede ocupar más de 1 byte. Entonces, ¿cómo solucionamos esto? Aquí, como mencionabamos antes, es donde el tipo runa (rune) nos salva.

1.4 Que es una runa

Un string es una slice de bytes o enteros uint8, así de simple. Cuando usamos el bucle for/range, obtenemos runa porque cada carácter del string está representado por el tipo de datos de runa.

En go, un carácter se puede representar entre comillas simples, también conocido como carácter literal. Por lo tanto, cualquier carácter UTF-8 válido dentro de una comilla simple (') es una runa y su tipo es int32.

package main

import "fmt"

func main() {
  r := 'õ'

  fmt.Printf("%x ", r)
  fmt.Printf("%v ", r)
  fmt.Printf("%T", r)

  fmt.Println()
}
f5 245 int32

El programa anterior imprimirá f5 245 int32 que es un valor hexadecimal/decimal y un tipo de datos de valor de code point de õ en la tabla UTF.

Ejemplo en vivo

Una runa es un tipo incorporado en go y es el alias de int32. Rune representa un code point unicode en go. No importa cuántos bytes ocupe el punto de código, puede representarse mediante una runa. Modifiquemos el programa anterior para imprimir caracteres usando una runa:

package main

import (
 "fmt"
)

func printBytes(s string) {
  fmt.Printf("Bytes: ")

  for i := 0; i < len(s); i++ {
    fmt.Printf("%x ", s[i])
  }
}

func printChars(s string) {
  fmt.Printf("Characters: ")

  runes := []rune(s)

  for i := 0; i < len(runes); i++ {
    fmt.Printf("%c ", runes[i])
  }
}

func main() {
  name := "Hello World"

  fmt.Printf("String: %s\n", name)
  printChars(name)
  fmt.Printf("\n")
  printBytes(name)

  fmt.Printf("\n\n")

  name = "Señor"

  fmt.Printf("String: %s\n", name)
  printChars(name)
  fmt.Printf("\n")
  printBytes(name)
}

Ejemplo en vivo

En la línea no. 16 del programa anterior, el string se convierte en un slice de runas. Luego lo recorremos y mostramos los caracteres. Este programa imprime:

String: Hello World
Characters: H e l l o   W o r l d
Bytes: 48 65 6c 6c 6f 20 57 6f 72 6c 64

String: Señor
Characters: S e ñ o r
Bytes: 53 65 c3 b1 6f 72

1.5 Strings son inmutables

Como se ve en la definición anterior de strings, son un slice de bytes de solo lectura. Por lo tanto, si intentamos reemplazar cualquier byte en el segmento, el compilador arrojará un error.

package main

import "fmt"

func main() {
  s := "Hello World"

  s[0] = 'F'

  fmt.Println(s)
}
./prog.go:8:2: cannot assign to s[0] (neither addressable nor a map index expression)

Go build failed.

Ejemplo en vivo

El programa anterior no se compilará y el compilador arrojará un error, no se puede asignar a s[0] ya que la cadena s es un slice de bytes de solo lectura.

Sin embargo, puede crear un string a partir de un slice de bytes y no solo a partir de un string literal. Pero una vez realizada la conversión de slice a string, no podrá modificar el string como se explica en el ejemplo anterior.

var1 := []uint8{72, 101, 108, 108, 111} // [72 101 108 108 111]
var2 := string(var1) // Hello

Note

Recuerda que, un byte es un alias para unit8 y rune es un alias para int32. Por lo tanto, puedes usarlos indistintamente.

Veamos otro ejemplo en el que haremos esto mismo pero usando una funcion que nosotros mismos crearemos mutate

package main

import (
  "fmt"
)

func mutate(s []rune) string {
	s[0] = 'a'
	return string(s)
}

func main() {
  h := "hello"
  fmt.Println(mutate([]rune(h)))
}

Ejemplo en vivo`

Cuya salida seria

aello

1.6 Strings usando backtick (comillas invertidas)

En lugar de comillas dobles, también podemos usar el carácter de comilla invertida backtick (`) para representar una cadena en Go. Usando comillas dobles (“) debes escapar de nuevas líneas, tabulaciones y otros caracteres que no necesitan escaparse entre comillas invertidas.

Si pones un salto de línea en una cadena de acento grave, se interpreta como un carácter '\n', ver string literals

Note

El valor de un string literal sin formato es el string formado por caracteres no interpretados (implícitamente codificados en UTF-8) entre las comillas invertidas; en particular, las barras invertidas no tienen un significado especial y el string puede contener nuevas líneas. Los caracteres de retorno de carro (\r) dentro de un string literal sin formato se descartan del valor de string sin formato.

Veamos un pequeño ejemplo

package main

import "fmt"

func main() {
  s := `Hello,\n
  My Big Blue
  "World"!`

  fmt.Println(s)
}
Hello,\n
  My Big Blue
 "World"!

Ejemplo en vivo

Podemos ver que el formato original del string con una nueva línea, tabulación y las dobles comillas se mantuvieron en la salida y el carácter de nueva línea \n no afecto en nada mientras que se descartó el retorno de carro \r.

1.7 Comparacion de caracteres

Como el carácter representado entre comillas simples en Go es runa, la runa se puede comparar porque representan code points Unicode (valores int32). Por lo tanto, si un carácter tiene más valor decimal, será mayor que el carácter que tiene menor.

Veamos un ejemplo muy sencillo.

package main

import (
 "fmt"
)

func main() {
  fmt.Printf("value of character a is %v of type %T\n", 'a', 'a')
  fmt.Printf("value of character b is %v of type %T\n", 'b', 'b')
  fmt.Println("hence 'b' > 'a' is", 'b' > 'a')
}
value of character a is 97 of type int32
value of character b is 98 of type int32
hence 'b' > 'a' is true

Ejemplo en vivo

Dado que el valor int32 de b es mayor que a, la expresión 'b' > 'a' será verdadera. Veamos otro ejemplo.

package main

import (
 "fmt"
)

func main() {
  fmt.Printf("value of character a is %v of type %T\n", 'a', 'a')
  fmt.Printf("value of character A is %v of type %T\n", 'A', 'A')
  fmt.Println("hence 'A' > 'a' is", 'A' > 'a')

  fmt.Printf("\nvalue of character ℻ is %v of type %T\n", '℻', '℻')
  fmt.Printf("value of character ™ is %v of type %T\n", '™', '™')
  fmt.Println("hence '℻' > '™' is", '℻' > '™')
}
value of character a is 97 of type int32
value of character A is 65 of type int32
hence 'A' > 'a' is false

value of character ℻ is 8507 of type int32
value of character ™ is 8482 of type int32
hence '℻' > '™' is true

Ejemplo en vivo

Como sabemos que los caracteres internamente no son más que int32, podemos hacer todo tipo de comparaciones con ellos. Por ejemplo, un bucle for entre dos rangos de valores de caracteres.

package main

import (
  "fmt"
)

func main() {
  for i := 'a'; i < 'g'; i++ {
    fmt.Printf("character = '%c' with decimal value %v\n", i, i)
  }
}
character = 'a' with decimal value 97
character = 'b' with decimal value 98
character = 'c' with decimal value 99
character = 'd' with decimal value 100
character = 'e' with decimal value 101
character = 'f' with decimal value 102

Ejemplo en vivo

1.8 Comparacion de strings

El operador == se utiliza para comparar la igualdad de dos cadenas. Si ambas cadenas son iguales, entonces el resultado es true, de lo contrario, es false.

package main

import (
	"fmt"
)

func compareStrings(str1 string, str2 string) {
	if str1 == str2 {
		fmt.Printf("%s and %s are equal\n", str1, str2)
		return
	}
	fmt.Printf("%s and %s are not equal\n", str1, str2)
}

func main() {
	string1 := "Go"
	string2 := "Go"
	compareStrings(string1, string2)

	string3 := "hello"
	string4 := "world"
	compareStrings(string3, string4)

}

Ejemplo en vivo

En la función compareStrings anterior, la línea no. 8 compara si los dos str1 y str2 son iguales usando el operador ==. Si son iguales, imprime el mensaje correspondiente y la función retorna.

El programa anterior imprime,

Go and Go are equal
hello and world are not equal

1.9 Crear un string a partir de un slice de bytes

package main

import (
  "fmt"
)

func main() {
  byteSlice := []byte{0x43, 0x61, 0x66, 0xC3, 0xA9}
  str := string(byteSlice)
  fmt.Println(str)
}

Ejemplo en vivo

byteSlice en la línea no. 8 del programa anterior contiene los bytes hexadecimales codificados en UTF-8 de la cadena Café. El programa imprime

Café

Pero, ¿Qué pasa si tenemos el equivalente decimal de los valores hexadecimales? ¿Funcionará el programa anterior? Vamos a ver.

package main

import (
  "fmt"
)

func main() {
  byteSlice := []byte{67, 97, 102, 195, 169}//decimal equivalent of {'\x43', '\x61', '\x66', '\xC3', '\xA9'}
  str := string(byteSlice)
  fmt.Println(str)
}
Café

Ejemplo en vivo

Los valores decimales también funcionan y el programa anterior también imprimirá Café.

1.10 Creando un string a partir de un slice de runas

package main

import (
  "fmt"
)

func main() {
  runeSlice := []rune{0x0053, 0x0065, 0x00f1, 0x006f, 0x0072}
  str := string(runeSlice)
  fmt.Println(str)
}

Ejemplo en vivo

En el programa anterior, runeSlice contiene los code points uinicode de la cadena Señor en hexadecimal. El programa imprime una salida:

Señor

1.11 Concatenacion de strings

Hay varias formas de realizar la concatenación de cadenas en go. Veamos un par de ellas.

La forma más sencilla de realizar la concatenación de cadenas es utilizar el operador +.

package main

import (
	"fmt"
)

func main() {
	string1 := "Go"
	string2 := "is awesome"
	result := string1 + " " + string2
	fmt.Println(result)
}

Ejemplo en vivo

En el programa anterior, en la línea no. 10, string1 se concatena con la string2 con un espacio en el medio. Este programa imprime,

Go is awesome

La segunda forma de concatenar cadenas es utilizar la función Sprintf del paquete fmt.

La función Sprintf formatea una cadena de acuerdo con el selector de formato de entrada y devuelve la cadena resultante. Reescribamos el programa anterior usando la función Sprintf.

package main

import (
	"fmt"
)

func main() {
	string1 := "Go"
	string2 := "is awesome"
	result := fmt.Sprintf("%s %s", string1, string2)
	fmt.Println(result)
}

Ejemplo en vivo

En la línea no. 10 del programa anterior, %s %s es la entrada del selector de formato para Sprintf. Este selector de formato toma dos strings como entrada y tiene un espacio entre ellas. Esto concatenará los dos strings con un espacio en el medio. El string resultante se almacena en result. Este programa también imprime,

Go is awesome

2. Referencias