1. 函数
基本结构
func 函数名(参数)(返回值){
函数体
}
函数的参数和返回值都是可选的
在调用有返回值的函数时,可以不接收其返回值
参数可以命名,也可以不命名
命名的返回值就相当于在函数中声明了一个变量
返回值被命名时,返回语句可直接写为return
func intSum(x int, y int) int {
return x + y
}
func sum(x int,y int)(ret int){
return x + y
}
函数的参数中如果相邻变量的类型相同,则可以省略类型
func sum(x,y int,m,n string) int {
return x + y
}
func sum()(int,int) {
return 1,2
}
可变参数
Go语言中的可变参数通过在参数名后加…来标识。
Go语言中没有默认参数的概念。
可变参数的类型是切片。
可变参数通常要作为函数的最后一个参数。
func sum(x string,y ...int){
}
sum("ss")
sum("ss",1,2)
多个返回值
Go语言中支持多个返回值
func f1(x,y int) (sum int,sub int) {
sum = x + y
sub = x - y
return
}
当一个函数返回值类型为slice时,nil可以看做是一个有效的slice,没必要显示返回一个长度为0的切片。
2. defer语句
defer语句将其后面跟随的语句进行延迟处理。
在defer归属的函数即将返回时,将延迟处理的语句按defer定义的逆序进行执行(类似栈)。
多用于函数结束前的资源释放。
func main() {
fmt.Println("start")
defer fmt.Println(1)
defer fmt.Println(2)
defer fmt.Println(3)
fmt.Println("end")
}
output:
start
end
1
2
3
在Go语言的函数中return语句在底层并不是原子操作,它分为给返回值赋值和RET指令两步。
defer语句执行的时机于返回值赋值操作后,RET指令执行前。
3. 函数进阶
值传递与引用传递
不论是值传递还是引用传递,传递给函数的都是变量的副本,值传递是值的拷贝,引用传递是引用的拷贝。
值传递:基本数据类型、数组、结构体
引用传递:指针,切片,map,管道,interface等。
作用域
全局变量:定义在函数外部的变量,它在程序整个运行周期内都有效。 在函数中可以访问到全局变量。
局部变量:函数内定义的变量无法在该函数外使用。
局部变量和全局变量重名,优先访问局部变量。
函数类型作参数和返回值
package main
import (
"fmt"
)
func f1(x,y int) (sum int,sub int) {
sum = x + y
sub = x - y
return
}
func f2(x,y int) (div int,mul int){
div = x / y
mul = x * y
return
}
//函数做参数类型
func f3(x func(int,int)(int,int))(a,b int){
return x(1,2)
}
//函数做返回值
func f4(x func(int,int)(int,int)) func(int,int) int {
f := func(a,b int) int {
return a + b
}
return f
}
func main() {
a := f1
b := f2
fmt.Printf("%T\n",a)
fmt.Printf("%T\n",b)
fmt.Println(f3(a)) // 3 -1
fmt.Println(f3(b)) // 0 2
f := f4(b)
fmt.Printf("%T\n",f)
fmt.Println(f(1,2)) // 3
}
output:
func(int, int) (int, int)
func(int, int) (int, int)
3 -1
0 2
func(int, int) int
3
Process finished with exit code 0
type关键字来定义一个函数类型
type calculation func(int, int) int
func f2(x,y int) (div int,mul int){
div = x / y
mul = x * y
return
}
var a calculation
a = f2
4. 匿名函数
匿名函数就是没有函数名的函数
匿名函数多用于实现回调函数和闭包
func(参数)(返回值){
函数体
}
// 将匿名函数保存到变量,通过变量调用匿名函数
package main
import "fmt"
func main() {
a := func() {
fmt.Println("aaa")
}
a()
a()
}
//自执行函数:匿名函数定义完加()直接执行
package main
import "fmt"
func main() {
func() {
fmt.Println("aaa")
}()
}
5. 闭包
闭包=函数+引用环境
即函数内部引用函数外部的变量,函数和外部变量共同构成闭包。
package main
import "fmt"
//累加器
func AddUpper() func(int) int{
var n int = 10
return func(i int) int {
n = n + i
return n
}
}
func main() {
f := AddUpper()
fmt.Println(f(1)) // 11
fmt.Println(f(2)) // 13
fmt.Println(f(3)) // 16
}
func makeSuffixFunc(suffix string) func(string) string {
return func(name string) string {
if !strings.HasSuffix(name, suffix) {
return name + suffix
}
return name
}
}
func main() {
jpgFunc := makeSuffixFunc(".jpg")
txtFunc := makeSuffixFunc(".txt")
fmt.Println(jpgFunc("test")) //test.jpg
fmt.Println(txtFunc("test")) //test.txt
}
返回的匿名函数引用了函数外的n,这个匿名函数和n变量形成了闭包。
n只初始化一次,每调用一次就进行累加。
6. 内置函数
close:主要用来关闭channel
len:用来求长度,比如string、array、slice、map、channel
new:用来分配内存,主要用来分配值类型,比如int、struct。返回的是指针
make:用来分配内存,主要用来分配引用类型,比如chan、map、slice
append:用来追加元素到数组、slice中
panic、recover:用来做错误处理,Go中可以抛出一个panic异常,然后再defer中中通过recover捕获异常
- recover()必须搭配defer使用。
- defer一定要在可能引发panic的语句之前定义。
package main
import "fmt"
func init(){
fmt.Println("init...")
}
func test(){
a := 1
b := 0
num := a / b
defer func() {
err := recover()
if err != nil {
fmt.Println(err)
}
}()
fmt.Println(num)
}
func main() {
fmt.Println("main...")
test()
fmt.Println("main...")
}
package main
import "fmt"
func init(){
fmt.Println("init...")
}
func test(){
a := 1
b := 0
num := a / b
defer func() {
if err := recover(); err != nil {
fmt.Println(err)
}
}()
fmt.Println(num)
}
func main() {
fmt.Println("main...")
test()
fmt.Println("main...")
}
init函数
每一个源文件中都可以包含一个init函数,init函数会在main执行之前调用。
通常可以在init函数中完成初始化工作。
执行流程:全局变量定义–>init函数–>main函数。
package main
import "fmt"
func init(){
fmt.Println("init...")
}
func main() {
fmt.Println("main...")
}
7. 函数的注意事项
形参列表和返回值的类型可以是值类型和引用类型
函数首字母大写可以被本包文件和其他包文件使用,首字母小写只能被本包文件使用
基本数据类型和数组默认都是值传递,即进行值拷贝。在函数内部修改不会影响原来的值。
如果希望修改函数外的变量,可以传入变量的地址&,函数内以指针的方式操作变量。
Go函数不支持重载。
8. 自定义错误处理
package main
import (
"errors"
"fmt"
)
func init() {
fmt.Println("init...")
}
func readConf(name string)(err error){
if name=="config.ini" {
return nil
} else {
return errors.New("错误信息...")
}
}
func test(){
err := readConf("config.xml")
if err != nil {
panic(err)
} else {
fmt.Println("test...")
}
}
func main() {
fmt.Println("main...")
test()
fmt.Println("main...")
}
init...
main...
panic: 错误信息...
goroutine 1 [running]:
main.test()
F:/goprodocus/Goproject1/main.go:23 +0xa5
main.main()
F:/goprodocus/Goproject1/main.go:32 +0x5b
Process finished with exit code 2
9. 字符串函数
按字节统计字符串长度:len(str)
字符串遍历,同时处理有中文的问题 r:=[]rune(str)
字符串转整数:n,err := strconv.Atoi(“12”)
整数转字符串:str = strconv.Itoa(12345)
字符串转[]byte:var bytes = []bytes(“string”)
[]byte转字符串:str = string([]byte{97,98,99})
十进制转换:str = strconv.FormatInt(123,2)
查找子串是否是在指定字符串中:strings.Contains(“Hello”,“lo”) //true
统计一个字符串中子串的个数:strings.Count(“hello”,“l”) //2
忽略大小写比较字符串:strings.EqualFold(“abc”,“ABC”) //true
返回子串在字符串中第一次出现的index值:strings.Index(“hello”,“l”)
返回子串在字符串中最后一次出现的index值:strings.LastIndex(“hello”,“l”)
将指定子串替换为另外一个子串:strings.Replace(“gohello”,“hello”,“,hi”,n) //n为替换个数 -1表示全部替换
按照指定分隔字符,为分隔标识,将一个字符串拆分为字符串数组:strings.Split(“hello,world”,“,”)
字符串大小写转换:strings.ToLower(“xxx”) strings.ToUpper(“xxx”)
去掉字符串两边的空格:strings.TrimSpace(" dd “)
去掉字符串两边空格+指定字符:strings.Trim(”! hello !“)
去掉字符串左边空格+指定字符:strings.TrimLeft(”! hello !“)
去掉字符串右边空格+指定字符:strings.TrimRight(”! hello !")
判断字符串是否以指定字符串开头:strings.HasPerfix(“hellp”,“h”)
判断字符串是否以指定字符串结尾:strings.HasSuffix(“hellp”,“h”)
var s string = "Hello 你好"
fmt.Println(s)
r:=[]rune(s)
for i := 0; i < len(r);i++ {
fmt.Println(r[i])
}
/**
Hello 你好
72
101
108
108
111
32
20320
22909
*/
10. 日期和时间函数
获取当前时间:time.Now()
now := time.Now()
获取年月日时分秒:now.Year()、now.Month()、now.Day()、now.Hour()、now.Minute()、now.Second()、int(now.Month())
格式化日期时间:now.Format(“2006/01/02 15:04:05”) //2006/01/02 15:04:05时间是固定的
时间常量:
time.Nanosecond Duration 纳秒
time.Microsecond 微秒
time.Millisecond 毫秒
time.Second 秒
time.Minute 分钟
time.Hour 小时
休眠:time.Sleep(100 * Millisecond) //休眠100ms
获取unix时间戳和unixnano时间戳:now.Unix() now.UnixNano() (获取随机数字):func (t Time) Unix() int64 func (t TIme) UnixNano() int64
11. 输入输出
Print系列函数会将内容输出到系统的标准输出,区别在于Print函数直接输出内容,Printf函数支持格式化输出字符串,Println函数会在输出内容的结尾添加一个换行符。
func Print(a ...interface{}) (n int, err error) func Printf(format string, a ...interface{}) (n int, err error) func Println(a ...interface{}) (n int, err error)
Fprint系列函数会将内容输出到一个io.Writer接口类型的变量w中,我们通常用这个函数往文件中写入内容。
func Fprint(w io.Writer, a ...interface{}) (n int, err error) func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) func Fprintln(w io.Writer, a ...interface{}) (n int, err error)
Sprint系列函数会把传入的数据生成并返回一个字符串。
func Sprint(a ...interface{}) string func Sprintf(format string, a ...interface{}) string func Sprintln(a ...interface{}) string
Errorf函数根据format参数生成格式化字符串并返回一个包含该字符串的错误。
func Errorf(format string, a ...interface{}) error err := fmt.Errorf("这是一个错误") Go1.13版本为fmt.Errorf函数新加了一个%w占位符用来生成一个可以包裹Error的Wrapping Error。 e := errors.New("原始错误e") w := fmt.Errorf("Wrap了一个错误%w", e)
占位符
- %v 值的默认格式表示
- %+v 类似%v,但输出结构体时会添加字段名
- %#v 值的Go语法表示
- %T 打印值的类型
- %% 百分号
- %t true或false
- %b 表示为二进制
- %c 该值对应的unicode码值
- %d 表示为十进制
- %o 表示为八进制
- %x 表示为十六进制,使用a-f
- %X 表示为十六进制,使用A-F
- %U 表示为Unicode格式:U+1234,等价于”U+%04X”
- %q 该值对应的单引号括起来的go语法字符字面值,必要时会采用安全的转义表示
- %b 无小数部分、二进制指数的科学计数法,如-123456p-78
- %e 科学计数法,如-1234.456e+78
- %E 科学计数法,如-1234.456E+78
- %f 有小数部分但无指数部分,如123.456
- %F 等价于%f
- %g 根据实际情况采用%e或%f格式(以获得更简洁、准确的输出)
- %G 根据实际情况采用%E或%F格式(以获得更简洁、准确的输出)
- %s 直接输出字符串或者[]byte
- %q 该值对应的双引号括起来的go语法字符串字面值,必要时会采用安全的转义表示
- %x 每个字节用两字符十六进制数表示(使用a-f
- %X 每个字节用两字符十六进制数表示(使用A-F)
- 指针:%p 表示为十六进制,并加上前导的0x
- %f 默认宽度,默认精度
- %9f 宽度9,默认精度
- %.2f 默认宽度,精度2
- %9.2f 宽度9,精度2
- %9.f 宽度9,精度0
- ’+’ 总是输出数值的正负号;对%q(%+q)会生成全部是ASCII字符的输出(通过转义);
- ’ ‘ 对数值,正数前加空格而负数前加负号;对字符串采用%x或%X时(% x或% X)会给各打印的字节之间加空格
- ’-’ 在输出右边填充空白而不是默认的左边(即从默认的右对齐切换为左对齐);
- ’#’ 八进制数前加0(%#o),十六进制数前加0x(%#x)或0X(%#X),指针去掉前面的0x(%#p)对%q(%#q),对%U(%#U)会输出空格和单引号括起来的go字面值;
- ‘0’ 使用0而不是空格填充,对于数值类型会把填充的0放在正负号后面;
func Scan(a ...interface{}) (n int, err error)
Scan从标准输入扫描文本,读取由空白符分隔的值保存到传递给本函数的参数中,换行符视为空白符。
本函数返回成功扫描的数据个数和遇到的任何错误。如果读取的数据个数比提供的参数少,会返回一个错误报告原因。
func Scanf(format string, a ...interface{}) (n int, err error)
Scanf从标准输入扫描文本,根据format参数指定的格式去读取由空白符分隔的值保存到传递给本函数的参数中。
本函数返回成功扫描的数据个数和遇到的任何错误。
fmt.Scanf不同于fmt.Scan简单的以空格作为输入数据的分隔符,fmt.Scanf为输入数据指定了具体的输入内容格式,只有按照格式输入数据才会被扫描并存入对应变量。
func Scanln(a ...interface{}) (n int, err error)
Scanln类似Scan,它在遇到换行时才停止扫描。最后一个数据后面必须有换行或者到达结束位置。
本函数返回成功扫描的数据个数和遇到的任何错误。
fmt.Scanln遇到回车就结束扫描了,这个比较常用。
有时候我们想完整获取输入的内容,而输入的内容可能包含空格,这种情况下可以使用bufio包来实现。
func bufioDemo() { reader := bufio.NewReader(os.Stdin) // 从标准输入生成读对象 fmt.Print("请输入内容:") text, _ := reader.ReadString('\n') // 读到换行 text = strings.TrimSpace(text) fmt.Printf("%#v\n", text) }
Fscan系列
这几个函数功能分别类似于fmt.Scan、fmt.Scanf、fmt.Scanln三个函数,只不过它们不是从标准输入中读取数据而是从io.Reader中读取数据。func Fscan(r io.Reader, a ...interface{}) (n int, err error) func Fscanln(r io.Reader, a ...interface{}) (n int, err error) func Fscanf(r io.Reader, format string, a ...interface{}) (n int, err error)
Sscan系列
这几个函数功能分别类似于fmt.Scan、fmt.Scanf、fmt.Scanln三个函数,只不过它们不是从标准输入中读取数据而是从指定字符串中读取数据。func Sscan(str string, a ...interface{}) (n int, err error) func Sscanln(str string, a ...interface{}) (n int, err error) func Sscanf(str string, format string, a ...interface{}) (n int, err error)
学习参考: