Resumo de como aprender rapidamente a linguagem GO

Observação: Este blog compartilhará o resumo do meu aprendizado inicial do GO. Espero que todos possam dominar rapidamente os recursos básicos de programação do GO através deste blog em pouco tempo. Se houver algum erro, deixe uma mensagem para me corrigir. Obrigado!

1. Compreensão preliminar da linguagem Go

(1) Principais questões e objetivos do nascimento da linguagem Go

  1. Arquitetura de hardware multi-core: Com o desenvolvimento do hardware de computador, os processadores multi-core tornaram-se comuns, tornando a computação paralela comum. No entanto, as linguagens de programação tradicionais podem enfrentar dificuldades no tratamento do paralelismo multinúcleo porque carecem de suporte nativo adequado. A linguagem Go facilita a programação simultânea, introduzindo goroutines leves e mecanismos de canal. Os desenvolvedores podem criar facilmente milhares de corrotinas em execução simultânea sem se preocupar com a complexidade do gerenciamento de threads.

  2. Clusters de computação distribuída de ultragrande escala: Com o surgimento da computação em nuvem e dos sistemas distribuídos, tornou-se cada vez mais comum construir e manter clusters de computação distribuída de ultragrande escala. Esses clusters precisam ser capazes de lidar com eficiência com grandes volumes de solicitações, compartilhamento de dados e coordenação. Os recursos de simultaneidade e o mecanismo de canal da linguagem Go facilitam a escrita de sistemas distribuídos. Os desenvolvedores podem usar corrotinas e canais para lidar com tarefas simultâneas, passagem de mensagens e trabalho de coordenação.

  3. O aumento na escala de desenvolvimento e na velocidade de atualização causado pelo modelo Web: A ascensão dos aplicativos Web trouxe uma escala de desenvolvimento sem precedentes e requisitos de atualização contínua. As linguagens de programação tradicionais podem enfrentar problemas como capacidade de manutenção, desempenho e eficiência de desenvolvimento ao desenvolver aplicações web em larga escala. Por meio de sua sintaxe concisa, velocidade de compilação eficiente e suporte à simultaneidade, a linguagem Go permite que os desenvolvedores iterem e implantem aplicativos da Web mais rapidamente e também podem lidar melhor com solicitações de rede altamente simultâneas.

Em conjunto, quando a linguagem Go nasceu, ela se concentrou na solução de desafios técnicos, como arquitetura de hardware multinúcleo, clusters de computação distribuídos em escala ultralarga e escala e velocidade de desenvolvimento no modo Web. Um de seus objetivos de design é fornecer uma linguagem de programação que se adapte às necessidades do desenvolvimento de software moderno para que os desenvolvedores possam lidar melhor com esses desafios.

(2) Representantes típicos de aplicativos de linguagem Go

A linguagem Go tem sido amplamente utilizada no desenvolvimento de aplicativos atuais. Muitas empresas e projetos conhecidos usam a linguagem Go para construir vários tipos de aplicativos. A seguir estão alguns produtos e projetos representativos que usam a linguagem Go como linguagem principal de desenvolvimento:

Estes são apenas alguns exemplos de aplicativos em linguagem Go. Na verdade, existem muitos outros projetos e produtos que usam a linguagem Go para construir aplicativos de alto desempenho, confiáveis ​​e de fácil manutenção. Isso mostra que a linguagem Go desempenha um papel importante no desenvolvimento de aplicações modernas, especialmente nas áreas de sistemas distribuídos, computação em nuvem e aplicações de alto desempenho.

(3) Mal-entendidos entre programadores Java, C++ e C ao aprender a escrever Go

Quando programadores de Java, C++, C e outras linguagens de programação começam a aprender a escrever a linguagem Go, eles podem encontrar alguns mal-entendidos porque Go é diferente dessas linguagens tradicionais em alguns aspectos. Aqui estão alguns equívocos comuns:

  1. Uso excessivo de modelos de simultaneidade tradicionais: Linguagens de programação tradicionais como Java, C++ e C geralmente usam threads e bloqueios para lidar com a simultaneidade, mas em Go, usar goroutines e canais é a melhor maneira . Os programadores novos no Go podem continuar a usar modelos de simultaneidade tradicionais sem aproveitar ao máximo as corrotinas e canais leves do Go, perdendo assim as vantagens de simultaneidade do Go.

  2. Uso excessivo de ponteiros: Linguagens como C e C++ enfatizam o uso de ponteiros, mas a linguagem Go evita operações excessivas de ponteiro durante o design. Programadores novos em Go podem usar ponteiros em excesso, tornando seu código complexo. No Go, tente evitar o uso de ponteiros, a menos que você realmente precise modificar o valor.

  3. Ignorar o tratamento de erros: Go incentiva o tratamento de erros explicitamente, em vez de simplesmente ignorá-los. Isso é diferente da convenção de algumas outras linguagens, onde os erros são frequentemente ignorados ou simplesmente lançados. Os programadores novos no Go podem ignorar o tratamento de erros, deixando possíveis problemas sem serem detectados.

  4. Uso excessivo de variáveis ​​globais: Variáveis ​​globais podem ser uma prática comum em linguagens como C e C++. Porém, em Go, o uso de variáveis ​​globais é considerado uma má prática. Go incentiva o uso de variáveis ​​locais e a passagem de parâmetros para transmitir dados, evitando a introdução de acoplamentos desnecessários e efeitos colaterais.

  5. Não familiarizado com fatias e mapas: fatias e mapas em Go são estruturas de dados poderosas, mas podem não ser familiares para programadores de outras linguagens. É importante aprender como usar fatias e mapas corretamente porque eles são amplamente usados ​​em Go para coletas e processamento de dados.

  6. Estilo Go errado: cada linguagem tem seu próprio estilo e convenções de codificação. Programadores novos em Go podem aplicar estilos de codificação de outras linguagens ao seu código Go, o que pode dificultar a leitura e compreensão do código. É importante compreender e seguir as convenções de codificação do Go.

Para evitar esses mal-entendidos, os programadores que aprendem Go devem investir tempo para compreender os conceitos básicos da linguagem Go, incluindo modelos de simultaneidade, tratamento de erros, estruturas de dados, etc., e ao mesmo tempo participar ativamente da comunidade Go e ler Documentação oficial e códigos de amostra do Go para melhor se adaptar à filosofia de design e às melhores práticas do Go.

2. Preparação do ambiente (explicado em Mac)

(1) Configurações ambientais

Configurar um ambiente de desenvolvimento da linguagem Go no macOS é muito simples, você pode seguir os seguintes passos:

  1. Instalar usando Homebrew: Este é o método mais conveniente se você usar o gerenciador de pacotes Homebrew. Abra um terminal e execute o seguinte comando para instalar a linguagem Go:

    brew install go
  2. Instalação manual: Se desejar instalar o idioma Go manualmente, você pode seguir estas etapas:

    a. Visite o site oficial para baixar o pacote de instalação `goX.XXdarwin-amd64.pkg

    b. Clique duas vezes no pacote de instalação baixado e siga as instruções para executar o programa de instalação. Basta seguir as configurações padrão, o caminho de instalação geralmente é /usr/local/go.

  3. Definir variáveis ​​de ambiente: Assim que a instalação for concluída, você precisa adicionar o caminho binário da linguagem Go à variável de ambiente PATH em seu arquivo de configuração do terminal. Isso permite que você execute comandos Go diretamente no terminal.

    a. Abra um terminal e edite seu arquivo de configuração do terminal usando um editor de texto como nano, vim ou qualquer editor de sua preferência. Por exemplo:

    nano ~/.bash_profile

    b. Adicione as seguintes linhas ao arquivo (ajuste ao caminho de instalação), salve e saia do editor:

    export PATH=$PATH:/usr/local/go/bin

    c. Para que a configuração tenha efeito, você pode executar o seguinte comando ou reiniciar o terminal:

    source ~/.bash_profile
  4. Verifique a instalação: Abra um terminal e digite o seguinte comando para verificar se o Go foi instalado corretamente:

    go version

    Se você vir o número da versão do Go, a instalação foi bem-sucedida.

(2) Instruções de seleção de IDE

Eu pessoalmente uso GoLand. Depois de baixá-lo diretamente do site oficial, você pode comprar a versão crackeada online. Não entrarei em detalhes aqui!

3. Vá aprendendo o programa de idiomas

Crie seu próprio diretório de projeto/Users/zyf/zyfcodes/go/go-learning e crie um novo diretório src.

(1) O primeiro escrito na linguagem Go

Crie o diretório Chapter1/hello no diretório src, crie um novo arquivo hello.go e escreva o código da seguinte forma:

package main

import (
	"fmt"
	"os"
)

/**
 * @author zhangyanfeng
 * @description 第一个godaima
 * @date 2023/8/20  23:45
 * @param
 * @return
 **/
func main() {
	if len(os.Args) > 1 {
		fmt.Println("Hello World", os.Args[1])
	}
}

Este código é um programa simples em linguagem Go que aceita parâmetros de linha de comando e imprime uma mensagem “Hello World” com parâmetros. Aqui está uma análise linha por linha do código:

  1. package main: Declare que este arquivo pertence ao pacote denominado "main", que é o nome do pacote de entrada de um programa Go.

  2. import ("fmt" "os"): Dois pacotes de biblioteca padrão são introduzidos, "fmt" para formatar a saída e "os" para interagir com o sistema operacional.

  3. func main() { ... }: Esta é a função de entrada do programa, ela será chamada primeiro quando o programa estiver em execução.

  4. if len(os.Args) > 1 { ... }: Esta instrução condicional verifica se o número de parâmetros da linha de comando é maior que 1, ou seja, se há parâmetros passados ​​para o programa. os.Argsé uma fatia de string que contém todos os parâmetros da linha de comando. O primeiro parâmetro é o nome do programa.

  5. fmt.Println("Hello World", os.Args[1]): Se algum parâmetro for passado para o programa, esta linha de código será executada. Ele usa fmt.Printlna função para imprimir uma mensagem os.Args[1]que consiste na string “Hello World” e os.Args[1]representa o primeiro argumento passado ao programa.

Resumindo, este código cobre os seguintes pontos de conhecimento:

  1. Importação de pacotes e uso da biblioteca padrão: Importe os pacotes "fmt" e "os" através importda palavra-chave e, em seguida, use as funções e tipos fornecidos por esses pacotes em seu código.

  2. Obtendo parâmetros de linha de comando: Use os.Argspara obter parâmetros de linha de comando.

  3. Instruções condicionais: Use ifinstruções condicionais para determinar se algum parâmetro de linha de comando será passado para o programa.

  4. Operações de string: use operações de concatenação de string para concatenar "Hello World" com parâmetros de linha de comando.

  5. Saída formatada: use fmt.Printlna função para enviar mensagens para a saída padrão.

Nota: Se nenhum parâmetro for passado ao programa, este código não imprimirá nenhuma mensagem. Se vários parâmetros forem passados, o código usará apenas o primeiro parâmetro e ignorará os outros.

Execute "go run hello.go ZYF" neste diretório e o resultado da execução será "Hello World ZYF".

(2) Aprendendo a escrever estruturas básicas de programas

Crie o capítulo2 no diretório src

1.Variáveis

Pré-requisito: Crie variáveis ​​no diretório Chapter2. O resumo do aprendizado é o seguinte:

  1. Declaração de variável: Use varpalavras-chave para declarar uma variável, por exemplo: var x int.
  2. Inferência de tipo: você pode usar :=operadores para declaração e atribuição de variáveis, e Go inferirá automaticamente o tipo de variável com base no valor à direita, por exemplo: y := 5.
  3. Atribuição de variáveis: Utilize operadores de atribuição =para atribuir valores às variáveis, por exemplo: x = 10.
  4. Declaração multivariável: Várias variáveis ​​podem ser declaradas ao mesmo tempo, por exemplo: var a, b, c int.
  5. Inicialização de variáveis: Variáveis ​​podem ser inicializadas quando declaradas, por exemplo: var name string = "John".
  6. Valor zero: variáveis ​​​​não inicializadas receberão valor zero, o tipo numérico é 0, o tipo booleano é o tipo booleano false, o tipo de string é uma string vazia, etc.
  7. Declaração curta de variável: Dentro de uma função, você pode usar declaração curta de variável, por exemplo: count := 10.

Crie um novo fib_test.go, histórico: sequência de Fibonacci simples e prática para praticar

package variables

import "testing"

func TestFibList(t *testing.T) {
	a := 1
	b := 1
	t.Log(a)
	for i := 0; i < 5; i++ {
		t.Log(" ", b)
		tmp := a
		a = b
		b = tmp + a
	}
}

func TestExchange(t *testing.T) {
	a := 1
	b := 2
	// tmp := a
	// a = b
	// b = tmp
	a, b = b, a
	t.Log(a, b)
}

Os pontos de conhecimento envolvidos no código são explicados um por um abaixo:

  1. package variables: declara um pacote chamado "variáveis", que é um nome de pacote usado para teste.

  2. import "testing": importou o pacote "testing" da estrutura de teste da linguagem Go para escrever e executar funções de teste.

  3. func TestFibList(t *testing.T) { ... }: Define uma função de teste "TestFibList", que é usada para testar a lógica de geração da sequência de Fibonacci. Esta é a nomenclatura padrão para uma função de teste, começando com "Teste", seguida do nome da função que está sendo testada.

    • aDentro da função de teste, duas variáveis ​​inteiras e são declaradas be inicializadas com 1, que são os dois primeiros números da sequência de Fibonacci.
    • Use o valor t.Log(a)da variável de impressão apara testar o log.
    • Use um loop para gerar os 5 primeiros números da sequência de Fibonacci, cada iteração imprime o bvalor de no log de teste e atualiza os valores de ae bpara gerar o próximo número.
  4. func TestExchange(t *testing.T) { ... }: É definida outra função de teste "TestExchange", que é usada para testar a lógica de troca de variáveis.

    • Dentro da função de teste, duas variáveis ​​inteiras ae são declaradas be inicializadas com 1 e 2 respectivamente.
    • O uso de comentários mostra uma forma de escrever troca de variáveis ​​(através de variáveis ​​intermediárias), mas na verdade é comentado. Em seguida, use a, b = b, aesta linha de código para implementar a troca de aand b, que é um método de troca exclusivo na linguagem Go e não requer variáveis ​​intermediárias adicionais.
    • Use para t.Log(a, b)imprimir os valores das variáveis ​​​​trocadas no log de teste.

2.Constante

Pré-requisito: Crie uma constante no diretório Chapter2. O resumo do aprendizado é o seguinte:

  1. Declaração constante: Use constpalavras-chave para declarar uma constante, por exemplo: const pi = 3.14159.
  2. Atribuição de constante: O valor de uma constante deve ser atribuído no momento de sua declaração. Uma vez atribuído, não pode ser modificado.
  3. Constantes de enumeração: Uma enumeração pode ser simulada usando um conjunto de constantes, por exemplo:
    const (
        Monday = 1
        Tuesday = 2
        // ...
    )
  4. Especificação de tipo: O tipo da constante também pode ser especificado, por exemplo: const speed int = 300000.
  5. Expressões constantes: As constantes podem ser avaliadas usando expressões, por exemplo: const secondsInHour = 60 * 60.
  6. Constantes não digitadas: As constantes podem ser não digitadas, com o tipo inferido automaticamente com base no contexto. Por exemplo, const x = 5um tipo inteiro será inferido.

Crie um novo constante_test.go e escreva o código da seguinte forma:

package constant

import "testing"

const (
	Monday = 1 + iota
	Tuesday
	Wednesday
)

const (
	Readable = 1 << iota
	Writable
	Executable
)

func TestConstant1(t *testing.T) {
	t.Log(Monday, Tuesday)
}

func TestConstant2(t *testing.T) {
	a := 1 //0001
	t.Log(a&Readable == Readable, a&Writable == Writable, a&Executable == Executable)
}

Os pontos de conhecimento envolvidos no código são explicados um por um abaixo:

  1. package constant: declara um pacote chamado "constant", que é um nome de pacote usado para teste.

  2. import "testing": importou o pacote "testing" da estrutura de teste da linguagem Go para escrever e executar funções de teste.

  3. const (...): Dois blocos constantes são definidos.

    • No primeiro bloco de constantes, um gerador de constantes é usado iotapara definir uma série de constantes começando em 1 e aumentando. Neste exemplo, Mondayé atribuído um valor 1, Tuesdayé atribuído um valor 2 e Wednesdayé atribuído um valor 3. iotaEle é incrementado cada vez que é usado em um bloco constante, portanto, as constantes subsequentes serão incrementadas em sequência.

    • No segundo bloco constante, é usado iotapara definir uma série de constantes bit a bit deslocadas para a esquerda. Neste exemplo, Readableé atribuído o valor 1, Writableé atribuído o valor 2 (10 em binário) e Executableé atribuído o valor 4 (100 em binário). Em operações bit a bit, a operação de deslocamento para a esquerda pode mover um número binário para a esquerda por um número especificado de dígitos.

  4. func TestConstant1(t *testing.T) { ... }: Define uma função de teste "TestConstant1" para testar as constantes definidas no primeiro bloco de constantes.

    • Use para t.Log(Monday, Tuesday)imprimir os valores das constantes Mondaye Tuesdaypara o log de teste.
  5. func TestConstant2(t *testing.T) { ... }: Define outra função de teste "TestConstant2" para testar operações de bits e o uso de constantes.

    • Dentro da função de teste, uma variável inteira é declarada ae inicializada com 1, que é 0001 em binário.
    • Use operações bit a bit e operações AND bit a bit para verificar ase uma variável possui as propriedades e . Por exemplo, a expressão verifica se a representação binária de contém o bit de sinalização.ReadableWritableExecutablea&Readable == ReadableaReadable
    • Use para t.Log()imprimir os resultados das três expressões no log de teste.

3.Tipo de dados

Pré-requisito: Crie o tipo no diretório Chapter2. O resumo do aprendizado é o seguinte:

Descrição dos principais tipos de dados

A linguagem Go possui um rico conjunto de tipos de dados integrados que são usados ​​para representar diferentes tipos de valores e dados. A seguir está uma análise resumida de alguns dos principais tipos de dados na linguagem Go:

  1. Tipos inteiros: a linguagem Go fornece tipos inteiros de tamanhos diferentes, como int, int8, e . Os tipos inteiros não assinados incluem , , e . O tamanho do tipo inteiro depende da arquitetura do computador, como 32 ou 64 bits.int16int32int64uintuint8uint16uint32uint64

  2. Tipos de ponto flutuante: a linguagem Go fornece float32dois float64tipos de ponto flutuante, correspondendo a números de ponto flutuante de precisão simples e de precisão dupla, respectivamente.

  3. Tipos complexos: a linguagem Go fornece complex64dois complex128tipos complexos, correspondendo a números complexos compostos por dois números de ponto flutuante.

  4. Tipo Booleano: O tipo Booleano é usado para representar valores verdadeiros ( true) e falsos ( ) e é usado para julgamento condicional e operações lógicas.false

  5. Tipo String: O tipo String representa uma sequência de caracteres. Strings são imutáveis ​​e podem ser definidas usando aspas duplas "ou crases .`

  6. Tipo de caractere (tipo Rune): O tipo de caractere runeé usado para representar caracteres Unicode, que é um alias de int32. Aspas simples geralmente são usadas 'para representar caracteres, como 'A'.

  7. Tipos de array: um array é uma coleção de elementos do mesmo tipo com tamanho fixo. Ao declarar um array, você precisa especificar o tipo e o tamanho do elemento.

  8. Tipos de fatia: Uma fatia é uma camada de encapsulamento de um array e uma sequência variável de comprimento dinâmico. As fatias não armazenam elementos, elas apenas fazem referência a uma parte do array subjacente.

  9. Tipos de mapas: mapas são coleções não ordenadas de pares chave-valor usados ​​para armazenar e recuperar dados. Chaves e valores podem ser de qualquer tipo, mas as chaves devem ser comparáveis.

  10. Tipos de estrutura: uma estrutura é um tipo de dados composto definido pelo usuário que pode conter campos de diferentes tipos. Cada campo possui um nome e um tipo.

  11. Tipos de interface: uma interface é um tipo abstrato que define um conjunto de métodos. O conjunto de métodos de um tipo que implementa uma interface implementa a interface.

  12. Tipos de função: os tipos de função representam a assinatura de uma função, incluindo tipos de parâmetros e valores de retorno. Funções podem ser passadas como parâmetros e retornadas.

  13. Tipos de canais: canais são um mecanismo de comunicação e sincronização entre corrotinas. Os canais possuem operações de envio e recebimento.

  14. Tipos de ponteiro: os tipos de ponteiro representam o endereço de memória das variáveis. O valor de uma variável pode ser acessado e modificado diretamente por meio de um ponteiro.

Os tipos de dados da linguagem Go têm sintaxe e semântica claras e suportam funções integradas avançadas. A seleção e o uso adequados de diferentes tipos de dados podem melhorar a eficiência e a legibilidade do programa.

Análise específica de expansão de código

package main

import "fmt"

type Person struct {
	FirstName string
	LastName  string
	Age       int
}

type Shape interface {
	Area() float64
}

type Circle struct {
	Radius float64
}

func (c Circle) Area() float64 {
	return 3.14 * c.Radius * c.Radius
}

func add(a, b int) int {
	return a + b
}

func subtract(a, b int) int {
	return a - b
}

type Operation func(int, int) int

func main() {
	fmt.Println("整数类型(Integer Types)")
	var x int = 10
	var y int64 = 100

	fmt.Println(x)
	fmt.Println(y)

	fmt.Println("浮点数类型(Floating-Point Types)")
	var a float32 = 3.14
	var b float64 = 3.14159265359

	fmt.Println(a)
	fmt.Println(b)

	fmt.Println("布尔类型(Boolean Type)")
	var isTrue bool = true
	var isFalse bool = false

	fmt.Println(isTrue)
	fmt.Println(isFalse)

	fmt.Println("字符串类型(String Type)")
	str1 := "Hello, "
	str2 := "Go!"

	concatenated := str1 + str2
	fmt.Println(concatenated)

	fmt.Println("切片类型(Slice Types)")
	numbers := []int{1, 2, 3, 4, 5}
	fmt.Println(numbers)

	// 修改切片元素
	numbers[0] = 10
	fmt.Println(numbers)

	// 切片操作
	subSlice := numbers[1:4]
	fmt.Println(subSlice)

	fmt.Println("映射类型(Map Types)")
	ages := map[string]int{
		"Alice": 25,
		"Bob":   30,
		"Eve":   28,
	}

	fmt.Println(ages)
	fmt.Println("Alice's age:", ages["Alice"])

	// 添加新的键值对
	ages["Charlie"] = 22
	fmt.Println(ages)

	fmt.Println("结构体类型(Struct Types)")
	person := Person{
		FirstName: "John",
		LastName:  "Doe",
		Age:       30,
	}

	fmt.Println(person)
	fmt.Println("Name:", person.FirstName, person.LastName)

	fmt.Println("接口类型(Interface Types)")
	var shape Shape
	circle := Circle{Radius: 5}

	shape = circle
	fmt.Println("Circle Area:", shape.Area())

	fmt.Println("函数类型(Function Types)")
	var op Operation
	op = add
	result := op(10, 5)
	fmt.Println("Addition:", result)

	op = subtract
	result = op(10, 5)
	fmt.Println("Subtraction:", result)

	fmt.Println("通道类型(Channel Types)")
	messages := make(chan string)

	go func() {
		messages <- "Hello, Go!"
	}()

	msg := <-messages
	fmt.Println(msg)

	fmt.Println("指针类型(Pointer Types)")
	x = 10
	var ptr *int
	ptr = &x

	fmt.Println("Value of x:", x)
	fmt.Println("Value stored in pointer:", *ptr)

	*ptr = 20
	fmt.Println("Updated value of x:", x)
}

Os pontos de conhecimento envolvidos no código são explicados um por um abaixo:

  1. type Person struct { ... }: define um tipo de estrutura para Personrepresentar as informações de uma pessoa, incluindo campos e .FirstNameLastNameAge

  2. type Shape interface { ... }: define um tipo de interface Shapeque requer a implementação de um método Area()que retorna um float64tipo.

  3. type Circle struct { ... }: Define um tipo de estrutura Circleque representa o raio de um círculo.

    func (c Circle) Area() float64 { ... }: CircleImplementa o método Shapeda interface para o tipo Area(), que é usado para calcular a área de um círculo.
  4. func add(a, b int) int { ... }: define uma função addque executa operações de adição de inteiros.

  5. func subtract(a, b int) int { ... }: Define uma função subtractque realiza subtração de inteiros.

  6. type Operation func(int, int) int: define um tipo de função Operationque aceita dois parâmetros inteiros e retorna um resultado inteiro.

  7. main() { ... }: Função de entrada do programa.

  • Vários tipos diferentes de variáveis ​​são definidos, incluindo inteiros, flutuantes, booleanos, strings, fatias, mapas, estruturas, interfaces, funções, canais e tipos de ponteiros.
  • Demonstra a inicialização, atribuição, acesso e operações básicas de diferentes tipos de variáveis.
  • Extraia fatias parciais usando a operação de fatia.
  • Demonstra o uso de mapeamentos, incluindo a adição de novos pares de valores-chave e o acesso a pares de valores-chave.
  • Demonstra a definição e inicialização de estruturas e acessa campos de estrutura.
  • Demonstra o uso de interfaces, Circleatribuindo tipos a Shapevariáveis ​​de tipo e chamando métodos de interface.
  • Demonstra a definição e o uso de tipos de função, atribui diferentes funções a Operationvariáveis ​​de tipo e as chama.
  • Utilize canais para implementar comunicação simultânea, enviando e recebendo mensagens em goroutines através de funções anônimas.
  • Demonstra o uso de ponteiros, incluindo operações como criação de variáveis ​​de ponteiro e modificação do valor de variáveis ​​por meio de ponteiros.

Digite instruções de conversão na linguagem Go

A linguagem Go suporta conversão de tipo, mas existem algumas regras e restrições que você deve conhecer. A conversão de tipo é usada para converter um valor de um tipo de dados em outro tipo de dados para uso em um contexto diferente. Aqui estão algumas informações importantes sobre a conversão de tipo na linguagem Go:

  1. Conversão entre tipos básicos: A conversão entre tipos de dados básicos é possível, mas deve-se prestar atenção à compatibilidade de tipos e à possível perda de dados. Por exemplo, uma conversão de intpara float64é segura, mas uma conversão float64de para intpode resultar no truncamento da parte decimal.

  2. Conversões explícitas: em Go, as conversões são usadas para especificar explicitamente a conversão de um valor para outro tipo. A sintaxe é: destinationType(expression). Por exemplo: float64(10).

  3. Conversão entre tipos incompatíveis: O compilador não converte automaticamente tipos incompatíveis. Por exemplo, você não pode converter diretamente um stringtipo em intum tipo.

  4. Conversão de alias de tipo: se houver um alias de tipo (Type Alias), você precisará prestar atenção à compatibilidade do alias durante a conversão.

Aqui estão alguns exemplos para demonstrar a conversão de tipo:

package main

import "fmt"

func main() {
	// 显式类型转换
	var x int = 10
	var y float64 = float64(x)
	fmt.Println(y)

	// 类型别名的转换
	type Celsius float64
	type Fahrenheit float64
	c := Celsius(25)
	f := Fahrenheit(c*9/5 + 32)
	fmt.Println(f)
}

4.Operador

Pré-requisito: Crie um operador no diretório Chapter2. O resumo do aprendizado é o seguinte:

Na verdade, esta parte é semelhante a outras línguas, pessoalmente sinto que não há nada para revisar e consolidar. A linguagem Go oferece suporte a uma variedade de operadores para realizar diversas operações aritméticas, lógicas e de comparação.

operadores regulares

A seguir estão alguns operadores comuns e seus pontos de uso e conhecimento em Go:

Operadores aritméticos:

  • +:Adição
  • -:Subtração
  • *:multiplicação
  • /:divisão
  • %: Módulo (restante)

Operadores de Atribuição:

  • =: Atribuição
  • +=: Atribuição de adição
  • -=: atribuição de subtração
  • *=:Atribuição de multiplicação
  • /=: Atribuição de divisão
  • %=: Atribuição de módulo

Operadores lógicos:

  • &&: E lógico (E)
  • ||: OU lógico (OU)
  • !: Lógico NÃO (NÃO)

Operadores de comparação:

  • ==:igual
  • !=:não é igual a
  • <: menor que
  • >:mais do que o
  • <=: menos que ou igual a
  • >=:maior ou igual a

Operadores bit a bit:

  • &: E bit a bit (E)
  • |: OU bit a bit (OU)
  • ^: OR exclusivo bit a bit (XOR)
  • <<: Deslocar para a esquerda
  • >>:Mover para a direita

Outros operadores:

  • &:Operador de endereço
  • *: operador de ponteiro
  • ++:operador de incremento
  • --: Operador de decremento

Ao usar operadores, há algumas coisas a considerar:

  • Os operandos de um operador devem corresponder ao tipo esperado do operador.
  • Alguns operadores têm precedência mais alta e exigem o uso de parênteses para esclarecer a precedência.
  • Os operandos dos operadores podem ser variáveis, constantes, expressões, etc.

Crie um novo operador_test.go. Aqui estão alguns exemplos para mostrar o uso de operadores:

package operator

import (
	"fmt"
	"testing"
)

const (
	Readable = 1 << iota
	Writable
	Executable
)

func TestOperatorBasic(t *testing.T) {
	// 算术运算符
	a := 10
	b := 5
	fmt.Println("Sum:", a+b)
	fmt.Println("Difference:", a-b)
	fmt.Println("Product:", a*b)
	fmt.Println("Quotient:", a/b)
	fmt.Println("Remainder:", a%b)

	// 逻辑运算符
	x := true
	y := false
	fmt.Println("AND:", x && y)
	fmt.Println("OR:", x || y)
	fmt.Println("NOT:", !x)

	// 比较运算符
	fmt.Println("Equal:", a == b)
	fmt.Println("Not Equal:", a != b)
	fmt.Println("Greater Than:", a > b)
	fmt.Println("Less Than:", a < b)
	fmt.Println("Greater Than or Equal:", a >= b)
	fmt.Println("Less Than or Equal:", a <= b)
}

func TestCompareArray(t *testing.T) {
	a := [...]int{1, 2, 3, 4}
	b := [...]int{1, 3, 2, 4}
	//	c := [...]int{1, 2, 3, 4, 5}
	d := [...]int{1, 2, 3, 4}
	t.Log(a == b)
	//t.Log(a == c)
	t.Log(a == d)
}

func TestBitClear(t *testing.T) {
	a := 7 //0111
	a = a &^ Readable
	a = a &^ Executable
	t.Log(a&Readable == Readable, a&Writable == Writable, a&Executable == Executable)
}

Os pontos de conhecimento envolvidos no código são explicados um por um abaixo:

  1. const (...): define três constantes Readable, soma Writablee Executableusa operações de deslocamento para gerar valores diferentes.

  2. func TestOperatorBasic(t *testing.T) { ... }: Define uma função de teste "TestOperatorBasic" para testar o uso de operadores básicos.

    • Operadores Aritméticos: Mostra operações de adição, subtração, multiplicação, divisão e resto.
    • Operadores Lógicos: Demonstra as operações lógicas AND, lógico OR e lógico NOT.
    • Operadores de comparação: mostra as operações igual, diferente, maior que, menor que, maior ou igual e menor que ou igual.
  3. func TestCompareArray(t *testing.T) { ... }: define uma função de teste "TestCompareArray" para testar a comparação de arrays.

    • aDuas matrizes inteiras e são declaradas b, bem como outra matriz donde o conteúdo de array ae array dsão idênticos.
    • Use o operador de comparação ==para verificar se as matrizes ae bsão iguais e se as matrizes ae dsão iguais.
  4. func TestBitClear(t *testing.T) { ... }: Define uma função de teste "TestBitClear" para testar operações de limpeza de bits.

    • Declare uma variável inteira ae inicialize-a como 7, ou seja, a representação binária 0111.
    • Use a operação de limpeza de bits &^para limpar os bits e aem .ReadableExecutable
    • Use a operação AND bit a bit &para verificar ase possui as propriedades e .ReadableWritableExecutable

Operador de limpeza bit a bit &^

Na linguagem Go, &^é o operador Bit Clear. É usado para limpar os bits em determinadas posições, ou seja, definir os bits nas posições especificadas como 0. &^Os operadores são muito úteis ao lidar com operações de bits binários.

&^Os operadores realizam as seguintes operações:

  1. Para cada bit, se o bit correspondente do operando direito for 0, o bit resultante será igual ao operando esquerdo.
  2. Para cada bit, se o bit correspondente no operando direito for 1, o bit resultante é forçado a 0.

Isso significa que &^o operador é usado para “limpar” um bit específico do operando esquerdo, de modo que o bit correspondente do operando direito não seja afetado. Escreva um código para verificar:

func TestOther(t *testing.T) {
	var a uint8 = 0b11001100 // 二进制表示,十进制为 204
	var b uint8 = 0b00110011 // 二进制表示,十进制为 51

	result := a &^ b

	fmt.Printf("a: %08b\n", a)               // 输出:11001100
	fmt.Printf("b: %08b\n", b)               // 输出:00110011
	fmt.Printf("Result: %08b\n", result)     // 输出:11000000
	fmt.Println("Result (Decimal):", result) // 输出:192
}

5. Declarações Condicionais

Pré-requisito: Criar condição no diretório capítulo 2. O resumo do aprendizado é o seguinte:

ifdeclaração

ifAs instruções são usadas para decidir se um determinado trecho de código deve ser executado com base em uma condição. Sua sintaxe básica é a seguinte:

if condition {
    // 代码块
} else if anotherCondition {
    // 代码块
} else {
    // 代码块
}

switchdeclaração

switchAs instruções são usadas para executar diferentes ramos de código com base em diferentes valores de uma expressão. Ao contrário de outras linguagens, Go switchpode corresponder automaticamente ao primeiro ramo que satisfaça a condição sem usar breakuma instrução. Sua sintaxe é a seguinte:

switch expression {
case value1:
    // 代码块
case value2:
    // 代码块
default:
    // 代码块
}

Crie condição_test.go para análise de verificação. O código específico é o seguinte:

package condition

import (
	"fmt"
	"testing"
)

func TestConditionIf(t *testing.T) {
	age := 18

	if age < 18 {
		fmt.Println("You are a minor.")
	} else if age >= 18 && age < 60 {
		fmt.Println("You are an adult.")
	} else {
		fmt.Println("You are a senior citizen.")
	}
}

func TestConditionSwitch(t *testing.T) {
	dayOfWeek := 3

	switch dayOfWeek {
	case 1:
		fmt.Println("Monday")
	case 2:
		fmt.Println("Tuesday")
	case 3:
		fmt.Println("Wednesday")
	case 4:
		fmt.Println("Thursday")
	case 5:
		fmt.Println("Friday")
	default:
		fmt.Println("Weekend")
	}
}

func TestSwitchMultiCase(t *testing.T) {
	for i := 0; i < 5; i++ {
		switch i {
		case 0, 2:
			t.Logf("%d is Even", i)
		case 1, 3:
			t.Logf("%d is Odd", i)
		default:
			t.Logf("%d is not 0-3", i)
		}
	}
}

func TestSwitchCaseCondition(t *testing.T) {
	for i := 0; i < 5; i++ {
		switch {
		case i%2 == 0:
			t.Logf("%d is Even", i)
		case i%2 == 1:
			t.Logf("%d is Odd", i)
		default:
			t.Logf("%d is unknow", i)
		}
	}
}

O conteúdo de cada função de teste é explicado um por um abaixo:

  1. func TestConditionIf(t *testing.T) { ... }: Teste ifo uso de instruções.

    De acordo com as diferentes circunstâncias de idade, é julgado se se trata de menor, adulto ou idoso através ifdos, else ife sucursais.else
  2. func TestConditionSwitch(t *testing.T) { ... }: Teste switcho uso de instruções. De acordo com dayOfWeeko valor de , use switcha instrução para gerar o dia da semana correspondente.

  3. func TestSwitchMultiCase(t *testing.T) { ... }: teste o caso em que switcha instrução possui vários casevalores. Use switcha instrução para determinar a paridade de cada número e gerar as informações correspondentes.

  4. func TestSwitchCaseCondition(t *testing.T) { ... }: teste switcha expressão condicional na instrução. Use switcha instrução para determinar a paridade de um número pegando o restante do número e gerando as informações correspondentes.

Essas funções de teste demonstram diferentes usos de instruções condicionais na linguagem Go, incluindo julgamento de ramificação baseado em condições e caseprocessamento de vários valores, bem como switcho uso de expressões condicionais em instruções.

6. Declarações de Loop

Pré-requisito: Crie um loop no diretório Chapter2. O resumo do aprendizado é o seguinte:

forciclo

forLoops são usados ​​para executar blocos de código repetidamente e dar suporte a instruções de inicialização, condições de loop e instruções após o loop. Sua forma básica é a seguinte:

for initialization; condition; post {
    // 代码块
}

Na instrução de inicialização, você inicializa as variáveis ​​do loop, depois usa condições dentro do corpo do loop para controlar o loop e, finalmente, postexecuta operações de incremento ou decremento na instrução.

forForma simplificada de loop

Os loops da linguagem Go também podem ser simplificados apenas para a parte da condição do loop, semelhante aos loops forem outras linguagens :while

for condition {
    // 代码块
}

rangeciclo

rangeLoops são usados ​​para iterar estruturas de dados iteráveis, como arrays, fatias, mapas e strings. Ele retorna o índice e o valor de cada iteração. Exemplo:

for index, value := range iterable {
    // 使用 index 和 value
}

Crie loop_test.go para análise de verificação. O código específico é o seguinte:

package loop

import (
	"fmt"
	"testing"
)

func TestLoopFor(t *testing.T) {
	for i := 1; i <= 5; i++ {
		fmt.Println("Iteration:", i)
	}
}

func TestLoopForBasic(t *testing.T) {
	i := 1
	for i <= 5 {
		fmt.Println("Iteration:", i)
		i++
	}
}

func TestLoopForRange(t *testing.T) {
	numbers := []int{1, 2, 3, 4, 5}

	for index, value := range numbers {
		fmt.Printf("Index: %d, Value: %d\n", index, value)
	}
}

func TestLoopForUnLimit(t *testing.T) {
	i := 1
	for {
		fmt.Println("Iteration:", i)
		i++
		if i > 5 {
			break
		}
	}
}

O conteúdo de cada função de teste é explicado um por um abaixo:

  1. func TestLoopFor(t *testing.T) { ... }: Teste forloops básicos. Usando forum loop, itere de 1 a 5 para gerar o número de iterações do loop.

  2. func TestLoopForBasic(t *testing.T) { ... }: Teste um loop sem instruções de inicialização for. Usando forum loop, itere de 1 a 5 para gerar o número de iterações do loop, mas não há nenhuma instrução de inicialização declarada no início do loop.

  3. func TestLoopForRange(t *testing.T) { ... }:Teste usando for rangefatiamento iterativo. Defina uma fatia inteira numbers, use for rangeum loop para iterar sobre cada elemento da fatia e produza o índice e o valor do elemento.

  4. func TestLoopForUnLimit(t *testing.T) { ... }break: teste loops e instruções infinitas . Use um loop infinito e breakuma instrução para determinar se deve encerrar o loop dentro do corpo do loop e isair do loop quando for maior que 5.

Essas funções de teste demonstram foro uso de diferentes tipos de loops na linguagem Go, incluindo loops de contagem padrão, loops sem instruções de inicialização, fatias de passagem e loops infinitos e condições de terminação de loop.

7. Declarações de salto

Pré-requisito: Crie um salto no diretório capítulo 2. O resumo do aprendizado é o seguinte:

A linguagem Go também oferece suporte a várias instruções de salto para controlar o fluxo em loops e condições:

  • break: Saia do loop.
  • continue: pule esta iteração do loop e continue com a próxima iteração.
  • goto: vai diretamente para a tag especificada no código (não recomendado) .

Crie jump_test.go para análise de verificação. O código específico é o seguinte:

package jump

import (
	"fmt"
	"testing"
)

func TestJumpBreak(t *testing.T) {
	for i := 1; i <= 5; i++ {
		if i == 3 {
			break
		}
		fmt.Println("Iteration:", i)
	}
}

func TestJumpContinue(t *testing.T) {
	for i := 1; i <= 5; i++ {
		if i == 3 {
			continue
		}
		fmt.Println("Iteration:", i)
	}
}

func TestJumpGoto(t *testing.T) {
	i := 1

start:
	fmt.Println("Iteration:", i)
	i++
	if i <= 5 {
		goto start
	}
}

O conteúdo de cada função de teste é explicado um por um abaixo:

  1. func TestJumpBreak(t *testing.T) { ... }: Teste breako uso de instruções. Use forum loop para iterar de 1 a 5, mas iquando a variável de iteração for igual a 3, use breaka instrução para encerrar o loop.

  2. func TestJumpContinue(t *testing.T) { ... }: Teste continueo uso de instruções. Use foro loop para iterar de 1 a 5, mas quando a variável de iteração ifor igual a 3, use continuea instrução para pular esta iteração e continuar para a próxima iteração.

  3. func TestJumpGoto(t *testing.T) { ... }: Teste gotoo uso de instruções. Um loop infinito é implementado usando gotoa instrução, que usa rótulos starte goto startpara pular para a posição inicial do loop dentro do corpo do loop. A condição de término do loop é quando ifor maior que 5.

Essas funções de teste demonstram instruções de salto de controle de loop na linguagem Go, incluindo instruções para encerrar loops break, pular a iteração atual continuee loops infinitos goto.

(3) Coleções e strings comumente usadas

Crie Chapter3 no diretório src. Na linguagem Go, um conjunto é uma estrutura de dados que armazena um conjunto de valores. Os tipos de coleção comumente usados ​​incluem matrizes, fatias, mapas e canais.

1. Matriz

Pré-requisito: Crie um array no diretório Chapter3. O resumo do aprendizado é o seguinte:

Um array na linguagem Go é uma coleção de elementos do mesmo tipo e comprimento fixo. A seguir está um resumo e aplicação do conhecimento sobre arrays Go:

Características das matrizes

  • O comprimento de um array é especificado no momento da declaração e não pode ser alterado após a criação.
  • Matrizes são tipos de valor e quando uma matriz é atribuída a uma nova variável ou passada como parâmetro, uma nova cópia é criada.
  • As matrizes são armazenadas continuamente na memória e suportam acesso aleatório.

Declaração e inicialização do array

var arrayName [size]dataType
  • arrayName: O nome da matriz.
  • size: O comprimento da matriz deve ser uma expressão constante.
  • dataType: o tipo de elementos armazenados na matriz.

Como inicializar um array

// 使用指定的值初始化数组
var arr = [5]int{1, 2, 3, 4, 5}

// 根据索引初始化数组
var arr [5]int
arr[0] = 10
arr[1] = 20

// 部分初始化
var arr = [5]int{1, 2}

// 自动推断数组长度
arr := [...]int{1, 2, 3, 4, 5}

Acesso e travessia de array

// 访问单个元素
value := arr[index]

// 遍历数组
for index, value := range arr {
    fmt.Printf("Index: %d, Value: %d\n", index, value)
}

Matriz como parâmetro de função

Uma cópia do array é criada quando o parâmetro da função é passado, portanto, modificações no array dentro da função não afetarão o array original. Se precisar modificar o array original dentro da função, você pode passar um ponteiro para o array.

func modifyArray(arr [5]int) {
    arr[0] = 100
}

func modifyArrayByPointer(arr *[5]int) {
    arr[0] = 100
}

Matrizes multidimensionais

A linguagem Go oferece suporte a matrizes multidimensionais, como matrizes bidimensionais e matrizes tridimensionais. A inicialização e o acesso de arrays multidimensionais são semelhantes aos de arrays unidimensionais, exceto que vários índices precisam ser especificados.

var matrix [3][3]int = [3][3]int{
    {1, 2, 3},
    {4, 5, 6},
    {7, 8, 9},
}

Matrizes são muito úteis ao armazenar um número fixo de elementos do mesmo tipo, mas devido às suas limitações de comprimento fixo, as fatias são geralmente mais comumente usadas no desenvolvimento real, que possuem as características de comprimento dinâmico. As fatias podem ser adicionadas, excluídas e realocadas conforme necessário, tornando-as mais flexíveis.

Crie array_test.go para análise de verificação. O código específico é o seguinte:

package array

import "testing"

func TestArrayInit(t *testing.T) {
	var arr [3]int
	arr1 := [4]int{1, 2, 3, 4}
	arr3 := [...]int{1, 3, 4, 5}
	arr1[1] = 5
	t.Log(arr[1], arr[2])
	t.Log(arr1, arr3)
}

func TestArrayTravel(t *testing.T) {
	arr3 := [...]int{1, 3, 4, 5}
	for i := 0; i < len(arr3); i++ {
		t.Log(arr3[i])
	}
	for _, e := range arr3 {
		t.Log(e)
	}
}

func TestArraySection(t *testing.T) {
	arr3 := [...]int{1, 2, 3, 4, 5}
	arr3_sec := arr3[:]
	t.Log(arr3_sec)
}

O conteúdo de cada função de teste é explicado um por um abaixo:

  1. func TestArrayInit(t *testing.T) { ... }: Teste a inicialização do array.

    • Use maneiras diferentes de inicializar arrays arre arr1.arr3
    • Modifique arr1o segundo elemento de 5.
    • Use para t.Log()gerar os valores dos elementos e o conteúdo de diferentes arrays.
  2. func TestArrayTravel(t *testing.T) { ... }: Teste a travessia da matriz.

    • Use forpara percorrer a matriz arr3e gerar o valor de cada elemento separadamente.
    • Use for rangepara percorrer a matriz arr3e também gerar o valor de cada elemento.
  3. func TestArraySection(t *testing.T) { ... }: teste o uso de divisão de array.

    • Cria uma fatia do array arr3_seccom base no array inteiro arr3.
    • Use o conteúdo t.Log()da fatia da matriz de saída arr3_sec.

2. Fatia

Pré-requisito: Crie uma fatia no diretório Chapter3. O resumo do aprendizado é o seguinte:

A linguagem Slice in Go é uma camada de encapsulamento de arrays, fornecendo uma sequência de comprimento dinâmico mais flexível. A seguir está um resumo do conhecimento e aplicações sobre fatiamento:

Características das fatias

  • Uma fatia é um tipo de referência, não armazena dados, apenas se refere a uma parte do array subjacente.
  • As fatias têm comprimento dinâmico e podem ser expandidas ou reduzidas conforme necessário.
  • As fatias são indexáveis ​​e podem ser cortadas por índice de fatia.

Declaração e inicialização de fatias

var sliceName []elementType

Como inicializar fatias

// 声明切片并初始化
var slice = []int{1, 2, 3, 4, 5}

// 使用 make 函数创建切片
var slice = make([]int, 5) // 创建长度为 5 的 int 类型切片

// 使用切片切割已有数组或切片
newSlice := oldSlice[startIndex:endIndex] // 包括 startIndex,但不包括 endIndex

Funções e operações integradas para fatiar

  • len(slice): Retorna o comprimento da fatia.
  • cap(slice): Retorna a capacidade da fatia, que é o comprimento da matriz subjacente.
  • append(slice, element):Acrescenta elementos ao final da fatia e retorna a nova fatia.
  • copy(destination, source): copia elementos da fatia de origem para a fatia de destino.

Travessia de fatia

for index, value := range slice {
    // 使用 index 和 value
}

Fatias como parâmetros de função

Quando uma fatia é passada como parâmetro para uma função, as modificações na fatia dentro da função afetarão a fatia original.

func modifySlice(s []int) {
    s[0] = 100
}

func main() {
    numbers := []int{1, 2, 3, 4, 5}
    modifySlice(numbers)
    fmt.Println(numbers) // 输出:[100 2 3 4 5]
}

O fatiamento é amplamente utilizado na linguagem Go para lidar com conjuntos de dados dinâmicos, como coleções, listas, filas, etc. Ele fornece maneiras convenientes de gerenciar elementos, evitando as limitações de arrays fixos. Em aplicações práticas, as fatias são frequentemente usadas para armazenar e processar dados de comprimento variável.

Crie slice_test.go para análise de verificação. O código específico é o seguinte:

package slice

import (
	"fmt"
	"testing"
)

func TestSlice(t *testing.T) {
	// 声明和初始化切片
	numbers := []int{1, 2, 3, 4, 5}
	fmt.Println("Original Slice:", numbers)

	// 使用 make 函数创建切片
	slice := make([]int, 3)
	fmt.Println("Initial Make Slice:", slice)

	// 添加元素到切片
	slice = append(slice, 10)
	slice = append(slice, 20, 30)
	fmt.Println("After Append:", slice)

	// 复制切片
	copySlice := make([]int, len(slice))
	copy(copySlice, slice)
	fmt.Println("Copied Slice:", copySlice)

	// 切片切割
	subSlice := numbers[1:3]
	fmt.Println("Subslice:", subSlice)

	// 修改切片的值会影响底层数组和其他切片
	subSlice[0] = 100
	fmt.Println("Modified Subslice:", subSlice)
	fmt.Println("Original Slice:", numbers)
	fmt.Println("Copied Slice:", copySlice)

	// 遍历切片
	for index, value := range slice {
		fmt.Printf("Index: %d, Value: %d\n", index, value)
	}
}

O conteúdo de cada função de teste é explicado um por um abaixo:

func TestSlice(t *testing.T) { ... }: Teste as operações básicas de fatiamento.

  • Declare e inicialize o slice numberse produza o conteúdo inicial do slice.
  • Use makea função para criar 3uma fatia com capacidade inicial de slicee gerar o conteúdo inicial da fatia.
  • Use uma função para adicionar elementos appenda uma fatia .slice
  • Use copya função para copiar uma fatia slicepara uma nova fatia copySlice.
  • Use Slice numberspara fazer cortes em fatias e criar subfatias subSlice.
  • subSliceO primeiro elemento de modificação é 100gerar a fatia modificada e a fatia original, bem como a fatia copiada.
  • Use for rangeum loop para percorrer o slice slice, gerando o índice e o valor de cada elemento.

Esta função de teste mostra várias operações de fatias na linguagem Go, incluindo criação de fatias, adição de elementos, cópia de fatias, corte de fatias, modificação de elementos de fatia, etc.

3.Mapa

Pré-requisito: Crie um mapa no diretório Chapter3. O resumo do aprendizado é o seguinte:

Um mapa na linguagem Go é uma coleção não ordenada de pares chave-valor, também conhecido como array associativo ou dicionário. A seguir está um resumo do conhecimento e aplicações sobre mapeamento:

Características de mapeamento

  • Um mapa é usado para armazenar um conjunto de pares chave-valor, onde cada chave é única.
  • O mapa não está ordenado e a ordem dos pares de valores-chave não é garantida.
  • As chaves podem ser de qualquer tipo comparável e os valores podem ser de qualquer tipo.
  • Mapas são tipos de referência que podem ser atribuídos e passados ​​para funções.

Declaração e inicialização do mapeamento

var mapName map[keyType]valueType

Como inicializar o mapeamento

// 声明和初始化映射
var ages = map[string]int{
    "Alice": 25,
    "Bob":   30,
    "Eve":   28,
}

// 使用 make 函数创建映射
var ages = make(map[string]int)

Operações de mapeamento

  • Adicione pares de valores-chave:ages["Charlie"] = 35
  • Exclua pares de valores-chave:delete(ages, "Eve")
  • Obter valor:value := ages["Alice"]

Mapeamento de travessia

for key, value := range ages {
    fmt.Printf("Name: %s, Age: %d\n", key, value)
}

Mapeamento como parâmetro de função

Quando um mapeamento é passado como parâmetro para uma função, as modificações no mapeamento dentro da função afetarão o mapeamento original.

func modifyMap(m map[string]int) {
    m["Alice"] = 30
}

func main() {
    ages := map[string]int{
        "Alice": 25,
        "Bob":   30,
    }
    modifyMap(ages)
    fmt.Println(ages) // 输出:map[Alice:30 Bob:30]
}

Mapa é uma estrutura de dados muito comumente usada na linguagem Go para armazenar e recuperar dados. É muito útil ao armazenar um conjunto de pares de valores-chave associados, como a correspondência entre nome e idade, a correspondência entre palavras e definições, etc. Em aplicações práticas, o mapeamento é uma ferramenta importante para processar e armazenar dados de valores-chave.

Crie map_test.go para análise de verificação. O código específico é o seguinte:

package my_map

import (
	"fmt"
	"testing"
)

func TestBasic(t *testing.T) {
	// 声明和初始化映射
	ages := map[string]int{
		"Alice": 25,
		"Bob":   30,
		"Eve":   28,
	}
	fmt.Println("Original Map:", ages)

	// 添加新的键值对
	ages["Charlie"] = 35
	fmt.Println("After Adding:", ages)

	// 修改已有键的值
	ages["Bob"] = 31
	fmt.Println("After Modification:", ages)

	// 删除键值对
	delete(ages, "Eve")
	fmt.Println("After Deletion:", ages)

	// 获取值和检查键是否存在
	age, exists := ages["Alice"]
	if exists {
		fmt.Println("Alice's Age:", age)
	} else {
		fmt.Println("Alice not found")
	}

	// 遍历映射
	for name, age := range ages {
		fmt.Printf("Name: %s, Age: %d\n", name, age)
	}
}

type Student struct {
	Name  string
	Age   int
	Grade string
}

func TestComplex(t *testing.T) {
	// 声明和初始化映射,用于存储学生信息和成绩
	studentScores := make(map[string]int)
	studentInfo := make(map[string]Student)

	// 添加学生信息和成绩
	studentInfo["Alice"] = Student{Name: "Alice", Age: 18, Grade: "A"}
	studentScores["Alice"] = 95

	studentInfo["Bob"] = Student{Name: "Bob", Age: 19, Grade: "B"}
	studentScores["Bob"] = 85

	// 查找学生信息和成绩
	aliceInfo := studentInfo["Alice"]
	aliceScore := studentScores["Alice"]
	fmt.Printf("Name: %s, Age: %d, Grade: %s, Score: %d\n", aliceInfo.Name, aliceInfo.Age, aliceInfo.Grade, aliceScore)

	// 遍历学生信息和成绩
	for name, info := range studentInfo {
		score, exists := studentScores[name]
		if exists {
			fmt.Printf("Name: %s, Age: %d, Grade: %s, Score: %d\n", info.Name, info.Age, info.Grade, score)
		} else {
			fmt.Printf("No score available for %s\n", name)
		}
	}
}

O conteúdo de cada função de teste é explicado um por um abaixo:

  1. func TestBasic(t *testing.T) { ... }: Operações básicas para testar mapeamento.

    • Declare e inicialize o mapa agespara armazenar pares de valores-chave de nome e idade da pessoa.
    • Produza o conteúdo do mapeamento inicial.
    • Use ages["Charlie"]para adicionar novos pares de valores-chave.
    • Use para ages["Bob"]modificar o valor de uma chave existente.
    • Use deletea função para excluir pares de valores-chave.
    • Use age, existspara obter o valor e verificar se a chave existe.
    • Use for rangeum loop para percorrer o mapa e gerar as informações para cada par de valores-chave.
  2. type Student struct { ... }: Define uma Studentestrutura nomeada para armazenar informações dos alunos.

  3. func TestComplex(t *testing.T) { ... }: teste operações de mapeamento contendo valores complexos.

    • Declare e inicialize dois mapeamentos, studentScoresusados ​​para armazenar pontuações e studentInfoinformações dos alunos.
    • Adicione informações e pontuações dos alunos ao mapeamento.
    • Use studentInfo["Alice"]para obter informações dos alunos e studentScores["Alice"]para obter as pontuações dos alunos.
    • Use for rangeum loop para percorrer o mapa e exibir as informações e a pontuação de cada aluno.

Essas funções de teste demonstram várias operações de mapeamento na linguagem Go, incluindo criação, adição, modificação, exclusão de pares de valores-chave, verificação se existe uma chave e passagem de pares de valores-chave mapeados.

4. Conjunto de Implementos

Pré-requisito: Crie um conjunto no diretório Chapter3. O resumo do aprendizado é o seguinte:

Na linguagem Go, embora a biblioteca padrão não forneça um tipo Set integrado, você pode usar várias maneiras de implementar a função Set. A seguir estão várias maneiras comuns de implementar Set:

Usar fatias

Criar exercício set_slice_test.go

Use fatias para armazenar elementos e verifique se os elementos existem percorrendo a fatia. Esta é uma implementação simples que funciona bem para coleções pequenas.

package set

import (
	"fmt"
	"testing"
)

type IntSet struct {
	elements []int
}

func (s *IntSet) Add(element int) {
	if !s.Contains(element) {
		s.elements = append(s.elements, element)
	}
}

func (s *IntSet) Contains(element int) bool {
	for _, e := range s.elements {
		if e == element {
			return true
		}
	}
	return false
}

func TestSet(t *testing.T) {
	set := IntSet{}
	set.Add(1)
	set.Add(2)
	set.Add(3)
	set.Add(2) // Adding duplicate, should be ignored

	fmt.Println("Set:", set.elements) // Output: [1 2 3]
}

Usar mapeamento

Criar exercício set_map_test.go

Use um mapa para armazenar elementos. As chaves do mapa representam os elementos da coleção e os valores podem ser de qualquer tipo. Essa implementação é mais rápida e adequada para coleções grandes porque a complexidade da pesquisa de mapeamento é O(1) .

package set

import (
	"fmt"
	"testing"
)

type Set map[int]bool

func (s Set) Add(element int) {
	s[element] = true
}

func (s Set) Contains(element int) bool {
	return s[element]
}

func TestSetMap(t *testing.T) {
	set := make(Set)
	set.Add(1)
	set.Add(2)
	set.Add(3)
	set.Add(2) // Adding duplicate, should be ignored

	fmt.Println("Set:", set) // Output: map[1:true 2:true 3:true]
}

Use bibliotecas de terceiros

Criar exercício set_third_test.go

Para evitar implementá-lo sozinho, você pode usar algumas bibliotecas de terceiros, por exemplo github.com/deckarep/golang-set, que fornecem funções Set mais ricas.

Adicione um proxy: vá env -w GOPROXY=https://goproxy.io,direct

Em seguida, instale o pacote: acesse github.com/deckarep/golang-set

package set

import (
	"fmt"
	"github.com/deckarep/golang-set"
	"testing"
)

func TestSetThird(t *testing.T) {
	intSet := mapset.NewSet()
	intSet.Add(1)
	intSet.Add(2)
	intSet.Add(3)
	intSet.Add(2) // Adding duplicate, will be ignored

	fmt.Println("Set:", intSet) // Output: Set: Set{1, 2, 3}
}

Acima estão várias maneiras de implementar o Set. Você pode escolher o método de implementação apropriado com base em suas necessidades e considerações de desempenho. Bibliotecas de terceiros podem fornecer mais funcionalidades e otimizações de desempenho para coletas de dados em grande escala.

5. Corda

Pré-requisito: Crie uma string no diretório Chapter3. O resumo do aprendizado é o seguinte:

Declaração e inicialização de strings

Na linguagem Go, uma string é composta por uma série de caracteres. Você pode usar aspas duplas "ou crases ``` para declarar e inicializar a string.

package main

import "fmt"

func main() {
    str1 := "Hello, World!"   // 使用双引号声明
    str2 := `Go Programming` // 使用反引号声明

    fmt.Println(str1) // Output: Hello, World!
    fmt.Println(str2) // Output: Go Programming
}

comprimento da corda

Use a função integrada len()para obter o comprimento de uma string, ou seja, o número de caracteres na string.

package main

import "fmt"

func main() {
    str := "Hello, 世界!"
    length := len(str)
    fmt.Println("String Length:", length) // Output: String Length: 9
}

Indexação e fatiamento de strings

Os caracteres de uma string podem ser acessados ​​pelo índice, que começa em 0. Você pode usar a operação de fatiamento para obter substrings de uma string.

package main

import "fmt"

func main() {
    str := "Hello, World!"

    // 获取第一个字符
    firstChar := str[0]
    fmt.Println("First Character:", string(firstChar)) // Output: First Character: H

    // 获取子串
    substring := str[7:12]
    fmt.Println("Substring:", substring) // Output: Substring: World
}

Concatenação de strings

Use +o operador para concatenar duas strings em uma nova string. Além disso, strings.Joina função é usada para concatenar fatias de string em uma nova string, que pode ser usada para unir várias strings.

Finalmente, o uso do buffer de bytes permite uma concatenação eficiente de strings sem fazer cópias redundantes de strings.

package main

import (
    "fmt"
    "strings"
    "bytes"
)

func main() {
    str1 := "Hello, "
    str2 := "World!"
    
    result := str1 + str2
    fmt.Println("Concatenated String:", result) // Output: Concatenated String: Hello, World!

    strSlice := []string{"Hello", " ", "World!"}
    result := strings.Join(strSlice, "")
    fmt.Println(result) // Output: Hello World!

    var buffer bytes.Buffer
    
    buffer.WriteString(str1)
    buffer.WriteString(str2)
    
    result := buffer.String()
    fmt.Println(result) // Output: Hello, World!
}

string multilinha

Use crases ``` para criar strings multilinhas.

package main

import "fmt"

func main() {
    multiLineStr := `
        This is a
        multi-line
        string.
    `
    fmt.Println(multiLineStr)
}

Iteração de string

Use for rangeum loop para iterar cada caractere da string.

package main

import "fmt"

func main() {
    str := "Go语言"
    
    for _, char := range str {
        fmt.Printf("%c ", char) // Output: G o 语 言
    }
}

Conversão entre string e array de bytes

Na linguagem Go, strings e matrizes de bytes podem ser convertidas entre si.

package main

import "fmt"

func main() {
    str := "Hello"
    bytes := []byte(str) // 转换为字节数组
    strAgain := string(bytes) // 字节数组转换为字符串

    fmt.Println("Bytes:", bytes)       // Output: Bytes: [72 101 108 108 111]
    fmt.Println("String Again:", strAgain) // Output: String Again: Hello
}

Comparação de strings

Strings podem ser comparadas usando os operadores ==e !=. É claro que existem outros tipos de funções que são diretamente aplicáveis: strings.Comparefunções que comparam duas strings e retornam um número inteiro com base no resultado da comparação.

Você também pode usar funções de comparação personalizadas para comparar strings e definir a lógica de comparação de acordo com suas próprias necessidades.

package main

import (
    "fmt"
    "strings"
)


func customCompare(str1, str2 string) bool {
    // 自定义比较逻辑
    return str1 == str2
}

func main() {
    str1 := "Hello"
    str2 := "World"

    if str1 == str2 {
        fmt.Println("Strings are equal")
    } else {
        fmt.Println("Strings are not equal") // Output: Strings are not equal
    }


    result := strings.Compare(str1, str2)
    if result == 0 {
        fmt.Println("Strings are equal")
    } else if result < 0 {
        fmt.Println("str1 is less than str2")
    } else {
        fmt.Println("str1 is greater than str2") // Output: str1 is less than str2
    }

    if customCompare(str1, str2) {
        fmt.Println("Strings are equal")
    } else {
        fmt.Println("Strings are not equal") // Output: Strings are not equal
    }
}

Esses conceitos e operações básicos podem ajudá-lo a entender e usar melhor strings na linguagem Go. Esteja ciente da imutabilidade das strings, bem como das conversões e comparações com outros tipos de dados.

Criar exercício string_test.go

package string

import (
	"strconv"
	"strings"
	"testing"
)

func TestString(t *testing.T) {
	var s string
	t.Log(s) //初始化为默认零值“”
	s = "hello"
	t.Log(len(s))
	//s[1] = '3' //string是不可变的byte slice
	//s = "\xE4\xB8\xA5" //可以存储任何二进制数据
	s = "\xE4\xBA\xBB\xFF"
	t.Log(s)
	t.Log(len(s))
	s = "中"
	t.Log(len(s)) //是byte数

	c := []rune(s)
	t.Log(len(c))
	//	t.Log("rune size:", unsafe.Sizeof(c[0]))
	t.Logf("中 unicode %x", c[0])
	t.Logf("中 UTF8 %x", s)
}

func TestStringToRune(t *testing.T) {
	s := "中华人民共和国"
	for _, c := range s {
		t.Logf("%[1]c %[1]x", c)
	}
}

func TestStringFn(t *testing.T) {
	s := "A,B,C"
	parts := strings.Split(s, ",")
	for _, part := range parts {
		t.Log(part)
	}
	t.Log(strings.Join(parts, "-"))
}

func TestConv(t *testing.T) {
	s := strconv.Itoa(10)
	t.Log("str" + s)
	if i, err := strconv.Atoi("10"); err == nil {
		t.Log(10 + i)
	}
}

O conteúdo de cada função de teste é explicado um por um abaixo:

  1. func TestString(t *testing.T) { ... }: Operações básicas para testar strings.

    • Declare uma variável de string sque produza seu valor padrão zero.
    • Atribua o valor da string a "hello" e produza o comprimento da string.
    • Tentar modificar um caractere na string resultará em erro porque as strings são imutáveis.
    • Use strings para armazenar dados binários e codificação Unicode.
    • Use uma string para armazenar um caractere chinês e exibir seu comprimento.
    • Converta a string para runeo tipo slice, produza o comprimento da fatia e a codificação Unicode e UTF-8 de caracteres chineses.
  2. func TestStringToRune(t *testing.T) { ... }: String de teste para runeconversão.

    Declare uma string contendo caracteres chineses s, rangeconverta a string em runetipo por meio de travessia e produza-a.
  3. func TestStringFn(t *testing.T) { ... }: Teste funções relacionadas a strings.

    Declare uma string contendo delimitadores de vírgula s, use strings.Splita função para dividir a string e gerar cada parte. Use strings.Joina função para mesclar as partes divididas em uma nova string e produzi-la.
  4. func TestConv(t *testing.T) { ... }: teste a conversão de strings para outros tipos.

    • Use strconv.Itoapara converter um número inteiro em uma string.
    • Concatene strings e números inteiros e produza o resultado.
    • Use strconv.Atoipara converter strings em números inteiros, realizar adições e tratar condições de erro.

Essas funções de teste demonstram várias operações em strings na linguagem Go, incluindo comprimento de string, codificação UTF-8, runeconversão de tipo, divisão e mesclagem de string e conversão de strings para outros tipos.

(4) Função

Crie o capítulo 4 no diretório src. Na linguagem Go, uma função é um bloco de código usado para executar uma tarefa específica e pode ser chamado várias vezes. A seguir estão alguns pontos de conhecimento importantes sobre as funções da linguagem Go:

1. Declaração de função

Em Go, uma declaração de função funccomeça com a palavra-chave seguida pelo nome da função, lista de parâmetros, valor de retorno e corpo da função.

func functionName(parameters) returnType {
    // 函数体
    // 可以包含多个语句
    return returnValue
}

2. Parâmetros de função

As funções podem ter zero ou mais parâmetros, que consistem em nomes e tipos de parâmetros. Use vírgulas para separar parâmetros.

func greet(name string) {
    fmt.Printf("Hello, %s!\n", name)
}

3. Vários valores de retorno

As funções da linguagem Go podem retornar vários valores. Os valores de retorno são colocados entre parênteses e separados por vírgulas.

func divide(a, b float64) (float64, error) {
    if b == 0 {
        return 0, errors.New("division by zero")
    }
    return a / b, nil
}

4. Nomeie o valor de retorno

As funções podem declarar valores de retorno nomeados,e estes nomes podem ser utilizados directamente para atribuição dentro do corpo da função.Finalmente,não há necessidade de utilizar explicitamente returna palavra-chave.

func divide(a, b float64) (result float64, err error) {
    if b == 0 {
        err = errors.New("division by zero")
        return
    }
    result = a / b
    return
}

5. Número variável de parâmetros

A linguagem Go suporta o uso de ...sintaxe para representar um número variável de parâmetros. Esses parâmetros são usados ​​como fatias dentro do corpo da função.

func sum(numbers ...int) int {
    total := 0
    for _, num := range numbers {
        total += num
    }
    return total
}

6. Função como parâmetro

Na linguagem Go, funções podem ser passadas como parâmetros para outras funções.

func applyFunction(fn func(int, int) int, a, b int) int {
    return fn(a, b)
}

func add(a, b int) int {
    return a + b
}

func main() {
    result := applyFunction(add, 3, 4)
    fmt.Println(result) // Output: 7
}

7. Funções anônimas e fechamentos

A linguagem Go oferece suporte a funções anônimas, também conhecidas como encerramentos. Essas funções podem ser definidas dentro de outras funções e acessar variáveis ​​​​da função externa.

func main() {
    x := 5
    fn := func() {
        fmt.Println(x) // 闭包访问外部变量
    }
    fn() // Output: 5
}

8. declaração de adiamento

deferAs instruções são usadas para atrasar a execução de funções, geralmente para realizar algumas operações de limpeza antes do retorno da função.

func main() {
    defer fmt.Println("World")
    fmt.Println("Hello")
}

Acima estão alguns pontos de conhecimento básico sobre as funções da linguagem Go. As funções desempenham um papel muito importante no Go, usadas para organizar código, implementar modularização funcional e melhorar a capacidade de manutenção do código.

Verificação 1: verificação básica de caso de uso

Crie um novo básico no capítulo 4 e crie a prática func_basic_test.go

package basic

import (
	"errors"
	"fmt"
	"testing"
)

// 普通函数
func greet(name string) {
	fmt.Printf("Hello, %s!\n", name)
}

// 多返回值函数
func divide(a, b int) (int, error) {
	if b == 0 {
		return 0, errors.New("division by zero")
	}
	return a / b, nil
}

// 命名返回值函数
func divideNamed(a, b int) (result int, err error) {
	if b == 0 {
		err = errors.New("division by zero")
		return
	}
	result = a / b
	return
}

// 可变数量的参数函数
func sum(numbers ...int) int {
	total := 0
	for _, num := range numbers {
		total += num
	}
	return total
}

// 函数作为参数
func applyFunction(fn func(int, int) int, a, b int) int {
	return fn(a, b)
}

// 匿名函数和闭包
func closureExample() {
	x := 5
	fn := func() {
		fmt.Println(x)
	}
	fn() // Output: 5
}

// defer语句
func deferExample() {
	defer fmt.Println("World")
	fmt.Println("Hello") // Output: Hello World
}

func TestBasic(t *testing.T) {
	greet("Alice") // Output: Hello, Alice!

	q, err := divide(10, 2)
	if err != nil {
		fmt.Println("Error:", err)
	} else {
		fmt.Println("Quotient:", q) // Output: Quotient: 5
	}

	qNamed, errNamed := divideNamed(10, 0)
	if errNamed != nil {
		fmt.Println("Error:", errNamed) // Output: Error: division by zero
	} else {
		fmt.Println("Quotient:", qNamed)
	}

	total := sum(1, 2, 3, 4, 5)
	fmt.Println("Sum:", total) // Output: Sum: 15

	addResult := applyFunction(func(a, b int) int {
		return a + b
	}, 3, 4)
	fmt.Println("Addition:", addResult) // Output: Addition: 7

	closureExample()

	deferExample()
}

Verificação 2: Exemplo de pequena empresa

Crie um novo negócio no capítulo 4 e crie o exercício func_biz_test.go. Suponha que você esteja desenvolvendo um sistema simples de processamento de pedidos e precise calcular o preço total das mercadorias no pedido e aplicar descontos. Você pode usar funções para lidar com essa lógica de negócios. Aqui está um exemplo simples:

package biz

import (
	"fmt"
	"testing"
)

type Product struct {
	Name  string
	Price float64
}

func calculateTotal(products []Product) float64 {
	total := 0.0
	for _, p := range products {
		total += p.Price
	}
	return total
}

func applyDiscount(amount, discount float64) float64 {
	return amount * (1 - discount)
}

func TestBiz(t *testing.T) {
	products := []Product{
		{Name: "Product A", Price: 10.0},
		{Name: "Product B", Price: 20.0},
		{Name: "Product C", Price: 30.0},
	}

	total := calculateTotal(products)
	fmt.Printf("Total before discount: $%.2f\n", total)

	discountedTotal := applyDiscount(total, 0.1)
	fmt.Printf("Total after 10%% discount: $%.2f\n", discountedTotal)
}

(5) Programação orientada a objetos

Crie o capítulo 5 no diretório src. A linguagem Go suporta Programação Orientada a Objetos (OOP), embora em comparação com algumas linguagens de programação orientadas a objetos tradicionais (como Java e C++), a implementação de Go possa ser um pouco diferente. Na linguagem Go, não existe conceito de classes, mas recursos orientados a objetos podem ser alcançados por meio de estruturas e métodos.

1. Definição de estrutura

Na linguagem Go, uma estrutura é um tipo de dados personalizado usado para combinar campos (variáveis ​​de membro) de diferentes tipos para criar um novo tipo de dados. Crie o diretório struct e escreva struct_test.go. A seguir estão exemplos de definição, uso e verificação da estrutura:

package _struct

import (
	"fmt"
	"testing"
)

// 定义一个结构体
type Person struct {
	FirstName string
	LastName  string
	Age       int
}

func TestStruct(t *testing.T) {
	// 创建结构体实例并初始化字段
	person1 := Person{
		FirstName: "Alice",
		LastName:  "Smith",
		Age:       25,
	}

	// 访问结构体字段
	fmt.Println("First Name:", person1.FirstName) // Output: First Name: Alice
	fmt.Println("Last Name:", person1.LastName)   // Output: Last Name: Smith
	fmt.Println("Age:", person1.Age)              // Output: Age: 25

	// 修改结构体字段的值
	person1.Age = 26
	fmt.Println("Updated Age:", person1.Age) // Output: Updated Age: 26
}

A definição de uma estrutura pode conter vários campos e cada campo pode ser de um tipo de dados diferente. Você também pode aninhar outras estruturas dentro de estruturas para formar estruturas de dados mais complexas. Escrevendo exemplo struct_cmpx_test.go:

package _struct

import (
	"fmt"
	"testing"
)

type Address struct {
	Street  string
	City    string
	ZipCode string
}

type PersonNew struct {
	FirstName string
	LastName  string
	Age       int
	Address   Address
}

func TestCmpxStruct(t *testing.T) {
	person2 := PersonNew{
		FirstName: "Bob",
		LastName:  "Johnson",
		Age:       30,
		Address: Address{
			Street:  "123 Main St",
			City:    "Cityville",
			ZipCode: "12345",
		},
	}

	fmt.Println("Full Name:", person2.FirstName, person2.LastName)
	fmt.Println("Address:", person2.Address.Street, person2.Address.City, person2.Address.ZipCode)
}

2. Criação e inicialização de instância

Na linguagem Go, existem muitas maneiras de criar e inicializar instâncias de estrutura. Crie o diretório creatinit. A seguir estão vários métodos comuns de criação e inicialização de instâncias. O código específico é creatinit_test.go

  • Inicialização literal : você pode usar chaves {}para inicializar campos de uma instância de estrutura.
  • Inicialização parcial de campos: Se desejar inicializar apenas alguns campos da estrutura, poderá omitir outros campos.
  • Inicialização usando nomes de campos: Os valores dos campos podem ser especificados com base nos nomes dos campos sem inicialização sequencial.
  • Inicialização do valor padrão: Os campos de uma estrutura podem ser inicializados de acordo com o valor padrão do seu tipo.
  • Usando a nova função: Você pode usar newa função para criar um ponteiro para uma estrutura e retornar seu ponteiro.
  • Inicialização da sequência de campos: Os nomes dos campos podem ser opcionalmente omitidos, mas neste momento os valores precisam ser atribuídos na ordem dos campos da estrutura.
package creatinit

import (
	"fmt"
	"testing"
)

type Person struct {
	FirstName string
	LastName  string
	Age       int
}

/**
 * @author zhangyanfeng
 * @description 字面量初始化
 * @date 2023/8/26  15:09
 **/
func TestCreateObj1(t *testing.T) {
	person1 := Person{
		FirstName: "Alice",
		LastName:  "Smith",
		Age:       25,
	}
	fmt.Println(person1.FirstName, person1.LastName, person1.Age) // Output: Alice Smith 25
}

/**
 * @author zhangyanfeng
 * @description 部分字段初始化
 * @date 2023/8/26  15:10
 **/
func TestCreateObj2(t *testing.T) {
	person2 := Person{
		FirstName: "Bob",
		Age:       30,
	}
	fmt.Println(person2.FirstName, person2.LastName, person2.Age) // Output: Bob  30
}

/**
 * @author zhangyanfeng
 * @description 使用字段名初始化
 * @date 2023/8/26  15:12
 **/
func TestCreateObj3(t *testing.T) {
	person3 := Person{
		LastName:  "Johnson",
		FirstName: "Chris",
		Age:       28,
	}
	fmt.Println(person3.FirstName, person3.LastName, person3.Age) // Output: Chris Johnson 28
}

/**
 * @author zhangyanfeng
 * @description 默认值初始化
 * @date 2023/8/26  15:13
 **/
func TestCreateObj4(t *testing.T) {
	var person4 Person
	fmt.Println(person4.FirstName, person4.LastName, person4.Age) // Output:   0
}

/**
 * @author zhangyanfeng
 * @description 使用 new 函数
 * @date 2023/8/26  15:14
 **/
func TestCreateObj5(t *testing.T) {
	person5 := new(Person)
	person5.FirstName = "David"
	person5.Age = 22
	fmt.Println(person5.FirstName, person5.LastName, person5.Age) // Output: David  22
}

/**
 * @author zhangyanfeng
 * @description 字段顺序初始化
 * @date 2023/8/26  15:24
 **/
func TestCreateObj6(t *testing.T) {
	// 使用字段顺序初始化
	person := Person{"Alice", "Smith", 25}
	fmt.Println(person.FirstName, person.LastName, person.Age) // Output: Alice Smith 25
}

3. Definição de comportamento (método)

Em Go, um método é uma função associada a um tipo específico que pode ser chamada em instâncias desse tipo. Os métodos permitem que as operações de tipo sejam colocadas junto com a definição de tipo, melhorando a legibilidade e a manutenção do código.

Crie um diretório de métodos para prática de codificação. A seguir está a definição, uso e análise dos métodos da linguagem Go:

definição de método

Na linguagem Go, os métodos são definidos adicionando receptores às funções. O receptor é um parâmetro comum, mas é colocado antes do nome do método para especificar a qual tipo o método está associado. Crie método_define_test.go

package method

import (
	"fmt"
	"testing"
)

type Circle struct {
	Radius float64
}

// 定义 Circle 类型的方法
func (c Circle) Area() float64 {
	return 3.14159 * c.Radius * c.Radius
}

func TestMethodDef(t *testing.T) {
	c := Circle{Radius: 5}
	area := c.Area()
	fmt.Printf("Circle area: %.2f\n", area) // Output: Circle area: 78.54
}

No exemplo acima, definimos uma Circleestrutura e, em seguida, definimos um Areamétodo nomeado nela. Este método pode c.Area()ser chamado via , onde cé uma Circleinstância do tipo.

chamada de método

A sintaxe da chamada de método é 实例.方法名(), ou seja, chamar o método através da instância. Crie método_rpc_test.go

package method

import (
	"fmt"
	"testing"
)

type Rectangle struct {
	Width  float64
	Height float64
}

func (r Rectangle) Area() float64 {
	return r.Width * r.Height
}

func TestMethonRpc(t *testing.T) {
	rect := Rectangle{Width: 3, Height: 4}
	area := rect.Area()
	fmt.Printf("Rectangle area: %.2f\n", area) // Output: Rectangle area: 12.00
}

receptor de ponteiro

A linguagem Go suporta o uso de ponteiros como receptores de métodos, para que os valores dos campos da instância do receptor possam ser modificados. Crie método_rec_test.go

package method

import (
	"fmt"
	"testing"
)

type Counter struct {
	Count int
}

func (c *Counter) Increment() {
	c.Count++
}

func TestMethonRec(t *testing.T) {
	counter := Counter{Count: 0}
	counter.Increment()
	fmt.Println("Count:", counter.Count) // Output: Count: 1
}

No exemplo acima, Incremento método usa um receptor de ponteiro para que Counto valor do campo seja modificado após a chamada do método.

A diferença entre métodos e funções

A principal diferença entre métodos e funções é que um método é uma função de um tipo específico, que está mais relacionado ao tipo e pode acessar os campos e outros métodos do tipo. Funções são blocos de código independentes de um tipo específico. Os métodos são frequentemente usados ​​para implementar tipos específicos de comportamento, enquanto as funções podem ser usadas para operações gerais.

Ao definir métodos, você pode tornar a operação dos tipos mais natural e consistente, melhorando a legibilidade e modularidade do seu código.

Pode ser explicado aqui que em method_rpc_test.go, definimos Rectangleum Areamétodo nomeado para a estrutura, que pode ser chamado por rect.Area(). Os métodos estão diretamente associados ao tipo Rectanglee têm acesso aos Rectanglecampos de ( Widthe Height).

Para comparar com o método, criamos um método no corpo do método correspondente da seguinte forma

// 定义一个函数来计算矩形的面积
func CalculateArea(r Rectangle) float64 {
    return r.Width * r.Height
}

Neste exemplo, definimos uma CalculateAreafunção chamada que aceita um Rectangleparâmetro do tipo para calcular a área de um retângulo. A função é independente do Rectangletipo, portanto não pode acessar diretamente Rectangleos campos do arquivo .

Resumo: A diferença entre métodos e funções é que métodos são funções de um tipo específico, que estão mais relacionadas ao tipo e podem acessar campos e outros métodos do tipo. Uma função é um bloco de código independente de um tipo específico e geralmente usado para operações comuns. No exemplo acima, o método está associado ao retângulo e pode acessar diretamente os campos do retângulo; a função é um processo de cálculo independente e não está diretamente associada a nenhum tipo específico.

Ao utilizar métodos, podemos tornar nosso código mais natural e consistente, melhorando sua legibilidade e modularidade, principalmente na implementação de tipos específicos de comportamento.

4. Definição e uso da interface

Na linguagem Go, uma interface é uma forma de definir uma coleção de métodos. Ela especifica a assinatura de um conjunto de métodos sem envolver detalhes de implementação. Através de interfaces, o polimorfismo e o desacoplamento de código podem ser alcançados, permitindo que objetos de diferentes tipos operem de maneira consistente.

Crie um diretório de interface para exercícios subsequentes. A seguir está uma explicação da interface da linguagem Go:

Definir interface

Uma interface é uma coleção de métodos, typedefinidos por meio de palavras-chave. Uma interface define um conjunto de assinaturas de métodos, mas não contém implementações de métodos. Crie interface_test.go para prática de código

package interface_test

import (
	"fmt"
	"testing"
)

// 定义一个简单的接口
type Shape interface {
	Area() float64
}

// 定义两个实现 Shape 接口的结构体
type Circle struct {
	Radius float64
}

func (c Circle) Area() float64 {
	return 3.14159 * c.Radius * c.Radius
}

type Rectangle struct {
	Width  float64
	Height float64
}

func (r Rectangle) Area() float64 {
	return r.Width * r.Height
}

func TestInterface(t *testing.T) {
	shapes := []Shape{
		Circle{Radius: 2},
		Rectangle{Width: 3, Height: 4},
	}

	for _, shape := range shapes {
		fmt.Printf("Area of %T: %.2f\n", shape, shape.Area())
	}
}

No exemplo acima, definimos uma Shapeinterface nomeada, que requer a implementação de um Areamétodo de cálculo da área da figura. Em seguida, definimos duas estruturas Circlee Rectanglee implementamos Areaos métodos respectivamente. Usando interfaces, podemos colocar diferentes tipos de objetos gráficos na mesma fatia e então chamar seus Areamétodos através de um loop.

Implementação de interface

Qualquer tipo é considerado para implementar a interface, desde que implemente todos os métodos definidos na interface. A implementação da interface é implícita e não requer declaração explícita. Contanto que a assinatura do método seja igual à assinatura do método na interface, o tipo é considerado para implementar a interface.

Polimorfismo de interface

Devido ao polimorfismo das interfaces, podemos considerar os objetos que implementam a interface como a própria interface. No exemplo acima, shapesdiferentes tipos de objetos são armazenados na fatia, mas todos implementam Shapea interface para que os métodos possam ser chamados de forma unificada Area.

Ao usar interfaces, o código pode ser abstraído e desacoplado, tornando-o mais flexível e extensível. As interfaces são amplamente utilizadas na linguagem Go para definir comportamentos e restrições comuns.

5. Expansão e reutilização

Na linguagem Go, a forma de estender e reutilizar código é diferente das linguagens tradicionais orientadas a objetos (como Java). Go incentiva o uso de recursos como composição, interfaces e campos anônimos para obter extensão e reutilização de código, em vez de herança de classe.

Crie o diretório de extensão para exercícios subsequentes. A seguir está uma explicação detalhada da extensão e reutilização na linguagem Go:

Combinando e aninhando

A composição na linguagem Go permite aninhar um tipo de estrutura dentro de outro tipo de estrutura para conseguir a reutilização do código. Estruturas aninhadas podem acessar seus membros diretamente por meio de nomes de campos. Crie composição_test.go

package extend

import (
	"fmt"
	"testing"
)

type Engine struct {
	Model string
}

type Car struct {
	Engine
	Brand string
}

func TestComposition(t *testing.T) {
	car := Car{
		Engine: Engine{Model: "V6"},
		Brand:  "Toyota",
	}

	fmt.Println("Car brand:", car.Brand)
	fmt.Println("Car engine model:", car.Model) // 直接访问嵌套结构体的字段
}

Neste exemplo, usamos composição para criar Caruma estrutura com Engineestruturas aninhadas dentro dela. Através do aninhamento, Caruma estrutura pode acessar diretamente Engineos campos da estrutura.

Implementação de interface

Com uma interface, você pode definir um conjunto de métodos que diferentes tipos podem implementar. Isto permite o polimorfismo e o desacoplamento de código, para que objetos de diferentes tipos possam ser operados através da mesma interface. Crie interface_ext_test.go

package extend

import (
	"fmt"
	"math"
	"testing"
)

// 定义 Shape 接口
type Shape interface {
	Area() float64
	Perimeter() float64
}

// 定义 Circle 结构体
type Circle struct {
	Radius float64
}

// 实现 Circle 结构体的方法,以满足 Shape 接口
func (c Circle) Area() float64 {
	return math.Pi * c.Radius * c.Radius
}

func (c Circle) Perimeter() float64 {
	return 2 * math.Pi * c.Radius
}

// 定义 Rectangle 结构体
type Rectangle struct {
	Width  float64
	Height float64
}

// 实现 Rectangle 结构体的方法,以满足 Shape 接口
func (r Rectangle) Area() float64 {
	return r.Width * r.Height
}

func (r Rectangle) Perimeter() float64 {
	return 2 * (r.Width + r.Height)
}

func TestInterfaceExt(t *testing.T) {
	circle := Circle{Radius: 3}
	rectangle := Rectangle{Width: 4, Height: 5}

	shapes := []Shape{circle, rectangle}

	for _, shape := range shapes {
		fmt.Printf("Shape Type: %T\n", shape)
		fmt.Printf("Area: %.2f\n", shape.Area())
		fmt.Printf("Perimeter: %.2f\n", shape.Perimeter())
		fmt.Println("------------")
	}
}

No exemplo acima, definimos uma Shapeinterface chamada que possui dois métodos Area()e Perimeter(), usados ​​para calcular a área e o perímetro de uma forma respectivamente. Em seguida, implementamos os dois métodos da estrutura Circlee Rectanglerespectivamente, para que satisfaçam Shapea interface.

Ao colocar diferentes tipos de instâncias de forma em uma fatia, podemos chamar os métodos e []Shapede forma unificada , obtendo polimorfismo e desacoplamento do código. Desta forma, independentemente de adicionarmos novas formas posteriormente, desde que implementem os métodos de interface, elas podem ser perfeitamente integradas à calculadora.Area()Perimeter()Shape

Reutilização anônima de campos e métodos

Ao usar campos anônimos, uma estrutura pode herdar os campos e métodos de outra estrutura. Crie other_ext_test.go

package extend

import (
	"fmt"
	"testing"
)

type Animal struct {
	Name string
}

func (a Animal) Speak() {
	fmt.Println("Animal speaks")
}

type Dog struct {
	Animal
	Breed string
}

func TestOtherExt(t *testing.T) {
	dog := Dog{
		Animal: Animal{Name: "Buddy"},
		Breed:  "Golden Retriever",
	}

	fmt.Println("Dog name:", dog.Name)
	dog.Speak() // 继承了 Animal 的 Speak 方法
}

No exemplo acima, Doga struct aninha Animaluma struct, herdando assim Animalos campos e métodos de .

Dessa forma, você pode estender e reutilizar código na linguagem Go. Embora Go não enfatize tanto a herança de classes quanto as linguagens tradicionais orientadas a objetos, por meio de recursos como composição, interfaces e campos anônimos, você ainda pode obter efeitos semelhantes, tornando o código mais flexível, mais legível e mantendo baixo acoplamento.

6. Interfaces e asserções vazias

Interfaces e asserções vazias são conceitos importantes na linguagem Go para lidar com tipos indefinidos e conversões de tipo.

Crie o diretório vazioassert para exercícios subsequentes. A seguir está um resumo do aprendizado sobre interfaces e asserções vazias:

Interface vazia

A interface vazia é a interface mais básica da linguagem Go. Ela não contém nenhuma declaração de método. Portanto, a interface vazia pode ser usada para representar qualquer tipo de valor. O método de declaração da interface vazia é interface{}.

O principal uso da interface vazia é em cenários onde você precisa lidar com tipos indefinidos. Ao utilizar a interface vazia, qualquer tipo de valor pode ser aceito e armazenado, semelhante à digitação dinâmica em outras linguagens de programação. No entanto, deve-se notar que o uso de uma interface vazia pode resultar em segurança de tipo reduzida porque o tipo concreto não pode ser verificado em tempo de compilação.

Afirmação

Asserção é um mecanismo para recuperar um tipo concreto em uma interface vazia, que nos permite verificar o tipo real de um valor em uma interface vazia em tempo de execução e convertê-lo para o tipo correspondente. A sintaxe para asserções é value.(Type)onde valueestá o valor da interface e Typeo tipo concreto a ser afirmado.

Crie emptyassert_test.go para verificação:

package emptyassert

import (
	"fmt"
	"testing"
)

func DoSomething(p interface{}) {
	switch v := p.(type) {
	case int:
		fmt.Println("Integer", v)
	case string:
		fmt.Println("String", v)
	default:
		fmt.Println("Unknow Type")
	}
}

func TestEmptyInterfaceAssertion(t *testing.T) {
	DoSomething(10)
	DoSomething("10")
}

func TestEmptyAssert(t *testing.T) {
	var x interface{} = "hello"
	str, ok := x.(string)
	if ok {
		fmt.Println("String:", str)
	} else {
		fmt.Println("Not a string")
	}
}

O conteúdo de cada função de teste é explicado um por um abaixo:

  1. func DoSomething(p interface{}) { ... }: define uma função DoSomethingque aceita um parâmetro de interface vazio p, executa asserções de tipo com base no tipo real do valor da interface e gera informações diferentes com base em tipos diferentes.

  2. func TestEmptyInterfaceAssertion(t *testing.T) { ... }: teste a operação de asserção da interface vazia.

    Chame DoSomething(10), passe o número inteiro 10para a função e a função gerará as informações do tipo inteiro com base na asserção do tipo. Chame DoSomething("10"), passe a string "10"para a função e a função exibirá as informações do tipo de string com base na asserção do tipo.
  3. func TestEmptyAssert(t *testing.T) { ... }: teste a operação de asserção de tipo da interface vazia.

    Declare uma variável de interface vazia xe atribua o "hello"valor da string a ela. Use a asserção de tipo x.(string)para determinar xse é um tipo de string. Em caso afirmativo, atribua-o a uma variável stre gere o valor da string; caso contrário, imprima "Não é uma string".

Essas funções de teste mostram a operação de asserção da interface vazia na linguagem Go. Por meio de asserções de tipo, o tipo específico na interface vazia pode ser determinado e as operações correspondentes podem ser executadas.

Resumo: Interfaces e asserções vazias são ferramentas poderosas na linguagem Go para lidar com tipos indefinidos e conversões de tipo. Interfaces vazias permitem armazenar qualquer tipo de valor, enquanto asserções nos permitem verificar e converter o tipo real de valores de interface em tempo de execução. O uso desses mecanismos permite um código mais flexível e versátil quando você precisa lidar com diferentes tipos de valores. Mas ao usar interfaces e asserções vazias, tome cuidado para manter a segurança do tipo e realizar o tratamento de erros apropriado.

7. Práticas recomendadas para interface GO

Na linguagem Go, as melhores práticas de uso de interfaces podem melhorar a legibilidade, manutenção e flexibilidade do código. Aqui estão algumas práticas recomendadas para interfaces Go:

  • Interfaces pequenas e interfaces grandes: Tente projetar uma interface pequena. Uma interface deve conter apenas um pequeno número de métodos em vez de projetar uma interface grande e abrangente. Isto evita encargos desnecessários na implementação da interface e torna a interface mais geral.
  • Projetar interfaces com base em cenários de uso: Ao projetar interfaces, você deve considerar cenários de uso em vez de partir de implementações específicas. Pense em como as interfaces são usadas em seu aplicativo e quais métodos a interface deve fornecer para satisfazer esses casos de uso.
  • Use nomenclatura apropriada: Use nomenclatura clara para interfaces e métodos para que transmitam sua finalidade e funcionalidade. A nomenclatura deve ser legível e expressiva para que outros desenvolvedores possam compreender facilmente o propósito da interface.
  • Evite interfaces desnecessárias: Não crie uma interface para cada tipo, use interfaces somente quando houver de fato comportamento e funcionalidade compartilhados entre vários tipos. Não abuse das interfaces para evitar complexidade desnecessária.
  • Use interfaces como parâmetros de função e valores de retorno: Usar interfaces como parâmetros de função e valores de retorno pode tornar a função mais versátil, permitindo que diferentes tipos de parâmetros sejam passados ​​e diferentes tipos de resultados retornados. Isso melhora a reutilização e escalabilidade do código.
  • Comentários e documentação: Forneça documentação e comentários claros para a interface, explicando o propósito da interface, a funcionalidade dos métodos e o comportamento esperado. Isso pode ajudar outros desenvolvedores a entender melhor como a interface é usada.
  • Use design orientado a casos: ao projetar uma interface, você pode começar da perspectiva de uso, primeiro considerar como a interface é chamada em cenários reais e, em seguida, projetar os métodos e assinaturas da interface.
  • Separe a implementação e a definição da interface: Separar a implementação da interface da definição da interface pode tornar a implementação mais flexível e novos tipos podem ser implementados sem modificar a definição da interface.
  • Implementação padrão: Na definição da interface, você pode fornecer implementações padrão para determinados métodos, reduzindo assim a carga de trabalho ao implementar a interface. Isso é útil para métodos opcionais ou para o comportamento padrão de determinados métodos.
  • Use interface vazia com cuidado: Use interface vazia ( interface{}) com cuidado, pois reduz a segurança de tipo. Use interfaces vazias apenas quando realmente precisar lidar com valores de tipos diferentes e tenha cuidado com asserções de tipo e tratamento de erros.

Resumindo, ao projetar e utilizar interfaces, você deve escolher a solução adequada com base nas necessidades reais e nas características do projeto. Seguir as práticas recomendadas acima pode ajudar a escrever código Go que seja mais sustentável, escalonável e legível.

(6) Escreva um bom mecanismo de erro

Crie o capítulo 6 no diretório src. O mecanismo de tratamento de erros na linguagem Go é implementado retornando valores de erro em vez de usar exceções. Este mecanismo de tratamento de erros é muito claro e controlável, permitindo aos desenvolvedores lidar com várias situações de erro com precisão.

1. Introdução ao uso básico

Crie o diretório básico e escreva basic_error_test.go

Tipo de erro

No Go, os erros são representados como um errortipo que implementa a interface. errorA interface possui apenas um método, ou seja, Error() stringretorna uma string descrevendo o erro.

type error interface {
    Error() string
}

Valor de erro de retorno

Quando uma função encontra uma condição de erro, geralmente retorna um valor de erro. Esse valor de erro pode ser um tipo personalizado que implementa errora interface ou pode ser um tipo de erro predefinido na biblioteca padrão Go, como errors.New()um erro criado por .

Verificação de erros

O chamador geralmente precisa verificar explicitamente o erro retornado pela função para determinar se ocorreu um erro. Isso pode ser conseguido usando a instrução após chamar a função if.

Os dois acima escrevem diretamente o código da seguinte maneira:

package basic

import (
	"errors"
	"fmt"
	"testing"
)

var LessThanTwoError = errors.New("n should be not less than 2")
var LargerThenHundredError = errors.New("n should be not larger than 100")

func GetFibonacci(n int) ([]int, error) {
	if n < 2 {
		return nil, LessThanTwoError
	}
	if n > 100 {
		return nil, LargerThenHundredError
	}
	fibList := []int{1, 1}

	for i := 2; /*短变量声明 := */ i < n; i++ {
		fibList = append(fibList, fibList[i-2]+fibList[i-1])
	}
	return fibList, nil
}

func TestGetFibonacci(t *testing.T) {
	if v, err := GetFibonacci(1); err != nil {
		if err == LessThanTwoError {
			fmt.Println("It is less.")
		}
		t.Error(err)
	} else {
		t.Log(v)
	}

}

2. Cadeia de erros

Crie o diretório da cadeia e escreva error_chain_test.go

Em alguns casos, os erros podem conter informações adicionais para compreender melhor a causa do erro. fmt.Errorf()Erros contendo informações adicionais podem ser criados usando a função.

Suponha que estejamos construindo uma biblioteca para operações de arquivos, que contém funções de leitura e gravação de arquivos. Às vezes, vários erros podem ocorrer durante a leitura ou gravação de arquivos, como inexistência de arquivo, problemas de permissão, etc. Gostaríamos de poder fornecer mais informações contextuais sobre o erro.

package chain

import (
	"errors"
	"fmt"
	"testing"
)

// 自定义文件操作错误类型
type FileError struct {
	Op   string // 操作类型("read" 或 "write")
	Path string // 文件路径
	Err  error  // 原始错误
}

// 实现 error 接口的 Error() 方法
func (e *FileError) Error() string {
	return fmt.Sprintf("%s %s: %v", e.Op, e.Path, e.Err)
}

// 模拟文件读取操作
func ReadFile(path string) ([]byte, error) {
	// 模拟文件不存在的情况
	return nil, &FileError{Op: "read", Path: path, Err: errors.New("file not found")}
}

func TestChain(t *testing.T) {
	filePath := "/path/to/nonexistent/file.txt"
	_, err := ReadFile(filePath)
	if err != nil {
		fmt.Println("Error:", err)
		// 在这里,我们可以检查错误类型,提取上下文信息
		if fileErr, ok := err.(*FileError); ok {
			fmt.Printf("Operation: %s\n", fileErr.Op)
			fmt.Printf("File Path: %s\n", fileErr.Path)
			fmt.Printf("Original Error: %v\n", fileErr.Err)
		}
	}
}

Aqui está uma explicação do código:

  1. FileErrorEstrutura: Define um tipo de erro personalizado FileError, contendo os seguintes campos:

    • Op: Tipo de operação, indicando se é uma operação de leitura ("read") ou gravação ("write").
    • Path: Caminho do arquivo, indicando qual arquivo está envolvido.
    • Err: Erro original, contendo informações de erro subjacentes.
  2. Error()Método: FileErrorImplementa o método errorda interface para a estrutura Error(), que é utilizado para gerar uma descrição textual do erro.

  3. ReadFile()Função: simular operação de leitura de arquivos. Neste exemplo, a função retorna um FileErrorerro do tipo, simulando a situação em que o arquivo não existe.

  4. TestChain()Função de teste: demonstra como usar tipos de erros personalizados no tratamento de erros.

    • Um caminho de arquivo é definido filePathe ReadFile(filePath)a função é chamada para simular uma operação de leitura de arquivo.
    • Verifique se há erros e envie uma mensagem de erro se ocorrer um erro.
    • No tratamento de erros, asserções de tipo são usadas para verificar se o erro é do *FileErrortipo e, nesse caso, mais informações contextuais podem ser extraídas, como tipo de operação, caminho do arquivo e informações originais do erro.

3.Pânico e recuperação

Na linguagem Go, panice recoversão mecanismos para tratar situações de exceção, mas devem ser usados ​​com cautela e apenas em situações específicas, e não como substitutos de mecanismos normais de tratamento de erros. Aqui está uma explicação detalhada de panice recover, com um caso de uso específico:

pânico

Crie panicum diretório e escreva panic_test.go. panicé uma função interna usada para causar pânico no tempo de execução. Quando o programa encontra um erro fatal que não pode continuar a execução, você pode usar panicpara interromper o fluxo normal do programa. Mas o uso indevido deve ser evitado panic, pois pode causar o travamento do programa sem fornecer uma mensagem de erro amigável. Normalmente panicusado para indicar um erro irrecuperável em um programa, como um índice de fatia fora dos limites.

package panic

import (
	"fmt"
	"testing"
)

func TestPanic(t *testing.T) {
	arr := []int{1, 2, 3}
	index := 4
	if index >= len(arr) {
		panic("Index out of range")
	}
	element := arr[index]
	fmt.Println("Element:", element)
}

No exemplo acima, se o índice indexultrapassar arro intervalo da fatia, ele será acionado panic, causando o travamento do programa. Neste caso, panicé utilizado para indicar um erro irrecuperável no programa.

recuperar

Crie recoverum diretório e escreva recover_test.go. recoverTambém é uma função integrada para recuperação de panicpânicos em tempo de execução causados ​​por arquivos . Ele só pode deferser usado dentro de uma função de atraso ( ) e é usado para restaurar o fluxo de controle do programa, não para tratar erros. Normalmente, após uma ocorrência panic, recovervocê pode capturá-la em uma função de atraso panic, realizar alguma limpeza e então o programa continua a execução.

package recover

import (
	"fmt"
	"testing"
)

func cleanup() {
	if r := recover(); r != nil {
		fmt.Println("Recovered from panic:", r)
	}
}

func TestRecover(t *testing.T) {
	defer cleanup()
	panic("Something went wrong")
	fmt.Println("This line will not be executed")
}

No exemplo acima, panicapós o acionamento, cleanupo valor da função recoveré capturado panice uma mensagem de erro é impressa. O programa então continuará em execução, mas deve-se ressaltar que o fluxo de controle não retornará ao paniclocal onde foi acionado, portanto fmt.Printlnnão será executado.

Em resumo, panice recoverdeve ser usado com cautela e apenas em casos especiais, como erros irrecuperáveis ​​ou operações de limpeza em funções adiadas. Na maioria dos casos, os valores de retorno de erro devem ser usados ​​em preferência ao tratamento de exceções, pois essa abordagem é mais segura, mais controlável e fornece melhores informações e tratamento de erros. panicVocê só deve considerar usar e em circunstâncias específicas, como ao encontrar um erro irrecuperável recover.

4. Tipos de erros personalizados

Crie defineum diretório e escreva error_define_test.go.

No Go, você pode definir seus próprios tipos de erros conforme necessário, bastando atender erroraos requisitos da interface. Isso permite criar tipos de erro mais descritivos e contextuais.

No Go, os tipos de erros personalizados são uma maneira poderosa de criar erros mais descritivos e contextuais para fornecer melhores informações sobre os erros. Os tipos de erros customizados devem atender erroraos requisitos da interface, ou seja, implementar Error() stringo método. Aqui está um exemplo que mostra como personalizar um tipo de erro e validar seu caso de uso:

package define

import (
	"fmt"
	"testing"
	"time"
)

// 自定义错误类型
type TimeoutError struct {
	Operation string    // 操作名称
	Timeout   time.Time // 超时时间
}

// 实现 error 接口的 Error() 方法
func (e TimeoutError) Error() string {
	return fmt.Sprintf("Timeout error during %s operation. Timeout at %s", e.Operation, e.Timeout.Format("2006-01-02 15:04:05"))
}

// 模拟执行某个操作,可能会超时
func PerformOperation() error {
	// 模拟操作超时
	timeout := time.Now().Add(5 * time.Second)
	if time.Now().After(timeout) {
		return TimeoutError{Operation: "PerformOperation", Timeout: timeout}
	}
	// 模拟操作成功
	return nil
}

func TestDefineError(t *testing.T) {
	err := PerformOperation()
	if err != nil {
		// 检查错误类型并打印错误信息
		if timeoutErr, ok := err.(TimeoutError); ok {
			fmt.Println("Error Type:", timeoutErr.Operation)
			fmt.Println("Timeout At:", timeoutErr.Timeout)
		}
		fmt.Println("Error:", err)
	} else {
		fmt.Println("Operation completed successfully.")
	}
}

Aqui está uma explicação do código:

  1. TimeoutErrorEstrutura: Define um tipo de erro personalizado TimeoutError, contendo os seguintes campos:

    • Operation: Nome da operação, indicando qual operação expirou.
    • Timeout: Tempo limite, indicando o momento em que a operação expira.
  2. Error()Método: TimeoutErrorImplementa o método errorda interface para a estrutura Error(), que é utilizado para gerar uma descrição textual do erro.

  3. PerformOperation()Função: simula a execução de uma operação e pode atingir o tempo limite. Neste exemplo, se a hora atual exceder o tempo limite, um TimeoutErrorerro do tipo será retornado.

  4. TestDefineError()Função de teste: demonstra como usar tipos de erros personalizados no tratamento de erros.

    • Chame PerformOperation()a função para simular a operação e verificar se ocorreu algum erro.
    • Se ocorrer um erro, primeiro verifique se o tipo de erro é TimeoutError, em caso afirmativo, extraia a operação de tempo limite e o tempo limite e gere informações relevantes.
    • Finalmente, independentemente de ter ocorrido um erro, uma mensagem de erro ou uma mensagem de conclusão bem-sucedida será emitida.

Este exemplo mostra como personalizar tipos de erros e como aproveitar esses tipos de erros personalizados no tratamento de erros para fornecer informações mais contextuais, tornando o tratamento de erros mais informativo e flexível. Aqui, TimeoutErrorsão fornecidas informações adicionais sobre operações e períodos de tempo limite.

Acho que você gosta

Origin blog.csdn.net/xiaofeng10330111/article/details/132390106
Recomendado
Clasificación