This is the fifth day of my participation in the August More text Challenge. For details, see: August More Text Challenge

Pointer to the

The value of a pointer is the address of a variable. A pointer indicates where the value is stored. Not all values have addresses, but all variables do. Using Pointers, you can indirectly read or update the value of a variable without knowing its name

If a variable is declared as var x int, the expression &x (the address of x) gets a pointer to the integer variable of type integer pointer (* int). If the value is called p, then p refers to x, or p contains the address of x. The variable that p refers to is * p. The expression *p gets the value of the variable, an integer, and since *p represents a variable, it can also appear on the left side of an assignment operation to update the value of the variable

Println(*p) // "1" *p = 2 // equivalent to x = 2 fmt.println (x) // the result is 2Copy the code

Each composition of an aggregate type variable (a member of a structure or an element in an array) is a variable, so it also has an address. The zero value of a pointer type is nil. Test p≠nil and the result is true, indicating that p refers to a variable. Pointers are comparable. Two Pointers are equal if and only if they point to the same variable or if both are nil

var x,y int
fmt.Printl(&x == &x, &x == &y, &x == nil) //true false false
Copy the code

It is very safe for functions to return the address of a local variable. For example, the local variable v generated by calling function f will still exist even after the call returns, and the pointer p will still refer to it

var p = f()
func f() *int {
		v := 1
		return &v
}
Copy the code

Every time we call f, we return a different value

fmt.Println(f() == f()) //false
Copy the code

Since a pointer contains the address of a variable, passing a pointer argument to a function causes the function to update the value of the variable passed indirectly. For example, the following function increments a variable pointed to by a pointer argument and returns its new value

Return *p} v := 1 incr(&v)//v is now equal to 2 FMT.Println(incr(&v))// v is now 3Copy the code

Each time we use the address of a variable or copy a pointer, we create a new alias or way to mark the same variable. For example, *p at the top is an alias for V. Pointer aliases allow us to access variables without their names, which is useful, but it’s a double-edged sword: in order to find all the statements that access variables, you need to know all the aliases. Not only do Pointers generate aliases, but other reference types, such as slice, map, channel, and even the structures, arrays, and interfaces that contain these reference types, also generate aliases

The new function

Another way to create variables is to use the built-in new function. The expression new(T) creates an unnamed variable of type T, initializes the zero value of type T, and returns its address (address type *T).

Println(*p)// output 0 *p = 2// set the unnamed int to 2 FMT.Println(*p)// Output 2Copy the code

A variable created with new is no different from an ordinary local variable that takes its address, except that instead of introducing (and declaring) a dummy name, it can be used directly in an expression via new(T). So new is just a syntactic convenience, not a basic concept. The two newInt functions in this example have the same behavior

func newInt() *int{
		return new(int)
}

func newInt() *int{
		var dummy int
		return &dummy
}
Copy the code

Each call to new returns a different variable with a unique address, as shown in the following example

p := new(int)
q := new(int)
fmt.Println(p == q) //false
Copy the code

There is one exception to this rule: two variables whose types carry no information and have a zero value, such as struct{} or [0]int, have the same address

New is a pre-declared function, not a keyword, so it can be redefined as another type. For example:

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

The life cycle of a variable

Life cycle refers to the period of time during the execution of a program that a variable exists. The lifetime of a package-level variable is the execution time of the entire program. In contrast, local variables have a dynamic life cycle: each time a declaration is executed, a new entity is created, and the variable persists until it becomes inaccessible, at which point its storage space is reclaimed. The parameters and return values of the function are also local variables, which are created when the function is called

For t: = 0.0; t < cycles*2*math.Pi; T +=res {x := math.sin (t) y := math.sin (t*freq + phase) img.setcolorindex (size+int(x*size+0.5), size+int(y*size+0.5), blackIndex) }Copy the code

The variable t is created at the start of each for loop, and the variables x and y are created during each iteration of the loop

So how does the garbage collector know if a variable should be collected? It’s a long story, but the basic idea is that each package-level variable, as well as the local variable of each currently executing function, can be used as the source of the path back to that variable, which can be found through Pointers and other references. If a variable’s path does not exist, the variable becomes inaccessible, so it does not affect any other computation

Because the life cycle of a variable is determined by whether it is reachable, a local variable can survive beyond one iteration of the loop that contains it. Its existence may continue even after the loop containing it has returned

The compiler can choose to use space on the heap or stack for allocation, and this choice is not based on declaring variables using the var or new keyword

var global *int

func f() {
		var x int
		x = 1
		global = &x
}

func g() {
		y := new(int)
		*y = 1
}
Copy the code

X must be using heap space here. This is because it can be accessed through the global variable after the return of f, even though it is declared as a local variable. In this case, you could say x is escaping from f. When g returns, the variable y becomes inaccessible and retrievable. Since y does not escape from g, the compiler can safely allocate *y on the stack, even if the new function is used to create it. In any case, the concept of escape saves you the extra trouble of writing the correct code, but it is useful to remember that it is useful for performance optimization, since each variable escape requires an additional memory allocation process

Garbage collection is a great help in writing the right programs, but there is always the memory burden. There is no need to explicitly allocate and free memory, but the life cycle of variables is something that must be clear to write efficient programs. For example, keeping unnecessary Pointers to short-life objects in long-life objects, especially in global variables, prevents the garbage collector from reclaiming the short-life object space

reference

Go Programming Language — Alan A. A. Donovan

Go Language Learning Notes — Rain Marks