go语言基础-函数应用

时间:2022-06-04 01:23:23


文章目录

  • 一. 函数定义
  • 1.1 函数的定义
  • 1.2 调用方式:
  • 1.3 案例
  • 二.函数参数说明
  • 2.1 类型的简写
  • 位置参数
  • 2.2 可变参数
  • 2.3 多返回值
  • 2.3.1 正常返回值
  • 2.3.2 无返回值
  • 2.3.3 多个返回值
  • 2.4 返回值命名
  • 三.函数的作用域
  • 3.1 局部变量
  • 3.2 全局变量
  • 四. 匿名函数和闭包
  • 4.1 匿名函数
  • 4.2 闭包
  • 五. 指针类型
  • 六.值传递
  • 6.1 值拷贝
  • 6.2 函数传参
  • 6.2.1 案例1
  • 6.2.2 案例二
  • 6.2.3 案例三
  • 七.高阶函数
  • 7.1 把函数作为参数
  • 7.1.1 案例一
  • 7.1.2 案例二
  • 7.2 以函数作为返回值
  • 八. 总结

一. 函数定义

为什么要使用函数,因为有时候相同的功能需要重复写:

每个数值相加都得需要重新写一遍

go语言基础-函数应用


使用函数就可以减少代码的重复,提高代码的复用性,减少代码冗余,代码维护性也提高了。

1.1 函数的定义

函数就是:为完成某个功能的程序命令结合
go语言是通过func关键字声明一个函数的,声明语法格式如下

func 函数名(形式参数) (返回值) {
        函数体
        return 返回值   // 函数终止语句
}
func intSum(x, y int) int {
	return x + y
}

上面的代码中,intSum函数有两个参数,这两个参数的类型均为int,因此可以省略x的类型,因为y后面有类型说明,x参数也是该类型。

  • 函数名:由字母、数字、下划线组成。但函数名的第一个字母不能是数字。在同一个包内,函数名也称不能重名(包的概念详见后文)。
  • 形式参数:参数由参数变量和参数变量的类型组成,多个参数之间使用,分隔。
  • 返回值:返回值由返回值变量和其变量类型组成,也可以只写返回值的类型,多个返回值必须用()包裹,并用,分隔。
  • 函数体:实现指定功能的代码块。

1.2 调用方式:

函数名()

1.3 案例

求 1+2+3+…+100的值日

package main

import "fmt"

func sum() {

	var s = 0
	for i := 1; i <= 100; i++ {

		s = s + i
	}
	fmt.Println(s)
}
func main() {
	sum()

}

二.函数参数说明

为什么需要参数?
形参和实参是一一对应的

2.1 类型的简写

位置参数

函数的参数中如果相邻变量的类型相同,则可以省略类型,例如

package main

import "fmt"

func add(x, y int) {  //位置参数,也叫形式参数
//此处x int ,y int的写法可以缩写为 x.y int
	fmt.Println(x + y)
}
func main() {
	add(2, 5)//2,5是实际参数,有几个实际参数就有几个形式参数,一一对应

}

x,y的值是一一对应的,不能出现x传递到y上去的情况

go语言基础-函数应用

练习:
计算从1到100的值,但是希望可以通过输入两个实际参数来计算,比如计算5到100, 3到1000等等。

import "fmt"

func sum(x, y int) {
	var t = 0
	for i := x - 1; i <= y; i++ {
		t = t + i
	}
	fmt.Println(t)
}
func main() {
	sum(1, 100)
	sum(2,150)

}

2.2 可变参数

可变参数也叫不定长参数
举例:

package main

import (
	"fmt"
	"reflect"
)

func add(nums ...int) {
	fmt.Println(nums, reflect.TypeOf(nums))
}
func main() {
	add(1, 2, 3)
	add(1, 2)

}

执行结果:

go语言基础-函数应用

可以看到: 无论传几个参数,都是切片类型

我们可以做它的累加和:

package main

import (
	"fmt"
)

func add(nums ...int) {
	//fmt.Println(nums, reflect.TypeOf(nums))
	t := 0
	for _, v := range nums {
		//fmt.Println(i, v)
		t += v

	}
	fmt.Println(t)
}
func main() {
	add(1, 2, 3)
	add(1, 2)

}

注意: 如果有位置参数先传递位置参数,等没有位置参数了之后再传递不定长参数

例如:

go语言基础-函数应用


源码如下:

package main

import (
	"fmt"
	"reflect"
)

func add(author string, nums ...int) {
	fmt.Println(author, nums, reflect.TypeOf(nums))
	t := 0
	for _, v := range nums {
		//fmt.Println(i, v)
		t += v

	}
	fmt.Println(t)
}
func main() {
	add("laoxin", 1, 2, 3)
	add("laowang", 1, 2)

}

2.3 多返回值

2.3.1 正常返回值

Go语言中函数支持多返回值,函数如果有多个返回值时必须用()将所有返回值包裹起来

语法:

func calc(x, y int) (int, int) {
	sum := x + y
	sub := x - y
	return sum, sub
}

文字版:

func 函数名(形式参数1,形式参数2,...)返回值类型{
函数体
return 值
}
调用函数
函数名(实际参数)

一旦有返回值,就必须要写值的类型

举例:

在不需要返回类型的时候,我们可以在终端打印

go语言基础-函数应用


当我们要用到return 返回的时候,需要加返回值类型:

go语言基础-函数应用


这样与终端打印有什么区别呢?

有区别,返回值还可以调用

package main

import "fmt"

func add(a, b int) int {
	//fmt.Println(a + b)
	return a + b
}
func mul(x, y int) int {
	//fmt.Println(a + b)
	return x * y
}

func main() {
	ret1 := add(1, 2)  //这个值是加法最后的结果赋值给了ret1
	//fmt.Println(ret1)
	ret2 := mul(ret1, 100) //ret1又作为参数x传递给另外一个函数mul
	fmt.Println(ret2)

}

2.3.2 无返回值

把结果作为值返回,再次作为参数使用,只有需要打印的时候才会打印

注意,无返回值的不能进行运算,只完成一个调用的功能
func foo(){
fmt.println(“ok”)
}

foo() // foo函数无返回值则不可以将调用出进行复制运算

2.3.3 多个返回值

package main

import "fmt"

func get_name_age() (string, int) {

	return "laoxin", 40

}

func main() {
	name, age := get_name_age()
	fmt.Println(name, age)
}

2.4 返回值命名

这个知识点,面试题的常客

前面的例子中,都没有做返回值命名

go语言基础-函数应用


而所谓的返回值命名是指的

函数定义时可以给返回值命名,并在函数体中直接使用这些变量,最后通过return关键字返回

package main

import "fmt"

func get_name_age() (name string, age int) {

	name = "laoxin"
	age = 10
	return

}

func main() {
	name, age := get_name_age()
	fmt.Println(name, age)
}

如果我们在函数内不定义name和age,会报错吗?

不会

package main

import "fmt"

func get_name_age() (name string, age int) {

	return

}

func main() {
	name, age := get_name_age()
	fmt.Println(name, age) //得到空和0
}

可以看到得到的结果是空字符串和0, 因为string和int都是值类型,在不赋值的时候有默认值

三.函数的作用域

3.1 局部变量

一说作用域指的是变量作用域
所谓变量作用域,即变量可以作用的范围。

作用域(scope)通常来说,程序中的标识符并不是在任何位置都是有效可用的,而限定这个标识符的可用性的范围就是这个名字的作用域。

变量根据所在位置的不同可以划分为全局变量和局部变量

局部变量 :写在{}中或者函数中或者函数的形参, 都是局部变量.

package main

import "fmt"

var x = 100 //全局作用域

func foo() {

	var x = 10 //局部作用域,局部有X,就执行局部的,局部没有去拿全局的
	fmt.Println(x)

}
func bar() {

	var x = 20
	fmt.Println(x)

}

func main() {
	foo()
	
}

如果在main函数里也加个println输出的结果

package main

import "fmt"

var x = 100 //全局作用域 

func foo() {

	var x = 10 //局部作用域,局部有X,就执行局部的,局部没有去拿全局的
	fmt.Println("foo函数的x值日", x)// 结果是10

}
func bar() {

	var x = 20
	fmt.Println(x)

}

func main() {
	foo()
	fmt.Println("main的函数x", x) //结果为100

}

如果局部变量不声明会出现什么情况

package main

import "fmt"

var x = 100 //全局作用域

func foo() {

	x = 10 // 重新复制,x在foo里面没有,于是去全局拿了个x,但赋值为10,用的是全局的x
	fmt.Println("foo函数的x值日", x, &x)  // 0xa9a258 可以看到内存地址和全局的x地址一样

}
func bar() {

	var x = 20
	fmt.Println(x)

}

func main() {
	foo()
	fmt.Println("main的函数x", x, &x) // 0xa9a258

}

注意:

1.、局部变量的作用域是从定义的那一行开始, 直到遇到 } 结束或者遇到return为止

2、局部变量, 只有执行了才会分配存储空间, 只要离开作用域就会自动释放

3、局部变量存储在栈区

4、局部变量如果没有使用, 编译会报错。全局变量如果没有使用, 编译不会报错

5、:=只能用于局部变量, 不能用于全局变量
6. 全局变量定义的越少越好

3.2 全局变量

全局变量 :函数外面的就是全局变量

1、全局变量的作用域是从定义的那一行开始, 直到文件末尾为止

2、全局变量, 只要程序一启动就会分配存储空间, 只有程序关闭才会释放存储空间,

3、全局变量存储在静态区(数据区)
func foo()  {
    // var x =10
    x = 10
    fmt.Println(x)
}


var x = 30   // 全局变量

func main() {`在这里插入代码片`

    // var x = 20
    foo()
    fmt.Println(x)

四. 匿名函数和闭包

4.1 匿名函数

函数当然还可以作为返回值,但是在Go语言中函数内部不能再像之前那样定义函数了,只能定义匿名函数。匿名函数就是没有函数名的函数,匿名函数多用于实现回调函数和闭包。 匿名函数的定义格式如下:

func(参数)(返回值){
    函数体
}

匿名函数没有名字,但可以复制给其他变量

package main

import "fmt"

func main() {
	add := func(x, y int) {

		fmt.Println(x + y)
	}
	add(10, 20)
}

结果为30

匿名函数因为没有函数名,所以没办法像普通函数那样调用,所以匿名函数需要保存到某个变量或者作为立即执行函数:

作为立即执行函数的写法:

package main

import "fmt"

func main() {
	//add := func(x, y int) {
	//
	//	fmt.Println(x + y)
	//}
	//add(10, 20)
	
	func(a,b int){
		
		fmt.Println(a +b)
	}(10,30)  
	//在这个位置直接写参数
}

4.2 闭包

闭包是引用了*变量的函数,被引用的*变量和函数一同存在,即使已经离开了*变量的环境也不会被释放或者删除,在闭包中可以继续使用这个*变量。

说一下我自己对闭包的理解,闭包就是函数返回一个匿名函数。简单的说:函数 + 引用环境 = 闭包

闭包就是一个函数和其他相关的引用环境组合的一个整体

go语言基础-函数应用


案例一:

package main

import "fmt"

func returnNum() func() (int, int)  {
    return func() (int, int) {
        return 0, 1
    }
}
func main()  {
    q := returnNum()
    a, b := q()
    fmt.Println(a, b) //0, 1
}

案例二: 累加器

package main

import "fmt"

//写一个累加器
func AddUpper() func(int) int {

	var n int = 10
	return func(x int) int {
		n = n + x
		return n
	}
}
func main() {
	//使用前面的代码
	f := AddUpper()
	fmt.Println(f(1))
	fmt.Println(f(2))
	fmt.Println(f(3))
}

对上面的代码总结:

  • AddUpper是一个函数,返回的数据类型是fun(int)int
  • 闭包部分:

    返回的是一个匿名函数,但这个匿名函数引用到了函数外的n,因此这个匿名函数和n就形成了一个整体,构成了闭包。
  1. 闭包是一个类(),函数是操作,n是字段 ,函数和他使用到的n构成闭包
    4)当我们反复的调用f函数时,因为n是初始化一次,因此没调用一次就进行更新和累加
    5) 我们要搞清楚闭包的关键,就是要分析出返回的函数,它引用到哪些变量,函数和它引用到的变量共同构成了闭包

感受:匿名函数中引用的那个变量会一直保存在内存中,可以一直使用

五. 指针类型

新增一个数据类型叫指针类型

对于我们以前学的int类型,在同一个函数内,他的内存地址是一样的吗?

go语言基础-函数应用


答案是一样的。

但问题是: x是什么样的变量?
在这里x是整型变量, x存在的是整型

var s =“hello” 这时候s是一个字符串变量

package main

import (
	"fmt"
	"reflect"
)

func main() {
	//声明赋值一个整型变量
	var x = 100
	fmt.Println(&x)  //这个地址可不可以赋值给变量
	var p = &x //这个p就是指针变量
	// var p *int  
	fmt.Println(p,reflect.TypeOf(p))  //*int  和整型相关的指针变量类型
	
}

指针类型 *int *string

存地址的变量就是指针变量

go语言基础-函数应用


使用指针类型取值:

package main

import (
	"fmt"
	"reflect"
)

func main() {
	//声明赋值一个整型变量
	var x = 100
	fmt.Println(&x) //这个地址可不可以赋值给变量
	var p = &x      //这个p就是指针变量
	// var p *int
	fmt.Println(p, reflect.TypeOf(p)) //*int  和整型相关的指针变量类型

	fmt.Println(*p) // * 是取值, p是内存地址。 *p就是获取p内存地址的值,地址对应的值取出来
}

fmt.Println(*p) // * 是取值, p是内存地址。 *p就是获取p内存地址的值,地址对应的值取出来

六.值传递

6.1 值拷贝

package main

import "fmt"

func main() {
	var x = 10
	fmt.Printf("x的地址%p\n", &x)
	y := x //值拷贝
	fmt.Printf("y的地址%p\n", &y)
	fmt.Println(y)
}

go语言基础-函数应用


1) x和y地址不同

2) 如果你给Y赋值,x的值不会变切片的值拷贝会怎么样呢?

go语言基础-函数应用


结论:

  1. a的地址和b的地址不同
  2. a[0]的值变了后,b的也会变

6.2 函数传参

6.2.1 案例1

package main

import "fmt"

func func01(a int) {
	//fmt.Println(x)
	a = 100
}

func main() {

	//
	// 案例1
	var x = 10
	func01(x) // a = x ,值拷贝
	fmt.Println(x)

}

结论:
1) 不管func01函数给a赋值为多少,都不影响main函数的x值等于10

6.2.2 案例二

案例一中a=100,最终想要获取到,怎么获取

package main

import (
	"fmt"
	"reflect"
)

func func02(a *int) {
	fmt.Println(a)                      // x的地址
	fmt.Println(*a, reflect.TypeOf(*a)) //
	*a = 100
}

func main() {

	// 案例2

	var x = 10
	var p *int = &x
	fmt.Println(p)
	func02(p) // a = p
	fmt.Println(":::", x)

}

6.2.3 案例三

如果是切片类型呢

package main

import (
	"fmt"
)



func func03(s []int) {
	fmt.Printf("func02的s的地址:%p\n", &s)
	// s[0] = 100
	s = append(s, 1000)
}
func main() {
	

	// 案例3
	var s = []int{1, 2, 3}
	fmt.Printf("main的s的地址:%p\n", &s)
	func03(s)
	fmt.Println(s)

}

七.高阶函数

一个高阶函数应该具备下面至少一个特点:

  • 将一个或者多个函数作为形参
  • 返回一个函数作为其结果

首先明确一件事情:函数名亦是一个变量

时间操作:

打印时间戳:

package main

import (
	"fmt"
	"time"
)

func main() {

	fmt.Println(time.Now().Unix())  //打印时间戳
    time.Sleep(time.Second * 2)  //停2秒钟
	fmt.Println(time.Now().Unix())
}

两种情况是高级函数,一种是把函数作为参数,一种是把函数作为返回值

7.1 把函数作为参数

7.1.1 案例一

package main

import (
	"fmt"
	"time"
)

func bar() {
	time.Sleep(time.Second * 3)
	fmt.Println("bar")
}

func foo() {
	time.Sleep(time.Second * 2)
	fmt.Println("foo")
}

func funcTimer(f func()) {

	t1 := time.Now().Unix()
	//bar()
	//foo()
	f()
	t2 := time.Now().Unix()
	fmt.Println("spend time:", t2-t1)

}

func main() {
	

	// (1) 以函数为参数

	funcTimer(foo)
	funcTimer(bar)

}

funcTimer 以函数作为参数,所以他就是高阶函数

7.1.2 案例二

双值计算器

package main

import "fmt"

func add(x, y int) int {
	return x + y
}

func mul(x, y int) int {
	return x * y
}

func caa(x, y int) int {
	return x - y
}

func cal(a, b int, calFunc func(int, int) int) { //3个形参,2个int的,一个函数的

	ret := calFunc(a, b)
	fmt.Println(ret)

}
func main() {

	cal(10, 5, mul)
	cal(111, 20, add)

}

7.2 以函数作为返回值

package main

import "fmt"

func foo() func(int, int) string {
	// 方式1
	/*var bar = func(x, y int) string {
		fmt.Println("bar...")
		return "bar"
	}

	return bar*/
	// 方式2
	return func(x, y int) string {
		fmt.Println("bar...")
		return "bar"
	}

}

func main() {
	var ret = foo()
	ret(1, 2)

}

八. 总结

函数这部分内容还是比较重要的,学习起来东西也比较繁琐,接下来,我们要进入复习阶段,利用现在学的一起做做练习吧。