golang基础知识汇总
golang基础知识汇总
基础之打印hello world
package main
import "fmt"
func main() {
fmt.Println("Hello World")
}
程序打印hello world是怎么做到的需要哪些东西,和我们学习的c语言有什么基础
每个程序都是由包开始的,从main包开始,在打印hello world这个程序中,我们还使用了另外一个包fmt,使用import的方式导入,fmt在基础使用就是打印,go这里面的打印其实又和java中的差不多,ln换行,其中fmt还有很多作用,可以慢慢尝试不同的方法打印,会有什么不同。
导入不同的包使用
import ("fmt"
"math"
)
导出名
go中一个字母开头以大写的方式开头,就代表他是已经导出的,比如我们在使用fmt的时候,fmt.Println时候P是大写的代表他是导出的,如果我们使用fmt.println放到编译器里面看看会有什么报错

image-20231210133552708
undefined没有被定义
函数
go里面的函数用法也很简单
package main
import "fmt"
func add(x int, y int) int {
return x + y
}
func main() {
fmt.Println(add(42, 13))
}
func开头 创建了一个add函数用法就是返回一x+y的值,其中add(x int, y int) int代表x和y是int类型的,后面的int代表返回的值是int类型的。
下面就是用main函数调用add函数实现一个和的功能
简化版
package main
import "fmt"
func add(x,y int) int { //有多个变类型相同时可以省略,只写一个
return x + y
}
func main() {
fmt.Println(add(42, 13))
}
变量
go中声明一个变量的方法,var
语句用于声明一个变量列表,跟函数的参数列表一样,类型在最后。
exp: var i,m,b int
同时变量声明也可以包含初始值,每个变量对应一个初始值,如果初始化值已存在,可以省略写类型,变量直接从初始值中获得类型
在函数中声明变量可以用更简单的
package main
import "fmt"
func main() {
var i, j int = 1, 2
k := 3
c, python, java := true, false, "no!"
fmt.Println(i, j, k, c, python, java)
}
例如这里的k:=3就是一个短变量声明,这种方法只能在函数内部使用,放到函数外部就必须要使用var,func关键字开始。
给变量声明时没有明确的初始化值时,变量会被赋予零值
- 数值类型的零值时0
- 布尔类型的零值是bool
- 字符串的零值是""(空字符串)

image-20231210142553617
变量的类型转换
package main
import (
"fmt"
"math"
)
func main() {
var x, y int = 3, 4
var f float64 = math.Sqrt(float64(x*x + y*y))
fmt.Println(f)
}
这个例子里面就是在main函数里面定义了两个int类型的,在后面又把他们转换成float64类型的函数内部还可以使用更简单的方式
i := 42
f := float64(i)
u := uint(f)
常量
常量的声明与变量类似,只不过是使用 const
关键字。
常量可以是字符、字符串、布尔值或数值。
常量不能用 :=
语法声明。
package main
import "fmt"
const Pi = 3.14
func main() {
const World = "世界"
fmt.Println("Hello", World)
fmt.Println("Happy", Pi, "Day")
const Truth = true
fmt.Println("Go rules?", Truth)
}
数值常量
数值常量是高精度的 值。
一个未指定类型的常量由上下文来决定其类型。
for与if的使用
go里面for的用法还是和c中有着不小的差距
package main
import "fmt"
func main() {
for i := 0; i < 10; i++ {
sum := i * 2
fmt.Println(sum)
}
}
一个简单的例子,在go中我们不需要使用(),但是{}是必须的
基本的 for
循环由三部分组成,它们用分号隔开:
- 初始化语句:在第一次迭代前执行
- 条件表达式:在每次迭代前求值
- 后置语句:在每次迭代的结尾执行
初始化语句通常为一句短变量声明,该变量声明仅在 for
语句的作用域中可见。
c里面的while就相当于go里面的while,直接看例子说明
package main
import "fmt"
func main() {
sum := 1
for sum < 100 {
sum += sum
fmt.Println(sum)
}
}
if的使用
if的结构和for类似也是不需要,括号但是花括号是必须的,此外if在执行条件表达式前还可以先执行一个简单的语句
exp
package main
import (
"fmt"
"math"
)
func pow(x, n, lim float64) float64 {
if v := math.Pow(x, n); v < lim {
return v
}
return lim
}
func main() {
fmt.Println(
pow(3, 2, 10),
pow(3, 3, 20),
)
}
此外if else语句,用法和c里面的相似,在整个if else语句里面定义的东西,在语句外面不可使用。
练习:循环与函数
为了练习函数与循环,我们来实现一个平方根函数:用牛顿法实现平方根函数。
计算机通常使用循环来计算 x 的平方根。从某个猜测的值 z 开始,我们可以根据 z² 与 x 的近似度来调整 z,产生一个更好的猜测:
z -= (z*z - x) / (2*z)
重复调整的过程,猜测的结果会越来越精确,得到的答案也会尽可能接近实际的平方根。
在提供的 func Sqrt
中实现它。无论输入是什么,对 z 的一个恰当的猜测为 1。 要开始,请重复计算 10 次并随之打印每次的 z 值。观察对于不同的值 x(1、2、3 ...), 你得到的答案是如何逼近结果的,猜测提升的速度有多快。
提示:用类型转换或浮点数语法来声明并初始化一个浮点数值:
z := 1.0
z := float64(1)
然后,修改循环条件,使得当值停止改变(或改变非常小)的时候退出循环。观察迭代次数大于还是小于 10。 尝试改变 z 的初始猜测,如 x 或 x/2。你的函数结果与标准库中的 math.Sqrt 接近吗?
(注: 如果你对该算法的细节感兴趣,上面的 z² − x 是 z² 到它所要到达的值(即 x)的距离, 除以的 2z 为 z² 的导数,我们通过 z² 的变化速度来改变 z 的调整量。 这种通用方法叫做牛顿法。 它对很多函数,特别是平方根而言非常有效。)
exp
package main
import (
"fmt"
"math"
)
func Sqrt(x float64) float64 {
z := 1.0
for i := 0; i < 10; i++ {
z -= (z*z - x) / (2 * z)
fmt.Printf("Iteration %v: z = %v\n", i+1, z)
}
return z
} //这里实现了一个求平方根的函数,来和math包里面的求平方根函数对比
func main() {
values := []float64{1, 2}
for _, x := range values {
result := Sqrt(x)
fmt.Printf("Square root of %v (math.Sqrt): %v\n", x, math.Sqrt(x))
fmt.Printf("Square root of %v (custom Sqrt): %v\n\n", x, result)
}
}
switch
switch
是编写一连串 if - else
语句的简便方法。它运行第一个值等于条件表达式的 case 语句。
Go 的 switch 语句类似于 C、C++、Java、JavaScript 和 PHP 中的,不过 Go 只运行选定的 case,而非之后所有的 case。 实际上,Go 自动提供了在这些语言中每个 case 后面所需的 break
语句。 除非以 fallthrough
语句结束,否则分支会自动终止。 Go 的另一点重要的不同在于 switch 的 case 无需为常量,且取值不必为整数。
举个简单例子说明(只运行选定的 case,而非之后所有的 case
exp
package main
import (
"fmt"
"runtime"
)
func main() {
fmt.Print("Go runs on ")
switch os := runtime.GOOS; os {
case "darwin":
fmt.Println("OS X.")
case "linux":
fmt.Println("Linux.")
default:
// freebsd, openbsd,
// plan9, windows...
fmt.Printf("%s.\n", os)
}
}
他只会输出你本机的系统
switch 的 case 语句从上到下顺次执行,直到匹配成功时停止。
如果switch没有条件的话,他就相当于switch=true,条件为真才跳出
defer用法
defer 语句会将函数推迟到外层函数返回之后执行。
推迟调用的函数其参数会立即求值,但直到外层函数返回前该函数都不会被调用。
exp
package main
import "fmt"
func main() {
defer fmt.Println("world")
fmt.Println("hello")
}
打印结果
//hello
//world
先打印了hello,在打印world
指针
go的指针保存了值的内存地址
指针用法
&操作符会生成一个指向其操作数的指针
i:=1
p=&i
*操作符表示指针指向的底层值
i:=1
p=&i
fmt.Println(*p) // 通过指针 p 读取 i
*p = 21 // 通过指针 p 设置 i
go没有指针运算,不同与c
package main
import "fmt"
func main() {
i, j := 42, 2701
p := &i // 指向 i
fmt.Println(*p) // 通过指针读取 i 的值
*p = 21 // 通过指针设置 i 的值
fmt.Println(i) // 查看 i 的值
p = &j // 指向 j
*p = *p / 37 // 通过指针对 j 进行除法运算
fmt.Println(j) // 查看 j 的值
}
结构体
package main
import "fmt"
type Vertex struct {
X int
Y int
}
func main() {
fmt.Println(Vertex{1, 2})
}
学过c就能理解的
结构体字段可以通过点号访问
exp
package main
import "fmt"
type Vertes struct {
X int
Y int
}
func main() {
s := Vertes{1, 2}
s.Y = 3
fmt.Println(s.Y)
}
结结构体指针
package main
import "fmt"
type Vertex struct {
X int
Y int
}
func main() {
v := Vertex{1, 2}
p := &v
p.X = 1e9
fmt.Println(v)
}
了解到上一节的指针便可以很快的搞明白这个,只不过把指针指向了结构体,通过指针改变变量的值
结构体文法
在Go语言中,结构体的文法指的是如何定义一个结构体类型。结构体文法的基本语法如下:
type StructName struct {
Field1 type1
Field2 type2
// ...
}
其中:
StructName
是结构体的名称。Field1
、Field2
等是结构体的字段名。type1
、type2
等是字段的数据类型。
例如,下面是一个表示矩形的结构体的定义:
type Rectangle struct {
Width float64
Height float64
}
这个结构体包含两个字段,分别是 Width
和 Height
,它们的数据类型都是 float64
。通过这个结构体,你可以创建表示具体矩形的实例。
结构体文法也支持在创建结构体实例时,通过字段名指定字段的值,例如:
// 创建一个 Rectangle 实例
rect := Rectangle{
Width: 10.0,
Height: 5.0,
}
这种方式可以提高代码的可读性,因为不依赖于字段的顺序。结构体文法是Go语言中用于定义和创建结构体类型的基本语法。
数组
类型 [n]T
表示拥有 n
个 T
类型的值的数组。
表达式
var a [10]int
会将变量 a
声明为拥有 10 个整数的数组。
数组的长度是其类型的一部分,因此数组不能改变大小。
比数组更灵活的切片
切片
数组的的大小是固定的,但是切片回味==会为数组提供动态大小,灵活的
切片的使用方法
用类型[]T表示一个元素类型为T的切片,切片通过下标来表示上下界限,例如
primes := [6]int{2, 3, 5, 7, 11, 13}
var s []int = primes[1:4]
打印s就会输出{3 5 7}
切片另说
package main
import "fmt"
func main() {
names := [4]string{
"John",
"Paul",
"George",
"Ringo",
}
fmt.Println(names)
a := names[0:2]
b := names[1:3]
fmt.Println(a, b)
b[0] = "XXX"
fmt.Println(a, b)
fmt.Println(names)
}

image-20231211143927659
能看懂为什么打印的是这个就能明白切片
切片不会存储任何数据,他只是描述了一段底层数组中的一段,如果更改了切片的元素,那么它对应的底层数组的元素也会修改,那么下面再引用,切片,它对应的内容都会修改。
此外切片可以利用他的默认行为来忽略上下界
a[0:10]
a[:10]
a[0:]
a[:] --等价的
切片文法
切片文法类似于没有长度的数组文法。
这是一个数组文法:
[3]bool{true, true, false}
下面这样则会创建一个和上面相同的数组,然后构建一个引用了它的切片:
[]bool{true, true, false}
exp
package main
import "fmt"
func main() {
q := []int{2, 3, 5, 7, 11, 13}
fmt.Println(q)
r := []bool{true, false, true, true, false, true}
fmt.Println(r)
s := []struct {
i int
b bool
}{
{2, true},
{3, false},
{5, true},
{7, true},
{11, false},
{13, true},
}
fmt.Println(s)
}
这个代码中通过切片文法演示了如何初始化不同类型的切片。在切片文法中,通过使用花括号 {}
将元素列表括起来,可以轻松地创建并初始化切片。
切片的长度与容量
切片拥有 长度 和 容量。
切片的长度就是它所包含的元素个数。
切片的容量是从它的第一个元素开始数,到其底层数组元素末尾的个数。
切片 s
的长度和容量可通过表达式 len(s)
和 cap(s)
来获取。
package main
import "fmt"
func main() {
s := []int{2, 3, 5, 7, 11, 13}
printSlice(s)
// 截取切片使其长度为 0
s = s[:0]
printSlice(s)
// 拓展其长度
s = s[:4]
printSlice(s)
// 舍弃前两个值
s = s[2:]
printSlice(s)
}
func printSlice(s []int) {
fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
}
//打印内容
len=6 cap=6 [2 3 5 7 11 13]
len=0 cap=6 []
len=4 cap=6 [2 3 5 7]
len=2 cap=4 [5 7]
用 make 创建切片
切片可以用内建函数 make
来创建,这也是创建动态数组的方式。
make
函数会分配一个元素为零值的数组并返回一个引用了它的切片:
a := make([]int, 5) // len(a)=5
要指定它的容量,需向 make
传入第三个参数:
b := make([]int, 0, 5) // len(b)=0, cap(b)=5
b = b[:cap(b)] // len(b)=5, cap(b)=5
b = b[1:] // len(b)=4, cap(b)=4
package main
import "fmt"
func main() {
a := make([]int, 5)
printSlice("a", a)
b := make([]int, 0, 5)
printSlice("b", b)
c := b[:2]
printSlice("c", c)
d := c[2:5]
printSlice("d", d)
}
func printSlice(s string, x []int) {
fmt.Printf("%s len=%d cap=%d %v\n",
s, len(x), cap(x), x)
}
向切片追加元素
为切片追加新的元素是种常用的操作,为此 Go 提供了内建的 append
函数。内建函数的文档对此函数有详细的介绍。
func append(s []T, vs ...T) []T
append
的第一个参数 s
是一个元素类型为 T
的切片,其余类型为 T
的值将会追加到该切片的末尾。
append
的结果是一个包含原切片所有元素加上新添加元素的切片。
当 s
的底层数组太小,不足以容纳所有给定的值时,它就会分配一个更大的数组。返回的切片会指向这个新分配的数组。
package main
import "fmt"
func main() {
var s []int
printSlice(s)
// 添加一个空切片
s = append(s, 0)
printSlice(s)
// 这个切片会按需增长
s = append(s, 1)
printSlice(s)
// 可以一次性添加多个元素
s = append(s, 2, 3, 4)
printSlice(s)
}
func printSlice(s []int) {
fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
}
代码中就可以很好的表现出这一点,初始的切片长度和容量都是0,但是我们使用append函数向里面追加元素的时候,append就会自动新建一个比原始切片两倍的切片
range
go里面的range和python里面的range大差不差,感觉用法都一样,当使用 for
循环遍历切片时,每次迭代都会返回两个值。第一个值为当前元素的下标,第二个值为该下标所对应元素的一份副本。
贴个代码自行感受一下
package main
import "fmt"
var pow = []int{1, 2, 4, 8, 16, 32, 64, 128}
func main() {
for i, v := range pow {
fmt.Printf("2**%d = %d\n", i, v)
}
}
range续
可以将下标或值赋予 _
来忽略它。
for i, _ := range pow
for _, value := range pow
若你只需要索引,忽略第二个变量即可。
for i := range pow
映射
map
package main
import "fmt"
type Vertex struct {
Lat, Long float64
}
var m map[string]Vertex
func main() {
m = make(map[string]Vertex)
m["Bell Labs"] = Vertex{
40.68433, -74.39967,
}
fmt.Println(m["Bell Labs"])
}
研读这一段代码
定义了一个结构体float64类型的
然后
var m map[string]Vertex
这一行代码创建了一个映射 m,其中的键是字符串,对应的值是 Vertex 类型的对象。
m = make(map[string]Vertex)
m["Bell Labs"] = Vertex{
40.68433, -74.39967,
}
这一段代码就是利用到了映射,string类型键值必须是string类型的,对应的Vertex的值是float64的
下面一个代码段更好的理解
// 创建一个空的映射
m := make(map[string]Vertex)
// 添加一个键值对
m["someKey"] = Vertex{1.0, 2.0}
// 访问键对应的值
v := m["someKey"]
fmt.Println(v) // 输出 {1.0, 2.0}
修改映射
在映射中插入或修改元素
m[key] = elem
获取元素:
elem = m[key]
删除元素:
delete(m, key)
通过双赋值检测某个键是否存在:
elem, ok = m[key]
若 key
在 m
中,ok
为 true
;否则,ok
为 false
。
若 key
不在映射中,那么 elem
是该映射元素类型的零值。
同样的,当从映射中读取某个不存在的键时,结果是映射的元素类型的零值。
注 :若 elem
或 ok
还未声明,你可以使用短变量声明:
修改exp
package main
import "fmt"
type Vertex struct {
X, Y float64
}
func main() {
// 创建一个空的映射
m := make(map[string]Vertex)
// 添加键值对
m["someKey"] = Vertex{1.0, 2.0}
// 输出原始值
fmt.Println("Original:", m)
// 修改映射中的值
m["someKey"] = Vertex{3.0, 4.0}
// 输出修改后的值
fmt.Println("Modified:", m)
}
删除
exp
package main
import "fmt"
type Vertex struct {
X, Y float64
}
func main() {
// 创建一个映射并添加键值对
m := make(map[string]Vertex)
m["someKey"] = Vertex{1.0, 2.0}
// 输出原始映射
fmt.Println("Original:", m)
// 删除键为 "someKey" 的键值对
delete(m, "someKey")
// 输出删除后的映射
fmt.Println("After deletion:", m)
}
用的delete函数
练习映射
题目
实现 WordCount
。它应当返回一个映射,其中包含字符串 s
中每个“单词”的个数。函数 wc.Test
会对此函数执行一系列测试用例,并输出成功还是失败。
package main
import (
"golang.org/x/tour/wc"
"strings"
)
func WordCount(s string) map[string]int {
// 使用 strings.Fields 函数将输入字符串分割成单词数组
words := strings.Fields(s)
// 创建一个映射来存储单词出现的次数
wordCount := make(map[string]int)
// 遍历单词数组,更新映射中的计数
for _, word := range words {
wordCount[word]++
}
return wordCount
}
func main() {
// 使用测试函数来验证 WordCount 函数的正确性
wc.Test(WordCount)
}
这边定义了一个函数WordCount,是一个map类型的
下面的strings.Fields(s)是go里面的一个函数
`strings.Fields(s)` 是 Go 语言标准库中 `strings` 包提供的一个函数,用于将字符串 `s` 按照空格分割成一个字符串切片(slice)
就是读取这个包"golang.org/x/tour/wc"里面给出的测试单词,并将他分割
函数值
函数也是值,他也可以像其他值一样传递,函数值可以用作函数的参数或返回值。
exp
package main
import (
"fmt"
"math"
)
func compute(fn func(float64, float64) float64) float64 {
return fn(3, 4)
}
func main() {
hypot := func(x, y float64) float64 {
return math.Sqrt(x*x + y*y)
}
fmt.Println(hypot(5, 12))
fmt.Println(compute(hypot))
fmt.Println(compute(math.Pow))
}
具体分析
func compute(fn func(float64, float64) float64) float64 {
return fn(3, 4)
}
第一眼看上去很迷糊,其实逐层分析就会很简单
这个compute函数接受一个fn参数,而这个参数的类型是一个函数,这个函数接受两个float64类型的数,并返回一个float64的数
func main() {
hypot := func(x, y float64) float64 {
return math.Sqrt(x*x + y*y)
}
这哥main函数里面创建了一个匿名函数,hypot := func(x, y float64) float64
两个float64类型的参数
fmt.Println(hypot(5, 12))
fmt.Println(compute(hypot))
fmt.Println(compute(math.Pow))
第一个调用了这个匿名函数给x y赋值,
第二个将hypot传递给了compute函数,所以计算结果是5,
第三个将math.Pow传递给了compute计算平方
函数的闭包
Go 函数可以是一个闭包。闭包是一个函数值,它引用了其函数体之外的变量。该函数可以访问并赋予其引用的变量的值,换句话说,该函数被这些变量“绑定”在一起。
例如,函数 adder
返回一个闭包。每个闭包都被绑定在其各自的 sum
变量上。
还是通过例子说明
package main
import "fmt"
func adder() func(int) int {
sum := 0
return func(x int) int {
sum += x
return sum
}
}
func main() {
pos, neg := adder(), adder()
for i := 0; i < 10; i++ {
fmt.Println(
pos(i),
neg(-2*i),
)
}
}
逐个分析
func adder() func(int) int {
sum := 0
return func(x int) int {
sum += x
return sum
}
}
创建了一个闭包函数,在这里这个闭包函数返回一个函数,函数内部引用了一个sum变量
它初始化为 0。这个变量
sum
被内部的匿名函数引用,并且由于闭包的特性,这个变量的生命周期会持续到返回的函数不再被引用。返回的匿名函数可以访问
adder
函数作用域内的变量sum
。每次这个函数被调用时,它将传入的参数x
加到sum
上,并返回新的sum
值。
func main() {
pos, neg := adder(), adder()
for i := 0; i < 10; i++ {
fmt.Println(
pos(i),
neg(-2*i),
main函数里面又创建了两个adder闭包,他们是相互独立的,各自引用里面的sum变量,互不干扰。
练习:斐波纳契闭包
实现一个 fibonacci
函数,它返回一个函数(闭包),该闭包返回一个斐波纳契数列 (0, 1, 1, 2, 3, 5, ...)
。
使用闭包函数实现还是比较简单的
package main
import "fmt"
// 返回一个“返回int的函数”
func fibonacci() func() int {
a, b := 0, 1
return func() int {
result := a
a, b = b, a+b
return result
}
}
func main() {
f := fibonacci()
for i := 0; i < 10; i++ {
fmt.Println(f())
}
}
简单的数学问题加闭包函数的基础使用
方法
Go 没有类。不过你可以为结构体类型定义方法。
方法就是一类带特殊的 接收者 参数的函数。
方法接收者在它自己的参数列表内,位于 func
关键字和方法名之间。
看例子说明其用法
package main
import (
"fmt"
"math"
)
type Vertex struct {
X, Y float64
}
func (v Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
func main() {
v := Vertex{3, 4}
fmt.Println(v.Abs())
}
在这里结构体的东西就不多赘述了,关键点就在
func (v Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
这里定义了一个Abs方法,该方法计算并返回 Vertex
结构体实例 v
的欧几里德范数(Euclidean norm)或称为长度
下面main函数就是引用了
方法即函数
记住:方法只是个带接收者参数的函数。
现在这个 Abs
的写法就是个正常的函数,功能并没有什么变化。
package main
import (
"fmt"
"math"
)
type Vertex struct {
X, Y float64
}
func Abs(v Vertex) float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
func main() {
v := Vertex{3, 4}
fmt.Println(Abs(v))
}