变量与常量
变量
1、var 声明变量
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
}
示例1:
func ifDemo1() {
score := 65
if score >= 90 {
fmt.Println("A")
} else if score > 75 {
fmt.Println("B")
} else {
fmt.Println("C")
}
}
示例2:if 条件判断特殊写法
//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 代替
示例1:
for 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 语句能简化一些代码的实现过程。
示例1:
func 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
1、map 基本使用
//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
2、map 的遍历
Go 语言中使用 for range 遍历 map。
func 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 {
字段名 字段类型
字段名 字段类型
…
}
示例1:
type 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)
}
2、map 的值实现空接口
//使用空接口实现可以保存任意值的字典。
// 空接口作为 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 可能是的类型。
示例1:
func 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 。
示例1:
var 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)
4、for 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.TypeOf 和reflect.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 类型,其中包含了原始值的值信息
示例1:
func 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())
}
}
示例2:
func 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)
}