This article is an original article. Please scan the code to follow the public account flysnow_org or www.flysnow.org/, and read the following wonderful articles for the first time. If you feel good, feel free to share it in moments, thanks for your support.

For those of you who know a language, are we passing values or references when we call a function?

In fact, for passing value and passing reference, is a relatively old topic, do research and development have this concept, but may not be very clear. For us who do Go language development, we also want to know what exactly is passing.

So let’s look at what value passing is and what reference passing is.

What is Value passing (value passing)

Passing means that the function always passes a copy, a copy, of the original thing. For example, if we pass a parameter of type int, we pass a copy of the parameter. Passing a pointer argument is actually passing a copy of the pointer, not the value to which the pointer points.

Basic types like ints are perfectly understandable, they’re just a copy, but Pointers? We think we can use it to change the original value, how can it be a copy? Let’s look at an example.

func main(a) {
	i:=10
	ip:=&i
	fmt.Printf("The memory address of the original pointer is: %p\n",&ip)
	modify(ip)
	fmt.Println("Int value changed, new value :",i)
}

 func modify(ip *int){
	 fmt.Printf("The memory address of the pointer received in the function is: %p\n",&ip)
 	*ip=1
 }
Copy the code

We run it and see the following input:

Int (0xc42000c038) int (1)Copy the code

The first thing we need to know is that everything stored in memory has an address, and Pointers are no exception. They point to something else, but they also have memory for that pointer.

So we can see from the output, this is a copy of the pointer, because the memory location of the two Pointers is different, although the value of the Pointers is the same, but they are two different Pointers.

You can understand it better by looking at the picture above. First of all, we declare a variable I with a value of 10, and its memory address is 0xC420018070, and from that memory address, we can find variable I, and that memory address is the pointer IP of variable I.

Pointer IP is also a pointer variable. It also needs memory to store it. What is its memory address? Is 0 xc42000c028. When we pass the pointer variable IP to modify, it is a copy of the pointer variable, so the newly copied pointer variable IP, whose memory address has been changed, is the new 0xC42000c038.

Either 0xC42000C028 or 0xC42000C038, we can call them Pointers to Pointers, they point to the same pointer 0xC420018070, which in turn points to variable I, and that’s why we can change the value of variable I.

What is reference-passing?

The Go language (Golang) is not passed by reference. I can’t use Go as an example here, but I can describe it by illustration.

For example, if the memory address printed in modify is unchanged, also 0xC42000c028, then it is passed by reference.

Confuse the Map

We know about passing values and references, but the Map type can be confusing because we can modify its contents with methods, and because it has no obvious Pointers.

func main(a) {
	persons:=make(map[string]int)
	persons["Zhang"] =19

	mp:=&persons

	fmt.Printf("The memory address of the original map is: %p\n",mp)
	modify(persons)
	fmt.Println("Map value changed, new value :",persons)
}

 func modify(p map[string]int){
	 fmt.Printf("The memory address received from this function is: %p\n",&p)
	 p["Zhang"] =20
 }
Copy the code

Run the printout:

The memory address of the original map is 0xC42000c028. The memory address of the received map is 0xC42000c038. The map value has been changed to: map[3:20]Copy the code

The two memory addresses are not the same, so this is another value pass (copy of value), so why can we change the contents of the Map? So before we rush, let’s look at a self-implemented struct.

func main(a) {
	p:=Person{"Zhang"}
	fmt.Printf("The memory address of the original Person is: %p\n",&p)
	modify(p)
	fmt.Println(p)
}

type Person struct {
	Name string
}

 func modify(p Person) {
	 fmt.Printf("The memory address received from Person in the function is: %p\n",&p)
	 p.Name = "Bill"
 }
Copy the code

Run the printout:

0xc4200721c0 = 0xc4200721c0Copy the code

We found that the Person type we defined was also passed as a value when the function passed its parameters, but its value (the Name field) was not changed. We tried to change it to Li Si, but found that the result was still Zhang SAN.

This means that the map type is not the same as the struct type we define ourselves. We tried to change the receive parameter of the modify function to a pointer to Person.

func main(a) {
	p:=Person{"Zhang"}
	modify(&p)
	fmt.Println(p)
}

type Person struct {
	Name string
}

 func modify(p *Person) {
	 p.Name = "Bill"
 }
Copy the code

On running to view the output, we find that it has been modified this time. We omit the printing of the memory address here because our int example demonstrates that pointer parameters are also passed by value. Pointer types can be changed, but non-pointer types cannot. So we can make a bold guess: is the map created using make a pointer? Take a look at the source code:

// makemap implements a Go map creation make(map[k]v, hint)
// If the compiler has determined that the map or the first bucket
// can be created on the stack, h and/or bucket may be non-nil.
// If h ! = nil, the map can be created directly in h.
// If bucket ! = nil, bucket can be used as the first bucket.
func makemap(t *maptype, hint int64, h *hmap, bucket unsafe.Pointer) *hmap {
    // omit extraneous code
}
Copy the code

The SRC/Runtime /hashmap.go source code shows that, as we suspected, make returns a pointer of type hmap *hmap. That is, map===*hmap. Func modify(p *hmap) is the same as func modify(p *hmap), which is the same as func modify(IP *int).

So, in this case, Go eliminates the need for Pointers by wrapping the make function in literals, making it easier to use map. The map here can be understood as a reference type, but remember that reference types are not pass-references.

Chan type

The chan type is essentially the same as the map type. I won’t go into details here, but refer to the source code:

func makechan(t *chantype, size int64) *hchan {
    // omit extraneous code
}
Copy the code

Chan is also a reference type, similar to map. Make returns an *hchan.

Slice is different from map and Chan

Slice is not the same as map or chan in that it is also a reference type and can be modified in functions.

func main(a) {
	ages:=[]int{6.6.6}
	fmt.Printf("The original slice memory address is %p\n",ages)
	modify(ages)
	fmt.Println(ages)
}

func modify(ages []int){
	fmt.Printf("The memory address in the function to receive slice is %p\n",ages)
	ages[0] =1
}
Copy the code

Running the print, we find that it has indeed been modified, and that the memory address of the printed slice can be printed directly through %p without using the amperage conversion.

Does this prove that a make slice is also a pointer? Not necessarily. It’s possible that FMt. Printf makes slice special.

func (p *pp) fmtPointer(value reflect.Value, verb rune) {
	var u uintptr
	switch value.Kind() {
	case reflect.Chan, reflect.Func, reflect.Map, reflect.Ptr, reflect.Slice, reflect.UnsafePointer:
		u = value.Pointer()
	default:
		p.badVerb(verb)
		return
	}
	// Omit some code
}
Copy the code

According to the source code, chan, map, slice, etc. are treated as Pointers, and the Pointer of the corresponding value is obtained through value.pointer ().

// If v's Kind is Slice, the returned pointer is to the first
// element of the slice. If the slice is nil the returned value
// is 0. If the slice is empty but non-nil the return value is non-zero.
func (v Value) Pointer(a) uintptr {
	// TODO: deprecate
	k := v.kind()
	switch k {
	// omit extraneous code
	case Slice:
		return (*SliceHeader)(v.ptr).Data
	}
}
Copy the code

Obviously, in slice, the address of the first element of the slice field Data is returned.

type SliceHeader struct {
	Data uintptr
	Len  int
	Cap  int
}

type slice struct {
	array unsafe.Pointer
	len   int
	cap   int
}
Copy the code

So the address of slice variable ages printed by % P is actually the address of the internal storage array element. Slice is a mixed type of structure + element pointer. The pointer of element array(Data) can be used to modify the storage element in slice.

So you can change the content of a type in a variety of ways, either as a pointer to the type itself, or as a field within the type that has a pointer type.

From the perspective of slice, we can modify the contents of the stored elements, but len and Cap can never be modified because they are only copies, passing *slice as arguments.

func main(a) {
	i:=19
	p:=Person{name:"Zhang",age:&i}
	fmt.Println(p)
	modify(p)
	fmt.Println(p)
}

type Person struct {
	name string
	age  *int
}

func (p Person) String(a) string{
	return "Name is:" + p.name + ", age:"+ strconv.Itoa(*p.age)
}

func modify(p Person){
	p.name = "Bill"
	*p.age = 20
}
Copy the code

Run the printout as follows:

Name: Zhang SAN, age: 19 Name: Zhang SAN, age: 20Copy the code

The comparison between Person and slice makes sense: The Name field of Person is similar to the Len and CAP fields of slice, and the age field is similar to the array field. When the parameter type is non-pointer, only the age field can be modified, but the name field cannot be modified. To change the name field, change the parameter to a pointer, as in:

modify(&p)
func modify(p *Person){
	p.name = "Bill"
	*p.age = 20
}
Copy the code

So both the name and age fields are modified.

So slice is also a reference type.

summary

Finally, we can confirm that all pass arguments in Go are value pass (pass value), a copy, a copy. Because copying content is sometimes a non-reference type (int, string, struct, etc.), there is no way to modify the original content data in the function; There are reference types (Pointers, map, Slice, chan, etc.) that allow you to modify the original content data.

Whether the original content data can be modified is not necessarily related to the transmission of values and references. In C++, passing a reference can definitely change the original content data. In Go, we can change the original content data even though we only pass values, because the parameters are reference types.

Remember, too, that reference types and passing references are two different concepts.

Again, remember that Go only passes values (value passes).

This article is an original article. Please scan the code to follow the public account flysnow_org or www.flysnow.org/, and read the following wonderful articles for the first time. If you feel good, feel free to share it in moments, thanks for your support.