# Go入门

配置proxy goproxy.cn

# 资料

Go标准库文档-Go语言中文网 (opens new window)

# go的命令行

运行go语言:

go run hello.go

生成二进制文件:

go build hello.go

可以直接运行build生成的二进制文件:

$ ./hello

# 基础语法

# 变量定义

# 函数返回

函数返回值处可以直接定义返回值的变量名,在函数体内直接使用此变量名;并且return时,可以不用显式return哪个变量。

example:

func example(num int) (rtn int) {
	rtn += 1
	return
}

# 条件判断

# 标记public、protect

以标识符的大小写区分“对象”的可见性

# 数据类型

数据类型 (opens new window)

# string类型

字符串操作参考资料 (opens new window)

定义字符串

var str string
str := "我是字符串"
var str = "i am string"

通过下标获取某一位字符

import "fmt"
fmt.Println(str[3])

// 但是不允许修改,此处是错误示例
str[1] = "a"

获取字符串的长度(要注意编码问题),因为utf-8编码中,一个汉字需要3个字节编码,len()输出的是字符串占据的字节数。需要打印字符长度,可以使用rune。

fmt.Println(len("hello")) // 结果是5

fmt.Println(len("hello世界")) // 结果是11

将string转化为rune数组,再计算长度

str := "hello世界"
fmt.Println(len([]rune(str))) // 结果是7

循环字符串

for _, ch := range str {
    fmt.Println(int(ch-'0'))
}

# 类型转换

# string和int互相转换

  • string转成int: int, err := strconv.Atoi(string)
  • string转成int64: int64, err := strconv.ParseInt(string, 10, 64)
  • int转成string: string := strconv.Itoa(int)
  • int64转成string: string := strconv.FormatInt(int64,10)

# 指针

指针变量指向一个值的内存地址。

正常定义的变量是有存储在一个地址上的,可以通过&获取正常变量的地址赋值给指针变量:

// 定义正常变量
var a int = 20
// 指向int类型变量的地址
var p *int
// 将正常变量的地址赋值给指针变量
p = &a
fmt.Println(&a) // 结果是a变量的地址
fmt.Println(p) // 结果同上
// 使用指针访问对应地址上的值
fmt.Println(*ip) // 结果为 20

声明指针

// 基础类型
var variableName *int
// Book可以是结构体
var variableName *Book

Go的空指针

当指针被定义后,没有被分配到任何变量,则指针的值为nil

向函数传递指针

通过传递指针,可以在调用函数中修改对应地址的值。

func main() {
    var a int = 100
    var b int = 200
    
    swap(&a, &b)
    
    fmt.Println(a) // result: 200
    fmt.Println(b) // result: 100
}

func swap(a, b *int) {
    *a, *b = *b, *a
}

指向指针的指针

一个指针变量存放的是另一个指针变量的地址,则这个指针被称为指向指针的指针变量

// 定义
var ptr **int

示例:

import "fmt"
func main() {
    var a int
    var ptr *int
    var pptr **int
    
    a = 100
    ptr = &a
    pptr = &ptr
    
    fmt.Println(a) // 三个print结果相同,都是 100
    fmt.Println(*ptr)
    fmt.Println(**pptr)
}

指针数组

func main() {
    const n = 3
	arr := []int{100, 200, 300}
	var ptr [n]*int
	for i := range arr {
		ptr[i] = &arr[i]
	}
	for _,val := range ptr {
		fmt.Println(*val) // 打印arr中的结果
	}
}

# 结构体

定义结构体

type theStruct struct {
    member definition
    member definition
    member definition
}

变量声明

variableName := theStruct {value1, value2, value3}
variableName := theStruct {key1 : value1, key2 : value2, key3: value3}

访问结构体成员

使用结构体.属性

import "fmt"
type Book struct {
    title string
    author string
    id int
}

func main {
    var b1 Book
    var b2 Book
    
    b1.title = "深入理解Java虚拟机"
    b1.author = "周志明"
    bi.id = 1001
    
    b2.title = "my book"
    b2.author = "jesse"
    b2.id = 1002
    
    fmt.Println(b1.title)
    fmt.Println(b1.author)
    fmt.Println(b1.id)
    
    fmt.Println(b2.title)
    fmt.Println(b2.author)
    fmt.Println(b2.id)
}

结构体作为函数参数

func main() {
    var b1 Book
    b1.title = "my book"
    
    printBook(b1)
}

func printBook(b Book) {
    fmt.Println(b.title)
}

结构体指针

// 定义结构体指针
var b1 *Book
// 查看结构体变量地址
var addr = &b1
// 使用结构体指针访问结构体成员
b1.title

结构体指针示例

func main() {
    var b1 Book
    
    b1.title = "my book"
    b1.author = "jesse"
    
    printBook(&b1)
}

func printBook(b *Book) {
    fmt.Println(b.title)
    fmt.Println(b.author)
}

给结构体定义方法

func main() {
	// m是指针,此处是不是指针都不影响a,b的结果
	m := &Mutatable{0, 1}
    fmt.Println(m) // result: &{0 1}
	m.StayTheSame()
    fmt.Println(m) // result: &{0 1}
	m.Mutate()
    fmt.Println(m) // result: &{5 7}
}

type Mutatable struct {
	a int
	b int
}

func (m Mutatable) StayTheSame() {
	// 不是指针对象,无法修改外部的值,函数内的修改只在函数内生效
	m.a = 5
	m.b = 7
}

func (m *Mutatable) Mutate() {
	// 指针修改可以影响函数外的值
	m.a = 5
	m.b = 7
}

# 数组和切片slice

# 数组

数组是由固定长度和固定对象类型组成的。

数组声明时,要方括号内要写明数组长度或者使用...符号自动计算长度:

// 初始化为0
var a [4]int
// 输出结果为[1 2 3]
var a [3]int = [3]int{1,2,3}
// 输出结果为[1 2 0]
var a [3]int = [3]int{1,2}
// 使用...符号,代替长度定义,自动根据初始化值的数量计算长度
var a = [...]int{1,2,3}
// 定义简写
a := [3]int{1,2,3}

go中的数组是一个实体对象,而不是指针。

并且数组的长度是数组的组成部分,长度一定是常量表达式,必须在编译阶段确定。

如果先定义一个变量为[3]int,则无法再将[4]int赋值给它了:

a := [3]int{1,2,3}
a = [4]int{1,2,3,4} // 编译报错,无法赋值

比较数组是否相等?

两个数组的长度和元素类型相同的话,可以直接使用等号或不等号比较。

只有当两个数组的所有元素都相等时,数组才算相等。

遍历数组

arr := [3]string{"a", "b", "c"}
for k, v := range arr {
    fmt.Println(k, v)
}
// 输出结果为
// 0 a
// 1 b
// 2 c

# 切片

切片是一个引用类型,类似于Java中的数组类型。(注意终止索引不包含在内,与Java一致)

切片的内部结构包含地址(开始位置)、大小和容量。

声明一个切片

var a []int // 声明语法,此时切片还没有被分配内存
var b = []int{} // 声明一个空切片,此时已经分配了内存,只是还没有元素
fmt.Println(a == nil) // true,声明但未使用的切片默认值是nil
fmt.Println(b == nil) // false

从数组或切片可以生成新的切片:

var a = [3]int{1,2,3}
fmt.Println(a, a[1:2]) // 输出 [1 2 3] [2]

从上面代码可看出语法格式为:slice[开始位置:终止位置],但要记住,结果是不包含终止位置的。当任一位置缺失时,像slice[:1]slice[5:],表示从开头或者到结尾。

使用make构造切片

make([]Type, size, cap) // 声明语法
a := make([]int, 2)
b := make([]int, 2, 10)
fmt.Println(a, b) // [0 0] [0 0]
fmt.Println(len(a), len(b)) // 2 2

Type代表切片元素类型;size是分配多少个元素;cap为预分配的元素数量,用于提前分配空间,降低多次分配空间造成的性能问题,不影响size

使用make分配切片一定会发生内存分配操作,但使用slice[start:finish]只是将新的切片结构指向已经分配好的内存区域,不发生内存分配操作。

清空切片:

a := []int{1,2,3}
fmt.Println(a[:]) // 清空切片

用append为切片追加元素

var a []int
a = append(a, 1) // 追加1个元素
a = append(a, 1, 2, 3) // 追加多个元素, 手写解包方式
a = append(a, []int{1,2,3}...) // 追加一个切片, 切片需要解包

追加切片时,若空间不足,切片就会进行扩容,长度会发生变化。切片扩容的规律是按照2倍数进行扩容:

var numbers []int
for i := 0; i < 10; i++ {
    numbers = append(numbers, i)
    fmt.Printf("len: %d  cap: %d pointer: %p\n", len(numbers), cap(numbers), numbers)
}

数组和切片互转

通过[0:2]的写法将数组转为切片

arr := [3]int{1,2,3}
fmt.Println(arr[0:2]) // 输出 [1 2]
fmt.Println((arr[0:2])[0:1]) // 输出 [1]

将切片的内容复制到数组

slice := []int{1, 2, 3}
var arr [3]int
fmt.Println(copy(arr[0:2], slice)) // 输出 2,代表将切片中
fmt.Println(arr[0:2]) 

其他

  • 获取数组长度

    使用自带函数len()

  • go中的数组长度只能指定常量值,若需要动态指定,则需要使用切片

    slice := make([]int, length)

# 其他数据结构

除了基本数据结构外,整理了一些常用的数据结构

# Map集合

定义map

// 创建集合
var theMap map[string]string
var theMap = make(map[string]string)
theMap := map[string]string{"a": "1", "b": "2", "c": "3"}

向map插入值

theMap["key"] = "value"

循环输出

for k := range theMap {
    fmt.Println(k, theMap[k])
}

查看元素在集合中是否存在

val, ok := theMap[key]
if (ok) {
    // 存在
} else {
    // 不存在
}

删除集合元素

delete(theMap, key)
// like this
delete(theMap, "a")

# fmt包

格式化IO函数

  • fmt.Println("")

  • fmt.Sprintf(patter, s1, s2...);

    格式化字符串:%d 表示整型数字,%s 表示字符串

  • fmt.Printf("%v %v %v %q\n", i, f, b, s)

# strings包

  • Trim/TrimLeft/TrimRight...

    用于修剪字符串,返回字符串的slice

  • Join

    将数组join起来,中间添加内容

    strings.Join(arrays, ",")

  • Fields

    将字符串按空格分隔

    strings.Fields(str)

# sort包

数组排序

import "sort"

func example(nums []int) []int {
    sort.Ints(nums)
    return nums
}

排序数组中部分内容

sort.Ints(nums[i:j])

# math包

i := -100
fmt.Println(math.Abs(float64(i))) //绝对值
fmt.Println(math.Ceil(5.0))       //向上取整
fmt.Println(math.Floor(5.8))      //向下取整
fmt.Println(math.Mod(11, 3))      //取余数,同11%3
fmt.Println(math.Modf(5.26))      //取整数,取小数
fmt.Println(math.Pow(3, 2))       //x的y次方
fmt.Println(math.Pow10(4))        // 10的n次方
fmt.Println(math.Sqrt(8))         //开平方
fmt.Println(math.Cbrt(8))         //开立方
fmt.Println(math.Pi)

# 自实现的工具类

# PowInt-求int指数结果

import "math"
func powInt(x, y int) int {
    return int(math.Pow(float64(x), float64(y)))
}

# 翻转字符串

func Reverse(s string) string {
    runes := []rune(s)
    for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 {
        runes[i], runes[j] = runes[j], runes[i]
    }
    return string(runes)
}

# 数组中交换两个值

func swap(nums []int, a, b int) {
    nums[a], nums[b] = nums[b], nums[a]
}

# 字符串排序

import "sort"
type sortRunes []rune

func (s sortRunes) Less(i, j int) bool {
    return s[i] < s[j]
}

func (s sortRunes) Swap(i, j int) {
    s[i], s[j] = s[j], s[i]
}

func (s sortRunes) Len() int {
    return len(s)
}

func SortString(s string) string {
    r := []rune(s)
    sort.Sort(sortRunes(r))
    return string(r)
}

func main() {
    w1 := "bcad"
    w2 := SortString(w1) // abcd
}
修改于: 8/11/2022, 3:17:56 PM