函数基本语法
func 函数名(形参列表)(返回值列表){
执行语句
return 返回值列表
}//返回值可以没有可以有多个可以有一个
包
引入
为了解决两个程序员取得函数名同名的情况
原理
本质就是创建不同的文件夹
概念
go的每一个文件都属于一个包,即go是以包来管理文件和项目目录结构的
作用
区分相同名字的函数,变量等标识符
程序文件很多时,很好的管理项目
控制变量,函数等访问范围,即作用域,大写公有,小写私有
说明
package:包名
import:包的路径
使用注意事项
给一个文件打包,该包对应一个文件夹,文件的包名通常和文件所在的文件夹名一致,一般为小写字母
要用包,先引包
import(
"包名"
”包名“
)
import “包名”
package指令在第一行,然后是import
在import包时,路径从$GOPATH的src下开始,不用带src,编译器会自己从src下引入
函数首字母大写才可以让其他包文件访问本包文件
访问:包名.函数名
如果包名较长,支持给包取别名,但是,取别名后原来的包名不能再用
在import里:别名 包名
同一包下,不能有相同函数名,否则报错
语法规范
编译生成一个可执行文件,需要将这个包名声明称main,即package main,但如果写的是一个库,包名可以自己定义
go build go_code/...../main
不需要带src编译器会自动带上
编译需编译main所在的文件夹
项目的目录结构最好按照规范来写
函数调用
说明
基本数据类型一般存放在栈区,编译器存在逃逸分析
引用类型存放在堆区,编译器存在逃逸分析
另外还有一个代码区
调用时分配新的空间,和其他栈区分开,每个空间独立不会混淆,函数执行完毕后销毁该空间
return语句
可接受多个,不想要可用占位符_忽略
若返回值有多个加(返回值类型列表),一个则不用_
res1, res2 := getsunAndsub(1,2)//该函数两个返回值
_, res2 := getsunAndsub(1,2)//占位符_忽略
递归
总结
执行函数,创建一个新的受保护的独立空间(新函数栈)
函数局部变量独立,不会相互影响
递归向退出递归条件的逼近,否则无限递归
函数执行完毕或遇到return就会返回,谁调用返回睡,自身销毁
小细节
数组是值传递,函数内修改,不会影响原来的值
希望函数可以改变函数外变量可以用&
函数内以指针操作
效果上看类似于引用
funct test(n1 *int){...}
test(&n1)
不同于c++的引用
int fun(int &a,int &b){...}
fun(a,b);
go中函数不支持重载,会报函数重复定义的错误
go中函数也是一种数据类型,可以赋值给一个变量,则该变量就是函数类型变量,可以通过改变量来对函数进行调用
func test(n int ,m,int){...}
a :=tset
res := a(10,20)//等价于res := test(10,20)
函数既然是一种数据类型,因此可以作为形参,并且调用
res2 :=myFun(getSun,n1,n2)
func myFun( funcvar fun(int, int) int, num1 int, num2 int) int{
return funcvar(num1,num2)
}
### go
支持自定义数据类型,可化简定义
type 自定数据类型名 数据类型
type myIny int
type myFuncType func(int, int )int
这是可用myFunType代替形参 func(int,int)int
支持函数返回值命名
func getSunAndSub( n1 int, n2 int) (sum int ,sub int){
sub = n1-n2
sum = n1+n2
return
}
_表示忽略
go支持可变参数
//支持0到多个参数
func sun(args...int) sun int{...}
//支持1到多个参数
func sum(n int, args...int) sun int{...}
//args是切片,通过args[index]访问到各个值
func sum(n1,n2 float)float{...}//n1 type=float
给函数类型的变量赋值函数:形参个数一样,返回值个数一样才算同种类型的即一个形参对应于一种函数类型
type myFuncType func(int, int )int
不能把func sum(n1,n2,n3 int)int{...}赋值给myFuncType
类型要匹配
init函数
介绍
每一个源文件都可以有一个init函数,会在main前执行,被go运行框架调用
通常在init中完成初始化工作
注意事项和细节
全局变量在init前执行
若import有utils.go则执行顺序
1.utils.go中的变量定义
2.utils.go中的init
3.main.go中的全局变量定义
4.main.go中的init
5.main.main函数
匿名函数(是一种类型)
没有名字,若只希望用一次,可以考虑使用匿名函数,当然也可调用多次
res := func( n1 int, n2 int) int {
return n1 +n2
}(10 ,20)
//这种方式为定义时调用,只能调用一次
res := func( n1 int, n2 int) int {
return n1 +n2
}
res2 := res(10 ,20)
//将函数付给一个变量,通过变量可多次调用匿名函数
全局匿名函数
将匿名函数付给一个全局变量,那吗可在程序中有效
var(
Fun1 = func (n1 int ,n2 int) int {
return n1*n2
}
)
闭包
就是一个函数和其相关的引用环境组合的一个整体
func AddUpper() func (int) int {
var n int = 10
return func (x int) int {
n = n+x
return n
}
}
//AddUpper是一个函数,返回类型为func(int) int
//n的值类似于一个静态变量
f := AddUpper()
fmt.println(f(1))//11
fmt.println(f(2))//13
上面匿名函数和函数外的形成了一个整体,构成闭包
函数和他引用到的变量构成了闭包
案例
判断文件后缀名是否为jpg,给文件加或不加
闭包完成
内函数引用到外函数的形参或定义的变量构成闭包
闭包可以保留上次引用的值,所以
优点:传入一次就可以反复使用
package main
import (
"fmt"
"strings"
)
func makeSuffix(suffix string) func (string) string {
return func (name string) string {
//如果 name 没有指定后缀,则加上,否则就返回原来的名字
if !strings.HasSuffix(name, suffix) {
return name + suffix
}
return name
}
}
func makeSuffix2(suffix string, name string) string {
//如果 name 没有指定后缀,则加上,否则就返回原来的名字
if !strings.HasSuffix(name, suffix) {
return name + suffix
}
return name
}
func main(){
//测试makeSuffix 的使用
//返回一个闭包
f2 := makeSuffix(".jpg") //如果使用闭包完成,好处是只需要传入一次后缀。
fmt.Println("文件名处理后=", f2("winter")) // winter.jgp
fmt.Println("文件名处理后=", f2("bird.jpg")) // bird.jpg
fmt.Println("文件名处理后=", makeSuffix2("jpg", "winter")) // winter.jgp
fmt.Println("文件名处理后=", makeSuffix2("jpg", "bird.jpg")) // bird.jpg
}
defer
why
函数中,程序员经常需要创建资源,为了在函数执行完毕后,及时释放资源,go的设计组提供了defer(延时机制)
执行到defer时将defer后面的语句压入到独立的栈中(defer栈)
当函数执行完毕后,再从defer栈中按先入后出的方式出栈
注意事项
defer入栈时,会将相关值进行拷贝一起放入栈,注意,此值不收其他表达式影响
其价值在于函数执行完毕后,及时释放函数创建的资源
创建资源
defer 释放资源,其他代码不用再担心在什么时候关闭资源
函数传参机制
值类型值拷贝,引用类型地址拷贝
一般来说地址拷贝效率高,因为数据小,而值拷贝,数据越大,效率越低
变量作用域
函数内部的作用域仅限于函数内
函数外部的定义变量叫作全局变量在整个包内有效,若首字母大写在整个程序有效
在一个代码块中定义,则只在代码块中有效
编译器会采用就近原则
name := "tom"//错误,因为等价于var Name string Name = "tom"
字符串常用的系统函数
统计字符串长度
len(str)
字符串遍历,可带中文
r :=[]rune(str)
字符串转整数
n,err := strconv.Atoi("12")
整数转字符串
str = strconv.Itoa(12345)
字符串转[]byte
var bytes = []byte("hello go")
[]byte转字符串
str = string([]byte{97,98,99})
10进制转2 8 16进制
str = strconv.FormatInt(132,2)
在母串中查子串
strings.Contains("seafood","food")
母串中统计子串
strings.Counts("seafood","food")
不区分大小写的比较
stringsEqualFold("abc",""ABC)
返回子串在字符串第一次出现的index若没有返回-1
strings.Index("NLT_abc","abc")
指定子串替换为另一个子串
strings.Replace("go go hello","go","go语言",n)//n可指定几个-1表示全部替换
按照特定的某个字符为标识符将一个字符串拆分成字符数组
strings.Split("hello,word,ok",",")
字符串大小写转换
strings.TOLower("Go")
strings.ToUpper("Go")
将字符串两边的空格去掉
strings.TrimSpace(" asassdf ")
将字符串两边指定字符去掉
strings.Trim("! hello!","!")//["!"],将“!”去掉
strings.TrimLeft()去左边
strings.TrimRight()去右边
判断是否已指定的字符串开头
strings.HasPrefix("ftp://192.168.10.1","ftp")
strings.HasSuffix()判断是否已字符串结束
时间和日期相关的函数
需先引入time包
time.Time获取时间
now := time.now()
now.year()
分秒以此类推,返回类型为字符串型可转换
格式化时间
法一:用Print或Sprintf
法二: 用time.Format()完成
注意格式
单位
Nanosecond纳秒
Microsecond微妙
Millisecond毫秒
在程序中获取指定单位的时间 100*time.Millisecond
结合sleep使用:time.Sleep(100*time.Millisecond)
time的UnixNano和Unix
时间戳
获取时间的单位为妙
单位为纳秒
Now.Unix() 从1970年1月1日到现在的时间差
可用来统计一个函数执行的时间。利用时间戳
len用来求长度
new用来分配内存,主要分配值类型,返回的是指针返回值是一个地址的数值,地址又是一个数值,都是系统分配的
make用来分配地址主要用于引用类型
错误处理
希望错误出现后可以捕获到错误并进行处理保证程序执行,还可以不货到后给管理员一个提示(邮件或短信),而不是崩溃,所以引出错误处理机制
go追求优雅,所以不支持try..catch...finally处理方式为defer,panic,recover
抛出一个panic错误然后在defer中通过recover捕获这个异常,然后正常处理
defer+recover处理
defer fucn() {
err := recover()
if err != nil {
fmt.Println("err=",err)
}
}
recover内置函数可以捕获到异常
错误处理好处
程序不会轻易挂掉,加入警戒代码,让代码更健壮
自定义错误
使用errows.New和panic内置函数
errors.New("错误说明"),会返回一个error类型的值,表示一个错误
panic内置函数接受一个interface{}类型的值(也就是任和值了),可以接受error类型变量,输出错误信息,并退出程序
package main
import (
"fmt"
_ "time"
"errors"
)
func test() {
//使用defer + recover 来捕获和处理异常
defer func() {
err := recover() // recover()内置函数,可以捕获到异常
if err != nil { // 说明捕获到错误
fmt.Println("err=", err)
//这里就可以将错误信息发送给管理员....
fmt.Println("发送邮件给admin@sohu.com~")
}
}()
num1 := 10
num2 := 0
res := num1 / num2
fmt.Println("res=", res)
}
//函数去读取以配置文件init.conf的信息
//如果文件名传入不正确,我们就返回一个自定义的错误
func readConf(name string) (err error) {
if name == "config.ini" {
//读取...
return nil
} else {
//返回一个自定义错误
return errors.New("读取文件错误..")
}
}
func test02() {
err := readConf("config2.ini")
if err != nil {
//如果读取文件发送错误,就输出这个错误,并终止程序
panic(err)
}
fmt.Println("test02()继续执行....")
}
func main() {
//测试
// test()
// for {
// fmt.Println("main()下面的代码...")
// time.Sleep(time.Second)
// }
//测试自定义错误的使用
test02()
fmt.Println("main()下面的代码...")
}