What is a pointer?

In Go, a pointer is a variable that stores the memory address of another variable. Therefore, a pointer variable points to the memory address of another variable, not the variable itself.

When declaring a pointer variable, you need to add * before the variable name to indicate that it is a pointer variable, for example:

1
var p *int

This means that a pointer named p is declared to an integer variable. The & operator can be used to obtain the address of a variable, for example:

1
2
x := 10
p := &x

This means obtaining the address of variable x and assigning the address to pointer variable p.

When using a pointer to access a variable, you need to use the * operator, which represents the dereference operator, for example:

1
2
3
x := 10
p := &x
fmt.Println(*p)

This means printing out the value of the variable pointed to by pointer variable p, that is, the value of variable x.

In addition to declaring pointer variables and getting the address of variables, you can also use the new function to create pointer variables. For example, to create a pointer to an integer variable:

1
p := new(int)

This will create a new integer variable and return its address, which is then assigned to the pointer variable p.

Pointers can also be used for function parameters and return values ​​to share data between function calls.

Be careful when using pointers, because if a pointer points to an invalid memory address, the program may crash or produce unpredictable behavior. Therefore, when using pointers, you need to ensure that the memory address pointed to by the pointer is valid.

Pointer usage scenarios

Passing parameters through pointer functions

Pointers can be used to pass function parameters. When a function needs to modify the value of an actual parameter, the address of the actual parameter can be passed to the function as a formal parameter, and the value of the actual parameter can be modified by operating the pointer. For example:

1
2
3
4
5
6
7
8
func modify(s *string) {
*s = "hello, world"
}
func main() {
s := "hello"
modify(&s)
fmt.Println(s) // Output: hello, world
}

Accessing structure fields through pointers

Pointers can more conveniently access fields in a structure, especially when the structure is large, passing pointers is more efficient than passing the entire structure. For example:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
type Person struct {
Name string
Age int
}
func main() {
p := &Person{
Name: "Alice",
Age: 18,
}
p.Name = "Bob"
fmt.Println(p) // Output: &{Bob 18}
}

Dynamically allocate memory through pointers

Pointers can be used to dynamically allocate memory at runtime, such as using the new() function to create a new variable and return its address. For example:

1
2
3
4
5
func main() {
p := new(int)
*p = 42
fmt.Println(*p) // Output: 42
}

Passing variable-length parameters

In Golang, you can use pointers to pass variable-length parameters. This is because when using variable-length parameters, a slice is passed, and the slice itself is a pointer to an array. This operation can avoid copying a large amount of data.

For example, consider the following function, which takes a variable length argument and prints it out:

1
2
3
4
5
func printArgs(args ...int) {
for _, arg := range args {
fmt.Println(arg)
}
}

Now, if you want to pass a slice as an argument to another function, you can use a pointer:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
func printArgsPtr(args *[]int) {
for _, arg := range *args {
fmt.Println(arg)
}
}

func main() {
args := []int{1, 2, 3}
printArgsPtr(&args)
}

In this example, we define a new function printArgsPtr that takes a pointer as an argument to receive a slice. Inside the function body, we use *args to dereference the pointer to get the actual slice and iterate over it to print each element. In the main function, we create a slice and pass its address to the printArgsPtr function.

It should be noted that when using pointers to pass variable-length parameters, if the slice is empty, you cannot pass a nil pointer, but should pass an empty slice. This is because in Golang, the meaning of using an empty slice and a nil pointer is different, which I will introduce later.

In addition to the above aspects, pointers are also very useful in some specific scenarios. For example, when processing data structures, pointers can more efficiently operate linked lists, trees and other data structures. However, it should be noted that excessive use of pointers may make the code difficult to maintain and need to be used with caution.

Common empty data types

In the Go language, there are some empty data types, including:

  • Nil pointer: A pointer to a non-existent address, generally represented by nil.

  • Nil slice: A slice with a length of 0, generally represented by nil.

  • Nil map: A dictionary with a length of 0, generally represented by nil.

  • Nil interface: an interface whose type and value are both nil, usually represented by nil.

  • Nil channel: a channel without allocated storage space, usually represented by nil.

nil represents a null pointer, that is, a pointer variable does not point to any address. It can represent the zero value of pointer, interface, map, channel and function types, but cannot be used to represent the zero value of other types, such as int, float, bool, etc.

The definitions of these empty data types are all zero values ​​of the corresponding types, among which the zero values ​​of pointers, slices, dictionaries, interfaces and channels are all nil, indicating that the default values ​​of these types are nil, that is, there are no pointers, slices, dictionaries, interfaces and channels pointing to any actual data. Therefore, when these data types are actually used, they can be initialized to zero value or explicitly assigned to nil.

For example:

1
2
3
4
5
var s []int = nil // define an empty slice
var p *int = nil // define a null pointer
var m map[string]int = nil // define an empty dictionary
var i interface{} = nil // define an empty interface
var c chan int = nil // define an empty channel

Note the following points:

  • nil is not a keyword, but a predefined identifier. In Go, nil can be assigned to any type of pointer, slice, dictionary, function, channel and other variables.
  • Empty slices and empty dictionaries need to be initialized before use, otherwise panic exception will occur.
  • Empty channels need to be initialized using the make function before they can be used.
  • Null pointers can be used directly because their value is nil.
  • Empty interfaces can be used directly without initialization. Because an empty interface is essentially a variable of type interface{}, it can accept any type of value, including nil. So when using an empty interface, if it is not explicitly assigned, it is an empty interface and can be used directly.

Avoid Null Pointer Exception

When using pointer type variables, you need to perform nil checks to avoid null pointer exceptions.

For example:

Integer pointer type

1
2
var p *int
fmt.Println(p == nil) // Output true

In the code, p is a variable of integer pointer type. Because it is not initialized, its value is nil. You can check whether it is a null pointer by comparing whether p is nil.

Integer slice type

1
2
var s []int
fmt.Println(s == nil) // Output true

In the code, s is a variable of integer slice type. Because it is not initialized, its value is nil. You can check whether it is an empty slice by comparing whether s is nil.

Dictionary type

1
2
var m map[string]int
fmt.Println(m == nil) // Output true

In the code, m is a variable of dictionary type. Because it is not initialized, its value is nil. You can check whether it is an empty dictionary by comparing whether m is nil.

Function type

1
2
var f func(int) int
fmt.Println(f == nil) // Output true

In the code, f is a variable of function type. Because it is not initialized, its value is nil. You can check whether it is an empty function by comparing whether f is nil.

Integer channel type

1
2
var ch chan int
fmt.Println(ch == nil) // Output true

In the code, ch is a variable of integer channel type. Because it is not initialized, its value is nil. You can check whether it is an empty channel by comparing whether ch is nil.

Finally, please note that the length of empty arrays, empty slices, and empty maps can be calculated using the len() function, and the result is 0. However, the length of null pointers, empty interfaces, and empty channels cannot be calculated using the len() function, otherwise a runtime error will be triggered.

This is because, in Go, the len() function returns the length of a value, which is predefined and usually calculated by the compiler. The length of type variables such as empty slices, empty arrays, and empty dictionaries is 0, because they already have a space in memory, so you can use the len() function to get their length.

For empty interfaces and empty channels, they do not store data, so their length is meaningless, so a runtime error will be triggered when using the len() function. If you want to determine whether they are empty, you should use the nil value to determine.