Golang basics: usage and implementation of Go Module, for range, slice, map, struct, etc.

The project uses Go to develop the backend, and it took some time to study systematically. Here is a summary.

The content of this article is compiled from the study notes and daily summary of "Go Language Lesson 1" in Geek Time.

Go program structure

https://time.geekbang.org/column/article/428267

Naming rules in Go:

  • Go source files are always named with short words in all lowercase letters and end with a .go extension
  • If you want to use multiple words in the name of the source file, we usually directly connect multiple words as the source file name instead of using other separators, such as underscores. That is, we usually use helloworld.go as the file name instead of hello_world.go.

import “fmt”:

  • "fmt" represents the import path (Import) of the package, which represents the fmt directory under the standard library, and the meaning of the entire import statement is to import the package under the fmt directory of the standard library
  • Usually the last segment name of the import path is the same as the package name used

package main:

  • Package is the basic unit of Go language, a Go program is essentially a collection of packages
  • The main package is a special package in Go, and only one package named main is allowed in the entire Go program

func main:

  • When running an executable go program, the entry point is the main function
  • Only functions whose first letter is uppercase are exported and can be called; if the first letter is lowercase, it means that it is only visible in the declared package

Inside the function:

  • Standard Go coding style uses tabs instead of spaces for indentation

go build main.go

  • Go is a compiled language, which means that only after you compile your Go program can you deliver the resulting executable to others
  • The application generated by go can run without depending on the environment (the other party can run without installing go)
  • In the development phase, you can use go run main.go to run directly

If you deliver to others a .rb, .py or .js dynamic language source file, then their target environment must have a corresponding Ruby, Python or JavaScript implementation to interpret and execute these source files

Go module build mode:

  • Go version 1.11 was officially introduced to completely solve the problem of complex version dependencies in Go projects
  • Go's default package dependency management mechanism and Go source code construction mechanism
  • A module is a collection of packages that are versioned, released, and distributed together with the module. The directory where go.mod resides is what we call the root directory of the module it declares
  • go.mod file, which stores the dependency information of this module on third parties ( a go.mod file represents a package, and a project can have multiple go.mods )
  • go mod init github.com/shixinzhang/hellomodule1: generate a go.mod file
  • go mod tidyIt can automatically download and add dependencies according to the dependencies in the .go file
  • go.sum file: record the hash value of the directly/indirectly dependent library, and check whether the local library version is consistent with the hash value in this file when building
  • Go Module natively supports reproducible builds without using a vendor. Of course, the Go Module mechanism also retains the vendor directory (the dependency package under the vendor can be generated through go mod vendor, and the vendor-based build can be realized through go build -mod=vendor)
admin@C02ZL010LVCK hellomodule % go mod tidy
go: finding module for package go.uber.org/zap
go: finding module for package github.com/valyala/fasthttp
go: downloading github.com/valyala/fasthttp v1.34.0
go: found github.com/valyala/fasthttp in github.com/valyala/fasthttp v1.34.0
go: found go.uber.org/zap in go.uber.org/zap v1.21.0
go: downloading github.com/andybalholm/brotli v1.0.4
go: downloading github.com/klauspost/compress v1.15.0
admin@C02ZL010LVCK hellomodule % ls  
go.mod  go.sum  main.go
admin@C02ZL010LVCK hellomodule % cat go.mod 
module github.com/shixinzhang/hellomodule1

go 1.16

require (
	github.com/valyala/fasthttp v1.34.0
	go.uber.org/zap v1.21.0
)
admin@C02ZL010LVCK hellomodule % 

project structure

https://time.geekbang.org/column/article/429143

Two kinds of projects:

  1. Executable program
  2. library item

Executable program

  • go.mod go.sum is placed in the project root directory
  • cmd directory: store the source code of the main package corresponding to the executable file to be built
  • Other codes are placed in corresponding directories according to different packages
  • internal directory: store Go packages that are used internally but cannot be accessed externally

In general, the main package should be clean. We will do in the main package: command line parameter parsing, resource initialization, log facility initialization, database connection initialization , etc.

After that, the execution authority of the program will be handed over to a higher-level execution control object

Reproducible Build:
Reproducible build, that is to build for the same go module source code, different people, on different machines (same architecture, for example, x86-64), on the same OS, can get the same at different time points binary file.

library item

A simplified version of the executable program, just remove the cmd and vendor directories.

The original intention of the Go library project is to expose APIs to the outside world (open source or public within the organization). For packages that are only used within the project and do not want to be exposed to the outside world, they can be placed under the internal directory at the top level of the project.

There is no absolute standard for Go project structure : https://github.com/golang-standards/project-layout/issues/117#issuecomment-828503689

Go Module build mode

https://time.geekbang.org/column/article/429941

Go program build process:

  1. Determine package version
  2. compile package
  3. link the compiled object files together

The construction mode of Go language has gone through three iterations and evolution processes:

  1. GOPATH: Go to the local environment variable directory to find dependent libraries
  2. Vendor: Download the code of the dependent library to the vendor and submit it together. When searching for dependencies, first search from the vendor directory
  3. Go Module: go.mod and the mechanics behind it

GOPATH:
The local missing third-party dependency package (and its dependencies) can be downloaded to the path configured by the local GOPATH environment variable through the go get command.

First look for $GOROOT and then $GOPATH

Before there was no go module mechanism, go get downloaded the latest version at that time. If others execute it at different times, it may be inconsistent with the library version you downloaded.
go env GOPATHView local environment variables

vendor:

To enable the vendor mechanism, your Go project must be located under the src directory of a certain path configured by the GOPATH environment variable. If this path requirement is not met, the Go compiler will ignore the vendor directory under the Go project directory.

Go Module:

The go.mod file turns the current project into a Go Module, and the project root directory becomes the module root directory

  1. go mod init: Create a go.mod file, turning a Go project into a Go Module
  2. go mod tidy: scan the Go source code, and automatically find out the external Go Module and version that the project depends on, download these dependencies and update the dependency information to the go.mod file, and generate a checksum file
  3. go build execution build: read the dependency and version information in go.mod, find the corresponding version of the dependent module in the local module cache path, execute compilation and linking

Relevant environment variables:

  • GOPROXY: Proxy service for downloads
  • GOMODCACHE: where to download
admin@C02ZL010LVCK ~ % go env GOPROXY
https://proxy.golang.org,direct
admin@C02ZL010LVCK ~ % go env GOMODCACHE
/Users/simon/go/pkg/mod

semantic import version

  • v1.2.1, major version number (major), minor version number (minor), patch version number (patch)
  • When the default major version number is different, it is not compatible; after the minor version number and patch version number are increased, it is forward compatible
  • If the main version number is upgraded, the version number needs to be added to the import path: import "github.com/sirupsen/logrus/v2", so that the Go Module mechanism will search for the library under the v2 path

Go's "semantic import version" mechanism: By introducing the main version number in the package import path, it can distinguish incompatible versions of the same package, so that we can even depend on two incompatible versions of a package at the same time.

Minimum version selection principle

A and B have a common dependency package C, but A depends on the v1.1.0 version of C, while B depends on the v1.3.0 version of C, and at this time the latest release version of the C package is C v1.7.0
Go command will Choose v1.3.0, which is compatible with the minimum version of the 2 libraries, and will not choose the latest version without authorization

Minimal version selection makes reproducible builds easier.

Just imagine, if you choose the largest and latest version, then for the same code, the latest and largest versions of its dependent packages may be different at different times, so the final files generated at different times will be different.

The build mode can be switched through the GO111MODULE environment variable.

However, it should be noted that from Go 1.11 to Go 1.16, different versions use different build modes when the GO111MODULE configuration is different.

General operation of Go Module

https://time.geekbang.org/column/article/431463

Empty import:

  • import _ "foo"
  • Empty import just introduces this package, which is common to import mysql driver, but does not use the methods exposed in this package. Some packages are implemented by drivers
  • An empty import means that the init function of the dependent package is expected to be executed, and this init function has the logic we need.

go private repository:

Private proxies that do better include goproxy.io, goproxy.cn, athen, etc.

1. Add dependencies

  1. Add the import statement to the code
  2. Execute go get, it will download and update go.mod
  3. go mod tidy can also achieve a similar effect, but better than go get, especially in complex projects

For complex project changes, it is obviously inefficient to manually add dependencies one by one, go mod tidy is a better choice

imported and not used: “github.com/google/uuid”

go mod tidysomewhat similarpip install -r requirements.txt

2. Upgrade/downgrade dependencies

go list -m -versions github.com/gin-gonic/ginView all version numbers of a library

Upgrading and downgrading also use go getand go mod tidy, the difference lies in the parameters

go get library name@version number: go get github.com/gin-gonic/[email protected]the specified version will be downloaded, and the configuration version number in go.mod will be updated at the same time

go mod:

  • First use go mod edit to modify the version number: go mod edit -require=library name@version number:go mod edit -require=github.com/gin-gonic/[email protected]
  • then executego mod tidy

3. Add a dependency with a major version number greater than 1

The reason why the major version number is greater than 1 is special, because generally speaking, the major version numbers are different, which is a major upgrade and not forward compatible.

If the new version number is not compatible with the previous one, you cannot use the default library name to import, but you need to add the version number after the library name:

import github.com/user/repo/v2/xxx

Then execute go get or something, it is the same as before.

4. Remove dependencies

After deleting the import statement of this library, just execute go mod tidyit . It is really worthy of its name, and it is handled cleanly.

go mod tidy will automatically analyze source code dependencies and remove unused dependencies from go.mod and go.sum

5. Vendor related

Execution go mod vendorwill create a vendor directory, and then copy the code of the dependent library to here. The modules.txt file will also record the version number of the library.

If you want to build based on vendor instead of locally cached Go Module, you need to add -mod=vendor parameter after go build.

For higher versions (after 1.14), if there is a vendor directory, go build will look for dependencies from the vendor first.

Entry function and package initialization: find out the execution order of Go programs

https://time.geekbang.org/column/article/432021

The entry function of a Go application: the main function in the main package

If you want to perform some work before the main.main function, you can define an init function and do it in it.

Each Go package can have more than one init function, and each Go source file that makes up a Go package can also define multiple init functions

##【Execution Flowchart】

Go package is the basic unit of program logic encapsulation. Each package can be understood as an "autonomous", well-encapsulated basic unit with limited external exposure
. The initialization of the program is the initialization of these packages.

Initialization sequence:

  1. In import order, recursively initialize the contents of all dependent packages (and the packages they depend on)
  2. The initialization order of a package: constant -> variable -> init function
  3. If it is the main package, then execute the main function

Packages that multiple packages depend on will only be initialized once

Purpose of the init function

Check and modify the initialization status of package-level variables. For example, some parameters that must be set are not set by the caller or there is a problem with the setting. You can explain it here.

Each init function will only be executed once in the entire Go program life cycle.

You can also modify the value of variables, such as url, according to the configuration (such as environment variables), which is very practical.

There is also a very common application scenario: combining empty imports to achieve some highly decoupled designs.

For example, when accessing a database, a specific driver implementation (mysql or postgres) is usually imported empty-handed. When the called file is initialized, the file initialization of the driver implementation will be executed, thereby executing its init method and injecting a specific driver implementation into the sql library. driver implementation.

import (
	"database/sql"
	_ "github.com/go-sql-driver/mysql"	//空导入
)

	db, err = sql.Open("mysql", "root:password@tcp(127.0.0.1:3306)/todo")

//mysql 的 driver.go
func init() {
	sql.Register("mysql", &MySQLDriver{})
}

From the perspective of the standard library database/sql package, this "registration mode" is essentially an implementation of a factory design mode. The sql.Open function is the factory method in this mode. It produces "Out of the different classes of database instance handles

Use the built-in package to implement a simple web service [click after learning]

https://time.geekbang.org/column/article/434017

variable declaration

https://time.geekbang.org/column/article/435858

## [Paste the first picture and the picture in the summary]
insert image description here

The significance of the static language declaration is to tell the compiler the boundary information (a few bytes) of the memory that the variable can operate on.

Different types of zero values:

  • Integer type: 0
  • Float type: 0.0
  • String type: ""
  • pointer, interface, slice, channel, map, function: nil
package test

import "fmt"

//包级变量
var Age int
var (
	name string = "shixinzhang"
	address = "Shanghai"	//省略类型
	a, b, c = 1, 2.1, 'c'	//一行声明多个,省略类型
)

func TestVariable()  {
	var height int = 128
	var h = int32(128)	//显式类型转换 等同于下面这个
	var h32 int32 = 128

	var a, b, c int = 1,2,3	//一行声明多个变量,类型其实可以推导出来,逗号不能少!

	weight := 140	//短变量声明,省略 var 和类型
	d, e, f := 4,5, "hi"	//短变量也可以声明多个,不同类型也可以

	fmt.Println("height ", height, h, h32, weight, a,b,c, d,e,f)
}

Declaration method:

  • Universal variable declaration
  • short variable declaration

Universal variable declaration:

var a int = 10

The variable name comes before the type, just like in typescript.

The reason for this is simple: Compared with C, in the complex case when the parameter is a pointer, this declaration format is relatively easy to understand.

You can also not assign a value when declaring, there will be a default value, called zero value

**Variable declaration block:** Use a var keyword to include multiple variable declarations:

var (
	name string = "shixinzhang"
	address = "Shanghai"	//省略类型
	a, b, c = 1, 2.1, 'c'	//一行声明多个,省略类型
)

short variable declaration

Short variable declarations (:=): omit the var keyword and type information

a := 12
a, b, c := 12, 'B', "CC"

Variable types are automatically deduced by the compiler.

Variable types in Go:

  • Package-level variables, exported (package-level variables with an initial capital letter) variables
  • local variable

Declaration form of package-level variables

1. Direct initialization while declaring

var ErrShortWrite = errors.New("short write")

2. Declare first, initialize later

Declaration clustering : Put lazy initialization and direct explicit initialization variables into different declaration blocks.
Can improve code readability.

Proximity principle: Variables are declared as close as possible to where they are used

The declaration form of local variables

1. Direct initialization when declaring

Use short variable declarations.

Short variable declaration is the most widely used declaration method for local variables.

age := 28
name := "shixinzhang"

Complex types do not support obtaining the default type, and explicit transformation needs to be added on the right side of the equal sign:

s := []byte("hello shixinzhang")

2. Declare first, initialize later

Because there is no initialization value, the type needs to be declared. Use generic variable declarations:
first var a number, then assign.

If there are multiple local variables that need to be declared, you can also consider using the var declaration block to complete.

func test() {
	var {
		age int
		name string
	}
}

Code Blocks and Scope

https://time.geekbang.org/column/article/436915

scope:

  • Variables are only valid within a certain scope.
  • In this scope, if you declare a variable with the same name as a higher level, it will be recreated instead of using the global variable. If assignment is made, the modification is also in the current scope ( variable shadowing )
  • After exiting this code block, the variable is no longer accessible.

Explicit code block: code surrounded by {}.

Implicit code block:

  1. Global/universe code blocks
  2. package code block
  3. file-level code block
  4. function-level code block
  5. Control Logic Level Code Blocks

Go language predefined identifiers with the largest scope:

insert image description here

Different files in the same package cannot have package-level variables with the same name! If file A in the same package defines global variable a, then other files in this package cannot define global variable a.

When importing other packages, only the export identifiers of other packages can be used, and export identifiers have package code block-level scope.

Export identifier:

  1. Declared in a package code block (global variable or method in the package)
  2. capitalized

Imported package names are scoped to file code blocks

Control the scope of logic-level code:

  1. Created in the if condition, it can also be accessed in the else
  2. Created in the switch condition, it cannot be accessed after the case ends
func bar() {
	if a := 1; false {
	} else if b := 2; false {
	} else if c := 3; false {	//在 if 条件里创建的临时变量,在 else 里也可以访问
	} else {
		println(a, b, c)
	}

	//在 if 条件里创建的临时变量,在 else 里也可以访问
	//因为这个创建等价于这样:
	{
		c := 3 // 变量c作用域始于此
		if false {

		} else {
			println(a, b, c)
		}
		// 变量c的作用域终止于此
	}
}

Note ⚠️: The combination of short variable declarations and control statements is very easy to cause variable shadowing problems, and it is not easy to identify!

How to solve variable shadowing:

  1. Variable shadowing checks can be done with the help of go vet
  2. Agree on naming rules to avoid duplication

go vetDownload and use:

  1. Download go vet: go install golang.org/x/tools/go/analysis/passes/shadow/cmd/shadow@latestneed a ladder
  2. implement:go vet -vettool=$(which shadow) -strict test/variable.go

-strictSpecify the file to be detected, the execution result:

# command-line-arguments
test/variable.go:18:6: declaration of "a" shadows declaration at line 10

It is agreed to use long names for package-level variables, and shorter names for more local variables, which should be able to solve most of the variable shadowing problems.

Basic Data Types: Numeric Types

https://time.geekbang.org/column/article/439782

Types in Go:

  1. basic data type
  2. composite data type
  3. Interface Type

Basic data types: integer, floating point, complex type

integer

Integers are divided into platform-independent and platform-dependent (similar to C/C++).

The difference is: whether the length is consistent under different CPU architectures and operating systems

insert image description here

There are two types of platform-independent integers, the difference lies in whether the first binary bit represents a number or a sign bit :

  1. signed
  2. unsigned

The sign bit is the positive and negative sign

Go's integer bit encoding: 2's complement method (bitwise inversion and then + 1),
so the integer value of the signed binary number 10000001 is negative 01111110 + 1 = -127

insert image description here

There seem to be only 3 platform-related shaping methods:

  1. int: 32 bits 4 bytes, 64 bits 8 bytes
  2. uint: 32-bit 4, 64-bit 8
  3. uintptr
    insert image description here

When writing programs for portability, don't use platform dependencies!

unsafe.Sizeof(a)You can view the length of the variable.

integer overflow

When using an integer with a number in its name, pay attention to its length range to avoid overflow.

func TestVariable()  {
	ss := int32(12)
	int8_a := int8(127)
	int8_a += 1	//溢出!

	uint8_b := uint8(1)
	uint8_b -= 2 //溢出!
	fmt.Println("sizeof int:", unsafe.Sizeof(a))
	fmt.Println("sizeof int8:", unsafe.Sizeof(int8_a), int8_a)
	fmt.Println("sizeof uint8:", unsafe.Sizeof(uint8_b), uint8_b)
	fmt.Println("sizeof int32:", unsafe.Sizeof(ss))
}

For example, int8 and uint8 are used above. From the name, we know that it has only 8 bits and 1 byte, so the range is [-128, 127]. If you accidentally exceed this range when using it, you will get unexpected results:

sizeof int: 8
sizeof int8: 1 -128
sizeof uint8: 1 255
sizeof int32: 4

Print in different base formats:

Printf:

func TestDiffFormatPrint() {
	value := 127
	value8 := 010
	value16 := 0x10

	fmt.Printf("value8 十进制:%d \n", value8)
	fmt.Printf("value16 十进制:%d \n", value16)
	fmt.Printf("十进制:%d \n", value)
	fmt.Printf("二进制:%b \n ", value)
	fmt.Printf("八进制:%o  \n", value)
	fmt.Printf("八进制带前缀:%O  \n", value)
	fmt.Printf("十六进制:%x \n ", value)
	fmt.Printf("十六进制带前缀:%X  \n", value)
}

output:

value8 十进制:8 
value16 十进制:16 
十进制:127 
二进制:1111111 
 八进制:177  
八进制带前缀:0o177  
十六进制:7f 
 十六进制带前缀:7F  

floating point number

Floating-point types are more complicated to represent and use in binary than integers!

The Go language provides two floating-point types, float32 and float64, which correspond to the single-precision and double-precision floating-point numeric types in IEEE 754, respectively.

Go floating point types are platform independent.

insert image description here

Binary representation of floating point numbers:

  • sign bit
  • exponent code
  • mantissa

The length of the exponent and the mantissa determine the range and precision of the floating-point numbers that the floating-point type can represent.

insert image description here
insert image description here

[The rules for converting floating-point decimal to binary need to be carefully sorted out]

Try to use float64 in daily use, so that the problem of floating point overflow is not easy to occur

func TestFloatNumber() {
	value := float64(1.2)

	fmt.Println("value: %d", value)

	var fl1 float32 = 16777216.0
	var fl2 float32 = 16777217.0
	fmt.Println("16777216.0 == 16777217.0? ", fl1 == fl2);

	bits := math.Float32bits(fl1)
	bits_fl2 := math.Float32bits(fl2)
	fmt.Printf("fl1 bits:%b \n", bits)
	fmt.Printf("fl2 bits:%b \n", bits_fl2)

	value3 := 6456.43e-2	//e-2 = 10^-2
	value4 := .12345e4	//10^4
	fmt.Printf("6456.43e-2 %f, .12345e4:%0.2f \n", value3, value4)

	//输出为科学计数法的形式
	fmt.Printf("%e \n", 6543.21)  //十进制的科学计数法
	fmt.Printf("%x \n", 6543.21)	//十六进制的科学计数法		//p/P 代表的幂运算的底数为 2
}

output:

value: %d 1.2
16777216.0 == 16777217.0?  true
fl1 bits:1001011100000000000000000000000 
fl2 bits:1001011100000000000000000000000 
6456.43e-2 64.564300, .12345e4:1234.50 
6.543210e+03 
0x1.98f35c28f5c29p+12 

16777216.0 of float32 type is equal to 16777217.0 because their binary numbers are the same. Because the mantissa of float32 is only 23bit.

plural type

Complex numbers: z=a+bi, where a is the real part and b is the imaginary part.

Complex numbers are mainly used in scenarios such as vector calculations.

Both real and imaginary parts are floating point types in Go.

There are two types of Go complex numbers: complex128 and complex64, with complex128 being the default.

func TestComplex() {
	//声明一个复数
	c := 5 + 6i

	fmt.Println(reflect.TypeOf(c))

	//real 获取复数实部
	//imag 获取复数虚部
	fmt.Printf("实部: %f, 虚部: %f \n", real(c), imag(c))

	var _complex = complex(7.7, 8.8)
	fmt.Printf("实部: %f, 虚部: %f \n", real(_complex), imag(_complex))
}

operation result:

complex128
实部: 5.000000, 虚部: 6.000000 
实部: 7.700000, 虚部: 8.800000 

type alias

Go also supports typedefs like C/C++, in two ways:

type MyInt int32	//类型定义
type MyInt = int32	//类型别名
  • The first one without the equal sign is equal to creating a new type. This type and int32 cannot be directly assigned, and need to be cast.
  • The second type with an equal sign is an alias whose type is the same as int32 and can be assigned.
type MyInt int32
type MyIntAlias = int32

func TestTypeDef()  {
	age := int32(29)

	height := MyInt(199)
	weight := MyIntAlias(150)

	//cannot use height (type MyInt) as type int32 in assignment
	age = height	//编译器报错:Cannot use 'height' (type MyInt) as type int32
	age = weight	//不报错

}

Basic data type: string type

https://time.geekbang.org/column/article/440804

  • Compared
  • principle
  • Practical
  • The design behind it and common methods

The C language does not provide native support for the string type, and it is implemented in the form of a character array ending with '\0'. Existing problems:

  1. When operating strings, always consider the trailing '\0' to prevent buffer overflow;
  2. A "string" defined in the form of a character array, its value is variable, and synchronization issues need to be considered in concurrent scenarios
  3. Getting the length of a string is expensive, strlenusually O(n) time complexity
  4. The C language does not have built-in support for non-ASCII characters (such as Chinese characters)

Features of string in Go:

  1. Characters are immutable:
  • It can only be modified as a whole, not one of the characters can be modified individually. The security of multi-thread access is guaranteed.
  • A value, only one copy in memory
  1. There is no need for '\0' at the end, and the efficiency of obtaining the length is also very high
  2. Support raw string, surrounded by a pair of ``
  3. By default, Unicode character set is used, and Chinese is supported

for example:

  1. output a string value in bytes
  2. Use the UTF-8 package provided by Go in the standard library to encode and decode Unicode characters (runes)
func TestString()  {

	location := "中国人"

	//1.按字节输出
	fmt.Printf("the length of location is:%d\n", len(location))	//len: 字节大小

	for i:= 0; i < len(location); i++ {
		fmt.Printf("0x%x,", location[i])
	}
	fmt.Print("\n")

	//2.按字符输出
	fmt.Println("the length of rune/character:", utf8.RuneCountInString(location))

	for _, c := range location {
		fmt.Printf("%c | 0x%x , ", c, c)
	}
	fmt.Print("\n")
}

operation result:

the length of location is:9
0xe4,0xb8,0xad,0xe5,0x9b,0xbd,0xe4,0xba,0xba,
the length of rune/character: 3
中 | 0x4e2d , 国 | 0x56fd , 人 | 0x4eba , 

What the len function does:

// The len built-in function returns the length of v, according to its type:
//	Array: the number of elements in v.
//	Pointer to array: the number of elements in *v (even if v is nil).
//	Slice, or map: the number of elements in v; if v is nil, len(v) is zero.
//	String: the number of bytes in v.
//	Channel: the number of elements queued (unread) in the channel buffer;
//	         if v is nil, len(v) is zero.
// For some arguments, such as a string literal or a simple array expression, the
// result can be a constant. See the Go language specification's "Length and
// capacity" section for details.
func len(v Type) int

rune

Go uses the rune type to represent the code point of a Unicode character. In order to transmit and store Unicode characters, Go also uses the UTF-8 encoding scheme. The UTF-8 encoding scheme uses variable-length byte encoding. Characters with small code points are encoded with fewer bytes, and characters with large code points are encoded with More byte encoding, this encoding method is compatible with the ASCII character set, and has a high space utilization rate.

The concept of Go rune is similar to that of Java's char. The character literal value, the encoding of Unicode characters, is essentially an integer

// $GOROOT/src/builtin.go
type rune = int32
  • Chinese characters in the Unicode character set: 'a', '中'
  • Unicode: character \u or \U as prefix: '\u4e2d' (character: medium), '\U00004e2d' (character: medium)

The UTF-8 encoding scheme has become the de facto standard of the Unicode character encoding scheme. Various platforms and browsers use the UTF-8 encoding scheme to encode and decode Unicode characters by default.

Features of UTF-8 (co-created by Rob Pike, the father of the Go language and others):

  • Use variable-length bytes to represent, ranging from 1 to 4 bytes, with high space utilization
  • Compatible with ASCII characters

Why doesn't UTF-8 have endian issues?

Endian problem: the problem of how to store data beyond one byte. Whether to use big endian or little endian, from which head to start reading is appropriate.
Because the UTF-8 header has been marked, there is no problem with the order.

UTF-8 is a variable-length encoding, and its coding unit is a single byte. There is no problem of who is in the high bit and who is in the low bit. The coding unit of UTF-16 is double-byte, and the coding unit of utf-32 is 4 bytes, both of which need to consider the byte order.
UTF-8 can determine how many bytes of character encoding are transmitted in the form of bytes (if it is represented by more than one byte, it will be represented by a special value in the first few digits of the first byte)

Test rune to byte array and byte array to rune:

func TestRune() {
	//定义一个字符
	var r rune = 0x4E2D
	fmt.Printf("The unicode character is: %c", r)
	fmt.Print("\n")

	//encode
	p := make([]byte, 3)	//创建一个数组
	_ = utf8.EncodeRune(p, r)	//编码为二进制
	fmt.Printf("encode result: 0x%X", p)
	fmt.Print("\n")

	//decode 0xE4B8AD
	buf := []byte {0xE4, 0xB8, 0xAD}
	r2, size := utf8.DecodeRune(buf)
	fmt.Printf("decode result: %c, size:%d", r2, size)
	fmt.Print("\n")
}

operation result:

The unicode character is: 中
encode result: 0xE4B8AD
decode result: 中, size:3

Internal representation of Go string types

When Go string is running, it is just a pointer and length, and does not store real data:

// StringHeader is the runtime representation of a string.
// It cannot be used safely or portably and its representation may
// change in a later release.
// Moreover, the Data field is not sufficient to guarantee the data
// it references will not be garbage collected, so programs must keep
// a separate, correctly typed pointer to the underlying data.
type StringHeader struct {
	Data uintptr
	Len  int
}

A pointer to the data and a length value.

insert image description here

for example:

func TestDumpBytes() {
	s := "hello"
	//1.获取这个字符串的地址,转换为一个 StringHeader
	header := (*reflect.StringHeader)(unsafe.Pointer(&s))
	fmt.Printf("0x%X\n", header.Data);	//输出底层数组的地址

	//2.通过 StringHeader 的数据指针获取到真实的字符串数据
	originString := (*[5]byte)(unsafe.Pointer(header.Data))	//StringHeader.Data 就是一个指针
	fmt.Printf("originString :%s\n", *originString)	//通过 * 获取指针指向的内容

	//3.遍历每个字符,打印
	for _, c := range *originString {
		fmt.Printf("%c_", c)
	}
	fmt.Print("\n")
}

In the above code, we unsafe.Pointerread the underlying implementation of the string, and then print the string through the data in the String.Header structure.

Output information:

0x10C9622
originString :hello
h_e_l_l_o_

string manipulation

  1. Read by subscript, the result is bytes (not characters)
  2. Character iteration (for iteration and for range iteration, the results obtained are: byte, character)
  3. String concatenation (+/+=, strings.Builder, strings.Join, fmt.Sprintf)
  4. String comparisons (==, !=, >=, <=)
  5. String conversion (string/byte[]/rune[])

String splicing performance comparison

  • +/+= is to allocate a new space after concatenating two strings. When the number of concatenated strings is small, there is no difference between the two, but when concatenated strings are many, the efficiency of Builder is higher than that of +/+= much taller.
  • Because string.Builder first takes out the address of the first string, and then splices the builder string to the back,

constant

https://time.geekbang.org/column/article/442791

Innovations in Go constants:

  • Untyped constant: A constant that is declared without a type
  • Implicit automatic conversion: Convert untyped constants to corresponding types according to the context
  • Can be used to implement enumeration

Using the const keyword also supports code blocks like var to declare multiple constants.

The combination of untyped constants and constant implicit conversions makes Go more flexible when operating on mixed data types, and code writing is also simplified.

Go does not provide an enumeration type, you can use const code block + iota to implement enumeration :

  • iota: line offset indicator, indicating the line number of the current code block, starting from 0
  • In the const code block, if there is no explicit initialization, the previous line will be copied, but because the line number is different, the increase is achieved

Each const code block has its own iota. Even if the same row appears multiple times, the values ​​of multiple iotas are the same

const (
	_ = iota
	APPLE
	WATERMELON
	_
	BINANA = iota + 1000
	_
	ORANGE
)
func TestConstValue() {
	fmt.Printf("test enum %d \n", APPLE)
	fmt.Printf("test enum %d \n", WATERMELON)
	fmt.Printf("test enum %d \n", BINANA)
	fmt.Printf("test enum %d \n", ORANGE)
}

output:

test enum 1 
test enum 2 
test enum 1004 
test enum 1006 

Note ⚠️: When defining a large number of constants, it is recommended not to use iota, otherwise it is not clear what the value is!

arrays and slices

https://time.geekbang.org/column/article/444348

array

//参数的类型,如果是数组,个数也必须一致
func printArray(a [5]int) {
	fmt.Printf("length of arr: %d \n", len(a))	//元素个数
	fmt.Printf("size of arr: %d \n", unsafe.Sizeof(a))	//所有元素占用的字节数

	for i := 0; i < len(a); i++ {
		fmt.Printf("index: %d , value: %d, addr: 0x%x \n", i, a[i], &a[i])	//也可以取地址
	}
}

func (Array) test() {
	var arr [5]int
	printArray(arr)		
	arr[0] = 1

	//var arr2 []int	//不声明默认长度为 0
	//printArray(arr2)	//编译报错:Cannot use 'arr' (type [5]int) as type []int

	//var arr3 = [5]int {1,2,3,4,5}	//直接初始化,类型在等号右边,花括号包围初始值
	//var arr3 = [...]int {1,2,3,4,5}	//也可以省略长度,编译时推导
	var arr3 = [5]int { 2: 3 }	//也可以指定下标赋值,只设置某个值
	printArray(arr3)
}

output:

length of arr: 5 
size of arr: 40 
index: 0 , value: 0, addr: 0xc00001e2d0 
index: 1 , value: 0, addr: 0xc00001e2d8 
index: 2 , value: 0, addr: 0xc00001e2e0 
index: 3 , value: 0, addr: 0xc00001e2e8 
index: 4 , value: 0, addr: 0xc00001e2f0 

As can be seen from the above example:

  • In Go, array equivalence requires the same type and length. If they are inconsistent, they cannot be passed or assigned.
  • Direct initialization: the type is written on the right side of the equal sign, and the initial value is surrounded by curly braces; the length can be omitted and derived at compile time; you can also specify a subscript assignment and only set a certain value
  • When the array is declared without assignment, the elements will be initialized to zero
  • The addresses of item are consecutive

In Java, the type verification is so strict, as long as the array parameters are of the same type.

The characteristics of the array: the number of elements is fixed; when passed as a parameter, it will be completely copied once, and the memory usage is large.

slice

Do not specify a size when declaring, appendadd elements, and dynamically expand

The Go compiler creates an array for each newly created slice, and makes the slice point to it.

Implementation of slices:

//go/src/runtime/slice.go
type slice struct {
	array unsafe.Pointer	//指向底层数组的指针
	len   int
	cap   int
}

insert image description here

Slicing is like opening a "window" to access and modify the array. Through this window, we can directly manipulate some elements in the underlying array.

insert image description here

type MySlice struct {}

func printSlice(sl []int) {
	fmt.Printf("length: %d, capcity: %d \n", len(sl), cap(sl))
	for i, i2 := range sl {
		fmt.Printf("(%d, %d )  ", i, i2)
	}
	fmt.Printf("\n")
}

func (MySlice) test() {
	//1.创建切片
	sl := make([]int , 5, 7)	//创建了一个切片,长度为 5,有 5 个元素为 0 的值
	printSlice(sl)
	sl = append(sl, 1,2,3,4,5,6)	//添加 6 个,超出容量,翻倍,元素个数为 5 + 6
	printSlice(sl)

	//数组与切片类型不兼容:Cannot use '[5]int {1,2,3,4,5}' (type [5]int) as type []int
	//printSlice([5]int {1,2,3,4,5})

	//2.数组的切片化
	var arr = [...]int {1,2,3,4,5}
	//从索引 1 (第二个)开始,长度到 2,容量到 5
	sl2 := arr[1:3:5]	//长度是第二个值减去第一个,容量是第三个值减去第一个
	printSlice(sl2)

	sl2[0] = 444	//修改切片,会影响原始数组
	fmt.Printf("origin array first value: %d\n", arr[1])

	sl2 = append(sl2, 2, 3, 4, 5)
	printSlice(sl2)
	sl2[0] = 555	//扩容后会创建新数组,再修改不会影响原始数组
	fmt.Printf("origin array first value: %d\n", arr[1])
}

When slices are passed as parameters, only pointers are passed, which is less costly.

After the slice is expanded, a new array will be created, and further modification will not affect the original array

**How ​​to convert a complete array into a slice: a[:]**, which means to convert the array a into a slice with the same length and capacity as the array.

In most cases, we will use slices instead of arrays.

Map use and implementation

https://time.geekbang.org/column/article/446032

A Go map is an unordered key-value data structure.

be careful:

  1. Do not rely on the element traversal order of the map;
  2. map is not thread-safe and does not support concurrent reading and writing;
  3. Do not try to get the address of the element (value) in the map.

The k and v in the for range process are common and cannot be passed by reference directly.


The key type of Go map has requirements and must support the == operator, which makes these types cannot be used as keys:

  • function
  • map
  • slice

Slice zero value is available, but map does not support it, it must be initialized before it can be used.

Two assignment methods:

  • The short variable method, plus {}, is initialized
  • Created by make
type MyMap struct {}

type Position struct {
	x float64
	y float64
}

func updateMap(m map[string]int) {
	m["hihi"] = 1024
}

func (MyMap) test() {
	//var m map[int]string	//不赋值,默认为 nil。这时操作的话会报错:panic: assignment to entry in nil map
	//m := map[int]string{}	//1.短变量的方式,加上 {},就是初始化了
	m := make(map[int]string, 6)	//2.通过 make 创建
	m[1] = "haha"
	m[1024] = "bytes"
	fmt.Println(m[1], len(m))	//len(map): 获取 map 中已存储的键值对

	for k, v := range m {
		fmt.Printf("(%d, %s) ", k, v)
	}
	fmt.Printf("\n")

	//第一种初始化方式:字面值初始化
	//m1 := map[Position]string {	//较为复杂的初始化,写全类型
	//	Position{1,2}: "home",
	//	Position{3,4 }: "company",
	//}
	m1 := map[Position]string {
		{1,2}: "home",	//初始化赋值时,可以省略掉类型,直接以内容作为 key
		{3,4 }: "company",
	}

	fmt.Println(m1)

	p := Position{1,2}
	m1[p] = "shop"
	fmt.Println(m1)

	delete(m1, p)	//通过内置函数 delete 删除 map 的值,参数为 map 和 key
	fmt.Println("after delete: ", m1)

	//通过下标访问不存在的值,会返回这个类型的零值
	emptyMap := make(map[string]int)
	fmt.Printf("try key that is not inside map: %s , %d\n", m[1024], emptyMap["hihi"])

	//map 作为参数是引用传递,内部修改,外部也有影响!
	updateMap(emptyMap)
	value, ok := emptyMap["hihi"]	//通过 _value, ok(逗号 + ok) 的方式判断是否存在于 map
	if !ok {
		fmt.Println("hihi not in map")
	} else {
		fmt.Println("hihi in the map! ", value)
	}
}

output:

haha 2
(1, haha) (1024, bytes) 
map[{1 2}:home {3 4}:company]
map[{1 2}:shop {3 4}:company]
after delete:  map[{3 4}:company]
try key that is not inside map: bytes , 0
hihi in the map!  1024

can be seen:

  • When traversing the map multiple times, the order of the elements is uncertain and may change.
  • Like slices, maps are reference types.
  • map is passed by reference as a parameter, internal modification, external also has an impact! ⚠️
  • When searching and reading in the map, it is recommended to use comma + ok to confirm whether the key exists!

The internal implementation of map

Schematic diagram of the implementation of the map type in the Go runtime layer:

insert image description here

Image from: https://time.geekbang.org/column/article/446032

  • hmap: runtime.hmap, the header structure of map type (header)
  • Bucket: The data structure that actually stores the key-value pair data. Elements with the same low order hash value will be placed in a bucket. A bucket contains 8 elements by default.
  • overflow bucket: When a bucket element > 8 && map does not need to be expanded, this overflow bucket will be created, and the data will be stored here

[2.hmap data introduction map]

Each bucket consists of three parts:

  1. tophash
  2. key
  3. value

The hash value is divided into two parts:

  • The low value is the bucket index, which determines which bucket the currently accessed data is in
  • The high value is the index in the bucket (the index of the tophash array), which determines the number in the bucket

Using the hash value can speed up the two-step lookup.

【3. Hash value map】

The Go runtime adopts the method of storing key and value separately, instead of using a kv followed by a kv to store kv next to each other. This actually brings algorithmic complexity, but reduces the memory alignment. Memory waste.

[4. Memory usage comparison chart]


// $GOROOT/src/runtime/map.go
const (
	// Maximum number of key/elem pairs a bucket can hold.
	bucketCntBits = 3
	bucketCnt     = 1 << bucketCntBits

	// Maximum average load of a bucket that triggers growth is 6.5.
	// Represent as loadFactorNum/loadFactorDen, to allow integer math.
	loadFactorNum = 13
	loadFactorDen = 2

	// Maximum key or elem size to keep inline (instead of mallocing per element).
	// Must fit in a uint8.
	// Fast versions cannot handle big elems - the cutoff size for
	// fast versions in cmd/compile/internal/gc/walk.go must be at most this elem.
	maxKeySize  = 128
	maxElemSize = 128
)

Two scenarios for map expansion :

  1. The number of elements in map > LoadFactor * 2^B
  2. When there are too many overflow buckets

Currently, the latest version 1.17 of Go has LoadFactor set to 6.5 (loadFactorNum/loadFactorDen)


// Like mapaccess, but allocates a slot for the key if it is not present in the map.
func mapassign(t *maptype, h *hmap, key unsafe.Pointer) unsafe.Pointer {
	// If we hit the max load factor or we have too many overflow buckets,
	// and we're not already in the middle of growing, start growing.
	if !h.growing() && (overLoadFactor(h.count+1, h.B) || tooManyOverflowBuckets(h.noverflow, h.B)) {
		hashGrow(t, h)
		goto again // Growing the table invalidates everything, so try again
	}
}

// overLoadFactor reports whether count items placed in 1<<B buckets is over loadFactor.
func overLoadFactor(count int, B uint8) bool {
	return count > bucketCnt && uintptr(count) > loadFactorNum*(bucketShift(B)/loadFactorDen)
}

// tooManyOverflowBuckets reports whether noverflow buckets is too many for a map with 1<<B buckets.
// Note that most of these overflow buckets must be in sparse use;
// if use was dense, then we'd have already triggered regular map growth.
func tooManyOverflowBuckets(noverflow uint16, B uint8) bool {
	// If the threshold is too low, we do extraneous work.
	// If the threshold is too high, maps that grow and shrink can hold on to lots of unused memory.
	// "too many" means (approximately) as many overflow buckets as regular buckets.
	// See incrnoverflow for more details.
	if B > 15 {
		B = 15
	}
	// The compiler doesn't see here that B < 16; mask B to generate shorter shift code.
	return noverflow >= uint16(1)<<(B&15)
}

If the number of elements exceeds the load factor, a bucket array of twice the size will be created, and the original bucket data will be hmapsaved oldbucketsunder until all the original bucket data is copied to the new array.

【5. hmap oldbuckets image】

Map instances are not concurrent write safe, nor do they support concurrent reads and writes. If we read and write concurrently to the map instance, an exception will be thrown when the program is running. If you want to read and write the map concurrently, you can use it sync.Map.

struct

https://time.geekbang.org/column/article/446840

Type definitions also support multiple definitions at once:

type (
	T1 int
	T2 T1
)

type T struct { 
	my T // compile error: Invalid recursive type 'MyStruct'
	t *T // ok
	st []T // ok 
	m map[string]T // ok
}

The memory usage of an empty structure is 0:

	var em EmptyStruct
	fmt.Println("size of empty struct: ", unsafe.Sizeof(em))	//size of empty struct:  0

A variable of empty structure type does not occupy memory space, and is very suitable as an "event" to be passed between concurrent Goroutines.

Go structs are zero-valued and can be used directly after declaration.

At the same time, it also supports literal value initialization when declaring values. There are several ways:

  1. Assign values ​​in order: p := Position{1,2}
  2. Assign by specifying the parameter name: q := Position{y: 3, x: 6}

If the construction of a structure is complicated and requires a lot of internal work after passing in parameters, we can define a function for the structure type to construct the structure:

func NewT(field1, field2, ...) *T {
    ... ...
}

For example:

func NewPosition(center float64) *Position {
	return &Position{
		x: center - 10,
		y: center + 10,
	}
}

struct memory layout

At runtime, the elements (members, methods) of a Go struct are stored in a contiguous block of memory.

Memory alignment is based on the consideration of improving the efficiency of processor access to data.

For various basic data types, the memory address value of its variables must be an integer multiple of the size of the type itself.

For example: the memory address of an int64 type variable should be divisible by the size of the int64 type itself, that is, 8; the memory address of a uint16 type variable should be divisible by the size of the uint16 type itself, that is, 2.

In order to use memory reasonably, the compiler may fill the structure with data (similar to C/C++), including two types:

  1. Field filling: let the address of the field be divisible by the number of bytes occupied by its own type
  2. Tail padding: ensure that the address of each structure variable is an integer multiple of a value, which is the length of the longest field in the structure and the minimum value of the system memory alignment coefficient

On 64-bit processors, the memory alignment factor is generally 8.

insert image description here

When our developers define the structure, try to arrange the order of the fields reasonably, otherwise too much filling will lead to a larger space occupation.


  • The loop statement in Go only has for, no while and do while
  • In switch in Go, the type can also be used as a condition; case does not need to write break

if own variable

https://time.geekbang.org/column/article/447723

Operator precedence:

[1. Operator priority table]

Note: The priority of logical and and logical or is very low. If there are other operators in a conditional statement, logical and and or are executed last.

Like this example:

func main() {
    a, b := false,true
    if a && b != true {
        println("(a && b) != true")
        return
    }
    println("a && (b != true) == false")
}

The first instinct is to execute a && b first. ⚠️ Actually, != has higher precedence than &&, so != is executed first.

Self-use variable of if statement: the variable declared before the Boolean expression, the scope of action is only in the if code block (including else).

The first intuition is only in the declared code block, ⚠️ In fact, it can also be accessed in the else.

	//if 的自用变量,在 if 的 else 代码块里也可以访问
	if tempA := 1; tempA > 0 {
		//...
	} else {
		fmt.Println(tempA)	//仍然可以访问
	}

	fmt.Println(tempA)	//Unresolved reference 'tempA'

Why among the multiple conditional logics, the one that is easiest to hit is written first, which is the best?
This mainly involves two technical points: pipeline technology and branch prediction
pipeline technology : simply put, the execution of a CPU instruction is composed of instruction fetching-instruction decoding-instruction execution-result writing back (in simple terms, The real pipeline is longer and more complex); when the first instruction is decoded, the second instruction can be fetched, so the CPU usage can be improved through pipeline technology.
Branch prediction : If there is no branch prediction, then it is executed in accordance with the code sequence of the program. Then when the last sentence of if is executed, the instruction decoding is the if statement, and the fetching instruction is the first sentence of the if statement block. If the if is not satisfied If it is, the JMP instruction will be executed and jump to else, so the instruction fetching and instruction decoding in the pipeline are actually useless. Therefore, without any branch prediction optimization, the if statement needs to write the conditions with higher probability to the top, which can better reflect the power of the pipeline.
Modern computers have branch prediction optimization, such as dynamic branch prediction and other technologies, but in any case, it is still necessary to put the one with the highest probability on the top.

In C language, there is a macro definition like this, you can use the __builtin_expect function, and the probability of actively prompting the code of that branch is higher

New tricks and pits for loops

There are several new ways to loop in Golang compared to other languages:

	var i int
	for ; i< 10; {	//1.省略前置和后置
		i++
	}
	
	for i< 10 {		//2.连冒号也省略
		i++
	}
	
	for {			//3.没条件(类似 while(true))
		if i < 10 {
			 
		} else {
			break
		}
	}

	s := []int{}
	for range s {	//4.不关心下标和值,可以省略
		//...
	}

⚠️ Some points for range:

  • There is a difference between using the classic form of for and using for range for the string type (when using for range string, the v value obtained by each loop is a Unicode character code point, that is, a rune type value, not a byte);
  • for range is the only way to traverse the map;
  • When the for range channel is used, the read operation of the channel will be blocked, and the for range will not end until the channel is closed.

Go's continue supports jumping to a label (usually used for nested loops).

⚠️ The first intuition is that this is similar to C/C++ goto (Go also supports goto). But in fact there is still a big difference, goto will restart after jumping, and continue will continue the previous logic.

Go's break also supports label, which is used to terminate the loop corresponding to label.

for example:

	outer:
	for i := 0; i < 3; i++ {
		fmt.Println("outer loop: ", i)
		for j := 0; j < 6; j++ {
			if j == 2 {
				//break outer	//结束 outer 冒号后的最外层循环
				continue outer	//继续执行外层循环
				//continue	//跳过 2
			}
			fmt.Println("inner loop: ", i, j)
		}
	}

3 pits that are easy to step on for range

1. Reuse of loop variables

⚠️ The first intuition is that for range will re-declare two new variables i and v for each iteration. But in fact, i and v will only be declared once by v in the for range statement, and will be reused in each iteration.

for i, v := range m {
	//...
}

Equivalent to:

i, v := 0, 0	//只创建一次
for i, v = range m {	//每次修改这个值
	//...	//如果有延迟执行的代码,可能访问到的是 i, v 的最终结果
}

The pit that others stepped on: The pit buried by range in the go for loop is so big

2. A copy is used in the loop, and the modification does not affect the original value


	var arr = [...]int {1,2,3,4,5}
	var copy [5]int

	fmt.Println("origin arr ", arr)

	for i, v := range arr {	
		if i == 0 {
			arr[1] = 100	//这里修改的是原始的
			arr[2] = 200
		}
		fmt.Println("in for-range ", i, v)
		copy[i] = v
	}

	fmt.Println("after loop, arr ", arr)
	fmt.Println("after loop, copy ", copy)

The first instinct would be that arr is modified during the first loop execution, and the value in the subsequent loop will also change, resulting in a slap in the face. Output result:

origin arr  [1 2 3 4 5]
in for-range  0 1
in for-range  1 2
in for-range  2 3
in for-range  3 4
in for-range  4 5
after loop, arr  [1 100 200 4 5]
after loop, copy  [1 2 3 4 5]

⚠️When for i, v := range arrthis line is executed for the first time, a copy will be made, and each subsequent iteration will fetch the copied data.
Equivalent to: for i, v := range arrCopy, so modifying arr in the code block does not affect the content of subsequent iterations.

Changing the object of range to arr[:]will output the desired content, because arr[:]means to create a slice based on the arr array. The length and capacity of this slice are the same as arr, and when the content of the slice is modified, the content of the underlying array arr will be modified.

3. When looping through the map, the order of the obtained results is not necessarily

It is very simple to traverse the map with for-range, but the result is different every time it is executed!

	var m = map[string]int {
		"shixin": 1,
		"hm" : 2,
		"laotang" : 3,
		"leizi" : 4,
	}

	counter := 0
	for i, v := range m {
//		if counter == 0 {
//			delete(m, "laotang")
//		}
		counter++
		fmt.Println("item ", i, v)
	}

The specific reasons still need to look at the relevant code in depth, and then read it if there is a need, and remember the conclusion first!

Since the map, like the slice, uses a pointer to point to the underlying data, so modifying the map in the for range loop will affect the original data.

So if there is an operation to modify the map data in the loop, there may be unexpected results, so be careful! !

switch is a little different from other languages

https://time.geekbang.org/column/article/455912

Compared with C/Java, the features of Go switch are:

  • List of supported expressions:case a, b, c, d:
  • After the code block of each case branch is executed, the switch ends without a break
  • In the case statement, the next case can be fallthroughexecuted by
  • support type switch, type judgment
	switch counter {
	case 4:
		fmt.Println("counter is 4")
		fallthrough	//仍然可以执行下一个 case
	case 77:
		fmt.Println("counter is 77")
	case 770:
		fmt.Println("counter is 770")
	default:
		fmt.Println("switch default")
	}

Due to the existence of fallthrough, Go will not evaluate the expression of the next case, but will directly execute the code branch corresponding to the next case

A break without a label breaks the innermost for, switch or select. If you want to break multi-level nesting (such as nesting switch in for), you need to pass the break label.

type switch

Go switch supports types that Java does not have: type switch, which is to judge the type. When using, write the variable .(type) after the switch, for example:

	var i interface{} = 13
	switch i.(type) {	//变量.(type),判断类型
	case nil:
		fmt.Println("type is nil", i)
	case int:
		fmt.Println("type is int", i)
	case interface{}:
		fmt.Println("type is interface", i)
	}

output:

type is int 13

⚠️ Only interface type variables can use type switch, and the types in all case statements must implement the interface type of the variable after the switch keyword

Practice Harvest Records

  1. Postgresql placeholder is $1 $2, mysql is ?
  2. Generate linux platform executable files: CGO_ENABLED=0 GOOS=linux go build

learning materials

Official documentation:

  • https://go.dev/doc/
  • https://github.com/golang/go/wiki

A relatively complete learning tutorial: https://www.liwenzhou.com/posts/Go/golang-menu/

Guess you like

Origin blog.csdn.net/u011240877/article/details/123441535