通过go help
可以查看所有的go命令
build : 编译包和依赖;如果是main
包,当执行 go build
之后,会在当前目录下生成一个可执行文件。如果需要再$GOPATH/pkg
目录下生成相应的文件,需要执行go install
,或者使用go build -o path/main.go 代码路径
,示例go]$ go build -o bin/sort src/day4/sort/test1/main.go
run:编译并运行go程序
get :下载并安装包和依赖,-u 强制使用网络去更新包;
clean: 移除对象文件;用来移除当前源码包和关联源码包里面编译生成的文件;go clean -i -n
doc :显示包或者符合的文档
env:打印go 的环境变量信息
bug:启动错误报告
fix:运行 go tool fix
fmt :运行gofmt 进行格式化,gofmt -w -l src ; 开发工具里面一般自带格式化功能
install : 编译并安装包和依赖
list:列出包
test:运行测试
tool : 运行go提供的工具
package main
import "fmt"
// 注释 注释不参与程序的编译, 可以帮助理解程序功能
// 行注释 只能注释一行
/*
块注释
可以注释多行
*/
// main 叫做主函数 是程序的主入口 程序有且只有一个主函数
func main() {
// 在终端打印 hello Go
fmt.Println("hello Go")
}
go语言是一门区分大小写的语言
命名规则设计变量、常量、全局函数、结构体、接口、方法等的命名,任何需要对外暴露的名字必须一大写字母开头、不需要对外暴露的则应该以小写字母开头。
当命名(常量、变量、类型、函数命、结构体字段)以一个大写字母开头,eg:Name
那么这种就可以被外部包的代码所引用(引用时需要先导入这个包)
包名称
保持package
的名字和目录保持一致,尽量采取有意义的包名,包名应该为小写单词,不要使用下划线或者混合大小写
package model
package service
文件命名
采取有含义的文件名,使用小写字母,使用下划线分割组合单词
user_dao.go
admin.go
结构体命名
采用驼峰命名法,首字母根据访问控制大写或者小写
type Info struct{
Name string
Age int
}
接口命名
采用驼峰命名法
单个函数的结构名以“er”作为后缀,eg:Reader
、Writer
type Usber interface {
Read()
Write()
变量命名规范
1、只能以字母或者下划线开头
2、只能使用字母数字下划线
3、区分大小写
4、不能使用系统关键字
break
、default
、func
、interface
、select
、case
、defer
、go
、map
、struct
、chan
、else
、goto
、package
、switch
、const
、fallthrough
、if
、range
、type
、contine
、for
、import
、return
、var
建议使用驼峰命名法
定义变量名称
还有一种流行的命名方法,使用 _
来链接所有的单词,例如:my_name
常量命名
常量需全部大写字母组成,并使用下划线分词
const APP_URL = "cc.com"
单元测试
单元测试文件命名规范为example_test.go
, 测试用例的函数必须以 Test
开头,eg:TestExample
什么叫变量
所谓的变量简单的理解就是计算机用来存储数据的。
变量就是一个指定名称和类型的数据存储位置。
变量的值在运行中是可以改变的
变量定义格式
var 变量名 数据类型 声明变量
var 变量名 数据类型 = 值 定义
变量名 := 值 自动推到类型
注意: 变量的类型不同不能进行计算,需要使用类型转换
变量赋值
package main
// 定义全局变量
var x string = "Hello Go"
func main() {
// 定义内部变量
var y string = "vic"
// 等价以下写法
var y string
y = "vic"
// 初始化赋值
var name string = "teacher"
// 或者直接赋值,让GO语言推断变量的类型
var y = "vic"
// 更简洁的写法,自动推到类型,:= ;也是最常用的方法, := ,此方法初始化变量的方式只能用在函数内部
x := "victor"
// 一次定义初始化多个变量
var (
v1 int = 20
v2 string = "vic"
)
// 等价于
v1, v2 := 20, "vic"
// 定义匿名变量
// _ 下划线定义,匿名变量配合函数返回值使用才有价值,一般是丢弃数据不进行处理
_, x := 1, 2
}
输出格式
fmt 包 实现了类似 C 语言 printf 和 scanf 的格式化
I/O
(输出输入)
https://studygolang.com/static/pkgdoc/pkg/fmt.htm#Formatter
func Println(a ...interface{})(n int, err error)
func Fprintln(w io.Writer, a ...interface{}) (n int, err error)
fmt.Println() 输出数据, 自带换行
fmt.Print() 输出数据,不带换行
fmt.Printf() 格式化输出数据
fmt.Fprintln() 、fmt.Fprint、fmt.Fprintf 功能同上面三个函数,只不过将转换结果写入到 w 中
fmt.Scan() 输入数据 &变量
常用的转义字符
\t 一个指标单位,实现对齐功能
\n 实现换行
\\ 一个 \
\" 一个 “
\r 一个回车
bool 默认值 false ,其值不为真即为假,不可以用数字代表,true 或者 false
float32 默认小数位置保留 7
位有效数据
float64 默认小数位置保留 15
位有效数据
没有单独的字符型,使用 byte 来保存单个字母的字符
字符 一般使用单引号,只有一个字符,转义字符除外\n
,打印值为 ASCII
字符串一旦赋值就不能修改,由一个或多个字符组成
“” 双引号,会识别转义字符
`` 反引号,以字符串原生形式输出,包括换行和特殊字符
注意: 在 Go 语言中一个汉字算作 3
个字符
字符串拼接
package main
import (
"bytes"
"fmt"
"strings"
)
func main() {
name := "王小二"
age := "20"
// 字符串拼接
str := name + ", " + age
fmt.Println(str)
// 利用strings.join 拼接, 把字符串转换成数组[]string
str1 := strings.Join([]string{name, age}, " - ")
fmt.Println(str1)
// 利用 buffer.WriteString() 拼接
var buff bytes.Buffer
buff.WriteString("王小二")
buff.WriteString(" , ")
buff.WriteString("20")
fmt.Println(buff.String())
}
字符串修改
在go语言中不能直接修改字符串的内容,一般先将字符串复制到一个可写的变量中(为[]byte、[]rune 的变量类型)再进行修改
byte
类型只能正常输出 ASCII 编码范围的字符,byte 表示一个字节rune
类型可以输出 UTF-8 编码范围的字符,rune 表示四个字节package main
import (
"fmt"
"strings"
)
func main() {
str := " Beijing is 首都, Welcome "
// 对于全是ASCII编码的字符串用 ([]byte)
strTmp := []byte(str) // 先转换为 []byte 类型
strTmp[3] = ',' // 把第 3 个字符,修改成 逗号
fmt.Println(string(strTmp)) // string()表示强制类型转换,转换为字符串
// 利用strings.LastIndex 查询索引值
str1 := []byte(str)
a := strings.LastIndex(str, "i") // 把最后一个 i 修改成 +
str1[a] = '+'
fmt.Println(string(str1))
// 对于包含中文等字符的字符串时用 ([]rune)
a2 := strings.Index(str, "首") // 修改中文,先转换成 []rune
str2 := []rune(str)
str2[a2] = '中'
fmt.Println(string(str2))
}
字符串反转
package main
import "fmt"
func main() {
str := " Beijing is , Welcome "
// 先转换成字节[]byte
args := []byte(str)
// 定义一个新的变量
str2 := ""
// 循环打印args 字节,并追加到 str2
for i := len(args) - 1; i >= 0; i-- {
str2 += string(args[i])
}
fmt.Println(str2)
}
strings 包
package main
import (
"fmt"
"strings"
)
func main() {
str := " Beijing is 首都, Welcome "
fmt.Println(str)
// strings.Contains 布尔判断,返回 true 、false
fmt.Println(strings.Contains(str, "co"))
// strings.Count 统计某个 字符 出现的次数
fmt.Println(strings.Count(str, "i"))
// strings.TrimSpace 去掉字符串 首尾的空格
fmt.Println(strings.TrimSpace(str))
// strings.Split 对字符串进行分割,返回数组切片
fmt.Println(strings.Split(str, ","))
// strings.Replace 字符串替换,替换次数为n 次,n 从 1 开始计算如果n 为 -1 ,则替换全部
fmt.Println(strings.Replace(str, "Beijing", "Shanghai", 1))
// strings.ToLower 全部转换为小写
fmt.Println(strings.ToLower(str))
// strings.ToUpper 全部转换成大写
fmt.Println(strings.ToUpper(str))
// strings.HasPrefix 判断是否以某字符开头,布尔判断,返回 true 、false
fmt.Println(strings.HasPrefix(str, ""))
// strings.HasSuffix 判断是否以某字符开头,布尔判断,返回 true 、false
fmt.Println(strings.HasSuffix(str, "1"))
// strings.TrimLeft 去掉字符串首部 定义的字符
fmt.Println(strings.TrimLeft(str, " "))
// strings.TrimRight 去掉字符串尾部 定义的字符
fmt.Println(strings.TrimRight(str, " "))
// strings.Index 查看字符所在的第一个索引出现的位置
fmt.Println(strings.Index(str, "i"))
// strings.LastIndex 查看字符所在的最后一个索引出现的位置
fmt.Println(strings.LastIndex(str, "i"))
}
int
和 uint
在32位操作系统上,使用 32 位(4个字节),64位系统上使用64位(8个字节)
整数
无符号整数
查看数据类型,以及最小~最大值
package main
import (
"fmt"
"math"
"unsafe"
)
func main() {
var i8 int
fmt.Printf("i8类型:%T \ni8长度:%d \n最小值 %v \n最大值 %v \n", i8, unsafe.Sizeof(i8), math.MinInt, math.MaxInt)
}
:=
不能用来定于常量示例:
// 常规定义
const X string = "vic"
const y = "tom"
// 一次定义多个
const x,y int = 1, 2
// 一般使用如下方式定义多个常量
const (
x int = 10
y string = "vic"
)
// 如果不提供初始类型,那么视作与上一个常量值相同
const(
a = "vic"
b // b = "vic"
)
常量声明可以使用iota
常量生成器初始化
const 中每新增一行常量声明将使 iota 计数一次
示例
// 第一个 iota 等于 0 ,每当 iota 在新的一行被使用时,它的值都会自动加 1 ;所以 a=0, b=1, c=2 可以简写如下
const(
a = iota
b
c
)
// 枚举 常见的iota示例
//关键字 iota 定义常量组中从 0开始按行计数的自增枚举值
const (
Sunday = iota // 0
Monday // 1,通常省略后续行表达式。
Tuesday // 2
Wednesday // 3
Thursday // 4
Friday // 5
Saturday // 6
)
fmt.Println(Sunday)
// 使用 _ 跳过某些值
const (
n1 = iota // 0
n2 // 1
_
n4 // 3
)
// 等价于使用_ 跳过某个值,自定义数值
const (
n1 = iota // 0
n2 // 1
n3 = 100
n4 // 3
)
示例:
假定 A 值为 10 ,B 值为 20
运算符 | 描述 | 实例 |
---|---|---|
+ | 相加 | A + B 输出结果 30 |
- | 相减 | A - B 输出结果 -10 |
* | 相乘 | A * B 输出结果 200 |
/ | 相除 | B / A 输出结果 2 |
% | 求余 | B % A 输出结果 0 |
++ | 自增 | A++ 输出结果 11 |
– | 自减 | A-- 输出结果 9 |
示例:
假定 A 值为 10 ,B 值为 20
运算符 | 描述 | 实例 |
---|---|---|
== | 检查两个值是否相等,如果相等返回 True 否则返回 False。 | (A == B) 为 False |
!= | 检查两个值是否不相等,如果不相等返回 True 否则返回 False。 | (A != B) 为 True |
> | 检查左边值是否大于右边值,如果是返回 True 否则返回 False。 | (A > B) 为 False |
< | 检查左边值是否小于右边值,如果是返回 True 否则返回 False。 | (A < B) 为 True |
>= | 检查左边值是否大于等于右边值,如果是返回 True 否则返回 False。 | (A >= B) 为 False |
<= | 检查左边值是否小于等于右边值,如果是返回 True 否则返回 False。 | (A <= B) 为 True |
示例:
假定 A 值为 True ,B 值为 False
运算符 | 描述 | 实例 |
---|---|---|
&& | 逻辑 AND 运算符。 如果两边的操作数都是 True,则条件 True,否则为 False。 | (A && B) 为 False |
|| | 逻辑 OR 运算符。 如果两边的操作数有一个 True,则条件 True,否则为 False。 | (A || B) 为 True |
! | 逻辑 NOT 运算符。 如果条件为 True,则逻辑 NOT 条件 False,否则为 True。 | !(A && B) 为 True |
运算符 | 描述 | 实例 |
---|---|---|
= | 简单的赋值运算符,将一个表达式的值赋给一个左值 | C = A + B 将 A + B 表达式结果赋值给 C |
+= | 相加后再赋值 | C += A 等于 C = C + A |
-= | 相减后再赋值 | C -= A 等于 C = C - A |
*= | 相乘后再赋值 | C _= A 等于 C = C _ A |
/= | 相除后再赋值 | C /= A 等于 C = C / A |
%= | 求余后再赋值 | C %= A 等于 C = C % A |
运算符 | 描述 | 实例 |
---|---|---|
& | 返回变量存储地址 | &a; 将给出变量的实际地址。 |
* | 指针变量。 | *a; 是一个指针变量 |
选择结构:程序依据是否满足条件,有选择的执行响应功能
循环结构: 程序依据条件是否满足,循环多次执行某段代码
if 语句
if 布尔表达式 {
/* 在布尔表达式为 True 时执行 */
}
if…else 语句
if 布尔表达式 {
/* 在布尔表达式为 True 时执行 */
} else {
/* 在布尔表达式为 False 时执行 */
}
if 语句嵌套
在 if 或 else if 语句中嵌入一个或多个 if 或 else if 语句
if 布尔表达式 1 {
/* 在布尔表达式 1 为 true 时执行 */
if 布尔表达式 2 {
/* 在布尔表达式 2 为 true 时执行 */
}
}
示例
// 根据布尔值 判断
var flag1 = true
if flag1 {
fmt.Println("flag1 is true")
}
fmt.Println("判断结束")
a := 10
b := 20
if b > a {
fmt.Printf("b: %d", b)
} else {
fmt.Printf("a: %d", a)
}
// if 里面可以在条件判断语句里面声明一个变量,这个变量的作用域只能在该条件逻辑块内,其他地方就不起作用了
if x :=1 ; x > 10 {
fmt.Println("x is greater than 10")
} else {
fmt.Println("x is less than 10")
}
//
c := 6
if c > 0 && (10/c) >1 {
fmt.Println("OK!")
} else {
fmt.Println("error!")
}
// 多条件判断
OS := "win"
if OS == "Centos" {
fmt.Printf("this system os: %s \n", OS)
} else if OS == "Ubuntu" {
fmt.Printf("this is system os: %s \n", OS)
} else {
fmt.Printf("this is system os: %s \n", OS)
}
// 嵌套案例; 三只小猪称体重
a, b, c := 30, 20, 10
if a > b {
if a > c {
fmt.Println("a 最重", a)
} else {
fmt.Println("c 最重", c)
}
} else {
if b > c {
fmt.Println("b 最重", b)
} else {
fmt.Println("c 最重", c)
}
}
switch 语句
case
分支都是唯一的,从上至下逐一测试,直到匹配为止break
switch var1 {
case val1:
...
case val2:
...
default:
...
}
示例:
// 单独写上面赋值,或者写在switch 里面赋值,再判断,二者选其一
x := 2
switch x {
case 1:
fmt.Println("1")
case 2:
fmt.Println("2")
default:
fmt.Println("6")
}
// switch 不提供任何判断的值,在每个case分支做测试判断
switch Num :=20; {
case Num < 0:
fmt.Println("Num < 0")
case Num > 0 && Num < 10:
fmt.Println("Num 值很小")
default:
fmt.Println("Num 值很大")
}
select 语句
switch
语句,但是每个 case
必须是一个通信操作,要么是发送要么是接收select
随机执行一个可运行的case
。如果没有case
可执行,它将阻塞,直到有case
可运行。一个默认的子句应该是总是可以运行的语法
select {
case communication clause :
statement(s);
case communication clause :
statement(s);
/* 你可以定义任意数量的 case */
default : /* 可选 */
statement(s);
}
示例:
var c1, c2, c3 chan int
var i1, i2 int
select {
case i1 = <-c1:
fmt.Printf("received ", i1, " from c1\n")
case c2 <- i2:
fmt.Printf("sent ", i2, " to c2\n")
case i3, ok := (<-c3): // same as: i3, ok := <-c3
if ok {
fmt.Printf("received ", i3, " from c3\n")
} else {
fmt.Printf("c3 is closed\n")
}
default:
fmt.Printf("no communication\n")
}
# 其中expression1 和expression3 是变量声明或者函数调用返回值,
# expression2 是用来条件判断,
# expression1 在循环开始之前调用,
# expression3 在每轮循环结束之时调用
for expression1; expression2; expression3 {
// ...
}
示例
// 循环条件初始化 条件判断,循环后条件变化
for a := 0; a < 5; a++ {
fmt.Printf("a is result: %d \n", a)
}
// for 配合 range 可以用于读取 slice 和 map 的数据 for 循环迭代数组
for i,x :=range num {
fmt.Printf("%d is result: %d \n", i, x)
}
strings := []string{"google", "runoob"}
for i, s := range strings {
fmt.Println(i, s)
}
numbers := [6]int{1, 2, 3, 5}
for i,x:= range numbers {
fmt.Printf("第 %d 位 x 的值 = %d\n", i,x)
}
// 打印九九乘法表
// 外层控制行,内行控制列
for k := 1; k <= 9; k++ {
for j := 1; j < k; j++ {
fmt.Printf("%d*%d = %d ", j, k, k*j)
}
fmt.Println()
}
Break 操作是跳出当前循环,可用于 for、switch、select
i := 0
for { // for 后面不写任何东西,循环条件永远为真,也就是死循环
i++
time.Sleep(time.Second) // 间隔 1s 打印一次
if i == 6 { //跳出循环,如果嵌套多个循环,,就跳出最近的那个内循环
break
}
fmt.Println("i = ", i)
}
continue 操作是跳过本次结构继续往下执行, 仅用于 for 循环语句
i := 0
for {
i++
time.Sleep(time.Second)
if i == 3 {
continue
}
fmt.Println("i = ", i)
}
goto
goto
语句通过标签进行代码之间的无条件跳转
// 跳出双层循环
/*
标签breakTag 只能被goto引用,不影响代码执行流程,
在定义breakTag标签之前有个return语句,
此处如果不手动返回,则在不满住条件时也会执行breakTag代码
*/
for i := 0; i < 5; i++ {
for j := 0; j < 5; j++ {
if i == 3 && j == 3 {
goto breakTag
}
fmt.Println(i, " -- ", j)
}
}
return
breakTag:
fmt.Println("跳出循环.....")
函数调用流程:先调用后返回,先进后出
语法
func 函数名(参数列表) (返回值) {
函数体
}
特点
定义函数名,但是参数和返回值均为空
package main
import "fmt"
// 无参无返回值函数的定义
func sayHello() {
fmt.Println("SayHello")
}
// main 叫做主函数 是程序的主入口 程序有且只有一个主函数
func main() {
sayHello()
}
定义函数名及参数,返回值为空
package main
import "fmt"
// 有参无返回值函数的定义
func sayHello(a int, b int) {
fmt.Println(a + b)
}
// main 叫做主函数 是程序的主入口 程序有且只有一个主函数
func main() {
sayHello(1, 2)
}
定义函数名及参数,返回一个返回值
package main
import "fmt"
// 有参有返回值函数的定义
func max(num1, num2 int) int {
// 定义局部变量
var result int
if num1 > num2 {
result = num1
} else {
result = num2
}
return result
}
// main 叫做主函数 是程序的主入口 程序有且只有一个主函数
func main() {
// 调用函数并返回最大值
ret := max(10, 20)
fmt.Println(ret)
}
定义函数名及参数,且定义命名返回值
package main
import "fmt"
// 定义函数名及参数,且定义一个命名返回值
func add(a, b int) (sum int) {
sum = a + b
return
}
// 定义函数名及参数,且定义二个命名返回值
func cacl(a, b int) (sum int, cut int) {
sum = a + b
cut = a - b
return
}
// main 叫做主函数 是程序的主入口 程序有且只有一个主函数
func main() {
ret1 := add(1, 2)
fmt.Println(ret1)
ret2, ret3 := cacl(3, 9)
fmt.Println(ret2, "\n", ret3)
}
注意 :
在上面的函数中,sum 和 cut 是命名返回值,return 语句没有指定任何返回值。因为在函数声明的时候已经指定 sum 和 cut 是返回值,在遇到 return 语句时它们会自动从函数中返回,在 Go 语言中,有返回值的的函数,无论是命名返回值还是普通返回值,函数中必须包涵 return 语句
有函数名,定义可变参数
参数里面arg
是一个slice
(切片)
通过arg(index)
依次访问所有参数
通过len(arg)
来判断传递参数的个数
// 0 个或 多个参数
func add(arg...int) int{
return
}
// 1 个或 多个参数
func add(a int, arg...int) int{
return
}
// 2 个或 多个参数
func add(a, b int, arg...int) int{
return
}
示例
package main
import (
"fmt"
)
// 传递值进行相加
func add(a int, arg...int) int {
sum := a
for i :=0; i < len(arg); i++ {
//打印 后面参数的值
fmt.Printf("可变参数: %d\n",arg[i])
sum +=arg[i]
}
return sum
}
// 传递字符串,结果拼接
func connect(a string, arg...string) (reslut string) {
reslut = a
for i := 0; i < len(arg); i++ {
reslut +=arg[i]
}
return
}
func main() {
sum := add(10, 20, 30)
fmt.Printf("result is :%d\n", sum)
res :=connect("Hi", " ", "Victor")
fmt.Println(res)
}
函数也是一种类型
package main
import (
"fmt"
)
// 在go 中,函数也是一种数据类型
// 可以赋值给一个变量,则该变量就是一个函数类型的变量了,通过变量可以对函数进行调用
func sum(a, b int) int {
return a + b
}
func main() {
c := sum
fmt.Printf("c 的类型是:%T \nsum 的类型是:%T \n", c ,sum)
res := c(1,2) // 等价 res := sum(1,2)
fmt.Println("res 的值是: ",res)
}
函数作为参数传递给另一个函数
package main
import "fmt"
// 定义一个函数
func sayHello(name string) {
fmt.Printf("Hello %s", name)
}
// 定义函数,传参类型为函数类型
func test(name string, f func(string)) {
f(name)
}
func main() {
test("Tom", sayHello)
}
函数作为函数的返回值
package main
import "fmt"
func add(a int, b int) int {
return a + b
}
func sub(a int, b int) int {
return a - b
}
func other(a int, b int) int {
fmt.Println("传递参数不对")
return 1001
}
func cal(s string) func(int, int) int {
switch s {
case "+":
return add
case "-":
return sub
default:
return other
}
}
func main() {
add := cal("+")
r := add(3, 5)
fmt.Println(r)
sub := cal("-")
r = sub(3, 5)
fmt.Println(r)
other := cal("/")
r := other(3, 5)
fmt.Println(r)
}
匿名函数即没有名字的函数
如果某个函数只是希望调用异常,那么就可以使用匿名函数,当然也可以实现多次调用
示例
package main
import "fmt"
// main 叫做主函数 是程序的主入口 程序有且只有一个主函数
func main() {
// 无参无返回值的匿名函数
func() {
fmt.Println("this is 匿名函数")
}() // 末尾使用 () 表示此匿名函数被自己调用
// 无参有返回值的匿名函数
func() int {
a := 10
fmt.Println(a)
return a
}()
// 匿名函数:自己调用自己;二个数求和,先赋值给一个变量再调用
ret := func(a1, a2 int) int {
return a1 + a2
}(10, 20)
fmt.Printf("ret type => %T, ret=%d \n", ret, ret)
// 第二种匿名函数定义方式
f := func(a1, a2 int) int {
return a1 - a2
}
f1 := f(10, 5)
fmt.Printf("f type => %T, f1 type => %T \n", f, f1)
fmt.Println(f1)
}
示例一
func recusive(){
fmt.Println("Hello ")
time.Sleep(time.Second)
recusive() //调用函数自身,实现递归
}
func main(){
recusive
}
示例二
func test(n int){
if n > 5 {
n-- // 递归必须向退出条件逼近,否则就是无限循环调用
test(n)
}else{
fmt.Println("n => ",n)
}
}
func main(){
test(5)
}
示例三
// 功能 阶乘 4 * 3 * 2 * 1
func mul(n int) int {
// 返回条件
if n == 1 {
return 1
} else{
// 自己调用自己
return mul(n-1) * n
}
}
func main() {
n := mul(4)
fmt.Println(n)
}
示例四
// 递归实现数字累加
//递归实现1+2+3+……100
func add1(n int) int {
if n == 100 {
return 100
}
return n + add1(n + 1)
}
func add2(i int) (sum int) {
if i == 1 {
return 1
}
return i + add2(i - 1)
}
func main() {
sum1 := add1(1)
fmt.Printf("sum1 = %d\n", sum1)
sum2 := add2(100)
fmt.Printf("sum2 = %d", sum2)
}
Golang 有一个特殊的函数 init
函数,先于 main
函数执行,实现包级别的一些初始化操作
init 函数优先于main 函数执行,不能被其他函数调用
每个包可以有多个init 函数,同一个包的 init 执行顺序,没有明确规定
init 函数没有参数 和 返回值
golang初始化顺序:变量初始化 > init() > main()
package main
import (
"fmt"
)
var age = test()
func test() int(
fmt.Println("test()") // 1
return 20
)
// 通常在init()函数中完成初始化工作
func init() {
fmt.Println("init()") // 2
}
func main() {
fmt.Println("main()") // 3
}
用来存储集合的数据
数组的声明和初始化
1、声明存储数据的类型
2、存储元素的数量,也就是数组的长度
3、数组是类型相同元素的集合,Go 不允许在数组中混合使用不同类型的元素
4、数组中的所有元素都被自动赋值为元素类型的 0 值,布尔类型是 false
,字符串是空字符串
5、数组的索引从 0 开始到 length-1
结束
数组的定义
第一种方式
var <数组名称> [<数组长度>]<数组元素类型>
示例:
// arr[0] 表示数组 arr 的第一个元素,arr[1] 表示数组的第二个元素,等等依次增加
var arr [5]
arr[0] = 4
arr[1] = 43
arr[2] = 3423
arr[3] = 234
arr[4] = 654
第二种方式
var <数组名称> = [<数组长度>]<数组元素类型>{元素1, 元素2, ...}
示例:
var arr = [3]{32, 323, 765}
GO提供了 := 操作符,可以在创建数组的时候直接初始化赋值
arr := [3]{32, 323, 765}
第三种方式
声明数组的时候可以忽略数组的长度使用 ... 代替,让编辑器自动推到数组的长度
var <数组名称> = [...]<数组元素类型>{元素1, 元素2, ...}
示例:
var arr = [...]{32342, 324, 43, 443}
或者
arr := [...]{32342, 324, 43, 443}
第四种方式
给数组指定的索引指定初始化的值,其他为 0
var <数组名称> = [...]<数组元素类型>{索引1:元素1, 索引2:元素2, ...}
示例:
var arr = [...]{3:232, 6:4353}
或者
arr := [...]{3:232, 6:4353}
示例:
循环打印数组中的值
package main
import (
"fmt"
)
func main() {
// 循环打印数组中的值
array :=[...]int{1,2,3,4}
for i :=0; i < len(array); i++ {
fmt.Printf("%d 值: %d \n", i, array[i])
}
// 使用 for range 循环
array1 :=[...]string{"name","Victor","age"}
for k,v :=range array1 {
fmt.Printf("索引:%d ,值:%s \n", k,v)
}
// 不要索引
for _,i :=range array1 {
fmt.Printf("值:%s \n", i)
}
}
在数组中寻找最大 or 最小值
package main
import (
"fmt"
)
func main() {
arr := [10]int{10, 4, 200, 43, 20, 324, 32, 90, 34, 31}
// 找出最大值 or 最小值
max := arr[0]
for i := 1; i < len(arr); i++ {
if max < arr[i] {
max = arr[i]
}
}
fmt.Println(max)
}
func main() {
// 求出一个数组里面的最大值,并得到对应的下标
// 1.声明一个数组
// 2.假定第一个元素就是最大值,下标 0
// 3.然后从第二个元素开始循环比较,如果发现有更大则替换
var myArr [...]int = [...]int {30, -2, 3, 20, 15}
max := myArr[0]
maxIndex := 0
for i := 1; i < len(myArr); i++{
// 从第二个元素开始比较,如果有更大,则交换
if max < myArr[i] {
max = myArr[i]
maxIndex = i
}
}
// 打印结果
fmt.Printf("max= %v ; maxIndex= %v", max, maxIndex)
}
数组的查找
package main
import (
"fmt"
)
func main() {
// 定义数组
names := [4]string{"tom", "vic", "ccg", "vc"}
var inName string
fmt.Println("请输入要查找的名字=》")
fmt.Scanln(&inName)
// 顺序查找 第一种方式
for i := 0; i < len(names); i++ {
if inName == names[i] {
fmt.Println("找到 :", inName, "下标:", i)
break
} else if i == (len(names) - 1){
fmt.Println("没有找到 :", inName)
}
}
// 顺序查找 第二种方式
index := -1
for i := 0; i < len(names); i++ {
if inName == names[i] {
index = i // 讲找到值的对应下标赋给 index
break
}
}
if index != -1 {
fmt.Println("找到 :", inName, "下标:", index)
} else {
fmt.Println("没有找到 :", inName)
}
}
冒泡排序
示例[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BhA5TfsZ-1678696518063)(images/image-20210331142932764.png)]
package main
import (
"fmt"
)
func main() {
arr := [10]int{10, 4, 200, 43, 20, 324, 32, 90, 34, 31}
// 冒泡排序
// 外层控制行
for k := 0; k < len(arr)-1; k++ {
// 内层控制列
for j := 0; j < len(arr)-1-k; j++ {
// 比较二个相邻元素,满足条件就交换数据
// 升序排列使用大于号; 降序排列使用小于号
if arr[j] > arr[j+1] {
arr[j], arr[j+1] = arr[j+1], arr[j]
}
}
}
fmt.Println(arr)
}
数组作为函数参数和返回值
package main
import (
"fmt"
)
// 数组作为函数参数和返回值
func bubbleSort(array [10]int) [10]int {
for k := 0; k < len(array); k++ {
for j := 0; j < len(array)-1-k; j++ {
// 比较二个相邻元素,满足条件就交换数据
if array[j] < array[j+1] {
array[j], array[j+1] = array[j+1], array[j]
}
}
}
return array
}
func main() {
arr := [10]int{10, 4, 200, 43, 20, 324, 32, 90, 34, 31}
arr = bubbleSort(arr)
fmt.Println(arr)
}
多维数组
多维数组的定义
二维数组
var 数组名 [行数][列数] 数据类型
示例
package main
import "fmt"
func main() {
// 多维数组
// 定义了一个数组 A ,A 数组里面包涵了 2 个元素, 但是每个元素里面又包涵了 3 个元素
A := [2][3]{
{1, 3, 4},
{34, 43, 54}
}
fmt.Println(A)
}
多维数组的遍历
package main
import (
"fmt"
)
func main() {
var arr = [3][3]int {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}
// for 循环 来遍历
for j := 0; j < len(arr); j++ {
for k :=0; k < len(arr[j]); k++{
fmt.Printf("%v \t", arr[j][k])
}
fmt.Println()
}
// for-range 来遍历, 使用 _ 省略索引的打印
for _,x := range arr {
for _,y := range x {
fmt.Printf("%v \t", y)
}
fmt.Println()
}
}
示例
fncu main() {
// 创建一个 byte 类型的26元素的数组,分别放置 A-Z
// 使用for 循环打印
//使用for 循环,利用字符可以进行运算的特点来进行赋值 'A' +1 -> 'B'
var myChars [26]byte
for i := 0; i < len(26); i++{
myChars[i] = 'A' + byte(i) // 需要将 i 装换成 byte 类型
}
for i := 0; i< 26; i++{
// %c 该值对应的unicode码值
fmt.Piintf("%c", myChars[i])
}
}
示例
func main() {
// 求数组的和及平均值
// 1.定义数组
// 2.求和
// 3.求平均值
var rSum [5]int = [...]int {20, -1, 10, 11}
sum := 0
for _, v := range rSum {
// 累计求和
sum += v
}
// 求平均值
fmt.Printf("sum= %v ; 平均值= %v", sum, float64(sum) / float64(len(v)))
}
示例
package main
import (
"time"
"math/rand"
"fmt"
)
// 随机生成 五个随机数,并将其反转打印
// 1.随机生成五个随机数,rand.Intn()
// 2.存到数组
// 3.反转打印
func main() {
var intArr [5]int
// 为了每次生成的随机数不一样,需要给一个seed值
rand.Seed(time.Now().UnixNano())
for i :=0; i < len(intArr); i++ {
intArr[i] = rand.Intn(100)
}
fmt.Println(intArr)
for x,y := 0, len(intArr) -1; x < y; x ,y = x + 1, y - 1{
intArr[x], intArr[y] = intArr[y], intArr[x]
}
fmt.Println(intArr)
}
切片是建立在数组之上的更方便、更灵活、更强大的数据结构,切片不存储任何元素而只是对现有数组的引用
切片对象非常小,是因为它是只有 3 个字段的数据结构:
指向底层数组的指针
切片的长度
切片的容量
使用内置的 make 函数时,需要传入一个参数指定切片的长度,如:切片的长度是 6,这时候容量也是 6 ;当然也可以单独指定切片的容量
注意:容量必须 (cap) >= 长度(len)
,不能创建长度大于容量的切片
切片和数组的区别
定义数组必须写元素个数,而切片不需要写元素个数
// 数组
array := [4]int{1, 2, 3, 4}
// 切片
slice := []int{1, 2, 3, 4}
通过 make
创建切片
通过 make
创建的切片可以指定切片的大小和容量
如果没有给切片赋值,那么就会使用默认值(int、float => 0,string => “”,bool => false)
通过make
创建的切片对应的数组是由make
底层维护,对外不可见,只能通过 slice
访问各个元素
基本语法
var 切片名 []type = make([], len, [cap])
// type 数据类型
// len 大小,即长度
// cap 容量,(容量必须 >= 长度)
// 没有赋值,默认都是 0
var slice []int = make([]int, 5, 10)
// 简写
slice := make([]int, 5, 10)
slice := make([]int, 5)
// 赋值
slice[2] = 5
切片直接引用数组
// 从下标 startIndex 开始,取到下标 endIndex, 但是数据不包含 arr[endIndex]
var slice = arr[startIndex:endIndex]
var slice = arr[0:end] // 简写 var slice = arr[:end]
var slice = arr[start:len(arr)] // 简写 var slice = arr[start:]
var slice = arr[0:len(arr)] // 简写 var slice = arr[:]
str := []string{"北京", "上海", "广州", "深圳"}
fmt.Println(str[0:]) // 取所有值
fmt.Println(str[0:3]) // 取0 、1、2 的值
fmt.Println(str[:len(str)]) // 取所有值
切片简单操作示例
// 声明一个切片并追加数据, 使用内置函数 append
var vic []string
vic = append(vic, "Go")
// 同时声明并初始化一个切片
v := []string{"GO", "JAVA"}
fmt.Println(v)
// 切片长度是可变的;使用内置函数 make 创建长度不为 0 的切片
s := make([]int, 5)
fmt.Println(s)
// 取值和赋值,和数组一样
s[1] = 111
s[3] = 333
fmt.Println(s)
fmt.Println(s[4])
// 使用内置函数 len 获取切片的长度
fmt.Println(len(s))
// append 函数不会改变原切片,而是生成了一个新的切片,需要用原来的切片接收这个新切片
s = append(s, 222, 444)
// 把一个切片追加到另一个切片, 通过 ... 操作符
x := []int{1, 2, 3, 4}
y := []int{11, 22, 33}
x = append(x ,y...)
// 切片也支持从一个切片拷贝元素到另一个切片,使用内置函数 copy
k := []string{"a", "b"}
j := []string{"x", "y"}
copy(k, j) // 拷贝切片 j 中的元素到 k 中
fmt.Println(k)
切片的迭代
// 切片是一个集合,可以使用 for range 循环迭代
S1 := []string{"Go", "JAVA", "Python"}
for k,v := range S1{
fmt.Printf("索引 %d => 值 %s \n", k, v)
}
// 如果不打印索引,可以使用 _ 来忽略
for _,v := range S1{
fmt.Printf("值 %s \n", v)
}
// 使用传统 for 循环,配合内置函数 len
for i :=0; i < len(S1); i++{
fmt.Println("值:", S1[i])
}
多维切片
// 同数组一样,切片也可以有多个维度,并且可以不固定长度
S2 := [][]string{
{"C", "JAVA"},
{"Go", "Perl"}
}
for _,v := range S2 {
fmt.Println(v)
}
切片作为函数参数
package main
import "fmt"
func test(v1 []string) {
fmt.Println(v1[1])
fmt.Printf("%v \n", v1)
}
func main() {
v1 := []string{"Go", "Java"}
test(v1)
}
切片作为函数参数和返回值
package main
import "fmt"
func test(v1 []int) (v2 []int) {
v2 = v1[0:2]
fmt.Printf("v2数据: %v \n", v2)
return
}
func main() {
v1 := []int{11, 22, 33, 44}
fmt.Println("v1数据:", v1)
test(v1)
}
基本语法
// map 赋值方式,必须先声明、再初始化,最后赋值
var Victor map[string]string
Victor = make(map[string]string)
Victor["name"] = "Tom"
// 或者
M := make(map[string]int)
M["age"] = 20
M = map[string]int{
"cc": 10,
"vv": 20,
}
// 直接初始化批量并赋值
M1 := map[string]int{
"age": 20,
"sex": 18
}
// 指定 map 长度,一般直接省略
M2 := make(map[string]string, 10)
map 增改删
// 向 map 中插入数据,语法和数组类似
shop := make(map[string]int)
shop["Apple"] = 20
shop["Orange"] = 15
shop["Banana"] = 9
// 修改数据
shop["Apple"] = 30
// 删除数据,使用内置函数 delete
// delete(map, key) 用于删除 map 中的 key,delete 函数没有返回值
delete(shop, "Banana")
遍历 map
shop := map[string]int{
"Apple": 30,
"Banana": 15,
}
// 使用 range 循环
for k,v := range shop{
fmt.Println(k,v)
}
// 判断 key 是否存在
if v, ok := shop["Apple"]; ok{
fmt.Println("Apple:", v)
}else {
fmt.Println("Key is Not found")
}
map 作为函数参数
func test(m map[int]string){
m[101] = "孙悟空"
fmt.Println(m)
}
func main() {
People := map[int]string{
101: "关羽",
102: "张飞",
103: "刘备",
}
fmt.Println(People)
test(People)
}
map 值排序
shop := map[30]string{
30: "tea",
15: "coffee",
50:"sweet",
}
var keys []int
for k,_ := range shop{
keys = append(keys, k)
}
fmt.Println(keys)
sort.Ints(keys[:])
fmt.Println(keys)
for _,v := keys{
fmt.Println(v, shop["v"])
}
统计单词出现的次数
s := "how to contribute who to contact about how"
// 定义 map 存放单词
wordCount := make(map[string]int)
// 分割字符串中的单词
words := strings.Split(s, " ")
// 遍历统计单词
for _, w := range words{
if v, ok := wordCount[w]; ok {
// map 中有这个单词的统计出现就 +1
wordCount[w] = v + 1
}else {
// map 中没有这个单词的统计记录就等于 1
wordCount[w] = 1
}
}
// 打印单词出现的次数
for k,v := range wordCount {
fmt.Println(k,v)
}
双层嵌套
// 存放学生信息,学生 name、age
studentMap := make(map[string]map[string]string)
// 添加数据
studentMap["stu1"] = make(map[string]string)
studentMap["stu1"]["Name"] = "vic"
studentMap["stu1"]["Age"] = "20"
studentMap["stu2"] = make(map[string]string)
studentMap["stu2"]["Name"] = "Tom"
studentMap["stu2"]["Age"] = "18"
fmt.Println(studentMap)
fmt.Println(studentMap["stu1"])
fmt.Println(studentMap["stu1"]["Name"])
// 定义字典,key 再定义字典 key,键值为切片
City := make(map[string]map[string][]string)
// 如果插新加入的元素也是个 map 的话需要再次 make()
Area := make(map[string][]string)
Area["朝阳区"] = []string{"工业大学", "语言大学", "传媒大学"}
Area["海淀区"] = []string{"清华大学", "理工大学", "农业大学"}
City["北京"] = Area
for k, v := range Area {
fmt.Println(k, v)
for x, y := range v {
fmt.Println(x, y)
}
}
三层嵌套
//定义国家
Country := make(map[string]map[string]map[string][]string)
//定义城市
City := make(map[string]map[string][]string)
//定义区
Area1 := make(map[string][]string)
Area2 := make(map[string][]string)
//赋值
Area1["朝阳区"] = []string{"工业大学", "语言大学", "传媒大学",}
Area1["海淀区"] = []string{"清华大学", "理工大学", "农业大学",}
Area2["黄浦区"] = []string{"复旦大学", "同济大学"}
Area2["静安区"] = []string{"交通大学", "财经大学"}
City["北京"] = Area1
City["上海"] = Area2
Country["中国"] = City
for k, v :=range City{
fmt.Println(k, v)
for x, y :=range v {
fmt.Println(x, y)
for i := range y {
fmt.Println(y[i])
}
}
}
Struct(结构体) 可以声明一个新的类型,作为其他类型的属性或者字段
示例:
创建一个自定义类型
Person
代表一个人的实体,实体有用属性:姓名和年龄。这样的类型就称之为struct
Name
字段string
类型,用来保存用户名属性Age
字段int
类型,用来保存 年龄属性
type Person struct{
Name string
Age int
}
基本语法声明类型
// 1. 按照顺序提供初始化
P := Person{"Vic", 20}
// 2. 通过 feild:value 的方式初始化赋值
P := Person{Name: "Vic", Age: 20}
// 3. 先定义 Person 类型的变量再赋值
var P Person
P.Name = "Vic"
P.Age = 20
// 4. 通过 new 函数分配一个指针,此次的 P 类型为 *Person
P := new(Person)
在结构体中使用 ==
或者 !=
可以对二个结构体的成员进行比较
简单示例
package main
import (
"fmt"
)
// 声明一个结构体
type Person struct {
Name string
Age int
}
// 比较二个人的年龄,返回年龄大的那个人,并且返回年龄差
// 定义函数,struct 传值
func Older(p1, p2 Person) (Person, int) {
if p1.Age > p2.Age {
return p1, p1.Age - p2.Age
}
return p2, p2.Age - p1.Age
}
func main() {
// 定义变量,类型为 struct
var T1 Person
T1.Name, T1.Age = "Tom", 20
// 对应字段初始化值
T2 := Person{Name: "Vic", Age: 30}
// 按照 struct 字段定义的顺序进行赋值
T3 := Person{"Bob", 10}
T1_T2_Older, T1_T2_Diff := Older(T1, T2)
T1_T3_Older, T1_T3_Diff := Older(T1, T3)
T2_T3_Older, T2_T3_Diff := Older(T2, T3)
fmt.Printf("%s 和 %s 对比, %s 的年龄最大,二人相差:%d \n", T1.Name, T2.Name, T1_T2_Older.Name, T1_T2_Diff)
fmt.Printf("%s 和 %s 对比, %s 的年龄最大,二人相差:%d \n", T1.Name, T3.Name, T1_T3_Older.Name, T1_T3_Diff)
fmt.Printf("%s 和 %s 对比, %s 的年龄最大,二人相差:%d \n", T2.Name, T3.Name, T2_T3_Older.Name, T2_T3_Diff)
}
匿名结构体
如果结构体是临时使用,可以不用起名字,直接使用
var dog struct{
id int
name string
}
dog.id = 1
dog.name = "小黑"
fmt.Println(dog)
匿名字段
struct
定义的时候字段名与其类型是一一对应的,实际上 GO 支持只提供类型而不写字段名的方式;也就是匿名字段
这是一种可以把已有的类型声明在新的类型中的一种方式,对于代码的复用非常重要。
在 Go 的标准库里经常有这种组合,比如 io 包里,可以看到
ReadWrite
接口就是嵌入Reader
和Writer
接口而组合成新的接口。
示例
创建Human
就是匿名字段(内部字段),Student
是外部字段, 外部字段可以添加自己的方法、字段属性
当匿名字段是struct
的时候,那么这个struct
所拥有的全部字段都被隐式低引入了当前定义的这个struct
package main
import (
"fmt"
)
type Human struct{
Name string
Age int
Wight int
}
type Student struct{
Human // 匿名字段。 默认Student包涵Human的所有字段,通过匿名字段实现继承操作
Level string
}
func main() {
// 初始化
T := Student{Human{"Tom", 20, 90}, "CTO"}
// 访问相应字段
fmt.Println("Name: ", T.Name)
fmt.Println("Age: ", T.Age)
fmt.Println("Level: ", T.Level)
// 修改 Age
T.Age = 30
fmt.Println("Change Age: ", T.Age)
// 修改 Level
T.Level = "CEO"
fmt.Println("Change Level: ", T.Level)
}
外部类型也可以声明同名的字段或者方法,来覆盖内部类型
内部类型
Human
有一个Age
方法,外部进行覆盖,同名重写Age
,不管我们如何同名覆盖,都不会影响内部类型,可以通过访问内部类型来访问它的方法、属性等字段。最外层的优先访问,当通过
Student.Age
访问的时候,访问的是Student
里面的字段,而不是Human
里面的字段,访问内部字段就需要使用Student.Human.Age
package main
import (
"fmt"
)
type human struct {
Name string
Age int // human 拥有的 Age 字段
Wight int
}
type student struct {
human // 匿名字段。 默认Student 包涵 Human 的所有字段
Level string
Age int // student 拥有同名的 Age 字段
}
func main() {
// 初始化
T := student{human{"Tom", 20, 90}, "CTO", 30}
// 访问 student 的 Age
fmt.Println("student Age: ", T.Age)
// 访问 human 的 Age
fmt.Println("human Age: ", T.human.Age)
}
多重继承
package main
import "fmt"
type testA struct {
id int
name string
}
type testB struct {
testA
age int
}
type testC struct {
testB
score int
}
func main() {
// 定义机构体变量
var s testC
s.id = 001
s.name = "Vic"
s.age = 20
s.score = 90
fmt.Println(s)
fmt.Println(s.testA.name)
}
struct 工厂模式
解决小写字母不能被调用问题
目录结构
─➤ tree
.
├── main.go
└── model
└── student.go
student.go 文件
package model
// 定义一个学生结构体
type student struct {
Name string
age int
}
// 因为定义的 student 结构体首字母为小写,所以只能在 model 使用
// 使用 工厂模式来解决外部不能访问的问题
func GetStudent(n string, a int) *student {
return &student{
Name: n,
age: a,
}
}
// 因为 age 字母是小写,所以在其他包不可以直接访问,因此提供一个方法
func (s *student) GetAge() int {
return s.age
}
main.go 文件
package main
import (
"example/day1/005/model"
"fmt"
)
func main() {
// 初始化
T := model.GetStudent("Vic", 20)
// 访问 student 结构体
fmt.Printf("student struct: %+v \n", *T)
// 访问 student 的 age
fmt.Println("student age: ", T.GetAge())
}
类型为结构体的数组
package main
import "fmt"
type student struct {
id int
name string
age int
}
func main() {
// 定义一个数组,数据类型为 student
var arr [4]student
arr[0] = student{000, "Vic", 18}
arr[1] = student{001, "Tom", 8}
arr[2] = student{002, "Bob", 20}
arr[3] = student{003, "Sky", 15}
fmt.Println(arr)
// 按照年龄排序结构体数组中的数据
for k := 0; k < len(arr)-1; k++ {
for j := 0; j < len(arr)-1-k; j++ {
// 比较结构体成员 年龄
if arr[j].age > arr[j+1].age {
// 结构体数组中的元素 允许相互赋值 ,将结构体成员中的数据进行互换
arr[j], arr[j+1] = arr[j+1], arr[j]
}
}
}
for i := 0; i < len(arr); i++ {
fmt.Println(arr[i])
}
}
类型为结构体的切片
package main
import "fmt"
type student struct {
id int
name string
age int
}
func main() {
// 定义一个切片,数据类型为 student
var arr []student
arr = []student{
{100, "张飞", 20},
{101, "关羽", 25},
{102, "刘备", 30}}
fmt.Println(arr)
// 切片中增加数据
arr = append(arr,
student{103, "嫦娥", 16},
student{104, "貂蝉", 18},
)
fmt.Println(arr)
}
类型为结构体的map
package main
import "fmt"
type student struct {
name string
age int
}
func main() {
// 定义一个 map,数据类型为 student, 一对一
var M1 = make(map[int]student)
M1[101] = student{"张飞", 20}
M1[102] = student{"关羽", 25}
M1[103] = student{"刘备", 30}
fmt.Println(M1)
// 定义一个 map,数据类型为 []student, 一对多
var M2 = make(map[int][]student)
// 因为类型是切片,所以要使用 append 增加数据
M2[101] = append(M2[101], student{"曹操", 20}, student{"周瑜", 20})
M2[102] = append(M2[102], student{"张飞", 20}, student{"黄忠", 20})
fmt.Println(M2)
for k, v := range M2 {
for j, l := range v {
fmt.Println("key: ", k, "index: ", j, "Value:", l)
}
}
}
类型为结构体的函数、返回值
package main
import "fmt"
type stu struct {
name string
age int
}
// 结构体作为函数参数
func test1(s stu) {
fmt.Println(s.name)
fmt.Println(s.age)
}
// 结构体作为函数参数、返回值
func test2(s stu) []stu {
stus := []stu{}
stus = append(stus, s)
stus = append(stus, stu{"Tom", 18})
return stus
}
func main() {
S1 := stu{"Vic", 20}
test1(S1)
fmt.Println(test2(S1))
}
什么是指针
&
, 例如获取var ret string
的地址&ret
*
, 例如获取var ret *string
指针变量的值使用*ret
获取*int
、*string
等等// 指针声明
var var_name *var-type
var_name: 指针变量名
var-type: 指针类型
* :*号用于指定变量是作为一个指针
// 指向整形
var ptr *int
// 指向指针的指针变量值需要使用二个 * 号
var pptr **int
V = 200
// 指针 ptr 地址
ptr = &a
//指向指针 ptr 地址
pptr = &ptr
// 使用指针访问值
fmt.Printf("指针变量 *ptr = %d\n", *ptr )
fmt.Printf("指向指针的指针变量 **pptr = %d\n", **pptr)
创建指针的另一种方法 new() 操作
package main
import(
"fmt"
)
func main(){
var p *int // nil 定义空指针
fmt.Println(p)
// 为指针变量创建一块内存空间
// 在堆区创建空间
p1 = new(int) // new 创建好的空间值为数据类型的默认值 0
// 打印 p1 的值
fmt.Println(p1)
// 打印 p 指向空间的值
fmt.Println(*p1)
*p1 = 10
fmt.Println(*p1)
*p1 = 20
fmt.Println(*p1)
}
当传递一个参数值到被调用函数里是,实际上是copy
了这个值进行传递,当在被调用的函数里修改这个值,只是修改了copy
过的这个值, 原有的这个值是不会发生变化的
package main
import "fmt"
// 实现了 参数 + 1 的函数
func add(a int) int {
a = a + 1 // 改变了 a 的值
return a // 返回一个新的值
}
func main() {
x := 2
fmt.Println("x+1 : ", add(x)) // 调用 add() 函数,返回新值
fmt.Println("x : ", x) // 还是返回原有的值
}
上面的操作,虽然调用了 add
函数,并在函数中执行了a=a+1
操作,但是x
变量的值并没有发生变化,因为调用add
函数的时候,接收的参数其实是X
的copy
值,并不是x
本身
如果真的需要改变x
变量本身,就需要用到指针操作,需要add
函数知道x
变量所在地址,才能修改x
变量的值。
将x
地址&x
传递给函数,将函数的参数类型改为*int
,指针类型,这样就能在函数中修改X
变量的值了,此时参数仍然是copy
值,参数copy
的是一个指针
& 符号,获取变量的地址
- 符号,获取变量的值
package main
import "fmt"
// 实现了 参数 + 1 的函数,参数类型改成了 指针
func add(a *int) int {
*a = *a + 1 // 使用指针方式改变了 a 的值
return *a // 返回一个新的值
}
func main() {
x := 2
fmt.Println("x+1 : ", add(&x)) // 调用 add() 函数,使用&获取值
fmt.Println("x : ", x) // 使用指针传值,原有变量值已经被更改
}
指针作为函数参数
package main
import "fmt"
// 指针作为函数参数
func swap(a *int, b *int) {
// 实现 a 、b 值交互
*a, *b = *b, *a
}
func main() {
// 定义局部变量
a, b := 10, 20
fmt.Println("修改之前:", a, b)
// 调用函数用于交换值, 指针作为函数参数,必须是地址传值
swap(&a, &b)
fmt.Println("修改之后:", a, b)
}
数组指针
前面用数组作为函数参数,但是数组作为参数进行传递是值传递,如果想引用,可以使用数组指针
定义一个数组
package main
import "fmt"
func main() {
a := [5]int{1, 2, 3, 4, 5}
fmt.Println("修改之前:", a)
// 数组指针是让一个指针指向数组,然后通过操作该指针来操作该数组
// 定义指针指向数组
var p *[5]int
// 将指针与数组建立关系
p = &a
fmt.Println("修改之后:", *p)
// 通过指针操作数组
p[0] = 10
fmt.Println("修改之后:", *p)
fmt.Println("修改之前:", a)
// 循环打印数组指针中的数据
for index, value := range *p {
fmt.Printf("index: %d ; value: %d \n", index, value)
}
}
这时指针
p
, 指向了数组a
,对指针p
的操作实际就是对数组a
的操作,所以如果执行打印*p
,会输出数组a
中的值,也可以通过*p
结合下标将对应的值取出来进行修改,最终在main
函数中输出数组a
,发现其原始也已经修改
指针数组
数组指针是让一个指针指向数组,然后通过操作该指针来操作数组,
指针数组指的是一个数组中存储的都是指针(也就是地址),也就是一个存储了地址的数组
// 指针数组定义
var p [2]*int
var i int = 10
var j int = 20
// 将地址赋值给指针数组
p[0] = &i
p[1] = &j
// 打印查看
fmt.Println(p[0])
fmt.Println(p[1])
注意:指针数组的定义方式 与 数组指针的定义方式不一 ; 指针数组是将 “*” 放在了下标的后面
切片指针
切片指针作为函数参数是地址,形参可以改变实参的值
package main
import "fmt"
func test(s []int) {
s = append(s, 4, 5, 6)
}
// 使用切片指针作为函数参数,可以改变原切片的值
func test1(s *[]int) {
*s = append(*s, 4, 5, 6)
}
func main() {
s := []int{1, 2, 3}
fmt.Println(s)
test(s)
fmt.Println(s)
// 切片指针作为函数参数是地址,形参可以改变实参的值
test1(&s)
fmt.Println(s)
}
指针切片
package main
import (
"fmt"
)
func main() {
// 指针切片
var p []*int
a := 10
b := 20
c := 30
p = append(p, &a, &b)
p = append(p, &c)
fmt.Println(p)
for k, v := range p {
fmt.Println(k, *v)
}
}
结构体指针
package main
import "fmt"
type people struct {
name string
age int
}
func main() {
// 定义结构体变量并赋值
P := people{"Vic", 20}
fmt.Println(P)
// 定义结构体指针,指向变量的地址
var ptr *people
// 结构体指针指向变量的地址
ptr = &P
fmt.Println(*ptr)
// 通过结构体指针间接操作结构体成员信息
// (*ptr).name = "Skr"
// 通过指针可以直接操作结构体成员
ptr.name = "Skr"
fmt.Println(P)
}
结构体切片
package main
import "fmt"
type people struct {
name string
age int
}
func main() {
// 定义结构体切片
var p []people
// 结构体切片指针
ptr := &p
fmt.Printf("%T \n", ptr)
*ptr = append(*ptr, people{"小猪佩奇", 20})
*ptr = append(*ptr, people{"乔治", 30})
fmt.Println(p)
for i := 0; i < len(*ptr); i++ {
fmt.Println((*ptr)[i])
}
}
由于在Go
中没有class
的关键字,也就是其他语言经常在面向对象使用的方法,但是Go
通过
struct
和 method
方法组合来实现面向对象
go中的方法是一种特殊的函数,定义与 结构体struct
之上(与struct
关联、绑定),被称为struct
的接收者receiver
, 方法就是有接收者的函数
方法的语法
type myName struct{}
func(recv myName) my_method(args) return_type{}
func(recv *myName) my_method(args) return_type{}
- 参数说明
- myName : 定义的结构体
- recv : 接收该方法的结构体(recevier)
- my_method : 方法名称
- args : 参数
- return_type : 返回的值类型
## 从语法上可以看出,一个方法和一个函数非常相似,只是多了一个接收类型
方法的声明
package main
import "fmt"
// 定义结构体
type testA struct {
name string
}
// 为获取 name 属性定义一个方法
func (t *testA) GetName() string {
return t.name
}
func main() {
// 定义结构体变量
T := testA{"乔峰"}
// 调用方法
fmt.Println(T.GetName())
}
// 对上面语法的解析说明
1、func (t *testA) GetName() string {} 表示 testA 结构体有一个方法,方法名为 GetName
2、(t *testA) : 表示 GetName 方法是和 testA 类型绑定的
method方法
method
是附属在一个给定的类型上的,他的语法和函数的声明语法几乎一样,只是在func
后面增加了receiver
(也就是method
所依从的主体)
func (r ReceiverType) funcName(Parameters) (results)
接收者 函数名 正常的函数结构
method
的函数名字一模一样,但是如果接收者的类型不一样,那么method
就不一样method
里面可以访问接收者的字段method
通过.
访问,就像 struct
里面访问字段一样类型别名和方法的结合
package main
import "fmt"
// 面向对象 , 方法: 给某个类型绑定一个函数
// 给类型定义一个别名
type vic int
// V 叫接收者,接收者就是传递的一个参数
func (V vic) add(b vic) vic {
return V + b
}
func main() {
// 定义一个变量,作为传递参数
var k vic = 10
// 调用方法: 变量名.函数(所需参数)
v := k.add(100)
fmt.Println(v)
}
方法示例
定义 method
方法,计算长方形 和 圆 的面积
package main
import (
"fmt"
"math"
)
// 定义结构体长方形的 长、宽
type Rectangle struct {
width, height float64
}
// 定义圆的半径
type Circle struct {
redius float64
}
// 定义 method 方法,计算长方形 、 圆的面积
// 不同 struct 的 method 方法不同
func (r Rectangle) getArea() float64 {
return r.height * r.width
}
func (c Circle) getArea() float64 {
return c.redius * c.redius * math.Pi
}
func main() {
// 长方形传值
r1 := Rectangle{10, 20}
// 圆传值
c1 := Circle{35}
// 调用方法
fmt.Println("r1: ", r1.getArea())
fmt.Println("c1: ", c1.getArea())
}
上面的示例中,getArea()
分别属于Rectangle
和 Circle
,于是他们的 Receiver
就变成了Rectangle
和 Circle
ReceiverType
也可以是指针, 二者的差别在于,
Receiver
会对实例对象的内容发生操作Receiver
仅仅是以副本作为操作对象,并不对原实例对象发生操作package main
import "fmt"
type testA struct {
name string
}
type testB struct {
name string
}
// 取一个变量 a, a 就是接收者,接收者类型就是 struct testA ,getPrint 就是方法名, 参数在 getPrint() 的括号中定义
// a 正常传递
func (a testA) getPrint() {
a.name = "AA"
fmt.Println("A")
}
// 加上 * 代表指针传递
func (b *testB) getPrint() {
b.name = "BB"
fmt.Println("B")
}
func main() {
// 值类型不使用指针,在这个方法结束之后,值不会被修改
a1 := testA{}
a1.getPrint()
fmt.Println(a1.name)
// 使用指针,在这个方法结束之后,值会被修改
b1 := testB{}
b1.getPrint()
fmt.Println(b1.name)
}
上面的例子可以看出
method
的 receiver
是 *T
,可以在一个 T
类型的实例变量V
上面调用这个 method
,而不需要 &V
去调用这个 method
method
的 receiver
是 T
,可以在一个 T
类型的变量 P
上面调用这个method
,而不需要 P
去调用这个 method
method 方法继承
method
方法也是可以继承的,如果匿名字段实现了一个 method
方法 ,那么包涵这个匿名字段的 也能调用该method
方法
package main
import "fmt"
// 定义结构体 people
type people struct {
name string
age int
}
// 定义结构体 student 包涵 people
type student struct {
people // 匿名字段
school string
}
// 定义结构体 employee 包涵 people
type employee struct {
people // 匿名字段
company string
}
// 定义方法 people上面定义方法
func (p *people) sayHi() {
fmt.Printf("Hi, I'm %s, age %d .\n", p.name, p.age)
}
func main() {
//赋值
tom := student{people{"Tom", 20}, "北京大学"}
vic := employee{people{"Vic", 28}, "CTO"}
tom.sayHi()
vic.sayHi()
}
method 方法重写
上面的例子,如果employee
想实现自己的sayHi
,可以在employee
定义一个方法,重写匿名字段的方法
package main
import "fmt"
// 定义结构体 people
type people struct {
name string
age int
}
// 定义结构体 student 包涵 people
type student struct {
people // 匿名字段
school string
}
// 定义结构体 employee 包涵 people
type employee struct {
people // 匿名字段
company string
}
// 定义方法 people上面定义方法
func (p *people) sayHi() {
fmt.Printf("Hi, I'm %s, age %d .\n", p.name, p.age)
}
// 定义 employee 的方法重写 people 的方法
func (e *employee) sayHi() {
fmt.Printf("Hi, I'm %s, my work %s, age %d .\n", e.name, e.company, e.age)
}
func main() {
//赋值
tom := &student{people{"Tom", 20}, "北京大学"}
vic := employee{people{"Vic", 28}, "CTO"}
tom.sayHi()
vic.sayHi()
}
接口的声明
接口是一种约定,是一个抽象的类型,它只有一组接口方法,我们并不知道它内部的实现;
虽然我们不知道接口是什么,但是我们可以知道利用它提供的方法做什么。
interface 类型定义了一组方法,如果某个对象实现了某个接口的所有方法,则此对象就实现了此接口
注意:
接口本身不能创建实例,但是可以指向一个实现了该接口的自定义类型的变量
一个自定义类型可以实现多个接口
接口中不能有任何变量
一个接口(A 接口)可以继承多个别的接口(B、C 接口),此时要实现A接口,也必须将B、C接口的方法也全部实现
interface 类型默认是一个指针(引用类型),如果没有对 interface 初始化就使用,那么就会输出 nil
空接口 interface{} 没有任何方法,所以所有类型都实现了空接口
示例
package main
import "fmt"
// 1.定义接口
type usb interface {
// 声明方法列表
Read()
Write()
}
// 2. 创建对象
type mobile struct {
name string
speed int
}
type computer struct {
name string
speed int
}
// 实现方法
func (m *mobile) Read() {
fmt.Printf("%s 正在读取数据速度为:%d M\n", m.name, m.speed)
}
func (m *mobile) Write() {
fmt.Printf("%s 正在写入取数据速度为:%d M\n", m.name, m.speed)
}
func (c *computer) Read() {
fmt.Printf("%s 正在读取数据速度为:%d M\n", c.name, c.speed)
}
func (c *computer) Write() {
fmt.Printf("%s 正在写入取数据速度为:%d M\n", c.name, c.speed)
}
func main() {
m := mobile{"华为荣耀", 20}
// 定义接口变量 u
var u usb
// u 变量能存储 moblie 信息
u = &m
u.Read()
u.Write()
// u 变量也能能存储 computer 信息
c := computer{"MacBook", 100}
u = &c
u.Read()
u.Write()
}
接口的继承
package main
import "fmt"
// 1.定义接口 子集
type humaner interface {
Sayhi()
}
// 超集
type personer interface {
humaner // 子集
Sing(string)
}
// 2. 定义对象
type student struct {
name string
age int
sex string
}
// 3.定义方法
func (s *student) Sayhi() {
fmt.Printf("大家好,我叫 %s, 今年 %d 岁了,是 %s 生。\n", s.name, s.age, s.sex)
}
func (s *student) Sing(name string) {
fmt.Printf("大家好,我叫 %s,给大家唱首歌 %s\n", s.name, name)
}
func main() {
var h humaner
h = &student{"王菲", 18, "女"}
h.Sayhi()
var p personer
p = &student{"林忆莲", 30, "女"}
// 继承子集的方法
p.Sayhi()
p.Sing("霸王别姬")
}
接口实现多态
多态:同一件事情由于条件不同而产生的结果不同;
由于Go语言的结构体不能体现相互转换,所以没有结构体的多态;
只有基于接口的多态
package main
import "fmt"
// 1. 定义接口
type humaner interface {
sayHello()
}
// 3.定义结构体对象
type person struct {
name string
sex string
age int
}
type student struct {
person
score int
}
type teacher struct {
person
subject string
}
// 3.定义方法
func (s *student) sayHello() {
fmt.Printf("我叫 %s, 是一名学生, 今年 %d 岁了,是 %s 生, 成绩是 %d\n", s.name, s.age, s.sex, s.score)
}
func (t *teacher) sayHello() {
fmt.Printf("我叫 %s, 是一名教师, 今年 %d 岁了,是 %s 生, 学科是 %s\n", t.name, t.age, t.sex, t.subject)
}
// 4. 实现多态
// 多态是将接口类型作为函数参数, 多态实现了接口的统一处理
func sayHello(h humaner) {
h.sayHello()
}
func main() {
var h humaner
h = &student{person{"郭靖", "男", 16}, 99} //接口不能实例化,只能对接口的结构体实例化
sayHello(h) //多态,条件不同结果不同
h = &teacher{person{"欧阳锋", "男", 36}, "毒师"} //接口不能实例化,只能对接口的结构体实例化
sayHello(h) //多态,条件不同结果不同
}
空接口的定义和使用
空interface(interface{})
不包含任何的方法,正因为如此,所有的类型都实现了空接口,
空interface
在我们需要存储任意类型数值的时候特别有用,因为它可以存储任意类型的数值
package main
import (
"fmt"
)
// 如果一个函数把 interface{} 作为参数,那么它可以接受任意类型的值作为参数
// 如果一个函数返回值 interface{},那么也就可以返回任意类型的值
func main() {
// 定义 空接口 a
var a interface{}
// a 可以存储任意类型的值
a = 100
fmt.Println(a)
a = "Hi"
fmt.Println(a)
// 空接口切片
var k []interface{}
k = append(k, "21", "乔峰", "vic")
for v := 0; v < len(k); v++ {
fmt.Println(k[v])
}
for _, i := range k {
fmt.Println(i)
}
}
在Go语言中可以使用 对象.(指定的类型)
判断该对象是否是指定的类型
package main
import "fmt"
func main() {
// 定义空接口切片并初始化, 通过 make 创建的切片可以指定切片的大小
arr := make([]interface{}, 4)
arr[0] = 123
arr[1] = 12.3
arr[2] = "小龙女"
arr[3] = []int{1, 2, 3}
// 对切片进行类型断言
for _, k := range arr {
if data, ok := k.(int); ok {
fmt.Println("整型数据", data)
} else if data, ok := k.(float64); ok {
fmt.Println("浮点型数据", data)
} else if data, ok := k.(string); ok {
fmt.Println("字符串数据", data)
} else if data, ok := k.([]int); ok {
fmt.Println("切片数据", data)
}
}
}
所谓异常:就是当Go检测到一个错误时,程序就无法正常执行了,反而出现了一些所谓错误的提示,这就是所谓的异常。所以为了保证程序的健壮性,要对异常的信息进行处理。
使用error 接口返回错误信息
package main
import (
"errors"
"fmt"
)
// 编辑时异常
// 编译时异常
// 运行时异常
func test(a, b int) (ret int, err error) {
// 0 不能作为除数
if b == 0 {
err = errors.New("0 不能作为除数")
return
} else {
ret = a / b
return
}
}
func main() {
// 赋值
ret, err := test(10, 0)
// err 不等于空表示有错误信息
if err != nil {
fmt.Println(err.Error())
} else {
fmt.Println(ret)
}
}
panic
函数返回的是让程序崩溃的错误
一般而言,当panic
异常发生时,程序会中断运行,随后程序崩溃并输出日志信息
package main
import (
"fmt"
)
// 编辑时异常
// 编译时异常
// 运行时异常
func test(a, b int) (ret int) {
ret = a / b
return
}
func main() {
// 赋值
ret := test(10, 0)
fmt.Println(ret)
}
直接调用panci使程序崩溃终止运行
package main
import (
"fmt"
)
func t1() {
fmt.Println("T1")
}
func t2() {
// fmt.Println("T2")
// 可以在程序中直接调用 panic,调用后程序会终止运行
panic("T2")
}
func t3() {
fmt.Println("T3")
}
func main() {
t1()
t2()
t3()
}
defer 用于注册延迟调用
这些调用直到return前才被执行
多个 defer 语句,按照先进后出的方式执行
defer 语句中的变量,在defer声明时就决定了
用途
package main
import (
"fmt"
)
func TryDefer() {
defer fmt.Println(1)
defer fmt.Println(2)
fmt.Println(3)
}
func main() {
TryDefer()
}
运行时panic
异常一旦被引用就会导致程序崩溃,这不是我们愿意看到的,Go语言提供了专用于拦截运行时 panic
的内建函数 recover
,它可以是当前程序从运行时 panic 的状态中恢复并重新获得流程控制权
注意:recover
只有在 defer
调用的函数中才有效
利用recover处理panic指令,defer 必须放在 panic 之前定义,另外 recover 只有在 defer 调用的函数中才有效。否则当panic时,recover无法捕获到panic,无法防止panic扩散。
recover 处理异常后,逻辑并不会恢复到 panic 那个点去,函数跑到 defer 之后的那个点。
多个 defer 会形成 defer 栈,后定义的 defer 语句会被最先调用。
使用 defer + recover 来捕获和处理异常, 使程序不终止继续运行
package main
import (
"fmt"
)
func test() {
// 使用 defer + recover 来捕获和处理异常
// 定义匿名函数
defer func() {
err := recover() // 内置函数 recover() ,可以捕获到异常
if err != nil {
fmt.Println("err =>", err)
}
}()
num1 := 10
num2 := 0
res := num1 / num2
fmt.Println("res=", res)
}
func main() {
// 测试
test()
fmt.Println("main() 下面的代码")
}
进程和线程
CPU
调度和分派的基本单位,是比进程更小的能独立运行的基本单位并发和并行
CPU
上运行就是并发CPU
上运行就行并行协程和线程
goroutine
goroutine
其实就行协程,但是它比线程更小,十几个 goroutine
可能体现在底层就是五六个线程。
当需要某个任务并发执行的时候,只需要把任务封装成一个函数,开启多个 goroutine
去执行这个函数就可以了。
Goroutine 是通过Go 的 runtime 管理的一个线程管理器,goroutine
通过 go
关键字实现,其实就是一个函数(普通函数、匿名函数),只需要在运行的时候前面加上一个 go
关键字,这样就是创建了一个 goroutine
一个 goroutine
必定对应一个函数,但是可以创建多个goroutine
去执行相同的函数
package main
import (
"fmt"
"time"
)
func Hello() {
fmt.Println("Hello Goroutine。。。")
}
func main() {
go Hello() // 启用 goroutine 执行 Hello函数
fmt.Println("Main 入口...")
time.Sleep(time.Second * 3)
go func() {
fmt.Println("匿名函数的Goroutine...")
}()
time.Sleep(time.Second * 3)
go Hello() // 再启动一个 goroutine,指向 Hello函数
}
单纯的将函数并发是没有意义的,函数与函数之间需要交换数据才能体现并发执行函数的意义。
如果说 goroutine 是go程序并发的执行体,那么 channel 就是他们之间的连接管道,channel 可以让一个goroutine 发送特定的值到另外一个 goroutine上的通信机制
channel类型
go提供了一种称为通道的机制,用于在goroutine
之间共享数据,当执行 goroutine
并发活动的时候,需要在groutine
之间共享资源或者数据,通道充当goroutine
之间的管道并提供一种机制来保证同步交换。
需要在声明通道时指定数据模型
,共享内置、命名、结构和引用类型的值和指针,
数据交换的行为,有二种类型的通道
goroutine
之间的同步通道由 make
函数创建,该函数指定chan
关键字和通道元素的类型
创建 channel 语法
// 声明一个传递整型的通道
var ch1 chan int
// 声明一个传递布尔类型的通道
var ch2 chan bool
// 声明一个传递 int 切片类型的通道
var ch3 chan []int
// 声明一个传递 接口类型的通道
var ch4 chan interface{}
// 整型无缓冲通道
stInt := make(chan int)
// 整型有缓存通道
StInt := make(chan int, 10)
// 由内置函数 make 创建,make的第一个参数需要关键字 chan,然后是通道运行交换的数据类型
将值发送到通道 使用 <-
// 字符串缓存通道
str := make(chan string, 10)
// 通过管道发送字符串, 把 Tom 字符串发送到channel str中
str <- "Tom"
// 一个包涵 10 个值的带缓存区的字符串类型的 groutine 通道,然后通过管道发送字符串 “Tom”
从管道接收值
// 从管道接收字符串,从 str 中接收数据,并赋值给 ret
ret := <-str
// <- 运算符附加到通道变量的左侧,以接收来自通道的值
// 关闭通道
close(str)
无缓冲通道
在无缓冲通道中,如果二个groutine
没有在同一时刻准备好,则通道会让执行其各自发送和接收操作的groutine
等待,同步是通道上发送和接收之间交互的基础,没有另一个则就不能发送
package main
import "fmt"
func NoCache(c chan string) {
ret := <-c // 从 str 接收值并赋值给变量 ret
fmt.Println("无缓冲的channel....", ret)
}
func main() {
// 定义一个无缓冲的 channel str
str := make(chan string)
go NoCache(str) // 启用 goroutine 从通道 str 中接收值
str <- "北京" // 发送值到 str 管道中
fmt.Println("执行结束")
close(str)
}
缓存通道
在缓存通道中,有能力接收到一个或者多个值之前保存它们,在这种类型的通道中,无需强制 groutine 在同一时刻准备好执行的接收和发送,
package main
import "fmt"
func main() {
// 定义容量为 1 有缓存的 channel str,
str := make(chan string, 1)
go func() {
str <- "北京" // 发送值到 str 管道中
}() // 启用 goroutine 从通道 str 中接收值
ret := <-str // 把 channel 管道的值 赋值给 ret 变量
fmt.Println("执行结束,", ret)
close(str)
}
for range 从channel通道循环取值
package main
import "fmt"
func main() {
chInt := make(chan int)
chStr := make(chan int)
// 开启 goroutine 循环 发送值到 chInt 中
go func() {
for i := 0; i < 10; i++ {
chInt <- i // 取 i值发送给 chInt
}
close(chInt) // 关闭管道 chInt
}()
// 开启 goroutine 循环从 chInt 中接收值,并将该值发送到 chStr 中
go func() {
for {
i, ok := <-chInt // channel 通道关闭取值 ok = false,停止循环
if !ok {
break
}
chStr <- i // 取 i 值 发送给 chStr
}
close(chStr) // 关闭管道 chStr
}()
// 循环从 chStr 管道中接收值打印
for i := range chStr {
fmt.Println("打印:", i)
}
}
使用 sync.WaitGroup
来实现 goroutine
的同步,
多次执行代码,发现每次打印的数字顺序都不一样,是因为 goroutine
打印是随机的调度的
package main
import (
"fmt"
"sync"
)
// 定义个 wg 变量,类型为 sync.WaitGroup
var wg sync.WaitGroup
func Hello(a int) {
defer wg.Done() // goroutine 结束就登记 -1
fmt.Println("Hello: ", a) // 打印 a 的值
}
func main() {
for i := 0; i < 10; i++ {
go Hello(i) // 启动一个 goroutine
wg.Add(1) // 启动一个 goroutine 就登记 +1
}
wg.Wait() // 等待所有登记的 goroutine 都结束
fmt.Println("main Done")
}
Go运行时的调度器使用GOMAXPROCS
参数来确定使用多少个OS
线程来同时执行代码,默认是机器上的CPU
核心数。
通过runtime.GOMAXPROCS()
函数设置当前程序并发时占用的CPU
逻辑核心数
package main
import (
"fmt"
"runtime"
)
func main() {
// 返回本地机器的逻辑 CPU 个数
cpuNum := runtime.NumCPU()
fmt.Println(cpuNum)
// 设置使用 CPU个数, GOMAXPROCS 设置
runtime.GOMAXPROCS(cpuNum - 4)
fmt.Println("ok")
}
设置CPU 核心数,并行执行任务
package main
import (
"fmt"
"runtime"
"sync"
)
var wg sync.WaitGroup
func Test1() {
defer wg.Done()
fmt.Println("Test1....")
}
func Test2() {
defer wg.Done()
fmt.Println("...Test2")
}
func main() {
// 设置 CPU个数
cpuNum := runtime.NumCPU()
runtime.GOMAXPROCS(cpuNum - 2)
wg.Add(1) // 启动一个 goroutine 就登记 +1
go Test1()
go Test2()
wg.Wait() // 等待所有的 goroutine 执行结束
fmt.Println("Main Done")
}
select
类似 switch
语句,有一系列的 case
分支和一个默认的分支,每个 case
对应一个通道的通信(接收或者发送)过程,select
会一直等待,直到某个 case
的通信操作完成
select
提高代码的可读性channel
的发送/接收操作case
同时满足,select
会随机选择一个package main
import (
"fmt"
"time"
)
func main() {
chInt := make(chan int, 1)
chStr := make(chan string, 1)
go func() {
chInt <- 100
time.Sleep(time.Second * 5)
fmt.Println("intType")
}()
go func() {
chStr <- "北京"
time.Sleep(time.Second * 5)
fmt.Println("stingType")
}()
select {
case c := <-chInt:
fmt.Println("Test1 :", c)
case c := <-chStr:
fmt.Println("Test2 : ", c)
default:
fmt.Println("default.....")
}
}
Ticker 时间到了多次执行
package main
import (
"fmt"
"time"
)
func Mon1() {
fmt.Println("每 10s 执行一次")
}
func Mon2() {
fmt.Println("每 1min 执行一次")
}
func main() {
second := time.Tick(time.Second * 10)
minute := time.Tick(time.Minute * 1)
for {
select {
case <-second:
Mon1()
case <-minute:
Mon2()
}
}
}
每 五分钟触发一次
package main
import (
"fmt"
"time"
)
func main() {
minute := time.NewTicker(time.Minute * 5)
for t := range minute.C {
fmt.Println("打印", t)
}
}
文件操作的大多数函数都是在os包里面
创建名称为 name 的目录,权限设置是perm,eg:0777
func Mkdir(name string, perm FileMode) error
根据 path 创建多级子目录,eg: /home/vic
func MkdirAll(path string, perm FileMode) error
删除名称为 name 的目录,当目录下有文件或者其他目录的时候会报错
func Remove(name string) error
根据 path 删除多级子目录,如果path 是单个名称,那么该目录下面的子目录全部删除
func RemoveAll(path string) error
示例
package main
import (
"fmt"
"os"
)
func main() {
// 创建目录,等于 mkdir
os.Mkdir("victor", 0755)
// 创建目录,等于 mkdir -p
os.MkdirAll("Victor/vic", 0777)
// 删除目录
err := os.Remove("Victor")
if err != nil {
fmt.Println(err)
}
// 递归删除目录
os.RemoveAll("Victor")
}
将数据存储到文件之前,先要创建文件,Go
语言中提供了一个 Create()
函数专门创建文件,
该函数在创建文件时,首先会判断要创建的文件是否存储,如果不存在则创建,如果存在会将文件中已有的数据清空。
同时,当文件创建成功后,该文件会默认的打开,所以不用再执行打开操作,可以直接向该文件中写入数据
创建文件的步骤
1、导入
os
包,创建文件,2、指定创建的文件存放路径以及文件名
3、执行
Create()
函数,进行文件创建4、关闭文件
创建文件函数
// 根据提供的文件名创建新的文件,返回一个文件对象,默认权限是 06666 ,返回的文件是可读写的
func Create(name string) (file *File, err Error)
示例:
package main
import (
"fmt"
"os"
)
func main() {
// 创建文件,路径分为绝对路径 和相对路径
fp, err := os.Create("./test.txt")
if err != nil {
fmt.Println("创建文件失败")
return // 如果 return 出现在主函数中 表示程序的结束
}
// 延迟调用,关闭文件句柄
defer fp.Close()
fmt.Println("文件创建成功")
}
写入数据包涵打开文件和写数据
打开文件
// 该方法打开一个名称为 name 的文件,但是是只读的方式,内部实现其实是调用了OpenFile
func Open(name string) (file *File, err Error)
// 打开名称为name 的文件,flag是打开的方式,只读、读写, perm 是权限
func OpenFIle(name string, flag int, perm uint32) (file *File, err Error)
写文件
// 写入 byte 类型的信息到文件
func (file *File) Write(b []byte) (n int, err Error)
// 在指定的位置开始写入 byte 类型的信息
func (file *File) WriteAt(b []byte, off int64) (n int, err Error)
// 写入 string 信息到文件
func (file *File) WriteString(s string) (ret int, err Error)
写文件示例
package main
import (
"fmt"
"os"
)
func main() {
// 创建文件,路径分为绝对路径 和相对路径
fp, err := os.Create("./test.txt")
if err != nil {
fmt.Println("创建文件失败")
return // 如果 return 出现在主函数中 表示程序的结束
}
// 延迟调用,关闭文件句柄
defer fp.Close()
fmt.Println("文件创建成功")
// 将字符串转换成字符切片写入到文件中。字符串和字符切片允许相互转换
str := "天龙八部\n"
b := []byte(str)
fp.Write(b)
// 字符串的形式直接写入
fp.WriteString("后羿射日\n")
}
一般都使用OpenFile
函数以追加的方式打开一个文件写入
OpenFile
函数有三个参数:文件名、一个或多个标注、使用的文件权限 os.O_CREATE 创建,如果指定文件不存在,就创建该文件
os.O_RDONLY 只读
os.O_WRONLY 只写
os.O_TRUNC 截断,如果指定文件已经存在,就将该文件的长度截为 0
package main
import (
"fmt"
"os"
)
func main() {
// 最佳的方式打开文件,如果不存在就创建,打开的模式可读可写,权限 0644
fp, err := os.OpenFile("./writeFile.txt", os.O_APPEND|os.O_CREATE|os.O_RDWR, 0644)
if err != nil {
fmt.Println("创建文件失败")
return
}
defer fp.Close()
// 写数据
fp.WriteString("Go语言基础\n")
str := "2021年五月"
b := []byte(str)
fp.Write(b)
}
文件是指向os.file
类型的指针来表示,也叫作句柄
标准输入 os.Stdin
和 标准输出 os.Stdout
的类型都是 os.File
在任何计算机设备中,文件必须是对象
读文件函数
// 读取文件到 b 中 func (file *File) Read(b []byte)(n int, err Error) // 从off开始读取数据到 b 中 func (file *File) ReadAt(b []byte, off int64) (n int, err Error)
读取文件示例
f1,_ : os.ReadFile("./test.txt")
fmt.Println(string(f1[:]))
package main
import (
"bufio"
"fmt"
"io/ioutil"
"os"
)
func main() {
f1 := "./writeFile.txt"
// 将整个文件的内容读取到一个字节切片中
buf, err := ioutil.ReadFile(f1)
if err != nil {
fmt.Fprintf(os.Stderr, "读取文件失败: %s\n", err)
return
}
fmt.Printf("%s\n", string(buf))
// 读取文件并一行行打印
f2 := "./test.txt"
file, err := os.Open(f2)
if err != nil {
fmt.Fprintf(os.Stderr, "读取文件失败: %s\n", err)
}
scanner := bufio.NewScanner(file)
for scanner.Scan() {
fmt.Println(scanner.Text())
}
// 拷贝源文件内容到新文件
var (
oldFile = "./test.txt"
newFile = "./test2.txt"
)
// 将整个文件读取到一个切片中
tmpFile, err := ioutil.ReadFile(oldFile)
if err != nil {
fmt.Fprintf(os.Stderr, "读取文件失败:%s", err)
}
// 将读取到的文件内容重新写入到新文件中
err = ioutil.WriteFile(newFile, tmpFile, 0644)
if err != nil {
fmt.Fprintf(os.Stderr, "写入文件失败:%s", err)
}
}
https://golang.org/pkg/strings/
对字符 串进行分割、连接、转换等操作
package main
import (
"fmt"
"strings"
)
func main() {
str1 := "hello, golang is dog"
// 在字符串中查找另外一个字符串是否出现,用于 模糊查找 Contains
value := strings.Contains(str1, "go")
if value {
fmt.Println("找到")
} else {
fmt.Println("未找到")
}
// 判断是否以某个字符串开头 HasPrefix
result := strings.HasPrefix(str1, "Hi")
if !result {
// 判断是否是 Hi 开头,如果不是就加上
str1 := fmt.Sprintf("Hi, %s", str1)
fmt.Println(str1)
}
// 判断是否以某个字符串开头 HasSuffix
ret := strings.HasSuffix(str1, "Night")
if !ret {
// 判断是否是 Hi 开头,如果不是就加上
str1 := fmt.Sprintf("%s ,Night", str1)
fmt.Println(str1)
}
// 字符串拼接 Join
slice1 := []string{"1313", "3213", "2344", "3242"}
// 将字符串切片 拼接成字符串
str2 := strings.Join(slice1, "-")
fmt.Println(str2)
// 字符串替换 Replace
// 在字符串中把 旧字符串 替换成 新字符串,n 表示替换的次数,小于 0 表示全部替换
fmt.Println(strings.Replace(str1, "golang", "Java", -1))
// 字符串分割 split
// 切分字符串返回字符串的切片
fmt.Println(strings.Split(str1, ","))
// 字符串全部全换成小写 ToLower
fmt.Println(strings.ToLower(str1))
// 字符串全部全换成大写 ToUpper
fmt.Println(strings.ToUpper(str1))
// 去掉字符串首尾的空白字符 TrimSpace
fmt.Println(strings.TrimSpace(str1))
// 统计字符串出现的次数 Count
fmt.Println(strings.Count(str1, "golang"))
// 字符串翻转
// 先转换成字节切片
byteArray := []byte(str1)
s1 := ""
for i := len(byteArray) - 1; i >= 0; i-- {
// 转换成字符串
s1 += string(byteArray[i])
}
fmt.Println(s1)
}
整形之间的转换
/*
整形之间的转换
建议把 低类型 转换成 高类型
*/
var a1 int8 = 20
var b1 int16 = 10
fmt.Println("sum:", int16(a1)+b1)
浮点型之间的转换
/*
浮点型之间的转换
建议把 低类型 转换成 高类型
*/
var a2 float32 = 10
var b2 float64 = 20
fmt.Println("浮点型:", float64(a2)+b2)
整形和浮点型之间的转换
/*
整形和浮点型之间的转换
建议把整形转换成浮点型
*/
var a3 float32 = 10.25
var b3 int = 20
fmt.Println("整形和浮点型之间的转换:", a3+float32(b3))
其他类型换行字符串类型,使用 strconv
包进行类型转换
将 bool 类型转换为字符串
// 将 bool 类型转换为字符串
var str string
str = strconv.FormatBool(false)
fmt.Println(str)
将整形转换成字符串
/*
FormatInt 接收二个参数
参数1:int64 类型的值
参数2:int 类型的进制;二进制 、八进制、十进制、十六进制
*/
var str string
str = strconv.FormatInt(21123, 10)
fmt.Println("str = ", str)
// 第二种方式
str = strconv.Itoa(21123) // 默认十进制
fmt.Println("str = ", str)
将浮点数转换成字符串
/*
FormatInt 接收四个参数
参数1:要转换的值
参数2:格式化类型 'f'
参数3:保留的小数点(-1 不对小数点格式化)
参数4:格式化的类型,32 或者 64
*/
s := strconv.FormatFloat(1.3264, 'f', 2, 32)
fmt.Println(s)
将字符串转换成整形
/*
string转换成整形
PaeseInt
参数1:string数据
参数2:进制
参数3:位数 16 32 64
*/
str := "golang"
ret, _ := strconv.ParseInt(str, 10, 64)
fmt.Println(ret)
// 第二种方式
ret, _ := strconv.Atoi(str) // 默认十进制
fmt.Println("str = ", ret)
将字符串转换成浮点型
/*
string转换成浮点型
ParseFloat
参数1:string数据
参数2:位数 32 64
*/
str5 := "vic"
num1, _ := strconv.ParseFloat(str5, 64)
fmt.Printf("值:%v 类型:%T \n", num1, num1)
package main
import (
"fmt"
"os"
)
//https://golang.org/pkg/os/
func main() {
// 终止进程
// os.Exit(1)
// 获取全部环境变量
env := os.Environ()
//获取当前工作目录
v,_ := os.Getwd()
fmt.Println(v)
//将当前工作目录更改为目录("/Users/victor/gitlab")
//成功切换目录,返回nil,否则报错 chdir 111: no such file or directory
v1 := os.Chdir("111")
fmt.Println(v1)
//更改文件的权限(读写执行,分为三类:all-group-owner)
v2 :=os.Chmod("/Users/victor/go/121.txt", 755)
fmt.Println(v2)
//更改文件拥有者
v3 :=os.Chown("/Users/victor/go/121.txt", 74, 74)
fmt.Println(v3)
//获取主机名
v4,_ := os.Hostname()
fmt.Println(v4)
//创建目录及文件
os.MkdirAll("/Users/victor/go/test", os.ModePerm)
os.Chdir("/Users/victor/go/test")
os.Create("file.txt")
v5,_ := os.Getwd()
fmt.Println(v5)
//删除文件或者目录,如果不存在remove file1.txt: no such file or directory
// func Remove(name string) error
//删除目录以及其子目录和文件,如果path不存在的话,返回nil
// func RemoveAll(path string) error
os.Getwd()
os.Chdir("/Users/victor/go/test")
v6 := os.Remove("file1.txt")
fmt.Println(v6)
//重命名文件,如果oldpath不存在,则报错no such file or directory
// func Rename(oldpath, newpath string) error
os.Chdir("/Users/victor/go/test")
v7 :=os.Rename("file1.txt", "newfile.txt")
fmt.Println(v7)
}
利用os.Stat 判断文件是否存在
ret, err := os.Stat("main.go")
if err != nil {
fmt.Println(err)
}
fmt.Println(ret) // 获取文件的全部信息
fmt.Println(ret.Name())
fmt.Println(ret.Size())
fmt.Println(ret.IsDir())
fmt.Println(ret.Mode())
}
os.Args 获取命令行参数,是一个切片
package main
import (
"fmt"
"os"
)
func main() {
// (os.Args[0]) // args 0 第一个片 是文件路径
// (os.Args[1]) // 1 第二个参数是, 用户输入的参数 例如 go run osdemo01.go 123
fmt.Println("命令行的参数有:", len(os.Args))
// 遍历 os.Args 切片,就可以得到所有的命令行输入的参数值
for k, v := range os.Args {
fmt.Printf("Args[%v] = %v \n", k, v)
}
}
// 执行结果
╰─$ go build -o args_demo main.go
╰─$ ./args_demo 31 24 vic
命令行的参数有: 4
Args[0] = ./args_demo
Args[1] = 31
Args[2] = 24
Args[3] = vic
封装了一些实用的I/O函数
https://pkg.go.dev/io/ioutil#pkg-functions
名称 | 作用 |
---|---|
ReadAll | 读取数据,返回读取到的字节切片(slice) |
ReadDir | 读取目录,返回目录入口数的数组[]os.FileInfo |
ReadFile | 读取文件,返回文件内容字节(slice) |
WriteFile | 根据文件路径写入字节(slice) |
TempDir | 在一个目录中创建指定前缀名的临时目录,返回新临时目录的路径 |
TempFile | 在一个目录中创建指定前缀的临时文件,返回os.File |
ReadAll读取接收到的数据
package main
import (
"fmt"
"io/ioutil"
"strings"
)
func main() {
r := strings.NewReader("Golang is a very good language")
b, _ := ioutil.ReadAll(r)
fmt.Printf("%s \n", b)
fmt.Println(string(b))
}
示例
package main
import (
"fmt"
"io/ioutil"
)
func main() {
// ReadDir 读取目录,返回目录下的数组切片
fs, _ := ioutil.ReadDir(".")
// 循环打印文件名
for _, i := range fs {
fmt.Println(i.Name())
}
// ReadFile 读取文件内容
content, _ := ioutil.ReadFile("main.go")
fmt.Println(string(content))
// WriteFile 将数据写入到传递的文件命文件中,如果不存在就创建
// 用byte接收数据
msg := []byte("Hello, Golang language!")
err := ioutil.WriteFile("hello.txt", msg, 0644)
if err != nil {
fmt.Println(err)
}
// TempFile 创建临时文件, 文件名是通过 pattern 并在末尾添加一个随机字符串来生成的
tmpFile, _ := ioutil.TempFile(".", "ccg")
// defer os.RemoveAll(tmpFile.Name()) // 最后清理删除临时文件
// 写入 msg 数据
if _, err := tmpFile.Write(msg); err != nil {
fmt.Println(err)
}
}
bufio 包实现了又缓冲的I/O
,它包装了io.Reader
和io.Writer
接口对象
默认缓存defaultBufSize = 4096
Reader实现了给一个io.Reader接口对象附加缓冲
NewReaderSize创建一个具有默认大小缓冲、从r读取的*Reader。NewReader相当于NewReaderSize(rd, 4096)
读操作
package main
import (
"bufio"
"fmt"
"strings"
)
func main() {
// 创建一个 newReader 数据,可以读取文件
str := "hello golang language word\n"
//f, _ := os.Open("hello.txt")
str1 := strings.NewReader(str)
// 使用 bufio.NewReader 进行封装已经存在的数据
str2 := bufio.NewReader(str1)
// 以字符串的方式读取,以 '\n' 结尾,或者读到文件尾
s, _ := str2.ReadString('\n')
fmt.Println(s)
// Reset丢弃缓冲中的数据, 重写内容
str3 := "Golang language"
str1.Reset(str3) // 将 str3 重写入到 缓存中
s, _ = str2.ReadString('\n')
fmt.Println(s)
// ReadSlice读取直到第一次遇到delim字节,返回缓冲里的包含已读取的数据和delim字节的切片
s1 := strings.NewReader("hello Go java")
s2 := bufio.NewReader(s1)
s3, _ := s2.ReadSlice(' ')
fmt.Println(string(s3))
s3, _ = s2.ReadSlice(' ')
fmt.Println(string(s3))
}
写操作
package main
import (
"bufio"
"bytes"
"fmt"
"os"
)
func main() {
f, _ := os.OpenFile("hello.txt", os.O_RDWR, 0644) // openfile 封装了 Reader Writer
fw := bufio.NewWriter(f) // 创建一个bufio 写的缓存取,封装 f
fw.WriteString("\n Go go go word") // 写入数据
fw.Flush() // 刷新缓冲区
// 利用 bytes 进行封装
b := bytes.NewBuffer(make([]byte, 0))
bw := bufio.NewWriter(b)
bw.WriteString("Hello go")
bw.Flush() // 刷新缓冲区
fmt.Println(b)
}
Scanner类型提供了方便的读取数据的接口
将文件分割为行、字节、unicode码值、空白分隔的word
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
//s := strings.NewReader("ABC DE FG") // 定义一个Reader,也可以是文件
f, _ := os.OpenFile("hello.txt", os.O_RDWR, 0644) // openfile 封装了 Reader Writer
fs := bufio.NewScanner(f) // 创建一个bufio 缓存区,封装 f
fs.Split(bufio.ScanWords) // 以 单词进行分割,或者bufio.ScanRunes、
for fs.Scan() {
fmt.Println(fs.Text())
}
}
bytes包提供了对字节切片进行读写操作的一系列函数,基本处理函数、比较函数、后缀检查函数、索引函数、分割函数、大小写处理函数、子切片处理函数,和 strings 类似
package main
import (
"bytes"
"fmt"
)
func main() {
var a int = 100
var b byte
b = byte(a) // 强制类型转换
fmt.Println(b)
fmt.Printf("%T \n", b)
s := "hello world"
sb := []byte(s) // 将字符串转换成字节切片
sb1 := []byte("ll")
// sb1 是否在 sb 里面
t := bytes.Contains(sb, sb1)
fmt.Println(t)
// bytes.Runes 可以统计有多少 汉字 或者字节
sh := "欢迎北京"
sy := []byte(sh)
sy1 := bytes.Runes(sy)
fmt.Println(len(sh))
fmt.Println(len(sy1))
// join 拼接 切片
sj1 := [][]byte{[]byte("你好"), []byte("上海")}
sj2 := []byte(",")
sj := bytes.Join(sj1, sj2)
fmt.Println(string(sj))
}
Reader
package main
import (
"bytes"
"fmt"
)
func main() {
data := "hello world"
//通过[]byte创建Reader
ret := bytes.NewReader([]byte(data))
// 初始化一个切片,4个容量
buf := make([]byte, 4)
for {
// 读取数据
n, err := ret.Read(buf)
// 读取不到数据,停止
if err != nil {
break
}
fmt.Println(string(buf[:n]))
}
}