函数是结构化编程的最小模块单元。它将复杂的算法过程分解为若干较小任务,隐藏相关 细节,使得程序结构更加清晰,易于维护。函数被设计成相对独立,通过接收输入参数完成一段算法指令,输出或存储相关结果。因此,函数还是代码复用和测试的基本单元。
关键字func用于定义函数
- 函数必须先定义,后调用,定义的过程成为函数定义,
- 函数创建后并不是直接可以运行的,需要手动使用后,才执行,该过程成为函数调用
Go函数特点:
- 无需前置声明
- 不支持命名嵌套定义(nested)
- 不支持同名函数重载(overload)
- 不支持默认参数
- 支持可变参数
- 支持多返回值
- 支持命名返回值
- 支持匿名函数和闭包
函数只能判断是否为nil,不支持其他比较操作
func a() {}
func b() {}
func main() {
println(a == nil)
println(a == b) // 无效操作:a == b (func can only be compared to nil
}
参数:
函数传参都是值传递,实参将自己的地址值拷贝一份给形参。如果参数是引用类型,那么就会改变实参的值(A栈帧修改B栈帧的值)
定义格式:
参数:由数据类型和变量名组成 数据类型 变量名
例: int a
func 函数名(参数1 数据类型,参数2 数据类型...) {}
普通格式调用:
函数名(参数1,参数2)
函数值格式调用:
变量 := 对象.函数名
函数表达式格式调用:
变量 := 类名.函数名
函数调用时,参数的数量与类型必须与函数定义中的设置相匹配,否则程序将报错。函数的返回值通常会使用变量接收,否则该返回值将无意义
演示:
func main() {
person := Person{24}
// 普通格式
person.show()
// 函数值格式
p1 := person.show
p1()
// 函数表达式格式
p2 := (*Person).show
p2(&person)
}
type Person struct {
age int
}
不支持有默认值的可选参数,不支持命名实参。调用时必须按签名顺序传递指定类型和数量的实参,就算以“_”命名也不能忽略。参数列表中,相邻的同类型参数可合并
func main() {
test(1, 2, "abc") // 报错: not enough arguments in call to test 给bool变量赋值就可以了
}
func test(x, y int, s string, _ bool) *int {
return nil
}
参数可视作函数的局部变量,因此不能在相同层次定义同名变量
func add(x, y int) int {
x := 100 // 错误:':=' 的左侧没有新变量
var y int // 错误:未使用的变量 'y'
return x + y
}
不管是指针、引用类型,还是其他类型参数,都是值拷贝传递,无非是拷贝目标对象,还是拷贝指针自身而已。在函数调用时,会为形参和返回值分配内存空间, 并将实参数据拷贝到形参内存。
func main() {
a := 0x100
p := &a
println(&p, p) //输出实参p的地址。
test(p)
}
func test(x *int) {
println(&x, x) //输出形参x的地址。
}
输出:
从输出结果可以看出,尽管实参和形参都指向同一目标,但指针自身依然被复制。
0x1400006cf60 0x1400006cf50 0x1400006cf58 0x1400006cf50
要实现传出参数,通常建议使用返回值。当然,二级指针也是常用的手法
func main() {
var p *int
test(&p)
println(*p)
}
func test(p **int) {
x := 100 * p = &x
}
如果函数参数过多,建议将其重构为一个复合类型参数,也算是变向实现可选参数和命名实参功能
func main() {
opt := newOption()
opt.port = 8085
server(opt)
}
type serverOption struct {
address string
port int
path string
timeout time.Duration
log *log.Logger
}
func newOption() *serverOption {
return &serverOption{
address: "0.0.0.0",
port: 8080,
path: "/var/test",
timeout: time.Second * 5,
log: nil,
}
}
func server(option *serverOption) {}
形参和实参:
形参是指函数定义中的参数,实参则是函数调用时所传递的参数。形参类同函数局部变量,而实参则函数外部对象,可以是常量、变量、表达式或函数等。
形参:
函数定义中的参数,顾名思义就是只有一个形式,没有具体的数值
等同于变量定义格式,例如:var num int
实参:
函数调用中的参数,顾名思义就是有实际的参数数值
等同于使用变量或常量,例如:var num int =10
传递的形参即使在函数中操作,也不会影响原来的数据值
func main() {
a, b := 1, 2
Demo12(a, b)
fmt.Println("main:", a, b) //main: 1 2
}
func Demo12(a, b int) {
a, b = b, a
fmt.Println("Demo12:", a, b) //Demo12: 2 1
可变参数(不定参数):
写函数的时候如果不知道形参长度,可以使用可变参数传递
变参本质上就是一个slice,它只能接收相同类型的参数值,且必须放在参数列表的最后
图解:
演示:
func main() {
//Demo01(1)
Demo02(1, 2, 3, 4, 5)
}
func Demo01(num int) {
for i := 0; i < num; i++ {
fmt.Println("test")
}
}
func Demo02(num ...int) {
for i := 0; i < len(num); i++ {
fmt.Println(num[i])
}
}
// 如果可变参数和其它参数一起作为形参时,可变参数需要放在后面
func Demo03(aaa int , num ...int) {
for i := 0; i < len(num); i++ {
fmt.Println(num[i])
}
}
变参是slice,那么参数复制的仅是slice struct自身,不包括底层数组,因此我们可以修改原数据。如果需要,可使用内置函数copy复制slice数据
func test(a ...int) {
for i := range a {
a[i] += 100
}
}
func main() {
a := []int{10, 20, 30}
test(a...)
fmt.Println(a)
}
返回值:
函数可以有返回值,也可以有返回值,也可以定义多个返回值。使用return关键字定义
如果没有定义返回值,但是写了return,那么就相当于终止了函数,跳出循环的意思
方式一:
func main() {
// 定义变量接收返回值并执行打印
r := Demo06(1, 2)
println(r)
}
// 要在参数后面加上返回值的数据类型
func Demo06(a int, b int) int {
// 将a+b的结果赋值给sum并返回,需要在函数里再定义返回值
sum := a + b
return sum
}
方式二:
func main() {
// 定义变量接收返回值并执行打印
r := Demo07(1, 2)
println(r)
}
// 要在参数后面加上返回值和返回值的数据类型,不需要在函数里再定义返回值
func Demo07(a int, b int) (sum int) {
sum = a + b
return sum
}
方式三:
func main() {
// 定义变量接收返回值并执行打印
r := Demo08(1, 2)
println(r)
}
// 要在参数后面加上返回值和返回值的数据类型,不需要在函数里再定义返回值
func Demo08(a int, b int) (sum int) {
sum = a + b
// 如果在函数上已经指定了返回值,在函数中可以省略返回值,直接return
return
}
函数返回多个返回值:
func main() {
// 定义变量接收返回值
a, b := Demo09()
fmt.Println("a = ", a, "n b = ", b)
}
// 在函数上只写返回值类型,在函数中写返回值
func Demo09() (int, int) {
a := 1
b := 2
// 返回两个变量的值
return a, b
}
有返回值的函数,必须有明确的return终止语句
func test(x int) int {
if x > 0 {
return -1
} else if x < 0 {
return -1
}
} // 错误:missing return at end of function
特例情况是panic,或者无break的死循环
func test(x int) int {
for {
break
}
}
借鉴自动态语言的多返回值模式,让函数得以返回更多状态,尤其是error模式。
import "errors"
func div(x, y int) (int, error) { //多返回值列表必须使用括号。
if y == 0 {
return 0, errors.New("division by zero")
}
return x / y, nil
}
相同类型的多返回值可用作调用实参,或直接返回
func main() {
log(test()) //多返回值直接用作实参。
}
func test() (int, error) {
return div(5, 0) //多返回值直接用作return结果。
}
func div(x, y int) (int, error) {
if y == 0 {
return 0, errors.New("division by zero")
}
return x / y, nil
}
func log(x int, err error) {
fmt.Println(x, err)
}
返回值命名:
从这个简单的示例就可看出,命名返回值让函数声明更加清晰,同时也会改善帮助文档和 代码编辑器提示。
func paging(sql string, index int) (count int, pages int, err error) {}
命名返回值和参数一样,可当做函数局部变量使用,最后由return隐式返回
func div(x, y int) (z int, err error) {
if y == 0 {
err = errors.New("division by zero")
return
}
z = x / y
return // 相当于 “return z, err”。
}
只是这些特殊的局部变量会被不同层级的同名变量遮蔽,造成潜在错误。好在编译器会自动检查此类状况,只需改为显式return返回即可
func add(x, y int) (z int) {
{
z := x + y //新定义的同名局部变量,同名遮蔽。
return // 错误:z is shadowed during return (改成 “return z” 即可)
}
return
}
除遮蔽外,我们还必须对全部返回值命名,否则编译器会稀里糊涂
func test() (int, s string, e error) {
return 0, "", nil // 错误:cannot use 0 (type int) as type string in return argument
}
函数重写:
子类中出现与父类一模一样的函数时,会使用就近原则,子类优先使用子类的函数,要想通过子类的对象调用父类的函数,需要使用子类对象.父类.函数名
函数重写的应用场景:
当子类需要父类的功能,而功能主体子类有自己特有内容时,可以重写父类中的函数,这样,即沿袭了父类的功能,又定义了子类特有的内容
演示:
func main() {
stu := Student{}
stu.show() // 子类的Show函数
stu.Person.show() // 父类的Show函数
}
type Person struct {
age int
}
type Student struct {
Person
name string
age int
score float64
}
func (student Student) show() {
fmt.Println("子类的Show函数")
}
func (person *Person) show() {
fmt.Println("父类的Show函数")
}
匿名函数:
匿名函数是指没有定义名字符号的函数
匿名函数除没有名字外,其他和普通函数完全相同。最大的区别是,我们可在函数内部定义匿名函数,形成类似嵌套函数的效果。匿名函数可直接调用,保存到变量,作为参数或返回值
格式:
func(形参) {
函数体
}(实参)
直接执行:
func main() {
func(s string) {
fmt.Println(s)
}("我是实参,上面的s,是形参,我会被打印不")
}
有返回值匿名函数:
func main() {
add := test()
println(add(1, 2))
}
func test() func(int, int) int {
return func(x, y int) int {
return x + y
}
}
作为第一类对象,不管是普通函数,还是匿名函数都可作为struct字段,或通过channel传递
func testStruct() {
type calc struct {
mul func(x, y int) int
}
x := calc{
mul: func(x, y int) int {
return x * y
},
}
println(x.mul(2, 3))
}
func testChannel() {
c := make(chan func(int, int) int, 2)
c <- func(x, y int) int {
return x + y
}
println((<-c)(1, 2))
}
回调函数:
本质就是函数类型的指针,通过指针,在特定位置,满足条件后,系统会自动调用函数。
普通函数调用格式:
func aaa(){}
func main(){
aaa()// 普通函数是通过函数名+括号调用的
}
回调函数调用格式:
func handler(w http.ResponseWriter, r *http.Request) {}
func main(){
http.HandleFunc("/itzhuzhu", handler)// 只需要将函数名传入即可
}
内置函数:
len:用来计算长度的,string、arr、slice、map、channel都可以
new:用来分配值内存的,int、float32、struct返回值是指针
make:用来分配引用类型内存的,slice、map、channel
func main() {
//num := 100
num2 := new(int)
*num2 = 100
fmt.Printf("num2的类型:%T,num2的值:%v,num2的内存地址:%v,num2指向地址存储的数据:%v", num2, num2, &num2, *num2)
}
直接定义变量的流程是:开辟内存空间->将数据存储到内存空间
适用new定义变量的流程是:开启指针内存空间->指向数据的内存地址



