2.js和go函数对比
函数定义
js函数中参数并没有规定严格的类型,也可以没有返回值,其中参数还是一个是伪
数组(argumnet)。
1.命名函数
function A(){
}
function B(a){
}
function c(a){
return a
}
2.函数表达式
let A=fucntion(){
}
go函数是go程序源代码的基本构造单位,一个函数的定义包括如下几个部分;函数声明关键字func,函数名,返回列表和函数体。函数名遵循标识符的命名规则,首字母的大小写决定该函数再其他包的可见性:大写时其他包可见,小写时只有相同的包可以访问;函数的参数和返回值需要使用"()“包裹,如果只有一个返回值,而且使用的非命名的参数,则返回参数的”()“可以省略。函数体使用”{}"包裹
func funcName(param-list)(result-list){
function-body
}
函数的特点
1.函数可以没有输入参数,也可以没有返回值(默认返回0)。
func A(){
}
func A(){
return 1
}
2.多个相邻的相同类型的参数可以使用简写模式。例如:
func add(a,b int)int{
return a+b
}
3.支持有名的返回值,参数名就相当于函数体内最外层的局部变量,命名返回值变量会被初始化为类型零值,最后的return可以不带参数名直接返回。
func add(a,b int)(sum int){
sum=a+b
return //return sum的简写
}
4.不支持默认值参数
5.不支持函数重载
6不支持函数嵌套,严格地说是不支持命名函数的嵌套定义,但支持嵌套匿名函数:
func add(a,b int)(sum int){
b:=func(x,y int)int{
return x+y
}
return b(a,b)
}
多值返回
func swap(a,b int)(int int){
return b,a
}
实参到行参的传递
js中所有函数的参数都是按值传递的, 也就是说把函数外部的值复制给函数内部的参数, 就如同把一个变量赋值给另一个变量一样.
- 基本类型值传递如同基本类型变量复制一样
- 引用类型则同引用类型变量的复制一样
function handleObj(obj) {
obj.name = 'jack';
obj = new Object();
obj.name = 'ruth'
console.log('FNobj:' + JSON.stringify(obj.name)); // ruth
}
var person = new Object();
handleObj(person);
console.log('person:' + JSON.stringify(person.name)); // jack
go函数实参到形参的传递永远是值拷贝,有时函数调用后实参指向的值发生了变化,那是因为参数传递的是指针值的拷贝,实参是一个指针变量,传递给形参的是这个指针变量的副本,二者指向同一地址,本质上参数传递仍然是值拷贝。例如:
package main
import "fmt"
func chvalue(a int)int{
a=a+1
return a
}
func chpointer(a *int){
*a=*a+1
return
}
func main() {
a:=10
chvalue(a) //实参传递给形参是值拷贝
fmt.Println(a)
chpointer(&a) //实参传递给形参是值拷贝,只不过复制的是a的地址值
fmt.Println(a)
//10
//11
}
不定参数
js中为argument(不是数组),ES6中的rest参数
go中函数支持不定数目的形式参数,不定参数声明使用param…type的语法格式。函数的不定参数有如下几个特点:
- 所有的不定参数类型必须是相同的。
- 不定参数必须是函数的最后一个参数。
- 不定参数名再函数体内相当于碎片,对切片的操作同样适合对不定参数的操作。例如:
func sum(arr...int)(sum int){
for _,v:range arr{
sum+=v;
}
return
}
- 切片可以作为参数传递给不定参数,切片名后要加上"…"。例如:
package main
import "fmt"
//不定参数
func sum(arr...int)(sum int){
for _,v:=range arr{
sum+=v
}
return
}
//参数为数组
func sum2(arr [4]int)(sum int){
for _,v:=range arr{
sum+=v
}
return
}
//参数为切片
func sum3(arr []int)(sum int){
for _,v:=range arr{
sum+=v
}
return
}
func main(){
slice:=[]int{1,2,3,4}
array:=[...]int{1,2,3,4}
fmt.Println(sum(slice...))
fmt.Println(sum3(slice))
fmt.Println(sum2(array))
}
- 形参为不定参数的函数和形参为切片的函数类型不相同
import "fmt"
func suma(arr...int)(sum int){
for v:=range arr{
sum+=v
}
return
}
func sumb(arr []int)(sum int){
for v:=range arr{
sum+=v
}
return
}
func main(){
fmt.Printf("%T\n",suma) //func(...int)int
fmt.Printf("%T\n",sumb) //func([]int)int
}
函数签名
函数类型又叫函数签名,一个函数的类型就是函数定义首行去掉函数名,参数名和{,可以使用fmt.Printf的"%T"格式化参数打印函数的类型
package main
import "fmt"
func add(a,b int)int{
return a+b
}
func main(){
fmt.Printf("%T\n",add) //func(int,int)int
}
两个函数类型相同的条件是:拥有相同的参数列表和返回值列表(列表元素的次序.个数和类型都相同),形参名可以不同。以下两个函数的函数类型完全一样。
func add(a,b int)int{ return a+b}
func sub(x int,y int)(c int){c=x-y;return c}
可以使用type定义函数类型,函数类型变量可以作为函数的参数或返回值。
package main
import "fmt"
var sum = func(a, b int) int {
return a + b
}
func doinput(f func(int, int) int, a, b int) int {
return f(a, b)
}
func wrap(op string) func(int, int) int {
switch op {
case "add":
return func(a, b int) int {
return a + b
}
case "sub":
return func(a, b int) int {
return a - b
}
default:
return nil
}
}
func main() {
//匿名函数直接调用
defer func() {
if err := recover(); err != nil {
fmt.Println(err)
}
}()
sum(1,2)
doinput(func(x,y int)int{
return x+y
},1,2)
opFunc:=wrap("add")
re:=opFunc(2,3)
fmt.Printf("%d\n",re)
}
defer
go函数例提供了defer关键字,可以注册多个延迟调用,这些调用以先进后出的额顺序在函数返回前被执行。类似于js里的setTimeOut().
package main
func main() {
defer func() {
println("1")
}()
defer func() {
println("2")
}()
println("3")
}
//3
//2
//1
defer后面必须是函数或方法的调用,不能是语句,否则会报expression in defer must funcion call错误
defer函数的实参在注册时通过值拷贝进去。下面示例代码中,实参a的值在defer注册时通过值拷贝传递进去,后续语句a++并不会影响defer语句最后的输出结果
package main
import "fmt"
func main() {
a:=f()
fmt.Println(a)
}
func f()int{
a:=0
defer func(i int) {
fmt.Println("defer i=",i)
}(a)
a++
return a
}
defer 语句必须先注册后才能执行,如果defer位于return之后,则defer因为没有注册,不会执行。
func main() {
defer func() {
println("1")
}()
a:=0
println(a)
return
defer func() {
println("2")
}()
}
//0
//1
主动调用os.Exit(int)退出进程时,defer将不再执行(即使defer已经提前注册)
package main
import "os"
func main() {
defer func() {
println("defer")
}()
println("hh")
os.Exit(1)
}
//hh
//exit status 1
defer可以避免内存泄漏和关闭资源…待续
闭包
这个再js中也是非常重要的
一、闭包的定义
能访问其他函数内变量的函数,这种结构就是闭包
二、闭包的用途
1.读取函数内部的变量
2.让这些变量的值始终保持在内存中(结果缓存)
3.创建匿名自执行函数(避免全局变量的污染)
三、闭包的弊端
1.使用不当会很容易造成内存泄露
2.常驻内存,增加内存使用量
使用闭包创建一个单例模式
function single(fn) {
var obj;
return function() {
return obj || (obj = fn.apply(this));
};
}
function A() {
console.log("hh");
return this;
}
var a = single(A);
a();
a();
a();
//hh
还有许多骚操作…
在go中
- 多次调用该函数,返回的多个闭包所引用的外部变量时多个副本,原因是每次调用该函数都会为局部变量分配内存。
- 用一个闭包函数多次,如果该闭包修改了其引用的外部变量,则每次调用该闭包对该外部变量都有影响,因为闭包函数共享外部引用。
example:
package main
func fa(a int)func(i int)int{
return func(i int) int {
println(&a,a)
a=a+1
return a
}
}
func main(){
f:=fa(1) //f引用的外部的闭包环境包括本次函数调用的形参a的值1
g:=fa(1) ////g引用的外部的闭包环境包括本次函数调用的形参a的值1
//此时f.g引用的闭包环境中的a的值并不是同一个,而是两次函数调用产生的结果
println(f(1)) //0xc00000a048 1 2
//多次调用f引用的是同一个副本a
println(f(1)) //0xc00000a048 2 3
println(g(1)) //0xc00000a060 1 2
println(g(1)) //0xc00000a060 2 3
}
如果引用的是全局变量,每次调用都会影响全局变量,这是不提倡的方式
同一个函数返回的多个闭包共享该函数的局部变量。example:
package main
func fa(base int)(func(int) int,func(int) int){
println(&base,base)
add:= func(i int)int {
base+=i
println(&base,base)
return base
}
sub:=func(i int) int{
base-=i
println(&base,base)
return base
}
return add,sub
}
func main(){
f,g:=fa(0)
s,k:=fa(0)
println("----")
println(f(1),g(2))
println(s(1),k(2))
}
/*
0xc00000a060 0
----
0xc00000a048 1
0xc00000a048 -1
1 -1
0xc00000a060 1
0xc00000a060 -1
1 -1
*/
panic和recover
panic用来主动抛出错误,recover用来捕获panic抛出的错误。
recover()用来捕获panic,阻止panic继续向上传递。recover()和defer一起使用,但是recover()只有在defer后面的函数体内被直接调用才能捕获panic终止异常,否则返回nil,异常继续向外传递。
上一篇: go语言学习笔记(一)
下一篇: python之json模块