Go语言函数:
函数定义的格式:
func fun(/*参数列表*/)(/*返回值列表*/){
//函数体
//返回语句
}
无参数无返回值的函数定义及调用:
package main
import "fmt"
func fun(){
fmt.Println("函数运行")
}
func main(){
fun()
}
需要注意的是:
1.程序从入口执行,函数必须先被定义才能被调用,需要在主函数中调用函数:函数名()
2.自定义函数在主函数前后无区别,函数被定义但是没有调用不会报错。
3.单独程序源代码内函数名大小写无区别
有参数无返回值的函数定义及调用:普通参数列表
func fun(a int){
fmt.Println("函数参数",a)
}
func main(){
fun(100)
}
需要注意的是:
1.定义函数时在函数名的()内定义的参数叫形参。
2.函数不能重名/重复定义,参数定义必须在参数列表内且不需要关键字,定义不可以放在函数体内。同时,参数的赋值只能在函数体内,不可以放在参数列表,否则将会报错。
3.函数调用时函数名(所需参数),调用函数传递的所需参数叫实参。
4.参数传递只能由实参传递给形参,不可以反向传递(单向传递)。
含有多个参数的函数的定义及调用:
func fun(a int,b int){ //参数类型相同可以简写为(a,b int)
fmt.Println("函数参数",a,b)
}
func main(){
fun(100,200)
}
需要注意的是:参数列表有几个参数调用的时候就要传递几个参数,否则会报错:不够参数调用。
有参数无返回值的函数定义及调用:不定参数列表
func fun(args ...int){
fmt.Println("函数参数",args)
}
func main(){
fun(100,200,300)
}
需要注意的是:
1.不定参数的类型:...int类似这样的类型叫做不定参数类型(...type)。
2.传递的实参可以是0-n多个
3.使用不定参数时,內建函数len(args)可以读取用户传递的参数的个数。
不定参数的使用:
for循环使用:在不定参数列表的函数的函数体内使用
for i:=0;i<len(args);i++{
fmt.Printf("参数%d的内容为%d\n",i,args[i])
}
迭代使用:在不定参数列表的函数的函数体内使用
for i,data:=range args{
fmt.Printf("参数%d的内容为%d\n",i,data)
}
需要注意的是:
1.不定参数一定要放在形参中的最后一个参数,否则报错。
func fun(a int,args ...int){}
2.此时固定参数a是必须传参的,不定参数可根据需求传参数。
不定参数的传递:我们可以在函数内将不定参数传给另外一个函数
func myfunc (tmp ...int){
}
func test(args ...int){
myfunc(args...) //全部元素传递给myfunc
myfunc(args[:2]...) //args[2]前(不包括)进行传递
myfunc(args[2:]...) //args[2]后(包括)进行传递
}
func main(){
test(1,2,3,4)
}
无参数有返回值的函数定义:
单一返回值具有多种写法:
写法一:
func myfunc() int {
return 100
}
需要注意的是:
1.只有一个返回值时可以不声明变量名称,省略括号。
2.有返回值的函数需要用return来中断函数并返回相应的量。
写法二:
func myfunc() (result int) {
return 100
}
此时我们也可以为返回值起一个变量名,并将代码优化为:常用方
func myfunc() (result int) {
result=100
return
}
此方法为返回值起名,这是Go语言的一种推荐写法,以清晰为主。
注意:返回整个返回值列表,return之后不用写内容。
无参数有返回值的函数的调用:
func main(){
var a int
a=myfunc() //赋值接受返回值
b:=myfunc() //自动推导接受返回值
}
无参数返回多个返回值的函数的声明:Go语言独特之处,不同于其他语言的单一返回值局限
传统写法:
func myfunc() (int,int,int) {
return 1,2,3
}
Go语言推荐的写法:
func myfunc() (a int,b int,c int) { //也可以写为(a,b,c int)
a,b,c=1,2,3 //多重赋值
return
}
无参数返回多个返回值的函数的调用:
func main(){
a,b,c:=myfunc()
fmt.Printf("第一个参数:%d\n第二个参数:%d\n第三个参数:%d\n",a,b,c)
}
此时如果对返回值进行输出,推荐使用格式化输出更为清晰方便
有参数也有返回值的函数声明及调用:例求两个数的最大值
func maxandmin(a,b int)(max,min int){
if a>b{
max=a
min=b
}else{
max=b
min=a
}
return
}
func main(){
max,min:=maxandmin(10,20)
}
此时,如果我们想单独接收最大值或最小值,也可以用匿名变量来缺省某个返回值:
max,_:=maxandmin(10,20)
普通函数的调用流程:一组嵌套调用--先调用后返回,先进后出
package main
import "fmt"
func fun2(x int) (a int) {
a=x+1
fmt.Println("函数2运行,当前a的值是", a)
fmt.Println("函数2运行结束,当前a的值是", a)
return
}
func fun1(x int) (a int) {
a=x+1
fmt.Println("函数1运行,当前a的值是", a)
a = fun2(a)
fmt.Println("函数1运行结束,当前a的值是", a)
return
}
func main() {
a := 0
fmt.Println("主函数运行,当前a的值是", a)
a = fun1(a)
fmt.Println("主函数运行结束,当前a的值是", a)
}
运行结果:
主函数运行,当前a的值是 0
函数1运行,当前a的值是 1
函数2运行,当前a的值是 2
函数2运行结束,当前a的值是 2
函数1运行结束,当前a的值是 2
主函数运行结束,当前a的值是 2
当发现每个函数都执行了类似的功能,此时,我们运用这种特点改写了函数递归调用:函数调用自己本身:
package main
import "fmt"
func test(a int) {
if a == 2 {
fmt.Printf("递归a=%d\n", a)
return //终止函数,否则将无限递归
}
fmt.Printf("递归a=%d开始\n", a)
test(a + 1)
fmt.Printf("递归%d结束\n", a)
}
func main() {
a := 0
test(a)
}
运行结果:
递归a=0开始
递归a=1开始
递归a=2
递归1结束
递归0结束
递归函数的应用:数字累加的实现(1-100)
传统方法:为for循环封装函数:
func test01() (sum int) {
for i := 1; i <= 100; i++ {
sum += i
}
return
}
func main() {
sum := test01()
fmt.Println(sum)
}
递归方法:
倒序累加:
func test02(i int) int {
if i == 1 {
return 1
}
return i + test02(i-1)
}
func main() {
sum := test02(100)
fmt.Println(sum)
}
正序累加:
func test03(i int) int {
if i == 100 {
return i
}
return i + test03(i+1)
}
func main() {
sum := test03(1)
fmt.Println(sum)
}
Go语言函数类型:
传统调用函数方法:函数名(参数列表)
func Add(a,b int ) int {
return a+b
}
func minus(a,b int) int{
return a-b
}
func main(){
result:=Add(1,1)
}
我们可以将函数也看做一种数据类型,通过type给函数起一个函数类型名称,但要求与函数有同样的参数列表和返回值,即可通过函数类型变量赋值。
type FuncType func(int,int)int //没有函数名字以及大括号,FuncType是一种自定义函数类型
函数类型变量的定义及赋值:
func main(){
var fTest FuncType //声明一个函数类型变量fTest
fTest = Add //将函数入口赋值给变量
result:=fTest(1,1) //等价于result:=Add(1,1)
}
需要注意的是:函数名就是函数的入口地址,函数类型则类似于c语言中的函数指针
多态思想的主要体现:回调函数--函数的一个参数是函数类型
计算函数实现四则运算:
func Calc(a,b int, fTest FuncType) (result int){
result=fTest(a,b) //函数没有实现
return
}
需要注意注意的是,此时的fTest具体函数并没有实现。对于此计算函数,先有框架,再实现功能。体现了多态性:多种形态调用,调用同一个接口,可以实现不同的功能--四则运算。
计算函数的调用:
func main(){
result:=Calc(1,1,Add)
}
此处类似于Python中的字典或switch调用函数的使用
此方法的优势:
不用先定义并实现后调用,此函数可以先预留出调用的空间,不需要立即实现传递的参数函数,给函数更多的拓展空间,具有独特的使用更多函数功能的优势。传统写法没有办法实现多态,需要立即实现功能。
匿名函数与闭包:没有函数名字,可以捕获外界的变量。
匿名函数的定义:
定义及手动调用:
func main(){
f1:=func(){ //自动推导类型,较为常用
fmt.Println("匿名函数")
}
f1()
}
函数别名调用:此方法不常用
type FuncType func()
var f2 FuncType
f2=f1
f2()
定义匿名函数同时自动调用:
func(){
fmt.Println("匿名函数")
}() //此()代表自动调用此匿名函数--传递参数括号
带有参数的匿名函数手动调用:
f3:=func(i,j int){
fmt.Println("匿名函数参数:",i,j)
}
f3(10,20)
带有参数的匿名函数自动调用:
func(i,j int){
fmt.Println("匿名函数参数:",i,j)
}(10,20)
带有参数和返回值的匿名函数自动调用:
x,y:=func(i,j int)(max,min int){
//函数体,将参数最大值赋给max,参数最小值赋给min。详细代码省略
return
}(10,20)
闭包捕获外部变量的特点:捕获外部的变量并可以在闭包内部修改变量值,在外部修改也生效
func main(){
a:=10
func(){
a=11
fmt.Println(a)
}()
fmt.Println(a)
}
闭包以引用方式来捕获外部变量:
普通函数:
func test01 () int{
var x int
x++
return x*x
}
此函数在主函数中每次调用,返回的结果都是1:每次调用,结果相同。因为此函数被调用时x才分配空间并初始化为0,函数调用完毕后x自动释放。
若让函数的返回值是一个匿名函数,返回一个函数类型:闭包
func test02() func() int{
var x int
return func() int{
x++
return x*x
}
}
func mian(){
f:=test02()
}
此时,函数的返回值是一个匿名函数,返回了一个函数类型,通过f来调用返回的匿名函数--闭包函数。
此函数在出函数中每次调用,返回的结果是不同的1,4,9,16....
我们可以得出结论:闭包并不关心捕获的变量和常量是否超出作用域,只要闭包还在使用这些量,他们就还会存在不会被释放和重置。即:闭包里变量的生命周期不是由他的作用域决定的。
延迟调用关键字defer:只能放在函数的内部,可以完成一些清理和关闭的作用,会在main函数结束前运行。
func main(){
defer fmt.Println("打印语句2")
fmt.Println("打印语句1")
}
此时输出结果为:
打印语句1
打印语句2
多个defer共同作用时:先执行没有defer的语句,之后按defer先写的后调用来执行。函数等的中途错误并不会导致defer执行中断。
defer与匿名函数结合使用:
defer func(a,b int){
fmt.Println("匿名函数参数:",a,b)
}(100,200)
需要注意的是:如果此类函数传递了参数,传递参数的顺序在整个程序的顺序结构中不会受到影响,但此类函数会在主函数结束前才被调用。
Go语言中获取命令行参数:
package main
import "os"
func main(){
list:=os.Args
}
此时可以通过內建函数来获取用户输入参数的个数:
n:=len(list)
但需要注意的是:接受用户传递的参数是以字符串方式接受,运行命令也被视作一个参数。
同时如果我们想获取用户的参数,也可以通过以下方式:
for循环方法:
for i:=0;i<n;i++{
fmt.Printf("用户第%d个参数:%d\n",i,list[i])
}
迭代方法:
for i,data:=range list{
fmt.Printf("用户第%d个参数:%d\n",i,data)
}
Go语言中的变量规则:
局部变量:定义在{}里或所属于某个语句块如if、for等的就是局部变量(作用域,变量的作用范围),只在大括号里或某个语句块内有效。执行到定义局部变量才会分配空间,离开作用域自动释放。
全局变量:定义在函数外部的变量就是全局变量,在程序的任何地方都能够使用。
当局部变量与全局变量同名时有以下规则:不同作用域允许定义同名变量,但使用变量采取就近原则,使用变量的语句所在的变量的作用域内的变量即是就近的变量。
Go语言的工作区:
1.Go语言代码必须放在工作区,否则无法导入包。
2.封装更具备模块化有利于代码的维护和调用。
3.源代码必须要放在src目录下。
4.导入包会从GOPATH导入。
导入包的方法:
传统方法:
import "fmt"
import "os"
常用写法:
import(
"fmt"
"os"
)
(点).操作:
import . "fmt"
使用此方法,调用函数无需通过包名,但由于个人习惯可能会导致重名问题。
别名方法:
import io "fmt"
使用此方法,调用函数时的fmt则可以变更为io。
忽略包方法:
import _ "fmt"
使用此方法是为了引入该包,但不使用包内的函数,只为盗用包内的init函数(后文有阐述)。
init函数的介绍:init函数相当于初始化函数
导入包时,执行包内init函数之后再来执行主函数。
若本身也有init函数,则先执行导入的包的init,在执行本身init,最后执行main。
Go语言的工程管理:
同文件夹下的工程管理:
1..分文件编程(多个源文件),必须放在工程目录下src目录(自写)。
2.设置GOPATH环境变量。
电脑属性>高级系统设置>环境变量>系统变量GOPATH设置src目录内工程文件夹(工程src目录所在目录)
3.同一个目录包名必须一样,只能有一个程序入口。
src内:main.go(package main) test.go(package main)
4.go env查看go相关环境路径。
5.同一个目录调用其他文件的函数可直接调用无需包名引用。
不同级目录文件夹下的工程管理:
1.不同目录包名不同
src内:main.go(package main)
src/cacl内: calc.go(package calc)
2.调用不同包里面的函数格式:包名.函数名() 包名需要import导入
3.临时配置:liteide下编译配置自定义GOPATH 工程src所在目录
4.如果调用别的包的函数,函数名字是小写则无法被调用,能被调用的函数必须首字母大写。
如果有多个文件或多个包:
1.配置GOPATH环境变量,配置src目录的绝对路径---工程文件夹。
2.自动生成bin和pkg,则需要使用go install命令。
3.配置GOBIN
在工程目录下:
1.在系统变量内新建GOBIN,变量值则为工程目录下的bin目录。
2.工程src目录下在命令行使用go install,自动生成bin与pkg。
src:存放源代码
bin:存放可执行程序
pkg:存放平台相关的库