文章 Go语言入门
Post
Cancel

Go语言入门

Hello World示例

1
2
3
4
5
6
7
package main

import "fmt"

func main(){
	fmt.Println("Hello world")
}

main包的main函数是go程序的入口函数

标识符

关键字

go语言仅有25个关键字, 按功能可分为3部分

引导程序整体结构的8个关键字

1
2
3
4
5
6
7
8
package // 定义包名的关键字
import // 导入包名关键字
const // 常量声明的关键字
var // 变量声明的关键字
func // 函数声明的关键字
defer // 延迟执行的关键字
go // 并发语法糖关键字
return // 函数返回关键字

声明复合数据结构的4个关键字

1
2
3
4
struct // 定义结构体关键字
interface // 定义接口关键字
map // 声明或创建map类型关键字
chan // 声明或创建chan类型关键字

控制程序结构的13个关键字

1
2
3
4
if else // 分支语句关键字
for range break continue // 循环语句关键字
switch select type case default fallthrough // switch和select语句使用的关键字
goto // 跳转语句

内置数据类型标识符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 数值 16 个
   // 整型 12 个
   byte int int8 int16 int32 int64
   uint uint8 uint16 uint32 uint64 uintptr
   // 浮点型 2 个
   float32 float64
   // 复数型 2 个
   complex64 complex128
// 字符和字符串类型 2 个
   string rune
// 接口型 1 个
   error
// 布尔型 1 个
bool

内置函数 15 个

1
make new len cap append copy delete panic recover close complex real print println

内置函数也是高级语言的一种语法糖, 不需要使用import引入, 具有全局可见性

常量值标识符 4 个

1
2
3
true false // bool类型的两个常量
iota // 用在连续的枚举类型的声明中
nil // 指针/引用类型变量的默认值

空白标识符 1 个

1
_ 

操作符和分割符

算数运算符 5 个

1
+ - * / %

位运算符

位运算符用于整数的位运算操作

1
& | ^ &^ >> <<

赋值和赋值复核运算

1
:= = += -+ *= /= %= &= |= ^= &^= >>= <<=

比较运算符

1
> >= < <= == !=

括号

1
() {} []

逻辑运算符

1
&& || !

自增自减操作符

1
++ --

其他运算符

1
: , ; . ... <-

字面常量

字面常量一般用于常量和变量的初始化和表达式里或者作为函数调用的实参, 字面量有如下几类.

整型字面量

整型字面量使用特定的字符序列来表示具体的整型数值, 常用与整型变量或常量的初始化, 例如

1
2
3
42
0600
0xBadFAce

浮点型字面量

浮点型字面常量使用特定字符序列来表示一个浮点数值. 他支持两种格式: 一种是标准的数学记录法, 比如0.1, 另一种是科学计数法的表示, 比如1E6

1
2
3
4
0.
98.1
1.e+0
1E6

复数类型字面量

1
2
3
0i
011i
1.e+0i

字符型字面量

go的源码采用的是UTF-8的编码方式, UTF-8的字符占用的字节数可以有1~4个字节, Rune字符常量也有多种的表现形式, 使用”'”将其括住

1
2
3
4
'a'
'本'
'\t'
'\u12e4'

字符串字面量

字符串字面量的基本表现形式就是使用’"’将其括住, 例如

1
"Hello World"

变量和常量

变量和常量用来绑定一块特定的内存, 只是变量指向的内存可以被修改, 常量指向的内存不可以被修改.

变量和常量简化的程序的编写, 记住标识符比记住某个地址更加容易, 另一方面极大地提升了程序的可读性.

变量

go的基本类型变量声明有两种

显示的完整声明

1
var varName dataType [ = value]
  • value是变量的初始值, 如果不指定初始值, 则会是该类型的零值
  • go的变量声明后就会立即为其分配空间

短类型声明

1
varName := value
  • := 声明只能出现在函数内(包括方法内)
  • Go的编译器会自动进行类型推断

go支持多个类型变量的同时声明并赋值

1
a, b := 1, "Hello"

常量

Go中的常量分为布尔型, 字符串型和数值型常量. 常量存储在程序的只读段里

预声明标识符iota用在常量声明中, 其初始值为0. 一组多个常量同时声明时其值逐行增加

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 一组声明
const (
 c0 = iota // 0
 c1 = iota // 1
 c2 = iota // 2
)

// 简写模式
const (
 c0 = iota // 0
 c1        // 1
 c2        // 2
)

// 在一组赋值中, 即使iota不出现, 其值也会增加
const (
  a = 1 << iota // a == 1 iota == 0
  b = 1 << iota // b == 2 iota == 1
  c = 3 // c == 3 iota == 2
  d = 1 << iota // d == 8 iota == 3
)

// 分开的const语句, iota每次都是从0开始
const x = iota // x == 0
const y = iota // y == 0

基本数据类型

布尔类型

布尔类型的关键字是bool, 布尔类型只有两个值truefalse, 同时这两个也是go内置的标示符.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 声明方式
var ok bool = false
var ok1 bool
ok1 = true
ok2 := true

// 布尔类型不能够与整型数据进行相互转换
var a bool
a = 1 // error

// 比较表达式和逻辑表达式的结果都是bool类型
var b bool = (x>y)&&(x>0)

// if 和 for 语句的条件部分一定是bool类型的值或者表达式

// bool类型的初始值为false

整型

go语言内置了12种整数类型, 分别是byte,int,int8,int16,int32,int64,uint,uint8,uint16,uint32,uint64,uintptr. 其中byteint8的别名, 不同类型的整型必须进行强制类型转换

1
2
3
4
5
6
7
var a int = 1
var b int32 =2
b = a // error

// 整型支持算数运算和位运算, 算数表达式和位运算的结果还是整型
var a int = (1+2)*3
var b int = 1000>>2

一个小小的坑

1
2
3
4
5
6
7
8
9
10
11
12
13
package main

import "fmt"

var n uint = 10
const N uint = 10

func main(){
	var x byte = (1<<n) %100
	var y byte = (1<<N) %100
	
	fmt.Printf("x:%d, y:%d",x,y) // x:0, y:24
}

x为0, 是因为左移的结果超出了byte的范围, 而y左移由于是个常量, 会继续运算

浮点型

浮点型用于表示包含小数点的数据, Go语言内置两种浮点数类型, 分别是float32和float64. 浮点数有两个注意事项,分别是

  1. 浮点数字面量被自动类型推断为float64类型
  2. 浮点数进行比较时不应该使用==或者!=, 高精度科学计算应该使用math标准库

复数类型

go语言内置的复数类型有两种, 分别是complex64和complex128, 复数在计算机中使用两个浮点数表示, 一个表示实部, 一个表示虚部, go有三个内置函数处理复数

1
2
3
4
v := complex(2.1, 3) // 构造一个复数
a:= real(v) // 返回复数实部
b:= imag(v) // 返回复数虚部
fmt.Println(a,b)

字符串

go语言将字符串作为一种原生的基本数据类型, 字符串的初始化可以使用字符串字面量, 例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
var a = "Hello"
// 字符串是常量, 可以通过类似数组的索引的方式访问其单元, 但是不能修改某个字节的值
b := a[0]

// 字符串转换为[]byte(s)要慎用, 每一次转换都会复制一份

// 字符串尾部不包含Null字符

// 字符串类型底层实现是一个二元的数据结构, 一个是指针, 指向字节数组的起点, 另一个是长度

// 基于字符串创建的切片和元字符串指向相同的底层字符数组, 一样不能修改, 对字符串的切片操作返回的子串仍然是sring
b := a[0:4]
c := a[1:4]
d := a[:4]

// 字符串可以转换为字节数组, 也可以转换为Unicode的字数组
b := []byte(a)
c := []rune(a)

// 字符串可以使用+进行拼接, 也可以进行遍历
for i:=0;i<len(a);i++{ // 遍历字节数组
    fmt.Println(a[i])
}

for i,v := range a{
    fmt.Println(i,v) // 遍历rune数组
}

rune类型

go内置两种字符类型: 一种是byte的字节类型, 另一种是Unicode编码的字符rune. rune在go内部是int32类型的别名, 占用4个字节.

复合数据类型

go语言基本的复合数据类型有指针,数组, 切片, 字典, 通道, 结构体和接口

指针

go语言支持指针, 指针的声明类型为*T, go同样支持多级指针**T.通过在变量名前加&来获取变量的地址, 指针的特点如下

  1. 在赋值语句中, *T出现在 = 的左面表示指针声明, *T出现在=的右面表示取指针指向的值

  2. 结构体指针访问结构体字段仍然使用.操作符, Go语言没有->操作符(C语言指针取值符号)

  3. GO不支持指针的运算

  4. 函数中允许返回局部变量的地址

    GO编译器使用栈逃逸机制将这种局部变量的空间分配到堆上. 例如

    1
    2
    3
    4
    
    func sum(a,b int) *int{
        sum := a+b
        return &sum
    }
    

数组

数组的类型名是[n]elemetType, 其中n是数组的长度, elemetType是数组元素的类型. 数组一般在创建时通过字面量初始化, 否则其中元素都是elemetType的默认值

1
2
3
4
5
// 数组的初始化
a := [3]int{1,2,3} // 指定长度和初始化字面量
a := [...]int{1,2,3} // 不指定长度, 由后面的初始化的列表的数量来确定其长度
a := [3]int{1:1,2:3} // 指定总长度, 通过索引进行初始化, 没有初始化的位置使用默认值
a := [...]int{1:1,2:3} // 不指定长度, 通过索引进行初始化, 数组的长度由最高的一个索引确定

数组的特点

  1. 数组创建完长度就固定了, 不可以再追加元素
  2. 数组是值类型的, 数组赋值或者作为函数参数都是值拷贝
  3. 数组长度是数组类型的组成部分, [10]int和[20]int不是一个类型
  4. 可以根据数组创建切片

数组相关操作

  1. 访问元素

    1
    2
    3
    4
    5
    
    a := [...]int{1,2,3}
    b := a[0]
    for i,v := range a{
           
    }
    
  2. 数组长度

    1
    
    alengh := len(a)
    

切片

go语言中的数组的定长性和值拷贝限制了其使用的场景, Go提供了另一种数据类型slice, 这是一种变长的数组, 其数据结构中有指向数组的指针, 是一种引用类型

Go为切片维护了三个元素: 指向底层数组的指针, 切片的元素数量和底层数组的容量.

切片的创建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 1. 由数组创建
	var array = [...]int{0,1,2,3,4,5,6,7}
	s1 := array[0:4]
	fmt.Println(s1, len(s1) ,cap(s1)) // [0 1 2 3] 4 8
	// 需要注意的是这是切片引用的数组还是原来的数组, 修改切片元素的话, 数组元素也会发生变化
	s1[0] = 8
	fmt.Println(array) // [8 1 2 3 4 5 6 7]
	// 使用append也是一样的结果
	s1 = append(s1, 10)
	fmt.Println(s1, len(s1) ,cap(s1)) // [8 1 2 3 10] 5 8
	fmt.Println(array) // [8 1 2 3 10 5 6 7]

// 通过内置函数make创建切片
	a := make([]int,10) // 此时创建了一个len为10的切片, 这10个元素被默认为该类型的0值
	fmt.Println(a)
	
	// 也可以指定cap
	a = make([]int,10, 15)
	fmt.Println(len(a), cap(a))
	
	// 切片中的零值是没有作用的, 可以创建长度为0的切片, 之后向里面添加元素
	a = make([]int,0)

切片支持的操作

  1. 内置函数len()返回切片长度
  2. 内置函数cap()返回切片底层数组容量
  3. 内置函数append()对切片追加元素’
  4. 内置函数copy()用于复制一个切片

字符串和切片的相关转换

1
2
3
str := "hello 世界"
a := []byte(str)
b := []rune(str)

map

Go语言内置的字典类型叫做map, map的类型格式是map[K]T, K是任意可以进行比较的类型, T是值类型. map也是一种引用类型

创建

1
2
3
4
5
6
7
// 使用字面量进行创建
ma := map[string]int{"a":1,"b":2}
println(ma["a"])

// 使用内置的make函数进行创建
mp1 := make(map[int]strign) // 使用默认容量
mp1 := make(map[int]strign, 10) // 指定容量

map支持的操作

  1. 使用单个键获取或设置值, mapName[key]

  2. 可以使用for range来遍历一个map类型的变量, 但是顺序是不确定的

    1
    2
    3
    
    for k,v := range mp1{
           
    }
    
  3. 删除map中的指定键, delete(mapName, key)

  4. 可以使用内置的len函数返回map中的键值对的数量

需要注意的是Go内置的map不是并发安全的, 并发安全的map可以使用sync包中的map.

如果想修改map value内某个元素的值, 需要整体赋值

struct

Go中的struct类型和C类似, struct有两种形式, 一种是struct类型字面量, ;另一种是type声明的自定义的struct类型

struct类型字面量

1
2
3
struct {
    Feild Type
}

自定义struct类型

1
2
3
type TypeName struct {
    Feild Type
}

struct类型的初始化

1
2
3
4
5
6
7
8
type Person struct {
    Name string
    Age int
}

a := Person{"wei",18} // 不推荐, 类型字段变化时, 初始化会出错
b := Person{Name:"wei",Age:18} // 这样的话, 没有初始化的字段都为0值
c := &Person{Name:"wei",Age:18}

更多的内容放到自定义类型那一节

控制结构

if结构

switch语句

switch语句会根据传入的参数检测并执行符合条件的分支, 这里也比较简单, 需要注意的是匹配到成功的case之后及时没有break, 也不会执行后面的代码, 如果想要执行, 则需要fallthrough关键字.

另外还有一个坑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package main

func main(){
	switch isOk()
	{
	case true:
		println("true")
	case false:
		println("false")
	}
}

func isOk()bool{
	return false
}

这里输出true是因为大括号换行, switch使用了默认的值

for语句

go语言仅支持一种循环语句, 即for 循环, for循环有两种方式, 一种是经典for循环

1
for init; condition;post{}

另一种是for range循环, 可以访问数组切片字符串,map和通道, 语法格式如下

1
2
3
4
5
6
7
8
9
10
11
// 访问map
for key, value := range map{}
for key := range map{}

// 访问数组或切片
for index, value := range array{}
for index := range array{}
for _, value := range array{}

// 访问通道
for value := range channel{}

跳转

break, continue , return. 略

This post is licensed under CC BY 4.0 by the author.
Contents