欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

2.js和go函数对比

程序员文章站 2024-02-03 10:05:40
...

函数定义

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的语法格式。函数的不定参数有如下几个特点:

  1. 所有的不定参数类型必须是相同的。
  2. 不定参数必须是函数的最后一个参数。
  3. 不定参数名再函数体内相当于碎片,对切片的操作同样适合对不定参数的操作。例如:
func sum(arr...int)(sum int){
	for _,v:range arr{
		sum+=v;
	}
	return
}
  1. 切片可以作为参数传递给不定参数,切片名后要加上"…"。例如:
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))
}
  1. 形参为不定参数的函数和形参为切片的函数类型不相同
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中

  1. 多次调用该函数,返回的多个闭包所引用的外部变量时多个副本,原因是每次调用该函数都会为局部变量分配内存。
  2. 用一个闭包函数多次,如果该闭包修改了其引用的外部变量,则每次调用该闭包对该外部变量都有影响,因为闭包函数共享外部引用。
    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入门