Cadenas en Go
En Go, una cadena es esencialmente una secuencia de bytes inmutable y de solo lectura (byte sequence). Aquí "secuencia de bytes" significa que los datos subyacentes de la cadena están compuestos por una serie de bytes ordenados secuencialmente, estos bytes ocupan un espacio de memoria continuo.
Literales
Anteriormente se mencionó que las cadenas tienen dos formas de expresarse como literales, divididas en cadenas ordinarias y cadenas raw.
Cadenas ordinarias
Las cadenas ordinarias se representan con comillas dobles "", soportan escapes, no soportan escritura en múltiples líneas. A continuación algunas cadenas ordinarias:
"esta es una cadena ordinaria\n"
"abcdefghijlmn\nopqrst\t\\uvwxyz"esta es una cadena ordinaria
abcdefghijlmn
opqrst \uvwxyzCadenas raw
Las cadenas raw se representan con acentos graves (backticks), no soportan escapes, soportan escritura en múltiples líneas. Todos los caracteres dentro de una cadena raw se output tal cual, incluyendo saltos de línea e indentación.
`esta es una cadena raw, salto de línea
tabulación con tab, \t tabulador pero inválido, salto de línea
"esta es una cadena ordinaria"
fin
`esta es una cadena raw, salto de línea
tabulación con tab, \t tabulador pero inválido, salto de línea
"esta es una cadena ordinaria"
finAcceso
Como las cadenas son esencialmente secuencias de bytes, su operación de índice str[i] está diseñada para devolver el i-ésimo byte. La sintaxis es consistente con los slices, por ejemplo para acceder al primer elemento de una cadena:
func main() {
str := "this is a string"
fmt.Println(str[0])
}La salida es el valor de codificación del byte, no el carácter:
116Cortar una cadena:
func main() {
str := "this is a string"
fmt.Println(string(str[0:4]))
}thisIntentar modificar un elemento de la cadena:
func main() {
str := "this is a string"
str[0] = 'a' // no puede compilar
fmt.Println(str)
}main.go:7:2: cannot assign to str[0] (value of type byte)Aunque no se puede modificar una cadena, se puede sobrescribir:
func main() {
str := "this is a string"
str = "that is a string"
fmt.Println(str)
}that is a stringConversión
Una cadena se puede convertir a un slice de bytes, y un slice de bytes o secuencia de bytes también se puede convertir a una cadena, por ejemplo:
func main() {
str := "this is a string"
// conversión explícita de tipo a slice de bytes
bytes := []byte(str)
fmt.Println(bytes)
// conversión explícita de tipo a cadena
fmt.Println(string(bytes))
}El contenido de una cadena es de solo lectura e inmutable, no se puede modificar, pero un slice de bytes sí se puede modificar.
func main() {
str := "this is a string"
fmt.Println(&str)
bytes := []byte(str)
// modificar el slice de bytes
bytes = append(bytes, 96, 97, 98, 99)
// asignar a la cadena original
str = string(bytes)
fmt.Println(str)
}Después de convertir una cadena a un slice de bytes, no hay ninguna relación entre ambos, porque Go asignará un nuevo espacio de memoria para el slice de bytes y luego copiará la memoria de la cadena. Modificar el slice de bytes no tendrá ningún efecto en la cadena original, esto es por seguridad de memoria.
En este caso, si la cadena o el slice de bytes a convertir es muy grande, el costo de rendimiento será alto. Sin embargo, también se puede lograr una conversión sin copia a través de la biblioteca unsafe, pero la seguridad subyacente es responsabilidad propia, como en el siguiente ejemplo, las direcciones de b1 y s1 son iguales.
func main() {
s1 := "hello world"
b1 := unsafe.Slice(unsafe.StringData(s1), len(s1))
fmt.Printf("%p %p", unsafe.StringData(s1), unsafe.SliceData(b1))
}0xe27bb2 0xe27bb2Longitud
La longitud de una cadena no es realmente el número de caracteres, sino la longitud de la secuencia de bytes. Es solo que la mayoría de las veces procesamos caracteres ASCII, donde cada carácter puede representarse con un solo byte, por lo que la longitud en bytes y el número de caracteres coinciden. Para obtener la longitud de una cadena se usa la función incorporada len, por ejemplo:
func main() {
str := "this is a string" // parece que la longitud es 16
str2 := "这是一个字符串" // parece que la longitud es 7
fmt.Println(len(str), len(str2))
}16 21Parece que la cadena de caracteres chinos es más corta que la de caracteres ingleses, pero la longitud real obtenida es mayor que la de la cadena inglesa. Esto se debe a que en la codificación unicode, un carácter chino en la mayoría de los casos ocupa 3 bytes, mientras que un carácter inglés solo ocupa un byte. Esto se puede ver al mostrar el primer elemento de la cadena:
func main() {
str := "this is a string"
str2 := "这是一个字符串"
fmt.Println(string(str[0]))
fmt.Println(string(str2[0]))
fmt.Println(string(str2[0:3]))
}t // letra t
è // un "fragmento" de un carácter chino (el primer byte), su valor de codificación coincide con el carácter è italiano
这 // carácter chinoCopia
Similar a la forma de copiar arrays y slices, la copia de cadenas es en realidad copia de slices de bytes, usando la función incorporada copy:
func main() {
var dst, src string
src = "this is a string"
desBytes := make([]byte, len(src))
copy(desBytes, src)
dst = string(desBytes)
fmt.Println(src, dst)
}También se puede usar la función strings.Clone, pero su implementación interna es similar:
func main() {
var dst, src string
src = "this is a string"
dst = strings.Clone(src)
fmt.Println(src, dst)
}Concatenación
La concatenación de cadenas usa el operador +:
func main() {
str := "this is a string"
str = str + " that is a int"
fmt.Println(str)
}También se puede convertir a un slice de bytes y luego agregar elementos:
func main() {
str := "this is a string"
bytes := []byte(str)
bytes = append(bytes, "that is a int"...)
str = string(bytes)
fmt.Println(str)
}Ambos métodos de concatenación anteriores tienen un rendimiento pobre, se pueden usar en situaciones normales, pero si se tienen mayores requisitos de rendimiento, se puede usar strings.Builder:
func main() {
builder := strings.Builder{}
builder.WriteString("this is a string ")
builder.WriteString("that is a int")
fmt.Println(builder.String())
}this is a string that is a intRecorrido
Al principio de este artículo se mencionó que las cadenas en Go son simplemente un slice de bytes de solo lectura, es decir, la unidad de composición de una cadena es el byte, no el carácter. Esta situación se encuentra a menudo al recorrer cadenas, por ejemplo en el siguiente código:
func main() {
str := "hello world!"
for i := 0; i < len(str); i++ {
fmt.Printf("%d,%x,%s\n", str[i], str[i], string(str[i]))
}
}El ejemplo muestra la forma decimal y hexadecimal de los bytes:
104,68,h
101,65,e
108,6c,l
108,6c,l
111,6f,o
32,20,
119,77,w
111,6f,o
114,72,r
108,6c,l
100,64,d
33,21,!Como los caracteres del ejemplo son todos caracteres ASCII, solo se necesita un byte para representarlos, por lo que el resultado es que cada byte corresponde a un carácter. Pero si contiene caracteres no ASCII, el resultado es diferente:
func main() {
str := "hello 世界!"
for i := 0; i < len(str); i++ {
fmt.Printf("%d,%x,%s\n", str[i], str[i], string(str[i]))
}
}Normalmente, un carácter chino ocupa 3 bytes, por lo que se pueden ver los siguientes resultados:
104,68,h
101,65,e
108,6c,l
108,6c,l
111,6f,o
32,20,
228,e4,ä
184,b8,¸
150,96,
231,e7,ç
149,95,
140,8c,
33,21,!Recorrer por bytes separará los caracteres chinos, lo que obviamente causará caracteres extraños. Las cadenas de Go soportan explícitamente UTF-8, para manejar esta situación se necesita usar el tipo rune. Al recorrer con for range, la unidad de recorrido por defecto es de tipo rune, por ejemplo:
func main() {
str := "hello 世界!"
for _, r := range str {
fmt.Printf("%d,%x,%s\n", r, r, string(r))
}
}La salida es:
104,68,h
101,65,e
108,6c,l
108,6c,l
111,6f,o
32,20,
19990,4e16,世
30028,754c,界
33,21,!rune es esencialmente un alias de tipo int32. El rango del conjunto de caracteres Unicode está entre 0x0000 - 0x10FFFF, el máximo es de solo 3 bytes, la codificación UTF-8 válida tiene un máximo de 4 bytes, por lo que usar int32 para almacenar es natural. En el ejemplo anterior, convertir la cadena a []rune y luego recorrer es lo mismo:
func main() {
str := "hello 世界!"
runes := []rune(str)
for i := 0; i < len(runes); i++ {
fmt.Println(string(runes[i]))
}
}También se pueden usar las herramientas del paquete utf8, por ejemplo:
func main() {
str := "hello 世界!"
for i, w := 0, 0; i < len(str); i += w {
r, width := utf8.DecodeRuneInString(str[i:])
fmt.Println(string(r))
w = width
}
}La salida de estos dos ejemplos es la misma.
TIP
Para más detalles sobre cadenas, puedes ir a Strings, bytes, runes and characters in Go.
