Go language Bible - Chapter 2 program structure

Keywords: Go Back-end

Chapter 2 program structure

Go language, like other languages, a large program is composed of many program components. Variables hold values, and various operations form expressions. The basic types are aggregated into more complex data types such as structures and arrays. Then, for and if are used to organize and control the execution flow of expressions, and then multiple statements are organized into a function for code isolation and reuse. Functions are organized as source files and packages

2.1 naming

All naming of variable name, function name, constant name, type name, statement label and package name in Go language follow two basic guidelines:

Starting with a letter or underscore, uppercase means that it can be exported, that is, it can be accessed by external packages

Case sensitive

There are 25 keywords in Go language. Keywords can only be used in specific grammatical structures and cannot be used for other purposes. We will talk about the usage of these keywords later

In addition, we have 30 predefined names, such as int and true, mainly for built-in constants, types and functions

Built in constants: true, false, iota, nil

Built in type: int, int8; uint8,uint…; float32…; bool,byte,rune,string,error

Built in functions: make, len, cap, new, append, copy, close, delete, complex, real, imag, panic, recover

Hump naming is recommended for Go language

2.2 declaration

Declaration statement defines various entity objects and some or all attributes of the program. It mainly has four types of declarations: var, const, type and func, which correspond to variables, constants, types and function entities respectively

A program written in go language corresponds to one or more source files ending in. Go. Each source file starts with the declaration statement of the package, indicating which package the source file belongs to. The package declaration statement is followed by the import of dependent package, and then the declaration statement of package level variables, types, functions and constants, as follows:

package main

import "fmt"

const boilingF = 212.0

func main() {
   var f = boilingF
   var c = (f-32)*5/9

   fmt.Printf("boiling point = %v℃ or %vF\n",f,c)
}

Analysis code:

We first declare a constant at the package level

const boilingF = 212.0

Then we define two variables inside the function, which can only be called inside the function

var f = boilingF
var c = (f-32)*5/9

Function structure: the function declaration consists of function name, parameter list (the caller of the function provides the specific value of parameter variables), an optional return value list and function body. The function can have no return value. The function is executed in the order of statements in the function body. It will return when it encounters return. If there is no return statement, it will be executed to the end of the function and then returned to the function caller

func main() {
   const freezingF, boilingF = 32.0,212.0
   fmt.Printf("%v℉ = %v℃\n",freezingF,ftoC(freezingF))
   fmt.Printf("%v℉ = %v℃\n",boilingF,ftoC(boilingF))
}
func ftoC(f float64)float64{
   return (f-32)*5/9
}

We can define a function (execute logic) and call it many times elsewhere

2.3 variables

The syntax of variable declaration is as follows:

//var variable name type = expression
var a = 45
var b int

fmt.Println(a,b)//45 0

When declaring, you can choose either = or type. If you select =, it is an initialization variable. If you select int, you can change the default value of the type; The value type is 0, the string type is an empty string, the boolean type is false, and the zero value corresponding to the variables of interface and reference types (including slice, pointer, map, chan and function) is nil

Declare the same set of variables and different variables at the same time

var i,k,j int
var d,f,s = true, 3.2, "four"

fmt.Println(i,k,j,d,f,s)//0 0 0 true 3.2 four

The initialization of variables can also be done by calling the function to complete the assignment of the return value of the function

var f,err = os.Open(fileName)

2.3.1 short variable declaration

The short variable declaration does not use the var keyword and uses the following format. Because of its simplicity and convenience, short declarations are widely used in the declaration and initialization of local variables

Variable name  := expression
anim := gif.GIF{LoopCount: nframes}
fred := rand.Float64()*3.0
t := 0.0

Declare the same set of variables and different variables at the same time

i , j := 0,1
a, b := 2, true

The initialization of variables can also be done by calling the function to complete the assignment of the return value of the function

f, err := os.Open(fileName)
if err != nil{
   return err
}
f.Close()

2.3.2 pointer

A variable corresponds to a memory space that holds the corresponding type value of the variable. Ordinary variables are bound to a variable name when the declaration statement is created.

The value of one pointer is the address of another variable. A pointer corresponds to the storage location of the variable in memory. Not every value will have a memory address, but there must be a corresponding memory address for each variable. The pointer can directly read and update the value of the variable without knowing the name of the variable (if the variable has a name)

The pointer of variable x is & X, and the data type corresponding to the pointer is * variable type. If the pointer name is p, it can be said that "P pointer points to variable X", or "P saves the memory address of variable X", and the * P expression corresponds to the variable value pointed to by P pointer

x := 1
p := &x
*p = 2
fmt.Println(p, *p) //0xc00001e090 2

For each member of the aggregation type, such as each member in the structure or each element of the array, it also corresponds to a variable, so it can be addressed

The zero value of any type of pointer is nil and can be tested for equality

var x, y int
fmt.Println(x==y,&x==&y)//true false

In the Go function, it is also safe to return the address of the local variable in the function. Every time we call it in the main function, we create a variable v, such as:

func main() {
   fmt.Println(f())//0xc000100010
}
var p = f()
func f() *int{
   v:=1
   return &v
}

You can update the value by using pointers. In fact, * p is the alias of v. in addition to pointers, other types will also create aliases. slice, map, chan, and even structures, arrays, and interfaces will create aliases for the referenced variables

func main() {
   v := 1
   incr(&v)
   fmt.Println(incr(&v))//3
}
func incr(p *int) int{
   *p++
   return *p
}

Pointer is the key technology to realize the flag package in the standard library. It uses the command line parameters to set the value of the corresponding variables, and the variables of these command line flag parameters may be scattered in the whole program

2.3.3 new function

Another way to create variables is to call the built-in new function

The expression new(T) will create an anonymous variable of type T, initialize it to the zero value of type T, and then return the variable address. The returned pointer type is * t, as follows:

p := new(int)
var q int
x := &q
fmt.Println(p,q,*p,*x)//0xc0000b2008 0xc0000b2010 0 0
*p=2
*x = 4
fmt.Println(*p,*x)//2 4

Using the new function omits creating a pointer to a non anonymous variable as a new temporary variable, which is simpler than the above comparison: in addition, we can use new(T) in the expression, that is, the new function is a syntax sugar rather than a new basic concept. The following two functions actually implement the same function, And each call creates a new value

func main() {
   p := newInt1()
   q := newInt2()

   fmt.Println(p,q)//0xc00001e090 0xc00001e098
}
func newInt1() *int{
   return new(int)
}
func newInt2() *int{
   var dummy int
   return &dummy
}

The new function is rarely used. It is only a predefined function, not a keyword. We can redefine the new name to another type. In the following functions, because new has been defined as int type, we can't use the built-in new function inside the delta function

func delta(old, new int) int {return new - old}

2.3.4 life cycle of variables

The life cycle of variables declared at the package level is consistent with the whole Chen Xu operation cycle. Local variables are created from its creation until they are no longer referenced, and then the storage space of variables may be recycled. Function parameters and return values are local variables. They are created every time a function is called. You can see the previous examples

So how does the Go language's automatic garbage collector know when a variable can be recycled? Let's ignore the technical details and introduce the idea: start with each package level variable and each local variable of each currently running function, and traverse through the access path of the pointer or reference to see whether the variable can be found. If the path does not exist, it indicates that the variable is unreachable, that is, whether it exists or not will not affect the subsequent calculation results of the program

Because the effective period of a variable only depends on whether it is reachable, the life cycle of local variables within a loop iteration may exceed its scope. At the same time, local variables may still exist after the function returns

The compiler will automatically choose whether to allocate storage space for local variables on the stack or heap, but this has nothing to do with the way variables are declared with var or new

var global *int
func f() {
   var x int
   x= 1
   global = &x
}
func g() {
   y :=new(int)
   *y = 1
}

The x variable in the f function must be allocated on the heap because it can still be found through the package level global variable after the function exits. Although it is internally defined, it escapes. On the contrary, the variable * y in the g function can be recycled immediately. Escape variables do not mean that the program will be incorrect, but will need to allocate additional memory, which will have a slight impact on the performance optimization of the program

Therefore, although the automatic garbage collection mechanism of Go language is a great help to write code, it does not mean that you don't need to consider memory at all. This should be noted after understanding the declaration cycle of variables

2.4 assignment

Assignment is to update the value of a variable. The simplest assignment statement is that the variable is on the left and the expression of the new value is on the right

x = 1
*p = true
person.name = "bob"
count[x] = count[x]*scale

The compound operation of specific binary arithmetic operators and assignment statements has a brief form. For example, the last statement above can be written as

count[x] *= scale

Numeric variables can also support + + and –, while expressions such as x = i + + are wrong

2.4.1 tuple assignment

Tuple assignment allows you to update the values of multiple variables at the same time (in fact, we have covered this in the previous example)

x,y,z = 2,3,5
x, y = y, x
a[i], a[j] = a[j], a[i]
//greatest common factor 
func gcd(x,y int) int{
	for y != 0{
		x,y = y, x%y
	}
	return x
}
//Calculate Fibonacci sequence
func fib(n int) int{
	x,y := 0,1
	for i:=0;i<n;i++ {
		x,y = y, x+y
	}
	return x
}

When assigning the return value of a function to a variable, there are usually multiple return values (in many cases, an operation success value and an error value)

v,ok = m[key]//map lookup
v,ok = x.(T)//Type Asserts 
v,ok = <-ch//Pain to receive

If the function returns multiple values, but we don't need them, we can use_ discard

-, err = io.Copy(dst, src)
-, ok = x.(T)

2.4.2 assignability

The assignment statement is an explicit assignment form, but there are many implicit assignment behaviors in the program:

The function call assigns the value of the call parameter to the function parameter variable

A return statement implicitly assigns the value of the return operation to the result variable

A literal of a compound type also produces an assignment behavior

medals := []string{ "gold","sliver","bronze"}

medals[0] = "gold"
medals[1] = "sliver"
medals[2] = "bronze"

Although the elements of map and chan are not ordinary variables, they also have similar implicit assignment behavior

Whether the assignment is implicit or explicit, the variable on the left and the final value on the right of the assignment statement must have the same data type

2.5 type

The type of variable or expression defines the attribute characteristics of the corresponding stored value, such as the storage size of values in memory, how they are expressed internally, whether some operators are supported, and their own associated method sets

Different programs may have the same internal structure, but they represent completely different concepts, especially variables

The declaration of type is generally at the package level, and the capitalized initial of type means that external packages can also be used

type Type name underlying type
type Celsius float64
type Fahrenheit float64

const (
   AbsoluteZeroC Celsius = -273.15
   FreezingC     Celsius = 0
   BoilingC      Celsius = 100
)

func CToF(c Celsius) Fahrenheit { return Fahrenheit(c*9/5 + 32) }
func FToC(f Fahrenheit) Celsius { return Celsius((f - 32) * 5 / 9) }

Although the basic types of Celsius and Fahrenheit are the same, they are different data types, so they can not be compared or mixed in an expression operation, but the two types with the same basic types can be explicitly converted to each other. The underlying data type determines the internal structure and expression method, and also determines whether they can support built-in operators like the underlying type, Undertake the above case:

func main() {
	var c Celsius
	var f Fahrenheit
	fmt.Println(c == 0)//true
	fmt.Println(f > 0)//false
	fmt.Println(c==f)//mismatched types Celsius and Fahrenheit
	fmt.Println(c==Celsius(f))//true
}

Naming types can make writing more convenient; A named type can also define new behaviors for the value of the type (these behaviors are represented as a set of functions associated with the type, which we call the type method set), such as:

func (c Celsius) String() string {return fmt.Sprintf("%g˚C",c)}

Many types define a String method that uses fmt package printing

c := FToC(212.0)
fmt.Println(c.String())//100˚C
fmt.Printf("%v\n",c)//100˚C

2.6 packages and documents

The concept of package in Go language is similar to that of library or module in other languages. The purpose is to support modularization, encapsulation, separate compilation and code reuse

Each package has a separate namespace

Packages allow us to hide internal implementation information by controlling which names are externally visible

Let's define a package whose function is to realize temperature conversion

Package name: tempconv. The package contains two. go source files with the following contents

package tempconv

import "fmt"

type Celsius float64
type Fahrenheit float64

const (
	AbsoluteZeroC Celsius = -273.15
	FreezingC     Celsius = 0
	BoilingC      Celsius = 100
)
func (c Celsius) String() string    { return fmt.Sprintf("%g˚C", c) }
func (f Fahrenheit) String() string { return fmt.Sprintf("%g˚F", f) }
func CToF(c Celsius) Fahrenheit { return Fahrenheit(c*9/5 + 32) }
func FToC(f Fahrenheit) Celsius { return Celsius((f - 32) * 5 / 9) }

Call in main function

package main

import (
   "awesomeProject/src/tempconv"//Here you can see the path of the package we defined
   "fmt"
)

func main() {
   fmt.Printf("Brrrr! %v\n", tempconv.AbsoluteZeroC)//Brrrr! - two hundred and seventy-three point one five ˚ C / / casually call a constant under the tempconv package
}

2.6.1 import package

In Go language programs, each package has a globally unique import path. A string similar to "awesomeProject/src/tempconv" corresponds to the import path of the package. Each package has a name, which has been specified at the time of declaration

2.6.2 package initialization

Package initialization first solves the dependency order of package level variables, and then initializes in the order of package level variable declaration

If the package contains multiple. Go source files, they will be initialized in the order they are sent to the compiler. The go Language Builder will first sort the. Go files by file name, and then call the compiler in turn

For variables that live at the package level, if there are initialization expressions, they are initialized with expressions, and some have no initialization expressions. For example, initialization of some table data is not a simple assignment process. In this case, we can use a special init initialization function to simplify the initialization work, and each file can contain multiple init functions

func init() {/* ... */}

Except that such init functions cannot be called or referenced, other behaviors are similar to ordinary functions

package popcount

var pc [256]byte

func init() {
   for i := range pc {
      pc[i] = pc[i/2] + byte(i&1)
   }
}
func PopCount(x uint64) int {
   return int(pc[byte(x>>(0*8))] +
      pc[byte(x>>(1*8))] +
      pc[byte(x>>(2*8))] +
      pc[byte(x>>(3*8))] +
      pc[byte(x>>(4*8))] +
      pc[byte(x>>(5*8))] +
      pc[byte(x>>(6*8))] +
      pc[byte(x>>(7*8))])
}

For initialization that requires complex processing such as pc, the initialization logic can be wrapped as an anonymous function, as follows

var pc [256]byte = func() (pc [256]byte) {
   for i := range pc {
      pc[i] = pc[i/2] + byte(i&1)
   }
   return
}()

2.7 scope

The scope is different from the life cycle. It is a code text area

A syntax block is a series of statements contained in curly braces whose internal declarations cannot be accessed externally. Some declarations are not explicitly wrapped in curly braces in the code, which is called lexical block. For global code, there is an overall lexical block, which is called global lexical block; For each package; Each for, if and switch statement also corresponds to lexical blocks. Each select or switch branch also has independent syntax blocks, including explicitly written lexical blocks

The built-in types, functions and constants are in the global scope, and the control flow labels: break, continue and goto are the function level scope

A program may contain multiple declarations of the same name, but it doesn't matter as long as they are in different lexical domains

The compiler will first look up the innermost lexical domain like the global scope. The internal declaration will shield the external declaration so that it cannot be accessed. The lexical domain can also be deeply nested

The following three XS are declared in different lexical domains, one in the function lexical domain, one in the initialization lexical domain, and one in the cyclic lexical domain

func main(){
   x := "hello"
   for _, x:= range x {
      x:= x+ 'A' - 'a'
      fmt.Printf("%c",x)
   }
}

if and switch statements also create implicit lexical domains in the condition section

At this point, we have seen how packages, files, declarations and statements express a program structure. Next, we will explore data structures

Posted by idotcom on Sat, 23 Oct 2021 07:27:40 -0700