文章对https://www.cnblogs.com/skymyyang/p/7659775.html 进行引用。膜拜
https://www.cnblogs.com/xbblogs/p/11102970.html 该文章介绍了值传递,引用传递、指针传递的区别。给大佬撒花。
https://blog.csdn.net/weixin_42255190/article/details/97944113 值传递与指针传递的区别,很白话
//函数的定义:
//函数分为2部分:func(A) (B){} 其中A是输入的自变量,必须; B是输出,可选
//A: 当A中的自变量类型相同时,可以写 a,b,c int;不同时, a int, b string, c float
//B: 写变量的类型:int, string, float
func A(a,b,c int )(d,e,f int) {}
func B(a int,b string){}
func C()(a,b,c int) {}
不定长变参
//由于输入的参数的长度无法计算,那么可以使用不定长变参 a...int表示。并且不定长变参必须是最后一个参数
由于不定长变参的引入,那么实参的引用就由几种可能:
- 值传递
- 引用传递
- 指针传递
《值类型》:
int、float、bool、array、sturct等
值传递是指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数
声明一个值类型变量时,编译器会在栈中分配一个空间,空间里存储的就是该变量的值
《感觉这个值类型,就像是Python中的局部变量。因此,在值传递作为函数参数,在被调用函数中引用值传递参数时候,需要重新为值传递参数开辟新的用于存放值传递参数的内存空间。内存调用的是不同地址的,变量名是一样的,值》
《引用类型》:
slice,map,channel,interface,func,string等
声明一个引用类型的变量,编译器会把实例的内存分配在堆上
string和其他语言一样,是引用类型,string的底层实现struct String { byte* str; intgo len; }; 但是因为string不允许修改,每次操作string只能生成新的对象,所以在看起来使用时像值类型。
所谓引用传递是指在调用函数时将实际参数的地址传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数。
需要注意的是:引用类型在函数的内部可以对它的值进行修改,但是如果给形参重新赋值,重新赋值后的形参再怎么修改都不会影响外面的实参了
《指针类型》:
一个指针变量指向了一个值的内存地址
当一个指针被定义后没有分配到任何变量时,它的值为 nil。nil 指针也称为空指针
一个指针变量通常缩写为 ptr
其实引用类型可以看作对指针的封装
package main
import ("fmt")
func main() {
s1:= []int{1,2,3,4}
a,b :=1,2
A(a,b)
fmt.Println(a,b)
B(s1)
fmt.Println(s1)
}
func A(a ...int) {
//这里传进来的实际上是一个slice,引用类型
a[0] = 3
a[1] = 4
//尽管我们在函数A当中接收到的是一个slice,但是它得到的是一个值拷贝
//和直接传递一个slice的区别看函数B
fmt.Println(a)
}
func B(s []int) {
//这里并不是传递一个指针进去,而是对这个slice的内存地址进行了一个拷贝
//这里还可以看到像int型、string型进行常规的参数传进去的话,只是进行了个值拷贝,slice传进去虽然也是拷贝,但是它是内存地址的拷贝
s[0] = 4
s[1] = 5
s[2] = 6
s[3] = 7
fmt.Println(s)
//在这里 我们看到我们在函数B当中的修改,实际上影响到了我们main函数当中的变量s1
//如果直接传递一个slice,它的修改就会影响到这个slice的本身
}
PS:值类型和引用类型进行函数传参拷贝是不一样的,一个是拷贝值,一个是拷贝地址
//[3 4]
//1 2
//[4 5 6 7]
//[4 5 6 7]
例子2:
写在最开头:https://studygolang.com/articles/7412
& 是取地址符号 , 即取得某个变量的地址 , 如 ; &a
*是指针运算符 , 可以表示一个变量是指针类型 , 也可以表示一个指针变量所指向的存储单元 , 也就是这个地址所存储的值 .
经常会见到: p . *p , &p 三个符号
p是一个指针变量的名字,表示此指针变量指向的内存地址,如果使用%p来输出的话,它将是一个16进制数。
而*p表示此指针指向的内存地址中存放的内容,一般是一个和指针类型一致的变量或者常量。
而我们知道,&是取地址运算符,&p就是取指针p的地址。
等会,怎么又来了个地址,它到底和p有什么区别?区别在于,指针p同时也是个变量,既然是变量,编译器肯定要为其分配内存地址,就像程序中定义了一个int型的变量i,编译器要为其分配一块内存空间一样。而&p就表示编译器为变量p分配的内存地址,而因为p是一个指针变量,这种特殊的身份注定了它要指向另外一个内存地址,程序员按照程序的需要让它指向一个内存地址,这个它指向的内存地址就用p表示。而且,p指向的地址中的内容就用*p表示。
package main
import "fmt"
func value(ss string) {
fmt.Println("value():")
fmt.Println("ss = ", ss) //golang
fmt.Println("&ss = ", &ss) //重新分配一块不同于Main函数的新的内存地址
fmt.Println("=====================")
ss = "Kotlin" //重新赋值字符串,引用类型
fmt.Println("ss = ", ss) //Kotlin
fmt.Println("&ss = ", &ss) //分配的是&ss的内存地址,因此在相同一块内存地址上写数据,原有数据被覆盖
}
func value2(ss string) {
fmt.Println("value2():")
fmt.Println("ss = ", ss) //在value中最后的输出是Kotlin,但是没有return ,因此在Value2的输入仍是golang
fmt.Println("&ss = ", &ss) ////重新分配一块不同于Main,不同于value函数的新的内存地址
fmt.Println("=====================")
ss = "Kotlin"
fmt.Println("ss = ", ss) //在同一个内存地址进行操作,覆盖原有值
fmt.Println("&ss = ", &ss) //同一个内存
}
func pointer(ps *string) {
fmt.Println("pointer():")
fmt.Println("ps = ", ps) //把golang的指针打出来
fmt.Println("&ps = ", &ps) //对*string的指针取地址
fmt.Println("*ps = ", *ps) //对*string的指针取值
fmt.Println("=====================")
*ps = "Kotlin"
fmt.Println("ps = ", ps)
fmt.Println("&ps = ", &ps)
fmt.Println("*ps = ", *ps)
}
func pointer2(ps *string) {
fmt.Println("pointer2():")
fmt.Println("ps = ", ps)
fmt.Println("&ps = ", &ps)
fmt.Println("*ps = ", *ps)
fmt.Println("=====================")
*ps = "Kotlin"
fmt.Println("ps = ", ps)
fmt.Println("&ps = ", &ps)
fmt.Println("*ps = ", *ps)
}
func main() {
s := "golang"
fmt.Println("s = ", s) //golang
fmt.Println("&s = ", &s) //s的内存地址
fmt.Println("__________________________________________________")
value(s) //字符串类型,引用传递
fmt.Println("__________________________________________________")
value2(s) //字符串类型,引用传递
fmt.Println("__________________________________________________")
pointer(&s)
fmt.Println("__________________________________________________")
pointer2(&s) //对地址进行引用
}
输出
s = golang
&s = 0xc0000881e0
__________________________________________________
value():
ss = golang
&ss = 0xc000088200
=====================
ss = Kotlin
&ss = 0xc000088200
__________________________________________________
value2():
ss = golang
&ss = 0xc000088230
=====================
ss = Kotlin
&ss = 0xc000088230
__________________________________________________
pointer():
ps = 0xc0000881e0
&ps = 0xc0000cc020
*ps = golang
=====================
ps = 0xc0000881e0
&ps = 0xc0000cc020
*ps = Kotlin
__________________________________________________
pointer2():
ps = 0xc0000881e0
&ps = 0xc0000cc028
*ps = Kotlin
=====================
ps = 0xc0000881e0
&ps = 0xc0000cc028
*ps = Kotlin
Process finished with exit code 0
例子3:
package main
import "fmt"
func paramFunc(a int, b *int, c []int) {
a = 100
*b = 200
c[1] = 300
fmt.Println("paramFunc:")
fmt.Println(a)
fmt.Println(*b)
fmt.Println(c)
}
func main() {
a := 1 //int
b := 1 //int
c := []int{1, 2, 3} //slide
paramFunc(a, &b, c)
fmt.Println("main:")
fmt.Println(a) //1
fmt.Println(b) //
fmt.Println(c)//
}
结果:
paramFunc:
100
200
[1 300 3] //100(分配的值), &b是b的内存地址, ,*&b 表示b的地址的指针的值,=200,
//在c所在的内存地址上已经覆盖了200
main:
1
200
[1 300 3] //[1,300,3], 在内存地址上直接覆盖
Process finished with exit code 0
匿名函数
匿名函数如其名字一样,是一个没有名字的函数,除了没有名字外其他地方与正常函数相同。匿名函数可以直接调用,保存到变量,作为参数或者返回值
这个非常类似于Python中的lambda。
package main
import "fmt"
func main() {
f:= func() (string){
return "hello kitty"
}
fmt.Println(f())
}
结果:
hello kitty
Process finished with exit code 0