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)
	
	// 这里开始就不能使用 vreturn 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 语言的主要内容,如果未能解决你的问题,请参考以下文章

Go 语言入门三部曲:能用 Go 写“生产-消费者”模型

Go语言入门150题 L1-045 宇宙无敌大招呼 (5 分) Go语言|Golang

Go语言基准测试(benchmark)三部曲之二:内存篇

Go语言基准测试(benchmark)三部曲之三:提高篇

Go语言基准测试(benchmark)三部曲之一:基础篇

Go 言 Go 语,一文看懂 Go 语言文件操作