最近在学习golang的时候发现一件有趣的事情,go有一个闭包的概念,于是我对比了一下C#的"闭包"...
calc 是一个接收一个形参a、两个函数返回值的函数,两个函数返回值是指这一段代码:(func(int) int, func(int) int),go语言支持多个返回值
main 函数,所有语言一样程序的入口
【1】 通过main函数调用calc函数执行返回两个函数,分别是f1和f2,同时会打印一边参数a的内存地址
【2】 调用f1函数和f2函数,打印一遍add和sub里面"引用"calc函数参数a的内存地址,并返回calc参数a+=i和a-=i的结果
【3】 输出f1函数和f2函数执行返回的结果
1 func calc(a int) (func(int) int, func(int) int) { 2 //打印变量地址 3 fmt.Println(&a) 4 5 //创建a+=add函数入参i,并返回的a的结果的函数,并返回给调用者 6 add := func(i int) int { 7 //返回值函数内部打印一遍变量地址,测试这里面的a和calc的a内存地址是否一致 8 fmt.Println(&a) 9 a += i 10 return a 11 } 12 //创建a-=sub函数入参i,并返回的a的结果的函数,并返回给调用者 13 sub := func(i int) int { 14 fmt.Println(&a) 15 a -= i 16 return a 17 } 18 //返回两个函数 19 return add, sub 20 } 21 22 func main() { 23 //f1对应add f2对应sub 24 f1, f2 := calc(10) 25 //调用f1和f2函数,同时打印f1 f2返回结果 26 fmt.Println(f1(1), f2(2)) //11 9 27 fmt.Println(f1(3), f2(4)) //12 8 28 fmt.Println(f1(5), f2(6)) //13 7 29 }
打印结果:
0xc00000a0a8 调用calc函数所打印 fmt.Println(&a) 的结果
0xc00000a0a8 调用f1(1) add里面的 fmt.Println(&a)
0xc00000a0a8 调用f2(2) sub里面的 fmt.Println(&a)
11 9 f1(1)和f2(2) 返回值
0xc00000a0a8 调用f1(3)
0xc00000a0a8 调用f1(4)
12 8 f1(3)和f2(4) 返回值
0xc00000a0a8 调用f1(5)
0xc00000a0a8 调用f1(6)
13 7 f1(5)和f2(6) 返回值
是不是很惊讶,calc函数参数a这个值变量变成了引用关系,在calc函数执行完之后并没有释放,并供给add和sub使用,这就是go的闭包。
通过下面代码能“实现”上面golang的闭包
1 public void Calc(int a, out Func<int, int> add, out Func<int, int> sub) 2 { 3 add = (int i) => 4 { 5 return a += i; 6 }; 7 8 sub = (int i) => 9 { 10 return a -= i; 11 }; 12 } 13 14 public void Show() 15 { 16 Func<int, int> f1; 17 Func<int, int> f2; 18 this.Calc(10, out f1, out f2); 19 20 Console.WriteLine(string.Format("{0} {1}", f1(1), f2(2))); 21 Console.WriteLine(string.Format("{0} {1}", f1(3), f2(4))); 22 Console.WriteLine(string.Format("{0} {1}", f1(5), f2(6))); 23 }
返回值打印结果:
11 9
12 8
13 7
得出结论确实是能实现和golang上面代码一样的功能,calc函数参数a变量变成"引用"关系,提供给add和sub使用。
但是...我将代码改了下
1 public class TestFunc 2 { 3 //存储内存地址的字典,供后续查看 4 private unsafe Dictionary<string, int*[]> PADic = new Dictionary<string, int*[]>(); 5 6 public void Calc(int a, out Func<int, int> add, out Func<int, int> sub) 7 { 8 //运行执行不安全的代码 9 unsafe 10 { 11 //取a的内存地址 12 int* aa = (int*)a; 13 //将内存地址存储到字典中 14 this.PADic.Add("Calc(a):", new int*[] { aa }); 15 } 16 17 add = (int i) => 18 { 19 unsafe 20 { 21 int* aa = (int*)a; 22 this.PADic.Add(string.Format("f1({0}):", i), new int*[] { aa }); 23 } 24 return a += i; 25 }; 26 27 sub = (int i) => 28 { 29 unsafe 30 { 31 int* aa = (int*)a; 32 this.PADic.Add(string.Format("f2({0}):", i), new int*[] { aa }); 33 } 34 return a -= i; 35 }; 36 } 37 38 public void Show() 39 { 40 Func<int, int> f1; 41 Func<int, int> f2; 42 this.Calc(10, out f1, out f2); 43 Console.WriteLine(string.Format("{0} {1}", f1(1), f2(2))); // 11 9 44 Console.WriteLine(string.Format("{0} {1}", f1(3), f2(4))); // 12 8 45 Console.WriteLine(string.Format("{0} {1}", f1(5), f2(6))); // 13 7 46 } 47 }
(指针地址没办法输出,直接调试看变量内容)输出结果:
通过上面代码可以看出,每次调用f1和f2的时候,所"引用"的参数a是“值复制”的,而不是引用,因为它们的内存地址是有变化。
得出结论,功能虽然都能实现,但是有本质区别的。