Here remains my dream.

程序设计#0 - 前期准备

14 min

叠甲: 这一篇短文与其说是教程,更像是笔记,所以可能多有缺漏或不严谨之处,尚祈见谅。我给出的方法不是最好的,但是一定是最适合新手实践的,这也是我写这篇教程的初衷。这系列教程是程序设计的教程,包括流程处理、数据类型、数据结构、经典算法以及复杂度等内容,在保证内容准确的前提下能够让新手快速上手,不必忍受当前教程中存在的专有名词过多、重理论而轻实践等问题。教程将保持使用 Go 语言,但不同语言的使用逻辑是一致的,仅仅在表达方式上有所不同。一些计算机常用概念和互联网问题在 https://cn.bing.com 都能找到解答,这里不会过多探讨。我使用的系统是 Windows 11,如果碰上 Linux 或 Mac 系统的问题,我很难给出有效的解决方法,但可以将你的问题具体地写出来,我尽量帮。欢迎批评,也欢迎提出探讨的新思路。

Go 语言安装和 IDE 下载

Go 语言安装包📦去这里下载 → All releases - The Go Programming Language

Go 语言下载
Go 语言下载

下载安装包(比如我使用 Windows 系统,就选择 .windows-amd64.msi),并运行安装。务必记住你的安装路径!

⚠️配置环境变量。环境变量是系统的“指挥中心”,它需要知道 Go 的“基地”在哪。

  1. 打开设置窗口: 🔍 在 Windows 搜索栏直接输入“环境变量”,选择“编辑系统环境变量”。
  2. 修改 Path 变量:
    • 在“系统变量”区域找到 Path,点击“编辑”。
    • 点击“新建”,然后填入你的 Go 安装路径加上 \bin。例如我的安装路径是 E:\Programs\Golang ,那就新增一条 E:\Programs\Golang\bin
    • ✅完成后一直选择“确定”关闭所有窗口。
  3. 确认 GOROOT(通常已自动设置):
    • 检查“系统变量”里是否有 GOROOT,其值就是你的安装路径。如果没有,就新建一个。
这是我的安装路径
这是我的安装路径

设置好这些后,打开命令行(Win + R 输入 cmd),输入 go version,如果输出版本号就代表安装成功。✅

C:\Users\Karlbaey> go version
go version go1.24.5 windows/amd64

IDE 推荐下载 VS Code → Download Visual Studio Code - Mac, Linux, Windows,汉化与美化教程很多,这里不做说明。然后打开一个 .go 后缀的文件,通常 VS Code 会提示是否安装 Go 语言插件,点击允许安装,这样 IDE 也配置好了。

Go 语言插件外观
Go 语言插件外观

Hello, World!

💡开一个新文件夹,创建一个 main.go 文件,把下面的代码复制进去。

package main // 当前的包名

import "fmt" // 导入 fmt 包(格式化输入输出)

func main() { // 主函数
    fmt.Println("Hello, World!") // 使用 fmt 包的 Println 函数,输出一行字符串
}

然后在你的文件夹中右键打开 cmd,输入 go run main.go

E:\Golang\algorithm\Helloworld> go run main.go
Hello, World!

这样就算写好了一个程序。

如果你要把这个程序提交到洛谷之类的 OJ 平台1,这样写是非常必要的,这样的话你的代码才能被编译成一个可执行文件(在 Windows 上是 .exe 后缀),编译的意思是将源代码转换为计算机可以执行的机器语言代码的过程,例如,我们写的 “Hello, World!” 程序就是源代码,在 cmd 执行 go build main.go 就是执行编译。 所以这里提供了一个模板,可以作为在 OJ 平台的输入输出模板。

package main

import "fmt"

func main() {
    var n int // 假设输入是一个整数,称作 n
    fmt.Scan(&n) // 输入 n。前面的 & 表示指向
    
    /* 程序 */
    
    fmt.Println(m) // 输出 m
}

main() 是这个程序的主函数,这个程序所有会被执行的代码都在里面。Go 语言的函数使用 func 来定义。

格式化输出

Println 很好用,但有时候我们想控制输出的格式,比如让小数只保留两位。这时就要用 fmt.Printf 了。它使用格式化动词来指定格式。

常用格式化动词:

  • %v:通用占位符,什么类型都能用(推荐初学者先用这个)
  • %T:打印变量的类型
  • %s:打印字符串
  • %d:打印整数
  • %f:打印小数。可以用 %.2f 来控制保留两位小数。
  • %t:打印布尔值
  • \n:换行符(Printf 不会自动换行,经常需要手动加 \n

例如上文的 n,我们可以使用这两种方式输出。它们是完全等价的。

fmt.Println(n)
fmt.Printf("%d\n", n)

其中有一些表达式,例如 var&n,我们之后会说明用途和意思。

Go 数据类型

💡数据类型就是存储数据的方式,存放不同种类,不同大小的数据时使用合适的数据类型可以帮助程序优化性能。下面列出一些常用的数据类型以及它们的默认值。

默认值列表
默认值列表

Go 语言的字符串使用 UTF-8 编码。这里有一个未提到的数据类型 rune,它能够存一个 UTF-8 字符(例如一个汉字);而 byte 能存储一个 ASCII 字符。比如,我们这样写一个程序。

package main

import "fmt"

func main() {
    var a byte = 'a'
    var b rune = ''
    c := int(a)
    d := int(b)
    
    fmt.Println(c)
    fmt.Println(d)
}

var 是定义变量的关键字,此处我们把变量 a 赋值为 byte 类型,实际值是 'a'。在定义 b 时使用了自动类型推断,也就是 :=:= 只能在函数体内部,给局部变量使用;而 var 则没有限制),它能够将 int(a) 直接作为一个整数赋值给 b,而省去了输入数据类型的麻烦,所以这种定义变量的方式是最常用的。Go 不喜欢浪费,每个被定义的变量都必须被使用。

这段程序的输出是这样:2

97
23383

查 ASCII 码表,会发现 a 恰好对应 97

ASCII 来源:维基百科
ASCII 来源:维基百科

而“字”的 Unicode 码点是 U+5B57,转换成十进制就是 23383


boolintstring 以及上面提到的数据都是存储单个数据,Go 中还有存储多个数据的集合类型,例如数组(array)、切片(slice)和映射(map)

数组: 数组是固定长度的容器。就像是一排鸡蛋纸盒,只能容纳固定数量的鸡蛋,数组也是这样3数组只能容纳相同类型的数据。数组有索引,索引能告诉我们在数组中某个位置的元素。索引从 0 开始,数组的第一位元素索引为 0、第二位为 1……以此类推。

package main

import "fmt"

func main() {
	a := [5]int{1, 2, 3, 4, 5}
    a[1] = 10 // 修改第二位元素

	fmt.Println(a)
}

输出:

[1 10 3 4 5]

如果试图寻找不存在的索引,程序会在编译时抛出错误。基于上面的程序,可以这样查询。

package main

import "fmt"

func main() {
	a := [5]int{1, 2, 3, 4, 5}

	fmt.Println(a[0]) // 查询第一个元素
	fmt.Println(a[5]) // 不存在
}

输出

# command-line-arguments
.\main.go:9:16: invalid argument: index 5 out of bounds [0:5]

切片: 切片是基于数组的可变序列,与 C++ 的 vector 类似。切片可以引用数组创建。

package main

import "fmt"

func main() {
	a := [5]int{1, 2, 3, 4, 5}
	b := a[0:3]

	b[1] = 10
	b = append(b, 6) // 在切片 b 后新增元素 6
	b = append(b, 7)
	b = append(b, 8)

	fmt.Println(a)
	fmt.Println(b)
}

输出

[1 10 3 6 7]
[1 10 3 6 7 8]

这时我们就发现了端倪,切片实际上是个引用类型。我们把原数组比作一幅画,切片在这里就是从画里框出了一部分,对框里内容的任何改变都会影响原来的画。反映到数组和切片操作上,那就是从数组引用而来的切片实际上会影响原数组。上面的代码中我们把切片 b (从 a 切片而来)的第二位改为 10,并往后连续接了 6、7、8,数组 a 同样发生了改变,但仍然保持长度是 5。

如果不希望切片的改变影响到原数组,可以用 Go 内置的 make()copy() 函数。

package main

import "fmt"

func main() {
	a := [5]int{1, 2, 3, 4, 5}
	b := make([]int, len(a)) // 复制一份长度为 5(a 的长度)的切片
	copy(b, a[0:3]) // 将 a 第一、二、三位的元素复制进 b,其余元素保持为 0

	b[1] = 10
	b = append(b, 6)
	b = append(b, 7)
	b = append(b, 8)

	fmt.Println(a)
	fmt.Println(b)
}

输出

[1 2 3 4 5]
[1 10 3 0 0 6 7 8]

注意:copy() 函数是短切片优先的。具体操作:

package main

import "fmt"

func main() {
	slice1 := []int{1, 2, 3} // 短切片
	slice2 := make([]int, 10) // 长的空切片
	copy(slice2, slice1) // 将短切片复制给长切片

	fmt.Printf("切片 2:%v\n", slice2)

	slice3 := []int{10, 11, 12, 13, 14} // 长切片
	slice4 := make([]int, 2) // 短的空切片

	copy(slice4, slice3)

	fmt.Printf("切片 4:%v", slice4)
}

输出

切片 2:[1 2 3 0 0 0 0 0 0 0]
切片 4:[10 11]

💡映射: 它使用键值对存储数据,类似 Python 的字典。映射的操作有很多。

package main

import "fmt"

func main() {
	var emptymap map[byte]int                          // 创建空映射
	fmt.Printf("emptymap 是空映射吗?%t\n", emptymap == nil) // nil 表示空

	a := make(map[string]int) // 最常用的初始化方式
	// 也可以直接初始化
	name := map[string]int{
		"Jerry": 256,
		"65":    6512345,
	}

	// 赋值操作
	a["A"] = 1
	a["B"] = 2
	fmt.Println(a)

	// 查询操作
	a_A := a["A"]
	a_C := a["C"] // 查询不存在的值
	fmt.Printf("a 中 A 对应的值是:%d\n", a_A)
	fmt.Printf("a 中 C 对应的值是:%d\n", a_C) // 输出 0(int 默认值)

	// 删除操作
	delete(a, "A")
	fmt.Println(a)

	// 获取值
	sixtyFive := name["65"]
	fmt.Printf("65 的全称是:%d\n", sixtyFive)
}

输出

emptymap 是空映射吗?true
map[A:1 B:2]
a 中 A 对应的值是:1
a 中 C 对应的值是:0
map[B:2]
65 的全称是:6512345

另外,如果要判断映射中是否存在某个键,可以用这种表达式。这叫逗号 ok 模式

// ...
_, ok := name["Karlbaey"]
fmt.Printf("在映射 name 中,键“Karlbaey”存在吗?%t", ok)

输出

在映射 name 中,键“Karlbaey”存在吗?false

这在之后的流程控制语句中非常常见。

这样做的原因是,当向映射查询某个键时,返回值会包含这个键对应的值和这个值是否已经提前赋值。因为我们不关心值具体是什么,所以用一个销毁变量 _ 存储值4


🎉那么,到这里,Go 的基础操作都说清楚了,下一步(如果有的话)会接着学习流程控制语句,函数体与结构体。然后就可以开始学习数据结构和经典算法了。

Footnotes

  1. OJ 是 Online Judge 的缩写,意思是在线评测系统,用来测试你的程序是否正确。

  2. 事实上,代码中定义 cd 的代码是多余的,byterune 本身就以 int 存储,所以直接输出 ab 得到的结果跟 cd 一致。这里是为了理解自动类型推断才这样做的。

  3. 注意:数组不能装鸡蛋。

  4. 事实上,_ 的正式名称是只写变量,但在这里因为给它的值都无法读取,所以我称它为“销毁变量”。