函数格式
- C语言中
在C语言中函数只能返回一个值
返回值类型 函数名称(形参列表){
逻辑语句;
}
- Go语言中
在Go语言中函数可以返回多个值
func 函数名称(形参列表) (返回值列表){
逻辑语句;
}
package main
import "fmt"
func main() {
//1.返回一个值
var num int = getValue()
fmt.Printf("%d\n", num)
//2.返回两个值
num1, num2 := calculate()
fmt.Printf("%d, %d\n", num1, num2)
}
func getValue()(int) {
return 666
}
func calculate()(int, int){
return 10, 20
}
C语言函数的类型
- 1.没有返回值没有形参的函数
- .2.有返回值没有形参的函数
- 3.没有返回值有形参的函数
- 4.有返回值有形参的函数
Go语言函数的类型和C语言一样
注意点:
- 在C语言中如果函数的定义写在函数的调用之前必须先声明再调用
- 在Go语言中如果函数的定义写在函数的调用之前, 不用声明就可以调用, Go语言没有声明(处于同级作用域的情况)
- 定义在全局作用域中任何地方的函数,main函数中可以任意调用
- Go语言函数可以没有返回值或形参
//没有返回值没有形参
func say(){
fmt.Println("没有返回值没有形参")
}
//没有返回值有形参的函数
func sum(a , b int){
res := a + b
fmt.Println(res)
}
//有返回值有形参的函数
func minus(a, b int)(res int){
res = a - b
return
}
- Go语言中如果函数只有一个返回值, 那么返回值列表的()可以省略
func minus(a int, b int)(int){
res := a - b
return res
}
//省略
func getNumber()int{
return 888
}
- Go语言中如果有返回值, 可以在返回值列表中定义变量, 然后在函数体中直接使用
func getValue() (num ,value int){
return 666, 888
//注意:外界要定义两个变量接收
}
- 如果在返回值列表中定义了变量, 那么return后面可以不用写任何内容, 默认就会将返回值列表中定义的变量返回
func getNumber() (num int){
num = 999
return
}
- Go语言中一个函数可以有多个返回值
func getValue() (int, int){
return 666, 888
}
- 如果一个函数有多个返回值, 但是我们只想使用其中的一个, 那么可以在接收的时候使用_来忽略, 不需要的那个返回值
package main
import "fmt"
func main() {
a, _ := getValue()
fmt.Println("a = ", a)
}
func getValue() (num ,value int){
return 666, 888
}
- 因为下划线在Go语言中有特殊的含义, 所以在定义标识符的时候下划线不能单独作为标识符
- 如果返回值列表中有多个返回值, 并且连续的多个返回值的数据类型都一样, 那么只用在最后一个变量后面添加数据类型即可
- 如果形参列表中有多个形参, 并且连续的多个形参的数据类型都一样, 那么只用在最后一个变量后面添加数据类型即可
函数参数
- 在C语言中如果基本数据类型作为函数的参数, 是值传递, 在函数内修改形参不会影响到外界的实参
package main
import "fmt"
func main() {
num := 666
change1(num)
fmt.Println(num) //666
}
func change1(a int) {
a = 789
}
- Go语言和C语言一样/ int float bool
- 如果想在函数内部修改外界传入的实参, 那么必须传入指针
package main
import "fmt"
func main() {
num := 666
change2(&num)
fmt.Println(num) //789
}
func change2(a *int) {
*a = 789
}
匿名函数
什么是匿名函数?
普通的函数都是有名字的函数, 而匿名函数就是没有名字的函数匿名函数的应用场景:
一般情况下某个函数只会被调用一次, 我们就会使用匿名函数
// 直接定义一个函数, 然后立即调用这个函数
func(){
fmt.Println("匿名函数")
}()
1、作为其他函数的参数
package main
import "fmt"
func main() {
//匿名函数作其他函数参数
result := calculate(10, 20, func(a, b int) int {
return a + b
})
}
//定义函数时将匿名函数类型作为参数传递,此时的函数有临时的名字,即method
func calculate(a, b int, method func(int, int) int) (int) {
res := method(a, b)
return res
}
package main
import "fmt"
func main() {
//此处限定一个
test(func() {
fmt.Println("匿名函数")
})
}
func test(method func()) {
fmt.Printf("%p\n", method) //0x48d160
method()
}
2、作为其他函数的返回值
package main
import "fmt"
func main() {
res := test()
res(10, 20)
}
func test() func(int, int) {
return func(a int, b int) {
fmt.Println(a + b)
}
}
注意点:
- 匿名函数定义之后, 如果没有立即调用, 编译器会报错
- 可以将一个匿名函数保存到一个变量中
- 在C语言中我们知道, 我们可以定义一个指向函数的指针来保存函数的地址, 然后通过指针来调用函数
int test(int a, int b){}
int (*test)(int, int)
- 在Go语言中函数是一等公民(可以定义函数类型变量, 作为形参, 作为返回值), 所以在Go语言中也可以用函数类型来定义变量(相当于C语言中指向函数的指针)
- 由于函数是一等公民, 所以以前定义变量所有的格式, 都可以用于定义函数类型的变量
格式 - 格式一
var 变量名称 函数类型
// 代表定义了一个名称叫做fn的变量, 这个变量的类型是一个函数 func(int, int) int
// 这个函数可以接收两个int类型的形参, 并且可以返回一个int类型的结果
// 也就是说变量fn将来可以保存所有接收两个int类型形参返回一个int类型结果的函数
var fn func(int, int) int
fn = func(a int, b int) int {
return a + b
}
- 格式二
var 变量名称 函数类型 = 匿名函数
var fn func(int, int) int = func(a int, b int) int {
return a + b
}
// 相当于
func fn(a int, b int) int {
return a + b
}
fmt.Printf("%T\n", fn) // func(int, int) int
- 格式三
var 变量名称 = 匿名函数
var f= func() {
fmt.Println("匿名函数")
}
- 格式四
变量名称 := 匿名函数
// 定义一个函数类型的变量, 保存匿名函数, 然后通过函数类型的变量调用匿名函数
f := func() {
fmt.Println("匿名函数")
}
fmt.Printf("%T\n", f) //func()
- 格式五
var(
var 变量名称 = 匿名函数
) - 同类型的函数类型变量才可以相互赋值
var fn func(int,int)int
fn = func(a int, b int) int{
return a + b
}
var method func(int, int)int
method = fn
- 匿名函数demo
package main
import (
"fmt"
)
func main() {
//work(func() {
// fmt.Println("参与部门会议")
// fmt.Println("修改BUG")
// fmt.Println("完成老大安排的任务")
// fmt.Println("... ...")
// fmt.Println("提交代码")
//})
work(func() {
fmt.Println("组织部门会议")
fmt.Println("给部门的员工分配任务")
fmt.Println("检查部门员工昨天提交的代码")
fmt.Println("... ...")
})
}
// 攻城狮的一天
func work(method func()){
// 1.上班前
func(){
fmt.Println("起床")
fmt.Println("刷牙洗脸")
fmt.Println("吃早餐")
fmt.Println("做地铁")
fmt.Println("打卡")
fmt.Println("打开电脑")
}()
// 2.上班中
method()
// 3.下班之后
func(){
fmt.Println("关电脑")
fmt.Println("打卡")
fmt.Println("坐地铁")
fmt.Println("到家")
fmt.Println("吃饭")
fmt.Println("娱乐")
fmt.Println("刷牙洗脸")
fmt.Println("睡觉")
}()
}
// 项目经理的一天
func work2(){
fmt.Println("起床")
fmt.Println("刷牙洗脸")
fmt.Println("吃早餐")
fmt.Println("做地铁")
fmt.Println("打卡")
fmt.Println("打开电脑")
fmt.Println("组织部门会议")
fmt.Println("给部门的员工分配任务")
fmt.Println("检查部门员工昨天提交的代码")
fmt.Println("... ...")
fmt.Println("关电脑")
fmt.Println("打卡")
fmt.Println("坐地铁")
fmt.Println("到家")
fmt.Println("吃饭")
fmt.Println("娱乐")
fmt.Println("刷牙洗脸")
fmt.Println("睡觉")
}
// 程序员的一天
func work1() {
fmt.Println("起床")
fmt.Println("刷牙洗脸")
fmt.Println("吃早餐")
fmt.Println("做地铁")
fmt.Println("打卡")
fmt.Println("打开电脑")
fmt.Println("参与部门会议")
fmt.Println("修改BUG")
fmt.Println("完成老大安排的任务")
fmt.Println("... ...")
fmt.Println("提交代码")
fmt.Println("关电脑")
fmt.Println("打卡")
fmt.Println("坐地铁")
fmt.Println("到家")
fmt.Println("吃饭")
fmt.Println("娱乐")
fmt.Println("刷牙洗脸")
fmt.Println("睡觉")
}
闭包
- 什么是闭包?
闭包是一个特殊的匿名函数, 它是匿名函数和相关引用环境组成的一个整体,只要匿名函数中用到了外界的变量, 那么我们就把匿名函数叫做闭包 - 只要闭包还在使用外界的变量, 那么外界的变量就会一直存在(和js作用域链有异曲同工之妙)
package main
import "fmt"
func main() {
num := 10
a := func() {
num++
fmt.Println(num)
}
a() // 11
a() // 12
a() // 13
// addUpper会返回一个闭包, 那么fn中保存的就是这个闭包, 那么fn就等于闭包
// 所以按照Go语言的规则, 只要fn还在, addUpper中被闭包引用的变量就不会释放
fn := addUpper()
fn()
fn()
fn()
}
func addUpper() func() {
x := 1
return func() {
x++
fmt.Println(x) // 2 , 3 , 4
}
}
延迟调用
- Go语言中没有提供其它面向对象语言的析构函数, 但是Go语言提供了defer语句用于实现其它面向对象语言析构函数的功能
- defer语句常用于
释放资源
、解除锁定
以及错误处理
等- 例如C语言中我们申请了一块内存空间,那么不使用时我们就必须释放这块存储空间
- 例如C语言中我们打开了一个文件,那么我们不使用时就要关闭这个文件
- 例如C语言中我们打开了一个数据库, 那么我们不使用时就要关闭这个数据库
- 这一类的操作在Go语言中都可以通过defer语句来完成
- 无论你在什么地方注册defer语句,它都会在所属函数执行完毕之后才会执行, 并且如果注册了多个defer语句,那么它们会按照
后进先出
的原则执行- 正是因为defer语句的这种特性, 所以在Go语言中关闭资源不用像C语言那样用完了再关闭, 我们完全可以打开的同时就关闭, 因为无论如何defer语句都会在所属函数执行完毕之后才会执行
package main
import "fmt"
func main() {
defer fmt.Println("我是第一个被注册的") // 3
fmt.Println("main函数中调用的Println") // 1
defer fmt.Println("我是第二个被注册的") // 2
}
package main
import "fmt"
func main() {
/*
1.defer语句的格式
defer 语句
2.defer语句的作用
延迟执行, 会在所属函数执行完毕之后再执行
常用于资源释放, 解锁, 错误处理等等
以前在C语言中我们说过, 通过malloc申请的存储空间, 一定更要释放free
但是由于我们不能写完malloc之后立即free,所以经常导致我们忘记释放
malloc -- >申请存储空间
... ... --> 使用存储空间
free --> 释放存储空间
注意点:
如果在同一个函数中编写了多个defer语句, 那么会遵守先进后出的原则
先注册的defer语句后执行, 后注册的defer语句先执行
*/
//fmt.Println("申请存储空间")
//defer fmt.Println("释放存储空间")
//fmt.Println("使用存储空间")
//fmt.Println("使用存储空间")
//fmt.Println("使用存储空间")
defer fmt.Println("第一条语句")
defer fmt.Println("第二条语句")
defer fmt.Println("第三条语句")
defer fmt.Println("第四条语句")
}
init函数
- golang里面有两个保留的函数:
- init函数(能够应用于所有的package)
- main函数(只能应用于package main)
- 这两个函数在定义时不能有任何的参数和返回值
- go程序会自动调用init()和main(),所以你
不能
在任何地方调用这两个函数 - package main必须包含一个main函数, 但是每个package中的init函数都是可选的
- 一个package里面可以写任意多个init函数,但这无论是对于可读性还是以后的可维护性来说,我们都强烈建议用户在一个package中每个文件只写一个init函数
- 单个包中代码执行顺序如下
main包-->常量-->全局变量-->init函数-->main函数-->Exit
package main
import "fmt"
const constValue = 998 // 1
var gloalVarValue int = abc() // 2
func init() { // 3
fmt.Println("执行main包中main.go中init函数")
}
func main() { // 4
fmt.Println("执行main包中main.go中main函数")
}
func abc() int {
fmt.Println("执行main包中全局变量初始化")
return 998
}
-
多个包之间代码执行顺序如下
-
init函数的作用
- init函数用于处理当前文件的初始化操作, 在使用某个文件时的一些准备工作应该放到这里
输入输出函数
输出函数
在C语言中, 我们使用printf来输出内容
在Go语言中也可以使用printf输出内容
第一种方式:
格式:fmt.Printf("格式化字符串", 数据列表)
特点: 不会自动换行, 但是可以自定义输出格式
num, value := 10, 20
fmt.Printf("num = %d, value = %d\n", num, value)
fmt.Printf("------")
- 但是在Go语言中还有其它更方便的函数, 也可以输出内容
第二种方式:
格式:fmt.Println(数据列表)
特点: 会自动换行, 但是不能使用占位符%d%c%s
num, value := 10, 20
fmt.Println("num = ", num, "value = ",value)
fmt.Println("----")
输入函数
在C语言中, 我们使用scanf来接收输入的内容
Go语言中也可以使用scanf来接收输入的内容
第一种方式:
格式: fmt.Scan(地址列表)
特点: 如果接收的不是字符串类型(%c), 会忽略空格和TAB和回车, 相当于C语言的scanf
var num int;
var value int;
fmt.Scan(&num, &value)
fmt.Println(num, value)
- 在Go语言中还有其它更方便的函数, 也可以接收输入的内容
第二种方式:
格式:fmt.Scanf(格式化字符串, 地址列表)
特点: 如果接收的不是字符串类型(%c), 会忽略空格和TAB, 但是不会忽略回车
var num int;
var value int;
fmt.Scanf("%d", &num)
fmt.Println(num, value)
第三种方式:
格式:fmt.Scanln(地址列表)
特点: 如果接收的不是字符串类型(%c), 会忽略空格和TAB, 但是不会忽略回车
var num int;
var value int;
fmt.Scanln(&num, &value)
fmt.Println(num, value)