学习 Go 语言 1 — 基础语法

一、第一个 Go 程序

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

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

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

image

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

在开始之前,你需要上 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 中的运算符其实没什么可说的,如果你有其他编程语言的经验,那么可以完全忽略掉这一部分无关紧要的内容,因为它和其他主流的编程语言没有什么差别。

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

image

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

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

image

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

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

第三类是逻辑运算符:

image

第四类是位运算符:

image

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,911评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,014评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 142,129评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,283评论 1 264
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,159评论 4 357
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,161评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,565评论 3 382
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,251评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,531评论 1 292
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,619评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,383评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,255评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,624评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,916评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,199评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,553评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,756评论 2 335