6.1 为什么需要函数
请大家完成个需求
输入两个数,再输入一个运算符(±*/),得到结果
var n1 float64 var n2 float64 var operator byte var res float64 switch operator { case '+': res = n1 + n2 case '-': res = n1 - n2 case '*': res = n1 * n2 case '/': res = n1 / n2 default: fmt.Println("操作符号错误...") } fmt.Println("res=",res)
分析一下上面代码问题
- 上面的写法是口语完成功能,但是代码冗余
- 同时候不利于代码维护
- 函数口语解决这个问题
6.2 函数的概念
为完成某一功能的程序指令(语句)的集合,成为函数
在Go中函数分为:自定义函数,系统函数(查看Go编程手册)
6.3 基本语法
func 函数名 (形参列表)(返回列表){ 执行语句… return 返回值列表 }
- 形参列表:表示函数的输入
- 函数中的语句:表示为了实现某一功能代码块
- 函数口语有返回值,也可以没有
6.4 快速入门案例
// 将计算功能放在函数中,需要使用时调用即可 func cal(n1 float64,n2 float64,operator byte) float64{ var res float64 switch operator { case '+': res = n1 + n2 case '-': res = n1 - n2 case '*': res = n1 * n2 case '/': res = n1 / n2 default: fmt.Println("操作符号错误...") } return res } // 调用 result := cal(1.2,2.3,'-')
6.5 包的引出
- 在实际的开发中,我们往往需要在不同的文件中,去调用其他文件的定义的函数,比如main.go 中,去使用utils.go 文件中的函数,如何实现?-》包
- 现在有两个程序员共同开发一个Go项目,程序员xiaoming希望定义函数Cal,程序员xiaomin也像定义函数Cal,两个程序员为此吵了起来怎么办?-》包
6.6 包的原理
6.12 函数-调用机制
函数的调用过程
为了让大家更好的理解函数的调用过程,看两个案例,并画出示意图
1.传入一个数+1
(1)在调用一个函数时,会给该函数分配一个新的空间,编译器会通过自身的处理让这个新的空间和其他的栈的空间区分开来
(2)在每个函数对应的栈中,数据空间是空间的,不会混淆
(3)当一个函数调用完毕(执行完毕)后,程序会销毁这个函数对应的栈空间。
2.计算两个数,并返回 getSum
return语句
基本语法:
案例说明
请编写一个函数,可以给两个数的和与差,并返回。
一个细节说明,可以使用_表示忽略(占位符)
6.13 函数的递归调用
快速入门
代码1:
代码2:
递归调用的总结
- 执行一个函数时,就创建一个新的受保护的独立空间(新函数栈)
- 函数的局部变量是独立的,不会相互影响
- 递归必须向退出递归的条件逼近,否则就是无限递归,死龟了:)
- 当一个函数执行完毕,或者遇到return,就会返回,遵守谁调用,就将结果返回给谁,同时当函数执行完毕或者返回时,该函数本身也会被系统销毁
6.14 函数使用的注意事项和细节讨论
- 函数的形参列表可以是多个,返回值列表也可以是多个
- 形参列表和返回值列表的数据类型可以是值类型和引用类型。
- 函数的命名遵循标识符命名规范,首字母不能是数字,首字母大写该函数可以被本包文件和其他包文件使用,类似public,首字母小写,只能被本包文件使用,其他包文件不能使用,类似private。
- 函数的变量是局部的,函数外不生效
- 基本数据类型和数组默认都是值传递的,即进行值拷贝,在函数内修改,不会影响到原来的值。
- 如果希望函数内部的变量能修改函数外的变量(指的是默认以值传递的方式的数据类型),可以传入变量的地址&,函数内以指针的方式操作变量。从效果上看类似引用
- Go函数不支持函数重载(就是不能命名相同名字的函数)
- 在Go中,函数也是一种数据类型,可以赋值给一个变量,则该变量就是一个函数的变量了。通过该变量可以对函数调用
- 函数既然是一种数据类型,因此在Go中,函数可以作为形参,并且调用
10. 为了简化数据类型定义,Go支持自定义数据类型
基本语法:type 自定义数据类型名 数据类型 // 理解:相当于一个别名
案例:type myInt int // 这时myInt就等价于int来使用了
案例:type mySum func(int,int) int // 这时mySum就等价 一个函数类型func(int,int) int
- 支持对函数返回值命名
func cal(n1 int,n2 int)(sum int,sub int){ sum = n1 + n2 sub = n1 - n2 return }
6.23 字符串常用的系统函数
说明:字符串在我们程序开发中,使用的是非常多的,常用的函数需要同学们掌握[带着手册或者官方编程指南]
- 统计字符串长度,按字节 len(str)
// 统计字符串的长度,按字节len(str) // golang的编码统一为utf-8 (ascii的字符(字母和数字) 占一个字节,汉字占用3个字节) str := "hello发" fmt.Println("str ",len(str)) // 8
- 字符串遍历,同时处理有中文的问题 r := []rune(str)
str2 := "hello 北京" r := []rune(str2) for i:=0;i<len(r);i++{ fmt.Printf("字符=%c\n",r[i]) }
- 字符串转整数:n,err = strconv.Atoi(“12”)
n,err := strconv.Aroi("123") if err != nil { fmt.Println("转换错误",err) }else{ fmt.Println("转换的结果是",n) }
- 整数转字符串
str := strconv.Itoa(12345) fmt.Printf("str=%v,str=%T",str,str)
- 字符串 转 []byte:
var bytes = []byte("hello go") fmt.Printf("bytes=%v\n",bytes)
- []byte 转 字符串:str = string([]byte{97,98,99})
- 10进制转2,8,16进制:str = strconv.FormatInt(123,2) // 2->8,16
- 查找子串是否在指定的字符串中:strings.contains(“seaFood”,“ood”) // true
- 统计一个字符串有几个指定的子串:strings.Count(“ccheese”,“e”) // 4
- 不区分大小写的字符串比较(==是区分字母大小写的):fmt.Println(strings.EqualFold(“abc”,“Abc”)) // true
- 返回子串在字符串中第一次出现的index值,如果没有返回-1:strings.Index(“NLT_abc”,“abc”) // 4
6.24 时间和日期相关函数
基本介绍
说明,在编程中,程序员会经常使用到日期相关函数,比如某段代码执行花费的时间等等。
- 时间和日期相关的函数,需要导入time包
- timeTime类型,用于表示时间
new := time.Now() fmt.Printf("type = %T val = %v",new,new)
- 获取到当前时间的方法
now := time.Now() //now 的类型就是time.Time - 如何获取其他相关信息
now := time.Now() fmt.Printf("年=%v\n",now.Year()) fmt.Printf("月=%v\n",now.Month()) fmt.Printf("日=%v\n",now.Day()) fmt.Printf("时=%v\n",now.Hour()) fmt.Printf("分=%v\n",now.Minute()) fmt.Printf("秒=%v\n",now.Second())
- 格式化日期时间
方式1:就是用Printf或者Sprintf
fmt.Printf("当前年月日 %d-%d-%d %d:%d:%d \n",now.Year(),now.Month(),now.Day(),now.Hour(),now.Minute(),now.Second()) dateStr := fmt.Sprintf("当前年月日 %d-%d-%d %d:%d:%d \n",now.Year(),now.Month(),now.Day(),now.Hour(),now.Minute(),now.Second()) fmt.Printf("dateStr=%v\n",dateStr)
方式2:使用time.Format()方法完成:
fmt.Printf(now.Format("2006-01-02 15:04:05")) fmt.Println() fmt.Printf(now.Format("2006-01-02")) fmt.Println() fmt.Printf(now.Format("15:04:05")) fmt.Println()
- 时间常量
const ( Nansecond Duration = 1 // 纳秒 Microsecond = 1000Nansecond // 微秒 Millsecond = 1000Microsecond // 毫秒 Second = 1000Millsecond // 秒 Minute = 60Second // 分 Hour = 60*Minute // 小时 )
常量的作用:在程序中科院用于获取指定单位的时间,比如想得到100毫秒- 结合Sleep来使用以下常量
// 需求:每隔1秒打印一个数字,打印到100时就推出 // 需求2:每隔0.1秒打印一个数字,打印到100就退出 i:=0 for{ i++ fmt.Println(i) // 休眠 // time.Sleep(time.Second) time.Sleep(time.Millisecond * 100) if i == 100{ break } }
- 获取当前unix时间戳和unixNano时间戳。(作用是可以获取随机字符串)
// unix和UnixNano的使用 fmt.Printf("unix时间戳=%v unixnano时间戳=%v\n",now.Unix(),now.UnixNano())
6.25 内置函数
说明
Golang设计者为了编程方便,提供了一些函数,这些函数可以直接使用,我们称为Go的内置函数,文档:https://studygolang.com/pkgdoc->builtin
- len:用来求长度,比如:string,array,slice,map,channel
- new:用来分配内存,主要用来分配类型,比如int,float32,struct返回的是指针
num1 := 100 fmt.Printf("num1的类型%T,num1的值=%v,num1的地址%v\n",num1,num1,&num1) num2 := new(int) //*int // num2 的类型%T =》*int // num2的值 = 地址 oxceooiree (这个地址是系统分配) // num2的地址%v = 地址 0xc043020a020 // num2指向的值 = 100 *num2 = 100 fmt.Printf("num2的类型%T,num2的值=%v,num2的地址%v\n,num2这个指针,指向的值=%v",num2,num2,&num2,*num2)
- make:用来分配内存,主要用来分配引用类型,比如channel,map,slice.这个我们后面讲解
6.26 自定义错误
自定义错误的介绍
Go程序中,也支持自定义错误,使用errors.New 和 panic 内置函数。
1.errors.New(“错误说明”),会返回一个error类型的值,表示一个错误
2.panic内置函数,接收一个interface{}类型的值(也就是任何值了)作为参数。可以接收error类型的变量,输出错误信息,并退出程序。