go学习笔记

变量与常量

变量

1var 声明变量
   var 变量名称 type
示例:var age int = 20
	 var username = "张三 //类型推导
	  n := 10 //短变量声明法(变量名 := 表达式)

2、一次定义多个变量
  var identifier1, identifier2 type
示例:
func main() {
    var username, sex string
    username = "张三"
    sex = "男"
    fmt.Println(username, sex)
}
申明变量的时候赋值
var a, b, c, d = 1, 2, 3, false
  
3、批量声明变量的时候指定类型
示例:
var (
    a string
    b int
    c bool
)
a = "张三" b = 10
c = true
fmt.Println(a,b,c)

//批量声明变量并赋值
var (
    a string = "张三" 
    b int = 20
    c bool = true
)
fmt.Println(a, b, c) 

注:匿名变量

在使用多重赋值时,如果想要忽略某个值,可以使用匿名变量(anonymous variable)。 匿名变量用一个下划线_表示,例如:

func getInfo() (int, string) {
    return 10, "张三"
}

func main() {
	_, username := getInfo()
    fmt.Println(username)
}

常量

1、使用 const 定义常量
const pi = 3.1415

2、多个常量也可以一起声明
const (
    pi = 3.1415
    e = 2.7182
)
const 同时声明多个常量时,如果省略了值则表示和上面一行的值相同。 例如:
const (
    n1 = 100
    n2
    n3
)

基本数据类型

Go 语言中数据类型分为:基本数据类型和复合数据类型

基本数据类型有:

整型、浮点型、布尔型、字符串

复合数据类型有:

数组、切片、结构体、函数、map、通道(channel)、接口等。

整型

整型分为以下两个大类:

有符号整形按长度分为:int8、int16、int32、int64

对应的无符号整型:uint8、uint16、uint32、uint64

func main() {
    var num int64
    num = 123
    fmt.Printf("值:%v 类型%T", num, num)
 	//unsafe.Sizeof(n1) 是 unsafe 包的一个函数,可以返回 n1 变量占用的字节数 
    fmt.Println(unsafe.Sizeof(num))
    //int 不同长度直接的转换
    num2 := int32(num1)
	fmt.Printf("值:%v 类型%T", num2, num2) //值:123 类型 int32
}

浮点型

Go 语言支持两种浮点型数:float32 和 float64。Go 语言中浮点数默认是 float64。

布尔型

Go 语言中以 bool 类型进行声明布尔型数据,布尔型数据只有 true(真)和 false(假)两个

值。

注意:

\1. 布尔类型变量的默认值为 false。

\2. Go 语言中不允许将整型强制转换为布尔型.

\3. 布尔型无法参与数值运算,也无法与其他类型进行转换。

字符串

Go 语言的字符串常见转义符包含回车、换行、单双引号、制表符等,如下表所示。
转义符 含义
\r 	回车符(返回行首)
\n 	换行符(直接跳到下一行的同列位置)
\t 	制表符
\' 	单引号
\" 	双引号
\\ 	反斜杠


示例:
//1、打印一个 Windows 平台下的一个文件路径:
func main() {
    fmt.Println("str := \"c:\\Code\\demo\\go.exe\"")
}
//2、多行字符串
s1 := `第一行
第二行
第三行
`
fmt.Println(s1)

//3、字符串的常用操作
//len(str)求字符串的长度
var str = "this is str"
fmt.Println(len(str))
//拼接字符串  +或 fmt.Sprintf
var str1 = "你好" var str2 = "golang"
fmt.Println(str1 + str2)
var str3 = fmt.Sprintf("%v %v", str1, str2)
//strings.Split 分割字符串
var str = "123-456-789"
var arr = strings.Split(str, "-")
fmt.Println(arr)
//strings.contains 判断是否包含
var str = "this is golang" 
var flag = strings.Contains(str, "golang")
fmt.Println(flag)
//strings.HasPrefix,strings.HasSuffix 前缀/后缀判断 判断首字符尾字母是否包含指定字符 
var str = "this is golang" 
var flag = strings.HasPrefix(str, "this")
fmt.Println(flag)
var str = "this is golang" 
var flag = strings.HasSuffix(str, "go")
fmt.Println(flag)
//strings.Index(),strings.LastIndex() 子串出现的位置 判断字符串出现的位置
var str = "this is golang" 
var index = strings.Index(str, "is") //从前往后
fmt.Println(index)
var str = "this is golang" 
var index = strings.LastIndex(str, "is") //从后网前
fmt.Println(index)
//Join 拼接字符串
var str = "123-456-789" var arr = strings.Split(str, "-")
var str2 = strings.Join(arr, "*")
fmt.Println(str2)

//4、修改字符串
要修改字符串,需要先将其转换成[]rune[]byte,完成后再转换为 string。无论哪种转换,
都会重新分配内存,并复制字节数组。
func changeString() {
    s1 := "big"
    // 强制类型转换
    byteS1 := []byte(s1)
    byteS1[0] = 'p' fmt.Println(string(byteS1))
    s2 := "白萝卜"
    runeS2 := []rune(s2)
    runeS2[0] = '红' fmt.Println(string(runeS2))
}

byte 和 rune 类型

Go 语言的字符有以下两种:

\1. uint8 类型,或者叫 byte 型,代表了 ASCII 码的一个字符。

\2. rune 类型,代表一个 UTF-8 字符。

当需要处理中文、日文或者其他复合字符时,则需要用到 rune 类型。rune 类型实际是一个int32

func main() {
    s := "hello 张三"
    for i := 0; i < len(s); i++ { //byte
        fmt.Printf("%v(%c) ", s[i], s[i])
    }
    fmt.Println()
    for _, r := range s { //rune
        fmt.Printf("%v(%c) ", r, r)
    }
    fmt.Println()
}

输出:
104(h) 101(e) 108(l) 108(l) 111(o) 32( ) 229(å) 188(¼) 160( ) 228(ä) 184(¸) 137()
104(h) 101(e) 108(l) 108(l) 111(o) 32( ) 24352() 19977()
因为 UTF8 编码下一个中文汉字由 3 个字节组成,所以我们不能简单的按照字节去遍历一个包含中文的字符串,否则就会出现上面输出中第一行的结果。
字符串底层是一个 byte 数组,所以可以和[]byte 类型相互转换。字符串是不能修改的字符串是由 byte 字节组成,所以字符串的长度是 byte 字节的长度。 rune 类型用来表示 utf8 字符,一个 rune 字符由一个或多个 byte 组成。

数据类型转换

//1、数值类型之间的相互转换
func main() {
    var a int8 = 20
    var b int16 = 40
    var c = int16(a) + b //要转换成相同类型才能运行
    fmt.Printf("值:%v--类型%T", c, c) //值:60--类型 int16
    
    var a float32 = 3.2
    var b int16 = 6
    var c = a + float32(b)
    fmt.Printf("值:%v--类型%T", c, c) //值:9.2--类型 float32
}
//2、其他类型转换成 String 类型
//注意:sprintf 使用中需要注意转换的格式 int 为%d float 为%f bool 为%t byte 为%c
func main() {
    var i int = 20
    var f float64 = 12.456
    var t bool = true
    var b byte = 'a' var strs string
    strs = fmt.Sprintf("%d", i)
    fmt.Printf("str type %T ,strs=%v \n", strs, strs)
    strs = fmt.Sprintf("%f", f)
    fmt.Printf("str type %T ,strs=%v \n", strs, strs)
    strs = fmt.Sprintf("%t", t)
    fmt.Printf("str type %T ,strs=%v \n", strs, strs)
    strs = fmt.Sprintf("%c", b)
    fmt.Printf("str type %T ,strs=%v \n", strs, strs)
}
//使用 strconv 包里面的几种转换方法进行转换
func main() {
    //1、int 转换成 string
    var num1 int = 20
    s1 := strconv.Itoa(num1)
    fmt.Printf("str type %T ,strs=%v \n", s1, s1)
    // 2、float 转 string
    var num2 float64 = 20.113123
    /* 参数 1:要转换的值
    参数 2:格式化类型
    'f'(-ddd.dddd)、
    'b'(-ddddp±ddd,指数为二进制)、
    'e'(-d.dddde±dd,十进制指数)、
    'E'(-d.ddddE±dd,十进制指数)、
    'g'(指数很大时用'e'格式,否则'f'格式)、
    'G'(指数很大时用'E'格式,否则'f'格式)。
    参数 3: 保留的小数点 -1(不对小数点格式化)
    参数 4:格式化的类型
    */
    s2 := strconv.FormatFloat(num2, 'f', 2, 64)
    fmt.Printf("str type %T ,strs=%v \n", s2, s2)
    // 3、bool 转 string
    s3 := strconv.FormatBool(true)
    fmt.Printf("str type %T ,strs=%v \n", s3, s3)
    //4、int64 转 string
    var num3 int64 = 20
    /*第二个参数为 进制
    */
    s4 := strconv.FormatInt(num3, 10)
    fmt.Printf("类型 %T ,strs=%v \n", s4, s4)
}

//3、String 类型转换成数值类型
	//1、string 类型转换成 int 类型
    var s = "1234"
    i64, _ := strconv.ParseInt(s, 10, 64)
    fmt.Printf("值:%v 类型:%T", i64, i64)
    //2、string 类型转换成 float 类型
    str := "3.1415926535" v1, _ := strconv.ParseFloat(str, 32)
    v2, _ := strconv.ParseFloat(str, 64)
    fmt.Printf("值:%v 类型:%T\n", v1, v1)
    fmt.Printf("值:%v 类型:%T", v2, v2)
    //3、string 类型转换成 bool 类型(意义不大)
    b, _ := strconv.ParseBool("true") // string 转 bool
    fmt.Printf("值:%v 类型:%T", b, b)
    //4、string 转字符
    s := "hello 张三"
    for _, r := range s { //rune
    fmt.Printf("%v(%c) ", r, r)
    }
    fmt.Println()

Golang 内置的运算符

算术运算符

运算符 	  描述
+ 			相加
- 			相减
*		 	相乘
/ 			相除
% 			求余=被除数-(被除数/除数)*除数

关系运算符

运算符 	  描述
== 			检查两个值是否相等,如果相等返回 True 否则返回 False。
!= 			检查两个值是否不相等,如果不相等返回 True 否则返回 False。
> 			检查左边值是否大于右边值,如果是返回 True 否则返回 False。
>= 			检查左边值是否大于等于右边值,如果是返回 True 否则返回 False。
< 			检查左边值是否小于右边值,如果是返回 True 否则返回 False。
<= 			检查左边值是否小于等于右边值,如果是返回 True 否则返回 False。

逻辑运算符

运算符 	  描述
&& 			逻辑 AND 运算符。 如果两边的操作数都是 True,则为 True,否则为 False。
|| 			逻辑 OR 运算符。 如果两边的操作数有一个 True,则为 True,否则为 False。
! 			逻辑 NOT 运算符。 如果条件为 True,则为 False,否则为 True。

赋值运算符

运算符   描述
= 		简单的赋值运算符,将一个表达式的值赋给一个左值
+= 		相加后再赋值
-= 		相减后再赋值
*= 		相乘后再赋值
/= 		相除后再赋值
%= 		求余后再赋值

//示例
d := 8 + 2*8 // 赋值运算从右向左
fmt.Println(d)
x := 10
x += 5 //x=x+5
fmt.Println("x += 5 的值:", x)
x := 10
x -= 5 //x=x-5
fmt.Println("x -= 5 的值:", x)
x := 10
x *= 5 //x=x*5
fmt.Println("x *= 5 的值:", x)
x := 10.0
x /= 5
fmt.Println("x /= 5 的值:", x)
x := 10
x %= 3
fmt.Println("x %= 3 的值:", x)

流程控制

if else(分支结构)

Go 语言中 if 条件判断的格式如下:
if 表达式 1 {
	分支 1
} else if 表达式 2 {
	分支 2
} else{
	分支 3
}


示例1func ifDemo1() {
    score := 65
    if score >= 90 {
    	fmt.Println("A")
    } else if score > 75 {
    	fmt.Println("B")
    } else {
    	fmt.Println("C")
    }
}

示例2if 条件判断特殊写法
//if 条件判断还有一种特殊的写法,可以在 if 表达式之前添加一个执行语句,再根据变量值进行判断
if score := 56; score >= 90 {
	fmt.Println("A")
} else if score > 75 {
	fmt.Println("B")
} else {
	fmt.Println("C")
}

for(循环结构)

for 循环的基本格式如下:
for 初始语句;条件表达式;结束语句{
    循环体语句
}
注意:Go 语言中是没有 while 语句的,我们可以通过 for 代替

示例1for i := 0; i < 10; i++ {
    fmt.Println(i)
}
示例2//for 循环的初始语句可以被忽略,但是初始语句后的分号必须要写
for i := 0; i < 10; i++ {
    fmt.Println(i)
}
示例3//for 循环的初始语句和结束语句都可以省略
i := 0
for i < 10 {
    fmt.Println(i)
    i++
}
示例4//for 无限循环
for {
	循环体语句
}
//for 循环可以通过 break、goto、return、panic 语句强制退出循环。
k := 1
for { // 这里也等价 for ; ; {
    if k <= 10 {
   	 	fmt.Println("ok~~", k)
    } else {
    	break //break 就是跳出这个 for 循环
    }
    k++
}

for range(键值循环)

Go 语言中可以使用 for range 遍历数组、切片、字符串、map 及通道(channel)。 通过 for range 遍历的返回值有以下规律:

\1. 数组、切片、字符串返回索引和值。

\2. map 返回键和值。

\3. 通道(channel)只返回通道内的值。

str := "abc 上海"
for index, val := range str {
    fmt.Printf("index=%d, val=%c \n", index, val)
}


str := "abc 上海"
for _, val := range str {
    fmt.Printf("val=%c \n", val)
}

switch case

Go 语言中每个 case 语句中可以不写 break,不加 break 也不会出现穿透的现象

示例1:
extname := ".a"
switch extname {
    case ".html":
    	fmt.Println("text/html")
    case ".css":
    	fmt.Println("text/css")
    case ".js":
    	fmt.Println("text/javascript")
    default:
    	fmt.Println("格式错误")
}

示例2:
//一个分支可以有多个值,多个 case 值中间使用英文逗号分隔
n := 2
switch n {
    case 1, 3, 5, 7, 9:
        fmt.Println("奇数")
    case 2, 4, 6, 8:
    	fmt.Println("偶数")
    default:
    	fmt.Println(n)
}

switch n := 7; n {
    case 1, 3, 5, 7, 9:
    	fmt.Println("奇数")
    case 2, 4, 6, 8:
    	fmt.Println("偶数")
    default:
    	fmt.Println(n)
}

示例3//分支还可以使用表达式,这时候 switch 语句后面不需要再跟判断变量
age := 56
switch {
    case age < 25:
    	fmt.Println("好好学习吧!")
    case age > 25 && age <= 60:
   	 fmt.Println("好好工作吧!")
    case age > 60:
    	fmt.Println("好好享受吧!")
    default:
    	fmt.Println("活着真好!")
}

示例4//switch 的穿透 fallthrought
func switchDemo5() {
    s := "a"
    switch {
        case s == "a":
        	fmt.Println("a")
        	fallthrough
        case s == "b":
        	fmt.Println("b")
        case s == "c":
       	 fmt.Println("c")
        default:
        	fmt.Println("...")
    }
}

输出:
a
b

break(跳出循环)

//1、 switch(开关语句)中在执行一条 case 后跳出语句的作用。
extname := ".a"
switch extname {
    case ".html":
        fmt.Println("text/html")
        break
    case ".css":
        fmt.Println("text/css")
        break
    case ".js":
        fmt.Println("text/javascript")
        break
    default:
        fmt.Println("格式错误")
        break
}
//2、 for 循环中默认 break 只能跳出一层循环
func main() {
    for i := 0; i < 2; i++ {
        for j := 0; j < 10; j++ {
            if j == 2 {
            	break
            }
        fmt.Println("i j 的值", i, "-", j)
        }
     }
}


k := 1
for { // 这里也等价 for ; ; {
    if k <= 10 {
    	fmt.Println("ok~~", k)
    } else {
    	break //break 就是跳出这个 for 循环
    }
    k++
}
//3、 在多重循环中,可以用标号 label 标出想 break 的循环。
func main() {
    lable2:
    for i := 0; i < 2; i++ {
        for j := 0; j < 10; j++ {
            if j == 2 {
           	 break lable2
            }
        fmt.Println("i j 的值", i, "-", j)
        }
    }
}

continue(继续下次循环)

continue 语句可以结束当前循环,开始下一次的循环迭代过程,仅限在 for 循环内使用。

示例1:
func main() {
    for i := 0; i < 2; i++ {
        for j := 0; j < 4; j++ {
            if j == 2 {
                continue
            }
            fmt.Println("i j 的值", i, "-", j)
        }
    }
}

输出:
i j 的值 0 - 0
i j 的值 0 - 1
i j 的值 0 - 3
i j 的值 1 - 0
i j 的值 1 - 1
i j 的值 1 - 3

示例2//在 continue 语句后添加标签时,表示开始标签对应的循环。
func main() {
    here:
    for i := 0; i < 2; i++ {
        for j := 0; j < 4; j++ {
            if j == 2 {
            	continue here
            }
            fmt.Println("i j 的值", i, "-", j)
        }
    }
}

输出:
i j 的值 0 - 0
i j 的值 0 - 1
i j 的值 1 - 0
i j 的值 1 - 1

goto(跳转到指定标签)

goto 语句通过标签进行代码间的无条件跳转。goto 语句可以在快速跳出循环、避免重复退出上有一定的帮助。Go 语言中使用 goto 语句能简化一些代码的实现过程。

示例1func main() {
    var n int = 30
    fmt.Println("ok1")
    if n > 20 {
        goto label1
    }
    fmt.Println("ok2")
    fmt.Println("ok3")
    fmt.Println("ok4")
    label1:
    fmt.Println("ok5")
    fmt.Println("ok6")
    fmt.Println("ok7")
}
输出结果:
ok1
ok5
ok6
ok7


示例2//使用 goto 语句能简化代码
func main() {
    for i := 0; i < 10; i++ {
        for j := 0; j < 10; j++ {
            if j == 2 {
                // 设置退出标签
                goto breakTag
            }
            fmt.Printf("%v-%v\n", i, j)
   		 }
	}
return
// 标签
breakTag:
fmt.Println("结束 for 循环")
}
输出结果:
0-0
0-1

数组、切片、map

数组(数组 值 修改问题疑问)

1、数组的初始化
var testArray [3]int //数组会初始化为 int 类型的零值
var numArray = [3]int{1, 2} //使用指定的初始值完成初始化
var cityArray = [3]string{"北京", "上海", "深圳"} //使用指定的初始值完成初始化
var numArray = [...]int{1, 2} //让编译器根据初始值的个数自行推断数组的长度
a := [...]int{1: 1, 3: 5}  //以使用指定索引值的方式来初始化数组

2、数组的遍历
func main() {
    var a = [...]string{"北京", "上海", "深圳"}
    // 方法 1:for 循环遍历
    for i := 0; i < len(a); i++ {
    	fmt.Println(a[i])
    }
    // 方法 2:for range 遍历
    for index, value := range a {
    	fmt.Println(index, value)
    }
}

3、二维数组
func main() {
    a := [3][2]string{
        {"北京", "上海"}, 
        {"广州", "深圳"}, 
        {"成都", "重庆"}, 
    }
    fmt.Println(a) //[[北京 上海] [广州 深圳] [成都 重庆]]
    fmt.Println(a[2][1]) //支持索引取值:重庆
}

4、二维数组遍历
二维数组的遍历
func main() {
    a := [3][2]string{
    	{"北京", "上海"},
        {"广州", "深圳"}, 
        {"成都", "重庆"}, 
    }
    for _, v1 := range a {
        for _, v2 := range v1 {
        	fmt.Printf("%s\t", v2)
        }
        fmt.Println()
    }
}

切片

切片(Slice)是一个拥有相同类型元素的可变长度的序列。它是基于数组类型做的一层封装。它非常灵活,支持自动扩容。切片是一个引用类型,它的内部结构包含地址长度容量

声明切片类型的基本语法如下:

var name []T

其中:

\1. name:表示变量名

\2. T:表示切片中的元素类型

1、声明切片类型
var a []string //声明一个字符串切片
var b = []int{} //声明一个整型切片并初始化
var c = []bool{false, true} //声明一个布尔切片并初始化
var d = []bool{false, true} //声明一个布尔切片并初始化

2、切片的循环遍历
var a = []string{"北京", "上海", "深圳"}
// 方法 1:for 循环遍历
for i := 0; i < len(a); i++ {
	fmt.Println(a[i])
}
// 方法 2:for range 遍历
for index, value := range a {
	fmt.Println(index, value)
}

3、切片 s 的长度和容量可通过表达式 len(s)cap(s) 来获取。
s := []int{2, 3, 5, 7, 11, 13}
fmt.Println(s)
fmt.Printf("长度:%v 容量 %v\n", len(s), cap(s))
  
4、使用 make()函数构造切片 
//make([]T, size, cap) 
// T:切片的元素类型  size:切片中元素的数量  cap:切片的容量
a := make([]int, 2, 10)

5、切片增加/删除元素 复制切片

var citySlice []string
// 追加一个元素
citySlice = append(citySlice, "北京")
// 追加多个元素
citySlice = append(citySlice, "上海", "广州", "深圳")
// 追加切片
a := []string{"成都", "重庆"}
citySlice = append(citySlice, a...)
fmt.Println(citySlice) //[北京 上海 广州 深圳 成都 重庆]
//切片的追加切片
s1 := []int{100, 200, 300}
s2 := []int{400, 500, 600}
s3 := append(s1, s2...)
fmt.Println(s3)

// 从切片中删除元素
a := []int{30, 31, 32, 33, 34, 35, 36, 37}
// 要删除索引为 2 的元素
a = append(a[:2], a[3:]...)
fmt.Println(a) //[30 31 33 34 35 36 37]

// copy()复制切片 
a := []int{1, 2, 3, 4, 5}
c := make([]int, 5, 5)
copy(c, a) //使用 copy()函数将切片 a 中的元素复制到切片 c
fmt.Println(a) //[1 2 3 4 5]
fmt.Println(c) //[1 2 3 4 5]
c[0] = 1000
fmt.Println(a) //[1 2 3 4 5]
fmt.Println(c) //[1000 2 3 4 5]
//注意:切片是引用数据类型 注意切片的赋值拷贝,对一个切片的修改会影响另一个切片的内容 
//copy是不会影响 是新生成一个切片 另一个切片直接其中一个切片会影响

6、Golang 内置 Sort 包对切片进行排序
//sort 升序排序
intList := []int{2, 4, 3, 5, 7, 6, 9, 8, 1, 0}
float8List := []float64{4.2, 5.9, 12.4, 10.2, 50.7, 99.9, 31.4, 27.81828, 3.14}
stringList := []string{"a", "c", "b", "z", "x", "w", "y", "d", "f", "i"}
sort.Ints(intList)
sort.Float64s(float8List)
sort.Strings(stringList)
输出:
[0 1 2 3 4 5 6 7 8 9]
[3.14 4.2 5.9 10.2 12.4 27.81828 31.4 50.7 99.9]
[a b c d f i w x y z]

//sort 降序排序
intList := []int{2, 4, 3, 5, 7, 6, 9, 8, 1, 0}
float8List := []float64{4.2, 5.9, 12.4, 10.2, 50.7, 99.9, 31.4, 27.81828, 3.14}
stringList := []string{"a", "c", "b", "z", "x", "w", "y", "d", "f", "i"}
sort.Sort(sort.Reverse(sort.IntSlice(intList)))
sort.Sort(sort.Reverse(sort.Float64Slice(float8List)))
sort.Sort(sort.Reverse(sort.StringSlice(stringList)))
fmt.Printf("%v\n%v\n%v\n", intList, float8List, stringList)
输出:
[9 8 7 6 5 4 3 2 1 0]
[99.9 50.7 31.4 27.81828 12.4 10.2 5.9 4.2 3.14]
[z y x w i f d c b a]

map

1map 基本使用
//make(map[KeyType]ValueType, [cap])
//map 中的数据都是成对出现的,map 的基本使用示例代码如下:
func main() {
    scoreMap := make(map[string]int, 8)
    scoreMap["张三"] = 90
    scoreMap["小明"] = 100
    fmt.Println(scoreMap)
    fmt.Println(scoreMap["小明"])
    fmt.Printf("type of a:%T\n", scoreMap)
}
输出:
map[小明:100 张三:90]
100
type of a:map[string]int

2map 的遍历
Go 语言中使用 for range 遍历 mapfunc main() {
    scoreMap := make(map[string]int)
    scoreMap["张三"] = 90
    scoreMap["小明"] = 100
    scoreMap["娜扎"] = 60
    for k, v := range scoreMap {
        fmt.Println(k, v)
    }
}

3、判断某个键是否存在
func main() {
    scoreMap := make(map[string]int)
    scoreMap["张三"] = 90
    scoreMap["小明"] = 100
    // 如果 key 存在 ok 为 true,v 为对应的值;不存在 ok 为 false,v 为值类型的零值
    v, ok := scoreMap["张三"]
    if ok {
        	fmt.Println(v)
        } else {
        	fmt.Println("查无此人")
    }
}

4、使用 delete()函数删除键值对
func main(){
    scoreMap := make(map[string]int)
    scoreMap["张三"] = 90
    scoreMap["小明"] = 100
    scoreMap["娜扎"] = 60
    delete(scoreMap, "小明")//将小明:100 从 map 中删除
    for k,v := range scoreMap{
    	fmt.Println(k, v)
    }
}

Golang 函数详解

注意:如果局部变量和全局变量重名,优先访问局部变量

//Go 语言中支持:函数、匿名函数和闭包
//Go 语言中定义函数使用 func 关键字,具体格式如下:
func 函数名(参数)(返回值){
	函数体
}

函数参数

//函数的参数中如果相邻变量的类型相同,则可以省略类型,例如:
func intSum(x, y int) int {
	return x + y
}

//可变参数是指函数的参数数量不固定。Go 语言中的可变参数通过在参数名后加...来标识。
//注意:可变参数通常要作为函数的最后一个参数。
func intSum2(x ...int) int {
    fmt.Println(x) //x 是一个切片
    sum := 0
    for _, v := range x {
    	sum = sum + v
    }
    return sum
}
调用上面的函数:
ret1 := intSum2()
ret2 := intSum2(10)
ret3 := intSum2(10, 20)
ret4 := intSum2(10, 20, 30)
fmt.Println(ret1, ret2, ret3, ret4) //0 10 30 60

//固定参数搭配可变参数使用时,可变参数要放在固定参数的后面,示例代码如下:
func intSum3(x int, y ...int) int {
    fmt.Println(x, y)
    sum := x
    for _, v := range y {
    	sum = sum + v
    }
    return sum
}
调用上述函数:
ret5 := intSum3(100)
ret6 := intSum3(100, 10)
ret7 := intSum3(100, 10, 20)
ret8 := intSum3(100, 10, 20, 30)
fmt.Println(ret5, ret6, ret7, ret8) //100 110 130 160

函数返回值

//函数多返回值
//Go 语言中函数支持多返回值,函数如果有多个返回值时必须用()将所有返回值包裹起来。:
func calc(x, y int) (int, int) {
    sum := x + y
    sub := x - y
    return sum, sub
}

//返回值命名
//函数定义时可以给返回值命名,并在函数体中直接使用这些变量,最后通过 return 关键字
func calc(x, y int) (sum, sub int) {
    sum = x + y
    sub = x - y
    return
}

函数类型与变量

//我们可以使用 type 关键字来定义一个函数类型,具体格式如下:
type calculation func(int, int) int

//add 和 sub 是calculation 类型。
func add(x, y int) int {
return x + y
}
func sub(x, y int) int {
return x - y
}

func main() {
    //定义函数类型
    var c calculation   // 声明一个 calculation 类型的变量 c
    c = add 			// 把 add 赋值给 c
    fmt.Printf("type of c:%T\n", c) // type of c:main.calculation
    fmt.Println(c(1, 2)) // 像调用 add 一样调用 c
    
    //定义函数类型变量
    f := add // 将函数 add 赋值给变量 f1
    fmt.Printf("type of f:%T\n", f) // type of f:func(int, int) int
    fmt.Println(f(10, 20)) // 像调用 add 一样调用 f
}

高阶函数

//函数作为参数
func add(x, y int) int {
	return x + y
}
func calc(x, y int, op func(int, int) int) int {
	return op(x, y)
}
func main() {
    ret2 := calc(10, 20, add)
    fmt.Println(ret2) //30
}
//函数作为返回值

func add(x, y int) int {
	return x + y
}

func sub(x, y int) int {
	return x - y
}

func do(s string) func(int, int) int {
    switch s {
    	case "+":
    	return add
    case "-":
    	return sub
    default:
    	return nil
    }
}

func main() {
    var a = do("+")
    fmt.Println(a(10, 20))
}

匿名函数和闭包

//匿名函数的定义格式如下:
func(参数)(返回值){
	函数体
}

func main() {
    // 将匿名函数保存到变量
    add := func(x, y int) {
    	fmt.Println(x + y)
    }
    add(10, 20) // 通过变量调用匿名函数
    
    //自执行函数:匿名函数定义完加()直接执行
    func(x, y int) {
   	 fmt.Println(x + y)
    }(10, 20)
}
//匿名函数多用于实现回调函数和闭包。
//闭包
//闭包可以理解成“定义在一个函数内部的函数“。在本质上,闭包是将函数内部和函数外部连接起来的桥梁。或者说是函数和其引用环境的组合体。 首先我们来看一个例子:
func adder() func(int) int {
    var x int
    return func(y int) int {
        x += y
        return x
    }
}
func main() {
    var f = adder()
    fmt.Println(f(10)) //10
    fmt.Println(f(20)) //30
    fmt.Println(f(30)) //60
    f1 := adder()
    fmt.Println(f1(40)) //40
    fmt.Println(f1(50)) //90
}
//变量 f 是一个函数并且它引用了其外部作用域中的 x 变量,此时 f 就是一个闭包。 在 f 的生命周期内,变量 x 也一直有效。

defer 、panic、recover 抛出异常

func readFile(fileName string) error {
    if fileName == "main.go" {
    	return nil
    }
    	return errors.New("读取文件错误")
}

func fn3() {
    defer func() {
        err := recover()
        if err != nil {
            fmt.Println("抛出异常给管理员发送邮件")
        }
    }()
    var err = readFile("xxx.go")
    if err != nil {
    	panic(err)
    }
    fmt.Println("继续执行")
}

func main() {
fn3()
}

time函数

1、time.Now()获取当前时间
func main() {
    now := time.Now() //获取当前时间
    fmt.Printf("current time:%v\n", now)
    year := now.Year() //年
    month := now.Month() //月
    day := now.Day() //日
    hour := now.Hour() //小时
    minute := now.Minute() //分钟
    second := now.Second() //秒
    fmt.Printf("%d-%02d-%02d %02d:%02d:%02d\n", year, month, day, hour, minute, secon
    d)
}
//注意:%02d 中的 2 表示宽度,如果整数不够 2 列就补上 0

2、Format 方法格式化输出日期字符串
func main() {
    now := time.Now()
    // 格式化的模板为 Go 的出生时间 2006 年 1 月 2 号 15 点 04 分 Mon Jan
    // 24 小时制
    fmt.Println(now.Format("2006-01-02 15:04:05"))
    // 12 小时制
    fmt.Println(now.Format("2006-01-02 03:04:05"))
    fmt.Println(now.Format("2006/01/02 15:04"))
    fmt.Println(now.Format("15:04 2006/01/02"))
    fmt.Println(now.Format("2006/01/02"))
}

3、时间戳转换为日期字符串
func unixToTime(timestamp int64) {
    //时间戳转换为日期字符串
    timeObj := time.Unix(timestamp, 0) //将时间戳转为时间格式
    year := timeObj.Year() //年
    month := timeObj.Month() //月
    day := timeObj.Day() //日
    hour := timeObj.Hour() //小时
    minute := timeObj.Minute() //分钟
    second := timeObj.Second() //秒
    fmt.Printf("%d-%02d-%02d %02d:%02d:%02d\n", year, month, day, hour, minute, secon
    d)
    
    //日期字符串转换为时间戳
    t1 := "2019-01-08 13:50:30" 间字符串
    timeTemplate := "2006-01-02 15:04:05" //常规类型
    stamp, _ := time.ParseInLocation(timeTemplate , t1, time.Local)
    fmt.Println(stamp.Unix())
}

func main() {
unixToTime(1587880013)
}

4、now.Format 把时间戳格式化成日期
var timestamp int64 = 1587880013 //时间戳
t := time.Unix(timestamp, 0) //日期对象
fmt.Println(t.Format("2006-01-02 03:04:05")) //日期格式化输出

时间操作函数

1、Add
//我们在日常的编码过程中可能会遇到要求时间+时间间隔的需求,Go 语言的时间对象有提供
//Add 方法如下:
func (t Time) Add(d Duration) Time
//举个例子,求一个小时之后的时间:
func main() {
    now := time.Now()
    later := now.Add(time.Hour) // 当前时间加 1 小时后的时间
    fmt.Println(later)
}
2、Sub
//求两个时间之间的差值:
func (t Time) Sub(u Time) Duration
//返回一个时间段 t-u。如果结果超出了 Duration 可以表示的最大值/最小值,将返回最大值/最小值。要获取时间点 t-d(d 为 Duration),可以使用 t.Add(-d)。
3、Equal
func (t Time) Equal(u Time) bool
//判断两个时间是否相同,会考虑时区的影响,因此不同时区标准的时间也可以正确比较。本方法和用 t==u 不同,这种方法还会比较地点和时区信息。
4、Before
func (t Time) Before(u Time) bool
//如果 t 代表的时间点在 u 之前,返回真;否则返回假。
5、After
func (t Time) After(u Time) bool
//如果 t 代表的时间点在 u 之后,返回真;否则返回假。

定时器

1、使用 time.NewTicker(时间间隔)来设置定时器
ticker := time.NewTicker(time.Second) //定义一个 1 秒间隔的定时器
n := 0
for i := range ticker.C {
    fmt.Println(i) //每秒都会执行的任务
    n++
    if n > 5 {
        ticker.Stop()
        return
    }
}

2、time.Sleep(time.Second) 来实现定时器
for {
    time.Sleep(time.Second)
    fmt.Println("我在定时执行任务")
}

指针

Go 语言中的指针操作非常简单,我们只需要记住两个符号:&(取地址)和 *(根据地址取值)

取变量指针的语法如下:
ptr := &v 

1、指针取值
//在对普通变量使用&操作符取地址后会获得这个变量的指针,然后可以对指针使用*操作,也就是指针取值,代码如下。
func main() {
    a := 10
    b := &a // 取变量 a 的地址,将地址保存到指针 b 中
    fmt.Printf("type of b:%T\n", b)
    c := *b // 指针取值(根据指针的值去内存取值)
    fmt.Printf("type of c:%T\n", c)
    fmt.Printf("value of c:%v\n", c)
}
//输出如下:
type of b:*int
type of c:int
value of c:10
//总结: 取地址操作符&和取值操作符*是一对互补操作符,&取出地址,*根据地址取出地址指向的值。

2、指针传值示例
func modify1(x int) {
	x = 100
}
func modify2(x *int) { 
    *x = 100
}
func main() {
    a := 10
    modify1(a)
    fmt.Println(a) // 10
    modify2(&a)
    fmt.Println(a) // 100
}

结构体

结构体的定义

使用 type 和 struct 关键字来定义结构体,具体代码格式如下:

type 类型名 struct {
    字段名 字段类型
    字段名 字段类型
    …
}

示例1type person struct {
    name string
    city string
    age int8
}
示例2//同样类型的字段也可以写在一行,
type person struct {
    name, city string
    age int8
}
//注意:结构体首字母可以大写也可以小写,大写表示这个结构体是公有的,在其他的包里面可以使用。小写表示这个结构体是私有的,只有这个包里面才能使用。

示例3//结构体的匿名字段
//结构体允许其成员字段在声明时没有字段名而只有类型,这种没有名字的字段就称为匿名字段。
//匿名字段默认采用类型名作为字段名,结构体要求字段名称必须唯一,因此一个结构体中同种类型的匿名字段只能有一个
type Person struct {
    string
    int
}

示例4//嵌套结构体
//一个结构体中可以嵌套包含另一个结构体或结构体指针。

//Address 地址结构体
type Address struct {
    Province string
    City string
}
//User 用户结构体
type User struct {
    Name string
    Gender string
    Address Address
}
func main() {
    user1 := User{
        Name: "张三",
        Gender: "男", 
        Address: Address{
        	Province: "广东",
            City: "深圳", 
        }, 
    }
    fmt.Printf("user1=%#v\n", user1)
    //user1=main.User{Name:" 张 三 ", Gender:" 男 ", Address:main.Address{Province:"广东", City:"深圳"}}
}

结构体实例化

type person struct {
    name string
    city string
    age int
}

func main() {
    //var 结构体实例 结构体类型
    var p1 person
    p1.name = "张三" p1.city = "北京" 
    p1.age = 18
    fmt.Printf("p1=%v\n", p1) //p1={张三 北京 18}
    fmt.Printf("p1=%#v\n", p1) //p1=main.person{name:"张三", city:"北京", age:18}
    
    //通过使用 new 关键字
    var p2 = new(person)
    p2.name = "张三" 
    p2.age = 20
    p2.city = "北京"
    fmt.Printf("%T\n", p2) //*main.person
    fmt.Printf("p2=%#v\n", p2) //p2=&main.person{name:"张三", city:"北京", age:20}
    
    //使用&对结构体进行取地址操作相当于对该结构体类型进行了一次new实例化操作
    p3 := &person{}
    fmt.Printf("%T\n", p3) //*main.person
    fmt.Printf("p3=%#v\n", p3) //p3=&main.person{name:"", city:"", age:0}
    p3.name = "zhangsan" 
    p3.age = 30
    p3.city = "深圳"
    (*p3).age = 40 //这样也是可以的
    fmt.Printf("p3=%#v\n", p3) //p3=&main.person{name:"zhangsan", city:"深圳", age:30}
    
    //通过键值对初始化
    p4 := person{
			name: "zhangsan", 
        city: "北京",
        age: 18, 
    }
	fmt.Printf("p4=%#v\n", p4) //p4=main.person{name:"zhangsan", city:"北京", age:18}
    
    
    //结构体指针进行键值对初始化
    p5 := &person{
			name: "zhangsan", 
            city: "上海", 
            age: 28, 
        }
	fmt.Printf("p5=%#v\n", p5) //p5=&main.person{name:"zhangsan", city:"上海", age:28}
    
    //使用值的列表初始化
    // 初始化结构体的时候可以简写,也就是初始化的时候不写键,直接写值:
	p7 := &person{ 
        "zhangsan", 
        "北京", 
        28,
	}
	fmt.Printf("p7=%#v\n", p7) //p7=&main.person{name:"zhangsan", city:"北京", age:28}
    //使用这种格式初始化时,需要注意:
    //1.必须初始化结构体的所有字段。
    //2.初始值的填充顺序必须与字段在结构体中的声明顺序一致。
    //3.该方式不能和键值初始化方式混用。
}

结构体方法和接收者

在 go 语言中,没有类的概念但是可以给类型(结构体,自定义类型)定义方法。所谓方法就是定义了接收者的函数。接收者的概念就类似于其他语言中的 this 或者 self。

方法的定义格式如下:

func (接收者变量 接收者类型) 方法名(参数列表) (返回参数) {
	函数体
}

//1、值类型的接收者
//当方法作用于值类型接收者时,Go 语言会在代码运行时将接收者的值复制一份。在值类型接收者的方法中可以获取接收者的成员值,但修改操作只是针对副本,无法修改接收者变量本身。
//2、指针类型的接收者
//指针类型的接收者由一个结构体的指针组成,由于指针的特性,调用方法时修改接收者指针的任意成员变量,在方法结束后,修改都是有效的。这种方式就十分接近于其他语言中面向对象中的 this 或者 self。
示例:
package main
import "fmt"
type Person struct {
    name string
    age int
}
//值类型接受者
func (p Person) printInfo() {
	fmt.Printf("姓名:%v 年龄:%v\n", p.name, p.age)
}
//指针类型接收者
func (p *Person) setInfo(name string, age int) {
    p.name = name
    p.age = age
}
func main() {
    p1 := Person{
    name: "小王子", age: 25, }
    p1.printInfo()
    p1.setInfo("张三", 20)
    p1.printInfo()
}

结构体的继承

//Animal 动物
type Animal struct {
	name string
}
func (a *Animal) run() {
	fmt.Printf("%s 会运动!\n", a.name)
}
//Dog 狗
type Dog struct {
	Age int8
	*Animal //通过嵌套匿名结构体实现继承
}
func (d *Dog) wang() {
	fmt.Printf("%s 会汪汪汪~\n", d.name)
}

func main() {
    d1 := &Dog{
    	Age: 4, 
        Animal: &Animal{ //注意嵌套的是结构体指针
    		name: "阿奇", 
        }, 
    }
    d1.wang() //乐乐会汪汪汪~ 
    d1.run() //乐乐会动!
}

结构体与 JSON 序列化

package main
import ( 
    "encoding/json"
	"fmt"
)

type Student struct {
    ID int
    Gender string
    name string //私有属性不能被 json 包访问
    Sno string
}

func main() {
    //1、结构体对象转化成 Json 字符串
    var s1 = Student{
   			 ID: 1, 
            Gender: "男", 
            Name: "李四",
            Sno: "s0001", 
    }
    fmt.Printf("%#v\n", s1)
    var s, _ = json.Marshal(s1)
    jsonStr := string(s)
    fmt.Println(jsonStr)
    
    //2、Json 字符串转换成结构体对象
    var jsonStr = `{"ID":1,"Gender":"男","Name":"李四","Sno":"s0001"}` 
	err := json.Unmarshal([]byte(jsonStr), &student)
	if err != nil {
		fmt.Printf("unmarshal err=%v\n", err)
	}
	fmt.Printf("反序列化后 student=%#v student.Name=%v \n", student, student.Name)
}

//结构体标签 Tag
type Student struct {
    ID int `json:"id"` //通过指定 tag 实现 json 序列化该字段时的 key
    Gender string `json:"gender"` Name string
    Sno string
}

func main() {
    //1、结构体对象转化成 Json 字符串
    var s1 = Student{
    			ID: 1, 
        		Gender: "男", 
        		Name: "李四", 
        		Sno: "s0001", 
    }
    fmt.Printf("%#v\n", s1)
    var s, _ = json.Marshal(s1)
    jsonStr := string(s)
    fmt.Println(jsonStr)
    
    //2、Json 字符串转换成结构体对象
    var s2 Student
	var str = "{\"id\":1,\"gender\":\"男\",\"Name\":\"李四\",\"Sno\":\"s0001\"}" 
    err := 	json.Unmarshal([]byte(str), &s2)
	if err != nil {
   	 fmt.Println(err)
    }
    fmt.Printf("%#v", s2)
}

go mod 以及 Golang 包详解

1、go mod init 初始化项目

实际项目开发中我们首先要在我们项目目录中用 go mod 命令生成一个 go.mod 文件管理我们项目的依赖。

比如我们的 golang 项目文件要放在了 itying 这个文件夹,这个时候我们需要在 itying 文件夹里面使用 go mod 命令生成一个 go.mod 文件

2、Golang 中自定义包

//定义一个包
1、定义一个包名为 calc 的包,代码如下:
package calc
//首字母大小表示公有,首字母小写表示私有
var a = 100 //私有变量
var Age = 20 //公有变量
func Add(x, y int) int {
    return x + y
}
func Sum(x, y int) int {
	return x - y
}

2、main.go 中引入这个包
访问一个包里面的公有属性方法的时候需要通过包名称.去访问
package main
import ( 
    "fmt"
	"itying/calc"
)
func main() {
    c := calc.Add(10, 20)
    fmt.Println(c)
}
//导入一个包
//单行导入的格式如下:
import "包 1"
import "包 2" 
//多行导入的格式如下:
import ( 
    "包 1"
	"包 2"
)
//匿名导入包
import _ "包的路径"

3、Golang 中使用第三方包

//第一种方法:go get 包名称 (全局)
go get github.com/shopspring/decimal
//第二种方法:go mod download (全局)
go mod download
//赖包会自动下载到$GOPATH/pkg/mod,多个项目可以共享缓存的 mod,注意使用 go mod download 的时候首先需要在你的项目里面引入第三方包
//第三种方法:go mod vendor 将依赖复制到当前项目的 vendor 下 (本项目)
go mod vendor
//将依赖复制到当前项目的 vendor 下注意:使用 go mod vendor 的时候首先需要在你的项目里面引入第三方包

接口

Golang 接口的定义

//Golang 中每个接口由数个方法组成,接口的定义格式如下:
type 接口名 interface{
    方法名 1( 参数列表 1 ) 返回值列表 1
    方法名 2( 参数列表 2 ) 返回值列表 2}

示例1//定义接口
type Usber interface {
    Start()
    Stop()
}

//定义接口实现得结构体以及方法
type Phone struct {
	Name string
}
func (p Phone) Start() {
    fmt.Println(p.Name, "开始工作")
}
func (p Phone) Stop() {
	fmt.Println("phone 停止")
}

//实现接口
func main() {
    phone := Phone{
    Name: "小米手机", }
    var p Usber = phone //phone 实现了 Usb 接口
    p.Start()
}

示例2//Computer 结构体中的 Work 方法必须传入一个 Usb 的接口 不符合接口结构体方法 将无法执行
type Usber interface {
    Start()
    Stop()
}


//phone缺下面定义的任何一个方法 都会报错
type Phone struct {
	Name string
}
func (p Phone) Start() {
	fmt.Println(p.Name, "开始工作")
}
func (p Phone) Stop() {
	fmt.Println("phone 停止")
}

//电脑的结构体
type Computer struct {
	Name string
}
// 电脑的 Work 方法要求必须传入 Usb 接口类型数据
func (c Computer) Work(usb Usber) {
    usb.Start()
    usb.Stop()
}
func main() {
    phone := Phone{
   			 Name: "小米手机",
    }
    computer := Computer{}
    //把手机插入电脑的 Usb 接口开始工作
    computer.Work(phone)
}

示例3//如果结构体中的方法是指针接收者,那么实例化后结构体指针类型都可以赋值给接口变量,结构体值类型没法赋值给接口变量
type Usb interface {
    Start()
    Stop()
}
type Phone struct {
	Name string
}
func (p *Phone) Start() {
	fmt.Println(p.Name, "开始工作")
}
func (p *Phone) Stop() {
	fmt.Println("phone 停止")
}
func main() {
    /* 错误写法
    phone1 := Phone{
    Name: "小米手机", }
    var p1 Usb = phone1
    p1.Start() */
    //正确写法
    phone2 := &Phone{
    Name: "苹果手机", }
    var p2 Usb = phone2 //phone2 实现了 Usb 接口 phone2 是 *Phone 类型
    p2.Start() //苹果手机 开始工作
}

示例4//一个结构体实现多个接口
type AInterface interface {
	GetInfo() string
}
type BInterface interface {
	SetInfo(string, int)
}
type People struct {
    Name string
    Age int
}
func (p People) GetInfo() string {
	return fmt.Sprintf("姓名:%v 年龄:%d", p.Name, p.Age)
}
func (p *People) SetInfo(name string, age int) {
    p.Name = name
    p.Age = age
}
func main() {
    var people = &People{
    	Name: "张三", 
        Age: 20, 
    }
    // people 实现了 AInterface 和 BInterface
    var p1 AInterface = people
    var p2 BInterface = people
    fmt.Println(p1.GetInfo())
    p2.SetInfo("李四", 30)
    fmt.Println(p1.GetInfo())
}

示例5//接口嵌套 接口与接口间可以通过嵌套创造出新的接口。

type SayInterface interface {
	say()
}
type MoveInterface interface {
	move()
}
// 接口嵌套
type Animal interface {
    SayInterface
    MoveInterface
}
type Cat struct {
    name string
}
func (c Cat) say() {
	fmt.Println("喵喵喵")
}
func (c Cat) move() {
	fmt.Println("猫会动")
}
func main() {
    var x Animal
    x = Cat{name: "花花"}
    x.move()
    x.say()
}

空接口

空接口在实际项目中用的是非常多的,用空接口可以表示任意数据类型。

func main() {
    // 定义一个空接口 x, x 变量可以接收任意的数据类型
    var x interface{}
    s := "你好 golang" x = s
    fmt.Printf("type:%T value:%v\n", x, x)
    
    i := 100
    x = i
    fmt.Printf("type:%T value:%v\n", x, x)
    
    b := true
    x = b
    fmt.Printf("type:%T value:%v\n", x, x)
}


1、空接口作为函数的参数
//使用空接口实现可以接收任意类型的函数参数。
func show(a interface{}) {
	fmt.Printf("type:%T value:%v\n", a, a)
}

2map 的值实现空接口
//使用空接口实现可以保存任意值的字典。
// 空接口作为 map 值
var studentInfo = make(map[string]interface{})
studentInfo["name"] = "张三"
studentInfo["age"] = 18
studentInfo["married"] = false
fmt.Println(studentInfo)

3、切片实现空接口
var slice = []interface{}{"张三", 20, true, 32.2}
fmt.Println(slice)

类型断言

如果我们想要判断空接口中值的类型,那么这个时候就可以使用类型断言,其语法格式:
x.(T)
其中:
• x : 表示类型为 interface{}的变量
• T : 表示断言 x 可能是的类型。


示例1func main() {
    var x interface{}
    x = "Hello golnag"
    v, ok := x.(string)
    if ok {
   	 fmt.Println(v)
    } else {
   	 fmt.Println("类型断言失败")
    }
}

示例2//注意:类型.(type)只能结合 switch 语句使用
func justifyType(x interface{}) {
    switch v := x.(type) {
        case string:
        	fmt.Printf("x is a string,value is %v\n", v)
        case int:
       	 fmt.Printf("x is a int is %v\n", v)
        case bool:
        	fmt.Printf("x is a bool is %v\n", v)
        default:
        	fmt.Println("unsupport type!")
    }
}

goroutine channel 实现并发和并行

goroutine

golang 中的主线程:(可以理解为线程/也可以理解为进程),在一个 Golang 程序的主线程上可以起多个协程Golang 中多协程可以实现并行或者并发。

**协程:**可以理解为用户级线程,这是对内核透明的,也就是系统并不知道有协程的存在,是完全由用户自己的程序进行调度的。Golang 的一大特色就是从语言层面原生支持协程,在函数或者方法前面加 go 关键字就可创建一个协程。可以说 Golang 中的协程就是goroutine 。

示例1var wg sync.WaitGroup //1、定义全局的 WaitGroup
func test() {
    for i := 1; i <= 10; i++ {
        fmt.Println("test () 你好 golang " + strconv.Itoa(i))
        time.Sleep(time.Millisecond * 50)
	}
	wg.Done() // 4、goroutine 结束就登记-1
}
func main() {
    wg.Add(1) //2、启动一个 goroutine 就登记+1
    go test()
    for i := 1; i <= 2; i++ {
        fmt.Println(" main() 你好 golang" + strconv.Itoa(i))
        time.Sleep(time.Millisecond * 50)
    }
    wg.Wait() // 3、等待所有登记的 goroutine 都结束
}

示例2//启动多个 Goroutine
func hello(i int) {
    defer wg.Done() // goroutine 结束就登记-1
    fmt.Println("Hello Goroutine!", i)
}
func main() {
    for i := 0; i < 10; i++ {
        wg.Add(1) // 启动一个 goroutine 就登记+1
        go hello(i)
	}
	wg.Wait() // 等待所有登记的 goroutine 都结束
}
//多次执行上面的代码,会发现每次打印的数字的顺序都不一致。这是因为 10 个 goroutine是并发执行的,而 goroutine 的调度是随机的。

示例3//Goroutine Recover 解决协程中出现的 Panic
func sayHello() {
    for i := 0; i < 10; i++ {
        time.Sleep(time.Second)
        fmt.Println("hello,world")
    }
}
//函数
func test() {
    //这里我们可以使用 defer + recover
    defer func() {
    //捕获 test 抛出的 panic
    if err := recover(); err != nil {
    	fmt.Println("test() 发生错误", err)
    }
    }()
    //定义了一个 map
    var myMap map[int]string
    myMap[0] = "golang" //error
}

func main() {
    go sayHello()
    go test()
    for i := 0; i < 10; i++ {
    fmt.Println("main() ok=", i)
   	 time.Sleep(time.Second)
    }
}

注:
//获取当前计算机上面的 Cup 个数
cpuNum := runtime.NumCPU()
//可以自己设置使用多个 cpu
runtime.GOMAXPROCS(cpuNum - 1)

Channel 管道

Go 语言中的管道(channel)是一种特殊的类型。管道像一个传送带或者队列,总是遵循先入先出(First In First Out)的规则,保证收发数据的顺序。每一个管道都是一个具体类型的导管,也就是声明 channel 的时候需要为其指定元素类型。

1、channel 类型
//channel 是一种类型,一种引用类型。声明管道类型的格式如下:
var 变量 chan 元素类型
//举几个例子:
var ch1 chan int // 声明一个传递整型的管道
var ch2 chan bool // 声明一个传递布尔型的管道
var ch3 chan []int // 声明一个传递 int 切片的管道

2、创建 channel
///声明的管道后需要使用 make 函数初始化之后才能使用。
//创建 channel 的格式如下:
make(chan 元素类型, 容量)
//举几个例子:
//创建一个能存储 10 个 int 类型数据的管道
ch1 := make(chan int, 10)
//创建一个能存储 4 个 bool 类型数据的管道
ch2 := make(chan bool, 4)
//创建一个能存储 3 个[]int 切片类型数据的管道
ch3 := make(chan []int, 3)

3、channel 操作
//定义一个管道
ch := make(chan int, 3)
//将一个值发送到管道中。
ch <- 10 
//从一个管道中接收值。
x := <- ch // 从 ch 中接收值并赋值给变量 x
<-ch // 从 ch 中接收值,忽略结果
//我们通过调用内置的 close 函数来关闭管道。
close(ch)

4for range 从管道循环取值
//循环遍历管道数据
func main() {
    var ch1 = make(chan int, 5)
    for i := 0; i < 5; i++ {
    	ch1 <- i + 1
    }
    close(ch1) //关闭管道
    //使用 for range 遍历管道,当管道被关闭的时候就会退出 for range,如果没有关闭管道就会报个错误 fatal error: all goroutines are asleep - deadlock!
    //通过 for range 来遍历管道数据 管道没有 key
    for val := range ch1 {
    	fmt.Println(val)
    }
}

5、单向管道
//1. 在默认情况下下,管道是双向
//var chan1 chan int //可读可写
//2 声明为只写
var chan2 chan<- int
chan2 = make(chan int, 3)
chan2<- 20
//num := <-chan2 //error
fmt.Println("chan2=", chan2)
//3. 声明为只读
var chan3 <-chan int
num2 := <-chan3
//chan3<- 30 //err
fmt.Println("num2", num2)

Goroutine 结合Channel 管道

需求 1:定义两个方法,一个方法给管道里面写数据,一个给管道里面读取数据。要求同步
进行。
1、开启一个 fn1 的的协程给向管道 inChan 中写入 100 条数据
2、开启一个 fn2 的协程读取 inChan 中写入的数据
3、注意:fn1 和 fn2 同时操作一个管道
4、主线程必须等待操作完成后才可以退出

var wg sync.WaitGroup
func fn1(intChan chan int) {
    for i := 0; i < 100; i++ {
        intChan <- i + 1
        fmt.Println("writeData 写入数据-", i+1)
        time.Sleep(time.Millisecond * 100)
    }
    close(intChan)
    wg.Done()
}
func fn2(intChan chan int) {
    for v := range intChan {
        fmt.Printf("readData 读到数据=%v\n", v)
        time.Sleep(time.Millisecond * 50)
    }
    wg.Done()
}
func main() {
    allChan := make(chan int, 100)
    wg.Add(1)
    go fn1(allChan)
    wg.Add(1)
    go fn2(allChan)
    wg.Wait()
    fmt.Println("读取完毕...")
}

select 多路复用

func main() {
//使用 select 可以解决从管道取数据的阻塞问题,传统的方法在遍历管道时,如果不关闭会阻塞而导致 deadlock,在实际开发中,可能我们不好确定什么关闭该管道. //1.定义一个管道 10 个数据 int
    intChan := make(chan int, 10)
    for i := 0; i < 10; i++ {
        intChan <- i
    }
    //2.定义一个管道 5 个数据 string
    stringChan := make(chan string, 5)
    for i := 0; i < 5; i++ {
        stringChan <- "hello" + fmt.Sprintf("%d", i)
    }
    for {
        select {
        //注意: 这里,如果 intChan 一直没有关闭,不会一直阻塞而 deadlock,会自动到
        下一个 case 匹配
        	case v := <-intChan:
                fmt.Printf("从 intChan 读取的数据%d\n", v)
                time.Sleep(time.Second)
        case v := <-stringChan:
                fmt.Printf("从 stringChan 读取的数据%s\n", v)
                time.Sleep(time.Second)
        default:
                fmt.Printf("都取不到了,不玩了, 程序员可以加入逻辑\n")
                time.Sleep(time.Second)
        return
        }
    }
}

使用 select 语句能提高代码的可读性。

​ • 可处理一个或多个 channel 的发送/接收操作。

​ • 如果多个 case 同时满足,select 会随机选择一个。

​ • 对于没有 case 的 select{}会一直等待,可用于阻塞 main 函数。

Golang 并发安全和锁

1、互斥锁
//互斥锁是一种常用的控制共享资源访问的方法,它能够保证同时只有一个 goroutine 可以访问共享资源。Go 语言中使用 sync 包的 Mutex 类型来实现互斥锁。

var (
    myMap = make(map[int]int)
    wg sync.WaitGroup
    lock sync.Mutex
)
// test 函数就是计算 n!, 让将这个结果放入到 myMap
func test(n int) {
    res := 1
    for i := 1; i <= n; i++ {
    	res *= i
	}
    //加锁
    lock.Lock()
    myMap[n] = res
    //解锁
    lock.Unlock()
    wg.Done()
}
func main() {
    for i := 1; i <= 60; i++ {
        wg.Add(1)
        go test(i)
    }
    wg.Wait()
    for i, v := range myMap {
    	fmt.Printf("map[%d]=%d\n", i, v)
    }
}
2、读写互斥锁
var (
    x int64
    wg sync.WaitGroup
    lock sync.Mutex
    rwlock sync.RWMutex
)
func write() {
    // lock.Lock() // 加互斥锁
    rwlock.Lock() // 加写锁
    x = x + 1
    time.Sleep(10 * time.Millisecond) // 假设读操作耗时 10 毫秒
    rwlock.Unlock() // 解写锁
    // lock.Unlock() // 解互斥锁
    wg.Done()
}
func read() {
    // lock.Lock() // 加互斥锁
    rwlock.RLock() // 加读锁
    time.Sleep(time.Millisecond) // 假设读操作耗时 1 毫秒
    rwlock.RUnlock() // 解读锁
    // lock.Unlock() // 解互斥锁
    wg.Done()
}
func main() {
    start := time.Now()
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go write()
	}
    for i := 0; i < 1000; i++ {
        wg.Add(1)
        go read()
    }
    wg.Wait()
    end := time.Now()
    fmt.Println(end.Sub(start))
}
//需要注意的是读写锁非常适合读多写少的场景,如果读和写的操作差别不大,读写锁的优势就发挥不出来。

反射

反射的基本介绍

Go语言中的变量是分为两部分的

类型信息:预先定义好的元信息。

值信息:程序运行过程中可动态变化的。

在 GoLang 的反射机制中,任何接口值都由是一个具体类型具体类型的值两部分组成的。

在 GoLang 中,反射的相关功能由内置的 reflect 包提供,任意接口值在反射中都可以理解为由 reflect.Type 和 reflect.Value 两 部 分 组 成 , 并 且 reflect 包 提 供 了 reflect.TypeOfreflect.ValueOf 两个重要函数来获取任意对象的 Value 和 Type。

1、reflect.TypeOf()获取任意值的类型对象
//在反射中关于类型还划分为两种:类型(Type)和种类(Kind)。因为在 Go 语言中我们可以使用 type 关键字构造很多自定义类型,而种类(Kind)就是指底层的类型,但在反射中,当需要区分指针、结构体等大品种的类型时,就会用到种类(Kind)。 举个例子,我们定义了两个指针类型和两个结构体类型,通过反射查看它们的类型和种类。
//Go 语言的反射中像数组、切片、Map、指针等类型的变量,它们的.Name()都是返回空
func reflectType(x interface{}) {
    t := reflect.TypeOf(x)
    fmt.Printf("TypeOf:%v Name:%v Kind:%v\n", t, t.Name(), t.Kind())
}

func main() {
    var a *float32 // 指针
    var b myInt // 自定义类型
    var c rune // 类型别名
    reflectType(a) // type: kind:ptr
    reflectType(b) // type:myInt kind:int64
    reflectType(c) // type:int32 kind:int32
    
    var d = Person{
    	Name: "itying", 
        Age: 18, 
    }
    reflectType(d) // type:Person kind:struct
    
    var f = []int{1, 2, 3, 4, 5}
    reflectType(f) //TypeOf:[]int Name: Kind:slice
}

2、reflect.ValueOf()返回的是 reflect.Value 类型,其中包含了原始值的值信息
示例1func reflectValue(x interface{}) {
    v := reflect.ValueOf(x)
    k := v.Kind()
    switch k {
    	case reflect.Int64:
            // v.Int()从反射中获取整型的原始值
            fmt.Printf("type is int64, value is %d\n", v.Int())
    	case reflect.Float32:
            // v.Float()从反射中获取浮点型的原始值
            fmt.Printf("type is float32, value is %f\n", v.Float())
    	case reflect.Float64:
            // v.Float()从反射中获取浮点型的原始值
            fmt.Printf("type is float64, value is %f\n", v.Float())
    }
}

示例2func reflectSetValue2(x interface{}) {
    v := reflect.ValueOf(x)
    // 反射中使用 Elem()方法获取指针对应的值
    if v.Elem().Kind() == reflect.Int64 {
   	 v.Elem().SetInt(200)
    }
}

//调用上面的方法
var a int64 = 100
//看传的值
reflectSetValue2(&a)

结构体反射

示例1//获取结构体属性,获取执行结构体方法

//student 结构体
type Student struct {
	Name string `json:"name"` 
    Age int `json:"age"` 
    Score int `json:"score"` 
}

func (s Student) GetInfo() string {
    var str = fmt.Sprintf("姓名:%v 年龄:%v 成绩:%v", s.Name, s.Age, s.Score)
    fmt.Println(str)
    return str
}
func (s *Student) SetInfo(name string, age int, score int) {
    s.Name = name
    s.Age = age
    s.Score = score
}
func (s *Student) Print() {
    fmt.Println("打印方法...")
}
//打印字段
func PrintStructField(s interface{}) {
    t := reflect.TypeOf(s)
    // v := reflect.ValueOf(s)
    kind := t.Kind()
    if t.Kind() != reflect.Struct && t.Elem().Kind() != reflect.Struct {
        fmt.Println("传入的不是结构体")
        return
	}
    //1、通过类型变量里面的 Field 可以获取结构体的字段
    field0 := t.Field(0)
    fmt.Println(field0.Name)
    fmt.Println(field0.Type)
    fmt.Println(field0.Tag.Get("json"))
    //2、通过类型变量里面的 FieldByName 可以获取结构体的字段
    field1, _ := t.FieldByName("Age")
    fmt.Println(field1.Name)
    fmt.Println(field1.Type)
    fmt.Println(field1.Tag.Get("json"))
    //3、获取到该结构体有几个字段
    num := t.NumField()
    fmt.Println("字段数量:", num)
}

示例2//方法
func PrintStructFn(s interface{}) {
    t := reflect.TypeOf(s)
    v := reflect.ValueOf(s)
    if t.Kind() != reflect.Struct && t.Elem().Kind() != reflect.Struct {
        fmt.Println("传入的不是结构体")
        return
	}
    //1、通过类型变量里面的 Method 可以获取结构体的方法
    var tMethod = t.Method(0) //注意
    fmt.Println(tMethod.Name)
    fmt.Println(tMethod.Type)
    //2、通过类型变量获取这个结构体有多少个方法
    fmt.Println(t.NumMethod())
    //3、执行方法 (注意需要使用值变量,并且要注意参数)
    // v.Method(0).Call(nil)
    v.MethodByName("Print").Call(nil)
    //4、执行方法传入参数 (注意需要使用值变量,并且要注意参数)
    var params []reflect.Value //声明了 []reflect.Value
    params = append(params, reflect.ValueOf("张三"))
    params = append(params, reflect.ValueOf(22))
    params = append(params, reflect.ValueOf(100))
    v.MethodByName("SetInfo").Call(params) //传入的参数是 []reflect.Value, 返回[]reflect.Va
    lue
    // 5、执行方法获取方法的值
    info := v.MethodByName("GetInfo").Call(nil)
    fmt.Println(info)
}

func main() {
    stu1 := Student{
    	Name: "小明", 
        Age: 15, 
        Score: 98, 
    }
    // 修改结构体属性值
    PrintStructFn(&stu1)
}

示例3//student 结构体
type Student struct {
	Name string `json:"name"` 
    Age int `json:"age"`
    Score int `json:"score"` 
}
func (s Student) GetInfo() string {
    var str = fmt.Sprintf("姓名:%v 年龄:%v 成绩:%v", s.Name, s.Age, s.Score)
    return str
}
//反射修改结构体属性
func reflectChangeStruct(s interface{}) {
    t := reflect.TypeOf(s)
    v := reflect.ValueOf(s)
    if t.Elem().Kind() != reflect.Struct {
        fmt.Println("传入的不是结构体指针类型")
        return
    }
    name := v.Elem().FieldByName("Name")
    name.SetString("李四") // 设置值
    age := v.Elem().FieldByName("Age")
    age.SetInt(20) // 设置值
}

func main() {
    stu1 := Student{
    	Name: "小明", 
        Age: 15, 
        Score: 98, 
    }
    reflectChangeStruct(&stu1)
    fmt.Println(stu1.GetInfo())
}

文件 目录操作

文件读取

file.Read() 读取文件
/*
		1、只读方式打开文件 file,err := os.Open()
		2、读取文件 file.Read()
		3、关闭文件流 defer file.Close()
*/

func main() {
	//1、打开文件
	file, err := os.Open("C:/test.txt")
	defer file.Close()

	if err != nil {
		fmt.Println(err)
		return
	}
	//2、读取文件里面的内容
	var strSlice []byte
	var tempSlice = make([]byte, 128)
	for {
		n, err := file.Read(tempSlice)
		if err == io.EOF { //err==io.EOF表示读取完毕
			fmt.Println("读取完毕")
			break
		}
		if err != nil {
			fmt.Println("读取失败")
			return
		}
		// fmt.Printf("读取到了%v个字节\n", n)
		strSlice = append(strSlice, tempSlice[:n]...) //注意写法
	}

	fmt.Println(string(strSlice))
}
bufio 读取文件
/*
		1、只读方式打开文件 file,err := os.Open()
		2、创建reader对象  reader := bufio.NewReader(file)
		3、ReadString读取文件  line, err := reader.ReadString('\n')
		4、关闭文件流 defer file.Close()
*/
func main() {
	file, err := os.Open("C:/test.txt")
	defer file.Close()

	if err != nil {
		fmt.Println(err)
		return
	}
	//bufio 读取文件
	var fileStr string
	reader := bufio.NewReader(file)
	for {
		str, err := reader.ReadString('\n') //表示一次读取以行
		if err == io.EOF {
			fileStr += str
			break
		}
		if err != nil {
			fmt.Println(err)
			return
		}
		fileStr += str
	}
	fmt.Println(fileStr)

}
ioutil 读取整个文件
func main() {
    content, err := ioutil.ReadFile("./main.go")
    if err != nil {
        fmt.Println("read file failed, err:", err)
        return
    }
    fmt.Println(string(content))
}

文件写入操作

os.OpenFile()函数能够以指定模式打开文件,从而实现文件写入相关功能。
func OpenFile(name string, flag int, perm FileMode) (*File, error) {
    ...
}
其中:
name:要打开的文件名 flag:打开文件的模式。 模式有以下几种:
模式 			   含义
os.O_WRONLY 	只写
os.O_CREATE 	创建文件
os.O_RDONLY 	只读
os.O_RDWR 		读写
os.O_TRUNC 		清空
os.O_APPEND 	追加
Write 和 WriteString
/*
		1、打开文件  file, err := os.OpenFile("C:/test.txt", os.O_CREATE|os.O_RDWR, 0666)
		2、写入文件
			file.Write([]byte(str))        //写入字节切片数据
			file.WriteString("直接写入的字符串数据") //直接写入字符串数据
		3、关闭文件流 file.Close()
*/

func main() {
    file, err := os.OpenFile("C:/test.txt", os.O_CREATE|os.O_RDWR, 0666)
    if err != nil {
        fmt.Println("open file failed, err:", err)
        return
    }
    defer file.Close()
    str := "你好 golang"
    file.Write([]byte(str)) //写入字节切片数据
    file.WriteString("直接写入的字符串数据") //直接写入字符串数据
}
bufio.NewWriter
/*
		1、打开文件  file, err := os.OpenFile("C:/test.txt", os.O_CREATE|os.O_RDWR, 0666)
		2、创建writer对象  writer := bufio.NewWriter(file)
		3、将数据先写入缓存  writer.WriteString("你好golang\r\n")
		4、将缓存中的内容写入文件	writer.Flush()
		5、关闭文件流 file.Close()
*/
func main() {
	file, err := os.OpenFile("C:/test.txt", os.O_CREATE|os.O_RDWR|os.O_APPEND, 0666)
	defer file.Close()

	if err != nil {
		fmt.Println("open file failed, err:", err)
		return
	}
	writer := bufio.NewWriter(file)

	// writer.WriteString("你好golang") //将数据先写入缓存
	for i := 0; i < 10; i++ {
		writer.WriteString("直接写入的字符串数据" + strconv.Itoa(i) + "\r\n")
	}

	writer.Flush() //将缓存中的内容写入文件

}
ioutil.WriteFile
func main() {
    str := "hello golang" 
    err := ioutil.WriteFile("C:/test.txt", []byte(str), 0666)
    if err != nil {
        fmt.Println("write file failed, err:", err)
        return
    }
}

文件操作

//文件重命名
err := os.Rename("C:/test1.txt", "D:/test1.txt") //只能同盘操作
if err != nil {
	fmt.Println(err)
}

//复制文件
示例1//自己编写一个函数,接收两个文件路径 srcFileName dstFileName
func CopyFile(dstFileName string, srcFileName string) (err error) {
    input, err := ioutil.ReadFile(srcFileName)
    if err != nil {
        fmt.Println(err)
        return err
    }
    err = ioutil.WriteFile(dstFileName, input, 0644)
    if err != nil {
        fmt.Println("Error creating", dstFileName)
        fmt.Println(err)
        return err
	}
	return nil
}
func main() {
    srcFile := "c:/test1.zip" 
    dstFile := "D:/test1.zip"
    err := CopyFile(dstFile, srcFile)
    if err == nil {
    	fmt.Printf("拷贝完成\n")
    } else {
   	 fmt.Printf("拷贝错误 err=%v\n", err)
    }
}

示例2//复制文件方法流的方式复制
func CopyFile(dstFileName string, srcFileName string) (err error) {
    source, _ := os.Open(srcFileName)
    destination, _ := os.OpenFile(dstFileName, os.O_CREATE|os.O_WRONLY, 0666)
    buf := make([]byte, 128)
    for {
        n, err := source.Read(buf)
        if err != nil && err != io.EOF {
            return err
        }
        if n == 0 {
         break
        }
        if _, err := destination.Write(buf[:n]); err != nil {
            return err
        }
    }
}
func main() {
    //调用 CopyFile 完成文件拷贝
    srcFile := "c:/000.avi" dstFile := "D:/000.avi" err := CopyFile(dstFile, srcFile)
    if err == nil {
        fmt.Printf("拷贝完成\n")
    } else {
   	 fmt.Printf("拷贝错误 err=%v\n", err)
    }
}

//创建目录
//一次创建一个目录
err := os.Mkdir("./abc", 0666)
if err != nil {
	fmt.Println(err)
}
//一次创建多个目录
err := os.MkdirAll("dir1/dir2/dir3", 0666) //创建多级目录
if err != nil {
	fmt.Println(err)
}

//删除目录和文件
//1、删除一个目录或者文件
err := os.Remove("t.txt")
if err != nil {
	fmt.Println(err)
}
//2、一次删除多个目录或者文件
err := os.RemoveAll("aaa")
if err != nil {
	fmt.Println(err)
}
  • 19
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值