Prefácio
Vá definição de idioma
Go(又称 Golang)是 Google 的 Robert Griesemer,Rob Pike 及 Ken Thompson 开发的一种静态、强类型、编译型语言。Go 语言语法与 C 相近,但功能上有:内存安全,GC,结构形态及 CSP-style 并发计算
。
Âmbito de aplicação
Este artigo é adequado para iniciantes que aprenderam outras linguagens orientadas a objetos (Java, Php), mas não aprenderam a linguagem Go. O artigo explica principalmente os quatro aspectos da sintaxe básica da linguagem Go, programação orientada a objetos, simultaneidade e erros na comparação das funções de Go e Java .
1. Gramática básica
A sintaxe básica da linguagem Go é basicamente semelhante à das linguagens de programação convencionais. A diferença é a forma de declarar variáveis. Os conceitos e funções de arrays, fatias e dicionários não são os mesmos de Java. em Java podem ser usados por analogia.
1.1 Variáveis, constantes, valores nulos e zero, métodos, pacotes, visibilidade, ponteiros
1.1.1 Declaração de variáveis
Existem duas maneiras na linguagem Go
1. Use var
a declaração de palavra-chave e deve-se observar que, diferentemente da maioria das linguagens fortemente tipadas, o tipo de variável declarado na linguagem Go está localizado após o nome da variável. Não há necessidade de ponto e vírgula no final de uma instrução Go.
var num int
var result string = "this is result"
2. Use :=
atribuição.
num := 3
Equivalente avar num int = 3
O tipo da variável será correspondido de acordo com o valor no lado direito. Por exemplo, "3" corresponderá a int, "3.0" corresponderá a float64 e "resultado" corresponderá a string.
1.1.2 Declaração constante
Use const
para declarar uma constante. Uma constante não pode ser alterada depois de declarada.
const laugh string = "go"
1.1.3 valor nulo e zero
Declare apenas variáveis não atribuídas, cujo valor seja nulo. Semelhante a " nulo" em java .
Declarações de variáveis sem valores iniciais explícitos recebem seu valor zero .
O valor zero é:
-
O tipo numérico é
0
, -
O tipo booleano é
false
, -
String é
""
(string vazia).
1.1.4 Métodos e pacotes
Definição de métodos em Go
Use a palavra-chave func para definir um método, seguida pelo nome do método, depois pelos parâmetros e pelo valor de retorno (se houver, se não houver valor de retorno, ele não será escrito).
func MethodName(p1 Parm, p2 Parm) int{}
//学习一个语言应该从Hello World开始!
package main
import "fmt"
func main() {
fmt.Println("Hello World!")// Hello World!
fmt.Println(add(3, 5)) //8
var sum = add(3, 5)
}
func add(a int, b int) int{
return a+b;
}
Vários valores de retorno
Uma grande diferença entre as funções Go e outras linguagens de programação é que elas suportam vários valores de retorno, o que é muito útil ao lidar com erros de programa. Por exemplo, se a função acima add
suportar apenas a adição de números inteiros não negativos, um erro será relatado se um número negativo for passado.
//返回值只定义了类型 没有定义返回参数
func add(a, b int) (int, error) {
if a < 0 || b < 0 {
err := errors.New("只支持非负整数相加")
return 0, err
}
a *= 2
b *= 3
return a + b, nil
}
//返回值还定义了参数 这样可以直接return 并且定义的参数可以直接使用 return时只会返回这两个参数
func add1(a, b int) (z int, err error) {
if a < 0 || b < 0 {
err := errors.New("只支持非负整数相加")
return //实际返回0 err 因为z只定义没有赋值 则nil值为0
}
a *= 2
b *= 3
z = a + b
return //返回 z err
}
func main() {
x, y := -1, 2
z, err := add(x, y)
if err != nil {
fmt.Println(err.Error())
return
}
fmt.Printf("add(%d, %d) = %d\n", x, y, z)
}
Parâmetros de comprimento variável
func myfunc(numbers ...int) {
for _, number := range numbers {
fmt.Println(number)
}
}
slice := []int{1, 2, 3, 4, 5}
//使用...将slice打碎传入
myfunc(slice...)
Pacotes e Visibilidade
Na linguagem Go, sejam variáveis, funções ou atributos de classe e métodos de membro, sua visibilidade é baseada na dimensão do pacote, em vez da programação tradicional, onde a visibilidade dos atributos de classe e métodos de membro é encapsulada na classe à qual eles pertencem e private
, modificam sua visibilidade com essas palavras-chave protected
e .public
A linguagem Go não fornece essas palavras-chave, sejam elas variáveis, funções ou atributos e métodos membros de classes personalizadas, sua visibilidade é determinada com base na capitalização de suas primeiras letras . a primeira letra do nome do método , você pode acessar diretamente essas variáveis, propriedades, funções e métodos fora do pacote, caso contrário, eles só podem ser acessados dentro do pacote. Portanto, a visibilidade dos atributos da classe da linguagem Go e dos métodos membros está no pacote. nível, e não classe um.
Se domain
houver três arquivos .go em uma pasta nomeada, então os três arquivos package
deverão estar domain
entre eles, o arquivo onde está localizado o método principal de entrada do programa e o pacote.main
//定义了此文件属于 main 包
package main
//通过import导入标注库中包
import "fmt"
func main() {
fmt.Println("Hello World!")// Hello World!
fmt.Println(add(3, 5)) //8
var sum = add(3, 5)
}
func add(a int, b int) int{
return a+b;
}
1.1.5 Ponteiros
Para aqueles que estudaram a linguagem C, os ponteiros são bastante familiares. O ponteiro que entendo é na verdade um valor de endereço hexadecimal real na memória.
func main() {
i := 0
//使用&来传入地址
fmt.Println(&i) //0xc00000c054
var a, b int = 3 ,4
//传入 0xc00000a089 0xc00000a090
fmt.Println(add(&a, &b))
}
//使用*来声明一个指针类型的参数与使用指针
func add(a *int, b *int)int{
//接收到 0xc00000a089 0xc00000a090
//前往 0xc00000a089位置查找具体数据 并取赋给x
x := *a
//前往 0xc00000a090位置查找具体数据 并取赋给y
y := *b
return x+y
}
1.2 Condições, loops, ramificações
1.2.1 Condições
Basicamente o mesmo que na linguagem Java
// if
if condition {
// do something
}
// if...else...
if condition {
// do something
} else {
// do something
}
// if...else if...else...
if condition1 {
// do something
} else if condition2 {
// do something else
} else {
// catch-all or default
}
1.2.2 Ciclo
sum := 0
//普通for循环
for i := 1; i <= 100; i++ {
sum += i
}
//无限循环
for{
sum++
if sum = 100{
break;
}
}
//带条件的循环
for res := sum+1; sum < 15{
sum++
res++
}
//使用kv循环一个map或一个数组 k为索引或键值 v为值 k、v不需要时可以用_带替
for k, v := range a {
fmt.Println(k, v)
}
1.2.3 Filial
score := 100
switch score {
case 90, 100:
fmt.Println("Grade: A")
case 80:
fmt.Println("Grade: B")
case 70:
fmt.Println("Grade: C")
case 65:
fmt.Println("Grade: D")
default:
fmt.Println("Grade: F")
}
1.3 Matrizes, fatias, dicionários
1.3.1 Matriz
A função de array é semelhante à linguagem Java, o comprimento é imutável e arrays multidimensionais podem ser usados, ou valores podem ser armazenados ou obtidos por meio de arrays[i].
//声明
var nums [3]int
//声明并初始化
var nums = [3]int{1,2,3} <==> nums:=[3]int{1,2,3}
//使用
for sum := 0, i := 0;i<10{
sum += nums[i]
i++
}
//修改值
num[0] = -1
Arrays são relativamente simples de usar, mas existe um problema difícil de resolver: comprimento fixo .
Por exemplo, quando precisamos de uma estrutura de dados no programa para armazenar todos os usuários obtidos, porque o número de usuários muda com o tempo, mas o comprimento do array não pode ser alterado, então o array não é adequado para armazenar dados cujo comprimento muda . Portanto, os problemas acima são resolvidos usando fatias na linguagem Go.
1.3.2 Fatiamento
Fatiar é um conceito completamente novo em comparação com Java. Em Java, para estruturas de armazenamento de dados de comprimento variável, você pode usar a interface List para concluir operações, como ArrayList e LinkList. Essas interfaces podem adicionar e obter dados a qualquer momento e não há limite de comprimento. No entanto, não existe tal interface em Go. Em vez disso, o armazenamento de dados de comprimento variável é realizado por meio de fatias .
A maior diferença entre fatias e arrays é que as fatias não precisam declarar o comprimento. Mas as fatias não são independentes das matrizes. Uma matriz pode ser considerada como a matriz subjacente da fatia, e a fatia pode ser considerada uma referência a um fragmento contínuo da matriz. As fatias podem ser criadas usando apenas uma parte de uma matriz ou a matriz inteira, e é ainda possível criar uma fatia maior que a matriz subjacente:
comprimento, capacidade
切片的长度就是它所包含的元素个数。
切片的容量是从它的第一个元素开始数,到其底层数组元素末尾的个数。
切片 s 的长度和容量可通过表达式 len(s) 和 cap(s) 来获取。
O comprimento da fatia é funcionalmente análogo ao size() de List em Java, ou seja, o comprimento da fatia é percebido por meio de len(slice) e len(slice) pode ser repetido para controlar dinamicamente o conteúdo específico no fatiar. A capacidade de slices não é muito utilizada no desenvolvimento atual, basta entender seu conceito.
Criar fatias
//声明一个数组
var nums =[3]int{1, 2, 3}
//0.直接声明
var slice =[]int{0, 1, 2}
//1.从数组中引用切片 其中a:b是指包括a但不包括b
var slice1 = nums[0:2] //{1,2}
//如果不写的则默认为0(左边)或最大值(右边)
var slice2 = slice1[:2] <==> var slice2 = slice1[0:] <==>var slice2 = slice1[:]
//2.使用make创建Slice 其中int为切片类型,4为其长度,5为容量
slice3 := make([]int, 5)
slice4 := make([]int, 4, 5)
Operação dinâmica de fatias
//使用append向切片中动态的添加元素
func append(s []T, vs ...T) []T
slice5 := make([]int, 4, 5) //{0, 0, 0, 0}
slice5 = append(slice5, 1) //{0,0,0,0,1}
//删除第一个0
sliece5 = slice5[1:]
Cenários comuns para fatiar
Simule o problema mencionado acima usando a solução de fatiamento
//声明切片
var userIds = []int{}
//模拟获取所有用户ID
for i := 0; i< 100{
userIds = append(userIdS, i);
i++;
}
//对用户信息进行处理
for k,v := range userIds{
userIds[k] = v++
}
1.3.3 Dicionário
Dicionário, também conhecido como 'par chave-valor' ou 'valor-chave', é uma estrutura de dados comumente usada. Existem várias interfaces de mapa em Java, e as comumente usadas são HashMap, etc. No Go, os dicionários são usados para armazenar pares de valores-chave. Os dicionários não são ordenados, portanto, a ordem dos dados não é garantida com base na ordem de adição.
Declaração e inicialização do dicionário
//string为键类型,int为值类型
maps := map[string]int{
"java" : 1,
"go" : 2,
"python" : 3,
}
//还可以通过make来创建字典 100为其初始容量 超出可扩容
maps = make(map[string]int, 100)
Cenários de uso de dicionário
//直接使用
fmt.Println(maps["java"]) //1
//赋值
maps["go"] = 4
//取值 同时判断map中是否存在该键 ok为bool型
value, ok := maps["one"]
if ok { // 找到了
// 处理找到的value
}
//删除
delete(testMap, "four")
2. Programação orientada a objetos
2.1 Aulas na linguagem Go
Como todos sabemos, em linguagens orientadas a objetos, uma classe deve ter três estruturas: atributos, construtores e métodos de membros, e a linguagem Go não é exceção.
2.1.1 Declaração e inicialização de classe
Não existe um conceito claro de classe na linguagem Go. Apenas struct
palavras-chave podem ser funcionalmente analogizadas a "classes" em linguagens orientadas a objetos. Por exemplo, para definir uma classe de aluno, você pode fazer o seguinte:
type Student struct {
id int
name string
male bool
score float64
}//定义了一个学生类,属性有id name等,每个属性的类型都在其后面
//定义学生类的构造方法
func NewStudent(id uint, name string, male bool, score float64) *Student {
return &Student{id, name, male, score}
}
//实例化一个类对象
student := NewStudent(1, "学院君", 100)
fmt.Println(student)
2.1.2 Métodos de membros
A declaração do método membro em Go não é a mesma que em outras linguagens. Tomando a classe Aluno como exemplo,
//在方法名前,添加对应的类,即可认为改方法为该类的成员方法。
func (s Student) GetName() string {
return s.name
}
//注意这里的Student是带了*的 这是因为在方法传值过程中 存在着值传递与引用传递 即指针的概念 当使用值传递时 编译器会为该参数创建一个副本传入 因此如果对副本进行修改其实是不生效的 因为在执行完此方法后该副本会被销毁 所以此处应该是用*Student 将要修改的对象指针传入 修改值才能起作用
func (s *Student) SetName(name string) {
//这里其实是应该使用(*s).name = name,因为对于一个地址来说 其属性是没意义的 不过这样使用也是可以的 因为编译器会帮我们自动转换
s.name = name
}
2.2 Interface
As interfaces desempenham um papel vital na linguagem Go. Se goroutine e channel são os pilares que suportam o modelo de simultaneidade da linguagem Go, então as interfaces são os pilares de todo o sistema de tipos da linguagem Go . A interface da linguagem Go não é apenas uma interface. Vamos explorar os recursos da interface da linguagem Go passo a passo.
2.2.1 Implementação de interface intrusiva tradicional
Semelhante à implementação de classes, o conceito de interface da linguagem Go é completamente diferente dos conceitos de interface fornecidos em outras linguagens. Tomando Java e PHP como exemplo, as interfaces existem principalmente como contratos entre diferentes classes, e a implementação do contrato é obrigatória. Isso se reflete nos detalhes específicos: se uma classe implementa uma determinada interface, ela deve implementar todos os métodos declarados. , isso é chamado de "cumprimento do contrato":
// 声明一个'iTemplate'接口
interface iTemplate
{
public function setVariable($name, $var);
public function getHtml($template);
}
// 实现接口
// 下面的写法是正确的
class Template implements iTemplate
{
private $vars = array();
public function setVariable($name, $var)
{
$this->vars[$name] = $var;
}
public function getHtml($template)
{
foreach($this->vars as $name => $value) {
$template = str_replace('{' . $name . '}', $value, $template);
}
return $template;
}
}
Neste momento, se houver outra interface iTemplate2
que declare um iTemplate
método de interface exatamente igual a , mesmo com o mesmo nome iTemplate
, mas localizado em um namespace diferente, o compilador também pensará que a classe acima Template
apenas implementa iTemplate
e não implementa iTemplate2
a interface.
Isso é dado como certo em nosso entendimento anterior, seja herança entre classes ou implementação entre classes e interfaces, em linguagens de herança única, como Java e PHP, existe um relacionamento hierárquico estrito. Uma classe só pode herdar diretamente de um. classe pai, e uma classe só pode implementar uma interface especificada. Se não for explicitamente declarada para herdar de uma classe pai ou implementar uma determinada interface, então a classe não tem nada a ver com a classe pai ou a relação de interface.
Chamamos esse tipo de interface de interface intrusiva . A chamada "intrusiva" significa que a classe de implementação deve declarar explicitamente que implementa uma interface. Embora este método de implementação seja bastante claro e simples, ainda existem alguns problemas, especialmente ao projetar a biblioteca padrão, porque a biblioteca padrão deve envolver o design da interface. O demandante da interface é a classe de implementação de negócios, e somente a classe de implementação de negócios pode. ser escrito especificamente. Só então saberemos quais métodos precisam ser definidos. Antes disso, a interface da biblioteca padrão pode ser implementada de acordo com a interface acordada. projetá-lo nós mesmos. O problema aqui é que o design da interface e a implementação do negócio nem sempre podem prever quais funções o lado comercial implementará, o que resulta em uma desconexão entre o design e a implementação.
O design excessivo da interface fará com que algumas classes de implementação de métodos declaradas sejam completamente desnecessárias. Se o design for muito simples, não atenderá às necessidades do negócio. Isso é realmente um problema e não faz sentido discuti-los sem o usuário. cenários de uso. Tomemos como exemplo a interface SessionHandlerInterface que vem com o PHP. Os métodos de interface declarados por esta interface são os seguintes:
SessionHandlerInterface {
/* 方法 */
abstract public close ( void ) : bool
abstract public destroy ( string $session_id ) : bool
abstract public gc ( int $maxlifetime ) : int
abstract public open ( string $save_path , string $session_name ) : bool
abstract public read ( string $session_id ) : string
abstract public write ( string $session_id , string $session_data ) : bool
}
O gerenciador de sessão definido pelo usuário precisa implementar esta interface, ou seja, deve implementar todos os métodos declarados por esta interface. Porém, ao realmente fazer o desenvolvimento de negócios, alguns métodos não precisam realmente ser implementados. Redis ou Memcached como sessão Quanto às memórias, elas próprias contêm um mecanismo de reciclagem de expiração, portanto gc
o método não precisa ser implementado. Por exemplo, close
o método não tem sentido para a maioria dos drivers.
É precisamente por causa desse design irracional que você precisa enfrentar os dois problemas a seguir ao escrever cada interface na biblioteca de classes PHP (Java é semelhante):
-
Quais métodos de interface precisam ser declarados para uma interface?
-
Se várias classes implementarem o mesmo método de interface, como a interface deverá ser projetada? Por exemplo, o acima
SessionHandlerInterface
, é necessário dividi-lo em várias interfaces mais subdivididas para se adaptar às necessidades de diferentes classes de implementação?
A seguir, vamos dar uma olhada em como a interface da linguagem Go evita esses problemas.
2.2.2 Implementação da interface da linguagem Go
Na linguagem Go, a implementação de uma interface por uma classe é igual à herança de uma subclasse de uma classe pai. Não existe implement
uma palavra-chave como esta para declarar explicitamente qual interface a classe implementa . métodos exigidos por uma interface, dizemos que esta classe implementa a interface .
Por exemplo, definimos uma classe e File
implementamos Read()
quatro métodos:Write()
Seek()
Close()
type File struct {
// ...
}
func (f *File) Read(buf []byte) (n int, err error)
func (f *File) Write(buf []byte) (n int, err error)
func (f *File) Seek(off int64, whence int) (pos int64, err error)
func (f *File) Close() error
Suponha que temos a seguinte interface (a linguagem Go usa a palavra-chave para interface
declarar a interface para mostrar a diferença do tipo de estrutura, e as chaves contêm o conjunto de métodos a serem implementados):
type IFile interface {
Read(buf []byte) (n int, err error)
Write(buf []byte) (n int, err error)
Seek(off int64, whence int) (pos int64, err error)
Close() error
}
type IReader interface {
Read(buf []byte) (n int, err error)
}
type IWriter interface {
Write(buf []byte) (n int, err error)
}
type ICloser interface {
Close() error
}
Embora File
a classe não implemente explicitamente essas interfaces, ou mesmo saiba da existência dessas interfaces, dizemos que File
a classe implementa essas interfaces porque File
a classe implementa os métodos declarados por todas as interfaces acima. Quando o conjunto de métodos membros de uma classe contém todos os métodos declarados por uma interface, ou seja, se o conjunto de métodos de uma interface é um subconjunto do conjunto de métodos membros de uma determinada classe, consideramos a classe para implementar a interface.
Em comparação com Java e PHP, chamamos essa interface da linguagem Go de interface não intrusiva , porque o relacionamento de implementação entre uma classe e uma interface não é declarado explicitamente, mas é julgado pelo sistema com base no conjunto de métodos dos dois. Isso tem dois benefícios:
-
Primeiro, a biblioteca padrão da linguagem Go não precisa desenhar o diagrama de árvore de herança/implementação da biblioteca de classes. Na linguagem Go, a árvore de herança da classe não tem sentido. o que cada método faz será suficiente.
-
Em segundo lugar, ao definir uma interface, você só precisa se preocupar com quais métodos deve fornecer. Você não precisa mais se preocupar com o quão detalhada a interface precisa ser. Não há necessidade de introduzir o pacote onde a interface está localizada. implementar uma interface. A interface é determinada pelo usuário, sem necessidade de projetar antecipadamente e sem considerar se outros módulos definiram interfaces semelhantes antes.
Desta forma, os problemas de design de interface na programação orientada a objetos tradicional são perfeitamente evitados.
3. Simultaneidade e multithreading
3.1 Gorotina
Para qualquer linguagem excelente, a capacidade de lidar com processamento simultâneo é a chave para determinar seus méritos. Na linguagem Go, o processamento simultâneo é implementado por meio do Goroutine.
func say(s string) {
fmt.Println(s)
}
func main() {
//通过 go 关键字新开一个协程
go say("world")
say("hello")
}
A linguagem Go não possui tantos bloqueios quanto Java para limitar o acesso simultâneo aos recursos e fornece Mutex apenas para operações de sincronização.
//给类SafeCounter添加锁
type SafeCounter struct {
v map[string]int
mux sync.Mutex
}
// Inc 增加给定 key 的计数器的值。
func (c *SafeCounter) Inc(key string) {
//给该对象上锁
c.mux.Lock()
// Lock 之后同一时刻只有一个 goroutine 能访问 c.v
c.v[key]++
//解锁
c.mux.Unlock()
}
3.2 Canal
A comunicação entre várias corrotinas é feita por meio de Canal, que pode ser funcionalmente análogo à palavra-chave volátil do Java.
ch := make(chan int)
ch
Declare um canal do tipo int e os dados int poderão ser comunicados entre duas corrotinas .
A transmissão de dados é realizada através do Canal.
ch <- v // 将 v 发送至信道 ch。
v := <-ch // 从 ch 接收值并赋予 v。
package main
import "fmt"
func sum(s []int, c chan int) {
sum := 0
for _, v := range s {
sum += v
}
c <- sum // 将和送入 c
}
//对于main方法来说 相当于就是开启了一个协程
func main() {
s := []int{7, 2, 8, -9, 4, 0}
c := make(chan int)
//通过go关键字开启两个协程 将chaneel当做参数传入
go sum(s[:len(s)/2], c)
go sum(s[len(s)/2:], c)
//通过箭头方向获取或传入信息
x, y := <-c, <-c // 从 c 中接收
fmt.Println(x, y, x+y)
}
4. Tratamento de erros
Erro 4.1
O mecanismo de tratamento de erros da linguagem Go é muito simples e claro. Não há necessidade de aprender conceitos, funções e tipos complexos. A linguagem Go define um padrão para tratamento de erros, ou seja, error
a definição da interface é muito simples. :
type error interface {
Error() string
}
Apenas um Error()
método é declarado, que retorna uma mensagem de erro do tipo string. Para a maioria das funções ou métodos de classe, se você quiser retornar um erro, você pode basicamente defini-lo como o seguinte padrão - retornar o tipo de erro como o segundo parâmetro:
func Foo(param int) (n int, err error) {
// ...
}
Então, ao chamar a função/método que retorna informações de erro, basta escrever o código de processamento de acordo com o seguinte modelo "Declaração Wei Shu":
n, err := Foo(0)
if err != nil {
// 错误处理
} else{
// 使用返回值 n
}
Muito simples e elegante.
4.2 adiar
defer é usado para garantir que após a execução de um método, as instruções em defer serão executadas independentemente de o resultado da execução ser bem-sucedido ou não. Semelhante ao uso try..catch..finalmente em Java. Por exemplo, no processamento de arquivos, o fluxo de arquivos deve ser fechado independentemente de o resultado ser bem-sucedido ou não.
func ReadFile(filename string) ([]byte, error) {
f, err := os.Open(filename)
if err != nil {
return nil, err
}
//无论结果如何 都要关闭文件流
defer f.Close()
var n int64 = bytes.MinRead
if fi, err := f.Stat(); err == nil {
if size := fi.Size() + bytes.MinRead; size > n {
n = size
}
}
return readAll(f, n)
}
4.3 pânico
Não existem muitas classes de exceção na linguagem Go. Ao contrário do Java, existem tipos de erro como Error e Exception. É claro que não existem instruções try..catch.
Pânico significa que ocorre um erro durante a operação do programa. Se o erro não for detectado, o sistema travará e sairá. Por exemplo, um simples pânico: a := 1/0
.
Isso causará pânico: número inteiro dividido por zero.
A primeira linha indica a rotina com o problema, a segunda linha é o pacote e a função onde o código do problema está localizado, a terceira linha é a localização específica do código do problema e a última linha é o status de saída do programa. informações podem ajudá-lo a localizar rapidamente o problema e resolvê-lo.
4.4 recuperar
Quando há um erro previsível e você não deseja que o programa trave e saia, você pode usar a instrução recuperar() para capturar o pânico não tratado. recuperar deve ser colocado na instrução defer, e a instrução deve estar na frente do método para evitar que o sistema saia de forma anormal se a instrução defer não puder ser executada.
package main
import (
"fmt"
)
func divide() {
//通过defer,确保该方法只要执行完毕都要执行该匿名方法
defer func() {
//进行异常捕获
if err := recover(); err != nil {
fmt.Printf("Runtime panic caught: %v\n", err)
}
}()
var i = 1
var j = 0
k := i / j
fmt.Printf("%d / %d = %d\n", i, j, k)
}
func main() {
divide()
fmt.Println("divide 方法调用完毕,回到 main 函数")
}
Pode-se observar que embora ocorra uma exceção, após usarmos retrieve() para capturá-la, o sistema não irá travar e sair, apenas encerrará o método. A fmt.Printf("%d / %d = %d\n", i, j, k)
instrução não foi executada porque ocorreu uma exceção quando o código foi executado na etapa anterior, fazendo com que o método terminasse mais cedo.
4 recuperar
Quando há um erro previsível e você não deseja que o programa trave e saia, você pode usar a instrução recuperar() para capturar o pânico não tratado. recuperar deve ser colocado na instrução defer, e a instrução deve estar na frente do método para evitar que o sistema saia de forma anormal se a instrução defer não puder ser executada.
package main
import (
"fmt"
)
func divide() {
//通过defer,确保该方法只要执行完毕都要执行该匿名方法
defer func() {
//进行异常捕获
if err := recover(); err != nil {
fmt.Printf("Runtime panic caught: %v\n", err)
}
}()
var i = 1
var j = 0
k := i / j
fmt.Printf("%d / %d = %d\n", i, j, k)
}
func main() {
divide()
fmt.Println("divide 方法调用完毕,回到 main 函数")
}
Pode-se observar que embora ocorra uma exceção, após usarmos retrieve() para capturá-la, o sistema não irá travar e sair, apenas encerrará o método. A fmt.Printf("%d / %d = %d\n", i, j, k)
instrução não foi executada porque ocorreu uma exceção quando o código foi executado na etapa anterior, fazendo com que o método terminasse mais cedo.
5. Resumo
Através do estudo acima, você pode inicialmente entender a sintaxe básica do go para fins de uso, mas este artigo por si só não é suficiente para aprender o go. Por exemplo, uma das maiores vantagens do go, a "co-rotina", não é detalhada devido ao objetivo do artigo. Os alunos interessados podem continuar aprendendo.