기본 Go를 시작하는 데 30분(Java Kid Edition)

머리말

Go 언어 정의

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

적용 범위

이 글은 다른 객체지향 언어(Java, Php)를 배웠지만 아직 Go 언어를 배우지 않은 초보자에게 적합합니다. 이 글 에서는 주로 Go와 Java의 기능을 비교하여 Go 언어의 기본 구문, 객체 지향 프로그래밍, 동시성 및 오류의 네 가지 측면을 설명합니다.

1. 기본 문법

Go 언어의 기본 구문은 기본적으로 기존 프로그래밍 언어와 유사하지만, 배열, 슬라이스, 사전의 개념과 기능은 Java와 다릅니다. Java에서는 모두 Go에서 사용할 수 있습니다.

1.1 변수, 상수, nil 및 0 값, 메소드, 패키지, 가시성, 포인터

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"는 string과 일치합니다.

1.1.2 상수 선언

상수를 선언하는 데 사용됩니다 const. 상수는 선언된 후에 변경할 수 없습니다.

const laugh string = "go"

1.1.3 nil과 0 값

값이 nil인 할당되지 않은 변수만 선언하세요. Java의 " null" 과 유사합니다 .

명시적인 초기값이 없는 변수 선언에는 0 값이 할당됩니다 .

0 값은 다음과 같습니다.

  • 숫자 유형은 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 함수와 다른 프로그래밍 언어의 큰 차이점 중 하나는 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 언어 클래스 속성 및 멤버 메소드의 가시성은 패키지에 있습니다. 레벨이고 클래스 1이 아닙니다.

domain이라는 폴더에 3개의 .go 파일이 있다면 그 3개의 파일은 이어야 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 언어를 공부한 사람들에게는 포인터가 꽤 익숙합니다. 제가 이해하는 포인터는 실제로 메모리에 있는 실제 16진수 주소 값입니다.

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 배열

배열 함수는 자바 언어와 유사하며, 길이가 불변이고, 다차원 배열을 사용할 수도 있고, 배열[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) 来获取。

슬라이스의 길이는 기능적으로 Java의 List의 size()와 유사합니다. 즉, 슬라이스의 길이는 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선언하는 또 다른 인터페이스가, 같은 이름이더라도 다른 네임스페이스에 위치하는 경우, 컴파일러는 위 클래스도 구현만 하고 구현하지 않는 것으로 간주하게 됩니다. 인터페이스.iTemplateiTemplateTemplateiTemplateiTemplate2

이는 이전 이해에서 당연한 것으로 간주됩니다. Java 및 PHP와 같은 단일 상속 언어에서는 클래스 간 상속이든 클래스와 인터페이스 간 구현이든 엄격한 계층 관계가 있습니다. 상위 클래스로부터 상속받거나 특정 인터페이스를 구현하도록 명시적으로 선언되지 않은 경우 클래스는 상위 클래스 또는 인터페이스 관계와 아무 관련이 없습니다.

이러한 종류의 인터페이스를 침입 인터페이스라고 합니다 . 소위 "침투"는 구현 클래스가 인터페이스를 구현한다고 명시적으로 선언해야 함을 의미합니다. 이 구현 방법은 충분히 명확하고 간단하지만, 특히 표준 라이브러리를 설계할 때 여전히 몇 가지 문제가 있습니다. 왜냐하면 표준 라이브러리에는 인터페이스 설계가 포함되어야 하기 때문입니다. 인터페이스의 수요자는 비즈니스 구현 클래스이고 비즈니스 구현 클래스만이 이를 수행할 수 있습니다. 그런 다음에야 어떤 메소드를 정의해야 하는지 알 수 있습니다. 그 전에 표준 라이브러리의 인터페이스가 설계되었습니다. 적합한 인터페이스가 없으면 이를 구현해야 합니다. 여기서 문제는 인터페이스입니다. 디자인과 비즈니스 구현이 분리되어 있습니다. 인터페이스 디자이너는 비즈니스 측에서 어떤 기능을 구현할지 항상 예측할 수 없으므로 디자인과 구현이 분리됩니다.

인터페이스를 과도하게 설계하면 선언된 일부 메서드 구현 클래스가 완전히 불필요해집니다. 설계가 너무 단순하면 비즈니스 요구 사항을 충족하지 못할 수 있으며 이는 실제로 문제가 되며 사용자 없이 이에 대해 논의하는 것은 의미가 없습니다. 사용 시나리오 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말합니다 . 클래스의 멤버 메서드 집합에 인터페이스가 선언한 모든 메서드가 포함되어 있는 경우, 즉 인터페이스의 메서드 집합이 특정 클래스의 멤버 메서드 집합의 하위 집합인 경우 인터페이스를 구현하는 클래스를 고려합니다.FileFile

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 채널

여러 코루틴 간의 통신은 채널을 통해 이루어지며 이는 기능적으로 Java의 휘발성 키워드와 유사할 수 있습니다.

ch := make(chan int)chint 유형의 채널을 선언하면 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) { 
    // ...
}

그런 다음 오류 정보를 반환하는 함수/메서드를 호출할 때 다음 "Wei Shu 문" 템플릿에 따라 처리 코드를 작성하면 됩니다.

n, err := Foo(0)

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

매우 간단하고 우아합니다.

4.2 연기

defer는 메소드가 실행된 후 실행 결과의 성공 여부에 관계없이 defer의 명령문이 실행되도록 하는 데 사용됩니다. Java의 try..catch..finally 사용법과 유사합니다. 예를 들어, 파일 처리에서는 결과의 성공 여부에 관계없이 파일 스트림을 닫아야 합니다.

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.

패닉이 발생합니다. 정수를 0으로 나눕니다.

이미지.png

첫 번째 줄은 문제가 있는 코루틴을 나타내고, 두 번째 줄은 문제 코드가 위치한 패키지와 함수, 세 번째 줄은 문제 코드의 구체적인 위치, 마지막 줄은 프로그램의 종료 상태를 나타냅니다. 정보는 문제를 신속하게 찾아 해결하는 데 도움이 될 수 있습니다.

4.4 복구

예측 가능한 오류가 있고 프로그램이 충돌하여 종료되는 것을 원하지 않는 경우, Recover() 문을 사용하여 처리되지 않은 패닉을 포착할 수 있습니다. Recover는 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

예외가 발생하더라도 Recover()를 사용하여 이를 캡처한 후에는 시스템이 충돌하고 종료되지 않고 메서드가 종료되는 것을 볼 수 있습니다. fmt.Printf("%d / %d = %d\n", i, j, k)코드가 이전 단계까지 실행될 때 예외가 발생하여 메서드가 일찍 종료되었기 때문에 문이 실행되지 않았습니다 .
4 회복하다

예측 가능한 오류가 있고 프로그램이 충돌하여 종료되는 것을 원하지 않는 경우, Recover() 문을 사용하여 처리되지 않은 패닉을 포착할 수 있습니다. Recover는 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 函数")
}

예외가 발생하더라도 Recover()를 사용하여 이를 캡처한 후에는 시스템이 충돌하고 종료되지 않고 메서드가 종료되는 것을 볼 수 있습니다. fmt.Printf("%d / %d = %d\n", i, j, k)코드가 이전 단계까지 실행될 때 예외가 발생하여 메서드가 일찍 종료되었기 때문에 문이 실행되지 않았습니다 .

5. 요약

위의 연구를 통해 처음에는 사용 목적에 맞는 go의 기본 구문을 이해할 수 있지만, go를 배우기에는 이 글만으로는 충분하지 않습니다. 예를 들어 go의 가장 큰 장점 중 하나인 '코루틴'은 글의 목적상 자세히 설명하지 않고, 관심 있는 학생들은 계속해서 학습할 수 있습니다.

오픈 소스 Hongmeng을 포기하기로 결정했습니다 . 오픈 소스 Hongmeng의 아버지 Wang Chenglu: 오픈 소스 Hongmeng은 중국에서 유일하게 기초 소프트웨어 분야의 건축 혁신 산업 소프트웨어 행사입니다. OGG 1.0이 출시되고 Huawei는 모든 소스 코드를 제공합니다. 구글 리더가 '코드 똥산'에 죽는다 페도라 리눅스 40 정식 출시 전 마이크로소프트 개발자: 윈도우 11 성능이 ' 어처구니없을 정도로 나쁨' 마화텡과 저우홍이가 악수하며 '원한 해소' ​​유명 게임사들이 새로운 규정 발표 : 직원 결혼 선물은 100,000위안을 초과할 수 없습니다. Ubuntu 24.04 LTS 공식 출시 Pinduoduo는 부정 경쟁 혐의로 판결을 받았습니다. 보상금 500만 위안
{{o.이름}}
{{이름}}

추천

출처my.oschina.net/u/4090830/blog/11054982