Golang基础语法

71 阅读5分钟

摘自go语言圣经

变量定义

var x int //定义int类型的变量
var x int = 0 //定义int类型变量,并赋初值
var x = 0 //定义变量并赋初值,自动判断类型
x := 0 //定义变量并赋初值,自动判断类型
// 使用:=定义变量时必须赋初值

基本数据类型

Go语言将数据类型分为四类:基础类型复合类型引用类型接口类型

  • 基础类型:数字、字符串、布尔型。
  • 复合类型:数组、结构体。
  • 引用类型:指针、切片、字典、函数、通道。
  • 接口类型

基础数据类型

整型

int8 int16 int32 int64 
uint8 uint16 uint32 uint64
byteuint8的等价类型,byte类型一般用于强调数值是一个原始的数据而不是一个小的整数。
虽然int32位的,但把int转换成int32时也需要显式转换。

浮点数

float32 float64

复数

布尔型

true false

字符串

str := "abc"
len(str) //内置的len函数可以返回字符串的字节数
str[1] // 可以按下标索引(按字节),返回字符
s[:1] // 支持切片,左闭右开,表示头尾可以省略。
      // 因为字符串不可修改,因此切片和原字符串共享内存是安全的,这使得切片操作代价低廉,复制字符串也是同理
  • 字符串不可改变,不可修改
  • 字符串可以用==和<比较(按字节)
  • 支持使用+拼接,但会重新分配空间 string和[]byte相互转化:
str := "abc"
b := []byte(str)
str2 := string(b)

字符串处理常用库:bytes,strings,strconv,unicode。 strings:

func Contains(s, substr string) bool
func Count(s, sep string) int
func Fields(s string) []string
func HasPrefix(s, prefix string) bool
func Index(s, sep string) int
func Join(a []string, sep string) string

bytes:

func Contains(b, subslice []byte) bool
func Count(s, sep []byte) int
func Fields(s []byte) [][]byte
func HasPrefix(s, prefix []byte) bool
func Index(s, sep []byte) int
func Join(s [][]byte, sep []byte) []byte

以上函数作用相同,只是string和[]byte的区别。

bytes包还提供了Buffer类型用于字节slice的缓存。

var buf bytes.Buffer
buf.WriteByte('a')
buf.WriteString("aaa")
str := buf.String()
// strings.Builder也能拼接字符串
var builder &strings.Builder // 如果不传入函数,加不加引用一样,不加引用不能传参
builder.WriteString("aaa")
builder.WriteByte('b')
builder.String() // 转化成string

字符串和数字相互转化

x := 123
y := fmt.Sprint("%d", x)
fmt.Println(y, strconv.Itoa(x)) // "123 123"
z := strconv.Atoi("123")

注意

for i, v := range "abc" {
    fmt.Println(i, v)
}
//0 97
//1 98
//2 99
//for遍历字符串值是ASCII码的值,类型为int32

常量

常量的值不可修改,所有常量有关的运算在编译时确定。

声明常量:

const a = 1
const (
    b = 2
    c = 3.0
)

iota常量生成器

type Weekday int

const (
    Sunday Weekday = iota
    Monday
    Tuesday
    Wednesday
    Thursday
    Friday
    Saturday
)

iota初始值为0,之后的常量逐个加1 例:

const (
    _ = 1 << (10 * iota)
    KiB // 1024
    MiB // 1048576
    GiB // 1073741824
    TiB // 1099511627776             (exceeds 1 << 32)
    PiB // 1125899906842624
    EiB // 1152921504606846976
    ZiB // 1180591620717411303424    (exceeds 1 << 64)
    YiB // 1208925819614629174706176
)

go语言中,许多常量没有默认的基础类型,编译器为这些没有明确基础类型的数字常量提供更高精度的算术运算,至少有256bit。这里有六种未明确类型的常量类型,分别是无类型的布尔型、无类型的整数、无类型的字符、无类型的浮点数、无类型的复数、无类型的字符串。

只有常量可以是无类型的。当一个无类型的常量被赋值给一个变量的时候,就像下面的第一行语句,或者出现在有明确类型的变量声明的右边,如下面的其余三行语句,无类型的常量将会被隐式转换为对应的类型,如果转换合法的话。

var f float64 = 3 + 0i // untyped complex -> float64
f = 2                  // untyped integer -> float64
f = 1e123              // untyped floating-point -> float64
f = 'a'                // untyped rune -> float64

上面的语句相当于:

var f float64 = float64(3 + 0i)
f = float64(2)
f = float64(1e123)
f = float64('a')

复合数据类型

数组

数组初始化

var a [3]int  // 默认情况下,数组中每个元素都被初始化为0值
var b [3]int = [3]int{1, 2, 3}
var c [3]int = [3]int{1, 2}  // c[2]=0
d := [...]int{1, 2, 3}  // ...表示初始化的数组长度由初值决定

数组的长度是数组类型的一部分,[3]int和[4]int是两种不同的类型。

数组可以用索引:值的方式初始化:

a := [3]int{0: 1, 1: 2, 2: 3}  // a[2]=3
b := [...]int{99:-1}  // b[99]=-1, b[98]=0

当数组元素的类型可以比较时,此时的数组也可以比较,当数组中所有值全都相等时,两个数组相等。

a := [2]int{1, 2}
b := [...]int{1, 2}
fmt.Println(a == b)  // true

函数调用数组:

func zero(ptr *[32]byte) {
    *ptr = [32]byte{}
}

Slice切片

slice代表变长序列,类型写作[]T,T是切片内存储的数据类型。

slice由三个部分组成:指针,长度和容量。

a := [...]int{1, 2, 3, 4, 5} # 数组
b := a[1:3] # 切片[2, 3]
fmt.Println(len(b)) # 2
fmt.Println(cap(b)) # 4
c := b[:3]
fmt.Println(c) # [2 3 4]

切片访问不可以超过其长度,但对切片的操作可以超过长度而不超过容量,这意味着扩展该切片,如上代码段中的变量c。

注意切片并不重新申请内存空间,切片与原数组、字符串、切片等共享内存空间,对切片的修改会改变原变量的数值。

因为切片的值中包含指向第一个切片元素的指针,因此函数中的形式参数不用像数组一样写为指针。

func name(a []T) {
    // 对a的操作可以直接改变原切片的数值
}
func name2(a [3]T) {
    // 对a的操作只会改变实参的复制内容,不修改实参的值
}

slice不能像数组一样直接用==判断两个slice是否相等,bytes.Equal函数可以比较两个字节切片是否相等,其他的切片类型判断只能一一比较每个元素的值。go语言圣经中有为什么不能用==直接比较的解释,没看懂。

slice可以直接和nil比较:

var s []int    // len(s) == 0, s == nil
s = nil        // len(s) == 0, s == nil
s = []int(nil) // len(s) == 0, s == nil
s = []int{}    // len(s) == 0, s != nil

因此判断一个slice是不是空的应该用len(s)==0而不是s==nil。除了以上判断外,任意一个可以对长度为0的slice做的操作都可以对值为nil的slice做。

内置的make函数也可以创建一个切片,定义一个切片:

a := make([]T, len) // 省略cap,这时cap=len
b := make([]T, len, cap)
var c []T # c==nil
d := []T{}
e := []int[1, 2, 3]

append函数

append函数用于向一个切片最后添加元素,可以添加一个或多个元素:

a := []int{}
a = append(a, 0)
a = append(a, 1, 2)
a = append(a, a...)

当append后的切片长度在原始数组、切片的容量范围内时,会直接占用原有的底层数组、切片上的空间(会修改原来该位置上的值)。如果append后的切片长度大于原始数组、切片容量,会重新申请内存空间并拷贝数据到新的空间中。为了减少申请空间的次数,一般新申请的容量空间会大于需要的空间。

Map

map利用哈希表存储key,value对,其中key必须是可以直接比较的元素类型。 map的定义:

var a map[int]bool 
var b map[int]bool{}
c := map[int]bool{}
d := make(map[int]bool)
e := map[int]bool{
    1:true,
    2:false,
}

需要注意第一种定义方式中a==nil,此时a相当于一个空map,但a不能直接用键值对的方式添加元素,如以下操作会报错:

var a map[int]bool  // 只是声明,不能直接用,可以作为结构体的成员变量
a[1] = true // panic: assignment to entry in nil map
b := map[int]bool{}  // 创建一个非nil的map
b[1] = true // 不会报错
c := make(map[int]bool)  // make也可以创建非空map
c[1] = true

删除map元素:

delet(a, 1) // delet(map名,键)

访问map中不存在的键时,不会报错,其值为对应类型的零值。

遍历map:

for key, value := range a{
    // 将以随机顺序遍历
}

判断key是否在map中:

if age, ok := ages["bob"]; !ok { /* "bob" is not a key in this map; age == 0. */ }

其中ok是一个布尔值,ages中存在key则返回true。

map的value也可以是map。