Go 语言入门三部曲:能看懂 Go 语言
Posted 看,未来
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Go 语言入门三部曲:能看懂 Go 语言相关的知识,希望对你有一定的参考价值。
文章目录
三部曲
0、能看完这篇却没动手 <-洗洗睡吧
1、能看懂 Go 语言 <- 拿到钥匙了
2、能用 Go 语言写管理系统 <- 趴门墩儿上了
3、能用 Go 语言写 “生产/消费者” 模型 <- 进门了,广阔天地大有可为
咱不玩虚的,这些都行,才算入了门。
搭建环境
喜欢 Windows 呢,还是 Linux 呀?我选 VScode 哈。
1、部署 Go 环境:Go 安装包下载
Windows 的话有操作指引,Linux 的话:
tar -C /usr/local -xzf goXXXXX.tar.gz
export PATH=$PATH:/usr/local/go/bin
source /etc/profile
现在的大环境都比较好,安装个环境都很快,不想我三年前初学的时候,安装个环境就要我小命了。
如果选择 Linux 开发的到这里环境也就可以了,习惯 VScode 的咱再往下安装一步。
2、安装 Go 插件。我想不知道要安装哪个的可以去反思一下自己。
3、设置 go 的代理,然后它会自动把该安装的包给你安装上。
go env -w GOPROXY=https://goproxy.cn,direct
4、那么环境就安装好了,让我们来问候一下世界:
package main
import "fmt"
func main()
fmt.Println("Hello, World!")
运行脚本:
go run hello.go
或者你想编译一下再运行也行:
go build hello.go
./hello
如果报了什么 mod 的错,go mod init 文件夹名
代码简要讲解
简要讲解一下上面的代码,提供一个缓冲的过程。
package main //定义了包名。你必须在源文件中非注释的第一行指明这个文件属于哪个包
//每个 Go 应用程序都包含一个名为 main 的包。
import "fmt" //导入 fmt 包
/*
程序开始执行的函数。main 函数是每一个可执行程序所必须包含的,
一般来说都是在启动后第一个执行的函数(如果有 init() 函数则会先执行该函数)。
*/
func main()
/* 这是我的第一个简单的程序 */
fmt.Println("Hello, World!") //输出函数
/*
当标识符(包括常量、变量、类型、函数名、结构字段等等)以一个大写字母开头,那么使用这种形式的标识符的对象就可以被外部包的代码所使用
(客户端程序需要先导入这个包),这被称为导出;
标识符如果以小写字母开头,则对包外是不可见的,但是他们在整个包的内部是可见并且可用的。
*/
包
每个 Go 程序都是由包构成的。
导入
对于包的导入,可以用圆括号将所有要导入的包写在一起,如:
package main
import (
"fmt"
"math"
)
func main()
fmt.Printf("Now you have %g problems.\\n", math.Sqrt(7))
也可以依次单个导入,如:
package main
import "fmt"
import "math"
func main()
fmt.Printf("Now you have %g problems.\\n", math.Sqrt(7))
导出名
在 Go 中,如果一个名字以大写字母开头,那么它就是已导出的。例如,Pizza 就是个已导出名,Pi 也同样,它导出自 math 包。
pizza 和 pi 并未以大写字母开头,所以它们是未导出的。
在导入一个包时,你只能引用其中已导出的名字。任何“未导出”的名字在该包外均无法访问。
执行代码,观察错误输出:
package main
import (
"fmt"
"math"
)
func main()
fmt.Println(math.pi)
然后将 math.pi 改名为 math.Pi 再试着执行一次。
标识符
标识符用来命名变量、类型等程序实体。一个标识符实际上就是一个或是多个字母(A~Z和a~z
)数字(0~9
)、下划线_组成的序列,但是第一个字符必须是字母或下划线而不能是数字。
关键字 和 预定义标识符
这不用去记,按我的方法来起名字(凡是起名必带 _)是不可能和这些关键字冲突的。
基本类型
Go 的基本类型有
bool
string
int int8 int16 int32 int64
uint uint8 uint16 uint32 uint64 uintptr
byte // uint8 的别名
rune // int32 的别名
// 表示一个 Unicode 码点
float32 float64
complex64 complex128
package main
import (
"fmt"
"math/cmplx"
)
var (
ToBe bool = false
MaxInt uint64 = 1<<64 - 1
z complex128 = cmplx.Sqrt(-5 + 12i)
)
func main()
fmt.Printf("Type: %T Value: %v\\n", ToBe, ToBe)
fmt.Printf("Type: %T Value: %v\\n", MaxInt, MaxInt)
fmt.Printf("Type: %T Value: %v\\n", z, z)
本例展示了几种类型的变量。 同导入语句一样,变量声明也可以“分组”成一个语法块。
int, uint 和 uintptr 在 32 位系统上通常为 32 位宽,在 64 位系统上则为 64 位宽。 当你需要一个整数值时应使用 int 类型,除非你有特殊的理由使用固定大小或无符号的整数类型。
变量与常量
变量声明
var 语句用于声明一个变量列表,格式为:
例:
var num int = 10
其中“类型”或“= 表达式”两个部分可以省略其中的一个。
1)根据初始化表达式来推导类型信息
2)默认值初始化为0。
例:
var num int // var num int = 0
var num = 10 // var num int = 10
既然说是列表,那就是可以一次性声明一串儿:
package main
import "fmt"
var c, python, java bool
func main()
var i int
fmt.Println(i, c, python, java)
变量声明可以包含初始值,每个变量对应一个:
package main
import "fmt"
var i,j = 1,2
func main()
var c,python,java=true,false,"no!"
fmt.Println(i, j, c, python, java)
短变量声明
在函数中,简洁赋值语句 := 可在类型明确的地方代替 var 声明。
函数外的每个语句都必须以关键字开始(var, func 等等),因此 := 结构不能在函数外使用。
package main
import "fmt"
func main()
var i, j int = 1, 2
k := 3
c, python, java := true, false, "no!"
fmt.Println(i, j, k, c, python, java)
类型转换
表达式 T(v) 将值 v 转换为类型 T。
一些关于数值的转换:
var i int = 42
var f float64 = float64(i)
var u uint = uint(f)
或者,更加简单的形式:
i := 42
f := float64(i)
u := uint(f)
与 C 不同的是,Go 在不同类型的项之间赋值时需要显式转换。
常量
1、常量的声明与变量类似,只不过是使用 const 关键字。
2、常量可以是字符、字符串、布尔值或数值。
3、常量不能用 := 语法声明。
package main
import "fmt"
const Pi = 3.14
func main()
const World = "世界"
fmt.Println("Hello", World)
fmt.Println("Happy", Pi, "Day")
const Truth = true
fmt.Println("Go rules?", Truth)
流程控制
for 循环
Go 只有一种循环结构:for 循环。
package main
import "fmt"
func main()
sum := 0;
for i:=0;i<10;i++
sum += i
fmt.Println(sum)
如果没有初始化语句和后置语句的话,可以将那两个分号也不要:
package main
import "fmt"
func main()
sum := 1
for ; sum < 1000;
sum += sum
fmt.Println(sum)
如果连循环条件都省去了,该循环就不会结束,因此无限循环可以写得很紧凑:
package main
func main()
for
break、continue、goto 依旧是有的。
if 分支
Go 的 if 语句与 for 循环类似,表达式外无需小括号 ( ) ,而大括号 则是必须的。
同 for 一样, if 语句可以在条件表达式前执行一个简单的语句。
package main
import (
"fmt"
"math"
)
func pow(x, n, lim float64) float64
if v := math.Pow(x, n); v < lim
return v
return lim
func main()
fmt.Println(
pow(3, 2, 10),
pow(3, 3, 20),
)
该语句声明的变量作用域仅在 if 之内。(在最后的 return 语句处使用 v 看看。)
在 if 的简短语句中声明的变量同样可以在任何对应的 else 块中使用:
package main
import (
"fmt"
"math"
)
func pow(x, n, lim float64) float64
if v := math.Pow(x, n); v < lim
return v
else
fmt.Printf("%g >= %g\\n", v, lim)
// 这里开始就不能使用 v 了
return lim
func main()
fmt.Println(
pow(3, 2, 10),
pow(3, 3, 20),
)
注:注意这里的写法,else 是要和 在一行的,不信可以换个行试试。
switch 分支
switch 是编写一连串 if - else 语句的简便方法。它运行第一个值等于条件表达式的 case 语句。
Go 只运行选定的 case,而非之后所有的 case。 实际上,Go 自动提供了在这些语言中每个 case 后面所需的 break 语句。Go 的另一点重要的不同在于 switch 的 case 无需为常量,且取值不必为整数。
package main
import (
"fmt"
"runtime"
)
func main()
fmt.Print("Go runs on ")
switch os := runtime.GOOS; os
case "darwin":
fmt.Println("OS X.")
case "linux":
fmt.Println("Linux.")
default:
// freebsd, openbsd,
// plan9, windows...
fmt.Printf("%s.\\n", os)
讲真,我几乎没有用过 Switch。
defer
defer 语句会将函数推迟到外层函数返回之后执行。
推迟调用的函数其参数会立即求值,但直到外层函数返回前该函数都不会被调用。
package main
import "fmt"
func main()
defer fmt.Println("world")
fmt.Println("hello")
推迟的函数调用会被压入一个栈中。当外层函数返回时,被推迟的函数会按照后进先出的顺序调用。
package main
import "fmt"
func main()
fmt.Println("counting")
for i := 0; i < 10; i++
defer fmt.Println(i)
fmt.Println("done")
高级结构
指针
Go 拥有指针。指针保存了值的内存地址。
类型 *T
是指向 T
类型值的指针。其零值为 nil
。
var p *int
& 操作符会生成一个指向其操作数的指针。
i := 42
p = &i
*
操作符表示指针指向的底层值。
fmt.Println(*p) // 通过指针 p 读取 i
*p = 21 // 通过指针 p 设置 i
这也就是通常所说的“间接引用”或“重定向”。
与 C 不同,Go 没有指针运算。
结构体
一个结构体(struct)就是一组字段(field),结构体字段使用点号来访问。
package main
import "fmt"
type Vertex struct
X int
Y int
func main()
v := Vertex1, 2
v.X = 4
fmt.Println(v.X)
数组
类型 [n]T 表示拥有 n 个 T 类型的值的数组。
var a [10]int
会将变量 a 声明为拥有 10 个整数的数组。
数组的长度是其类型的一部分,因此数组不能改变大小。这看起来是个限制,不过没关系,Go 提供了更加便利的方式来使用数组。
package main
import "fmt"
func main()
var a [2]string
a[0] = "Hello"
a[1] = "World"
fmt.Println(a[0], a[1])
fmt.Println(a)
primes := [6]int2, 3, 5, 7, 11, 13
fmt.Println(primes)
如果数组长度不确定,可以使用 … 代替数组的长度,编译器会根据元素个数自行推断数组的长度:
var balance = [...]float321000.0, 2.0, 3.4, 7.0, 50.0
或
balance := [...]float321000.0, 2.0, 3.4, 7.0, 50.0
切片
每个数组的大小都是固定的。而切片则为数组元素提供动态大小的、灵活的视角。在实践中,切片比数组更常用。
类型 []T
表示一个元素类型为 T 的切片。
切片通过两个下标来界定,即一个上界和一个下界,二者以冒号分隔:
a[low : high]
它会选择一个半开区间,包括第一个元素,但排除最后一个元素。
以下表达式创建了一个切片,它包含 a 中下标从 1 到 3 的元素:
a[1:4]
切片的底层
1、切片并不存储任何数据,它只是描述了底层数组中的一段。
2、更改切片的元素会修改其底层数组中对应的元素。
3、与它共享底层数组的切片都会观测到这些修改。
(看这个描述,如果有C语言功底,很快就能想到指针实现切片的方法吧)
package main
import "fmt"
func main()
names := [4]string
"John",
"Paul",
"George",
"Ringo",
fmt.Println(names)
a := names[0:2]
b := names[1:3]
fmt.Println(a, b)
b[0] = "XXX"
fmt.Println(a, b)
fmt.Println(names)
切片的默认行为
会 Python 的小伙伴对切片应该不陌生, 在进行切片时,你可以利用它的默认行为来忽略上下界。
切片下界的默认值为 0,上界则是该切片的长度。
对于数组
var a [10]int
来说,以下切片是等价的:
a[0:10]
a[:10]
a[0:]
a[:]
切片的长度与容量
切片拥有 长度 和 容量,切片的长度就是它所包含的元素个数。,切片的容量是从它的第一个元素开始数,到其底层数组元素末尾的个数,切片 s 的长度和容量可通过表达式 len(s) 和 cap(s) 来获取。
(刚刚有思考切片的实现原理的朋友很快也能反应过来吧)
package main
import "fmt"
func main()
s := []int2, 3, 5, 7, 11, 13
printSlice(s)
// 截取切片使其长度为 0
s = s[:0]
printSlice(s)
// 拓展其长度
s = s[:4]
printSlice(s)
// 舍弃前两个值
s = s[2:]
printSlice(s)
func printSlice(s []int)
fmt.Printf("len=%d cap=%d %v\\n", len(s), cap(s), s)
我们可以通过将上面的 [:4] 换成 [:40] 再探究一波。
nil 切片
切片的零值是 nil。
nil 切片的长度和容量为 0 且没有底层数组。
用 make 创建切片
切片可以用内建函数 make 来创建,这也是你创建动态数组的方式。
make 函数会分配一个元素为零值的数组并返回一个引用了它的切片:
a := make([]int, 5) // len(a)=5
要指定它的容量,需向 make 传入第三个参数:
b := make([]int, 0, 5) // len(b)=0, cap(b)=5
b = b[:cap(b)] // len(b)=5, cap(b)=5
b = b[1:] // len(b)=4, cap(b)=4
package main
import "fmt"
func main()
a := make([]int, 5)
printSlice("a", a)
b := make([]int, 0, 5)
printSlice("b", b)
c := b[:2]
printSlice("c", c)
d := c[2:5]
printSlice("d", d)
func printSlice(s string, x []int)
fmt.Printf("%s len=%d cap=%d %v\\n",
s, len(x), cap(x), x)
切片的切片
切片可包含任何类型,甚至包括其它的切片。
package main
import (
"fmt"
"strings"
)
func main()
// 创建一个井字板(经典游戏)
board := [][]string
[]string"_", "_", "_",
[]string"_", "_", "_",
[]string"_", "_", "_",
// 两个玩家轮流打上 X 和 O
board[0][0] = "X"
board[2][2] = "O"
board[1][2] = "X"
board[1][0] = "O"
board[0][<以上是关于Go 语言入门三部曲:能看懂 Go 语言的主要内容,如果未能解决你的问题,请参考以下文章