概述
作为已经掌握了 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个方向自然需要攻破。
最后
以上就是高兴太阳为你收集整理的不到1000行,Go语言就算入门了吧基本规则读取输入与类型转换读取文本文件随机数包循环:for也能当while用函数与返回错误指针多模块下载三方包文档数组与切片可变参数map结构体接口与类型断言defer: go中的finallypanic与recovergo routine测试http服务接下来的全部内容,希望文章能够帮你解决不到1000行,Go语言就算入门了吧基本规则读取输入与类型转换读取文本文件随机数包循环:for也能当while用函数与返回错误指针多模块下载三方包文档数组与切片可变参数map结构体接口与类型断言defer: go中的finallypanic与recovergo routine测试http服务接下来所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复