1

一、第一个 Go 程序

最近在闲来无事之际开始学习点 Go 语言,Go 语言在近几年很火,有 Docker 和 Kubernetes 这两大杀器做支撑,它已经是云计算领域主流的编程语言了,并且有蚕食部分 C、C++、Java 等领域的趋势,怀着一些对新技术的兴趣,我开始学习了 Go 语言,在对其有了简单的了解之后,我渐渐开始喜欢上这门语言了。

目前我在工作中所使用的主要的编程语言是 Java,Java 虽说优秀,但是我对其并没有太大的好感,主要是它啰嗦复杂,当初学习它也主要是为了找工作。嗯。。。有一丝丝惭愧。

看到 Go 语言的 logo 我就觉得比较新奇,瞬间心情都变得不错了(Go 语言的 logo 是一只地鼠,英文称 Gopher,现在很多 Go 开发者也称自己为 Gopher 了)。

好了,闲话说了这么多,开始学习吧。

在开始之前,你需要上 Go 官网下载 Go 语言所需要的开发工具包,然后安装,安装的步骤非常简单,跟一个普通的应用程序没有差别,这里不再赘述了。

然后再选择一个自己喜欢的 IDE,我自己使用的是 Goland,当然 Vscode,Atom,Vim 也都有相应的支持 Go 语言的插件,选择适合自己的即可。

还是以一个经典的 Hello World 为例子,开始第一个 Go 语言程序:

package main

import "fmt"

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

这段代码虽然简单,但是也包括了 Go 语言程序的基本结构:关键字 package 代表程序所在的模块(包),import 引入代码依赖,这两个关键字的作用和 Java 中的十分类似。关键字 func 声明一个函数,实现代码逻辑。

和其他编程语言最主要的差别是,Go 语言的启动入口 main 函数不支持任何返回值和参数,并且它必须要在名为 main 的 package 下,否则 main 只是一个普通的函数。

二、基本程序结构

2.1 变量与常量

Go 语言中的变量有下面几种声明的方式:

    //1.
    var a int
    a = 1

    //2.
    var b string = "2"

    //3. 短变量声明,自动类型推断
    c := 10

    //4.
    var d = 123

    //5.
    var (
        x int = 1
        y string = "1"
    )

可以看到,Go 语言的变量声明可以使用 var 关键字,后面跟变量名称和类型,也可以不设置变量的类型,Go 会自动推断。

比较特殊的一点是 Go 可以在一行代码中对多个变量赋值(和 Python 一样),这样的话常见的交换两个变量的值就可以写得非常简洁了:

m, n := 10, 20
m, n = n, m   //交换 m n 的值

Go 语言中的常量主要通过关键字 const 来实现,声明的方式一般有下面两种:

const ABC = "TEST"
const(
   A = 1 
   B
   C = "123"
)

2.2 数据类型

Go 语言中的数据类型有下面表格中的这些:

Go 数据类型
布尔值bool
字符串string
整数int int8 int16 int32 int64
无符号整数uint uint8 uint16 uint32 uint64 uintptr
字节byte //uint8 的别称
Unicode 字符rune // int32 的别称
浮点数float32 float64
复数complex32 complex64

数据类型和主流的编程语言其实没有太大的差别,只不过需要注意下面几点:

  • Go 语言中不支持隐式的类型转换,例如下面的代码是不会通过编译的:
   var a int = 1
   var b int64
   b = a            //报错
   
   b = int64(a)   //只能这样转换
  • 别名和原有类型也不能进行隐式的类型转换
type MyInt int

func main() {
   var a int = 1
   var b MyInt
   b = a            //报错
}
  • Go 语言中的 string 类型的默认值是空字符串而不是 nil
  var s string
  fmt.Println("*" + s + "*") //初始化
  fmt.Println(len(s))
  fmt.Println(s == "")       //true

2.3 运算符

Go 中的运算符其实没什么可说的,如果你有其他编程语言的经验,那么可以完全忽略掉这一部分无关紧要的内容,因为它和其他主流的编程语言没有什么差别。

第一类运算符是算术运算符,有下列几种:

这里需要注意一点的是,Go 语言中不支持前置的自增和自减运算符,所以 ++A--A 这种表达式是无法通过编译的。

第二类是比较运算符,这没什么可说的:

在 Go 语言中,如果是比较数组的话,那么参与比较的数组的长度必须保持一致,否则无法通过编译:

x := [...]int{1, 2, 3}
y := [...]int{1, 2, 3}
//y := [...]int{1, 2, 3, 4}  无法和 x 进行比较
fmt.Println(x == y)

第三类是逻辑运算符:

第四类是位运算符:

2.4 条件和循环

Go 语言中的条件语句仍然使用关键字 if 来实现,只不过 if 后面的条件表达式不会加上圆括号:

func oddOrEven(n int) {
   if n % 2 == 0{
      fmt.Printf("%d is even\n", n)
   }else {
      fmt.Printf("%d is odd\n", n)
   }
}

这里需要注意的一点是 Go 语言中的 if 条件可以支持变量赋值,如下:

if i := 1 == 1; i{
   fmt.Println("i == 1")
}else {
   fmt.Println("i != 1")
}

当然这段代码仅仅是用来做演示,无任何实际的意义,if 条件中的赋值语句通常可以用来对一个函数的调用的返回值进行判断,下面是一个简单的示例(Go 语言中的函数支持多返回值,这个后面在介绍函数时会提及):

func main() {
   if res, err := divide(10, 0); err != nil{
      fmt.Println("there is something wrong")
   }else {
      fmt.Println(res)
   }
}

func divide(x float32, y float32) (float32, error) {
   if y == 0{
      return 0, errors.New("cant`t divide by zero")
   }

   return x / y, nil
}

上面的代码在 if 条件中调用了函数,函数如果 error 类型的返回值不为空,则说明出现了错误,否则正常输出结果。

还有一个常见的条件控制语句是 switch,Go 语言中也支持,基本的用法和其他的编程语言没有太大的差别:

for i := 0; i < 5; i++ {
   switch i {
   case 1, 3:
      fmt.Println("odd")
   case 2, 4:
      fmt.Println("even")
   default:
      fmt.Println("not between 0-3")
   }
}

可以看到 case 条件后面可以跟多个条件,并且每个 case 块中没有带 break 关键字,这是因为默认就有。

Go 语言中的循环只有关键字 for 来支持,而没有常见的 while,for 关键字可以代替 while 实现其功能,如下:

//常见的for使用
for i := 0; i < 10; i++ {
   fmt.Println(i * i)
}

//当做while使用,相当于 while(x > 0)
x := 10
for x > 0 {
   fmt.Println(x * x)
   x--
}

//无限循环,相当于while(true)
for {
   fmt.Println("I love Go")
}

三、常用数据结构

3.1 数组

先来看一下 Go 语言中的数组的几种声明方式:

var arr [3]int  //声明数组并初始化为零值
arr[0] = 10

arr2 := [3]int{1, 2}   //声明时初始化
arr3 := make([]int, 2)    //声明长度为2的数组
arr4 := [3][3]int{{1, 2, 3}, {4, 5, 6}}    //多维数组的初始化

数组的常见操作是对其进行遍历,我们可以按照一贯的做法,写一个 for 循环来完成,但是 Go 语言中的 range 关键字可以使代码更加的简洁:

//常规的遍历方式
for i := 0; i < len(arr2); i++ {
   fmt.Println(arr2[i])
}

//数组元素的遍历
for i, v := range arr2 {
   fmt.Println(i, v)
}

//忽略下标或者值,_ 表示
for _, val := range arr2{
   fmt.Println(val)
}

数组还支持截取操作,这和 python 的特性非常类似:

//数组截取
fmt.Println(arr[0:2])
fmt.Println(arr[:2])
fmt.Println(arr[0:])
fmt.Println(arr[1:len(arr)])

3.2 切片

切片可以看做是对数组的一层封装,因为每个切片的底层一定都是一个数组,先来看看切片的声明方式:

var sli0 []int
sli0 = append(sli0, 1, 2)

sli1 := []int{}
sli1 = append(sli1, 1, 2, 3)

sli2 := []int{1, 2, 3}

sli3 := make([]int, 5) //指定长度,默认值0
fmt.Println(len(sli2))
fmt.Println(cap(sli2))

sli4 := make([]int, 5, 8)  //指定长度及容量,
fmt.Println(sli4[3])      //可以访问,默认值0
fmt.Println(sli4[7])      //无法访问

可以看到切片的声明方式其实和数组非常的类似,只是方括号中没有了表示指定长度的数字。如果需要往切片中新增加元素,我们可以使用 append 函数:

sli := make([]int, 5)
sli = append(sli, 1)
sli = append(sli, 2, 3)

那么数组和切片的区别都有哪些呢?

数组的长度一定是一个定值,就是其在声明时所指定的长度,但是切片类型的长度是可变化的,切片的长度随着其中元素的增长而增长,只不过却不会随着元素的减少而减少。

当切片的容量不够时,那么它会扩展至原来的两倍,如果切片中的数据量大于等于 1024 的话,那么每次扩容 1.25 倍。

在声明切片时,可以指定切片的长度及容量,如果不指定容量,那么默认长度就是其容量。

//指定长度及容量
sli1 := make([]int, 5, 10)
fmt.Println(len(sli1))  //5
fmt.Println(cap(sli1))  //10

//不指定容量,那么长度就是其容量
sli2 := make([]int, 5)
fmt.Println(len(sli2)) //5
fmt.Println(cap(sli2)) //5

你可以将切片想象成一个数组上方的窗口,你只能通过这个窗口看见数组的一部分元素,这个窗口的大小就是切片的大小,而数组就是切片底层的数组。

例如上面声明的切片 slie1,长度是 5,容量是 10,那么切片能够看到的数组中的元素便是下标为 0 - 4 的这五个元素。

3.3 集合

Go 语言中的 map 的声明方式有下面的几种:

map1 := map[string]int{}
map1["a"] = 1

map2 := map[string]int{
   "a": 1,
   "b": 2,
}

map3 := make(map[int]string, 10)

map 也可以使用 range 关键字像数组那样进行遍历:

m := map[int]string{
   1: "a",
   2: "b",
   3: "c",
   4: "d",
}

for k, v := range m{
   fmt.Println(k, v)
}

需要注意的是,map 中如果一个键对应的值不存在,那么它的默认值会是零值,例如下面的示例:

m := map[int]int{
   1: 1,
   2: 2,
   3: 3,
   4: 4,
}

fmt.Println(m[5])    //打印出 0

这样的话就会存在一个问题,我们怎么判断一个 map 中键对应的值,到底是不存在还是它的值本来就是 0 呢?其实访问 map 中的值时,它会带有两个返回值,一个返回值是键对应的值,另一个则是是否存在,可以借助这个返回值来判断:

i, ok := m[5]
if ok{
   fmt.Println(i)
}else {
   fmt.Println("不存在键为5")
}

了解了 map,你可能会很自然的想到 set,是的,set 也是一个非常重要并且常用的数据结构,但是在 Go 语言中并没有 set 的实现,只不过我们可以使用 map 来实现一个 set,具体看代码:

set := map[int]bool{}

//存放数据
set[1] = true
set[4] = true
set[4] = true

//删除数据
delete(set, 1)

//判断元素是否存在
if set[1] {
   fmt.Println("存在 1")
}else {
   fmt.Println("不存在 1")
}

//元素的个数
size := len(set)

上面的程序基本完成了常见的 set 的常用功能,其实现也很简单,就是改造了一个 map,其键就相当于我们 set 的值,而 map 键对应的值是一个 bool 值,如果为 true 则表示存在于 set 中,如果为 false 则表示不存在。

3.4 字符串

Go 语言中的字符串 string 是数据类型,而不是引用类型或者指针类型,这也是为什么前面提到的,string 的默认值是空字符串,而不是 nil。

字符串也可以像数组一样进行截取和遍历操作:

s := "I like Golang"

//截取
fmt.Println(s[1])
fmt.Println(s[1:4])

//遍历
for i, v := range s{
   fmt.Printf("%d, %c \n", i, v)
}

字符串的常用操作方法都在 strings 和 strconv 包下面,下面代码给出了几个示例:

s := "1,2,3"

//字符串分割
spliS := strings.Split(s, ",")
fmt.Println(spliS)

//是否包含
c := strings.Contains(s, "1")
fmt.Println(c)

//替换
reS := strings.Replace(s, ",", "-", -1)
fmt.Println(reS)

//strconv 包中的函数,主要是和其他数据类型的转换
v, _ := strconv.ParseBool("false")
fmt.Println(v)

Go 语言的基础语法介绍的第一篇文章就结束了,如果大家有不懂的地方,或者对文中的内容有疑议,欢迎与我交流!

我一直认为编程是一项熟能生巧的手艺,只有多写代码才能够在实践当中提升自己的编程能力。

为此我在 Github 上新建了一个学习项目,使用 Java、Golang、Python 三种语言实现常见的数据结构和算法,以此做为练习编程的素材,你可以多看看代码,并且自己多动手编写,项目地址是 :

https://github.com/roseduan/algo-learn


roseduan
170 声望43 粉丝