目录
语法与Java的不同之处
/**
不支持 ++i 的写法
i++ 只是运算,不能当作值处理
String 在 go 中属于基本数据雷星
在go语言中,用 + 连接时左右两边的数据类型必须一样
*/
环境搭建
msi安装包,略
查看已安装版本:go version
配置环境变量
GOROOT
go安装目录,类似JAVA_HOME,有的程序可能会用到
GOBIN
新增环境变量GOBIN,指向bin目录
PATH
将GOBIN
添加到PATH
中
GOPATH
配置方式:创建一个文件夹,里面创建src、bin、pkg文件夹,然后新增一条环境变量GOPATH指向路径即可
GOPATH
是一个环境变量,用来表明go项目的存放目录- 所有的项目代码都请放到
GOPATH
的src
目录下 - 推荐只设置一个GOPATH
设置多个GOPATH:像PATH一样写多个
查看go环境信息:go env
https://goproxy.cn
# Go 1.13 及以上
$ go env -w GO111MODULE=on
$ go env -w GOPROXY=https://goproxy.cn,direct
go mod
go项目需要在gopath目录的src下,才能正常编译运行。go mod使得可以在src其他的目录创建项目,并正常编译运行
GoLand中用法:
- 先在GoLand终端直接执行
go mod init
- 稍等片刻GoLand会提示
Go Modules are detected
,选择Enable Integration
(或者:Setting -- Go -- Go Modules(vgo)) - 在打开的窗口中启用Go Modules,在Proxy中填上上面的代理地址
https://goproxy.cn,direct
,Vendoring mode也勾上(Vendoring mode复选框,它允许您使用vendor文件夹中的库,而不需要任何外部依赖关系或连接到Internet)
项目结构
个人项目一般结构
公司项目一般结构
编译命令
// 只有main包可以编译成二进制文件
package main
import "fmt"
// 入口函数
func main() {
fmt.Print("Hello World", "!", " ", 666, nil)
}
go build
- 在项目目录(go文件的目录)下:
go build
- 在其他任何位置:
go build 路径
,路径是$GOPATH/src后的路径 - 指定编译生成文件名称:
go build -o 期望的结果文件名 路径
(默认生成的文件名是:上级目录.exe) go build -n
print the commands but do not run them.go build -a
force rebuilding of packages that are already up-to-date.- 更多:
go help build
go run
go run HelloWorld.go
go install
- 相比
go build
多了一步:会将二进制文件移动到(应该是直接编译到)GOBIN
目录中(需设置了GOBIN才行) go install ...
编译安装当前目录下所有子项目
跨环境编译
设置环境变量(直接cmd中set即可,仅在当前窗口有效)
// 禁用CGO
// golang的cgo是调用gcc编译c代码的,gcc工具链在linux上很方便,但windows上是没有的,多以在windows上跨环境编译linux二进制文件需要禁用cgo
SET CGO_ENABLED=0
// 目标平台是linux(似乎只能这样写,大写不识别)
SET GOOS=linux
// 目标处理器架构是amd64
SET GOARCH=amd64
go fmt
go语言自带格式化:go fmt HelloWorld.go
常用工具
官方 go tools 安装
mkdir -p $GOPATH/src/golang.org/x
cd $GOPATH/src/golang.org/x
git clone https://github.com/golang/tools.git
cd tools/cmd/
go install ...
# 官方地址
https://github.com/golang/tools.git
# 或者找一个国内的克隆(但是 go install 的时候还是需要访问 github 下载依赖)
https://gitee.com/pengpenger/golang-tools.git
# 安装成功后会生成可执行文件在 %GAOPATH%in 目录下
godoc
# 可以指定一个端口,在本地启动一个 http 服务,方便查看 doc
godoc --http:127.0.0.1:8888
基础知识
变量
package main
import "fmt"
// 推荐使用驼峰命名
// 声明变量(如果没有初始化需指明类型,带初始化可以不指明类型,可以类型推断)
var name string
// 批量声明
var (
age int
ok = true
)
var a, b= "张三", 18
// 匿名变量(不占用命名空间,不分配内存,多用于占位)
var _ = "aaa"
var _ = false
func main() {
name = "zhangsan"
age = 12
ok = true
// 短变量声明(似乎只能在方法里面使用,并且不用的话会报错)
msg := "1"
fmt.Print(name, age, ok, msg, a, b)
// format:%s %d %T
fmt.Print("name type is: %T", name)
}
常量
package main
import (
"fmt"
)
const name = "zhangsan"
const (
// iota是go中的常量计数器,每出现一次常量定义,它的值就加1,每次关键字const出现,就会重置为 0
age = iota
// 省略了复制代表和上面数值一样
age2
age3
)
// 应用示例
const (
_ = iota
KB = 1 << (10 * iota)
MB
GB
TB
PB
)
func main() {
fmt.Print(name, age, age2, age3)
println()
println(KB)
println(MB)
println(GB)
println(TB)
println(PB)
}
基本数据类型
基本数据类型都是值类型,传递都是值传递
布尔
略
整形
整形分为有符号和无符号两种,并且有位数区别
int/uint、int8/uint8、int16/uint16、int32/uint32、int64/uint64
var i2 = 0b10 // 二进制
var i8 = 07 // 八进制
var i10 = 10 // 十进制
var i16 = 0xf // 16进制
浮点数
var f1 = float32(0.1)
var f2 = 0.1 // 默认float64(0.1)
复数
var c1 = complex64(1 + 2i)
var c2 = 1 + 2i // 默认complex128(1 + 2i)
字符串
- 字符串用双引号,字符用单引号
- go支持多行字符串,用反引号
- 字符串工具放在中strings
类型转换:T(表达式)
int("12")
键盘输入和打印输出
/**
输入和输出:
fmt包:输入、输出
输出:
print 打印
printf 格式化打印
println 打印之后换行
格式化打印占位符:
%v 原样输出
%T 打印类型
%t 打印boolen类型
%s 打印字符串类型
%f 打印浮点数类型
%d 打印10进制整数
%b 打印2进制
%o 打印8进制
%x 打印16进制(a-f小写)
%X 打印16进制(A-F大写)
%c 打印字符
%p 打印地址
输入(使用fmt包):
fmt.Scanln(&x, &y) // 该函数支持可变参数;每行是一个参数;需用用参数的地址来操作,所以加上&符号;
fmt.Scanlnf("%d,%f", &x, &y) // 格式化输入,使用这种格式:10,1.02
输入(使用bufio包):
reader := bufio.NewReader(os.Stdin)
后面再看
*/
位运算符
流程控制
if else
- 判断条件不需要加小括号
- 逻辑运算符与Java相同
// 这样写内部变量r只存在于if else中
if r := rand.Int(); r > 1000 && r < 2000 {
println(r)
} else if r > 2000 || r < 50 {
println(r)
}
for循环及其变种
// 基本格式
for i := 0; i < 100; i++ {
println(i)
}
// 前后均可省略
is := 10
for is < 100 {
is += 10
println(is)
}
// 死循环
for {
}
switch case
i := 1
switch j := 10; i * j { // 类似 if 中的写法,不需要小括号,并且可以写语句在里卖
case 1, 2, 3, 4, 5: // case 中支持多个匹配项
println("case 1")
case 10:
println("case 2")
fallthrough // 由于 go 语言的 switch 中, break不需要写,所以为了实现 switch 穿透,可以用 fallthrough 关键字
default:
println("default")
}
switch { // 对于 boolean 型,此处不需要写条件
case i > 100:
println("case 1")
case i < 100:
println("case 2")
default:
println("defalut")
}
go to
rand.Seed(time.Now().Unix()) // go 语言中的随机数必须设置种子,否则每次执行的随机数结果都是固定的
a1: // 即使下面的 go to 无法跳转到 a1 ,a1 还是会随程序顺序流程正常执行
println("a1")
if i := rand.Int() % 2; i == 3 {
println(i)
goto a1
} else if i == 0 {
println(i)
goto b1
} else {
println(i)
goto c1
}
b1:
println("b1")
c1:
println("c1")
数组(Array)
GO语言中数组是值类型,传递是值传递(复制给其他变量的时候实际上是复制了一份,而不是传递引用)
var a1 [5]int // 只定义变量,不做初始化
var a2 = [5]int{1, 2, 3, 4} // 定义变量并初始化
var a3 = [...]int{1, 2, 3, 4, 5} // 可以自动识别数组的长度
var a4 = [5]int{0: 1, 3: 1, 4: 1} // 可以单独设置特定位置的数
fmt.Println(len(a1)) // 数组的长度
fmt.Println(cap(a1)) // 数组的容量(对于数组来说,长度等于容量)
fmt.Println(a1)
fmt.Println(a2)
fmt.Println(a3)
fmt.Println(a4)
for i := 0; i < len(a4); i++ {
fmt.Println(a4[i])
}
for i, j := range a4 {
// i 是下标,如果不需要下标可以用 _ 替换之
// j 是值
fmt.Println(i, j)
}
切片(Slice)
GO语言中切片是引用类型
在切片容量变化之前,数据传递是引用传递,类似于引用类型;在切片容量变化之后,如append超过容量,数据传递是值传递,类似于值类型
var s1 []int // [] 中没有长度表示是切片
s1 = []int{1: 1, 9: 1}
fmt.Println(s1, len(s1), cap(s1))
var s2 = make([]int, 5, 10) // 通常使用make函数来创建切片,第一个参数是切片类型,第二个参数是长度 len ,第三个参数是容量 cap
s2[4] = 1 // 容量虽然是10,但是当前长度为5,所以只能用到4(从0开始)
fmt.Println(s2, len(s2), cap(s2))
fmt.Printf("当前s2的内存地址:%p
", &s2)
fmt.Printf("当前s2存放的内容是(实际上就是底层数组的内存地址):%p
", s2)
// 在现有长度之外添加元素
s2 = append(s2, 10, 11, 12) // 因为切片扩容会导致底层数组引用地址的变化,所以需要重新赋值(容量不变化的时候地址不变)
fmt.Println(s2, len(s2), cap(s2))
fmt.Printf("当s2添加元素但是容量没有变化时,s2的内存地址(不变):%p
", &s2)
fmt.Printf("当s2添加元素但是容量没有变化时,s2存放的内容(不变):%p
", s2)
s2 = append(s2, 1, 1, 1, 1, 1, 1)
fmt.Printf("当s2容量变化之后,s2的内存地址(不变):%p
", &s2)
fmt.Printf("当s2容量变化之后,s2存放的内容(变化):%p
", s2)
// 循环同数组
// 在数组基础上创建切片
// 从数组到切片是引用传递,修改数组(或切片)中的数据,对应切片(或数组)的值也会变化
// 如果对切片进行 append ,在超过数组长度之前,切片的变化会影响数组的数据,超过数组长度之后,切片就会被复制为一个新的内存空间,该切片与数组解除关联
a1 := [...]int{1, 1, 1, 1, 1, 1, 1} // 创建一个数组,长度为7,容量为7
s3 := a1[:5] // 1到5位,长度为5,容量为7(因为是从第一位开始的)
s4 := a1[2:4] // 2到4位,长度为2,容量为5(因为是从第二位开始的)
s5 := a1[5:] // 6到结束,长度为2,容量为2
s6 := a1[:] // 所有,长度为7,容量为7
fmt.Println(s3, len(s3), cap(s3))
fmt.Println(s4, len(s4), cap(s4))
fmt.Println(s5, len(s5), cap(s5))
fmt.Println(s6, len(s6), cap(s6))
切片的数据类型
package main
import "fmt"
func main() {
s1 := make([]int, 5, 6)
fmt.Println(s1)
// 在s2长度和容量变化之前,修改s2中的数值,s1中的数值也会跟着变化
s2 := s1
test(s2, 9)
fmt.Println(s1)
// 在s2长度变化之后但容量不变,修改s2中的数值,s1中的数值也会跟着变化
s2 = append(s2, 0)
test(s2, 99)
fmt.Println(s1)
// 在s2长度和容量都变化后,修改s2中的数值,s1中的数值不会变化
s2 = append(s2, 0)
test(s2, 999)
fmt.Println(s1)
}
func test(s []int, i int) {
s[0] = i
}
深拷贝与浅拷贝
/*
深拷贝与浅拷贝
深拷贝:拷贝的是数据本身
值类型的数据默认都是深拷贝:array、int、float、string、boolean、struct
浅拷贝:拷贝的是数据的地址
多个变量指向同一块内存
引用类型的数据默认都是浅拷贝:skice、map
*/
s1 := []int{1, 2, 3, 4, 5, 6, 7}[:]
s2 := make([]int, 10, 10)
// copy函数可以实现切片的深拷贝(当然也可以用for循环自己实现)
// s2必须初始化长度,copy会将s1按顺序填充到s2中,s2长度不够时只填充前几位
fmt.Println(copy(s2, s1))
s2[0] = 100
fmt.Println(s1)
fmt.Println(s2)
// 拷贝可以控制位置,源和目的都可以控制
s2 = make([]int, 10, 10)
copy(s2[5:], s1[2:4])
fmt.Println(s1)
fmt.Println(s2)
集合(Map)
Map是引用类型
var map1 map[string]string = map[string]string{"name": "zhangsan"}
map2 := make(map[string]int)
// 添加元素
map2["age1"] = 1
map2["age2"] = 2
map2["age3"] = 3
map2["age4"] = 4
// 删除元素
delete(map2, "age4")
fmt.Println(map1)
fmt.Println(map2)
// 获取不存在的元素,结果为value对应数据类型的默认值
fmt.Println(map2["age4"])
value4, ok4 := map2["age4"]
if ok4 {
fmt.Println("key age4 exist", value4)
} else {
fmt.Println("key age4 not exist")
}
// 遍历
for k, v := range map2 {
fmt.Println(k, v)
}
字符串(string)
字符串是值类型
// 字符串转字节切片
slice1 := []byte("hello go")
// 字节切片转字符换
string1 := string(slice1)
string2 := string(slice1[3:6])
fmt.Println(slice1)
fmt.Println(string1)
fmt.Println(string2)
// 字符串常用方法:import strings
fmt.Println(strings.Contains(string1, "go"))
fmt.Println(strings.ContainsAny(string1, "gdasf")) // 检查是否包含chars中的任意一个字符
fmt.Println(strings.Count(string1, "l"))
fmt.Println(strings.Count(string1, "l"))
fmt.Println(strings.HasPrefix(string1, "hello"))
fmt.Println(strings.HasSuffix(string1, "go"))
fmt.Println(strings.Index(string1, "go"))
fmt.Println(strings.IndexAny(string1, "eego"))
fmt.Println(strings.LastIndex(string1, "l"))
slice2 := []string{"1", "2", "3", "4", "5"}
fmt.Println(strings.Join(slice2, " - ")) // join
fmt.Println(strings.Split(string1, " ")) // split
fmt.Println(strings.Replace(string1, "go", "java", 1)) // n表示替换多少次,-1表示全体替换
fmt.Println(strings.ReplaceAll(string1, "go", "java"))
fmt.Println(strings.ToLower(string1))
fmt.Println(strings.ToUpper(string1))
fmt.Println(strings.ToTitle(string1)) // ???
// 截取字符串,类似python,直接使用类似切片的方法
fmt.Println(string1[4:])
// strconv包:字符串和基本数据类型的转换
test1 := strconv.Itoa(100) // 整数转字符串
fmt.Printf("%T
", test1)
test2, _ := strconv.Atoi("100") // 字符串转整数,第二个参数是可能出现的异常,这里不需要,用占位符代替
fmt.Printf("%T
", test2)
test3, _ := strconv.ParseFloat("3.14", 64) // parse函数:字符串转其他类型(bitSize是位数)
fmt.Printf("%T
", test3)
test4 := strconv.FormatBool(false) // format函数:其他类型转字符串
fmt.Printf("%T
", test4)
函数
基本信息
/*
函数命名规范:
字母数字下划线等组成,数字不能放在开头
首字母大写表示函数是公共的,可以被其他包访问
首字母小写表示函数是私有的,只能在本包当中使用
相同类型的参数可以写在一起
支持可变参数(需要放在参数列表的最后,并且只能有一个可变参数)
参数传递:
值类型 - 值传递:传递的是数据的副本,修改数据对原数据不产生影响(如:数组等参数)
引用类型 - 引用传递:传递的是数据的地址,修改数据会对原数据产生影响(如:切片、Map等参数)
函数定义格式如下:
*/
func sum(a, b string, params ...int) int {
s := 0
for i := range params {
s += params[i]
}
return s
}
// 直接指明要返回的变量,这样函数内部不需要再声明这个变量,并且也不需要单独return
func test_1(param string) (i int) {
fmt.Println(param)
i = 100
return
}
// 函数多返回值
func test_2(i1, i2 int) (int, int) {
return i1 * i2, i1 + i2
}
// 函数多返回值中也可以指明返回的变量
func test_3(i1, i2 int) (r1, r2 int) {
r1 = i1 * i2
r2 = i1 + i2
return
}
函数 - 高级
函数是一种复合类型,可以看作是一种变量
package main
import (
"fmt"
"strconv"
)
func main() {
fmt.Printf("%T
", test_1) // func()
fmt.Printf("%T
", test_2) // func(int) int
fmt.Printf("%T
", test_3) // func(int, int) (int, string)
// 函数是一种复合类型,可以看作是一种变量
f1 := test_3
// 直接打印函数,结果是该函数指向的内存地址(函数名指向函数体的内存地址)
fmt.Println(f1)
fmt.Println(f1(1, 2))
}
func test_1() {
}
func test_2(i int) int {
return 0
}
func test_3(x, y int) (res1 int, res2 string) {
res1 = x * y
res2 = strconv.Itoa(x * y)
return
}
匿名函数
package main
import "fmt"
func main() {
/*
匿名函数:
GO语言支持函数式编程
1. 将匿名函数作为另一个函数的参数:回调函数
2. 将匿名函数作为另一个函数的返回值:闭包
*/
// 匿名函数,创建后直接调用(这样只能使用一次)
func (x, y int) (res1, res2 int) {
res1 = x + y
res2 = x * y
return
}(3, 3)
// 将匿名函数赋值给一个变量
f1 := func (x, y int) (res1, res2 int) {
res1 = x + y
res2 = x * y
return
}
fmt.Printf( "f1的数据类型是:%T
", f1)
fmt.Println(f1(3, 3))
}
回调函数
package main
import "fmt"
func main() {
add := func(x, y int) int {
return x + y
}
// 这里的add就是回调函数
fmt.Println(test_1(10, 100, add))
// 直接用匿名函数作为参数
fmt.Println(test_1(10, 100, func(x, y int) int {
return x * y
}))
// todo: go中有没有Lambda
// fmt.Println(test_1(10, 100, nil))
}
// 创建一个高阶函数(就是参数中可以传递函数的函数)
func test_1(x, y int, f func(x, y int) int) int {
return f(x, y)
}
闭包
package main
import "fmt"
func main() {
/*
闭包:
一个外层函数中,有内层函数,该内层函数中会操作外层函数的局部变量(外层函数中的变量,或者外层函数中直接定义的变量)
外层函数的返回值就是该内层函数,那么将这个内层函数和外层函数的局部变量统称为闭包结构
此时外层函数中局部变量的生命周期将发生改变:
正常局部变量会随着函数的调用而创建,随着函数的结束而销毁
但是闭包结构中外层函数的局部变量并不会随着外层函数的结束而销毁,因为内层函数还要继续使用
*/
fmt.Printf("闭包的返回值类型是一个函数:%T
", test_1(5, 6))
t1 := test_1(5, 6)
fmt.Println(t1(1, 2)) // 结果是:80
fmt.Println(t1(1, 2)) // 结果是:5162
fmt.Println(t1(1, 2)) // 结果是:26223200
// 说明t1每次执行,会导致函数内x和y数值的变化
}
func test_1(x, y int) (f func(i, j int) int) {
fmt.Println("test_1 run")
// 在这个函数的内部可以使用传进来的x和y
f = func(i, j int) int {
fmt.Println("inner func run")
// 看一下闭包中函数的执行会不会影响已传递的参数
x = x + y
y = x * y
return x + y + i + j
}
return
}
延迟(defer)
func deferTest_1() {
/*
在go语言中,使用defer关键字来延迟一段代码的执行
1. 当所在方法执行完毕时(return之前)才会执行defer的语句(或者出现异常导致函数退出)
2. 如果有多个defer,按照后进先出的模式执行(就是倒序执行)
3. defer中用到的参数是已经传递进去了的,如果是值类型就不会因后续操作而发生变化(如果是引用类型的话,该怎么变还是怎么变)
*/
i := 0
m := make(map[string]int)
m["a"] = 1
defer fmt.Println("延迟执行:1", i, m)
i++
m["b"] = 2
defer fmt.Println("延迟执行:2", i, m)
i++
m["c"] = 3
defer fmt.Println("延迟执行:3", i, m)
i++
m["d"] = 4
defer fmt.Println("延迟执行:4", i, m)
fmt.Println("deferTest_1执行完毕")
}
指针
指针是引用数据类型
指针常对于值类型的数据来使用,引用数据类型一般不需要使用指针
初始指针
package main
import "fmt"
func main() {
/*
指针
*/
i := 10
fmt.Printf("%p
", &i)
i= 100
// 更改变量的值,变量的内存地址不会发生变化
fmt.Printf("%p
", &i)
// 定义指针类型变量,格式:var var_name *var_type
var p *int
// &符号放在一个变量前面就会返回相应变量的内存地址
p = &i
// 获取指针中存的数据,用符号*
fmt.Printf("类型是:%T,值是:%v
", *p, *p)
// 通过指针修改变量的值
*p = 999
fmt.Println(i)
// 指针的指针
var pp **int
pp = &p
fmt.Printf("%T,%T,%T
", i, p, pp)
}
数组指针 和 指针数组
package main
import "fmt"
func main() {
/*
数组指针 和 指针数组
*/
array1 := [...]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 0}
// 创建一个数组指针
var p *[len(array1)]int // 此处的长度必须为数组的长度
p = &array1 // 其实就是数组第一个元素的地址
fmt.Println(p) // &[1 2 3 4 5 6 7 8 9 0],前边的&代表其实是一个指针,后面是内容
fmt.Printf("%p
", p) // p指针存放的地址(数组的地址)
fmt.Printf("%p
", &p) // p指针自己的地址
fmt.Println((*p)[0])
fmt.Println(p[0]) // 简化写法,对于数组指针,可以这样简写
// 指针数组
a, b, c, d := 1, 2, 3, 4
array2 := [4]*int{&a, &b, &c, &d}
fmt.Println(array2)
for i := range array2 {
fmt.Println(*array2[i])
*array2[i] = 1000
}
// 通过指针修改数据
fmt.Println(a, b, c, d)
}
函数指针 和 指针函数
package main
import "fmt"
func main() {
/*
函数指针 和 指针函数
函数指针:go语言中,function,默认看作一个指针,不需要加*
指针函数:一个函数,该函数的返回值是一个指针
*/
var p func() (p *int)
p = test_1 // 这里的p就是一个指针
fmt.Println(p)
fmt.Printf("%T
", p)
}
// test_1的返回值是一个指针,所以test_1就是一个指针函数
func test_1() (p *int) {
i := 100
return &i
}
指针作为参数
package main
import "fmt"
func main() {
i := 10
fmt.Println(i)
// 传递的是i的地址,属于引用传递,所以函数内部通过i的地址可以需改i的真实值
test_1(&i)
fmt.Println(i)
}
func test_1(p *int) {
*p = 1000
}
结构体
初始结构体
package main
import "fmt"
func main() {
/*
结构体:
由一系列相同类型或者不同类型的数据构成的数据集合
结构体的成员是由一系列的成员变量构成,这些成员变量也称为 - 字段
*/
// 创建方式一
var p1 Persion
fmt.Println(p1)
p1.Name = "zhangsan"
p1.age = 12
p1.sex = 0
fmt.Println(p1)
// 创建方式二
p2 := Persion{Name: "lisi", age: 20, sex: 1}
fmt.Println(p2)
// 创建方式三
p3 := Persion{"haha", 120, 2}
fmt.Println(p3)
}
// 定义结构体
type Persion struct {
Name string // 大写开头表示公有,可以通过导包,在别的包中使用
age int // 小写开头表示私有,只能在当前包使用
sex int
}
结构体指针
package main
import "fmt"
func main() {
/*
结构体属于值类型,传递方式为值传递,默认是深拷贝
*/
p1 := Persion{"lisi", 120, 2}
fmt.Printf("p1:%T
", p1)
// 通过指针,实现结构体的浅拷贝
p2 := &p1
fmt.Printf("p2:%T
", p2)
(*p2).age = 100
// 结构体指针中的*也可以省略不写,类似于函数指针和数组指针
p2.Name = "zhangsan"
fmt.Println("通过p2可以修改p1的数据:", p1)
// go语言中的new:返回的是指针
// new(T)分配了0值填充的T类型的内存空间,并且返回其地址,即一个*T类型的值(它返回了一个指针,指向新分配的类型T的零值)
// 注意:new(T)不是nil,里面存储的是0值填充的T类型
p3 := new(Persion)
fmt.Printf("p3:%T,%v
", p3, *p3)
}
// 定义结构体
type Persion struct {
Name string // 大写开头表示公有,可以通过导包,在别的包中使用
age int // 小写开头表示私有,只能在当前包使用
sex int
}
匿名结构体与匿名字段
package main
import "fmt"
func main() {
/*
匿名结构体和匿名字段
*/
// 匿名结构体(有点类似匿名类),说实话不常用
s1 := struct {
name string
age int
}{
name: "zhangsan",
age: 12, // 注意这里如果不和 } 在同一行,则需要加上 ,
}
fmt.Printf("%T
", s1)
fmt.Println(s1)
s2 := Test{"lisi", 100}
fmt.Printf("%T
", s2)
fmt.Println(s2.string, s2.int) // 匿名字段直接用类型获取数值
}
// 匿名字段中的类型不能相同,都只能有一个
type Test struct {
string
int
}
结构体嵌套
package main
import "fmt"
func main() {
/*
结构体嵌套:一个结构体中的字段是另一个结构体
*/
s1 := Test_1{
int: 0,
Persion: Persion{
name: "zhangsan",
age: 18,
},
}
fmt.Printf("%T
", s1)
fmt.Println(s1.Persion.name, s1.Persion.age)
fmt.Println(s1.name, s1.age) // 提升字段:如果是匿名结构体,可以省略结构体类型,实现类似Java中继承的特征
s2 := Test_2{
int: 0,
Persion: &Persion{
name: "zhangsan",
age: 18,
},
}
fmt.Printf("%T
", s2)
fmt.Println(s2.Persion.name, s2.Persion.age)
fmt.Println(s2.name, s2.age) // 提升字段:如果是匿名结构体,可以省略结构体类型,实现类似Java中继承的特征
}
// 结构体嵌套,默认是深拷贝
type Test_1 struct {
int
Persion
}
// 结构体嵌套,使用指针实现浅拷贝,一般建议使用这个
type Test_2 struct {
int
*Persion
}
type Persion struct {
name string
age int
}
结构体函数
package main
import "fmt"
func main() {
/*
方法:包含了接受者的函数,接受者可以是命名类型或者结构体类型的一个值或者是一个指针,所有给定类型的方法属于该类型的方法集
注意go语言中方法和函数有区别:函数可以被随意调用而函数只能被接受者调用
*/
p1 := Persion{name: "结构体测试"}
p1.show()
p2 := &Persion{name: "结构体指针测试"}
(*p2).show()
p2.show()
// 类似Java中的多态,当子结构体与父结构体有相同名字函数的时候调用的其实是子结构体的方法
zhangsan := ZHANGSAN{Persion{name: "zhansgan"}}
zhangsan.show()
}
type Persion struct {
name string
}
// 定义一个方法,其接受者是结构体Persion
// 又称结构体函数
// 不支持重载
func (p Persion) show() {
fmt.Printf("my name is: %s
", p.name)
}
type ZHANGSAN struct {
Persion
}
// 类似Java中的多态,当子结构体与父结构体有相同名字函数的时候调用的其实是子结构体的方法
func (zhangsan ZHANGSAN) show() {
fmt.Printf("多态~~~ my name is: %s
", zhangsan.name)
}
接口
接口初识
package main
import "fmt"
func main() {
/*
接口:在go语言中,接口是一组方法的签名
当一个类型为接口中的所有方法提供了实现,那么它便实现了该接口,并不需要显示的使用关键字描述(即:go语言中接口和类型的实现关系是非侵入式的)
当需要接口类型对象的时候可以使用任意实现类对象代替(和Java一样)
使用接口类对象不能访问成员类中的其他方法和属性(和Java一样)
*/
// 由于Test结构体实现了InterfaceTest的所有方法,所以Test实际实现了InterfaceTest,可以用InterfaceTest接收Test类型数据
var t InterfaceTest = Test{name: "test"}
t.show("hello")
fmt.Printf("%T
", t)
}
type InterfaceTest interface {
show(desc string) (i int)
}
type Test struct {
name string
}
func (t Test) show(desc string) (i int) {
fmt.Println(t.name, desc)
i = 0
return
}
空接口(类似Java的Object)
package main
import "fmt"
func main() {
/*
空接口:不包含任何方法的接口,所以所有类型都实现了空接口,因此空接口可以用来存储任意类型的数值
有点类似Java的Object
*/
test("字符串类型")
test(1)
test(1.1)
test(false)
}
type A interface {
}
func test(a A) {
fmt.Printf("%T
", a)
}
接口嵌套
package main
import "fmt"
func main() {
/*
接口嵌套
*/
var test_1 A = Test{name:"test_1"}
var test_2 B = Test{name:"test_2"}
var test_3 C = Test{name:"test_3"}
test_1.testA()
test_2.testB()
test_3.testA()
test_3.testB()
test_3.testC()
}
type A interface {
testA()
}
type B interface {
testB()
}
// 接口C中嵌套了接口A和接口B
type C interface {
A
B
testC()
}
// Test结构体实现了C中的方法和C嵌套接口的方法
// 那么Test结构体同时实现了A、B、C
type Test struct {
name string
}
func (t Test) testA() {
fmt.Printf("my name is %v, running testA.
", t.name)
}
func (t Test) testB() {
fmt.Printf("my name is %v, running testB.
", t.name)
}
func (t Test) testC() {
fmt.Printf("my name is %v, running testC.
", t.name)
}
接口断言
package main
import "fmt"
func main() {
/*
接口断言
通过接口断言,获取其真实类型
方式一:
1. instance := 接口对象.(实际类型) // 不安全,可能出现panic
2. instance, ok := 接口对象.(实际类型) // 不安全,可能出现panic
方式二:
switch instance := 接口对象.(type) {
case 实际类型1:
...
case 实际类型2:
...
}
*/
// 注意,这里的a1,a2只能是接口,否则下面无法使用a1.(AS1)语句
// 在真实使用的过程中,往往是一个函数参数类型数接口,穿进去的是该接口的各种实现,在方法内部判断传进参数的实际类型,执行对应的逻辑
var a1 A = AS1{name: "lisi"}
var a2 A = AS2{id: 100}
// 注意:由于a1是接口体,所以这里的instance是值传递,修改instance中的属性,a1的属性不会变化
test_1(a1)
test_2(a1)
test_1(a2)
test_2(a2)
// 结合指针的使用,实现引用传递
var a3 A = &AS1{name: "指针测试"}
fmt.Println("指针测试 - 修改之前:", a3)
if instance, ok := a3.(*AS1); ok {
instance.name = "6666666"
}
fmt.Println("指针测试 - 修改之后:", a3)
}
/**************************方式一:**************************/
func test_1(a A) {
if instance, ok := a.(AS1); ok {
fmt.Printf("类型是:AS1 - %v
", instance)
} else if instance, ok := a.(AS2); ok {
fmt.Printf("类型是:AS2 - %v
", instance)
} else {
fmt.Println("未找到类型")
}
}
/**************************方式二:**************************/
func test_2(a A) {
switch instance := a.(type) {
case AS1:
fmt.Printf("类型是:AS1 - %v
", instance)
case AS2:
fmt.Printf("类型是:AS2 - %v
", instance)
default:
fmt.Println("未找到类型")
}
}
// 接口A与两个实例
type A interface {
show()
}
type AS1 struct {
name string
}
func (as AS1) show() {
fmt.Printf("my name is %v
", as.name)
}
type AS2 struct {
id int
}
func (as AS2) show() {
fmt.Printf("my id is %v
", as.id)
}
type关键字
package main
import "fmt"
func main() {
m := mystr("mystr test")
m.print()
}
// 1. 定义结构体,略
// 2. 定义接口,略
// 3. 定义其他新类型(和scala不同的是这里的myint和int已经是不同的类型了,不能互相赋值)
type myint int
type mystr string
// 4. 定义函数的类型(可以给一个函数类型提供别名,一般用作复杂函数作为参数或者返回值时)
type myprint func(i int)
// 5. 定义别名(这里就和scala一样了)
type myint2 = int
type mystr2 int
// 函数绑定不止可以用在结构体,可以是任意类型
func (m mystr) print() {
fmt.Println(m)
}
// 其它花里胡哨的不看了
error
package main
import (
"errors"
"fmt"
)
func main() {
/*
go语言的错误和异常有不同的定义
错误:可能出现问题的地方出现了问题
type error interface {
Error() string
}
异常:不应该出现问题的地方出现了问题
go中的错误也是一种类型。错误用内置的error类型表示
go语言中没有抛异常的机制,通常是将error信息和结果一起返回出来(因为go中可以多返回值),需要自行处理判断是否有error出现
*/
e1 := errors.New("自定义Error")
fmt.Printf("%T,%v
", e1, e1) // 这里的error是一个指针类型
if msg, err := test_1(-1); err != nil {
// 结合接口断言使用
if err, ok := err.(*MyError); ok {
fmt.Println("错误类型是:MyError,", err)
} else {
fmt.Println("没找到错误类型")
}
// 第二种
switch instance := err.(type) {
case *MyError:
fmt.Println("错误类型是:MyError,", instance)
default:
fmt.Println("没找到错误类型")
}
} else {
fmt.Println(msg)
}
}
// 注意:error应当是一个指针类型
func test_1(i int) (msg string, err error) {
if i > 0 {
msg = "OK"
} else {
msg = "KO"
err = &MyError{desc: "请传入大于0的值"}
}
return
}
// 自定义error:创建一个结构体,使其实现error接口即可
type MyError struct {
desc string
}
func (me MyError) Error() string {
return me.desc
}
panic 与 recover
package main
import "fmt"
func main() {
/*
panic()和recover(),用来处理异常(注意是异常而不是错误)
panic:
1. 是一个内建函数
2. 代码执行如果遇到了panic语句,就会终止其后代码的执行,如果函数中存在defer,按照defer的逆序执行(只有panic之前的defer)
3. A函数调用B函数,B函数中遇到了panic,不仅B函数会终止执行,A函数也会终止执行,如果A中UC你在defer,按照defer逆序执行(只有panic之前的defer)
4. 直到整个goroutine整个退出,并报告错误
recover:
1. 是一个内建函数
2. 用来捕获panic
3. 一般在defer中,通过recover来终止一个goroutine的panicking过程,从而恢复正常代码的执行
4. 直到goroutine整个退出,并报告错误
*/
A()
fmt.Println("==OVER==")
}
func A() {
fmt.Println("A")
// 使用defer,捕获可能出现的panic
defer func() {
if msg := recover(); msg != nil {
fmt.Println("捕获到panic:", msg)
}
}()
defer fmt.Println("A", 1)
defer fmt.Println("A", 2)
defer fmt.Println("A", 3)
B()
defer fmt.Println("A", 4)
}
func B() {
fmt.Println("B")
defer fmt.Println("B", 1)
defer fmt.Println("B", 2)
defer fmt.Println("B", 3)
panic("哈哈哈")
defer fmt.Println("B", 4)
}