
go get github.com/avb/asd
go get -u //自动更新包
module github.com/shixiaocaia/Learn_go
go 1.20
require rsc.io/quote v1.5.1
require (
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c // indirect
rsc.io/sampler v1.3.0 // indirect
)
Go 模块准备好后,只需简单几步,即可将其发布出去:
打开命令行并进入模块根目录;
执行 go mod tidy
命令,删除一些不必要的模块依赖;
我们演示发布的模块没有引用任何依赖,因此这步可有可无;
执行测试用例,最后一次确认代码运行无误;
go test ...
go test
命令执行模块的单测用例,用例可以用 Go 语言的测试框架来写。我们演示的模块没有测试用例,因此这步也可以跳过。
执行 git tag
命令,用新版本号打标签;
# 提交代码
git commit -m "first version"
# 打标签
git tag v1.0.0
版本号必须符合模块版本命名规则,以便让用户知晓代码变更情况(接口是否兼容)。
将新标签推到原仓库(如 Github );
git push origin v1.0.0
执行 go list
命令触发 Go 更新模块索引信息,确保新发布的模块可用;
GOPROXY=proxy.golang.org go list -m github.com/fasionchan/goutils@v1.0.0
注意到,我们在命令之前设置了 GOPROXY 环境变量,指定使用 proxy.golang.org 作为 Go 代理。这可以确保我们请求到官方代理,而不是各种加速镜像。
类型声明
type FreezingC float64
type Fahrenheit float64
FreezingC Fahrenheit虽然底层都相同,都是float64,但是仍然不是同一种类型,进行计算时需要显示类型转换
Go中不具有隐式类型转换
全局变量如果要在包外访问,首字母需要大写,golang是以首字母大小写来区分对包外是否可见。
const str = "hello"
const(
e = 1.2323
p = 2312
)
:=
s := "hello world"
var s string = "hello world"
s2 := s[0:5] // 创建[0,5)的子串
" "
和 ' '
前者表示string,后者表示byte类型。[]byte
字节切片。
在Go语言当中,字符的概念被称为 rune
- 它是一个表示 Unicode 编码的整数。
var a int = 5
p := &a
var a *int
&
取地址,用*
取地址中的值&
特殊标记,这是一个指针变量。array := [3]string{"heli,"hello","world"}
array := [...]string{"heli,"hello","world"}
func modify(array [5]string){
array[0] = "another"
}
modify(array)
a := [2][3]int{
{1, 1, 1},
{2, 2, 2}
}
slice := []string{"heli,"hello","world"}
newSlice := make([]int, 2)
// len = cap = 2
newSlice := make([]int, 2, 100)
// len = 2, cap = 100
multSlice := make([][]int, 0)
multSlice = append(multSlice, []int{1,2,3})
multSlice = append(multSlice, []int{2,2,3})
multSlice[0][1] = 100
reflect.DeepEqual
// 创建一个变量
var m map[int]string = make(map[int]string)
m := map[sting]int{
"ming":10,
"zhangsan":13,
}
// 二维
var m map[int]map[int]string
m = make(map[int]map[int]string)
// 分布初始化
m[1] = make(map[int]string)
m[1][1] = "OK"
a,ok := m[2][1]
if !ok{
m[2] = make(map[int]string)
}
score, err := m["ming"]
//访问时返回值和是否存在(true or false)
//注意这边的value知识一个拷贝,不对实际产生影响
for key, value := range m{
fmt.Println("%s\t %d\t", key, value)
}
// 删除
delete(m, "ming")
type person struct{
Name string
Age int
}
func main() {
//a := person{}
//a.Name = "joe"
//a.Age = 19
a := &person{
Name: "joe",
Age: 19
}
a.Age = 20
}
// 匿名结构
func main(){
a := & struct{
Name string
Age
}{
Name: "joe",
Age: 19
}
}
普通传递是值传递,所以开始就加&,取地址,方便后续使用,避免每一次使用取地址
type human struct{
Sex int
}
type teacher struct{
human
Name string
Age int
}
a := teacher{Name: "joe", Age: 19, human: human{Sex:0}}
a.Sex = 1
支持嵌套
sm1 := struct {
age int
m map[string]string
}{age: 11, m: map[string]string{"a": "1"}}
sm2 := struct {
age int
m map[string]string
}{age: 11, m: map[string]string{"a": "1"}}
if sm1 == sm2 {
fmt.Println("sm1 == sm2")
}
- 只有相同类型的结构体才可以比较,结构体是否相同不但与属性类型个数有关,还与属性顺序相关.
- 此外构体属性中有不可以比较的类型,如
map
,slice
,则结构体不能用==
比较。应该使用reflect.DeepEqual(s1,s2)
package main
import "fmt"
// K, V是类型参数
func MapKeys[K comparable, V any](m map[K]V) []K {
r := make([]K, 0, len(m))
for k := range m {
r = append(r, k) // 复制key值
}
return r
}
type List[T any] struct {
head, tail *element[T]
}
type element[T any] struct {
next *element[T]
val T
}
// 使用时必须保留类型参数
func (lst *List[T]) Push(v T) {
if lst.tail == nil {
lst.head = &element[T]{val: v}
lst.tail = lst.head
} else {
lst.tail.next = &element[T]{val: v}
lst.tail = lst.tail.next
}
}
func (lst *List[T]) GetAll() []T {
var elems []T
for e := lst.head; e != nil; e = e.next {
elems = append(elems, e.val)
}
return elems
}
func main() {
var m = map[int]string{1: "2", 2: "4", 4: "8"}
fmt.Println("keys m:", MapKeys(m))
// 可以不用指定类型参数
_ = MapKeys[int, string](m)
lst := List[int]{}
lst.Push(10)
lst.Push(13)
lst.Push(23)
fmt.Println("list:", lst.GetAll())
}
package main
import (
"errors"
"fmt"
)
func f1(arg int) (int, error) {
if arg == 42 {
return -1, errors.New("can't work with 42")
}
return arg + 3, nil
}
type argError struct {
arg int
prob string
}
func (e *argError) Error() string {
return fmt.Sprintf("%d - %s", e.arg, e.prob)
}
func f2(arg int) (int, error) {
if arg == 42 {
return -1, &argError{arg, "can't work with it"}
}
return arg + 3, nil
}
func main() {
for _, i := range []int{7, 42} {
if r, e := f1(i); e != nil {
fmt.Println("f1 failed:", e)
} else {
fmt.Println("f1 worked:", r)
}
}
for _, i := range []int{7, 42} {
if r, e := f2(i); e != nil {
fmt.Println("f2 failed:", e)
} else {
fmt.Println("f2 worked:", r)
}
}
_, e := f2(42)
if ae, ok := e.(*argError); ok {
fmt.Println(ae.arg)
fmt.Println(ae.prob)
}
}
error
类型,它是一个内建的接口。Error()
方法来自定义 error
类型。 这里使用自定义错误类型来表示上面例子中的参数错误。func main(){
a := 1
switch a {
case a >= 0:
fmt.Println("a")
fallthrough
case 1:
}
}
for _, url := range urls {
go func() {
results[url] = wc(url)
}()
}
url
变量的引用 —— 它们没有自己的独立副本。for _, url := range urls {
go func(u string) {
results[u] = wc(u)
}(url)
}
url
作为参数调用匿名函数,我们确保 u
的值固定为循环迭代的 url
值,重新启动 goroutine
。u
是 url
值的副本,因此无法更改。// 不定长变参,这是拷贝值
func A(b string, a ...int){
}
// 拷贝地址
func A(b string, a []int){
}
- Go函数不支持嵌套、重载和默认参数
- 无需声明原型、不定长度变参、多返回值、命名返回值参数
func main(){
a := func(){
fmt.Println("A")
}
a()
}
A 是一个函数类型变量,当作函数一样调用
func main(){
f := closure(10)
fmt.Println(f(1))
}
func closure(x int) func(int) int{
return func(y int) int {
return x + y
}
}
返回值是一个函数,x是声明时给的值,y是后传入的值
package main
/*
下面代码是否编译通过?
*/
func myFunc(x,y int)(sum int,error){
return x+y,nil
}
func main() {
num, err := myFunc(1, 2)
fmt.Println("num = ", num)
}
- 在函数有多个返回值时,只要有一个返回值有指定命名,其他的也必须命名
- 如果返回值有有多个返回值必须加上括号
- 如果只有一个返回值并且有命名也需要加上括号
延迟函数什么时候被调用
延迟调用的语法规则
defer语句执行的顺序是先见后出LIFO
defer用于资源的释放,会在函数返回之前进行调用。一般采用如下模式:
f,err := os.Open(filename)
if err != nil {
panic(err)
}
defer f.Close()
要使用defer时不踩坑,最重要的一点就是要明白,return xxx这一条语句并不是一条原子指令!
函数返回的过程是这样的:先给返回值赋值,然后调用defer表达式,最后才是返回到调用函数中。
func f() (result int) {
// result = 0 //return语句不是一条原子调用,return xxx其实是赋值+ret指令
func() { //defer被插入到return之前执行,也就是赋返回值和ret指令之间
result++
}()
return
}
func f() (r int) {
t := 5
// r = t //赋值指令
func() { //defer被插入到赋值与返回之间执行,这个例子中返回值r没被修改过
t = t + 5
}
return //空的return指令 return t
}
func f(){
var num = 1
defer fmt.Printf("num is %d", num)
num = 2
return
}
func deferRun()(res int){
num := 1
defer func() {
res++
}()
return num
}
func deferRun() int{
var num int
defer func(){
num++
}()
return 1
}
func deferRun() int{
num := 1
defer func(){
num++
}()
return num
}
区别匿名函数和有名函数的,返回值的区别,记住先赋值、再defer、最后返回。
package main
import "fmt"
func Demo(i int) {
//定义10个元素的数组
var arr [10]int
//错误拦截要在产生错误前设置
defer func() {
//设置recover拦截错误信息
err := recover()
//产生panic异常 打印错误信息
if err != nil {
fmt.Println(err)
}
}()
//根据函数参数为数组元素赋值
//如果i的值超过数组下标 会报错误:数组下标越界
arr[i] = 10
}
func main() {
Demo(10)
//产生错误后 程序继续
fmt.Println("程序继续执行...")
}
package main
import "fmt"
func sum(nums ...int) {
fmt.Print(nums, " ")
total := 0
for _, num := range nums {
total += num
}
fmt.Println(total)
}
func main() {
sum(1, 2)
sum(1, 2, 3)
nums := []int{1, 2, 3, 4}
sum(nums...)
}
匿名函数在你想定义一个不需要命名的内联函数时是很实用的。
package main
import "fmt"
func intSeq() func() int {
i := 0
return func() int {
i++
return i
}
}
func main() {
nextInt := intSeq()
fmt.Println(nextInt())
fmt.Println(nextInt())
fmt.Println(nextInt())
newInts := intSeq()
fmt.Println(newInts())
}
是某种特定类型的函数。
var a TZ
//method value
a.Print()
//method expression
(*TZ).Print(&a)
func (a *TZ) Print(){
fmt.Println("Tz")
}
一个数据通过func funcName(interface{})的方式传进来的时候,也就意味着这个参数被自动的转为interface{}的类型。
在我们使用时,应该进行断言
var a interface{}
fmt.Println("Where are you,Jonny?", a.(string))
但是直接断言,错误会发生panic,所以一般在断言前进行一定的判断
value, ok := a.(string)
value, ok := a.(string)
if !ok {
fmt.Println("It's not ok for type string")
return
}
fmt.Println("The value is ", value)
类型断言用于判断一个接口对象是否实现了特定的接口,并且获取其对应的实际值。在这段代码中,r.(io.Writer)
是一种类型断言,用于判断r
是否实现了io.Writer
接口,并将其转换为io.Writer
类型的实例赋值给变量w
。
而类型强制转换是将一个数据从一种类型转换为另一种类型,这通常在非接口类型之间进行。例如,将一个整数类型转换为浮点数类型,或者将一个字符串类型转换为字节切片类型等。
package main
import "fmt"
type USB interface {
Name() string
Connect()
}
type PhoneConnecter struct {
name string
}
func (pc PhoneConnecter) Name() string {
return pc.name
}
func (pc PhoneConnecter) Connect() {
fmt.Println("Connect:", pc.name)
}
func main() {
a := PhoneConnecter{name: "xiaocai"}
a.Connect()
Disconnect(a)
}
func Disconnect(usb USB) {
fmt.Println("Disconnext.")
}
er
,接口作为函数参数时,任何满足这个接口的变量类型都可以传入type USB interface {
Name() string
Connecter
}
type Connecter interface {
Connect()
}
func Disconnect(usb USB) {
if pc, ok := usb.(PhoneConnecter); ok {
fmt.Println("Disconnect:", pc.name)
return
}
fmt.Println("Unknown device")
}
type empty interface{
}
func Disconnect(usb USB) {
switch v := usb.(type) {
case PhoneConnecter:
fmt.Println("Disconnect:", v.name)
default:
fmt.Println("Unknown device")
}
}
type switch
type tvConnecter struct{
name string
}
func (tv tvConnecter)Connect{
...
}
tv := tvConnecter{"1234"}
var a USB
a = USB(tv) //error
a := PhoneConnecter{name: "xiaocai"}
var tmp PhoneConnecter
tmp = a
tmp.Connect()
a.name = "pc"
tmp.Connect()
func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) {
p := newPrinter()
p.doPrintf(format, a)
n, err = w.Write(p.buf)
p.free()
return
}
io.Writer
是一个很好的通用接口,用于「将数据放在某个地方」。反射是一种机制,在编译时不知道具体类型的情况下,可以透视结构的组成、更新值。使用反射,可以让我们编写出能统一处理所有类型的代码。
为什么不通过将所有参数都定义为 interface 类型来得到真正灵活的函数呢?
如果你想实现函数的多态性,请考虑是否可以围绕接口(不是 interface
类型,这里容易让人困惑)设计它,以便用户可以用多种类型来调用你的函数,这些类型实现了函数工作所需要的任何方法。
编写测试和函数很类似,其中有一些规则
xxx_test.go
的文件中编写Test
开始t *testing.T
现在这些信息足以让我们明白,类型为 *testing.T
的变量 t
是你在测试框架中的 hook(钩子),所以当你想让测试失败时可以执行 t.Fail()
之类的操作。
package main
import "testing"
func TestHello(t *testing.T) {
// 将断言重构为匿名函数,减少重复,提高可读性
assertCorrectMessage := func(t *testing.T, got, want string) {
t.Helper() // 这是辅助函数,出错显示在调用地方,而不是辅助函数内部
if got != want {
t.Errorf("got '%q' want '%q'", got, want)
}
}
// 子测试
t.Run("saying hello to people", func(t *testing.T) {
got := Hello("Chris", "")
want := "Hello, Chris"
assertCorrectMessage(t, got, want)
})
t.Run("empty string defaults to 'world'", func(t *testing.T) {
got := Hello("", "")
want := "Hello, World"
assertCorrectMessage(t, got, want)
})
t.Run("in Spanish", func(t *testing.T) {
got := Hello("Elodie", "Spanish")
want := "Hola, Elodie"
assertCorrectMessage(t, got, want)
})
t.Run("in French", func(t *testing.T) {
got := Hello("Elodie", "French")
want := "Bonjour, Elodie"
assertCorrectMessage(t, got, want)
})
}
func ExampleAdd() {
sum := Add(2, 2)
fmt.Println(sum)
// Output: 4
}
_test.go
,编写示例代码,提高可读性func BenchmarkRepeat(b *testing.B) {
for i := 0; i < b.N; i++ {
Repeat("a")
}
}
testing.B
可使你访问隐性命名(cryptically named)b.N
。
基准测试运行时,代码会运行 b.N
次,并测量需要多长时间。
代码运行的次数不会对你产生影响,测试框架会选择一个它所认为的最佳值,以便让你获得更合理的结果。
用 go test -bench=.
来运行基准测试。 (如果在 Windows Powershell 环境下使用 go test -bench="."
)
func TestArea(t *testing.T) {
areaTests := []struct {
shape Shape
want float64
}{
{Rectangle{12, 6}, 72.0},
{Circle{10}, 314.1592653589793},
}
for _, tt := range areaTests {
got := tt.shape.Area()
if got != tt.want {
t.Errorf("got %.2f want %.2f", got, tt.want)
}
}
}