2-golang基础
1. 流程控制
1. 顺序控制
2. 分支控制
- if-else
func main() {
// golang支持在if中直接定义一个变量
if age := 20; age > 18 {
fmt.Println("aa")
}
}
-
switch
func main() { var age int = 18 switch age { // case 中可以使用多个表达式(常量、变量、有结果的函数),用逗号隔开 // case 结束不需要break case 18, 20: fmt.Println("aaa") // fallthrough穿透,会同时执行下一个case(不管下一个case条件是否满足) fallthrough case 25: fmt.Println("bbb") default: fmt.Println("ccc") } }
3. 循环控制
golang中没有while和do-while循环
func main() {
// 第一种
for i := 0; i < 5; i++ {
fmt.Println("hello1")
}
// 第二种
j := 0
for j < 5 {
fmt.Println("hello2")
j++
}
// 第三种,配合break使用
k := 0
for {
fmt.Println("hello3")
k++
if k >= 5 {
break
}
}
}
func main() {
// for-range遍历字符串
// for-range按照字符遍历,可以有中文不会乱码
// golang按utf8编码,中文占3字节,所以北的下标是6,京的下标是9
str := "abc~ok北京"
for index, val := range str {
fmt.Printf("index=%v val=%c \n", index, val)
}
}
2. 函数
1. 基本介绍
func 函数名(形参列表) (返回值列表) {
执行语句...
return 返回值列表
}
// 斐波那契数列 1 1 2 3 5 8 13...
func test(n int) int {
if n == 1 || n == 2 {
return 1
} else {
return test(n-1) + test(n-2)
}
}
func main() {
result := test(7)
fmt.Println(result)
}
-
golang中基本数据类型和数组都是值传递的,即进行值拷贝,在函数内修改,不会影响到函数外的值
-
如果希望函数内的变量能修改函数外的变量,可以传递变量的地址(&)到函数中
-
golang支持自定义类型
// 相当于是给int定义一个别名,但是语法会将myInt和int作为两个不同的类型 type myInt int
-
golang支持可变参数个数
通过args[index]可以访问到各个参数的值
// 例如:支持0到多个参数 func sum(args ...int) sum int { } // 例如:支持1到多个参数 func sum(n1 int, args ...int) sum int { }
func sum(n1 int, args ...int) int { sum := n1 for i := 0; i < len(args); i++ { sum += args[i] } return sum } func main() { result := sum(10, 20, 30, 40, 50) fmt.Println(result) }
2. init函数
每个源文件都能包含一个init函数,该函数在main函数之前调用,可以在init函数中做一些初始化操作。
执行顺序:先执行全局变量定义,再执行init函数,最后执行main函数
如果main.go中import utils.go,则先执行utils.go中的全局变量和init函数,再执行main中的全局变量和init函数
3. 闭包
一个函数与其相关的引用环境组合的一个整体(实体)
// 累加器
//AddUpper是一个函数,返回数据类型是 func(int) int
func AddUpper() func(int) int {
var n int = 10
var str = "hello"
// 匿名函数,引用到函数外的n和str,这个匿名函数和n、str构成闭包
return func(x int) int {
n = n + x
str += "a"
fmt.Println("str=", str)
return n
}
}
func main() {
f := AddUpper()
// 反复调用f函数时,n和str是初始化一次,因此每次调用都进行累加
fmt.Println(f(1)) //11
fmt.Println(f(2)) //13
fmt.Println(f(3)) //16
}
4. defer
为了在函数执行完毕后,及时释放资源(比如:关闭文件流、关闭数据库连接等),golang提供了defer(延时机制)。
在创建资源后,可以立即使用defer关闭资源。此时defer后面的逻辑中可以继续使用之前创建的资源,等函数结束后,会自动调用defer关闭资源,程序员不用再为什么时候关闭资源而烦心。
func sum(n1 int, n2 int) int {
// 当执行到defer时,会将defer后面的语句压入到独立的栈(defer栈)
// 当函数执行完毕后,再从defer栈按照先入后出的方式出栈
defer fmt.Println("ok1 n1=", n1)
defer fmt.Println("ok2 n2=", n2)
n1 = 20 // 此时n1改为20,影响res的结果,但是之前defer栈中,n1还是10
res := n1 + n2
fmt.Println("ok3 res=", res)
return res
}
func main() {
res := sum(10, 20)
fmt.Println("res=", res)
// 运行结果
// ok3 res= 40
//ok2 n2= 20
//ok1 n1= 10
//res= 40
}
5. 错误处理机制
golang中可以抛出一个panic的异常,然后在defer中通过recover捕获这个异常。
func test() {
defer func() {
// recover是一个内置函数,可以捕获到异常
err := recover()
// 说明捕获到异常
if err != nil {
fmt.Println("err= ", err)
}
}()
n1 := 10
n2 := 0
res := n1 / n2
fmt.Println("res= ", res)
}
func main() {
test()
fmt.Println("main over")
}
自定义错误处理:
func readConf(name string) (err error) {
if name == "config.ini" {
return nil
} else {
return errors.New("读取失败...")
}
}
func test() {
err := readConf("config2.ini")
if err != nil {
// 如果读取发生错误,就输出这个错误,并终止程序
panic(err)
}
fmt.Println("test ...")
}
func main() {
test()
fmt.Println("main over")
}
3. 数组
- 数组可以存放多个同一类型的数据,在golang中,数组是值类型
- 数组的地址可以用数组名来获取
- 数组的地址和数组中第一个元素的地址相同
// 数组定义的几种方式
var arr1 [3]int = [3]int{1, 2, 3}
fmt.Println(arr1)
var arr2 = [3]int{5, 6, 7}
fmt.Println(arr2)
var arr3 = [...]int{8, 9, 10}
fmt.Println(arr3)
arr4 := [...]int{1: 100, 0: 200, 2: 300}
fmt.Println(arr4)
数组遍历:
// 方式1:常规for循环遍历
// 方式2:for-range遍历
func main() {
arr := [...]int{1, 2, 3, 4, 5}
for index, value := range arr {
fmt.Printf("下标%v 的值为%v\n", index, value)
}
}
数组属于值类型,在默认情况下是值传递,因此会进行值拷贝。(书组件不会相互影响)
func test(arr [3]int) {
arr[0] = 88
}
func main() {
arr := [3]int{11, 22, 33}
test(arr)
fmt.Println(arr) // 输出[11 22 33]
}
若想修改数组内的数据,可以采用引用传递(指针)
func test(arr *[3]int) {
(*arr)[0] = 88
}
func main() {
arr := [3]int{11, 22, 33}
test(&arr)
fmt.Println(arr) // 输出[88 22 33]
}
4. 切片
- 切片是一个引用类型,遵守引用传递机制
- 切片的长度是可以变化的,切片可以理解为是一个可以动态变化的数组
- 修改切片中元素的值,被引用的数组中的值也同时改变
- 切片可以继续切片
func main() {
var intArr [5]int = [...]int{1, 22, 33, 55, 66}
//声明一个切片
// intArr[1:3]表示slice引用到intArr这个数组中下标1到3的元素(含头不含尾)
slice := intArr[1:3]
fmt.Println("intArr=", intArr) // [1 22 33 55 66]
fmt.Println("slice=", slice) // [22 33]
fmt.Println("slice的元素个数=", len(slice))
// 切片的容量,可变
fmt.Println("slice的容量=", cap(slice))
}
切片定义方式:
// 第一种:引用已经创建好的数组
// 第二种:使用make创建,cap选填
// var 切片名 []type = make([]type, len, cap)
// 第三种:定义切片是,直接指定具体数组
// var slice []int = []int{1,2,3,4}
注意:
var slice = arr[0:end] 等价于 var slice = arr[:end]
var slice = arr[start:len(arr)] 等价于 var slice = arr[start:]
var slice = arr[0:len(arr)] 等价于 var slice = arr[:]
切片扩容
func main() {
var slice []int = []int{100, 200, 300}
// 通过append追加具体元素
slice = append(slice, 400, 500, 600)
fmt.Println(slice)
// 通过append追加切片
slice = append(slice, slice...)
fmt.Println(slice)
}
string与切片
func main() {
str := "hello world"
slice := str[6:]
fmt.Println(slice) // world
// 因为要替换中文,所以选择[]rune
// 如果没有中文,也可以选择[]byte
slice1 := []rune(str)
slice1[0] = '北'
str = string(slice1)
fmt.Println(str) // 北ello world
}
5. 排序与查找
1. 冒泡排序
func BubbleSort(slice []int) []int {
temp := 0
for i := 0; i < len(slice)-1; i++ {
for j := 0; j < len(slice)-1-i; j++ {
if slice[j] > slice[j+1] {
temp = slice[j]
slice[j] = slice[j+1]
slice[j+1] = temp
}
}
}
return slice
}
func main() {
// 定义切片
slice := []int{24, 69, 80, 57, 13}
fmt.Println("排序前slice=", slice)
slice = BubbleSort(slice)
fmt.Println("排序后slice=", slice)
}
2. 二分查找(前提是有序数组)
func BinaryFind(arr *[6]int, leftIndex int, rightIndex int, findVal int) {
// 判断leftIndex是否大于rightIndex,如果大于,说明找不到
if leftIndex > rightIndex {
fmt.Println("找不到")
return
}
// 先找到中间的下标
middle := (leftIndex + rightIndex) / 2
if (*arr)[middle] > findVal {
BinaryFind(arr, leftIndex, middle-1, findVal)
} else if (*arr)[middle] < findVal {
BinaryFind(arr, middle+1, rightIndex, findVal)
} else {
fmt.Printf("找到了, 下标为%v\n", middle)
}
}
func main() {
// 定义数组
arr := [6]int{1, 3, 5, 7, 9, 11}
BinaryFind(&arr, 0, len(arr)-1, 13)
}
6. map
- golang中的map,key可以是bool、数字、string、指针、channel
- 通常key为int、string
- slice、map、function不能作为key,因为没法用 == 判断
- golang中没有办法一次删除所有的key,可以遍历删除;或者map = make(…), make一个新的,原来的等待GC回收
func main() {
// 第一种
var map1 map[int]string
map1 = make(map[int]string)
map1[1] = "aaa"
fmt.Println(map1)
// 第二种
map2 := make(map[int]string)
map2[1] = "bbb"
fmt.Println(map2)
// 第三种
map3 := map[int]string{
1: "ccc",
2: "ddd",
}
fmt.Println(map3)
}
func main() {
cityMap := make(map[string]string)
cityMap["c1"] = "北京"
cityMap["c2"] = "上海"
cityMap["c3"] = "广州"
fmt.Println("111--->", cityMap)
cityMap["c3"] = "深圳" // 覆盖广州
fmt.Println("222--->", cityMap)
delete(cityMap, "c3") // 删除
delete(cityMap, "c5") // 不存在key,也不会报错
fmt.Println("333--->", cityMap)
// 查找
// 如果存在,res为true; 如果不存在,res为false
val, res := cityMap["c2"]
if res {
fmt.Printf("有c2, 值为%v\n", val)
} else {
fmt.Println("没有c2")
}
// make一个新的空间,之前的map等待GC回收
cityMap = make(map[string]string)
fmt.Println(cityMap)
}
7. 结构体
- golang支持面向对象变成特性,不是纯粹的OOP语言
- golang的结构体和OOP的class同等地位
- golang面向对象编程较为简单,没有继承、重载、构造函数、this指针等等
type Stu struct {
Name string
Age int
Sex int
}
func main() {
var stu1 Stu
fmt.Println(stu1)
stu1.Name = "Tom"
stu1.Age = 20
stu1.Sex = 1
fmt.Println(stu1)
}
- struct的每个字段上,可以写一个tag,该tag可以通过反射机制获取,常见的使用场景就是序列化和反序列化
// `json:"name"`就是结构体的tag
type Stu struct {
Name string `json:"name"`
Age int `json:"age"`
Sex int `json:"sex"`
}
func main() {
var stu1 Stu
stu1.Name = "Tom"
stu1.Age = 20
stu1.Sex = 1
// 将stu序列化为 json格式字串
jsonStr, err := json.Marshal(stu1)
if err != nil {
fmt.Println("json 处理错误 ", err)
}
fmt.Println("jsonStr", string(jsonStr))
}
8. 方法
golang中方法是作用在指定的数据类型上的(即:和指定的数据类型绑定), 因此,自定义类型都可以有方法,而不仅仅是struct
func (receiver type) methodName(参数列表) (返回值列表) {
方法体
return 返回值
}
// func (a A) test(){}表示A结构体有一个方法,名为test()
// test()是和A类型绑定的,只能通过A类型调用,不能直接调用或使用其他类型调用
type A struct {
Num int
}
func (a A) test() {
fmt.Println(a.Num)
}
func main() {
var a A
p.Num = 10
p.test()
}