Thirty minutes to get started with basic Go (Java Kid Edition)

Preface

Go language definition

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

Scope of application

This article is suitable for beginners who have learned other object-oriented languages ​​​​(Java, Php), but have not learned Go language. The article mainly explains the four aspects of Go language's basic syntax, object-oriented programming, concurrency and errors from the comparison of the functions of Go and Java .

1. Basic grammar

The basic syntax of Go language is basically similar to that of conventional programming languages. The difference is the way to declare variables. The concepts and functions of arrays, slices, and dictionaries are not the same as those of Java. However, these data structures in Java can all be used by analogy. Used in Go.

1.1 Variables, constants, nil and zero values, methods, packages, visibility, pointers

1.1.1 Variable declaration

There are two ways in Go language

1. Use varkeyword declaration, and it should be noted that unlike most strongly typed languages, the declared variable type in Go language is located after the variable name. There is no need for a semicolon at the end of a Go statement.

var num int

var result string = "this is result"

2. Use :=assignment.

num := 3Equivalent tovar num int = 3

The type of the variable will be matched according to the value on the right side. For example, "3" will match int, "3.0" will match float64, and "result" will match string.

1.1.2 Constant declaration

Use constto declare a constant. A constant cannot be changed after it is declared.

const laugh string = "go"

1.1.3 nil and zero value

Only declare unassigned variables, whose value is nil. Similar to " null" in java .

Variable declarations without explicit initial values ​​are assigned their zero value .

The zero value is:

  • The numerical type is 0,

  • The Boolean type is false,

  • String is ""(empty string).

1.1.4 Methods and packages

Definition of methods in Go

Use the func keyword to define a method, followed by the method name, then parameters, and return value (if any, if there is no return value, it will not be written).

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

Multiple return values

One big difference between Go functions and other programming languages ​​is that they support multiple return values, which is very useful when handling program errors. For example, if the above addfunction only supports the addition of non-negative integers, an error will be reported if a negative number is passed in.

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

Variable length parameters

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

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

Packages and Visibility

In the Go language, whether it is variables, functions, or class attributes and member methods, their visibility is based on the package dimension, rather than like traditional programming, where the visibility of class attributes and member methods is encapsulated in the class to which they belong. private, and then modify its visibility with these keywords, protectedand .public

The Go language does not provide these keywords. Whether they are variables, functions, or attributes and member methods of custom classes, their visibility is determined based on the case of their first letters. If the variable name, attribute name, function name Or capitalize the first letter of the method name , you can directly access these variables, properties, functions and methods outside the package, otherwise they can only be accessed within the package. Therefore, the visibility of Go language class attributes and member methods is at the package level, and Not class one.

If domainthere are three .go files in a folder named, then the three files packageshould be domain. Among them, the file where the program's entry main method is located, and the package ismain

//定义了此文件属于 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 Pointers

For those who have studied C language, pointers are quite familiar. The pointer I understand is actually an actual hexadecimal address value in the memory. The value of the reference variable is used to retrieve the corresponding real value from the memory. .

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 Conditions, loops, branches

1.2.1 Conditions

Basically the same as if in Java language

// 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 Loop

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 Branch

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 Arrays, slices, dictionaries

1.3.1 Array

The array function is similar to the Java language, the length is immutable, and multi-dimensional arrays can be used, or values ​​can be stored or obtained through 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 are relatively simple to use, but there is a problem that is difficult to solve: fixed length .

For example, when we need a data structure in the program to store all the users obtained, because the number of users changes with time, but the length of the array cannot be changed, so the array is not suitable for storing data whose length changes. Therefore, the above problems are solved by using slices in Go language.

1.3.2 Slicing

Slicing is a completely new concept compared to Java. In Java, for data storage structures of variable length, you can use the List interface to complete operations, such as ArrayList and LinkList. These interfaces can add and obtain data at any time, and there is no limit on the length. However, there is no such interface in Go. Instead, variable-length data length storage is accomplished through slices .

The biggest difference between slices and arrays is that slices do not need to declare the length. But slices are not unrelated to arrays. An array can be regarded as the underlying array of the slice, and the slice can be regarded as a reference to a continuous fragment of the array. Slices can be created using only a portion of an array or the entire array, and it is even possible to create a slice that is larger than the underlying array:

length, capacity

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

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

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

The length of the slice is functionally analogous to the size() of List in Java, that is, the length of the slice is perceived through len(slice), and len(slice) can be looped to dynamically control the specific content in the slice. The capacity of slices is not used much in actual development, just understand its concept.

Create slices

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

Dynamic operation of slices

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

Common scenarios for slicing

Simulate the above mentioned problem using slicing solution

//声明切片
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 Dictionary

Dictionary, also known as 'key-value pair' or 'key-value', is a commonly used data structure. There are various Map interfaces in Java, and the commonly used ones are HashMap, etc. In Go, dictionaries are used to store key-value pairs. Dictionaries are unordered, so the order of data is not guaranteed based on the order of addition.

Dictionary declaration and initialization

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

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

Dictionary usage scenarios

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

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

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

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

2. Object-oriented programming

2.1 Classes in Go language

As we all know, in object-oriented languages, a class should have three structures: attributes, constructors, and member methods, and Go language is no exception.

2.1.1 Class declaration and initialization

There is no clear concept of class in Go language. Only structkeywords can be functionally analogized to "classes" in object-oriented languages. For example, to define a student class, you can do this:

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 Member methods

Member method declaration in Go is not the same as in other languages. Taking the Student class as an example,

//在方法名前,添加对应的类,即可认为改方法为该类的成员方法。
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

Interfaces play a vital role in the Go language. If goroutine and channel are the cornerstones that support the Go language concurrency model, then interfaces are the cornerstone of the entire type system of the Go language . The interface of Go language is not just an interface. Let us explore the interface features of Go language step by step.

2.2.1 Traditional intrusive interface implementation

Similar to the implementation of classes, the interface concept of Go language is completely different from the interface concepts provided in other languages. Taking Java and PHP as examples, interfaces mainly exist as contracts between different classes, and the implementation of the contract is mandatory. This is reflected in the specific details: if a class implements a certain interface, it must implement the interface. All methods declared, this is called "contract fulfillment":

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

At this time, if there is another interface iTemplate2that declares an iTemplateinterface method that is exactly the same as , even with the same name iTemplate, but is located in a different namespace, the compiler will also think that the above class Templateonly implements iTemplateand does not implement iTemplate2the interface.

This is taken for granted in our previous understanding. Whether it is inheritance between classes or implementation between classes and interfaces, in single inheritance languages ​​such as Java and PHP, there is a strict hierarchical relationship. A class can only directly inherit from a parent class, and a class can only implement a specified interface. If it is not explicitly declared to inherit from a parent class or implement a certain interface, then the class has nothing to do with the parent class or the interface. relation.

We call this kind of interface an intrusive interface . The so-called "intrusive" means that the implementation class must explicitly declare that it implements an interface. Although this implementation method is clear and simple enough, there are still some problems, especially when designing the standard library, because the standard library must involve interface design. The demander of the interface is the business implementation class, and only the business implementation class can be written specifically. Only then do we know which methods need to be defined. Before that, the interface of the standard library has been designed. We can either implement it according to the agreed interface. If there is no suitable interface, we need to design it ourselves. The problem here is the interface. Design and business implementation are separated. Interface designers cannot always predict what functions the business side will implement, which results in a disconnect between design and implementation.

Over-design of the interface will cause some declared method implementation classes to be completely unnecessary. If the design is too simple, it will not meet the needs of the business. This is indeed a problem, and it is meaningless to discuss these without user usage scenarios. Take the SessionHandlerInterface interface that comes with PHP as an example. The interface methods declared by this interface are as follows:

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
}

The user-defined Session manager needs to implement this interface, that is, it must implement all the methods declared by this interface. However, when actually doing business development, some methods do not actually need to be implemented. For example, if we use Redis or Memcached as the Session As for memories, they themselves contain an expiration recycling mechanism, so gcthe method does not need to be implemented at all. For example, closethe method is meaningless for most drivers.

It is precisely because of this unreasonable design that you need to struggle with the following two issues when writing each interface in the PHP class library (Java is similar):

  1. What interface methods need to be declared for an interface?

  2. If multiple classes implement the same interface method, how should the interface be designed? For example, the above one SessionHandlerInterface, is it necessary to split it into multiple more subdivided interfaces to adapt to the needs of different implementation classes?

Next, let's take a look at how the Go language interface avoids these problems.

2.2.2 Go language interface implementation

In the Go language, the implementation of an interface by a class is the same as the inheritance of a subclass from a parent class. There is no implementkeyword like this to explicitly declare which interface the class implements. A class only needs to implement all the methods required by an interface. , we say that this class implements the interface .

For example, we define a class and Fileimplement Read()four methods: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

Suppose we have the following interface (Go language uses the keyword to interfacedeclare the interface to show the difference from the structure type, and the curly braces contain the set of methods to be implemented):

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 
}

Although Filethe class does not explicitly implement these interfaces, or even knows about the existence of these interfaces, we say that Filethe class implements these interfaces because Filethe class implements the methods declared by all the above interfaces. When the member method set of a class contains all the methods declared by an interface, in other words, if the method set of an interface is a subset of the member method set of a certain class, we consider the class to implement the interface.

Compared with Java and PHP, we call this interface of the Go language a non-intrusive interface , because the implementation relationship between a class and an interface is not explicitly declared, but is judged by the system based on the method set of the two. This has two benefits:

  • First, the standard library of the Go language does not need to draw the inheritance/implementation tree diagram of the class library. In the Go language, the inheritance tree of the class is meaningless. You only need to know which methods the class implements and what each method does. Will suffice.

  • Secondly, when defining an interface, you only need to care about which methods you should provide. You no longer need to worry about how detailed the interface needs to be. There is no need to introduce the package where the interface is located in order to implement an interface. The interface is determined by the user. Define on demand, no need to design in advance, and no need to consider whether other modules have defined similar interfaces before.

In this way, the interface design problems in traditional object-oriented programming are perfectly avoided.

3. Concurrency and multi-threading

3.1 Goroutine

For any excellent language, the ability to handle concurrent processing is the key to determining its merits. In the Go language, concurrency processing is implemented through Goroutine.

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

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

The Go language does not have as many locks as Java to limit simultaneous access to resources, and only provides Mutex for synchronization operations.

//给类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 Channel

Communication between multiple coroutines is through Channel, which can be functionally analogized to Java's volatile keyword.

ch := make(chan int)chDeclare a Channel of type int, and int data can be communicated between two coroutines .

Data transmission is carried out through Channel.

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. Error handling

4.1 error

The Go language error handling mechanism is very simple and clear. There is no need to learn complex concepts, functions and types. The Go language defines a standard pattern for error handling, that is, errorthe interface. The definition of the interface is very simple:

type error interface { 
    Error() string 
}

Only one Error()method is declared, which returns an error message of string type. For most functions or class methods, if you want to return an error, you can basically define it as the following pattern - return the error type as the second parameter:

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

Then when calling the function/method that returns error information, just write the processing code according to the following "Wei Shu Statement" template:

n, err := Foo(0)

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

Very simple and elegant.

4.2 defer

defer is used to ensure that after a method is executed, the statements in defer will be executed regardless of whether the execution result is successful or not. Similar to try..catch..finally usage in Java. For example, in file processing, the file stream must be closed regardless of whether the result is successful or not.

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 panic

There are not many exception classes in the Go language. Unlike Java, there are error types such as Error and Exception. Of course, there are no try..catch statements.

Panic means that an error occurs during program operation. If the error is not caught, it will cause the system to crash and exit. For example, a simple panic: a := 1/0.

It will cause panic: integer divide by zero.

image.png

The first line indicates the coroutine with the problem, the second line is the package and function where the problem code is located, the third line is the specific location of the problem code, and the last line is the exit status of the program. This information can help you quickly Locate the problem and resolve it.

4.4 recover

When there is a foreseeable error and you do not want the program to crash and exit, you can use the recover() statement to capture the unhandled panic. recover should be placed in the defer statement, and the statement should be at the front of the method to avoid the system exiting abnormally if the defer statement cannot be executed.

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

image.png

It can be seen that although an exception will occur, after we use recover() to capture it, the system will not crash and exit, but will just end the method. The fmt.Printf("%d / %d = %d\n", i, j, k)statement was not executed because an exception occurred when the code was executed to its previous step, causing the method to end early.
4 recover

When there is a foreseeable error and you do not want the program to crash and exit, you can use the recover() statement to capture the unhandled panic. recover should be placed in the defer statement, and the statement should be at the front of the method to avoid the system exiting abnormally if the defer statement cannot be executed.

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

It can be seen that although an exception will occur, after we use recover() to capture it, the system will not crash and exit, but will just end the method. The fmt.Printf("%d / %d = %d\n", i, j, k)statement was not executed because an exception occurred when the code was executed to its previous step, causing the method to end early.

5. Summary

Through the above study, you can initially understand the basic syntax of go for the purpose of use, but this article alone is not enough to learn go. For example, one of the biggest advantages of go, "coroutine", is not elaborated in detail due to the purpose of the article. Interested students can continue to learn.

I decided to give up on open source Hongmeng. Wang Chenglu, the father of open source Hongmeng: Open source Hongmeng is the only architectural innovation industrial software event in the field of basic software in China - OGG 1.0 is released, Huawei contributes all source code Google Reader is killed by the "code shit mountain" Fedora Linux 40 is officially released Former Microsoft developer: Windows 11 performance is "ridiculously bad" Ma Huateng and Zhou Hongyi shake hands to "eliminate grudges" Well-known game companies have issued new regulations: employee wedding gifts must not exceed 100,000 yuan Ubuntu 24.04 LTS officially released Pinduoduo was sentenced for unfair competition Compensation of 5 million yuan
{{o.name}}
{{m.name}}

Guess you like

Origin my.oschina.net/u/4090830/blog/11054982