conceptos básicos de go 09-Tipo de cadena del lenguaje Go

El tipo de cadena es uno de los tipos de datos más utilizados en los lenguajes de programación modernos. En el lenguaje C, uno de los ancestros del lenguaje Go, el tipo de cadena no está definido explícitamente, sino que se presenta como una constante literal de cadena o una matriz de tipo de carácter (char) terminada en '\0':

#define GOAUTHERS "Robert Griesemer, Rob Pike, and Ken Thompson"
const char * s = "hello world"
char s[] = "hello gopher"


Esto trae algunos problemas a los programadores de C cuando usan cadenas, como por ejemplo:

● Seguridad de tipo deficiente;

● Las operaciones de cadena siempre deben considerar la terminación '\0';

● Los datos de cadena son variables (se refiere principalmente al tipo de cadena definido en forma de matriz de caracteres);

● Obtener la longitud de la cadena es costoso (complejidad temporal O(n));

● No hay procesamiento integrado de caracteres que no sean ASCII (como los caracteres chinos).

El lenguaje Go solucionó este "defecto" del lenguaje C, el tipo de cadena incorporado y unificó la abstracción de cadenas.

Tipo de cadena en lenguaje Go

En el lenguaje Go, ya sea una constante de cadena, una variable de cadena o un literal de cadena que aparece en el código, su tipo se establece uniformemente en cadena:

const (
	s = "string constant"
)
func main() {
    
    
	var s1 string = "string variable"
	fmt.Printf("%T\n", s) // string
	fmt.Printf("%T\n", s1) // string
	fmt.Printf("%T\n", "temporary string literal") // string
}

El diseño de tipo de cadena de Go se basa completamente en la experiencia y las lecciones del diseño de cadenas en lenguaje C y combina las mejores prácticas en diseño de tipos de cadena de otros lenguajes convencionales.El tipo de cadena final tiene las siguientes características funcionales .

(1) Los datos de tipo cadena son inmutables

Una vez que se declara un identificador de tipo cadena, ya sea una constante o una variable, los datos a los que hace referencia el identificador no se pueden cambiar durante todo el ciclo de vida del programa. Intentemos modificar los datos de la cadena y ver qué resultados podemos obtener.

Veamos primero el primer método:

func main() {
    
    
// 原始字符串
	var s string = "hello"
	fmt.Println("original string:", s)
	// 切片化后试图改变原字符串
	sl := []byte(s)
	sl[0] = 't'
	fmt.Println("slice:", string(sl))
	fmt.Println("after reslice, the original string is:", 	string(s))
}

Los resultados de ejecutar este programa son los siguientes:


original string: hello
slice: tello
after reslice, the original string is: hello

En el ejemplo anterior, intentamos convertir la cadena en un segmento y modificar su contenido a través del segmento, pero el resultado fue contraproducente. Después de dividir una cadena, el compilador Go reasignará el almacenamiento subyacente para la variable de segmento en lugar de compartir el almacenamiento subyacente de la cadena. Por lo tanto, la modificación del segmento no tiene ningún impacto en los datos de la cadena original.

Intentemos "atacar" datos de cadena por medios más "violentos":

func main() {
    
    
// 原始string
var s string = "hello"
fmt.Println("original string:", s)
// 试图通过unsafe指针改变原始string
modifyString(&s)
fmt.Println(s)

}



func modifyString(s *string) {
    
    
	// 取出第一个8字节的值
	p := (*uintptr)(unsafe.Pointer(s))
	// 获取底层数组的地址
	var array *[5]byte = (*[5]byte)(unsafe.Pointer(*p))
	var len *int = (*int)(unsafe.Pointer(uintptr(unsafe.Pointer(s)) + unsafe.Sizeof((*uintptr)(nil))))
	for i := 0; i < (*len); i++ {
    
    
		fmt.Printf("%p => %c\n", &((*array)[i]), (*array)[i])
		p1 := &((*array)[i])
		v := (*p1)
		(*p1) = v + 1 //try to change the character
	}
}

Intentamos utilizar un puntero inseguro para señalar la dirección del bloque de almacenamiento de datos en la estructura de representación interna de la cadena en tiempo de ejecución (consulte la explicación más adelante en este artículo para obtener más detalles) y luego modificamos los datos almacenados en esa memoria a través del puntero. .

La ejecución de este programa produce los siguientes resultados:

original string: hello
0x10d1b9d => h
unexpected fault address 0x10d1b9d
fatal error: fault
[signal SIGBUS: bus error code=0x2 addr=0x10d1b9d pc=0x109b079]

Vemos que solo se pueden realizar operaciones de solo lectura en el área de almacenamiento de datos subyacente de la cadena. Una vez que intente modificar los datos en esa área, obtendrá un error de tiempo de ejecución de SIGBUS y el "ataque de manipulación" en Los datos de cadena fallarán nuevamente.

2) Valor cero disponible

El tipo de cadena Go admite la filosofía de "valor cero disponible". Las cadenas Go no necesitan considerar el carácter final '\0' como en el lenguaje C, por lo que su valor cero es "" y su longitud es 0.

var s string
fmt.Println(s) // s = ""
fmt.Println(len(s)) // 0


3) La complejidad temporal para obtener la longitud es el nivel O (1)

Los datos de tipo cadena de Go son inmutables, por lo que una vez que tienen un valor inicial, el dato no cambiará, ni su longitud. Go almacena esta longitud como un campo en la estructura de representación interna del tipo de cadena en tiempo de ejecución (se explica más adelante). La operación de obtener la longitud de la cadena de esta manera, es decir, len (s), en realidad lee el valor de longitud almacenado en el tiempo de ejecución, lo que es una operación O (1) de costo extremadamente bajo.

4) Admite concatenación de cadenas mediante el operador +/+=

Para los desarrolladores, la concatenación de cadenas mediante el operador +/+= es la mejor operación de concatenación de cadenas. El lenguaje Go admite esta operación:

s := "Rob Pike, "
s = s + "Robert Griesemer, "
s += " Ken Thompson"
fmt.Println(s) // Rob Pike, Robert Griesemer, Ken Thompson

5) Admite varios operadores de comparación: ==, !=, >=, <=, > y <

func main() {
    
    
	// ==
	s1 := "世界和平"
	s2 := "世界" + "和平"
	fmt.Println(s1 == s2) // true
	// !=
	s1 = "Go"
	s2 = "C"
	fmt.Println(s1 != s2) // true
	// < 和 <=
	s1 = "12345"
	s2 = "23456"
	fmt.Println(s1 < s2) // true
	fmt.Println(s1 <= s2) // true
	// > 和 >=
	s1 = "12345"
	s2 = "123"
	fmt.Println(s1 > s2) // true
	fmt.Println(s1 >= s2) // true
}

Dado que las cadenas de Go son inmutables, si las longitudes de dos cadenas son diferentes, se puede concluir que las dos cadenas son diferentes sin comparar los datos de la cadena específica. Si las longitudes son las mismas, es necesario determinar más a fondo si los punteros de datos apuntan al mismo bloque de datos de almacenamiento subyacentes. Si son iguales, las dos cadenas son equivalentes; si son diferentes, es necesario comparar más a fondo el contenido de los datos reales.

6) Proporcionar soporte nativo para caracteres no ASCII

El conjunto de caracteres Unicode utilizado por los archivos fuente del idioma Go de forma predeterminada. El conjunto de caracteres Unicode es actualmente el conjunto de caracteres más popular del mercado y cubre casi todos los caracteres convencionales que no son ASCII (incluidos los caracteres chinos). Cada carácter de una cadena Go es un carácter Unicode y estos caracteres Unicode se almacenan en la memoria en formato de codificación UTF-8.

Veamos un ejemplo:

func main() {
    
    
	// 中文字符 Unicode码点 UTF8编码
	// 中 U+4E2D E4B8AD
	// 国 U+56FD E59BBD
	// 欢 U+6B22 E6ACA2
	// 迎 U+8FCE E8BF8E
	// 您 U+60A8 E682A8
	s := "中国欢迎您"
	rs := []rune(s)
	sl := []byte(s)
	for i, v := range rs {
    
    
	var utf8Bytes []byte
	for j := i * 3; j < (i+1)*3; j++ {
    
    
		utf8Bytes = append(utf8Bytes, sl[j])
	}
	fmt.Printf("%s => %X => %X\n", string(v), v, utf8Bytes)
}
}

Vemos que el texto almacenado en la variable de cadena s son los cinco caracteres chinos "China le da la bienvenida" (categoría de caracteres no ASCII). Aquí se genera el punto de código Unicode correspondiente a cada carácter chino (Punto de código, consulte la segunda parte de resultado de salida), una runa corresponde a un punto de código. La codificación UTF-8 es una forma de codificación de caracteres de puntos de código Unicode. Es el formato de codificación más utilizado y también es el formato de codificación de caracteres predeterminado de Go. También podemos utilizar otros formatos de codificación de caracteres para asignar puntos de código Unicode, como UTF-16, etc.

En UTF-8, la mayoría de los caracteres chinos se representan mediante tres bytes. La transformación de []byte(s) nos permite obtener una "copia" del almacenamiento subyacente de s, obteniendo así los bytes codificados en UTF-8 correspondientes a cada carácter chino (consulte la tercera columna del resultado de salida).

=> 4E2D => E4B8AD
国 => 56FD => E59BBD
欢 => 6B22 => E6ACA2
迎 => 8FCE => E8BF8E
您 => 60A8 => E682A8

7) Soporte nativo para cadenas de varias líneas

El lenguaje Go proporciona directamente un método para construir una cadena de varias líneas "lo que ves es lo que obtienes" mediante comillas invertidas:

const s = `好雨知时节,当春乃发生。
			随风潜入夜,润物细无声。
			野径云俱黑,江船火独明。
			晓看红湿处,花重锦官城。`
func main() {
    
    
	fmt.Println(s)
}

resultado de la operación:

好雨知时节,当春乃发生。
随风潜入夜,润物细无声。
野径云俱黑,江船火独明。
晓看红湿处,花重锦官城。

Supongo que te gusta

Origin blog.csdn.net/hai411741962/article/details/132740171
Recomendado
Clasificación