文章目录
- 一. 函数定义
- 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 以函数作为返回值
- 八. 总结
一. 函数定义
为什么要使用函数,因为有时候相同的功能需要重复写:
每个数值相加都得需要重新写一遍
使用函数就可以减少代码的重复,提高代码的复用性,减少代码冗余,代码维护性也提高了。
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上去的情况
练习:
计算从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)
}
执行结果:
可以看到: 无论传几个参数,都是切片类型
我们可以做它的累加和:
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)
}
注意: 如果有位置参数先传递位置参数,等没有位置参数了之后再传递不定长参数
例如:
源码如下:
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 值
}
调用函数
函数名(实际参数)
一旦有返回值,就必须要写值的类型
举例:
在不需要返回类型的时候,我们可以在终端打印
当我们要用到return 返回的时候,需要加返回值类型:
这样与终端打印有什么区别呢?
有区别,返回值还可以调用
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 返回值命名
这个知识点,面试题的常客
前面的例子中,都没有做返回值命名
而所谓的返回值命名是指的
函数定义时可以给返回值命名,并在函数体中直接使用这些变量,最后通过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 闭包
闭包是引用了*变量的函数,被引用的*变量和函数一同存在,即使已经离开了*变量的环境也不会被释放或者删除,在闭包中可以继续使用这个*变量。
说一下我自己对闭包的理解,闭包就是函数返回一个匿名函数。简单的说:函数 + 引用环境 = 闭包
闭包就是一个函数和其他相关的引用环境组合的一个整体
案例一:
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就形成了一个整体,构成了闭包。
- 闭包是一个类(),函数是操作,n是字段 ,函数和他使用到的n构成闭包
4)当我们反复的调用f函数时,因为n是初始化一次,因此没调用一次就进行更新和累加
5) 我们要搞清楚闭包的关键,就是要分析出返回的函数,它引用到哪些变量,函数和它引用到的变量共同构成了闭包
感受:匿名函数中引用的那个变量会一直保存在内存中,可以一直使用
五. 指针类型
新增一个数据类型叫指针类型
对于我们以前学的int类型,在同一个函数内,他的内存地址是一样的吗?
答案是一样的。
但问题是: 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
存地址的变量就是指针变量
使用指针类型取值:
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)
}
1) x和y地址不同
2) 如果你给Y赋值,x的值不会变切片的值拷贝会怎么样呢?
结论:
- a的地址和b的地址不同
- 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)
}
八. 总结
函数这部分内容还是比较重要的,学习起来东西也比较繁琐,接下来,我们要进入复习阶段,利用现在学的一起做做练习吧。