Тридцать минут, чтобы начать работу с базовым Go (Java Kid Edition)

Предисловие

Определение языка Go

Go(又称 Golang)是 Google 的 Robert Griesemer,Rob Pike 及 Ken Thompson 开发的一种静态、强类型、编译型语言。Go 语言语法与 C 相近,但功能上有:内存安全,GC,结构形态及 CSP-style 并发计算

Область применения

Эта статья подойдет новичкам, которые изучили другие объектно-ориентированные языки (Java, Php), но не изучали язык Go. В статье в основном объясняются четыре аспекта базового синтаксиса языка Go, объектно-ориентированного программирования, параллелизма и ошибок при сравнении функций Go и Java .

1. Базовая грамматика

Основной синтаксис языка Go в основном аналогичен синтаксису обычных языков программирования. Разница заключается в способе объявления переменных. Концепции и функции массивов, срезов и словарей не такие же, как в Java. в Java все можно использовать по аналогии.

1.1 Переменные, константы, нулевые и нулевые значения, методы, пакеты, видимость, указатели

1.1.1 Объявление переменной

В языке Go есть два способа

1. Используйте varобъявление ключевых слов. Следует отметить, что в отличие от большинства строго типизированных языков, объявленный тип переменной в языке Go расположен после имени переменной. Точка с запятой в конце оператора Go не требуется.

var num int

var result string = "this is result"

2. Используйте :=присваивание.

num := 3Эквивалентноvar num int = 3

Тип переменной будет соответствовать значению в правой части. Например, «3» соответствует int, «3.0» соответствует float64, а «result» соответствует строке.

1.1.2 Постоянное объявление

Используйте constдля объявления константы. Константу нельзя изменить после ее объявления.

const laugh string = "go"

1.1.3 ноль и нулевое значение

Объявляйте только неназначенные переменные, значение которых равно нулю. Аналогично « null» в Java .

Объявлениям переменных без явных начальных значений присваивается их нулевое значение .

Нулевое значение:

  • Числовой тип 0:

  • Логический тип false:

  • Строка ""(пустая строка).

1.1.4 Методы и пакеты

Определение методов в Go

Используйте ключевое слово func для определения метода, за которым следует имя метода, затем параметры и возвращаемое значение (если оно есть, то при отсутствии возвращаемого значения оно не будет записано).

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;
}

Несколько возвращаемых значений

Одно большое различие между функциями Go и другими языками программирования заключается в том, что они поддерживают несколько возвращаемых значений, что очень полезно при обработке ошибок программы. Например, если приведенная выше addфункция поддерживает только сложение неотрицательных целых чисел, будет сообщено об ошибке, если будет передано отрицательное число.

//返回值只定义了类型 没有定义返回参数
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)
}

Параметры переменной длины

func myfunc(numbers ...int) {
    for _, number := range numbers {
        fmt.Println(number)
    }
}

slice := []int{1, 2, 3, 4, 5}
//使用...将slice打碎传入
myfunc(slice...)

Пакеты и видимость

В языке Go, будь то переменные, функции или атрибуты класса и методы-члены, их видимость основана на размерности пакета, а не как в традиционном программировании, где видимость атрибутов класса и методов-членов инкапсулируется в класс, к которому они относятся. они принадлежат private, измените его видимость с помощью этих ключевых слов protectedи .public

Язык Go не предоставляет эти ключевые слова. Независимо от того, являются ли они переменными, функциями или атрибутами и методами-членами пользовательских классов, их видимость определяется на основе их первых букв, если имя переменной, имя атрибута, имя функции или пишутся с заглавной буквы . первую букву имени метода , вы можете напрямую получить доступ к этим переменным, свойствам, функциям и методам вне пакета, в противном случае доступ к ним возможен только внутри пакета. Таким образом, видимость атрибутов класса языка Go и методов-членов находится в пакете. уровень, а не первый класс.

Если domainв папке с именем есть три файла .go, то эти три файла packageдолжны быть domain. Среди них файл, в котором находится основной метод записи программы, и пакет.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 Указатели

Тем, кто изучал язык C, указатели вполне знакомы. Я понимаю, что указатель на самом деле представляет собой фактическое шестнадцатеричное значение адреса в памяти. Значение ссылочной переменной используется для извлечения соответствующего реального значения из памяти.

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 Условия, циклы, ветвления

1.2.1 Условия

В основном то же самое, что и на языке 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 Цикл

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 Филиал

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 Массивы, срезы, словари

1.3.1 Массив

Функция массива аналогична языку Java, длина неизменяема, можно использовать многомерные массивы или значения можно сохранять или получать через массивы[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

Массивы относительно просты в использовании, но есть проблема, которую сложно решить: фиксированная длина .

Например, когда нам нужна структура данных в программе для хранения всех полученных пользователей, потому что количество пользователей меняется со временем, но длину массива изменить нельзя, поэтому массив не подходит для хранения данных, длина которых меняется. . Поэтому вышеперечисленные проблемы решаются с помощью слайсов на языке Go.

1.3.2 Нарезка

Нарезка — это совершенно новая концепция по сравнению с Java. В Java для структур хранения данных переменной длины вы можете использовать интерфейс List для выполнения операций, например ArrayList и LinkList. Эти интерфейсы могут добавлять и получать данные в любое время, и длина не ограничена. Однако в Go такого интерфейса нет. Вместо этого хранение данных переменной длины осуществляется с помощью срезов .

Самая большая разница между срезами и массивами заключается в том, что срезам не нужно объявлять длину. Но срезы неотделимы от массивов. Массив можно рассматривать как базовый массив среза, а срез можно рассматривать как ссылку на непрерывный фрагмент массива. Срезы можно создавать, используя только часть массива или весь массив, и даже возможно создать срез, превышающий размер базового массива:

длина, вместимость

切片的长度就是它所包含的元素个数。

切片的容量是从它的第一个元素开始数,到其底层数组元素末尾的个数。

切片 s 的长度和容量可通过表达式 len(s) 和 cap(s) 来获取。

Длина среза функционально аналогична методу size() List в Java, то есть длина среза воспринимается через len(slice), а len(slice) может быть зациклена для динамического управления конкретным содержимым в кусочек. Емкость срезов не особо используется в реальной разработке, просто поймите ее концепцию.

Создание фрагментов

//声明一个数组
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)

Динамическая работа срезов

//使用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:]

Распространенные сценарии нарезки

Смоделируйте вышеупомянутую проблему, используя решение нарезки.

//声明切片
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 Словарь

Словарь, также известный как «пара ключ-значение» или «ключ-значение», представляет собой широко используемую структуру данных. В Java существуют различные интерфейсы Map, наиболее часто используемые из них — HashMap и т. д. В Go словари используются для хранения пар ключ-значение. Словари неупорядочены, поэтому порядок данных не гарантируется в зависимости от порядка сложения.

Объявление словаря и инициализация

//string为键类型,int为值类型
maps := map[string]int{
  "java" : 1,
  "go" : 2,
  "python" : 3,
}

//还可以通过make来创建字典 100为其初始容量 超出可扩容
maps = make(map[string]int, 100)

Сценарии использования словаря

//直接使用
fmt.Println(maps["java"]) //1

//赋值
maps["go"] = 4

//取值 同时判断map中是否存在该键 ok为bool型
value, ok := maps["one"] 
if ok { // 找到了
  // 处理找到的value 
}

//删除
delete(testMap, "four")

2. Объектно-ориентированное программирование

2.1 Классы на языке Go

Как мы все знаем, в объектно-ориентированных языках класс должен иметь три структуры: атрибуты, конструкторы и методы-члены, и язык Go не является исключением.

2.1.1 Объявление и инициализация класса

В языке Go нет четкого понятия класса. Только structключевые слова можно функционально сравнить с «классами» в объектно-ориентированных языках. Например, чтобы определить класс учащихся, вы можете сделать это:

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 Методы-члены

Объявление метода-члена в Go не такое, как в других языках. Взяв в качестве примера класс Student,

//在方法名前,添加对应的类,即可认为改方法为该类的成员方法。
func (s Student) GetName() string  {
    return s.name
}

//注意这里的Student是带了*的 这是因为在方法传值过程中 存在着值传递与引用传递 即指针的概念 当使用值传递时 编译器会为该参数创建一个副本传入 因此如果对副本进行修改其实是不生效的 因为在执行完此方法后该副本会被销毁 所以此处应该是用*Student 将要修改的对象指针传入 修改值才能起作用
func (s *Student) SetName(name string) {
    //这里其实是应该使用(*s).name = name,因为对于一个地址来说 其属性是没意义的 不过这样使用也是可以的 因为编译器会帮我们自动转换
    s.name = name
}

2.2 Интерфейс

Интерфейсы играют жизненно важную роль в языке Go. Если горутина и канал являются краеугольными камнями, поддерживающими модель параллелизма языка Go, то интерфейсы являются краеугольным камнем всей системы типов языка Go . Интерфейс языка Go — это не просто интерфейс. Давайте шаг за шагом рассмотрим особенности интерфейса языка Go.

2.2.1 Традиционная реализация интрузивного интерфейса

Подобно реализации классов, концепция интерфейса языка Go полностью отличается от концепций интерфейса, представленных в других языках. Если взять в качестве примера Java и PHP, то интерфейсы в основном существуют как контракты между различными классами, и реализация контракта является обязательной. Это отражается в конкретных деталях: если класс реализует определенный интерфейс, он должен реализовать все объявленные методы. , это называется "исполнение контракта":

// 声明一个'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;
    }
}

В это время, если существует другой интерфейс iTemplate2, который объявляет iTemplateметод интерфейса, точно такой же, как , даже с тем же именем iTemplate, но расположенный в другом пространстве имен, компилятор также будет думать, что указанный выше класс Templateтолько реализует iTemplate, а не реализует. iTemplate2интерфейс.

В нашем предыдущем понимании это считается само собой разумеющимся. Будь то наследование между классами или реализация между классами и интерфейсами, в языках с единым наследованием, таких как Java и PHP, существует строгая иерархическая связь. Класс может наследовать только напрямую от класса. родительский класс, и класс может реализовывать только указанный интерфейс. Если явно не объявлено, что он наследуется от родительского класса или реализует определенный интерфейс, то этот класс не имеет ничего общего с родительским классом или отношением интерфейса.

Мы называем этот тип интерфейса интрузивным интерфейсом . Так называемый «интрузивный» означает, что класс реализации должен явно заявить, что он реализует интерфейс. Хотя этот метод реализации достаточно ясен и прост, все же существуют некоторые проблемы, особенно при проектировании стандартной библиотеки, поскольку стандартная библиотека должна включать в себя проектирование интерфейса. Требователем интерфейса является класс бизнес-реализации, и только класс бизнес-реализации может это сделать. Только тогда мы знаем, какие методы нужно определить. До этого был спроектирован интерфейс стандартной библиотеки. Мы можем либо реализовать его по согласованному интерфейсу, то нужно. Проектируйте сами. Проблема здесь в том, что проектирование и бизнес-реализация разделены. Дизайнеры интерфейсов не всегда могут предсказать, какие функции будет реализовывать бизнес-сторона, что приводит к разрыву между проектированием и реализацией.

Чрезмерное проектирование интерфейса приведет к тому, что некоторые объявленные классы реализации методов станут совершенно ненужными. Если дизайн будет слишком простым, он не будет соответствовать потребностям бизнеса. Это действительно проблема, и обсуждать ее без пользователя бессмысленно. сценарии использования. Возьмем в качестве примера интерфейс SessionHandlerInterface . Методы интерфейса, объявленные в этом интерфейсе, следующие:

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
}

Пользовательский менеджер сеансов должен реализовать этот интерфейс, то есть он должен реализовать все методы, объявленные в этом интерфейсе. Однако при фактическом развитии бизнеса некоторые методы фактически не нуждаются в реализации. Например, если мы используем. Redis или Memcached как сессия Что касается памяти, то они сами содержат механизм повторного использования по истечении срока действия, поэтому gcметод вообще не нуждается в реализации. Например, closeдля большинства драйверов метод бессмыслен.

Именно из-за такого неразумного дизайна вам придется бороться со следующими двумя проблемами при написании каждого интерфейса в библиотеке классов PHP (Java аналогична):

  1. Какие интерфейсные методы необходимо объявить для интерфейса?

  2. Если несколько классов реализуют один и тот же метод интерфейса, как следует спроектировать интерфейс? Например, приведенный выше SessionHandlerInterface, необходимо ли разделить его на несколько более разделенных интерфейсов, чтобы адаптироваться к потребностям различных классов реализации?

Далее давайте посмотрим, как интерфейс языка Go позволяет избежать этих проблем.

2.2.2 Реализация интерфейса языка Go

В языке Go реализация интерфейса классом аналогична наследованию подкласса от родительского класса. Нет implementтакого ключевого слова, которое бы явно определяло, какой интерфейс реализует класс. Классу нужно только реализовать все. методы, необходимые интерфейсу, мы говорим, что этот класс реализует интерфейс .

Например , мы определяем класс и Fileреализуем Read()четыре метода: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

Предположим, у нас есть следующий интерфейс (язык Go использует ключевое слово для interfaceобъявления интерфейса, чтобы показать отличие от типа структуры, а фигурные скобки содержат набор методов, которые необходимо реализовать):

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 
}

Хотя Fileкласс не реализует эти интерфейсы явно или даже не знает о существовании этих интерфейсов, мы говорим, что Fileкласс реализует эти интерфейсы, поскольку Fileкласс реализует методы, объявленные всеми вышеперечисленными интерфейсами. Когда набор методов-членов класса содержит все методы, объявленные интерфейсом, другими словами, если набор методов интерфейса является подмножеством набора методов-членов определенного класса, мы рассматриваем класс для реализации интерфейса.

По сравнению с Java и PHP мы называем этот интерфейс языка Go неинтрузивным интерфейсом , поскольку связь реализации между классом и интерфейсом не объявляется явно, а оценивается системой на основе набора методов из них. Это имеет два преимущества:

  • Во-первых, стандартной библиотеке языка Go не нужно рисовать древовидную диаграмму наследования/реализации библиотеки классов. В языке Go дерево наследования класса не имеет смысла. Вам нужно только знать, какие методы реализует класс. того, что делает каждый метод, будет достаточно.

  • Во-вторых, при определении интерфейса вам нужно заботиться только о том, какие методы вам следует предоставить. Вам больше не нужно беспокоиться о том, насколько подробным должен быть интерфейс. Для этого нет необходимости вводить пакет, в котором находится интерфейс. реализовать интерфейс. Интерфейс определяется пользователем. Определите его по требованию, нет необходимости проектировать заранее и нет необходимости учитывать, определили ли другие модули подобные интерфейсы ранее.

Таким образом, можно полностью избежать проблем проектирования интерфейсов в традиционном объектно-ориентированном программировании.

3. Параллелизм и многопоточность

3.1 Горутина

Для любого превосходного языка способность обрабатывать параллельную обработку является ключом к определению его достоинств. В языке Go параллельная обработка реализована через Goroutine.

func say(s string) {
	fmt.Println(s)
}

func main() {
    //通过 go 关键字新开一个协程
	go say("world")
	say("hello")
}

Язык Go не имеет такого количества блокировок, как Java, для ограничения одновременного доступа к ресурсам, и предоставляет только Mutex для операций синхронизации.

//给类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 Канал

Связь между несколькими сопрограммами осуществляется через канал, который функционально можно сравнить с ключевым словом voluty в Java.

ch := make(chan int)chОбъявите канал типа int, и данные int могут передаваться между двумя сопрограммами .

Передача данных осуществляется через Канал.

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. Обработка ошибок

4.1 ошибка

Механизм обработки ошибок языка Go очень прост и понятен. Нет необходимости изучать сложные понятия, функции и типы. Язык Go определяет стандартный шаблон обработки ошибок, то есть errorопределение интерфейса очень простое. :

type error interface { 
    Error() string 
}

Error()Объявлен только один метод, который возвращает сообщение об ошибке строкового типа. Для большинства функций или методов класса, если вы хотите вернуть ошибку, вы можете определить ее по следующему шаблону — вернуть тип ошибки в качестве второго параметра:

func Foo(param int) (n int, err error) { 
    // ...
}

Затем при вызове функции/метода, возвращающего информацию об ошибке, просто напишите код обработки в соответствии со следующим шаблоном «Заявление Вэй Шу»:

n, err := Foo(0)

if err != nil { 
    // 错误处理 
} else{
    // 使用返回值 n 
}

Очень просто и элегантно.

4.2 отложить

defer используется для того, чтобы гарантировать, что после выполнения метода операторы defer будут выполнены независимо от того, является ли результат выполнения успешным или нет. Аналогично использованию try..catch..finally в Java. Например, при обработке файлов поток файлов должен быть закрыт независимо от того, успешен результат или нет.

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 паника

В языке Go не так много классов исключений. В отличие от Java, есть такие типы ошибок, как Error и Exception. Конечно, операторов try..catch нет.

Паника означает, что во время работы программы возникает ошибка. Если ошибку не обнаружить, это приведет к сбою и завершению работы системы. Например, простая паника: a := 1/0.

Это вызовет панику: целое число разделить на ноль.

изображение.png

Первая строка указывает сопрограмму с проблемой, вторая строка — пакет и функция, в которой находится код проблемы, третья строка — конкретное расположение кода проблемы, а последняя строка — статус выхода программы. Информация может помочь вам быстро найти проблему и устранить ее.

4.4 восстановить

Если произошла предсказуемая ошибка и вы не хотите, чтобы программа аварийно завершилась и завершилась, вы можете использовать оператор восстановления(), чтобы перехватить необработанную панику. восстановление следует поместить в оператор defer, и этот оператор должен находиться в начале метода, чтобы избежать аварийного завершения работы системы, если оператор defer не может быть выполнен.

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 函数")
}

изображение.png

Видно, что хотя исключение и произойдет, после того, как мы воспользуемся функцией восстановления() для его захвата, система не выйдет из строя и не выйдет из строя, а просто завершит метод. Оператор fmt.Printf("%d / %d = %d\n", i, j, k)не был выполнен, поскольку при выполнении кода до предыдущего шага возникло исключение, что привело к преждевременному завершению метода.
4 выздороветь

Если произошла предсказуемая ошибка и вы не хотите, чтобы программа аварийно завершилась и завершилась, вы можете использовать оператор восстановления(), чтобы перехватить необработанную панику. восстановление следует поместить в оператор defer, и этот оператор должен находиться в начале метода, чтобы избежать аварийного завершения работы системы, если оператор defer не может быть выполнен.

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 函数")
}

Видно, что хотя исключение и произойдет, после того, как мы воспользуемся функцией восстановления() для его захвата, система не выйдет из строя и не выйдет из строя, а просто завершит метод. Оператор fmt.Printf("%d / %d = %d\n", i, j, k)не был выполнен, поскольку при выполнении кода до предыдущего шага возникло исключение, что привело к преждевременному завершению метода.

5. Резюме

Благодаря приведенному выше изучению вы сможете изначально понять базовый синтаксис go для целей его использования, но одной этой статьи недостаточно, чтобы изучить go. Например, одно из самых больших преимуществ Go, «сопрограмма», не рассматривается подробно в связи с целью статьи. Заинтересованные студенты могут продолжить обучение.

Я решил отказаться от открытого исходного кода Hongmeng Ван Чэнлу, отец Hongmeng с открытым исходным кодом: Hongmeng с открытым исходным кодом — единственное мероприятие в области промышленного программного обеспечения, посвященное архитектурным инновациям в области базового программного обеспечения в Китае: выпущен OGG 1.0, Huawei предоставляет весь исходный код. Google Reader убит «горой кодового дерьма» Официально выпущена Fedora Linux 40 Бывший разработчик Microsoft: производительность Windows 11 «смехотворно плоха» Ма Хуатэн и Чжоу Хунъи пожимают друг другу руки, чтобы «устранить обиды» Известные игровые компании издали новые правила : свадебные подарки сотрудников не должны превышать 100 000 юаней Ubuntu 24.04 LTS официально выпущена Pinduoduo был приговорен к недобросовестной конкуренции Компенсация в размере 5 миллионов юаней
{{o.name}}
{{м.имя}}

рекомендация

отmy.oschina.net/u/4090830/blog/11054982
рекомендация