Go语言简单介绍
作为一个从C++来学golang的小白,个人总结了一些C++与Go的区别:
- 并发支持:
- C++在并发编程方面需要依赖于操作系统提供的线程库和同步原语,如std::thread和std::mutex。
- Go在语言级别原生支持并发,通过goroutine和channel来实现并发编程,使得编写并发程序变得更简单和安全。
- 内存管理:
- C++需要手动管理内存,开发者需要负责分配和释放内存,这可能导致内存泄漏和悬挂指针等问题。
- Go具有自动垃圾回收(GC)功能,开发者不需要显式管理内存,Go运行时会自动处理内存回收。
- 应用场景:
- C++通常用于系统级编程、游戏开发、图形图像处理和嵌入式系统等需要高性能和底层控制的领域。
- Go主要用于构建高并发的网络服务、Web后端、云原生应用和分布式系统等领域。
- 语法和简洁性:
- C++的语法相对复杂,支持多种编程范式,因此代码可能显得更冗长。
- Go的设计简洁,舍弃了一些复杂的特性,使得代码更易读、易写。
- 语法特点:
- C++是一种多范式编程语言,支持面向对象编程(OOP)和泛型编程等多种编程范式。它允许开发者使用类、继承、模板等特性。
- Go是一种以并发为中心的编程语言,鼓励使用轻量级并发(goroutine)和通信(channel)来处理并发任务,但它没有传统的类和继承概念。
总的来说,C++适用于对性能有严格要求或需要底层控制的任务,而Go则更适合构建高并发的网络应用和分布式系统,让开发人员能够更快速、安全地处理并发任务。
Go如何运行
- 直接编译运行
go run test.go
- 先编译成exe再运行
go build test.go //生成test.exe
./test.exe
Go基本语法
- 打印输出
// 表示这是一个可独立执行的程序,而不是一个库。
package main
// 导入 fmt 包,它提供了格式化的输入和输出功能。
import (
"fmt"
)
// func main() 是程序的主入口点,当程序运行时,将首先执行这个函数。
func main() {
// 调用 fmt 包中的 Println 函数,用于在控制台输出。
fmt.Println("hello world")
}
- 注意:在go语言中, { 必须在函数名后面,不能换行
- 变量的使用
package main
import (
"fmt"
"math"
)
func main() {
// 声明并初始化变量 a 为 "initial",Go会自动推断其类型为字符串。
var a = "initial"
// 声明并初始化变量 d 为布尔类型,并赋值为 true。
var d = true
// 声明并初始化变量 b 和 c 为整数,分别赋值为 1 和 2。
var b, c int = 1, 2
// 声明变量 e 为浮点数,默认值为 0。
var e float64
// 使用简短变量声明语法,将 e 的值转换为 float32,并赋值给变量 f。
f := float32(e)
// 使用字符串拼接,将变量 a 的值与 "foo" 拼接,并赋值给变量 g。
g := a + "foo"
// 使用简短变量声明语法,赋值为1
h := 1
// 使用 fmt.Println 函数打印输出变量的值,每个值之间用空格分隔。
fmt.Println(a, b, c, d, e, f, w) // 输出:initial 1 2 true 0 0
fmt.Println(g) // 输出:initialfoo
// 声明常量 s 为字符串,赋值为 "constant"。
const s string = "constant"
// 声明常量 h 为整数,赋值为 500000000。
const h = 500000000
// 声明常量 i 为浮点数,赋值为 3e20 / h。3e20 表示 3 乘以 10 的 20 次方,即 3 * 10^20。
const i = 3e20 / h
// 使用 fmt.Println 函数打印输出常量的值以及 math 包中的 Sin 函数对 h 和 i 的计算结果。
fmt.Println(s, h, i, math.Sin(h), math.Sin(i))
}
- 注意:在go语言中, 声明的变量必须要使用,否则会编译出错
var关键字用于声明变量,而:=是简短变量声明语法,用于声明并初始化变量。const关键字用于声明常量。常量在声明时必须初始化,而且在编译时其值是固定的,不能被修改。
- 判断
func main() {
// 判断 7 是否为偶数,由于 7%2 的结果为 1,所以条件不成立,执行 else 块中的代码。
if 7%2 == 0 {
fmt.Println("7 is even")
} else {
fmt.Println("7 is odd")
}
// 声明 num 为 9,并进行多重条件判断。
// 首先判断 num 是否小于 0,由于 num 为正数,该条件不成立。
// 接着判断 num 是否小于 10,由于 num 等于 9,该条件成立,输出 "9 has 1 digit"。
// 如果前面两个条件都不成立,则执行最后的 else 块。
if num := 9; num < 0 {
fmt.Println(num, "is negative")
} else if num < 10 {
fmt.Println(num, "has 1 digit")
} else {
fmt.Println(num, "has multiple digits")
}
}
- 循环
func main() {
// 无限循环,不带条件的循环会一直执行,除非遇到 break 语句跳出循环。
for {
fmt.Println("loop")
// 使用 break 语句跳出当前循环,终止无限循环。
break
}
// 使用 for 循环的常规形式,初始化 j 为 7,j 小于 9 时执行循环,每次循环后 j 自增 1。
for j := 7; j < 9; j++ {
fmt.Println(j)
}
// 初始化 n 为 0,n 小于 5 时执行循环,每次循环后 n 自增 1。
// 使用 continue 语句跳过当前循环中的剩余语句,进入下一次循环。
for n := 0; n < 5; n++ {
if n%2 == 0 {
continue
}
fmt.Println(n)
}
// 使用 for 循环的另一种形式,类似于 while 循环,只有条件部分,并且没有初始化和迭代部分。
i := 1
for i <= 3 {
fmt.Println(i)
i = i + 1
}
}
- Go语言只有
for循环一种循环结构,但它有多种使用方式。 - Go中的
for循环有三种形式:无限循环、常规循环、类似while的循环。 - 无限循环通过
for { ... }的形式实现,当循环体内满足跳出条件时,可以使用break语句跳出循环。使用continue关键字可以跳过循环体中剩余的语句,直接进行下一次循环迭代。 - 常规循环使用
for 初始化; 条件; 迭代 { ... }的形式,与其他语言的循环类似。 - 类似
while的循环使用for 条件 { ... }的形式,没有初始化和迭代部分,可以在循环体内手动控制循环条件和迭代步骤。
- switch
func main() {
a := 2
// 使用 switch 语句根据 a 的值进行不同的处理。
switch a {
case 1:
fmt.Println("one")
case 2:
fmt.Println("two") // 输出:two
case 3:
fmt.Println("three")
case 4, 5:
fmt.Println("four or five")
default:
fmt.Println("other")
}
// 获取当前时间。
t := time.Now()
// 使用 switch 语句根据当前时间的小时部分进行不同的处理。
switch {
case t.Hour() < 12:
fmt.Println("It's before noon")
default:
fmt.Println("It's after noon")
}
}
switch后面可以跟一个表达式或者不跟任何表达式。如果不跟表达式,则会在满足条件的分支执行后直接退出,相当于switch true。case后面跟的是一个或多个条件,用于匹配表达式的值。如果匹配成功,将执行相应的代码块。- 可以在一个
case中同时匹配多个值,用逗号分隔,如case 4, 5:。 - 如果没有匹配的
case,则会执行default分支(可选的),用于处理没有匹配的情况。 - Go语言中的
switch语句自带break功能,一旦执行了匹配的case分支,会自动退出整个switch语句。 - 如果希望继续执行后续的
case分支,可以使用fallthrough关键字,但该功能并不常用,一般不推荐使用。
- 数组
func main() {
// 声明一个长度为 5 的整数数组 a,所有元素都会被初始化为整数类型的零值(0)。
var a [5]int
// 在数组 a 中的第 5 个位置(索引为 4)赋值为 100。
a[4] = 100
// 打印数组 a 中索引为 2 的元素的值
fmt.Println("get:", a[2])
// 打印数组 a 的长度,输出:len: 5
fmt.Println("len:", len(a))
// 使用数组字面值的方式创建数组 b,长度为 5,并初始化为 {1, 2, 3, 4, 5}。
b := [5]int{1, 2, 3, 4, 5}
// 打印数组 b 的值,输出:[1 2 3 4 5]
fmt.Println(b)
// 声明一个二维整数数组 twoD,大小为 2x3。
var twoD [2][3]int
// 使用嵌套循环为二维数组 twoD 赋值。
for i := 0; i < 2; i++ {
for j := 0; j < 3; j++ {
twoD[i][j] = i + j
}
}
// 打印二维数组 twoD 的值,输出:2d: [[0 1 2] [1 2 3]]
fmt.Println("2d: ", twoD)
}
- Go语言中的数组是固定长度的数据结构,一旦创建,其长度不可更改。
- 数组的索引从0开始,最大索引为长度减1。
- 可以使用
var关键字声明一个数组,并通过索引赋值和取值。 - 使用数组字面值的方式可以在声明数组的同时进行初始化。
- 切片
func main() {
// 使用 make 函数创建一个长度为 3 的字符串切片 s,初始值为默认零值(空字符串)。
s := make([]string, 3)
// 给切片 s 的前 3 个元素赋值。
s[0] = "a"
s[1] = "b"
s[2] = "c"
// 打印切片 s 的索引为 2 的元素的值,输出:get: c
fmt.Println("get:", s[2])
// 打印切片 s 的长度,输出:len: 3
fmt.Println("len:", len(s))
// 使用 append 函数向切片 s 添加元素 "d"。
s = append(s, "d")
// 使用 append 函数向切片 s 后续添加两个元素 "e" 和 "f"。
s = append(s, "e", "f")
// 打印切片 s 的值,输出:[a b c d e f]
fmt.Println(s)
// 创建一个与切片 s 长度相同的新切片 c,并将 s 的元素复制到 c 中。
c := make([]string, len(s))
copy(c, s)
// 使用切片操作取出 s 中索引为 [2:5) 的子切片。
// 注意:切片操作不包含结束索引位置的元素,输出:[c d e]
fmt.Println(s[2:5])
// 使用切片操作取出 s 中索引为 [2:len(s)) 的子切片。
// 输出:[c d e f]
fmt.Println(s[2:])
// 直接声明一个字符串切片 good,并初始化为 {"g", "o", "o", "d"}。
good := []string{"g", "o", "o", "d"}
fmt.Println(good) // 输出:[g o o d]
}
- Go语言中的切片(Slice)是一个灵活的动态数组,它是对数组的一个连续片段的引用。切片的长度可以动态增长或缩小,是一种动态数据结构。切片不需要指定固定长度,可以根据需要自动调整。
- 切片是对数组的一个连续片段的引用,是动态长度的动态数组。
- 使用
make([]T, length, capacity)函数可以创建一个长度为length的切片,容量为capacity。容量表示切片可增长的上限,可以省略,省略时容量与长度相同。 - 可以通过
slice[low:high]来获取切片的子切片,取值范围为索引low到high-1。 - 使用
append函数可以在切片末尾添加新的元素,如果容量不足,切片会自动扩容。 - 使用
copy(dst, src)函数可以将一个切片的元素复制到另一个切片。
数组和切片区别:
- 长度固定 vs. 长度动态:
- 数组是一个长度固定的数据结构,在声明时需要指定其长度,并且不能动态增长或缩小。
- 切片是一个长度动态的数据结构,它是对数组的一个连续片段的引用,并且可以动态增长或缩小。
- 声明方式:
- 数组的声明方式为
[长度]类型,如[5]int表示长度为 5 的整数数组。 - 切片的声明方式为
[]类型,如[]int表示一个整数切片。
- 数组的声明方式为
- 初始化:
- 数组的初始化可以使用数组字面值,通过在声明时直接赋值来初始化数组的元素。
- 切片的初始化通常是通过
make函数创建,也可以使用切片字面值进行初始化。
- 传递方式:
- 数组作为函数参数传递时是按值传递的,即在函数内部会复制整个数组。
- 切片作为函数参数传递时是按引用传递的,即在函数内部操作的是原始切片的数据。
- 长度属性:
- 数组具有一个
len函数,用于获取数组的长度。 - 切片也有
len函数,用于获取切片的长度,切片的长度可以动态变化。
- 数组具有一个
- 容量属性:
- 数组没有容量的概念,其长度就是其容量。
- 切片有一个
cap函数,用于获取切片的容量,容量表示切片可增长的上限。
- 空值:
- 数组在声明时未初始化的元素会根据元素类型设置为零值。
- 切片在声明时未初始化会被设置为
nil,表示一个空切片。
- map
func main() {
// 使用 make 函数创建一个空的字符串到整数的映射(map)m。
m := make(map[string]int)
// 向 map m 中添加键值对,"one" 是键,1 是值。
m["one"] = 1
// 继续向 map m 中添加键值对,"two" 是键,2 是值。
m["two"] = 2
// 打印 map 的内容,输出:map[one:1 two:2]
fmt.Println(m)
// 使用 len 函数获取 map m 的元素个数,输出:2
fmt.Println(len(m))
// 使用 m["one"] 获取键为 "one" 的值,输出:1
fmt.Println(m["one"])
// 当获取不存在的键时,map 返回值类型的零值。
fmt.Println(m["unknow"]) // 输出:0
// 使用 m["unknow"] 获取键为 "unknow" 的值和是否存在的标志。
// 因为 "unknow" 不存在于 map m 中,所以返回值 r 是 int 类型的零值 0,ok 是 false。
r, ok := m["unknow"]
fmt.Println(r, ok) // 输出:0 false
// 使用 delete 函数删除 map m 中的键为 "one" 的键值对。
delete(m, "one")
// 使用 map 字面值的方式创建一个非空的字符串到整数的映射 m2 和 m3。
m2 := map[string]int{"one": 1, "two": 2}
var m3 = map[string]int{"one": 1, "two": 2}
// 打印 m2 和 m3 的内容,输出:map[one:1 two:2] map[one:1 two:2]
fmt.Println(m2, m3)
}
map是一种无序的键值对集合,也被称为哈希表或字典。map中的每个元素都由一个唯一的键(key)和相应的值(value)组成。其中,键必须是唯一且不可重复的,值可以重复。- 使用
len(map)可以获取map的元素个数。 - 通过
map[key]可以获取指定键对应的值,如果键不存在,会返回值类型的零值。 - 使用
delete(map, key)可以删除map中的指定键值对。
- range
func main() {
nums := []int{2, 3, 4}
sum := 0
// 使用 range 遍历整数切片 nums,i 是索引,num 是值。
// 遍历过程中将每个元素的值累加到 sum 中,并打印出值为 2 的元素的索引和值。
for i, num := range nums {
sum += num
if num == 2 {
fmt.Println("index:", i, "num:", num) // 输出:index: 0 num: 2
}
}
// 打印整数变量 sum 的值,输出:9
fmt.Println(sum)
m := map[string]string{"a": "A", "b": "B"}
// 使用 range 遍历映射 m,k 是键,v 是值。
// 遍历过程中打印每个键值对的键和值。
for k, v := range m {
fmt.Println(k, v) // 输出:b B; a A
}
// 使用 range 遍历映射 m,只遍历键。
// 遍历过程中打印每个键。
for k := range m {
fmt.Println("key", k) // 输出:key a; key b
}
}
range关键字用于遍历数组、切片、字符串、map等数据结构。- 在遍历数组和切片时,
range返回元素的索引和值。 - 在遍历字符串时,
range返回字符的索引和字符本身。 - 在遍历映射(map)时,
range返回键和值。 - 对于不需要索引或值的情况,可以使用下划线
_来忽略其中一个值。例如for _, v := range nums,表示只获取切片元素的值而不获取索引。 range遍历是一个安全的操作,它不会出现索引越界的情况。即使在遍历过程中修改了切片或映射,遍历会继续按照原来的长度和顺序进行。
- 函数
// 定义一个名为 add 的函数,接收两个整数参数 a 和 b,返回它们的和。
func add(a int, b int) int {
return a + b
}
// 省略类型的函数定义,当参数类型相同时,可以将参数的类型省略。
func add2(a, b int) int {
return a + b
}
// 定义一个名为 exists 的函数,接收一个字符串到字符串的映射 m 和键 k,返回键 k 对应的值和是否存在的标志。
func exists(m map[string]string, k string) (v string, ok bool) {
v, ok = m[k]
return v, ok
}
func main() {
// 调用 add 函数,并将返回值赋给 res。
res := add(1, 2)
// 打印 res,输出:3
fmt.Println(res)
// 调用 exists 函数,并将返回值赋给 v 和 ok。
v, ok := exists(map[string]string{"a": "A"}, "a")
// 打印 v 和 ok,输出:A true
fmt.Println(v, ok)
}
- 在Go语言中,函数通过
func关键字来定义。 - 函数名后面跟着参数列表,参数列表中的每个参数都包含一个参数名和类型,多个参数之间使用逗号分隔。
- 函数可以有一个或多个返回值,使用
return关键字来返回结果。如果函数有多个返回值,可以使用括号将它们括起来。 - 调用函数时,通过传入参数获取结果。函数的返回值可以被忽略,也可以通过赋值给变量来使用。
- 可以使用
:=来定义和初始化函数的返回值,Go语言会根据返回值的类型自动推断。 - 函数的参数和返回值可以是任意数据类型,包括基本类型、复合类型(切片、映射、结构体等)以及自定义类型。
- 指针
// 定义一个名为 add2 的函数,接收一个整数参数 n,但在函数内部对 n 进行修改并不会影响外部的 n。
func add2(n int) {
n += 2
}
// 定义一个名为 add2ptr 的函数,接收一个整数指针参数 n,通过指针对原始变量 n 进行修改。
func add2ptr(n *int) {
*n += 2
}
func main() {
n := 5
// 调用 add2 函数,并传入 n 作为参数。
// 但因为传递的是 n 的值(拷贝),函数内部的修改不会影响外部的 n。
add2(n)
// 打印 n 的值,输出:5
fmt.Println(n)
// 调用 add2ptr 函数,并传入 n 的地址(指针)作为参数。
// 由于传递的是指针,函数内部对原始变量 n 进行了修改。
add2ptr(&n)
// 打印 n 的值,输出:7
fmt.Println(n)
}
- Go语言中的指针是一种特殊的数据类型,用于存储其他变量的内存地址。
- 通过指针,我们可以直接访问变量所在内存的值,而不是拷贝变量的值,从而可以在函数内部对变量进行修改,并影响到外部的变量。
- 在函数调用时,参数传递可以是值传递或指针传递。值传递会将原始值的拷贝传递给函数,对函数内部的修改不会影响外部的变量。指针传递会将变量的内存地址传递给函数,对函数内部的修改会影响外部的变量。
- 使用
*运算符可以获取指针指向的值,使用&运算符可以获取变量的内存地址。 - 在 Go 语言中,没有像 C++ 中的引用概念。Go 语言中的变量赋值和函数参数传递都是按值传递的,而不像 C++ 中的引用可以实现按引用传递。在 Go 语言中,如果想要在函数内部修改外部变量的值,需要使用指针来实现,通过传递变量的指针,可以在函数内部对原始变量进行修改,从而达到类似引用的效果。
- 结构体和结构体方法
// 定义一个名为 user 的结构体,包含两个字段 name 和 password。
type user struct {
name string
password string
}
// 定义一个函数 checkPassword,用于检查结构体变量 u 的密码是否匹配。
func checkPassword(u user, password string) bool {
return u.password == password
}
// 定义一个函数 checkPassword2,用于通过结构体指针检查结构体变量 u 的密码是否匹配。
func checkPassword2(u *user, password string) bool {
return u.password == password
}
// 定义一个名为 checkPassword 的结构体方法,接收一个名为 password 的字符串参数。
// 这个方法的接收者是一个值类型的 user(非指针),因此该方法在调用时接收的是 user 的副本。
// 方法用于检查接收者 user 的密码是否匹配传入的 password,并返回一个布尔值。
func (u user) checkPassword(password string) bool {
return u.password == password
}
// 定义一个名为 resetPassword 的结构体方法,接收一个名为 password 的字符串参数。
// 这个方法的接收者是一个指针类型的 *user,因此该方法在调用时接收的是 user 的指针。
// 方法用于重置接收者 user 的密码为传入的 password。
func (u *user) resetPassword(password string) {
u.password = password
}
func main() {
// 使用结构体字面值的方式创建结构体变量 a,字段值可以通过字段名初始化。
a := user{name: "wang", password: "1024"}
// 使用结构体字面值的方式创建结构体变量 b,可以直接赋值字段值,字段的顺序要和结构体定义一致。
b := user{"wang", "1024"}
// 使用结构体字面值的方式创建结构体变量 c,并只初始化其中一个字段值,其他字段值将使用默认零值(空字符串)。
c := user{name: "wang"}
c.password = "1024" // 对 c 结构体的字段进行单独赋值。
// 声明结构体变量 d,并逐个字段赋值。
var d user
d.name = "wang"
d.password = "1024"
// 打印结构体变量 a、b、c、d 的值,输出:{wang 1024} {wang 1024} {wang 1024} {wang 1024}
fmt.Println(a, b, c, d)
// 调用函数 checkPassword 检查结构体变量 a 的密码,输出:false
fmt.Println(checkPassword(a, "haha"))
// 调用函数 checkPassword2 通过结构体指针检查结构体变量 a 的密码,输出:false
fmt.Println(checkPassword2(&a, "haha"))
// 调用结构体方法 resetPassword 来重置结构体变量 a 的密码。
// 因为 resetPassword 方法的接收者是 *user(指针类型),所以在调用时使用 &a 获取指向结构体 a 的指针。
a.resetPassword("2048")
// 调用结构体方法 checkPassword 来检查结构体变量 a 的密码是否匹配传入的密码 "2048"。
// 因为 checkPassword 方法的接收者是 user(值类型),所以在调用时使用结构体变量 a 的副本。
// 输出:true
fmt.Println(a.checkPassword("2048"))
}
- Go 语言中的结构体是一种复合数据类型,用于将多个不同类型的数据组合在一起。
- 可以通过结构体字面值的方式创建结构体变量,可以按字段名初始化,也可以按字段顺序赋值。
- 结构体的字段可以通过点号(
.)访问,使用结构体变量.字段名的形式来获取或修改字段的值。 - Go 语言中的结构体方法是指与特定类型关联的函数。结构体方法允许为结构体类型定义属于自己的行为,这些方法可以在结构体变量上调用。方法的定义与函数类似,但在其前面加上了一个接收者,这个接收者指定了方法作用的目标类型。
- error
package main
import (
"errors"
"fmt"
)
type user struct {
name string
password string
}
// 定义一个名为 findUser 的函数,接收一个 user 切片和一个字符串 name 作为参数。
// 函数会在 user 切片中查找与 name 匹配的用户,如果找到,则返回该用户的指针和 nil,表示无错误;
// 如果未找到,则返回 nil 和一个新的错误,表示找不到用户。
func findUser(users []user, name string) (v *user, err error) {
for _, u := range users {
if u.name == name {
return &u, nil
}
}
return nil, errors.New("not found")
}
func main() {
// 调用 findUser 函数来查找名为 "wang" 的用户。
// 因为 user 切片中包含名为 "wang" 的用户,所以函数会返回该用户的指针和 nil,表示无错误。
u, err := findUser([]user{{"wang", "1024"}}, "wang")
if err != nil {
// 如果发生错误,打印错误信息并返回。
fmt.Println(err)
return
}
// 打印找到的用户的名字,输出:wang
fmt.Println(u.name)
// 使用 if 语句中的短变量声明来调用 findUser 函数,并查找名为 "li" 的用户。
// 因为 user 切片中不包含名为 "li" 的用户,所以函数会返回 nil 和一个新的错误,表示找不到用户。
// 在 if 语句中使用短变量声明声明了新的变量 u 和 err,只在 if 语句块中生效。
if u, err := findUser([]user{{"wang", "1024"}}, "li"); err != nil {
// 如果发生错误,打印错误信息并返回。
fmt.Println(err) // 输出:not found
return
} else {
// 打印找到的用户的名字,但在此代码块中的 u 和 err 变量是 if 语句块中的局部变量,不影响外部的 u 和 err。
fmt.Println(u.name)
}
}
- 在 Go 语言中,
error是一个内置的接口类型,用于表示可能发生的错误。 - 当函数遇到错误情况时,可以通过返回
error类型来表示错误,并在调用函数的地方进行错误处理。 - 可以使用
errors.New("error message")创建一个新的错误,表示发生了指定的错误情况。 - 在某些情况下,可以在 if 语句中使用短变量声明来检查函数返回的
error,并执行相应的错误处理逻辑。这样声明的变量只在 if 语句块内部有效,不会影响外部的同名变量。
- string方法
import (
"fmt"
"strings"
)
func main() {
a := "hello"
fmt.Println(strings.Contains(a, "ll")) // true
fmt.Println(strings.Count(a, "l")) // 2
fmt.Println(strings.HasPrefix(a, "he")) // true
fmt.Println(strings.HasSuffix(a, "llo")) // true
fmt.Println(strings.Index(a, "ll")) // 2
fmt.Println(strings.Join([]string{"he", "llo"}, "-")) // he-llo
fmt.Println(strings.Repeat(a, 2)) // hellohello
fmt.Println(strings.Replace(a, "e", "E", -1)) // hEllo
fmt.Println(strings.Split("a-b-c", "-")) // [a b c]
fmt.Println(strings.ToLower(a)) // hello
fmt.Println(strings.ToUpper(a)) // HELLO
fmt.Println(len(a)) // 5
b := "你好"
fmt.Println(len(b)) // 6 (中文字符使用 UTF-8 编码,一个中文字符占用 3 个字节)
}
- Go 语言的字符串类型是不可变的,这意味着一旦创建了字符串,就不能修改它的内容。Go 提供了一组字符串方法,这些方法用于对字符串进行操作和处理。
strings.Contains(str, substr string) bool:检查字符串str中是否包含子字符串substr,返回一个布尔值表示是否包含。strings.Count(str, substr string) int:计算字符串str中子字符串substr出现的次数,并返回一个整数。strings.HasPrefix(str, prefix string) bool:检查字符串str是否以prefix开头,返回一个布尔值。strings.HasSuffix(str, suffix string) bool:检查字符串str是否以suffix结尾,返回一个布尔值。strings.Index(str, substr string) int:返回字符串str中子字符串substr的第一个匹配的索引,如果没有找到,返回-1。strings.Join(str []string, sep string) string:使用分隔符sep将字符串切片str中的所有元素连接起来,并返回一个新的字符串。strings.Repeat(str string, count int) string:将字符串str重复count次,并返回一个新的重复后的字符串。strings.Replace(str, old, new string, n int) string:将字符串str中的old子字符串替换为new子字符串,替换n次(如果n小于0,则替换所有匹配项),并返回一个新的字符串。strings.Split(str, sep string) []string:使用分隔符sep将字符串str分割成多个子字符串,并返回一个包含子字符串的切片。strings.ToLower(str string) string:将字符串str中的所有字符转换为小写,并返回一个新的字符串。strings.ToUpper(str string) string:将字符串str中的所有字符转换为大写,并返回一个新的字符串。len(str string) int:返回字符串str的字节长度。
- fmt
import "fmt"
type point struct {
x, y int
}
func main() {
s := "hello"
n := 123
p := point{1, 2}
// 使用 fmt 包的 Println 函数
// 打印字符串 s 和整数 n,输出:hello 123
fmt.Println(s, n)
// 打印结构体 p,输出:{1 2}
fmt.Println(p)
// 使用 fmt 包的 Printf 函数
// 使用 %v 格式化输出,输出:s=hello
fmt.Printf("s=%v\n", s)
// 使用 %v 格式化输出,输出:n=123
fmt.Printf("n=%v\n", n)
// 使用 %v 格式化输出,输出:p={1 2}
fmt.Printf("p=%v\n", p)
// 使用 %+v 格式化输出,输出:p={x:1 y:2}
fmt.Printf("p=%+v\n", p)
// 使用 %#v 格式化输出,输出:p=main.point{x:1, y:2}
fmt.Printf("p=%#v\n", p)
f := 3.141592653
// 使用 fmt 包的 Println 函数,打印浮点数 f,输出:3.141592653
fmt.Println(f)
// 使用 fmt 包的 Printf 函数,使用 %.2f 格式化输出,输出:3.14
fmt.Printf("%.2f\n", f)
}
- Go 语言中的
fmt包提供了格式化输入和输出的功能,用于将数据格式化输出到标准输出或字符串。 fmt.Printf和fmt.Println是fmt包中最常用的函数,Printf用于格式化输出,Println用于打印一行数据并换行。- 在格式化字符串中,可以使用
%v来表示将变量的值格式化输出。%+v格式化输出会将结构体字段名一并打印出来,%#v则会打印变量的 Go 语法表示。 %.2f表示格式化浮点数时,保留小数点后两位。fmt包还提供了其他的格式化输出方式,例如%d表示整数,%s表示字符串,%t表示布尔值等。根据不同的需求,可以选择合适的格式化方式来输出数据。
- json
import (
"encoding/json"
"fmt"
)
// 定义一个名为 userInfo 的结构体,包含三个字段 Name、Age 和 Hobby。
// 结构体字段的标签 `json:"age"` 表示在 JSON 编码和解码时,使用 "age" 作为字段名。
type userInfo struct {
Name string
Age int `json:"age"`
Hobby []string
}
func main() {
a := userInfo{Name: "wang", Age: 18, Hobby: []string{"Golang", "TypeScript"}}
// 使用 json.Marshal 函数将 userInfo 结构体编码为 JSON 格式的字节数组。
buf, err := json.Marshal(a)
if err != nil {
panic(err)
}
fmt.Println(buf) // [123 34 78 97 ... ]
fmt.Println(string(buf)) // {"Name":"wang","age":18,"Hobby":["Golang","TypeScript"]}
// 使用 json.MarshalIndent 函数将 userInfo 结构体编码为格式化后的 JSON 字符串。
buf, err = json.MarshalIndent(a, "", "\t")
if err != nil {
panic(err)
}
fmt.Println(string(buf))
// 输出为:
// {
// "Name": "wang",
// "age": 18,
// "Hobby": [
// "Golang",
// "TypeScript"
// ]
// }
var b userInfo
// 使用 json.Unmarshal 函数将 JSON 格式的字节数组解码为 userInfo 结构体。
// 注意:需要传递 userInfo 结构体的指针作为参数,以便函数能够修改结构体的值。
err = json.Unmarshal(buf, &b)
if err != nil {
panic(err)
}
fmt.Printf("%#v\n", b) // main.userInfo{Name:"wang", Age:18, Hobby:[]string{"Golang", "TypeScript"}}
}
- Go 语言中的
encoding/json包提供了 JSON 数据与 Go 语言数据结构之间的编码和解码功能。 json.Marshal函数用于将 Go 语言数据结构编码为 JSON 格式的字节数组,json.MarshalIndent函数用于编码为格式化后的 JSON 字符串。json.Unmarshal函数用于将 JSON 格式的字节数组解码为指定的 Go 语言数据结构,并需要传递结构体的指针作为参数,以便函数能够修改结构体的值。- 可以通过结构体字段的标签
json:"field"来指定在编码和解码时使用不同的字段名,这样可以灵活地处理字段名与 JSON 数据的对应关系。
- time
import (
"fmt"
"time"
)
func main() {
// 获取当前时间:
now := time.Now()
fmt.Println(now) // 2022-03-27 18:04:59.433297 +0800 CST m=+0.000087933
// 使用 time.Date 函数创建指定时间 t 和 t2。
t := time.Date(2022, 3, 27, 1, 25, 36, 0, time.UTC)
t2 := time.Date(2022, 3, 27, 2, 30, 36, 0, time.UTC)
fmt.Println(t) // 2022-03-27 01:25:36 +0000 UTC
// 使用 time 对象的 Year、Month、Day、Hour、Minute 方法获取年、月、日、时、分等时间组成部分。
fmt.Println(t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute()) // 2022 March 27 1 25
// 使用 time 对象的 Format 方法按指定的格式进行时间格式化。
fmt.Println(t.Format("2006-01-02 15:04:05")) // 2022-03-27 01:25:36
// 使用 time 对象的 Sub 方法计算两个时间之间的时间间隔 diff。
diff := t2.Sub(t)
fmt.Println(diff) // 1h5m0s
// 使用 time.Duration 类型的方法获取时间间隔的分钟数和秒数。
fmt.Println(diff.Minutes(), diff.Seconds()) // 65 3900
// 使用 time.Parse 函数将字符串时间 "2022-03-27 01:25:36" 解析为 time 对象 t3。
t3, err := time.Parse("2006-01-02 15:04:05", "2022-03-27 01:25:36")
if err != nil {
panic(err)
}
fmt.Println(t3 == t) // true
now := time.Now()
// 使用 Unix 方法获取当前时间的 Unix 时间戳。
fmt.Println(now.Unix()) // 1648738080
}
- Go 语言的
time包提供了处理时间和日期的功能。 - 可以使用
time.Now()获取当前时间。 - 可以使用
time.Date()创建指定的时间。 - 可以使用
time.Format()方法按照指定的格式对时间进行格式化输出。 - 可以使用
time.Sub()方法计算两个时间之间的时间间隔。 - 可以使用
time.Parse()方法将字符串时间解析为time.Time对象。 - 可以使用
time.Unix()方法获取时间的 Unix 时间戳。
- strconv
import (
"fmt"
"strconv"
)
func main() {
// 使用 strconv.ParseFloat 函数将字符串 "1.234" 转换为 float64 类型的浮点数。
f, _ := strconv.ParseFloat("1.234", 64)
fmt.Println(f) // 1.234
// 使用 strconv.ParseInt 函数将字符串 "111" 转换为 int64 类型的整数。
n, _ := strconv.ParseInt("111", 10, 64)
fmt.Println(n) // 111
// 使用 strconv.ParseInt 函数将字符串 "0x1000" 转换为 int64 类型的整数。
// 参数 base 为 0,表示根据字符串前缀判断数字的进制(0x 表示十六进制)。
n, _ = strconv.ParseInt("0x1000", 0, 64)
fmt.Println(n) // 4096
// 使用 strconv.Atoi 函数将字符串 "123" 转换为 int 类型的整数。
n2, _ := strconv.Atoi("123")
fmt.Println(n2) // 123
// 使用 strconv.Atoi 函数将字符串 "AAA" 转换为 int 类型的整数。
// 如果转换失败,Atoi 函数会返回 0 和一个错误信息。
n2, err := strconv.Atoi("AAA")
fmt.Println(n2, err) // 0 strconv.Atoi: parsing "AAA": invalid syntax
}
- Go 语言的
strconv包提供了字符串和基本数据类型之间的相互转换功能。 - 可以使用
strconv.ParseFloat()将字符串转换为浮点数。 - 可以使用
strconv.ParseInt()将字符串转换为整数,base参数指定进制。 - 可以使用
strconv.Atoi()将字符串转换为整数,如果转换失败,会返回 0 和一个错误信息。
- os
import (
"fmt"
"os"
"os/exec"
)
func main() {
// 使用 os.Args 可以获取命令行参数,第一个参数是程序的名称。
// go run example/20-env/main.go a b c d
fmt.Println(os.Args) // [C:\Users\Administrator\AppData\Local\Temp\go-build1619588333\b001\exe\main.exe a b c d]
// 使用 os.Getenv 函数可以获取指定的环境变量的值。
fmt.Println(os.Getenv("PATH")) // D:\go\bin;C:\Program Files (x86)\Common Files\MVS\Runtime\Win32_i86;C:\Program Files (x86)\Common Files\MVS\Runtime\Win64_x64;C:\Program Files (x86)\Common Files\Oracle\Java\javapath;D:\HuarayTech\MV Viewer\Runtime\x64\;D:\HuarayTech\MV Viewer\Runtime\Win32\;D:\VMware\bin\;D:\NVIDIA\bin;D:\NVIDIA\libnvvp;C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;C:\Windows\System32\OpenSSH\;C:\Program Files\NVIDIA Corporation\Nsight Compute 2022.2.0\;D:\Qt5.12\5.12.9\msvc2017_64\bin;C:\Program Files (x86)\NVIDIA Corporation\PhysX\Common;C:\Program Files\NVIDIA Corporation\NVIDIA NvDLISR;D:\mingw64\bin;D:\Git\cmd;D:\mingw64\bin;D:\Python36\;D:\Python36\Scripts\;D:\jdk1.8.0_181;D:\android_SDK\sdk\platform-tools;D:\android_SDK\sdk\tools;D:\MATLAB2016a\runtime\win64;D:\MATLAB2016a\bin;D:\MATLAB2016a\polyspace\bin;D:\go\bin;C:\Users\Administrator\AppData\Local\Microsoft\WindowsApps;D:\Microsoft VS Code\bin;D:\opencv\build\x64\vc15\bin;D:\cmake-3.24.0-rc4-windows-x86_64\bin;D:\mingw64\bin;D:\Python36\;D:\Python36\Scripts\;
// 使用 os.Setenv 函数可以设置环境变量的值。
fmt.Println(os.Setenv("AA", "BB")) // 设置环境变量 AA 的值为 BB。
// Linux
// 使用 exec.Command 函数可以创建一个新的 cmd 对象,用于执行外部命令。
cmd := exec.Command("grep", "127.0.0.1", "/etc/hosts")
// 使用 CombinedOutput 方法可以执行外部命令并返回输出的结果和错误信息。
buf, err := cmd.CombinedOutput()
if err != nil {
panic(err)
}
fmt.Println(string(buf)) // 输出 grep 命令在 /etc/hosts 文件中查找到的结果。
// Windows
// 在 Windows 下,使用 exec.Command 创建 cmd 对象,并执行 `dir` 命令来列出当前目录中的文件和文件夹。`/C` 参数告诉 cmd 执行完命令后关闭命令行窗口。而使用 `cmd /K` 可以执行一个命令并保持命令行窗口打开。
cmd := exec.Command("cmd", "/C", "dir")
// 使用 cmd.CombinedOutput 方法执行命令并获取输出结果和错误信息。
output, err := cmd.CombinedOutput()
if err != nil {
fmt.Println("Error:", err)
return
}
// 输出命令执行结果。
fmt.Println(string(output))
}
- Go 语言的
os包提供了访问操作系统功能的接口。 - 可以使用
os.Args获取命令行参数。 - 可以使用
os.Getenv获取指定的环境变量的值,使用os.Setenv设置环境变量的值。 - 可以使用
exec.Command创建一个新的 cmd 对象,用于执行外部命令,然后使用CombinedOutput方法执行外部命令并返回输出结果和错误信息。
- 猜字游戏
package main
import (
"bufio" // 提供了用于带缓冲的读写操作的功能。它可以在读取和写入数据时提供更高效的性能,特别是对于大量的小数据块,因为它会对输入和输出进行缓冲,减少了系统调用的次数,从而提高了 I/O 操作的效率。
"fmt"
"math/rand" // 提供了伪随机数生成器(pseudo-random number generator),可以生成不同类型的随机数,包括整数、浮点数和字节序列。
"os"
"strconv"
"strings"
"time"
)
func main() {
// 设置最大随机数和随机数种子
maxNum := 100
// 这里使用 `rand.Seed(time.Now().UnixNano())` 来设置随机数生成器的种子,确保每次运行程序都会生成不同的随机数。
rand.Seed(time.Now().UnixNano())
secretNumber := rand.Intn(maxNum)
fmt.Println("Please input your guess") // 提示用户输入猜测的数字
reader := bufio.NewReader(os.Stdin) // 创建标准输入的 reader 对象
for {
input, err := reader.ReadString('\n') // 读取用户输入的字符串
if err != nil {
fmt.Println("An error occurred while reading input. Please try again", err)
continue
}
input = strings.Trim(input, "\r\n") // 去除输入字符串中的换行符
guess, err := strconv.Atoi(input) // 将输入的字符串转换为整数
if err != nil {
fmt.Println("Invalid input. Please enter an integer value")
continue
}
fmt.Println("Your guess is", guess) // 打印用户猜测的数字
// 根据用户猜测和秘密数字之间的大小关系,给出相应的提示
if guess > secretNumber {
fmt.Println("Your guess is bigger than the secret number. Please try again")
} else if guess < secretNumber {
fmt.Println("Your guess is smaller than the secret number. Please try again")
} else {
fmt.Println("Correct, you Legend!")
break // 猜对了,退出游戏循环
}
}
}
rand:
- 生成随机整数: 可以使用
rand.Int()、rand.Intn(n)和rand.Int31()等函数生成随机整数。rand.Int()会返回一个随机的大整数,rand.Intn(n)会返回一个介于 0 和 n-1 之间的随机整数,rand.Int31()会返回一个 31 位的非负随机整数。 - 生成随机浮点数: 可以使用
rand.Float32()和rand.Float64()函数生成随机浮点数。rand.Float32()返回一个介于 0.0 和 1.0 之间的随机 32 位浮点数,rand.Float64()返回一个介于 0.0 和 1.0 之间的随机 64 位浮点数。 - 生成随机字节序列: 可以使用
rand.Read()函数生成随机的字节序列。rand.Read()接受一个字节切片作为参数,并将随机的字节序列填充到该切片中。 - 设置随机数种子: 可以使用
rand.Seed()函数设置随机数种子。如果不手动设置种子,rand包会默认使用时间种子(time.Now().UnixNano())作为种子,以确保每次运行程序都会生成不同的随机数序列。
bufio
- 缓冲读取(Buffered Reading):
bufio可以将os.Stdin(标准输入)或者其他io.Reader(例如文件)包装成带缓冲的读取器,从而优化读取数据的性能。它提供了bufio.NewReader()函数用于创建带缓冲的读取器,可以使用ReadString()、ReadBytes()、ReadLine()等方法从输入源中读取数据,并将数据存储在内部缓冲区中,减少了实际的 I/O 操作次数。 - 缓冲写入(Buffered Writing):
bufio可以将os.Stdout(标准输出)或者其他io.Writer(例如文件)包装成带缓冲的写入器,从而优化写入数据的性能。它提供了bufio.NewWriter()函数用于创建带缓冲的写入器,可以使用Write()、WriteString()等方法将数据写入输出源,并将数据暂存在内部缓冲区中,减少了实际的 I/O 操作次数。 - 行扫描(Line Scanning):
bufio提供了Scanner类型,可以通过bufio.NewScanner()创建行扫描器。行扫描器可以方便地从输入源中逐行读取数据,并且支持自定义的分隔符,如换行符。 - 其他功能:
bufio还提供了其他一些功能,如Peek()方法可以在读取数据的同时预览缓冲区中的下一部分数据,Buffered()方法可以获取缓冲区中的未读取数据长度等。