The basic grammar of Golang

Hello, everyone. Today I will share with you the basic grammar of Go language. Please give me more advice, thank you.

The basic grammar content of Go language is divided into three chapters, and this paper is the second chapter

Contents of this chapter

  • channel
  • structural morphology
  • Pointer
  • Control statement

channel

introduce

Simply executing functions concurrently is meaningless. Functions need to exchange data between functions to reflect the significance of concurrent execution of functions. channel is the connection between them.

channel is a communication mechanism that allows one goroutine to send a specific value to another goroutine.

Channel in Go language is a special type. The channel is like a conveyor belt or queue. It always follows the First In First Out rule to ensure the order of sending and receiving data. Each channel is a conduit of a specific type, that is, when declaring a channel, you need to specify the element type for it.

Note: goroutine is a unique mechanism in go language, which can be understood as thread in go language. With goroutine, you can use the go keyword

The knowledge of go concurrency will be put into subsequent articles to share with you. Simply understand this knowledge point

use

channel declaration

channel is a type, a reference type. Syntax format:

var variable chan Element type

example

 var ch1 chan int   // Declare a channel that passes an integer
 var ch2 chan bool  // Declare a channel that passes Booleans
 var ch3 chan []int // Declare a channel that passes int slices    

Create channel

The channel is a reference type, and the null value of the channel type is nil.

The declared channel can only be used after being initialized with the make function.

make(chan Element type, Buffer size) // format

example

ch4 := make(chan int)
ch5 := make(chan bool)
ch6 := make(chan []int)

channel operation

The channel has three operations: send, receive and close.

Both sending and receiving use the < - symbol.

send out

Send a value to the channel

ch := make(chan int)
ch <- 100 // Send 100 to ch

receive

Receive values from a channel

x := <- ch // Receive from ch channel and assign x
<- ch // Receive from ch channel, ignore value 

close

Call the built-in close function to close the channel

close(ch)

The closed channel has the following characteristics:

  1. Sending a value to a closed channel will result in panic
  2. Receiving a closed channel will get the value until the channel is empty
  3. Performing a receive operation on a closed channel with no value will result in a corresponding type of zero value
  4. Closing a closed channel will cause panic

Note: the channel needs to be closed only when the receiver is notified that all data of goroutine has been sent. The channel can be recycled by the garbage collection mechanism. It is different from closing the file. Closing the file after the operation is necessary, but closing the channel is not necessary.

Unbuffered channel

Unbuffered channels are also called blocked channels

Unbuffered channels can only send values when someone receives them. Just as there is no express cabinet and collection point in your community, the courier must send this item to your hand when calling you. In short, the unbuffered channel must be received before sending.

The channel created with CH: = make (Chan int) is unbuffered

func main() {
    ch := make(chan int)
    ch <- 10
    fmt.Println("Sent successfully")
}   

The above code can be compiled, but there will be deadlock errors during execution

fatal error: all goroutines are asleep - deadlock!

One way is to enable a goroutine to receive values

func recv(c chan int) {
    ret := <-c
    fmt.Println("Received successfully", ret)
}
func main() {
    ch := make(chan int)
    go recv(ch) // Enable goroutine to receive values from the channel
    ch <- 10
    fmt.Println("Sent successfully")
}   

The sending operation on the unbuffered channel will be blocked until another goroutine performs the receiving operation on the channel. At this time, the value can be sent successfully, and the two goroutines will continue to execute. Conversely, if the receive operation is performed first, the receiver's goroutine will block until another goroutine sends a value on the channel.

The use of unbuffered channels for communication will result in the synchronization of the goroutine sent and received. Therefore, the unbuffered channel is also called the synchronous channel.

Buffered channel

As long as the capacity of the channel is greater than zero, the channel is a buffered channel. The capacity of the channel indicates the number of elements that can be stored in the channel. Just like the express cabinet in your community has only so many grids. When the grid is full, it will not fit, and it will be blocked. When someone else takes one, the courier can put one in it.

Specify the capacity of the channel when initializing the channel with the make function

func main() {
    ch := make(chan int, 1) // Create a buffered channel with a capacity of 1
    ch <- 10
    fmt.Println("Sent successfully")
}

Cycle value case from channel

func main() {
    ch1 := make(chan int)
    ch2 := make(chan int)
    // Turn on goroutine to send the number from 0 to 100 to ch1
    go func() {
        for i := 0; i < 100; i++ {
            ch1 <- i
        }
        close(ch1)
    }()
    // Turn on goroutine to receive the value from ch1 and send the square of the value to ch2
    go func() {
        for {
            i, ok := <-ch1 // After the channel is closed, the value is ok=false
            if !ok {
                break
            }
            ch2 <- i * i
        }
        close(ch2)
    }()
    // Receive print value from ch2 in main goroutine
    for i := range ch2 { // After the channel is closed, the for range loop will exit
        fmt.Println(i)
    }
}   

Unidirectional channel

Sometimes we will pass the channel as a parameter between multiple task functions. Many times, when we use the channel in different task functions, we will restrict it. For example, we can only send or receive the channel in the function.

One way channel case

func counter(out chan<- int) {
    for i := 0; i < 100; i++ {
        out <- i
    }
    close(out)
}

func squarer(out chan<- int, in <-chan int) {
    for i := range in {
        out <- i * i
    }
    close(out)
}
func printer(in <-chan int) {
    for i := range in {
        fmt.Println(i)
    }
}

func main() {
    ch1 := make(chan int)
    ch2 := make(chan int)
    go counter(ch1)
    go squarer(ch2, ch1)
    printer(ch2)
}  

Of which:

  1. Chan < - int is a channel that can only be sent, which can be sent but cannot be received;
  2. < - Chan int is a channel that can only receive, but cannot send.

It is possible to convert a two-way channel into a one-way channel in the function transfer parameter and any assignment operation, but the reverse is not possible.

Pointer

introduce

Different from the pointer in C/C + +, the pointer in Go language cannot be offset and operated. It is a safe pointer.

Pointer in Go language needs to know three concepts: pointer address, pointer type and pointer value.

Function parameters in Go language are value copies. When we want to modify a variable, we can create a pointer variable pointing to the address of the variable.

Passing data uses pointers instead of copying data.

The pointer operation in Go language is very simple. You only need to remember two symbols: & (take the address) and * (take the value according to the address).

Each variable has an address at runtime, which represents the location of the variable in memory. In Go language, the & character is placed in front of the variable to "take the address" the variable.

The value types in Go language (int, float, bool, string, array, struct) have corresponding pointer types, such as: * int, * int64, * string, etc.

When a pointer is defined and not assigned to any variable, its value is nil

use

The syntax for fetching variable pointers is as follows

var v int = 10
ptr := &v
fmt.Printf("v:%d ptr:%p\n", v, ptr)

c := *ptr // Pointer value (according to the pointer to the memory value)
fmt.Printf("c:%d\n", c)

v: Represents the variable of the address taken. ptr: the variable used to receive the address. The type of ptr is called the pointer type of int* Represents a pointer.

The program defines the address of an int variable num and prints it

Assign the address of a to the pointer p, and modify the value of a through p

func main() {
    var a int
    fmt.Println(&a) // Pointer address
    var p *int
    p = &a  // Equivalent to P: = & A
    *p = 20
    fmt.Println(a) // Output 20
}

Judgment of null pointer

func main() {
    var p *string
    fmt.Printf("p The value of is%v\n", p)
    if p != nil {
        fmt.Println("Non empty")
    } else {
        fmt.Println("Null value")
    }
}

structural morphology

introduce

A structure is an aggregated data type. It is an entity composed of zero or more values of any type. Each value is called a member of the structure.

Usually, a row corresponds to a structure member, with the member name before the type, but if the adjacent member types are the same, they can be merged into one row.

If the name of a structure member starts with a capital letter, the member is exported; This is determined by the Go language export rules. A structure may contain both exported and non exported members.

A struct type named S will no longer contain members of type S: because an aggregate value cannot contain itself. (the same restriction applies to arrays.)

The zero value of structure type is that each member is zero. Zero is usually the most reasonable default.

If the structure has no members, it is an empty structure. Write struct {}. It has a size of 0 and does not contain any information, but it is still valuable sometimes.

use

type Info struct {  // Create structure
    ID      int
    Name, Hobby  string
}
var info Info // Declaration structure

Members of structure variables can be accessed through point operators

info.Name = "Gunmen in Maoershan"

Take the address of the member and access it through the pointer

name := &info.Name
fmt.Println(*name)

If you want to modify the structure member inside the function, it is necessary to pass it in with a pointer; Because in Go language, all function parameters are passed in by value copy, the function parameters will no longer be the original variables when the function is called.

func UpdateInfo(i *info) {
    i.Hobby = "Travel"
}

Create and initialize a structure variable through a pointer and return the address of the structure

pp := &Point{1, 2}

It is equivalent to the following statement

pp := new(Point)
*pp = Point{1, 2}

Note: structures can also be compared. Two structures can use = = or= Operator. The equality comparison operator = = compares each member of two structs.

Control statement

if judgment

Single condition judgment

if condition {
    // do something
}

Multi condition judgment

if condition {
    
} else if condition {
    // do something
} else {
    // do something
}

if single condition is followed by a statement before making conditional judgment

if statement;condition{
    //do something
}

Multi conditional sentence judgment

if num := 78;num <= 50{
    fmt.Println("Number is less then 50")
} else if num >= 51 && num <= 100{
    fmt.Println("The number is between 51 and1 100")
} else{
    fmt.Println("The number is greater than 100")
}

for loop

There is only one way of loop in go language, for loop.

The first syntax format

for Loop variable initialization; Cycle conditions; Cyclic variable iteration {
    // Loop operation (statement)
}

for j := 1; j <= 10; j++ {
    // Circular execution statement
}

The second syntax format

for Cyclic judgment condition {
    // Circular execution statement
}

j := 1
for j <= 10 {
    j++
}

The third syntax format

for {
    // The circular execution statement is an infinite loop, which is usually used in conjunction with the break statement
}

Traversal syntax format for range

var names []string{"xiaoming", "xiaohong", "xiaojun"}
for _, name := range names {
    fmt.Println(name)
}

Use the goto statement to jump out of the for loop

func main() {
    for i := 0; i < 10; i++ {
        fmt.Println(i)
        if i == 5 {
            goto end
        }
    }
    end:
        fmt.Println("end")
}

output

0
1
2
3
4
5
end

switch

The first syntax format

func main() {
    num := 1
    switch num {
        case 1:
            fmt.Println("num=1")
        case 2:
            fmt.Println("num=2")
        case 3:
            fmt.Println("num=3")
        default:
            fmt.Println("Unmatched")
    }
}

The second syntax format

func main() {
    num := 1
    switch {
        case num == 1:
            fmt.Println("num=1")
        case num == 2:
            fmt.Println("num=2")
        case num == 3:
            fmt.Println("num=3")
        default:
            fmt.Println("Unmatched")
    }
}

The third syntax format

func main() {
    switch num := 1; {
        case num == 1:
            fmt.Println("num=1")
        case num == 2:
            fmt.Println("num=2")
        case num == 3:
            fmt.Println("num=3")
        default:
            fmt.Println("Unmatched")
    }
}

The fourth syntax format

Use the keyword fallthrough. By default, in the switch, each case will have a hidden break. If you want to remove the hidden break, we can use fallthrough to replace it

package main

import (
    "fmt"
)

func main() {
    a := 2
    switch a {
    case 1:
        fmt.Println("a=1")
    case 2:
        fmt.Println("a=2")
        fallthrough
    case 3:
        fmt.Println("a=3")
        case 4:
        fmt.Println("a=4")
    default:
        fmt.Println("default")
    }
}

output

a=2
a=3

select

The select statement is used to handle I/O operations related to channel:

  1. Each case must be a communication;
  2. All channel expressions and sent expressions will be evaluated;
  3. If any channel can run, it will execute and others will be ignored;
  4. Multiple case s can be run, and one can be selected randomly for execution;
  5. Can not run. If there is a default, execute the default, and block if there is no default, until a communication can run and the expression will not be evaluated again;
  6. A select can execute the code in the case at most once. The case needs to be detected all the time, and a for loop is added to the outer layer;
  7. The break in case only exits the current select and has nothing to do with the for loop;

Random execution case usage

package main

import (
    "fmt"
    "time"
)

func main() {
    ch1 := make(chan int)
    ch2 := make(chan int)

    go func() {
        time.Sleep(time.Second)
        ch1 <- 1
    }()

    go func() {
        time.Sleep(time.Second)
        ch2 <- 2
    }()

    time.Sleep(2 * time.Second)
    select {
    case i := <-ch1:
        fmt.Println("ch1 receive", i)
    case i := <-ch2:
        fmt.Println("ch2 receive", i)
    default:
        fmt.Println("no i/o opeartion")
    }
}

The results are printed randomly: ch1 receive: 1 or ch2 receive: 2. Because the two channels are waiting to be written, both case s in the select meet the execution conditions and are executed randomly.

Set receive channel timeout usage

func main() {
    ch1 := make(chan int)
    select {
    case i := <-ch1:
        fmt.Println(i)
    case <-time.After(5 * time.Second):
        fmt.Println("ch receive timeout")
    }
}

Check whether the channel is full of usage

func main() {
    ch1 := make(chan int, 5)
    ch1 <- 1
    ch1 <- 2
    ch1 <- 3
    ch1 <- 4
    ch1 <- 5

    select {
    case ch1 <- 6:
        fmt.Println("send 6 to ch1")
    default:
        fmt.Println("channel is full")
    }
}

Technical articles are constantly updated. Please pay more attention~~

Keywords: Go Programming Back-end

Added by me1000 on Thu, 10 Mar 2022 15:42:02 +0200