白乐天

道阻且长,行则将至。

Go语言基础

开发环境搭建

安装Go

打开Go的官网进行下载安装。

在终端输入命令go version可查看Go的版本,输入go env可查看Go的环境配置。

可以通过go build命令编译代码并生成可执行文件。

可以通过go run命令在终端运行Go文件。

开发工具

下载安装GoLand开发工具

HelloWorld

1
2
3
4
5
6
7
8
9
10
// 声明这是一个属于 main 包的程序。Go 的可执行程序必须包含一个名为 main 的包。
package main

// 导入 Go 的标准库 fmt 包,用于格式化输出
import "fmt"

// 定义程序的入口函数 main。每个 Go 程序的执行都会从 main 函数开始。
func main() {
fmt.Println("Hello World!") // Hello World!
}

基础语法

注释

Go语言的注释和C和C++一样,单行注释是//和多行注释/* */

程序入口

可执行程序需要声明main包。

main函数是程序的入口函数。

变量

Go语言通过var关键字来声明变量。

Go语言声明变量的方式

1
var <变量名> <数据类型>

匿名变量

在Go语言中,匿名变量(也称为”空白标识符”)使用_来表示,用来占位但忽略其值。

匿名变量不会占用内存,也不会引发编译错误,因此在不需要某个返回值或变量时,可以使用匿名变量代替。

短变量声明

在Go语言中可以通过:=操作符在函数体内声明和初始化变量,它是Go中常用的一种语法糖(语法糖是一种语法优化,程序的功能和逻辑不会因为语法糖而发生改变)。(不能用于全局变量)

:=可以自动推断变量的类型并简化语法,提高代码简洁性。

1
2
num := 10 //  等价于 var num int = 10
name := "Bileton" // 等价于 var name string = "Bileton"

类型推导

在生命变量的时候,省略数据类型,编译器可以根据上下文或赋值语句,自动推导出变量或常量的类型而不需要显式的指定类型。

未初始化变量

没有明确初始化的变量声明会被赋予对应类型的 零值

  • 数值类型为 0
  • 布尔类型为 false
  • 字符串为 ""(空字符串)。

常量

在Go语言中通过const关键字来声明常量。

1
const NAME string = "Bileton"

iota

iotago语言的常量计数器,只能在常量的表达式中使用。

iotaconst关键字出现时将被重置为0

const中每新增一行常量声明将使iota计数一次(iota可理解为const语句块中的行索引)。

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

import "fmt"

func main() {
const (
A = iota // A = 0
B // B = 1
C // C = 2
)
fmt.Println(A, B, C) // 输出: 0 1 2
}

关键字

type

  • 自定义类型

    1
    2
    3
    4
    5
    6
    type Age int

    func main() {
    var myAge Age = 25 // myAge的类型是Age,而不是int了
    fmt.Println(myAge)
    }
  • 类型别名

    1
    2
    3
    4
    5
    6
    type AliasInt = int

    func main() {
    var x AliasInt = 10 //AliasInt和int是一样的
    fmt.Println(x)
    }
  • 定义结构体

  • 定义接口

数据类型

整型

整型分为以下两个大类: 按长度分为:int8int16int32int64对应的无符号整型:uint8uint16uint32uint64

uint8也就是byte类型。

runeint32类型

浮点型

Go语言支持两种浮点型数:float32float64

布尔型

布尔型数据只有true(真)false(假)两个值。

字符串

Go语言中字符串是string类型,以原生数据类型出现。

Go 语言里的字符串的内部实现使用UTF-8编码。

nil

转义字符

转义字符 含义
\r 回车符(返回行首)
\n 换行符(跳转到下一行同列位置)
\t 制表符
\‘ 单引号
\“ 双引号
\ 反斜杠

数值类型之间的转换

形式为

1
2
3
T(value)

其中 T 是目标类型,value 是要转换的值。

运算符

算数运算符

1
2
3
4
5
+
-
*
/
%

注意: ++(自增)和--(自减)在Go语言中是单独的语句,并不是运算符。

逻辑运算符

1
2
3
&&
||

位运算符

1
2
3
4
5
6
&
|
^
<<
>>
~

流程控制

if

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
if 表达式{
...
}

if 表达式{
...
}else{
...
}

if 表达式{
...
}else if{
...
}else{
...
}

switch

1
2
3
4
5
6
7
8
switch var1 {
case val1:
...
case val2:
...
default:
...
}

for

基本语法

1
2
3
4
5
6
7
for 初始化语句; 条件表达式; 循环后语句 {
// 循环体
}

for i:=0;i<10;i++{
...
}

条件判断的for循环,相当于while

1
2
3
for 条件表达式 {
// 循环体
}

无限循环

1
2
3
for {
// 循环体
}

for range循环

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

import "fmt"

func main() {
nums := []int{10, 20, 30, 40, 50}
for index, value := range nums {
fmt.Printf("Index: %d, Value: %d\n", index, value)
}
}

>>>
Index: 0, Value: 10
Index: 1, Value: 20
Index: 2, Value: 30
Index: 3, Value: 40
Index: 4, Value: 50

range

range 是一个关键字,常用来迭代数组、切片、映射(map)、字符串和通道(channel)。

1
2
3
4
5
6
7
for index, value := range collection {
// 在这里处理 index 和 value
}

// collection 是可以迭代的集合(如数组、切片、map 等)。
// index 是迭代过程中的索引(对于数组、切片、字符串)或键(对于 map)。
// value 是当前迭代的值。

函数

基本语法

1
2
3
4
func 函数名(参数列表) 返回值类型{
函数体
return 返回值
}
1
2
3
4
func 函数名(参数列表) 返回值列表{
函数体
return
}

可变参数

可以传递可变参数,用...表示

1
2
3
func myfunc(args ...int) {    //0个或多个参数
...
}

args是一个[]int切片,可以遍历和操作。

递归函数

在函数的定义中调用自身的函数称为递归函数。

递归通常有两个主要部分:

  • 定义递归的停止条件,避免无限递归。
  • 在函数内部调用自身,并逐步接近基准情况。

延迟调用

通过defer关键字实现延迟调用。

延迟调用的特点是:无论函数正常执行还是因为 return 或异常退出,defer 注册的语句都会在函数返回之前执行。

defer 通常用于资源清理、文件关闭、解锁互斥锁等场景。

简单示例

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

import "fmt"

func main() {
defer fmt.Println("defer 1")
fmt.Println("2")
fmt.Println("3")
}

>>>
2
3
defer 1

如果有多个defer语句,会按照后进先出的顺序执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package main

import "fmt"

func main() {
defer fmt.Println("defer 1")
fmt.Println("2")
defer fmt.Println("defer 3")
fmt.Println("4")
defer fmt.Println("defer 5")
fmt.Println("6")
}

>>>
2
4
6
defer 5
defer 3
defer 1

函数的数据类型

函数的数据类型描述了它的参数列表和返回值列表,包括参数的数量、顺序以及类型,以及返回值的数量和类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package main

import "fmt"

func func1() {
fmt.Println("This is func1")
}

func func2(num int) int {
return num
}

func main() {
fmt.Printf("%T\n", func1)
fmt.Printf("%T\n", func2)
}

>>>
func()
func(int) int

函数可以赋值给变量

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

import "fmt"

func func1(num int) int {
return num
}

func main() {
var func2 func(int) int
func2 = func1
fmt.Println(func2(100))
}

>>>
100

匿名函数

基本用法

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

import "fmt"

func main() {
func() {
fmt.Println("This is a func")
}()
}

>>>
This is a func

匿名函数赋值给变量,通过变量调用函数

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

import "fmt"

func main() {
func1 := func() {
fmt.Println("This is a func")
}
func1()
}

>>>
This is a func

匿名函数的返回值赋值给变量

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

import "fmt"

func main() {
ret := func(a, b int) int {
return a * b
}(3, 5)
fmt.Println(ret)
}

>>>
15

回调函数

回调函数可以在一个函数中动态调用另一个函数。函数作为参数传递给另一个函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import "fmt"

func add(a, b int) int {
return a + b
}

func operate(a, b int, fun func(int, int) int) int {
ret := fun(a, b)
return ret
}

func main() {
fmt.Println(operate(1, 2, add))
}

>>>
3

回调函数调用匿名函数

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

import "fmt"

func operate(a, b int, fun func(int, int) int) int {
ret := fun(a, b)
return ret
}

func main() {
add := func(a, b int) int {
return a + b
}
fmt.Println(operate(1, 2, add))
}

>>>
3

在回调函数中直接传递匿名函数

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

import "fmt"

func operate(a, b int, fun func(int, int) int) int {
ret := fun(a, b)
return ret
}

func main() {
fmt.Println(operate(1, 2, func(a int, b int) int {
return a + b
}))
}

>>>
3

闭包

闭包(Closure)是指一个函数包含了它外部作用域中的变量,即使在外部作用域结束后,这些变量依然可以被内部函数访问和修改。闭包使得函数可以“记住”外部作用域的状态,这种状态在函数调用之间是保持的。

闭包是一个函数与其外部环境变量的组合。

闭包的核心概念是函数内部可以引用外部作用域的变量,即使在函数内部外部作用域已经结束。

简单的闭包示例

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
28
29
30
31
32
package main

import "fmt"

func test() func() int {
counter := 0
return func() int {
counter++
return counter
}
}

func main() {
func1 := test()
fmt.Println("func1:", func1())
fmt.Println("func1:", func1())
fmt.Println("func1:", func1())
func2 := test()
fmt.Println("func2:", func2())
fmt.Println("func2:", func2())
fmt.Println("func2:", func2())
fmt.Println("func1:", func1())
}

>>>
func1: 1
func1: 2
func1: 3
func2: 1
func2: 2
func2: 3
func1: 4

Go语言内置函数

len

用于返回特定数据类型的长度或大小。

cap

用于获取切片、数组或通道(channel)的容量。

make

为slice、map或chan初始化并返回引用。make仅仅用于创建slice、map和channel,并返回它们的实例。

函数签名

1
2
3
4
func make(t Type, size ...IntegerType) Type

// t slice, map或channel类型。
// size 整数类型切片,面对不同类型时意义也不同。是否可省略跟创建类型有关。

创建slice

1
make([]Type, len, cap)

append

append 用于向切片中添加元素。

append 可以将一个或多个元素添加到现有的切片中,并返回一个新的切片。

如果切片的容量不足以容纳新元素,append 会自动分配一个新的、更大的底层数组,并返回一个新的切片。

1
slice = append(slice, elements...)

容量每次成倍增加。

每次扩容后,切片的地址就会发生改变。

copy

用于从一个切片复制元素到另一个切片中。

1
2
3
4
func copy(dst, src []Type) int

// dst: 目标切片,复制的内容会放到这个切片中。
// src: 源切片,数据会从这个切片中读取。

append扩容的底层是通过copy实现的。

示例

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
28
package main

import "fmt"

func main() {
var s = []int{1, 2, 3, 4, 5}
fmt.Println(s)
fmt.Printf("%p\n", s)
fmt.Println(len(s))
fmt.Println(cap(s))
s = append(s, 1, 2)
fmt.Println(s)
fmt.Printf("%p\n", s)
fmt.Println(len(s))
fmt.Println(cap(s))
}

>>>
[1 2 3 4 5]
0xc0000081b0
5
5
[1 2 3 4 5 1 2]
0xc0000081b0
7
10


... 在 Go 中用于扩展切片,将其元素展开为单独的参数传递给函数。

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

import "fmt"

func main() {
var s = []int{1, 2, 3, 4, 5}
s = append(s, s...) //
fmt.Println(s)
}

>>>
[1 2 3 4 5 1 2 3 4 5]

delete

用于从 map 中删除指定的键值对。

1
2
3
4
delete(map, key)

// map:表示目标映射(map 类型)。
// key:表示要删除的键。

new

1
ptr := new(T)

new用于为指定的类型分配内存,并返回指向该类型的指针。

数组

在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
package main

import "fmt"

func main() {
var arry [5]int
for i := 0; i < 5; i++ {
arry[i] = i * 100
}
fmt.Println(arry)
fmt.Printf("%T", array)
fmt.Printf("%p\n", &arry[0])
fmt.Printf("%p\n", &arry[1])
fmt.Printf("%p\n", &arry[2])
fmt.Printf("%p\n", &arry[3])
fmt.Printf("%p\n", &arry[4])
}

>>>
[0 100 200 300 400]
[5]int
0xc000010330
0xc000010338
0xc000010340
0xc000010348
0xc000010350

数组的初始化

1
2
3
4
5
6
7
8
9
10
11
12
// 第一种方式
var array [5]int
array = [5]int{1, 2, 3, 4, 5}

// 第二种方式
var array = [5]int{1, 2, 3, 4, 5}

// 第三种方式
array := [5]int{1, 2, 3, 4, 5}

// 第四种方式
array := [...]int{1, 2, 3, 4, 5}

数组的值传递

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main

import "fmt"

func main() {
array1 := [...]int{1, 2, 3, 4, 5}
fmt.Println("array1:", array1)
fmt.Printf("array1:%p\n", &array1)
array2 := array1 // 将array1赋值给array2
array2[0] = 100
fmt.Println("array2:", array2)
fmt.Printf("array2:%p\n", &array2)
fmt.Println("array1:", array1)
fmt.Printf("array1:%p\n", &array1)
}

>>>
array1: [1 2 3 4 5]
array1:0xc00012e000
array2: [100 2 3 4 5]
array2:0xc00012e060
array1: [1 2 3 4 5]
array1:0xc00012e000

利用函数传递数组参数

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
package main

import "fmt"

func update(array2 [5]int) {
array2[0] = 100
fmt.Println("array2:", array2)
fmt.Printf("array2:%p\n", &array2)
}

func main() {
array1 := [...]int{1, 2, 3, 4, 5}
fmt.Println("array1:", array1)
fmt.Printf("array1:%p\n", &array1)
update(array1)
fmt.Println("array1:", array1)
fmt.Printf("array1:%p\n", &array1)
}

>>>
array1: [1 2 3 4 5]
array1:0xc000010330
array2: [100 2 3 4 5]
array2:0xc000010390
array1: [1 2 3 4 5]
array1:0xc000010330 // 数组是值传递

多维数组

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

import "fmt"

func main() {
var array = [4][3]int{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}, {10, 11, 12}}
fmt.Println(array)
fmt.Println(array[0][1])
}

>>>
[[1 2 3] [4 5 6] [7 8 9] [10 11 12]]
2

切片Slice

在 Go 语言中,切片(slice)是一个动态数组,是对数组的抽象。它比数组更加灵活,因为切片的长度可以动态调整,而数组的长度是固定的。

引用类型:切片本质是一个对底层数组的引用。

  • 切片本身不存储数据,底层指向的是数组。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
package main

import "fmt"

func main() {
var s []int
var a [5]int
fmt.Printf("Slice:%T\n", s)
fmt.Printf("Slice:%T\n", a)
}

>>>
Slice:[]int(切片)
Slice:[5]int(数组)
1
2
3
4
5
6
7
8
9
10
11
package main

import "fmt"

func main() {
var s []int = []int{1, 2, 3, 4, 5}
fmt.Printf("%T", s)
}

>>>
[]int // 切片打印的类型里不包含长度

通过数组创建切片

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

import "fmt"

func main() {
array := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
s1 := array[0:9]
fmt.Printf("%T\n", s1)
fmt.Println(s1)
}

>>>
[]int
[1 2 3 4 5 6 7 8 9]

这种方式创建的切片的长度和容量的关系

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package main

import "fmt"

func main() {
array := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
s1 := array[0:5]
fmt.Println(s1)
fmt.Println("s1--len:", len(s1), "cap:", cap(s1))
s2 := array[1:5]
fmt.Println(s2)
fmt.Println("s2--len:", len(s2), "cap:", cap(s2))
s3 := array[3:10]
fmt.Println(s3)
fmt.Println("s3--len:", len(s3), "cap:", cap(s3))
}

>>>
[1 2 3 4 5]
s1--len: 5 cap: 10
[2 3 4 5]
s2--len: 4 cap: 9
[4 5 6 7 8 9 10]
s3--len: 7 cap: 7

切片的容量是从切片起始位置到底层数组末尾的元素个数。

使用make创建切片

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

import "fmt"

func main() {
map1 := make([]map[int]string, 5) //使用make创建切片,需要初始化长度,容量可选。
fmt.Println(map1)
fmt.Printf("%T", map1)
}

>>>
[map[] map[] map[] map[] map[]]
[]map[int]string

map

在Go语言中Map是一种无序的键值对结构。

map的声明

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

import "fmt"

func main() {
var map1 map[string]int
fmt.Println(map1)
}


>>>
map[] //map是一种引用数据类型,未初始化值为nil(空)

使用make创建map

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

import "fmt"

func main() {
map1 := make(map[int]string)
fmt.Println(map1)
fmt.Printf("%T", map1)
}

>>>
map[]
map[int]string

向map中添加元素

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

import "fmt"

func main() {
var map1 map[string]int
map1 = map[string]int{"one": 1, "two": 2, "three": 3}
fmt.Println(map1)
}

>>>
map[one:1 three:3 two:2]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package main

import "fmt"

func main() {
var map1 map[string]int
map1 = make(map[string]int) // 在使用前进行初始化,未初始化的 map 是 nil,不能直接用于存储键值对。
map1["one"] = 1
map1["two"] = 2
map1["three"] = 3
fmt.Println(map1)
}

>>>
map[one:1 three:3 two:2]

遍历map

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package main

import "fmt"

func main() {
var map1 map[string]int

map1 = make(map[string]int)
map1["one"] = 1
map1["two"] = 2
map1["three"] = 3
for key, value := range map1 {
fmt.Println(key, value)
}
}

>>>
three 3
one 1
two 2
  • map是无序的,遍历时结果的顺序不一样
  • map的长度不固定,是引用类型
  • 可以用len查看map的长度,但不能使用cap查看map的容量
  • map的key可以是所以可以比较的类型。

map结合切片进行使用

1.使用map存储学生信息

2.每个map保存一个学生的信息

3.将这些map存储到切片里

4.打印这些学生的信息

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
28
29
30
31
32
33
package main

import "fmt"

func main() {
stu1 := map[string]string{"name": "stu1", "age": "17", "gender": "male"}
stu2 := make(map[string]string)
stu2["name"] = "stu2"
stu2["gender"] = "female"
stu2["age"] = "18"
var stu3 = map[string]string{"name": "stu3", "age": "17", "gender": "male"}
s := append(make([]map[string]string, 0), stu1, stu2, stu3)
fmt.Println(s)
for _, value := range s {
fmt.Println(value)
}
for _, value := range s {
fmt.Print("name\t", value["name"], "\t")
fmt.Print("gender\t", value["gender"], "\t")
fmt.Println("age", value["age"])

}

}

>>>
[map[age:17 gender:male name:stu1] map[age:18 gender:female name:stu2] map[age:17 gender:male name:stu3]]
map[age:17 gender:male name:stu1]
map[age:18 gender:female name:stu2]
map[age:17 gender:male name:stu3]
name stu1 gender male age 17
name stu2 gender female age 18
name stu3 gender male age 17

指针

指针是存储变量地址的变量。

指针的声明

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

import "fmt"

func main() {
var ptr *int
fmt.Println(ptr)
fmt.Printf("%T", ptr)
}

>>>
<nil>
*int

数组指针

存储数组地址的指针

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

import "fmt"

func main() {
var arr = [4]int{}
var ptr = &arr
fmt.Printf("%p\n", &arr)
fmt.Printf("%p\n", ptr)
}

>>>
0xc0000161c0
0xc0000161c0

通过数组指针来修改数组

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

import "fmt"

func main() {
var arr = [4]int{1, 2, 3, 4}
var ptr = &arr
(*ptr)[0] = 5
fmt.Println(*ptr)
}

>>>
[5 2 3 4]

数组指针语法糖

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

import "fmt"

func main() {
var arr = [4]int{1, 2, 3, 4}
var ptr = &arr
ptr[0] = 5
fmt.Println(*ptr)
}

>>>
[5 2 3 4]

指针数组

一个数组,数组里面的元素是指针

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

import "fmt"

func main() {
a := 1
b := 2
c := 3
d := 4
arr := [4]*int{&a, &b, &c, &d}
fmt.Println(arr)
*arr[0] = 100
fmt.Println(a)
}
[0xc00010c098 0xc00010c0b0 0xc00010c0b8 0xc00010c0c0]
100

指针函数

指针函数是一个函数,返回值是指针。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package main

import "fmt"

func main() {
ptr := pointer()

fmt.Printf("%p\n", ptr)
fmt.Println(*ptr)
fmt.Println(ptr[0])
}

func pointer() *[4]int {
arr := [4]int{1, 2, 3, 4}
return &arr
}

>>>
0xc000122040
[1 2 3 4]
1

指针作为函数的参数

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

import "fmt"

func add(x *int, y *int, z *int) {
*z = *x + *y
}

func main() {
x := 100
y := 200
var z int
add(&x, &y, &z) //add函数的参数是三个指针
fmt.Println(z)
}

>>>
300

type

type关键字用于自定义新的类型或为现有类型创建别名。

自定义新的类型

1
type 新类型名 基础类型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package main

import "fmt"

type Myint int // 定义了一个新类型Myint

func main() {
var myint Myint
fmt.Printf("%T\n", myint)
myint = 10
fmt.Printf("%d", myint)
}

>>>
main.Myint
10

为现有类型创建别名

1
type 新类型名 = 已有类型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package main

import "fmt"

type Myint = int

func main() {
var myint Myint
fmt.Printf("%T\n", myint)
myint = 10
fmt.Printf("%d", myint)
}

>>>
int
10

结构体

结构体定义

结构体是一种自定义数据类型,可以封装多个基本数据类型。

可以通过struct来定义自己的类型。

1
2
3
4
5
type 类型名 struct {
字段名 字段类型
字段名 字段类型
...
}

示例:Person类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package main

import "fmt"

//定义结构体
type Person struct {
name string
age int
gender string
}

func main() {
var person Person // 结构体实例化
person.name = "Bileton"
person.age = 21
person.gender = "male"
fmt.Println(person)
}

>>>
{Bileton 21 male}

创建对象的方式

这里以Person结构体为例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 第一种,先声明,然后初始化
var person Person
person.name = "Bileton"
person.age = 21
person.gender = "male"
// 第二种
person := Person{}
person.name = "Bileton"
person.age = 21
person.gender = "male"
// 第三种
person :=Person{name: "Bileton", age: 30, gender: "male"}
// 第四种,这种方式需要参数顺序与结构体中的参数顺序匹配
person := Person{"Bileton", 30, "male"}

结构体指针

结构体是值类型

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

import "fmt"

type Person struct {
name string
age int
gender string
}

func main() {
person := Person{"Bileton", 30, "male"}
personc := person
personc.gender = "female"
fmt.Println(person)
}

>>>
{Bileton 30 male} // 修改personc不影响person
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package main

import "fmt"

type Person struct {
name string
age int
gender string
}

func main() {
person := Person{"Bileton", 30, "male"}
personc := &person // personc是指针
(*personc).gender = "female"
fmt.Println(person)
}

>>>
{Bileton 30 female}

结构体指针语法糖

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

import "fmt"

type Person struct {
name string
age int
gender string
}

func main() {
person := Person{"Bileton", 30, "male"}
personc := &person // personc是指针
personc.gender = "female" // 语法糖写法
fmt.Println(person)
}

>>>
{Bileton 30 female}

匿名结构体

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

import "fmt"

func main() {
// 匿名结构体
per := struct {
name string
age int
}{
name: "Bileton",
age: 30,
}
fmt.Println(per)
}

>>>
{Bileton 30}

结构体匿名字段

结构体的字段没有名字,但是不能有重复的数据类型

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

import "fmt"

type Person struct {
string
int
}

func main() {
per := Person{"Bileton",21}
fmt.Println(per)
}

>>>
{Bileton 21}

结构体嵌套

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
28
package main

import (
"fmt"
)

type Person struct {
name string
age int
gender string
}

type Student struct {
Person
school string
grade string
}

func main() {
stu1 := new(Student)
stu1.Person = Person{"JetBrains", 20, "M"}
stu1.school = "yangguangxiaoyuan"
stu1.grade = "2"
fmt.Println(*stu1)
}

>>>
{{JetBrains 20 M} yangguangxiaoyuan 2}

结构体导出

在Go语言中,结构体的导出指的是如何让结构体及其字段在包(package)外部访问。Go 使用 大小写 来决定标识符的可见性:

  • 大写开头的标识符(包括结构体、字段、函数、方法等)是导出的,可以被其他包访问。

  • 小写开头的标识符未导出的,只能在当前包中使用。

面向对象

Go语言不是面向对象的语言,它采用了一种“简化版”的面向对象模型,强调组合而非继承,接口而非类型层次。

继承

在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
28
29
30
package main

import (
"fmt"
)

type Person struct {
name string
age int
gender string
}

type Student struct {
Person // 匿名字段
school string
grade string
}

func main() {
stu1 := new(Student)
stu1.name= "JetBrains" // 字段提升
stu1.age = 20 // 字段提升
stu1.gender = "M"
stu1.school = "yangguangxiaoyuan"
stu1.grade = "2"
fmt.Println(*stu1)
}

>>>
{{JetBrains 20 M} yangguangxiaoyuan 2}

方法

在Go语言中定义方法需要有一个额外的接受者,用于指定方法属于哪个类型。

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

import "fmt"

// 定义一个结构体
type Person struct {
Name string
Age int
}

// 为结构体定义方法
func (p Person) SayHello() {
fmt.Printf("Hello, my name is %s and I am %d years old.\n", p.Name, p.Age)
}

func main() {
p := Person{Name: "Alice", Age: 25}
p.SayHello() // 调用方法
}

方法与函数的区别

函数:独立存在,不依附于任何类型。

方法:绑定到某个具体类型,通过类型的实例调用。

方法的继承

通过嵌套结构体,可以复用嵌套结构体的方法。

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

import "fmt"

type Animal struct{}

// 嵌套结构体的方法
func (a Animal) Speak() {
fmt.Println("I am an animal.")
}

type Dog struct {
Animal // 嵌套
}

func main() {
d := Dog{}
d.Speak() // 调用嵌套结构体的方法
}

方法的重写

结构体嵌套和同名方法覆盖来实现方法的重写。

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
28
29
package main

import "fmt"

type Animal struct{}

// 嵌套结构体的方法
func (a Animal) Speak() {
fmt.Println("I am an animal.")
}

type Dog struct {
Animal // 嵌套
}

// 外层结构体定义了与嵌套结构体同名的方法
func (d Dog) Speak() {
fmt.Println("I am a dog.")
}

func main() {
d := Dog{}

// 调用外层结构体的方法
d.Speak() // 输出: I am a dog.

// 调用嵌套结构体的方法
d.Animal.Speak() // 输出: I am an animal.
}

接口

接口是方法的集合。

接口只定义方法的签名,不实现具体的逻辑。

只要一个类型实现了接口中的所有方法,该类型就自动实现了接口。

在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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
package main

import "fmt"

type USB interface {
input() string
output(data string)
}

type Mouse struct {
data string
}

type Keyboard struct {
data string
}

func (m Mouse) input() string {
fmt.Print("Mouse input:")
var data string
_, err := fmt.Scanln(&data)
if err != nil {
return ""
}
return data
}

func (m Mouse) output(data string) {
fmt.Println("this is a Mouse")
fmt.Println(data)
}

func (k Keyboard) input() string {
fmt.Print("Keyboard input:")
var data string
_, err := fmt.Scanln(&data)
if err != nil {
return ""
}
return data
}

func (k Keyboard) output(data string) {
fmt.Println("this is a Keyboard")
fmt.Println(data)
}

func main() {
mouse := new(Mouse)
mouse.data = mouse.input()
mouse.output(mouse.data)
keyboard := new(Keyboard)
keyboard.data = keyboard.input()
keyboard.output(keyboard.data)
}

多态

在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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
package main

import "fmt"

// 定义接口
type Animal interface {
eat()
sleep()
}

type Cat struct {
name string
}

type Dog struct {
name string
age int
}

func (cat Cat) eat() {
fmt.Println(cat.name, "is eating")
}

func (cat Cat) sleep() {
fmt.Println(cat.name, "is sleeping")
}
func (dog Dog) eat() {
fmt.Println(dog.name, "is eating")
}

func (dog Dog) sleep() {
fmt.Println(dog.name, "is sleeping")
}

func main() {
cat := new(Cat)
cat.name = "Tom"
test(*cat)
dog := new(Dog)
dog.name = "Jack"
test(*dog)
}


func test(a Animal) {
a.eat()
a.sleep()
}

>>>
Tom is eating
Tom is sleeping
Jack is eating
Jack is sleeping

空接口

在Go语言中,空接口是一个非常特殊的接口类型,表示可以存储任何类型的值。

1
type interface{}
  • 空接口没有任何方法,因此所有类型都实现了空接口。

  • 换句话说,任何类型的值都可以赋值给空接口。

any

any是空接口的别名,可以用来替代interface{}

any 的本质是一个简单的语法糖,表示空接口interface{}

1
type any = interface{}

接口类型断言

在 Go 语言中,类型断言(Type Assertion)用于将接口类型的变量转换为具体的类型,从而访问存储在接口中的值。

1
value, ok := x.(T)
  • x 是一个接口类型的变量。

  • T 是要断言的具体类型。

  • value 是转换后的具体类型值。

  • ok 是一个布尔值,表示断言是否成功。

    • 如果成功,oktrue,并且 value 是断言后的值。

    • 如果失败,okfalsevalue 为零值。

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

import "fmt"

func main() {
var i interface{} = "Hello, Go!"

// 尝试将 i 转换为字符串类型
if str, ok := i.(string); ok {
fmt.Printf("The value is a string: %s\n", str)
} else {
fmt.Println("The value is not a string")
}
}

>>>
The value is a string: Hello, Go!

类型断言与switch搭配使用

.(type)只能出现在switch语句中

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
28
29
package main

import "fmt"

func checkType(i interface{}) {
switch v := i.(type) {
case string:
fmt.Printf("The value is a string: %s\n", v)
case int:
fmt.Printf("The value is an integer: %d\n", v)
case bool:
fmt.Printf("The value is a boolean: %t\n", v)
default:
fmt.Printf("Unknown type: %T\n", v)
}
}

func main() {
checkType("Hello, Go!")
checkType(123)
checkType(true)
checkType(3.14)
}

>>>
The value is a string: Hello, Go!
The value is an integer: 123
The value is a boolean: true
Unknown type: float64

接口嵌套

在 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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
package main

import "fmt"

// 定义接口 AA 和 BB
type AA interface {
testAA()
}

type BB interface {
testBB()
}

// 定义接口CC,组合AA和BB
type CC interface {
AA
BB
testCC()
}

// 定义结构体 Test,嵌套接口CC
type Test struct {
CC
}


func (test Test) testAA() {
fmt.Println("This is testAA")
}

func (test Test) testBB() {
fmt.Println("This is testBB")
}

func (test Test) testCC() {
fmt.Println("This is testCC")
}

func main() {
test := new(Test)
test.testAA()
test.testBB()
test.testCC()

var ttt AA = test // 接口转换
ttt.testAA()
}

>>>
This is testAA
This is testBB
This is testCC
This is testAA

嵌套接口中出现同名方法

在 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
28
29
30
package main

import "fmt"

type Engine interface {
Start()
}

type Transmission interface {
Start()
}

type Vehicle interface {
Engine
Transmission
}

type Car struct{}

func (c Car) Start() {
fmt.Println("Car is starting...")
}

func main() {
var v Vehicle = Car{}
v.Start() // 调用 Start 方法
}

>>>
Car is starting...

error错误

在 Go 语言中,error 是内置接口,专门用于表示和处理错误。

Go 语言提倡将错误作为普通值处理,通过显式返回错误对象来提高代码的安全性和健壮性。

error的定义

1
2
3
type error interface {
Error() string
}

示例

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

import (
"fmt"
"os"
)

func main() {
file, err := os.Open("test.txt")
if err != nil {
fmt.Println(err)
return
}
fmt.Println(file.Name())
}

>>>
open test.txt: The system cannot find the file specified.

error示例

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
package main

import (
"errors"
"fmt"
)

func main() {
age_err := printAge(-1)
if age_err != nil {
fmt.Println(age_err)
}

}

func printAge(age int) error {
if age < 0 {
return errors.New("age must be greater than zero")
}
fmt.Println("age:", age)
return nil
}

>>>
age must be greater than zero

自实现error接口

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
28
29
30
31
32
33
34
35
36
37
38
39
package main

import "fmt"

type myError struct {
code int
}

func (e *myError) Error() string {
return fmt.Sprintf("myError:%d", e.code)
}

func main() {
i, err := test(1)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(i)

}

func test(i int) (int, error) {
switch i {
case 1:
return 1, &myError{code: 1}
case 2:
return 2, &myError{code: 2}
case 3:
return 3, &myError{code: 3}
case 4:
return 4, &myError{code: 4}
default:
return 777, nil
}
}

>>>
myError:1

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
28
29
30
31
32
33
34
package main

import "fmt"

func main() {
fmt.Println("This is a Calc Application")
for {
var num1, num2 int
fmt.Print("Please input your first num:")
fmt.Scan(&num1)
fmt.Print("Please input your second num:")
fmt.Scan(&num2)
fmt.Print("Please input your operation (+, -, *, /, exit):")
var operation string
fmt.Scan(&operation)
switch operation {
case "+":
fmt.Printf("Result: %d\n", num1+num2)
case "-":
fmt.Printf("Result: %d\n", num1-num2)
case "*":
fmt.Printf("Result: %d\n", num1*num2)
case "/":
if num2 == 0 {
fmt.Println("Error: Division by zero is not allowed. Please try again.")
continue
}
result := float64(num1) / float64(num2)
fmt.Printf("Result: %.2f\n", result)
default:
fmt.Println("Invalid operation. Please enter +, -, *, /, or exit.")
}
}
}

9*9乘法表

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

import "fmt"

func main() {
for i := 1; i < 10; i++ {
for j := 1; j <= i; j++ {
fmt.Printf("%d * %d = %d\t", j, i, i*j)
}
fmt.Println()
}
}

打印菱形

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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
package main

import "fmt"

const CIR = 7

func Prt(xing, blank int) {
for i := 0; i < blank; i++ {
fmt.Print(" ")
}
for j := 0; j < xing; j++ {
fmt.Print("*")
}
for i := 0; i < blank; i++ {
fmt.Print(" ")
}
fmt.Println()
}

func main() {
var xing = 1
var blank = (CIR - xing) / 2
for i := 1; i <= CIR; i++ {
Prt(xing, blank)
if i < int(CIR/2)+1 {
xing = xing + 2
blank = blank - 1
} else {
xing = xing - 2
blank = blank + 1
}
}
}

>>>
*
***
*****
*******
*****
***
*

递归求和

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

import "fmt"

func getSum(n int) int {
if n == 1 {
return 1
}
return getSum(n-1) + n

}

func main() {
fmt.Println(getSum(3))
}

递归实现斐波那契数列

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

import "fmt"

func FBI(n int) int {
if n == 0 {
return 0
} else if n == 1 {
return 1
}
return FBI(n-1) + FBI(n-2)
}

func main() {
fmt.Println(FBI(4))
}

冒泡排序

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

import "fmt"

func main() {
var array = [10]int{23, 45, 67, 12, 89, 34, 56, 78, 90, 11}
for i := 0; i < 9; i++ {
for j := 0; j < 9-i; j++ {
if array[j] > array[j+1] {
array[j], array[j+1] = array[j+1], array[j]
}
}
}
fmt.Println(array)
}

>>>
[11 12 23 34 45 56 67 78 89 90]

通过map和slice写一个简单的图书数据库,并可以通过书名或书号来查询书的位置

一个map存放一本书的信息和书的位置

一个切片存放一组书

一个切片存放所有的书

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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
package main

import "fmt"

func main() {
book1 := map[string]string{"name": "wangluo", "id": "001", "position": "floor1"}
book2 := map[string]string{"name": "shujuku", "id": "002", "position": "floor1"}
book3 := map[string]string{"name": "mimaxue", "id": "003", "position": "floor2"}
book4 := map[string]string{"name": "C", "id": "004", "position": "floor2"}
book5 := map[string]string{"name": "C++", "id": "005", "position": "floor3"}
book6 := map[string]string{"name": "Python", "id": "006", "position": "floor3"}
floor1 := append(make([]map[string]string, 0), book1, book2)
floor2 := append(make([]map[string]string, 0), book3, book4)
floor3 := append(make([]map[string]string, 0), book5, book6)
database := append(make([][]map[string]string, 0), floor1, floor2, floor3)
fmt.Println("Please select your query_style: (name or id)")
var queryStyle string
find_it := 0
fmt.Scan(&queryStyle)
if queryStyle == "name" {
fmt.Print("input book name:")
var name string
fmt.Scanln(&name)
for _, floor := range database {
for _, book := range floor {
if name == book["name"] {
find_it = 1
fmt.Println("You have the name of book [" + book["name"] + "]")
fmt.Print("name\t", book["name"], "\t")
fmt.Print("id\t", book["id"], "\t")
fmt.Print("position\t", book["position"], "\n")
break
}
}
if find_it == 1 {
break
}
}
if find_it == 0 {
fmt.Println("no this book!")
}
} else if queryStyle == "id" {
fmt.Print("input book id:")
var id string
fmt.Scanln(&id)
for _, floor := range database {
for _, book := range floor {
if id == book["id"] {
find_it = 1
fmt.Println("You have the name of book [" + book["name"] + "]")
fmt.Print("name\t", book["name"], "\t")
fmt.Print("id\t", book["id"], "\t")
fmt.Print("position\t", book["position"], "\n")
break
}
}
if find_it == 1 {
break
}
}
if find_it == 0 {
fmt.Println("no this book!")
}
} else {
fmt.Println("Yout select is invalid!")
}
}

标准库

fmt

标准库包,用于格式化输入和输出。

输出

  • Print

    直接输出内容。

  • Printf

    格式化输出字符串

  • Println

    会在输出内容末尾自动添加一个换行符。

  • Errorf

    创建带格式的错误对象,返回一个 error 类型的值。

    1
    2
    3
    4
    5
    func Errorf(format string, a ...interface{}) error

    format:格式化字符串,与 fmt.Sprintf 的工作方式相同。
    a ...interface{}:可变参数列表,用于插入格式化字符串中的占位符。
    error:返回一个实现了 error 接口的错误对象。
  • Sprintf

    1
    2
    3
    4
    5
    func Sprintf(format string, a ...interface{}) string

    format:格式化字符串,支持占位符(即格式化动词)。
    a ...interface{}:可变参数,用于替换占位符。
    string:返回一个格式化后的字符串。

输入

获取用户的输入。

  • Scan

    从标准输入扫描文本,读取由空白符分隔的值保存到传递给本函数的参数中,换行符视为空白符。

  • Scanf

    根据format参数指定的格式去读取由空白符分隔的值保存到传递给本函数的参数中。

  • Scanln

    用于按行读取用户输入,并且按空白字符分隔输入数据,在读取到换行符(回车)时停止。

格式化占位符

1
2
3
4
5
6
7
8
%T  //数据类型
%t //true或false
%b //二进制
%d //十进制
%x //十六进制
%f //浮点数
%s //字符串或[]byte
%p //指针