教程集 www.jiaochengji.com
教程集 >  Golang编程  >  golang教程  >  正文 Go 语言从入门到精通

Go 语言从入门到精通

发布时间:2021-05-06   编辑:jiaochengji.com
教程集为您提供Go 语言从入门到精通等资源,欢迎您收藏本站,我们将为您提供最新的Go 语言从入门到精通资源

Go 语言从入门到精通

Go 语言是一门开源语言,能够轻松的构建简单,可靠,高效的软件。
—— Golang

在很多语言中,解决给定的问题通常有多种方式。工程师需要花费大量的时间思考什么才是解决问题的最优解法。而在Golang中,问题的解法通常只有一种。

这一特性大大节约了工程师的时间,而且使得维护大型代码库变得更容易。在Golang中没有maps和filter这样高消耗的特性。

语言的特性带来更好的表现力也带来代价。

——Rob Pike

1. 开始动手

Golang由包组成。 Golang编译器将main包编译为可执行文件,而非共享库。main包是应用的入口,通常被定义如下:

package main

下面看一个hello world 的例子,在Golang 的工作空间创建main.go文件。

1.1 工作空间

在Go语言中,工作空间由环境变量GOPATH定义。所有编写的代码需要在工作空间中。Go语言会在GOPATH和GOROOT的路径中搜索包。GoROOT是在安装的时候确定的安装路径。

下面来设置GOPATH,我们将~/workspace 加入工作空间。

# export env
export GOPATH=~/workspace
# go inside the workspace
cd ~/workspace

1.2 HELLO WORLD!

我们在刚才的工作空间下创建main.go 文件,代码如下:

package main
import (
	"fmt"
)
func main(){
    fmt.Println("Hello World!")
}

上面的例子中,fmt是Go内置的格式化I/O函数。

我们在Go语言中使用import关键字导入包,func main 是入口函数。Println是fmt包中函数,用于打印 “Hello World!”。

让我们开始运行该文件。众所周知Go是编译型语言,我们在运行之前先进行编译。

> go build main.go

这将会创建一个二进制运行文件main,我们现在来运行它:

> ./main
# Hello World!

另一种简单的方式是使用go run 命令:

go run main.go
# Hello World!

2. 变量

Go中的变量类型是显式指定的。Go语言是强类型语言,这意味着在变量声明的时候会检查变量类型。

变量定义如下所示:

var a int

在这个例子中,a的初始值被设置为0。用下面的方式可以定义并初始化变量。

var a = 1

这里的变量被编译器推断为int。更简单的变量定义如下所示:

message := "hello world"

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

var b, c int = 2, 3

3.数据类型

3.1 NUMBER,STRING, BOOLEAN

int 的类型有 int, int8, int16, int32, int64, unit, unit8, unit16, unit 32, unit64, unitptr…

String类型使用byte序列存储数据,用关键字string来声明变量。

bool 关键字表示布尔类型。

Golang 也支持复数,用conplex64和complex128表示。

var a bool = true
vat b int = 1
var c string = "hello world"
var d float32 = 1.222
var x complex128 = cmplx.Sqrt(-5  12i)

3.2 ARRAYS,SLICES,MAPS

Array 是同类型元素的数组。Array在声明的时候会指定长度且不能改变。一个数组的定义如下:

var a[5] int

也有多维数组,定义如下

var multiD [2][3]int

Slices 是能随时扩容的同类型元素的序列 。Slice的声明方式如下:

var b []int

这将会创建一个容量为0,长度为0的Slice。Slice也可以定义容量和长度,格式如下:

numbers := make([]int, 5, 10)

这个Slice初始长度为5,容量为10。

Slice是数组的封装,其内部实现是数组,slice有三个元素,容量,长度和指向内部数组的指针。

Slice的容量可以通过append 或者 copy函数增加。Append函数也能在数组的末尾添加元素,在容量不足的情况下会对slice扩容。

numbers = append(numbers, 12, 34)

另一种增加slice容量的方式是使用copy函数。Copy函数的原理是创建一个新的大容量的slice,并把原有的slice拷贝到新的slice中。

// 创建新的slice
number2 := make([]int, 15)
// 复制原有的slice到新的slice
copy(number2, number)

我们也可以创建slice的子slice。例子如下:

 package main

 import (
     "fmt"
 )

 func main() {
     // 初始化slice
     number2 := []int{1, 2, 3, 4}
     fmt.Println(number2) // -> [1 2 3 4]
     // 创建子slice
     slice1 := number2[2:]
     fmt.Println(slice1) // -> [3 4]
     slice2 := number2[:3]
     fmt.Println(slice2) // -> [1 2 3]
     slice3 := number2[1:4]
     fmt.Println(slice3) // -> [2 3 4]
 }

Go语言中的Map是键值对,定义如下:

var m map[string]int

m是定义的变量名,键的类型是string,值的类型是integers。Map中添加键值对的例子如下:

 package main

 import (
     "fmt"
 )

 func main() {
     m := make(map[string]int)
     // 添加键值对
     m["clearity"] = 2
     m["simplicity"] = 3
     // 打印值
     fmt.Println(m["clearity"])   // -> 2
     fmt.Println(m["simplicity"]) // -> 3
 }

4. 类型转换

使用类型转换能够改变数据类型,例子如下:

 package main

 import (
     "fmt"
 )

 func increment(i *int) {
     *i  
 }

 func main() {
     a := 1.1
     b := int(a)
     fmt.Println(b)
     //-> 1
 }

5. 条件表达式

5.1 IF ELSE

If else 的例子如下,需要注意的是花括号和条件表达式位于同一行。

 package main

 import (
     "fmt"
 )

 func increment(i *int) {
     *i  
 }

 func main() {
     if num := 9; num < 0 {
         fmt.Println(num, "is negative")
     } else if num < 10 {
         fmt.Println(num, "has 1 digit")
     } else {
         fmt.Println(num, "has multiple digits")
     }
 }

5.2 SWITCH CASE

Switch case 能组织多条件表达式,例子如下:

package main

 import (
     "fmt"
 )

 func increment(i *int) {
     *i  
 }

 func main() {
     i := 2
     switch i {
     case 1:
         fmt.Println("one")
     case 2:
         fmt.Println("two")
     default:
         fmt.Println("none")
     }
 }

6. 循环

Golang中只有一个循环表达的关键字,不同形式的循环表达式如下:

package main

 import (
     "fmt"
 )

 func increment(i *int) {
     *i  
 }

 func main() {
     i := 0
     sum := 0
     for i < 10 {
         sum  = 1
         i  
     }
     fmt.Println(sum)
 }

上面的例子和C语言中的while循环类似,更为正式的循环表达形式如下:

 package main

 import (
     "fmt"
 )

 func increment(i *int) {
     *i  
 }

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

Go 语言中的死循环定义如下:

for {
}

7. 指针

Go 语言可以使用指针,指针存储变量的地址,指针用*来定义。指针的定义和所指数据的类型相关:

var ap *int

这里的ap是指向整型数据的指针,&用于获取所变量的地址。

a :=12
ap = &a

* 用于获取指针所指的地址的值。

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

指针通常用于将结构体做为参数传递。

  1. 传值通常意味着拷贝,意味着需要更多的内存。
  2. 使用指针传递时,在函数中改变的变量会传递给调用的方法或函数。
 package main

 import (
     "fmt"
 )

 func increment(i *int) {
     *i  
 }

 func main() {
     i := 10
     increment(&i)
     fmt.Println(i)
 }

 //=> 11

8. 函数

main 包中main函数是golang 程序的入口。我们可以定义多个函数并调用。例如:

 package main

 import (
     "fmt"
 )

 func add(a int, b int) int {
     c := a   b
     return c
 }

 func main() {
     fmt.Println(add(2, 1))
 }
 //=> 3

从上面的例子中我们可以看出,Golang 中的函数用func关键字加上函数名, 后面是附带数据类型的参数,最后是函数的返回类型。

函数的返回值可以被预先定义,例子如下:

 package main

 import (
     "fmt"
 )

 func add(a int, b int) (c int) {
     c = a   b
     return
 }

 func main() {
     fmt.Println(add(2, 1))
 }
 //=> 3

这里c定义为返回值,因此变量c将会被自动返回,无需在函数最后的return中声明。

你也可以定义一个多个返回值的函数,使用,进行分割。

 package main

 import (
     "fmt"
 )

 func add(a int, b int) (int, string) {
     c := a   b
     return c, "successfully added"
 }

 func main() {
     sum, message := add(2, 1)
     fmt.Println(message)
     fmt.Println(sum)
 }

 //=> successfully added
 //=> 3

8. 方法,结构体,接口

Golang 不是完全的面向对象语言,但是支持很多面向对象的特性,例如有结构体,接口,方法等。

8.1 结构体

结构体是有类型,不同变量的集合。例如我们想定义Person类型,其中包含姓名,年龄,性别。例如:

type person struct {
    name String
    age int
    gender string
}

定义好了person结构体后,我们现在来使用它:

//方式 1: 指定属性和值
p = person{name: "Bob", age: 42, gender: "Male"}
//方式 2: 只指定值
person{"Bob", 42, "Male"}

我们可以使用.符号访问这些属性:

p.name
//=> Bob
p.age
//=> 42
p.gender
//=> Male

你也可是使用指针访问结构体的属性:

pp = &person{name: "Bob", age: 42, gender: "Male"}
pp.name
//=> Bob

8.2 方法

方法是一种带有接受器的特殊函数。接收器可以是值或者指针。例子如下:

 package main

 import "fmt"

 // 定义结构体
 type person struct {
     name   string
     age    int
     gender string
 }

 // 定义方法
 func (p *person) describe() {
     fmt.Printf("%v is %v years old.", p.name, p.age)
 }

 func (p *person) setAge(age int) {
     p.age = age
 }

 func (p person) setName(name string) {
     p.name = name
 }

 func main() {
     pp := &person{name: "Bob", age: 42, gender: "Male"}
     pp.describe()
     // => Bob is 42 years old
     pp.setAge(45)
     fmt.Println(pp.age)
     //=> 45
     pp.setName("Hari")
     fmt.Println(pp.name)
     //=> Bob
 }

从上面的例子我们可以看出,使用.操作符调用方法,例如pp.describe。需要注意的是,接收器是指针的话,我们传递的是值的引用,这意味着我们在方法做修改将会反映到变量pp上。该不会创建对象的拷贝,将会节省内存。

从上面的例子我们可以看出,age的值被改变了,而name的值并没有改变。这是因为方法setName的接受器不是指针。

8.3 接口

Golang中的接口是方法的集合,接口有助于将同类型的属性组合起来,让我们一起来看一个anminal的接口。

 type animal interface {
     description() string
 }

这里的animal是接口类型,我们来创建两种类型的animal并实现接口。

 package main

 import (
     "fmt"
 )

 type animal interface {
     description() string
 }

 type cat struct {
     Type  string
     Sound string
 }

 type snake struct {
     Type      string
     Poisonous bool
 }

 func (s snake) description() string {
     return fmt.Sprintf("Poisonous: %v", s.Poisonous)
 }

 func (c cat) description() string {
     return fmt.Sprintf("Sound: %v", c.Sound)
 }

 func main() {
     var a animal
     a = snake{Poisonous: true}
     fmt.Println(a.description())
     a = cat{Sound: "Meow!!!"}
     fmt.Println(a.description())
 }

 //=> Poisonous: true
 //=> Sound: Meow!!!

在main函数中,我们创建可一个animal类型的变量a。我们把 snake和cat类型赋值给animal,使用Println 输出a.description。

我们在cat和snake中使用不同的方式实现了describe方法,我们得到了不同类型的输出。

9.包

在Golang中,我们的代码在某个包下。main包是程序执行的入口。在Go中有很多内置的包,例如我们之前用过的fmt包。

Go 的包机制是大型软件的基础,能够将大型的工程分解成小部分。

—— Robert Griesemer

9.1 安装一个包

go get 
// 例子
go get github.com/satori/go.uuid

安装的包保存在GOPATH的环境中,你可以在 $GOPATH/pkg 路径下看到安装的包。

9.2 创建一个自定义包

首先创建一个文件夹 custom_package:

> mkdir custom_package
> cd custom_package

创建自定义包的第一步是创建一个和包名相同的文件夹。我们要创建person包,因此我们在custom_package文件下创建person文件夹:

> mkdir person
> cd person

在该路径下创建一个文件person.go:

 package person

 func Description(name string) string {
     return "The person name is: "   name
 }

 func secretName(name string) string {
     return "Do not share"
 }

现在我们来安装这个包,这样我们就可以导入和使用它了:

> go install

接下来我们返回custom_package 文件夹中,创建一个main.go:

 package main

 import (
     "custom_package/person"
     "fmt"
 )

 func main() {
     p := person.Description("Milap")
     fmt.Println(p)
 }

 // => The person name is: Milap

在这里,我们可以导入之前创建的包person,需要注意的是在person包中函数secretName不能被访问,这是因为Go中小写字母开头的函数是私有函数。

9.3 生成包文档

Golang中有内置的功能支持包文档。运行下面的命令将生成文档:

godoc person Description

这将会为Description 函数生成文档,想要在web服务器上查看文档需要运行下面的命令:

godoc -http=":8080"

现在打开链接http://localhost:8080/pkg/将会看到我们刚才看到的文档。

9.4 Go 中内置的包

9.4.1 fmt

fmt包实现可标准的I/O函数,我们在之前的包中用过其中的打印输出函数。

9.4.2 json

Golang中另一个内置的重要包的是json,它能够对JSON进行编解码。

编码
 package main

 import (
     "encoding/json"
     "fmt"
 )

 func main() {
     mapA := map[string]int{"apple": 5, "lettuce": 7}
     mapB, _ := json.Marshal(mapA)
     fmt.Println(string(mapB))
 }

解码
package main

 import (
     "encoding/json"
     "fmt"
 )

 type response struct {
     PageNumber int      json:"page"
     Fruits     []string json:"fruits"
 }

 func main() {
     str := {"page": 1, "fruits": ["apple", "peach"]}
     res := response{}
     json.Unmarshal([]byte(str), &res)
     fmt.Println(res.PageNumber)
 }

 //=> 1

解码的时候使用Unmarshal方法,第一个参数是json字节,第二个参数是要映射的结构体的地址。需要注意的是json中的“page”对应的是结构体中的PageNumber。

10. 错误处理

错误是程序中不应该出现的结果。假设我们编写一个API调用外部的服务。这个API可能成功也可能失败。当存在错误是,Golang程序能够识别:

resp, err := http.Get("http://example.com/")

对API的 调用可能成功也可能失败,我们可以通过检查错误是否为空来选择处理方式。

 package main

 import (
     "fmt"
     "net/http"
 )

 func main() {
     resp, err := http.Get("http://example.com/")
     if err != nil {
         fmt.Println(err)
         return
     }
     fmt.Println(resp)
 }

10.1 从函数中返回自定义错误

当我们在自定义函数是,某些情况下会产生错误。我们可以使用error 对象返回这些错误:

package main

 import (
     "errors"
     "fmt"
 )

 func Increment(n int) (int, error) {
     if n < 0 {
         // return error object
         return 0, errors.New("math: cannot process negative number")
     }
     return (n   1), nil
 }

 func main() {
     num := 5
     if inc, err := Increment(num); err != nil {
         fmt.Printf("Failed Number: %v, error message: %v", num, err)
     } else {
         fmt.Printf("Incremented Number: %v", inc)
     }
 }

 // => The person name is: Milap

Go 内置的包,外部的包都有处理错误的机制。因此我们调用的函数都有可能产生错误。这些错误不应该忽略而是应该向上面的例子那样被优雅的处理。

10.2 Panic

Panic是程序运行中突然产生未经处理的异常。在Go中,panic不是合理处理异常的方式,推荐使用error对象代替。当Panic产生时,程序将会暂停运行。当panic被defer之后,程序才能继续运行。

//Go
 package main

 import "fmt"

 func main() {
     f()
     fmt.Println("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)

您可能感兴趣的文章:
想系统学习GO语言(Golang
Go语言的主要特性和发展影响
Go 语言一本通
Go 语言十年而立,Go2 蓄势待发
go run main.go 参数_Go语言入门:Hello world
Go 语言到底适合干什么?
[GO语言基础] 一.为什么我要学习Golang以及GO语言入门普及
Go语言爱好者周刊:第 78 期 — 这道关于 goroutine 的题
龙芯平台构建Go语言环境指南
GO语言零基础从入门到精通视频教程

[关闭]
~ ~