不到1000行,Go语言就算入门了吧

作为已经掌握了 C、Java、Python、JavaScript等多种语言的我们,再学习个新语言,自然不需要重头开始。
建立在已有基础上学习一般都会更快,张三丰那种无招胜有招?抱歉,我目前还达不到。
不过即便如此,断断续续花个几小时把Go语言学会了,也是有诸多好处。毕竟作为一名资深Java开发者,有预感,Go语言会超越Java。
语言罢了,还都是GC语言,不过如此,确实简单。

下面是不到1000行的学习笔记,包含了Go的关键特点,入个门足矣。

基本规则

  • go是静态类型语言
  • 短变量声明: var a int = 1等价于a := 1
  • 公开访问的名称规则:大写开头就是公开访问的
  • 常用 go命令: got build xxx.go, go run xxx.go
  • if true{}或者 for i:=1;i<10;i++{} 后面的括号()是多余的
  • 导包有多层时用斜杠: import "math/rand"

读取输入与类型转换

package main

import (
	"bufio"
	"log"
	"os"
	"strconv"
	"strings"
)

func main() {
	reader := bufio.NewReader(os.Stdin)
	s, err := reader.ReadString('\n')
	if err != nil {
		log.Fatal(err)
	}
	age, err := strconv.ParseInt(strings.TrimSpace(s), 10, 32)
	age2, err := strconv.Atoi(strings.TrimSpace(s))
	println("age=", age, ",age2=", age2)
}

读取文本文件

package main

import (
	"bufio"
	"log"
	"os"
)

func main() {
	file, err := os.Open("go.mod")
	if err != nil {
		log.Fatal(err)
	}
	scanner := bufio.NewScanner(file)

	for scanner.Scan() {
		println(scanner.Text())
	}
	err = file.Close()
	if err != nil {
		log.Fatal(err)
	}
}

随机数包

package main

import (
    "math/rand"
    "time"
)

func main() {
    fixed := rand.Intn(100) + 1
    
    milli := time.Now().UnixMilli()
    rand.Seed(milli)
    randInt := rand.Intn(100) + 1
    println(fixed, randInt)
}

循环:for也能当while用

package main

func main() {
	for i := 0; i < 10; i++ {
		print(i, ",")
	}
	// while
	j := 1
	for j < 10 {
		print(j, ",")
		j++
	}

	for i := 0; i < 10; i++ {
		if i < 4 {
			break
		}
		if i > 3 {
			continue
		}
	}
}

函数与返回错误

多个返回值

package main

import "fmt"

func main() {
	a, err := area(3, 5)
	println("3*5=", a, err)
}

func area(len int, width int) (int, error) {
	if len < 0 || width < 0 {
		return 0, fmt.Errorf("错误参数:%d,%d", len, width)
	}
	return len * width, nil
}

指针

package main

import "fmt"

func main() {
	a := 2
	p := myPointer(&a)
	fmt.Println(a, p, *p) // 4 0xc0000200e0 10
}

func myPointer(number *int) *int {
	*number *= 2
	a := 10
	return &a
}

多模块

通过 go init mod 01 初始化,然后实现以下结构。

├── go.mod
├── hello
│   ├── chinese
│   │   ├── chinese.go
│   │   ├── dialect
│   │   │   ├── dialect.go
│   │   │   └── lang.go
│   │   └── putonghua
│   │       └── putonghua.go
│   └── english
│       └── english.go
├── main.go
└── README.md

go.mod

module 01

go 1.17

chinese.go

package chinese

func Hello() {
	println("你好")
}

dialect.go

package dialect

func Hello() {
	siChuanHua()
	dongBeiHua()
}

lang.go

package dialect

func siChuanHua() {
	println("瓜娃子")
}

func dongBeiHua() {
	println("你瞅啥")
}

putonghua.go

package putonghua

func Hello() {
	println("您好呀")
}

english.go

package english

import "fmt"

func Hello() {
	fmt.Println("Hello")
}

main.go
开始引用这些包, 从mod名开始。

package main

import (
	"01/hello/chinese"
	"01/hello/chinese/dialect"
	"01/hello/chinese/putonghua"
	"01/hello/english"
)

func main() {
	english.Hello()
	chinese.Hello()
	putonghua.Hello()
	dialect.Hello()
}

下载三方包

设置代理

go env -w GOPROXY=https://goproxy.cn,direct
go get github.com/xxx/xxx

自己发布包

建一个新仓库,我的示例:https://gitee.com/halfgold/go-test-mod

版本号通过打 git tag来定。

然后在项目里引用:

go.mod

module 03

go 1.17

require (
	gitee.com/halfgold/go-mod-test/sing v0.0.1
)

main.go

package main

import "gitee.com/halfgold/go-mod-test/sing"

func main() {
	sing.Song()
}

不过很不幸,gitee测试会弹出输入密码的报错:

$ go get gitee.com/halfgold/go-mod-test/sing@v0.0.1
go: gitee.com/halfgold/go-mod-test/sing@v0.0.1: reading gitee.com/halfgold/go-mod-test/sing/sing/go.mod at revision sing/v0.0.1: git ls-remote -q origin in /home/jack/go/pkg/mod/cache/vcs/522776bd8367d68a9aaaca87075c70c40767d41e0e33cc6e07bd270c38fbf6ff: exit status 128:
        fatal: could not read Username for 'https://gitee.com': terminal prompts disabled
Confirm the import path was entered correctly.
If this is a private repository, see https://golang.org/doc/faq#git_https for additional information.

既然这样,我就输以下嘛:

$ export GIT_TERMINAL_PROMPT=1
jack@jack-f1:~/workspace/git/go-in/03-go-get$ go get gitee.com/halfgold/go-mod-test/sing@v0.0.1
Username for 'https://gitee.com': halfgold
Password for 'https://halfgold@gitee.com': 
go: gitee.com/halfgold/go-mod-test/sing@v0.0.1: reading gitee.com/halfgold/go-mod-test/sing/sing/go.mod at revision sing/v0.0.1: git ls-remote -q origin in /home/jack/go/pkg/mod/cache/vcs/522776bd8367d68a9aaaca87075c70c40767d41e0e33cc6e07bd270c38fbf6ff: exit status 128:
        remote: 404 not found!
        fatal: 仓库 'https://gitee.com/halfgold/go-mod-test/' 未找到

结果他居然说找不到。

这就伤心了,先存个档。

文档

使用 go doc xxx查看文档,比如 go doc fmt.

更详细的:

$ go doc fmt Println
package fmt // import "fmt"

func Println(a ...interface{}) (n int, err error)
    Println formats using the default formats for its operands and writes to
    standard output. Spaces are always added between operands and a newline is
    appended. It returns the number of bytes written and any write error
    encountered.

我们也要写文档

我们初始化的04项目写上:包注释和方法注释:

想必看出来规则了:

  • 包注释:Package 包名 注释内容
  • 方法注释:方法名 注释内容
// Package hello 这个包很牛逼
package hello

// Hello 说你好的方法
func Hello() {
	println("你好")
}

展示文档:

04-go-doc$ go doc
package hello // import "04"

Package hello 这个包很牛逼

func Hello()

04-go-doc$ go doc hello
package hello // import "04"

func Hello()
    Hello 说你好的方法

安装go的工具集后,可以在网页上展示文档:

sudo apt install golang-golang-x-tools

# 然后起服务
$ godoc -http=:8080

在页面上访问: localhost:8080/pkg,还能看到自己的包

数组与切片

先看数组的声明和遍历

package main

func main() {
	var arr = [3]string{"a", "b", "c"}
	arr[0] = "a1"
	for i, s := range arr {
		println(i, s)
	}
	var colors [3]int
	colors[0] = 1
	println("长度:", len(colors))
}

再看切片,可以利用 append 方法延长数组切片

package main

import "fmt"

func main() {
	// 数组
	var arr [3]int
	// 切片
	var slice = make([]int, 3)
	// 预填充
	slice1 := []int{1, 2, 3}

	slice2 := slice1[0:2]
	slice3 := slice1[1:]
	// 修改依赖的底层元素,切片也跟着改变
	slice1[1] = 22
	fmt.Println(slice3)
	// 追加新元素,不会影响原有切片
	slice31 := append(slice3, 4, 5)
	fmt.Println(slice31, slice1)
	slice4 := slice1[:3]
	fmt.Println(arr, slice, slice1, slice2, slice3, slice4)
}

可变参数

注意切片传输的写法

package main

func main() {
	println(sum(1, 2, 3))
	
	intSlice := []int{1, 2, 3}
	println(sum(intSlice...))
}

func sum(nums ...int) int {
	sum := 0
	for _, n := range nums {
		sum += n
	}
	return sum
}

map

package main

import "fmt"

func main() {
	// 初始化
	var m map[string]int
	m = make(map[string]int)
	m["jimo"] = 18
	fmt.Println(m)

	// map字面量
	m = map[string]int{"hehe": 20, "lily": 19}
	fmt.Println(m)

	// 区分是否设了值
	age, hasSet := m["hehe"]
	age1, hasSet1 := m["jimo"]
	fmt.Println(age, hasSet, age1, hasSet1) // 20 true 0 false

	// 删除
	delete(m, "hehe")
	fmt.Println(m)

	// 遍历
	for k, v := range m {
		fmt.Println(k, v)
	}
}

结构体

注意大对象使用指针传参,而不是值传递

package main

import "fmt"

type Person struct {
	name string
	age  int8
}

func main() {
	var p Person
	p.name = "jimo"
	p.age = 18
	fmt.Println(p)
	growUp(&p)
	fmt.Println(p) // 19

	growUpFake(p)
	fmt.Println(p) // 19

	// 字面量赋值
	hehe := Person{
		name: "hehe", age: 20,
	}
	fmt.Println(hehe)
}

func growUpFake(u Person) {
	u.age += 1
}

func growUp(u *Person) {
	u.age += 1
}

自定义基本类型

让基本类型变得有含义

package main

type KM float32
type M float32

func main() {
	println(KM(1), M(1000))
	dist := KM(1.1)
	dist += toKM(M(2345)) // 可以做计算
	println(dist)
}

func toKM(m M) KM {
	return KM(m / 1000)
}

方法和函数

不过是一丘之貉

package main

type KM float32
type M float32

func main() {
	m := M(123)
	println(m.toKM())
}

func (m M) toKM() KM {
	return KM(m / 1000)
}

Getter和Setter方法

注意使用指针作为参数

package main

import (
	"errors"
	"fmt"
	"log"
	"strconv"
)

type Person struct {
	name string
	age  int8
}

func (p *Person) SetName(name string) {
	p.name = name
}

func (p *Person) SetAge(age int8) error {
	if age <= 0 {
		return errors.New("不合法的年龄:" + strconv.Itoa(int(age)))
	}
	p.age = age
	return nil
}

func (p *Person) Age() int8 {
	return p.age
}

func main() {
	p := Person{}
	p.SetAge(18)
	fmt.Println(p.Age())

	err := p.SetAge(-1)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(p)
}

接口与类型断言

go的接口实现居然是通过方法名来匹配的

package main

import "fmt"

type Animal interface {
	Run()
}

type Dog struct {
}

// Run 通过方法继承
func (d Dog) Run() {
	println("小狗跑了")
}

type Cat struct {
}

func (c Cat) Run() {
	println("小猫跑了")
}

type Student struct{}

func main() {
	animalRun(Dog{})
	animalRun(Cat{})

	// 接口类型断言
	var a Animal = Dog{}
	d, ok := a.(Dog)
	fmt.Println(d, ok)

	//s, ok := a.(Student)
	//println(s, ok)
}

func animalRun(a Animal) {
	a.Run()
}

那空接口怎么实现?

不用实现,所有的类都是其实例

package main

import (
	"fmt"
)

// 空接口
type nothing interface {
}

type Person struct {
	name string
	age  int8
}

func acceptAnything(a nothing) {
	fmt.Println(a)
	p, ok := a.(Person)
	if ok {
		fmt.Println("is Person: " + p.name)
	}
}

func main() {
	acceptAnything(Person{
		name: "寂寞",
		age:  18,
	})
	acceptAnything(1.2)
	acceptAnything(1)
	acceptAnything("hehe")
}

error接口

气接口定义为:

type error interface {
	Error() string
}
package main

import "fmt"

type MyError string

func (e MyError) Error() string {
	return string(e)
}

func main() {
	var err error
	err = MyError("我的错误实现")
	fmt.Println(err)
}

Stringer接口:重写toString方法

type Stringer interface {
	String() string
}
package main

import (
	"fmt"
	"strconv"
)

type Person struct {
	name string
	age  int8
}

func (p Person) String() string {
	return "姓名:" + p.name + ",年龄:" + strconv.Itoa(int(p.age))
}

func main() {
	fmt.Println(Person{
		name: "寂寞",
		age:  18,
	})
}

defer: go中的finally

package main

import (
	"fmt"
	"log"
	"os"
	"strconv"
)

func OpenFile(file string) (*os.File, error) {
	fmt.Println("打开文件", file)
	return os.Open(file)
}

func CloseFile(file *os.File) {
	fmt.Println("关闭文件")
	file.Close()
}

func main() {
	file, err := OpenFile("go.mod")
	if err != nil {
		log.Fatal(err)
	}
	// 无论如何都要调用关闭文件
	defer CloseFile(file)
	println("处理文件...")
	float, err := strconv.ParseFloat("hehe", 32)
	if err != nil {
		// 这样不能调用 关闭文件
		//log.Fatal(err)
		// 这样可以
		log.Println("出错了")
		return
	}
	println(float)
}

panic与recover

看起来像 try-catch,不过go语言维护者不建议使用,而是采用 if 和return去处理错误

package main

func revive() {
	recover()
}

func fine() {
	// 不能直接调用 defer recover()
	defer revive()
	panic("啊,我要死了")
	println("我愉快的生活")
}

func main() {
	println("人生开始")
	fine()
	println("人生继续")
	/*
		人生开始
		人生继续
	*/
}

go routine

简单的并发

package main

import (
	"fmt"
	"time"
)

func task(a int) {
	for i := 0; i < 1000; i++ {
		fmt.Print(a)
	}
}

func main() {
	go task(7)
	go task(8)
	time.Sleep(time.Second)
	fmt.Println("main结束")
}

goroutine返回值

上门的方法无法返回内容,如果想返回每个routine的值,要用到channel

package main

import (
	"fmt"
)

func sum(a int, b int, channel chan int) {
	s := a + b
	// 结果发送到通道
	channel <- s
}

func main() {
	sumChannel := make(chan int)
	go sum(1, 2, sumChannel)
	go sum(4, 5, sumChannel)
	fmt.Println("结果1:", <-sumChannel)
	fmt.Println("结果2:", <-sumChannel)
	fmt.Println("main结束")
}

构造channel时可以指定缓冲数量

make(chan int, 3)

测试

  • 测试文件名以 xxx_test.go结尾, 放在哪里都可以
  • 测试需引入 testing
  • 测试方法需以 Test开头,方法一般参数为 *testing.T

文件

.
├── sum.go
└── sum_test.go

sum.go

package sum

func Sum(a int, b int) int {
	return a + b
}

sum_test.go

package sum

import "testing"

func TestSum(t *testing.T) {
	s := Sum(1, 2)
	if s != 3 {
		t.Errorf("1+2=3,实际得到%d", s)
	}
}

func TestSumFailed(t *testing.T) {
	s := Sum(1, 2)
	t.Errorf("1+2=3,实际得到%d", s)
}

然后使用 go test测试

$ go test -v
=== RUN   TestSum
--- PASS: TestSum (0.00s)
=== RUN   TestSumFailed
    sum_test.go:14: 1+2=3,实际得到3
--- FAIL: TestSumFailed (0.00s)
FAIL
exit status 1
FAIL    00-test/sum     0.001s

http服务

go语言对http的支持简单友好

package main

import (
	"fmt"
	"log"
	"net/http"
)

func handleHello(writer http.ResponseWriter, req *http.Request) {
	fmt.Println("Access ", req.RequestURI)
	msg := []byte("hello jimo")
	_, err := writer.Write(msg)
	if err != nil {
		log.Fatal(err)
	}
}
func main() {
	http.HandleFunc("/hello", handleHello)
	err := http.ListenAndServe("localhost:8080", nil)
	log.Fatal(err)
}

接下来

刚刚开了个头,就结束了?不过,新的自虐之路才刚刚开始。

接下来,web和并发2个方向自然需要攻破。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值