赣州新闻网

首页 / 科技

学习Golang-从零到大师

2020-04-10 · 赣州新闻网 · 科技


学习Golang-从零到大师

Pic: Gopher mascot and old logo


让我们从Go(或Golang)的简短介绍开始。 Go是由Google工程师Robert Griesemer,Rob Pike} @ x和Ken Thompson设计的。 它是一种静态类型的编译语言。 第一个版本= . -于2012年3月作为开源发布。

\" Go是一种开放源代码编程语言,可轻松构建简单,可靠且高效的软件\"。

—Q ! P , Goc C NLang

在许多语言中,有很多方法可以解决给定的问题_ p n b / ^ f H。 程序员可能会花费大量时间来思考解v b _ F 2 * S x决问题的最佳方法。

另一方面,Go相信更少o } r q的功能-只有一种正确的方法来解决问题。

这样可以节省开发人员时k T : s l间,并使大型代码库易于维护。 Go中没有诸如地图和过滤器之类的\"表达性\"功能。

\"当您具有增加表达力的功能时,通常会增加I I !费用\"

—罗伯派克

学习Golang-从零到大师

Recently published new logo ofI p m _ 8 go lang:

入门

Go由包组成。 包主体告诉Go编译器该程序h T & W 7被编译为可执行文件,而不是共享库。 它是应用程序的入口点。 主程序包l 4 C C p *定义为:x / ^ ` o a y H N

package main


让我们通过在Go工作区中创建一个文件main.go来编写一个简单的hello wG _ + Q s y 6 9orld示例v v r * t。

工作空间

Go中的工作N v &空间由环境变量GOPATH定义。

您编写的任何代码均应写入工作H ( / r空间内。 Go将搜索GOPATH目录或GOROOT目录(在安装Go时a = |默 I @ # [ 1认设置)中的所有软件包。 GOROOT是安装go的路R Q s M 2径2 = + 8。

将$ N y N J z # mGOPATH设置为所需的目录。 现在,让我们将其添加到〜/ workspace文件夹中。

# expon S 9 v ! : _ q 7rt env
ex9 L v u f . j Gport GOPATH=~/workspace
# go inY ` J V * }side the workspace directory
cP A ; 4 _ ( E b Md ~/workspace

在我们刚刚创建的工作空间文件夹中,使用以下代码创建文件main.go。

你好,世界!

package main

imh i yport (- Z ) 6
\"fmt\"
)

func main(){
fmt.Prind 2 k Z W [ ; g stln(\"Hello World!\")
}

在上面的示例中,fmt是Go中的内置程序包,它实现用于格式化I /l T 5 Z O的功能。

我们使用import关键字在Go中导入包。 func main是执行代码的主要入口点。 Println是fmt软4 b { B件包中的一个函数,可为我们打印\" hello world\"。

让我们看看运行此文件j U { F ) r ~。 我们可以通过两种方式运行Go命令。 众所周知,Go是一种编译语言,因此我们首先需要对其进行编译,然后再执行。

> go build main.go


这将创建一个二进制可执行文件main,现在我们} f q O可以运行它:

> ./main 
# Hello World!


还有另一种更c V T简单的方法来运行程序。 go run命令有助于抽象编译步骤。 您只需运行以下H 5 7 * 命令即可执行该程序。

go run main.go 
# Hd k J k Dello World!


注意:要试用此博客中提到的代码,可以使用

变量

Go中的变量被明确声明。 Go是一种静态类型的语言。 这意味着在变量声明时检查变量类型。 变量可以声w L * * ~ l 6 !明为:

var a int


在这种情况下,该值将设置为0。使用以下语法声明和初始化具有不同值的变量:

var a = 1


此处,T g & V n K B [ }变量自动分配为int。 我们可以为变量声明使用简写定义:

message := \8 q f 9 ( 8 {"hello world\"


我们还可以在同一行中声明多个变量:

var b, c int = 2, 3


资料类型

像任何其他编程语言一样,Go支持各种不同的数据结构。 让我们探索其中{ n - d L :的一些:

数字,字符串和布尔值

一些受支持的数字存储类型为int,int8,int16,int32,int64,uint,uint8,uint16,uint32,uint64,uintptr…

字符串类型存储字节序列。 它用关键字字符串表示和声明。

使用_ } 0 % }关键字bool存储布尔值。

Go还支持复数类型数据类型,可以使用complex64和complex128进行声明。

var a bool = true
var b int = 1
var c string = \'hello world\'
var d float32 = 1.222
vart [ L Y x complex* 8 J S H 128 = cmplx.[ 4 D d NSqr- G =t(-5 + 12i)


数组,切片和Map

数组是具有相同数据类型的元素序列。 数组在声明时定义了固定的长度,因此不能扩展得更多。 数组声明为:

var a int


数组也可以是多维g - | { U } ]的。 我们可以简单地使用以下格式创建它们:

var multiD int


对于运行时数组值更改的情况,数组是有限制的。 数组也不0 - x . 提供获取子数组的能力C ` 6 m f ? + !。 为此,Go具有称为切片的数据类型。

切片存储一系列元素,并且可以随, 2 U 9 u ^时扩展。 切片声明类似于数组声明-未定义容量:

var b intl # 8 x , | -


这将创建零: ! p z Z 1 X :容量和零长度的切片。d S 7 z h V g 切片也可以用容量和长度来定义。 我们可以使用以下语法:& h ~ A + @

numbers := make(int,5,10)


在此,切片的初始长度为Y & ?5,容量 ] U ` ~ U k为10。

切片是对数组的抽象。z Z 3 k { U _ 切片使用数组作为基础结构。 切片包含三个组成部分:容量,长度和指向基础数组的指针,如s % F { B [ u下图所示:

学习Golang-从零到大师

image src:


可以通过使用附加或复制功能来增加切片的容量。 附加函数将值添加到数组的末尾0 v V } H r ? K C,并在需要时增加容量。

numbers = append(numbers, 1, 2, 3, 4)


增加切片容量的另一种方法是使用复制功能。 只需创建另一个更大容量的0 l , 2 F ! 2 E ~切片并将原始切片复制到新创建的切片即可:

// create a n)  N P Gew slice
number2 := make(int, 15)
// copy the original slice to new slF } X M v j 1 &ice
copy(number2, number)


我们可以创建切Z ) * X { Y , [片的子切片。 只需使用以下命令即可完成此操作:

// inf 2 o $ | k N ^ =itialy : | : , r vizA # B _ * N ee& & # S ( { d ) h a slice with 4 len and values
number2 = int{1,2,3,4}
fmt.Println(numbers) // ->
// cr7 4 a h 8 $eate sub sl9 ~ ! J iices
slice1 := number2
fmt.Println(slice1) // ->
slice2 := number2
fmt.Println(slice2) // ->
slice3 := number2E 7 d @
fmd 8 8 S X rt.Println(slice3) // ->


Map是Go中的一种数据类型,它将键映射到值。 我们可以使用以下命令定义地图:

var m mapint


这里m是新的mapd i ~变量,其键为字符串,值是整数。 我们可以轻松地向地图添加键和值:

// adding key/value
m = 2
m = 3
//` [ O { _ B [ printing the values
fmt.Println(m) // -> 2
fmt.Println(m) // ->! i t 6 H; 3

类型转换

可以使用类型转换将一种类型的数据类型n o c U 9 z H转换为另一种类型。 让我们看一个简单的类型转换:

a := 1X @ T o [.1
b := int(a)
fmt.Println(b)
//-> 1


并非所有类型的数据类型都可以转换为另一种类型。 确保数据类型与转换兼容。

条件语句

if/else

对于条件语句,我们可以使用if-else语句,如下例所示。 确保花b { D N括号与条件在同一行。

if num := 9; num < 0 {
fmt.Println(num, \"is negative\")
} elsep J & 6 lt; 10 {
sum += 1
i++
}
fmt.Println(sum)


上面的示例类似于C中的while循环。相同的i P n e for语句可用于普通的for循环

sum := 0
for i := 0; i < 10; i++ {
sum += i
}
fmt.Println(sum)


Go中的无限循环:

for {
}* 5 v * g 6


指针

Go提供了指针。 指针是存放值地址的地方。 指针由*定义。 根据数据类型定义指针。 例:

var ap *int


ap是指向整数类型的指针。 &运算符可用于获取变量的地址。

a := 1H  ^2
ap = &a


指针指向的值可以使用*运算符进行访问:

fmt.Println(*ap)
// => 12


在将结构作为参数传递或为定义的类型声明方法时,通常首选指针。

传递值时,实际上会复制该值,这意味着需要更多内存

传递指针后,函数更改的值会在方法/函数调v U K用器中反映m m ~ 出来。

例:

funO u Dc increment(i *int) {
*i++
}& } 0 o ] l a d

func main()[ - { k t r = 5Println(add(J ^ b2, 1))
}
//=> 3


这里c被定义为返回变量。 因此,定义的变量c将自动返回,而无需在最后的return语句中定义。

您也可以从单个函数中返回多个返回值,这些函数用逗号分隔返回值。

func add(a int, b in{ v o * g O St) (int, string) {
c := a + b
return c, \"successfully added\"
}
func main() {
sum, message :=3 - U add(2, 1)
fmO j N ot.Println(message)
fmt.Prin| w = [ I A K K ;tln(sum)
}


方法,结构和接口

Go并不是一种完全面向对象的语言,但是具有结构,接口U / O @ :和方法,它具有很多面f p U % x { O E c向对象的支持和感觉。

结构

结构是不同字段C ! #的类型化集合。 结构用于将数据分组在D ; 一起。 例如,如果我们要对Person类型的数据进行分组,则定义一个person的属T | 0 E V z F . L性,其中可以包括姓名,年龄,性别。 可以使用以下语法定义结构:

type persT H $ L | K v *on struct {
name string
age int
gender string
}


定义了人员类型结构后,现在创建一个人员:

//way 1: specifying attribute and value
p = person{name: \"Bob\", age: 42, gender: \g [ n"Male\"}
//way 2: specifying only value
person{\"Bob\", 42,[ H { \"Male\"}


我们可以使用点(.)轻松访问这些数据。

p.name
//=&j b 1 M 6 ; M .gt; Bob
p.age
//=> 42
p.gender
//=> Male


您还可以直接通过结构体的指针访问结构体的属性:

pp = &personO - ~ J `{name: \"Bob\", age: 42,F e  i J & # ` gender: \"MaV a 2l, X C 0 ? [ ke\"}
ppk f S O i J ? # =.name
//=>k 4 R y Z m % Bob


方法

方法是带有接收器的一种特殊e w m | B M 0 , W改,它将反映在接收器pp中。它也不会创建该对象的新副本,从而节省了内存。

请注意,在上面的示例中,agD E C F q H Xe的值已更改,而name的值未更改,因为- S c A r p ]方法setName是接收者类型,而setAge是指针类型。

介面

Go接口是方法的集合。 接口可将类型的属性组合在一起。 让我们以界面动物为例:

type animal interface {
description() string
}


这里的动物是接口类型。 现在,让我们创建2种不同类型的动物,它们实现动物接口类型:

packr 0 ^ V u ^age main

import (
\"fmt\"
)

type animal interface {
description() string
}

typep & ( cat struct {
Type string
Sound string
}

type snake struct {
Type string
Poisonous bool
}B z f

func (s snake) descrD i + h iiption() string {
return fmt.Sprintf(\"Poisonous: %v\", s.Poisonous)
}

func (c cat) description() string {
return fR ) W ) _ e ~ smt.Sprintf(\"Sound: %v\", c.Sound)
}

func main() {
var a animal
a = snake{Poisonous: true}
fmt# K H Y t { Z r n.Println(a.description())
a = cat{Sound: \"Meow!!!\"}
fmt.Println(a.description())
}

//=> Poisonous: true
//=&g1 Y h O / @ ; i :t; Sound: Meow!!!


在mai` K T T y ! -n函数中,我们创建一个animal类型的变量a。 我们为动物指定蛇和猫的类型,并使用Println打印a.description。 由于我们以不同的方式实现了在两种类型(猫和蛇)中描述的方法z W D x p ~ O L L,因此我们获得了对动物的描述。

配套

我们将所有代码都包装在Go中。 主程序包是程序执行的入口点。 Go中有很多内置软件包。 我们一直使用的最著名的是fmt软件包。

\" go软件包提) b h z N供了大型编程的主要机制中0 / & s m的Go软件包,它们使将大型项目分成较小的部分成为可能。\"—罗伯特格里塞默尔

安装套件

go get <package-url-github>
// example
go get github.com/s J vckage


要创建自定义程序包,我们需要首先创建一个_ ~ Z : Y t 8 p具有所需程序包名称的文件夹。 假设我们正在建立一个打包人员。 为此,我们在custom_pack] ! F 9 ] $ )age文件夹中创建一个名为pe~ C J V Z 3 E M ,rson的文件夹:

>u ~ m # 2 Y n 7 mkdir person> cd person


现在,K 1 ~ ^在此文件夹中创建一个文件person.go。

package person
func Description(name string) string {
return2 4 G u H X M 4 t \"The person name is: \" + name
}
func secretName(name string) string {
return \"Do not sharea Y + - h @ k ^ ;们已经使用该软件包将其打印输Q R 9 j y 5 o S出到stdout。

json

Go中另一个有用的软件包是json* 5 E 5 r : k软件包。 这有助于对JSON进行编* 5 } n V _ s C #码/解码。 让我们以一个示例来对一些json进行编码/解码:! } X w o c K L ;

编码

package main

import (
\"X , c _ e 3 Y .fmt\"
\"encoding/json\"
)

f! G & ^ `unc main(){
mapA := map% x c j 8 | eint{\"apple\K K ~ 4 9 A :": 5, \"lettuce\": 7}
mapB, _ := json.Marshal(mapA)
fmt.Println(striK = J g y ? . +ng(mapB))
}


解码

package mN 9 Gain

import (
\"fmt\"
\"encoding/json\"
)

type response struct {
PageNi b ? # 2 - 5 Zumber int `json:\"page\"`
Fruits string `json:\"fruits\"`
}

func main(){
str := `{\"page\": 1,Y . p , m 6 f % @ \"fruits\": }`
res := response{}
json.Unmarsh2 l 9 b K 6 s #al(byte(str), &ag r A - F Z !mp;res)
fmt.Prin, 3 .tln(res.PageNumb - ~ ,ber)
}
//=> 1


当使用unmarshal解码jso2 s E ; n w 2 * k R址。 请注意,json:\" page\"将页面密+ B H V r & #钥映射到结构中的PageNumber密钥Q X q W ) o。

错误处理

错误是程序的意外结果。 假设我们正在对外部服务进行API[ = I ^ % # Cexample.com/\f b _ ] s Z A")


在这里,对错误对象的API k 7 & m ~ +s.New(\"math: cannot process negative number\")
}
return (4 9 Q d @ + & dn + 1), nil
}

func main() {
num := 5

if inc, err := Increment(num); err != nil {
fmt.Printf(\R { = F - q"FaG / $ ^ ^ ! [iled Number: %v, error meH t b @ Q f . % Xssage: %v\l E R", num, err)
}else {
fo k w P r , u ^mt.Printf(\"Incremented Numb: o $er: %v\", inc)
}
}


Go中内置的大多数程序包或我们使用的外部程序包都有错9 e o H 1 ( q G n误处理机制。 因此,我们调用的任何函数都可能存在错误。 就像在上+ m s & b p , A面的示例中所做的那样,这A $ ;些错误绝不能忽略,并且总是在我们调用这些函数的地方进行适当地处理。

panic j + S x V

panic是无法处理的,在程序执行过程中会突然遇到J w Y R n 9 l H。 在Go中,panic不是处理程序中异常的理想方法。 建议改用错误对象。 发生panic时,程序执行将停止。 panic后被执行的事情是推迟。m u 0 c g

defer

defer是始终在函数末) T k尾执行的0 M 5 R $东D / B # Q Q M v 8西。

//Go
package main

import \0 Q v z B L D"fmt\"

func main() {
f()
fmt.PrinK ` D ; } _ R stln(\"Returned normally from f.\")
}

func f() {
defer func() {
if r := recover(); r != nil {
fmt.Println(\"Recovered in f\", r)
}
}()
fmt.Println(\"Calling g.\")
g(0)
fmt.Prm 3 c Bintln(\) ^ J o T 2"Returned normally from g.\")
}

func g(i int) {
if i > 3 {
fmt.Println(\"Panicking!\2 , #")
panic(fmt.Sprintf(A W r! 3 4 0 ? t K )\"%v\", i))
}
defer fmt.PrL H 7 n y hintln(\"Defer in g\", i)
fmt.Println(\ w } g"PrintingL W ~ = R X w I in g\", if 9 l)
g(i + 1)
}


在上面的示例中,我们使用panic()恐慌了程序的执行H { : _ } 9 %。 您会注意x O w { B g到,有一个defer语句,它将使程序在程序执行结束时执行该行。 当我们需要在函数末尾执N M P @ n N行某些操作(例R , Z c 3 5 b | |如关闭文件)时,也可以使用Defer。% b w ) g

并发

Go在构建时考虑了并发性。 Go中的并发可以通过轻量级线程Go例程来实现。

例行

Go例程是可以与另一个函数并行或同时运行的函数。 创建Go例程非常简单。 只需在函数前面添加关键字Go,就可以使其并行执行。 Go例程非常轻巧,因此我们可以Z ~ 7 6 ( +创建数千个例程。 让我们看一个简单的例子:

package main
import (
\"fmt\"
\"time\"
)
func main() {
go c()
fmt.Println(\"I am main\")
time.Sleep(time.D c - Z R 1 m @Second * 2)
}

func c() {
time.Sleep(time.Second * 2)
fmt.Println(\"I am concurrent\")
}
//=> I am main
//=> I am concurrent

如上例所示,函数c是Go例程,与Go主线程并行执行。 有时我们想在多个线L . . , x [ e程之间共享资源。 Go倾向于不与另一个线程共享变量,因为这会增加死锁和资源等待的机会。 在Go例程之间共享资源的另一种方法是:通过go通道。

channel

我们可以使用通道在两个Go例程之间传递数据( 3 [ f q 8 ? j。 创建通道时,必须指定通道接收的数据类型。 让我们创建一个具有字符串类型的简单频道,如下所示:

c := make(chan string)


通过此通道,我们可以发送字符串类型的数据。 我们可以在此通道中发送和接收数据E W s , - u 8:

package main

import \"fmt\"

func main(mt.Println(s2)
}
}

func speed1(ch chan string* 5 w k f ! x l) {
time.Sleep(2 * time.Second)
ch <- \"speed 1\"
}

func speed2O t O t(ch chan string) {m Z ;
time.Sleep(1 * time.Seco) 9 R V P c L wnd)
ch <- \"speed 2\"
}


在上面的示例中,主电源在} L 7 T J s两个通道c1和c2上等待。 使用select case语句打印主要功能,然后从该通道中首先接收的那个消息发送消息。

缓冲通道

您可以在Golang中创建一个缓( G } b K _ _ C冲通道。 对于缓冲的通道,如果缓冲区已满,则将阻止发送到该通道的消息。 让我们看一个例子:

package main
import \"fmt\"
func main(){
ch := make(chan string, 2)
ch <- \"hello\"
ch <- \"world\"
ch <- \"!\" // extra messs oage in buffer
fmt.Pg 2 %rintln(<-ch)
}
// => fh W w = v S %atal error: all goroutines areT h b 9 ( aslej { U 3ep - deadlock!

为什么Golang成功?

简单……— Rob-pike

!

我j G 0 9 6 S U R们了解了Go的一些主要组件和功能。

变量,数据类型

数组切片和Map

函数d E | _ . p /

循环和条件语句

指针

配套

方法,结构和接口

错误处理

并发-Go例程和通道

恭喜,您现在对Gz : ;o有了一个不错的了解。

我最有生产力的日子之一就是扔掉1000行代码。

—肯汤普森

不要在这里停下来。 继续前进。 考虑一个小型应用程序,然后开始构建。

(本文翻译自Milap Neupane$ G ?的文章《Learning Go — from zero to hero》,参考:htu } 2tps://medium.com/free-code-camp/learning-go-from-zero-toX C /-hero-d2a3223b3d86)

确保项目工作文章接口设置维护兴国学习