1.1 什么是函数
1.2 函数的声明
首先,go语言至少有一个main函数
函数声明语法:
func funcName(parametername type1, parametername type2) (output1 type1, output2 type2) {
//这里是处理逻辑代码
//返回多个值
return value1, value2
}
- func:函数有func开始声明
- funcName:函数名称,函数名和参数列表一起构成-函数签名
- parametername type:参数是可选的,也就是说函数也可以不包含参数,如果多个参数的类型都相同,可以只在最后写一次类型
- output1 type1,output2 type2:返回类型,函数返回一列值。return_types是该列值的数据类型。有些功能不需要返回值,这种情况下return_types不是必须的。
- 上面返回值声明了两个变量output1和output2,如果你不想声明也可以,直接就两个类型
- 入股只有一个返回值且不声明返回值变量,那么你可以省略包括返回值的括号(即一个返回值可以不声明返回类型)
- 函数体:函数定义的代码集合
1.3 函数的使用
示例代码: ```go // 不带返回值 func outPutNowTime() { unix := time.Now().Unix() timeObj := time.Unix(unix, 0) format := timeObj.Format(“2006-01-02 15:04:05”) fmt.Println(format) }
// 带返回值 func stringReverse(str string) string { // 变量值交换 runes := []rune(str) for from, to := 0, len(runes)-1; from < to; from, to = from+1, to-1 { runes[from], runes[to] = runes[to], runes[from] } return string(runes) }
<a name="oWa03"></a>
## 2.1 参数的使用
形式参数:定义函数时,用于接收外部传入的数据,叫做形式参数,简称形参。<br />实际参数:调用函数时,传给形参的实际的数据,叫做实际参数,简称实参。<br />函数调用:
- 函数名称必须匹配
- 实参和形参必须对应--顺序,个数,类型
<a name="BvpKd"></a>
## 2.2 可变参数
Go函数支持可变参数。接受变参的函数是有着不定数量的参数的。为了做到这点,首先需要定义函数使其接受变参:
```go
func myfunc(arg ...int){}
arg ..int告诉Go这个函数接受不定数量的参数。注意,这些参数的类型必须全部是int。在函数体中,变量arg是一个对应类型的切片(slice)
for -,n:=range arg{
fmt.Printf("And the number is:%d\n",n)
}
示例:
func sum(arg ...int) {
sum := 0
for i, num := range arg {
sum = sum + num
fmt.Printf("索引是%d,值是%d\n", i, num)
}
fmt.Printf("和是%d\n", sum)
}
/*
索引是0,值是1
索引是1,值是2
索引是2,值是3
索引是3,值是4
索引是4,值是5
和是15
*/
2.3 参数传递
go语言函数 的参数也是存在值传递和引用传递
函数运用场景
值传递
// 值传递
func validDeliver() {
// 声明函数变量
getSquareRoot := func(x float64) float64 {
return math.Sqrt(x)
}
// 使用函数
fmt.Println(getSquareRoot(9))
}
引用传递
下面两段代码,演示引用传递的使用
首先是不用引用传递
func main() {
x := 3
fmt.Println("x = ", x) // 应该输出 x=3
x1 := add(x)
fmt.Println("x1 = ", x1)
fmt.Println("x = ", x)
}
func add(a int) int {
a = a + 1
return a
}
/*
x = 3
x1 = 4
x = 3
*/
可以看出,没有使用引用传递之前,x调用add()方法后,x的值仅仅是调用的时候起作用,调用完后,还是原先的值。
下面是引用传递
/*
引用传递需要牵扯到所谓的指针。我们知道,变量在内存中是存放于一定地址上的,修改变量实际上是修改变量地址处的内存。
只有add函数知道x的变量所在的地址,才能修改x变量的值。所以我们需要将x所在地址&x传入函数,并将函数的参数类型由int改为*int
即改为指针类型,才能在函数中修改x变量的值。此时参数仍然是按copy的是一个指针。
*/
func main() {
x := 3
fmt.Println("x = ", x) // 应该输出 x=3
x1 := add(&x)
fmt.Println("x1 = ", x1)
fmt.Println("x = ", x)
}
func add(a *int) int {
*a = *a + 1
return *a
}
/*
x = 3
x1 = 4
x = 4
*/
可以看出,引用传递后,x的值调用后,值也相应的改变了。
2.3.1 数组作为参数传递给函数
将一个大数组传递给函数会消耗很多内存,有两种方法可以避免这种现象:
- 传递数组的指针
- 使用数组的切片** ```go arr := [3]float64{7.0, 8.5, 9.1} x := Sum(&arr) fmt.Printf(“The sum of the array is :%f”, x)
// 针对类型,位a置的函数或者返回值,可以使用interface{} func Sum(a *[3]float64) interface{} { var sum float64 = 0 for _, v := range a { sum += v } return sum }
/ The sum of the array is :24.600000 /
<a name="NPvA9"></a>
### 2.3.2 切片作为参数传递给函数
数组作为参数在Go中并不常用,通常还是使用下面的切片方式:
```go
arr := [5]int{10, 9, 8, 7, 6}
x := subtract(arr[:])
fmt.Printf("The subtraction of the array is :%d", x)
// 如果明确返回值,可以直接使用返回值return无需多写
func subtract(a []int) (result int) {
for i, v := range a {
if i == 0 {
result = v
} else {
result = result - v
}
}
return
}
/*
The subtraction of the array is :-20
*/
3.1 函数的返回值
3.1.1 一个函数可以有多个返回值
func main() {
x := "hello"
y := "world"
fmt.Printf("未转换前的数据:x=%s,y=%s\n", x, y)
x, y = swap(x, y)
fmt.Printf("转换后的数据:x=%s,y=%s", x, y)
}
// 多个返回值
func swap(x, y string) (string, string) {
return y, x
}
/*
未转换前的数据:x=hello,y=world
转换后的数据:x=world,y=hello
*/
3.1.2 空白标识符
_是Go中的空白标识符。它可以代替任何类型的任何值。
比如range遍历slice的时候,可以不需要索引,示例代码:
countryCapitalMap := make(map[string]string)
countryCapitalMap["France"] = "Paris"
countryCapitalMap["Italy"] = "Rome"
countryCapitalMap["Japan"] = "Tokyo"
countryCapitalMap["India"] = "new Delhi"
for k, v := range countryCapitalMap {
fmt.Println(k, v)
}
fmt.Println("----------------------")
// 不需要键名
for _, v := range countryCapitalMap {
fmt.Println(v)
}
/*
France Paris
Italy Rome
Japan Tokyo
India new Delhi
----------------------
Paris
Rome
Tokyo
new Delhi
*/
4.1 延迟函数defer
延迟语句被用于定义defer函数出函数执行结束后执行的语句。
func main() {
nums := []int{24, 166, 2, 56, 20}
largest(nums)
}
func largest(nums []int) {
// 延迟执行finished()函数
defer finished()
fmt.Println("started finding largest")
max := nums[0]
for _, v := range nums {
if v > max {
max = v
}
}
fmt.Println("Largest number in", nums, "is", max)
}
func finished() {
fmt.Println("Finished finding largest")
}
/*
started finding largest
Largest number in [24 166 2 56 20] is 166
Finished finding largest
*/
延迟并不仅仅局限于函数,延迟一个方法调用也是合法的,示例:
// 定义结构体
type person struct {
name string
age int
}
// 定义一个只有person可以调用的函数
func (p person) fullInfo() {
fmt.Printf("姓名:%s,年龄:%d", p.name, p.age)
}
func main() {
p := person{
name: "zhaoyy",
age: 15,
}
defer p.fullInfo()
fmt.Println("个人信息: ")
}
/*
个人信息:
姓名:zhaoyy,年龄:15
*/
4.2 延迟参数
延迟函数的参数在执行延迟语句时被执行,而不是在执行实际的函数调用时执行。可以用于参数调用完后,回调得到刚开始的参数。
示例:
func printA(a int) {
fmt.Println("原始值是:", a)
}
func main() {
a := 5
defer printA(a)
a = 10
fmt.Println("修改值后:", a)
}
/*
修改值后: 10
原始值是: 5
*/
4.3 堆栈的延迟
当一个函数有多个延迟调用时,它们被添加到一个堆栈中,并在Last In First Out(后进先出)的顺序执行。
示例:
func main() {
alphabet := "ABCDEFG"
fmt.Println("原始值是:", alphabet)
fmt.Println("反转:")
for _, v := range alphabet {
defer fmt.Printf("%c\t", v)
}
}
/*
原始值是: ABCDEFG
反转:
G F E D C B A
*/
4.4 defer注意点
defer函数:
当外围函数中的语句正常执行完毕时,只有其中所有的延迟函数都执行完毕,外围函数才会真正的结束执行。
当执行外围函数的return时,只有其中所有的延迟函数都执行完毕后,外围函数才会这正的返回。
当外围函数中的代码引发运行恐慌时,只有其中所有的延迟函数都执行完毕后,该运行时恐慌才会真正被扩展至调用函数。
5.1 使用函数调试
有的时候需要知道程序运行到哪里出错,需要知道程序运行的位置,那么可以通过下面两种方式获取程序所在位置
func where() {
// 参数:skip是要提升的堆栈帧数,0-当前函数,1-上一层函数,....
_, file, line, _ := runtime.Caller(1)
/*
1.pc是uintptr这个返回的是函数指针
2.file是函数所在文件名目录
3.line所在行号
4.ok 是否可以获取到信息
*/
log.Printf("%s%d", file, line)
}
func where2(){
log.SetFlags(log.Llongfile)
log.Print("")
}
上面两种函数都可以,只需要在需要调用的地方直接调用绗棉的函数即可。
5.2 计算函数执行时间
有时候需要进行基准测试,计算函数执行时间,可以使用time包中的Now和Sub函数
func calcTime() {
start := time.Now()
sum := 0
for i := 1; i <= 3000; i++ {
sum = sum + i
fmt.Println(sum)
}
fmt.Println("计算结果:", sum)
end := time.Now()
delta := end.Sub(start)
fmt.Printf("计算1-3000累加的和花费时间为:%s", delta)
}
/*
...
...
...
计算结果: 4501500
计算1-3000累加的和花费时间为:4.9616ms
*/