Go03-函数+包+异常处理
1.函数的基本介绍
func main() {
// 1 为完成某一功能的程序指令的集合,成为函数。
// 2 函数分为自定义函数和系统函数。
// 3 函数的定义,使用关键字func来定义一个函数。
/*
func 函数名 (形参列表) (返回值列表) {
函数执行的语句
return 返回值列表
}
*/
a1 := test01(1)
fmt.Println(a1) // 11
}
func test01(a int) int {
return a + 10
}
2.包的基本介绍
-
Go中每一个文件都属于一个包,Go是以包的形式管理文件和项目目录的。
-
包的作用:区分相同的函数、变量名;当程序文件很多时,可以更好的管理项目;控制函数、变量的访问范围,即作用域。
-
包的基本语法:打包语法,package 包名;引入包的基本语法,import 包的路径。
-
包的基本使用
- $GOPATH/src/GoCode/GoDemo02/main/main.go。
package main import ( // 引入包,从GOPATH下引入包,src可以省略。 "GoCode/GoDemo02/utils" "fmt" ) func main() { a := utils.Add(1, 2) fmt.Println(a) // 3 }
- $GOPATH/src/GoCode/GoDemo02/utils/add.go。
package utils func Add(a int, b int) int { return a + b }
-
包名通常和文件所在的文件夹名一致,如add.go在utils文件夹下,就package utils打为utils包;add.go在utils下,也可以打为其他包,如package abc,打为abc包,引入语句为import "GoCode/GoDemo02/abc",使用语句为abc.Add(1, 2)。
-
包名一般为小写字母,如果有多个小写字母使用_隔开。
-
引入包的两种方式:引入包的第一种方式,用于引入一个包,import "包名";引入包的第二种方式,用于引入多个包,import ( "包名" \n "包名")。
-
import导入包时,路径从$GOPATH的src开始,一般不需要不用写src,编译器会自动从src下开始寻找。
-
如果需要让其包访问到本包的函数或者变量,则包中的函数或者变量首字母需要大写,类似于其他编程语言中的public权限访问修饰符,这样才能进行跨包访问。
-
包名较长时,则可以给包取别名,去别名之后,就需要使用别名来访问包中的函数。
- 取别名import a "GoCode/GoDemo02/utils"。
- 取别名之后访问包中的函数就需要使用别名来访问,a.Add(1, 2)。
-
在同一个包下,不能有相同的函数名或者全局变量名,否则报重复定义的错误。理解:包中函数的调用语法是包名.函数名,如果有相同的函数名,则不知道调用的函数。即GoCode/GoDemo02/utils包下,可以有多个go文件,如add1.go、add2.go,但是utils包下所有的文件中只能有一个Add函数。
-
如果需要编译一个可执行文件,就需要将这个包声明为main包,这是一个语法规范;如果开发一个库文件,则包名可以自定义。
-
D:\GOPATH>go build -o bin/my.exe GoCode/GoDemo02/main,go build的-o参数可以指定打包之后可执行文件的路径和名称。在GOPATH下打包,并将可执行文件放到GOPATH/bin下。
-
go build打包时,会将引入的外部项目打包为.a文件,并且.a文件保存在pkd下。如将引入的redis打包为$GOPATH\pkg\windows_amd64\github.com\garyburd\redigo\redis.a。
3.函数的参数和返回值
- 函数的return。
func main() {
// 1 Go中的函数支持多个参数的返回值。
// 在返回多个值时,如果希望忽略某个返回值,则使用_符号占位忽略。
a1, a2 := test01()
fmt.Println(a1, a2) // 2 3
// 使用占位符忽略第一个返回值。
_, a3 := test01()
fmt.Println(a3) // 3
// 2 当函数的返回值只有一个时,可以忽略返回值的()。
a4 := test02()
fmt.Println(a4) // 10
}
func test02() int {
return 10
}
func test01() (int, int) {
return 2, 3
}
- 函数的参数。
func main() {
// 1 基本数据类型和数组都是值传递,即进行值拷贝。在函数内进行修改不会影响原值。
s1 := "alice"
test01(s1)
fmt.Println(s1) // alice
// 2 如果希望函数可以修改值类型的参数,可以传递变量的地址。
s2 := "alice"
test02(&s2)
fmt.Println(s2) // tom
// 3 Go中的函数不支持重载,即不支持同名函数。
}
// 报错。Go不支持函数的重载。
//func test02(a int) {
//
//}
func test02(s *string) {
*s = "tom"
}
func test01(s string) {
s = "tom"
}
- 函数返回值命名。
func main() {
// 1 Go支持对函数的返回值命名。
a1 := test01(10, 10)
fmt.Println(a1) // 20
}
func test01(a int, b int) (sum int) {
sum = a + b
return
}
- 可变参数。
func main() {
// 1 可变参数本质是slice切片,可以通过args[index]访问。
// 2 如果函数中的形参中有可变参数,则可变参数需要写在最后。
a1 := test01(1, 2, 3)
fmt.Println(a1) // 6
}
func test01(a ...int) int {
sum := 0
for i := 0; i < len(a); i++ {
sum += a[i]
}
// 可变参数for...range遍历。
for index, value := range a{
fmt.Println(index, value)
}
return sum
}
5.Go中函数也是一种数据类型
func main() {
// 1 Go中函数也是一种数据类型,可以赋值给一个变量,
// 则该变量就是一个函数类型的变量,并且该变量也可以像函数一样被调到。
var a1 func(int, int) int = test01
// a1的类型func(int, int) int,test01的类型func(int, int) int
fmt.Printf("a1的类型%T,test01的类型%T\n", a1, test01)
// 变量像函数一样调用,等价于a2 := test01(10, 20)
a2 := a1(10, 20)
fmt.Println(a2) // 30
// 2 Go中函数也是一种数据类型,所以可以作为形参传递。
a3 := test02(test01)
fmt.Println(a3) // 20
}
func test02(a func(int, int) int) int {
return a(10, 10)
}
func test01(a int, b int) int {
return a + b
}
6.自定义数据类型
// 1 Go中支持自定义数据类型,可以将自定义数据类型理解为别名。
// 自定义数据语法:type 自定义数据类型名 数据类型
type myInt int
var a1 myInt = 10
// a1类型main.myInt,a1=10
fmt.Printf("a1类型%T,a1=%d\n", a1, a1)
// 2 自定义函数类型。
// a2的类型是func(int, int) int,太长了,可以使用自定义类型。
var a2 func(int, int) int = test01
// a2的类型func(int, int) int
fmt.Printf("a2的类型%T\n", a2)
type MyTest func(int, int) int
var a3 MyTest = test01
// main.MyTest
fmt.Printf("%T", a3)
7.init函数
- init函数。
func main() {
// 1 Go中的init函数会在main函数前执行。
// a b
fmt.Println("b")
}
func init() {
fmt.Println("a")
}
- init函数的执行顺序。
func main() {
// 1 如果.go文件中同时存储全局变量、init函数、main函数,
// 则执行顺序是:全局变量、init函数、main函数。
/*
全局变量执行。。。
init函数执行。。。
main函数开始执行。。。
*/
fmt.Println("main函数开始执行。。。")
// 2 如果main.go和utils.go中都有init函数,main.go使用utils.go中的函数。
// 则执行顺序是:utils.go中的全局变量、utils.go中的init函数、main.go中的全局变量、
// main.go中的init函数、main.go中的main函数。
}
var a int = test01()
func test01() int {
fmt.Println("全局变量执行。。。")
return 1
}
func init() {
fmt.Println("init函数执行。。。")
}
8.匿名函数
func main() {
// 1 匿名函数使用方式一,定义匿名函数时就进行匿名函数的调用。
// 这种匿名函数只能调用一次。
a := func(a int, b int) int {
return a + b
}(10, 20)
fmt.Println(a) // 30
// 2 匿名函数使用方式二,将匿名函数赋值给某个变量,
// 通过该变量来调用匿名函数,该方式定义的匿名函数可以被多次调用。
b := func(a int, b int) int {
return a + b
}
fmt.Println(b(10, 10)) // 20
fmt.Println(b(20, 20)) // 40
// 3 全局匿名函数,将一个匿名函数赋值给一个全局变量,则这个匿名函数,
// 就成为了一个全局匿名函数。
fmt.Println(c(5, 5)) // 25
}
var (
c = func(a int, b int) int {
return a * b
}
)
9.闭包
- 闭包。
func main() {
// 1 闭包就是一个函数和与其相关的应用环境组合的一个整体。
// test01()的返回值是一个函数,这个函数和所拥有的test01()中的全部变量组成了闭包。
// test01()返回的函数中应用的变量只会被初始化一次。
a := test01()
fmt.Println(a(1)) // 11
fmt.Println(a(1)) // 12
}
func test01() func(int) int {
a := 10
return func(b int) int {
a = a + b
return a
}
}
- 闭包练习。
func main() {
a := makeSuffix(".jpg")
fmt.Println(a("1.jpg"))
fmt.Println(a("1.png"))
}
// 1 makeSuffix接受一个文件后缀名.jpg并返回一个闭包。
// 2 调用闭包,传入文件名,如果该文件名的后缀和传入的后缀.jpg相同,则返回原文件名;
// 如果该文件没有指定的后缀,则返回文件名+后缀名。
func makeSuffix(suffix string) func(fileName string) string {
return func(fileName string) string {
if strings.HasSuffix(fileName, suffix) {
return fileName
}
return fileName + suffix
}
}
10.函数的defer
func main() {
// 1 defer在函数执行完成之后执行某些语句,如函数执行完成之后释放资源。
// 2 当执行到defer时,不会立即执行defer后的语句,而是将defer压入栈中,
// 然后继续执行后面的语句。当函数执行完成后,依次从栈顶取出语句执行。
/*
test01
defer02
defer01
*/
test01()
// 3 将defer语句压入栈中时,会将相关的值拷贝到栈中。
/*
10 20
11 21
b= 20
a= 10
*/
test02(10, 20)
}
func test02(a int, b int) {
defer fmt.Println("a=", a)
defer fmt.Println("b=", b)
fmt.Println(a, b)
a++
b++
fmt.Println(a, b)
}
func test01() {
defer fmt.Println("defer01")
defer fmt.Println("defer02")
fmt.Println("test01")
}
11.函数的参数传递
- 两种参数传递的方式:值传递和引用传递。
- 不管是值传递还是引用传递,传递的都是变量的副本。不同的是,值传递的是值的副本拷贝;引用传递的是地址的副本拷贝。一般来说,地址拷贝效率高,因为数据量小。
- 值类型的数据有:基本数据类型int系列、float系列、string、bool、数组和结构体;引用类型的数据有:指针、slice切片、map、管道chan、interface。
- 值类型默认是值传递,变量直接存储值,内存通常在栈中分配。
- 引用类型默认是引用传递,变量存储的是一个地址,变量对应的地址存储真正的数据,内存通常在堆上分配。当没有没有任何变量引用这个地址时,该地址对应的数据空间就成为一个垃圾,由GC来回收。
- 如果希望函数内的变量可以修改函数外值类型的数据,可以传入值类型变量的地址&,函数以指针的方式操作变量。
12.字符串相关的函数
// 1 len()统计字符串的长度,按字节。一个英文和数字占一个字节,一个汉字占三个字节。
a1 := "1中"
fmt.Println(len(a1)) // 4
// 2 []rune(),处理字符串遍历时中文乱码问题。
a2 := "1中"
// 1 ä ¸
for i := 0; i < len(a2);i++ {
fmt.Printf("%c\t", a2[i])
}
fmt.Println()
a3 := []rune(a2)
// 1 ä ¸
for i := 0;i < len(a3);i++ {
fmt.Printf("%c\t", a3[i])
}
fmt.Println()
// 3 字符串转整数。
a4, _ := strconv.Atoi("12")
fmt.Println(a4) // 12
// 4 整数转字符串。
a5 := strconv.Itoa(123)
fmt.Println(a5) // 123
// 5 字符串转[]byte数组
a6 := []byte("hello 张三")
// [104 101 108 108 111 32 229 188 160 228 184 137]
fmt.Println(a6)
// 6 byte转字符串。
a7 := []byte{97, 98, 99}
a8 := string(a7)
fmt.Println(a8) // abc
// 7 十进制转2、8、16进制。
fmt.Println(strconv.FormatInt(10, 2)) // 1010
fmt.Println(strconv.FormatInt(10, 8)) // 12
fmt.Println(strconv.FormatInt(10, 16)) // a
// 8 查找子串是否在指定的字符串中。
fmt.Println(strings.Contains("hello", "ll")) // true
// 9 统计字符串包含几个指定的子串。
fmt.Println(strings.Count("hello hello hello", "ll")) // 3
// 10 字符串比较。
b1 := "hello"
b2 := "HeLLo"
b3 := "hello"
// ==在比较字符串时,区分大小写
fmt.Println(b1 == b2) // false
fmt.Println(b1 == b3) // true
// strings.EqualFold()不区分大小写比较字符串。
fmt.Println(strings.EqualFold(b1, b2)) // true
// 11 子串查找,如果包含子串,则返回子串第一次出现的索引;如果不包含子串,则返回-1。
fmt.Println(strings.Index("hello", "ll")) // 2
fmt.Println(strings.Index("hello", "k")) // -1
// 12 返回子串最后一次出现的位置,没有找到字符返回-1。
fmt.Println(strings.LastIndex("hello", "e")) // 1
fmt.Println(strings.LastIndex("hello", "m")) // -1
// 13 字符串替换。
// strings.Replace(),最后一个参数n表示,要替换字符串的个数,-1表示替换所有。
// hemmo hello
fmt.Println(strings.Replace("hello hello", "ll", "mm", 1))
// hemmo hemmo
fmt.Println(strings.Replace("hello hello", "ll", "mm", -1))
// 14 字符串切割,将字符串按照指定字符切割为数组。
// [hello hello]
fmt.Println(strings.Split("hello,hello", ","))
// 15 字符串转大写和小写。
// 转小写 hello
fmt.Println(strings.ToLower("HeLLO"))
// 转大写 HELLO
fmt.Println(strings.ToUpper("hello"))
// 16 去掉字符串两边的空格。
fmt.Println(strings.TrimSpace(" hello ")) // hello
// 17 Trim的其中用法。
// 去掉字符串两边的指定字符。hello
fmt.Println(strings.Trim(",hello,", ","))
// 去掉字符串左边指定字符。,hello
fmt.Println(strings.TrimLeft("1,hello", "1"))
// 去掉字符串右边指定字符。hello,
fmt.Println(strings.TrimRight("hello,1", "1"))
// 18 判断字符串是否以指定字符开头。
fmt.Println(strings.HasPrefix("hello", "h")) // true
// 19 判断字符串是否以指定字符串结尾。
fmt.Println(strings.HasSuffix("hello", "o")) // true
13.时间和日期相关的函数
// 1 获取当前时间,time.Time用于表示时间类型。
a1 := time.Now();
// 2023-01-07 15:40:20.9207375 +0800 CST m=+0.002348001,type=time.Time
fmt.Printf("%v,type=%T\n", a1, a1)
// 2 获取日期的年月日时分秒信息。
fmt.Printf("年=%v\n", a1.Year()) // 年=2023
fmt.Printf("月=%v\n", a1.Month()) // 月=January
// Month(),返回的是Month类型,可以转换为int。
fmt.Printf("月=%v\n", int(a1.Month())) // 月=1
fmt.Printf("日=%v\n", a1.Day()) // 日=7
fmt.Printf("时=%v\n", a1.Hour()) // 时=15
fmt.Printf("分=%v\n", a1.Minute()) // 分=51
fmt.Printf("秒=%v\n", a1.Second()) // 秒=0
// 3 日期时间格式化方式一。
a2 := time.Now()
// 当前时间 2023-1-7 16:19:58
fmt.Printf("当前时间 %d-%d-%d %d:%d:%d\n", a2.Year(),
a2.Month(), a2.Day(), a2.Hour(), a2.Minute(), a2.Second())
a3 := fmt.Sprintf("当前时间 %d-%d-%d %d:%d:%d\n", a2.Year(),
a2.Month(), a2.Day(), a2.Hour(), a2.Minute(), a2.Second())
fmt.Printf(a3)
// 3 日期时间格式格式化方式二。
a4 := time.Now()
// Format()的参数2006-01-02 15:04:05是固定的,必须这样写。
// 2023-01-07 16:27:21
fmt.Printf(a4.Format("2006-01-02 15:04:05"))
fmt.Println()
// 2023-01-07
fmt.Printf(a4.Format("2006-01-02"))
fmt.Println()
// 16:27:21
fmt.Printf(a4.Format("15:04:05"))
fmt.Println()
// 4 time中的时间常量。
minute := time.Minute
// 10分钟
fmt.Println(10 * minute) // 10m0s
// 5 Sleep的使用,每个一秒打印一个数字,打印到3退出。
a5 := 0
// 0 1 3
for {
fmt.Println(a5)
time.Sleep(1 * time.Second)
a5++
if a5 == 3 {
break
}
}
// 6 Unix()和UnixNano()
a6 := time.Now()
// Unix()返回从1970年1月1日到时间点所经过的时间,单位秒。
fmt.Println(a6.Unix()) // 1673081313
// UnixNano()返回从1970年1月1日到时间点所经过的时间,单位纳秒;
// UnixNano()返回值类型是int64,如果返回值范围超过int64,结果时未定义的。
fmt.Println(a6.UnixNano()) // 1673081313761040800
14.new和make分配内存
// 1 new用来给值类型分配内存,比如,int系列、float系列、string、bool、数组和struct。
// new()返回一个指针,这个指针指向new分配的内存。
a1 := new(int)
*a1 = 100
// a1的地址 0xc000006028
fmt.Println("a1的地址", &a1)
// a1的值 0xc00000a0a8
fmt.Println("a1的值", a1)
// a1地址对应的值 100
fmt.Println("a1地址对应的值", *a1)
// 2 make可以给引用类型分配内存,比如给channel、map、slice分配内存。
15.异常处理
func main() {
// 1 当程序报错后,会由于异常而退出,如果想不退出就需要进行异常处理。
// 报错:panic: runtime error: integer divide by zero
// test01()
fmt.Println("程序继续执行")
// 2 Go中可以通过panic抛出异常,然后再defer中通过recover来捕获异常。
test02()
fmt.Println("程序继续执行。。。")
// 3 自定义错误。使用errors.New()和panic内置函数完成自定义错误。
// errors.New("xxx错误")会返回一个error类型值,这个值表示一个错误。
// panic内置函数可以接受一个interface{}类型的值(interface{}类型的值可以理解为任何值)作为参数。
// panic接受error类型的变量后会输出错误信息,并退出程序。
// test03继续执行
test03("init.config")
// 报错打印panic: 文件名错误,并且退出程序的执行,不会输出test03继续执行。
test03("init1.config")
}
// 读取init.config配置文件的信息,如果文件名不是init.config则报错。
func readFile(fileName string) error {
if fileName == "init.config" {
return nil
} else {
return errors.New("文件名错误")
}
}
func test03(fileName string) {
err := readFile(fileName)
if err != nil {
panic(err)
}
fmt.Println("test03继续执行")
}
func test02() {
// 使用defer + recover来捕获异常。
defer func() {
// recover是系统内置函数,可以捕获到运行时抛出的异常。
error := recover()
fmt.Println(error)
if error != nil {
fmt.Println("捕获到异常", error)
}
}()
a1 := 10
a2 := 0
fmt.Println(a1 / a2)
}
func test01() {
a1 := 10
a2 := 0
fmt.Println(a1 / a2)
}
标签:函数,int,fmt,Go03,a1,func,Println,异常
From: https://www.cnblogs.com/godistance/p/17258512.html