学习目标:
Go语言核心编程 第一章“基础知识”
学习内容:
Go语言核心编程 第一章“基础知识”
第一章 基础知识
1.1 语言简介
1.1.1 诞生背景
- 当前编程语言对并发支持不是很好
- 程序规模大,编译速度慢
- 当前编程语言设计复杂
1.1.2 Go语言特性
| Go语言特性项 | 特性值 |
|---|---|
| 关键字和保留字 | 25个 |
| 控制结构 | 支持顺序循环分支 |
| 动静特性 | 静态语言,支持运行时动态类型 |
| 强弱特性 | 强类型 |
| 隐式类型推导 | 支持 |
| 类型安全 | 类型安全 |
| 自定义数据类型 | 支持type自定义 |
| 面向对象支持 | 类型组合支持面向对象 |
| 多态 | 通过接口支持 |
| 接口 | Duck模型 |
| 函数支持 | 有 |
| 反射支持 | 有 |
| 泛型支持 | 无 |
| 编译模式 | 编译成可执行程序 |
| 运行模式 | 直接运行 |
| 内存管理 | 支持自动垃圾回收 |
| 并发编程 | 协程(Go原生支持) |
| 交叉编译 | 支持 |
| 跨平台 | 支持 |
| 框架 | 丰富,发展很快 |
| 标准库和第三方库 | 丰富,发展很快 |
| 语法兼容性 | 向前兼容性好 |
| 影响力 | 社区活跃,Google背书 |
| 应用领域 | 云计算基础设施软件、中间件、区块链 |
1.2 初始Go程序
package main
import "fmt"
func main() {
fmt.Println("Hello world")
}
编译运行命令:go build .\main.go
1.3 词法单元
1.3.1 token
token是源程序不可再分割的单元。可以分为标识符,操作符,分隔符和字面常量等
sun:=a+b
包含5个token:sum := a + b
1.3.2 标识符
总体分为两种:
1.设计者预留的标识符,包括预留的标识符以及后续语言扩展的保留字
2.用户自行定义的标识符,主要是用户在编程中自行定义的变量名,常量名,函数名等

1.3.3 操作运算符和分隔符
操作符具备语法含义同时起到分割token的作用,而仅起到分割作用的包括:空格,制表符,回车和换行。
47个操作符
算数运算符(5个):+ - * / %
位运算符(6个):& | ^ << >> &^
括号(6个):{ } [ ] ( )
逻辑运算符(3个):&& || !
自增自减运算符(2个):++ –
比较运算符(6个):> >= <= < == !=
赋值和赋值复核运算符(13个)
:= = += -= *= /= %= &= |= ^= >>= <<= &^=
其他运算符(6个):: , ; . … <-
注:
&^运算符
假如有两个变量 var1 &^ var2 作&^运算
如果var2变量的位为0 则取var1变量对应的位值作为结果位
如果var2变量的位为1 则结果位取0
1.3.4 字面常量
源程序中表示固定值的符号叫做字面常量,简称字面量。字面量是由字母,数字等构成的字符串或者数值,它只能作为右值出现,所谓右值是指等号右边的值。
字面量与常量的区别在于,常量是赋过值后不能再改变的变量。
const b int = 10 b为常量,10为字面量
字面量出现在两个地方
1.常量和变量的初始化
2.表达式里或者作为调用函数的实参
Go不支持用户自定义字面量,Go中的字面量包含以下5种:
1.整型字面量
42
0600
...
2.浮点型字面量
0.
72.4
...
3.复数型字面量
0i
0.i
...
4.字符型字面量
'a'
'\t'
...
5.字符串字面量
"\n"
"abc"
...
1.4 常量和变量
1.4.1 变量
使用一个名称来绑定一块内存地址,该内存地址中存放的数据类型由定义变量时指定的类型决定,该内存地址中存放的内容可以改变
1.4.1.1 变量声明方式
显式声明
var varName dataType [ = value]
var a int = 5
如果不指定初始值,则默认将该变量初始化为类型的零值
短类型声明
varName := value
:=只能出现在函数内(包括方法内)
Go编译器会自行进行数据类型推断
支持多类型变量同时声明并赋值
a,b:="hello",1
1.4.1.2 变量属性
变量名
变量值
类型信息
可见性和可见域:Go提供自动内存管理,通常无需关注生存期和存放位置。编译器用栈逃逸技术能够自动为变量分配空间,可能在栈上也可能在堆上。
变量存储和生存期:使用统一的命名空间对变量进行管理,每个变量都有唯一的名字,包名是这个名字的前缀
1.4.2 常量定义
常量定义:使用一个名称来绑定一块内存地址,该内存地址中存放的数据类型由定义变量时指定的类型决定,该内存地址中存放的内容不可以改变
1.4.2.1 Go语言常量
- 布尔型
- 字符串型
- 数值型
const flag bool = true
const str string = "hello"
const value int64 = 5
1.4.2.2 Go语言预声明标识符iota
标识符:是用来表示变量、函数、类、对象等程序实体的名称,由字母、数字和下划线组成
预定义标识符:预先定义的标识符,具有特殊功能含义,表示常用功能、库函数或预定义常量等。
关键字是编程语言中具有特殊含义的保留字,不能用作标识符
-------------------iota用在常量声明中,初始化为0---------------
package main
import "fmt"
const (
c0 = iota
c1 = iota
c2 = iota
)
func main() {
fmt.Println(c0) //0
fmt.Println(c1) //1
fmt.Println(c2) //2
}
--------------------------可以进行简化-------------------------
package main
import "fmt"
const (
c0 = iota
c1
c2
)
func main() {
fmt.Println(c0) //0
fmt.Println(c1) //1
fmt.Println(c2) //2
}
-------------------分开的const每次从0开始-------------------------
package main
import "fmt"
const c0 = iota
const c1 = itoa
func main() {
fmt.Println(c0) //0
fmt.Println(c1) //0
}
-------------------iota逐行递增-------------------------
package main
import "fmt"
const (
c0 = iota
c1
c2 =iota
)
func main() {
fmt.Println(c0) //0
fmt.Println(c1) //1
fmt.Println(c2) //2
}
1.5 基本数据类型
七类基本数据类型(20个具体类型)
1.布尔类型:
bool 默认为false,无法和整型进行相互转化
2.整型:
byte,int,int8,int32,int64,uint,uint8,uint16,uint32,uint64,uintptr
不同类型整型必须强制类型转化
3.浮点型:
float32,float64
字面量默认被识别为float64,var b:=10.00,浮点数间比较应该用math库
import (
"fmt"
"math"
)
func main() {
a := 0.1 + 0.2
b := 0.3
mistake := 1e-9 // 定义一个误差范围
if math.Abs(a-b) < mistake {
fmt.Println("a 和 b 相等")
} else {
fmt.Println("a 和 b 不相等")
}
}
4.复数:
complex64,complex128
value:= 3.1+6i,complex64由2个float32组成,complex128由2个float64组成。
复数函数:
var v = complex(2.1,3)//构造复数 [注:默认complex128]
a:=real(v)//返回实部
a:=image(v)//返回虚部
5.字符:
rune
GO中两种字符类型
一种是byte的字节类型,byte是unit的别名,1字节
另一种是Unicode编码的字符rune,rune是GO中int32的别称,4字节
[注:name:='a’默认为int32]
6.错误类型:
error
error类型是Golang内置类型之一,其本质上只是一个接口
package main
import "fmt"
type DivideError struct {
dividea int
divideb int
}
func (e *DivideError) Error() string {
return fmt.Sprintf("cannot divide %d by %d", e.dividea, e.divideb)
}
func divide(a, b int) (int, error) {
if b == 0 {
return 0, &DivideError{dividea: a, divideb: b}
}
return a / b, nil
}
func main() {
result, err := divide(10, 0)
if err != nil {
if e, ok := err.(*DivideError); ok {
fmt.Println("Divide error:", e)
return
}
fmt.Println(err)
return
}
fmt.Println(result)
}
断言的格式
if concreteValue, ok := interfaceValue.(ConcreteType); ok {
// 处理具体类型的逻辑
} else {
// 处理非具体类型的逻辑
}
7.字符串类型:
string
Golang中的字符串是不可变的,不能通过索引下标的方式修改字符串中的数据
func main() {
var s string = "abc"
s[0] = 99
}
报错信息:
# command-line-arguments
.\main.go:5:7: cannot assign to s[0] (strings are immutable)
源码分析
StringHeader is the runtime representation of a string.
Go语言内部定义的字符串结构体类型,开发者不能直接访问和使用。
type StringHeader struct {
Data uintptr
Len int
}
Go语言标准库中的类型,StringHeader是一个公开可见的类型
type stringStruct struct {
str unsafa.Pointer
len int
}
Golang字符串赋值是数组指针和长度字段的拷贝。
多个string的值指向同一片内存区域时,内部数组指针地址相同,string地址不同
str1 := "hello,world"
strptr1 := (*reflect.StringHeader)(unsafe.Pointer(&str1))
fmt.Println("strptr1 ptr:", unsafe.Pointer(strptr1.Data))
fmt.Println("strptr1 len", strptr1.Len)
str2 := "hello,world"
strptr2 := (*reflect.StringHeader)(unsafe.Pointer(&str2))
fmt.Println("strptr2 ptr:", unsafe.Pointer(strptr2.Data))
fmt.Println("strptr2 len", strptr2.Len)
fmt.Println("&st1", &str1)
fmt.Println("&st2", &str2)
strptr1 ptr: 0xcd639d
strptr1 len 11
strptr2 ptr: 0xcd639d
strptr2 len 11
&st1 0xc000088220
&st2 0xc000088230
------------------------------------------------------------
改变string的值时,内部数组指针地址和长度发生变化,string地址不变
str1 := "hello,world"
strptr1 := (*reflect.StringHeader)(unsafe.Pointer(&str1))
fmt.Println("before strptr1 ptr:", unsafe.Pointer(strptr1.Data))
fmt.Println("before strptr1 len", strptr1.Len)
fmt.Println("before &st1", &str1)
str1 = "hello"
strptr2 := (*reflect.StringHeader)(unsafe.Pointer(&str1))
fmt.Println("after strptr1 ptr:", unsafe.Pointer(strptr2.Data))
fmt.Println("after strptr1 len", strptr2.Len)
fmt.Println("after &st1", &str1)
before strptr1 ptr: 0x9763af
before strptr1 len 11
before &st1 0xc000088220
after strptr1 ptr: 0x9754fe
after strptr1 len 5
after &st1 0xc000088220
string和[]byte的互相转换
var str = "ab"
bytes := []byte(str)//要慎用,尤其数据量大时,每次转化都需要复制内容
bytes[1] = 'c'
strcov := string(bytes)
fmt.Println(str)
fmt.Println(strcov)
ab
ac
1.5.1 Go语言和Java语言数据类型对比
GO JAVA
int8 byte
int16 short
int32 int
int64 long
float32 float
float64 double
Go中byte=uint,rune=int32
1.6 复合数据类型
1.6.1 指针
Go语言支持指针,指针是一种用于存储变量内存地址的特殊类型。指针允许你间接访问变量的值和修改变量的内容。指针类型的声明为*T,同时Go支持多级指针**T。通过变量名之前加&获取变量的地址。
1.6.1.1 指针的操作
1.创建指针:你可以使用&运算符来获取一个变量的地址
var x int = 42
var ptr *int = &x
2.获取指针的值
fmt.Println(*ptr) // 输出:42
修改指针指向的值
*ptr = 100
fmt.Println(x) // 输出:100
3.传递指针给函数
func main() {
var value int = 150
modifyValue(&value)
fmt.Println(value) // 输出:200
}
func modifyValue(ptr *int) {
*ptr = 200
}
4.返回指针,Go编译器使用"栈逃逸"将这种局部变量分配在堆上
func createPointer() *int {
value := 42
return &value
}
ptr := createPointer()
fmt.Println(*ptr) // 输出:42
1.6.1.2 指针的特点
1.在赋值语句中,*T出现在"=“左边表示指针声明,出现在”="右边表示取指针指向的值
var a = 11
p := &a
2.Go不支持指针运算
Go由于支持垃圾回收,如果支持指针运算,会给垃圾回收的实现带来诸多不便
a := 123
p := &a
p++ //不允许,报non-numeric type +int错误
3.结构体指针访问结构体字段仍然使用"."操作符
package main
import "fmt"
// 定义一个结构体
type Person struct {
FirstName string
LastName string
Age int
}
func main() {
// 创建一个结构体指针
p1 := &Person{
FirstName: "John",
LastName: "Doe",
Age: 30,
}
// 访问结构体字段,使用"."操作符
fmt.Println("First Name:", p1.FirstName)
fmt.Println("Last Name:", p1.LastName)
fmt.Println("Age:", p1.Age)
}
1.6.1.2 空指针
指针可以是空的,表示未指向任何有效的内存地址。在Go中,空指针的零值是nil。
var ptr *int // 这是一个空指针
if ptr == nil {
fmt.Println("ptr is nil")
}
1.6.2 数组
[n]elementType
数组一般在创建时通过字面量进行初始化,单独声明一个数组类型变量而不进行初始化无意义
1.6.2.1 数组特点
1.数组创建完成长度就固定了,无法新增加元素
2.数组长度是数组类型的组成部分,[10]int和[20]int表示不同类型
3.可以根据数组创建切片
4.数组是值类型,数组赋值或者作为函数参数都是值拷贝。
数组赋值:当你将一个数组分配给另一个数组时,不会共享相同的内存位置。相反,数组中的元素会被复制到新数组中,创建了两个独立的数组,它们在内存中有不同的位置。这意味着对一个数组所做的更改不会影响到另一个数组。
函数参数传递:当你将一个数组作为函数参数传递时,也会发生值拷贝。这意味着在函数内部对数组的修改不会影响到原始数组。
第4点
func main() {
array1 := [3]int{1, 2, 3}
array2 := array1 // 这不是引用,而是值拷贝
array2[0] = 100
fmt.Println(array1[0]) // 仍然是1,array1不受array2的影响
}
-------------------------------------------------------------
func ModifyArray(arr [3]int) {
arr[0] = 100
}
originalArray := [3]int{1, 2, 3}
ModifyArray(originalArray)
fmt.Println(originalArray[0]) // 输出为1,原始数组不受函数内部修改的影响
1.6.2.2 数组初始化
a := [3]int{1,2,3} //指定长度和初始化字面量
a := [...]int{1,2,3} //不指定长度,但是由后面的初始化列表数量来确定其长度
a := [3]int{1:1,2:3} //指定长度和利用索引下标进行初始化,未指定时按照默认值初始化
a := [...]int{1:1,2:3} //不指定长度,利用索引下标进行初始化,数组长度为最后一个索引值决定
1.6.2.3 数组相关操作
1.数组元素访问
a := [3]int{1,2,3}
b := a[0]
for k,v :=range a{
}
2.数组长度
a := [3]int{1,2,3}
length := len(a)
1.6.3 切片
1.6.3.1 切片和数组
Go语言数组的定长性和值拷贝限制了其使用场景,Go提供了另一种数据类型slice,是一种变长数组,其数据结构中有指向数组的指针,所以是一种引用类型。
type slice struct{
array unsafe.Pointer //指向底层数组
len int //切片元素的数量
cap int //底层数组的容量
}
1.6.3.2 切片的创建
1.由数组创建
var array = [...]int{0,1,2,3,4,5,6}
s1 := array[0:4]
fmt.Printf("%v\n",s1) //[0 1 2 3]
2.通过内置函数make创建切片,默认被初始化为切片元素类型的零值
a := make([]int, 10)//len10 cap10
b := make([]int, 10, 15)//len10 cap15
注意:直接声明切片类型的变量没有意义
var a []int
1.6.3.3 切片的操作
1.len()
2.cap()
3.append
4.copy()
slice := []int{1, 2, 3, 4, 5}
length := len(slice)
fmt.Println(length) // 输出:5
-------------------------------------
slice := []int{1, 2, 3, 4, 5}
capacity := cap(slice)
fmt.Println(capacity) // 输出:5
-------------------------------------
slice := []int{1, 2, 3}
slice = append(slice, 4, 5)
fmt.Println(slice) // 输出:[1 2 3 4 5]
-------------------------------------
//copy 不共享底层数数组
slice1 := []int{1, 2, 3}
slice2 := make([]int, len(slice1))
copy(slice2, slice1)
slice2[0] = 5
fmt.Println(slice2) // 输出:[5 2 3]
fmt.Println(slice1) // 输出:[1 2 3]
1.6.4 map
Go语言中map类型的格式为map[K]T,是一种引用类型
1.6.4.1 map的创建
// 使用make创建一个空的map
m := make(map[string]int) //map的容量使用默认值
m := make(map[string]int,len) //map的容量使用给定的len值
// 使用字面量初始化map
m := map[string]int{
"apple": 5,
"banana": 3,
}
1.6.4.2 map的操作
1.使用键向map添加元素,如果键已经存在,将会更新对应的值。
m["orange"] = 7
2.通过键获取map的值
value := m["banana"] // 获取键"banana"对应的值
3.通过delete函数删除map的值
delete(m, "apple") // 删除键"apple"对应的键值对
4.检查键是否存在
value, exists := m["pear"]
if exists {
fmt.Println("pear exists with value", value)
} else {
fmt.Println("pear does not exist")
}
5.使用for range遍历map
for key, value := range m {
fmt.Println(key, value)
}
6.map的零值:如果一个map没有初始化,它的零值是nil,即一个空的map
var m map[string]int
if m == nil {
fmt.Println("map is nil")
}
7.map是无序的:map中的键值对没有固定的顺序,每次迭代可能以不同的顺序访问键值对
8.map的值可以是任何类型,但是map的键必须是可以进行相等性比较的类型
9.map键值对的个数通过len()获得
fmt.Println(len(map))
注:Go内置的map不是并发安全的,并发安全的map可以使用标准包sync中的map
不要直接修改map value内某个元素的值,如果想修改map的某个键值必须整体赋值
// 不要直接修改 map 值内的元素
m := map[string][]int{
"a": {1, 2, 3},
"b": {4, 5, 6},
}
// 以下代码是不允许的,会导致编译错误
// m["a"][0] = 100
// 要修改 map 中的某个键值对,必须重新分配整个键值对
m["a"] = []int{100, 2, 3}
// 现在 map 中的键"a"对应的值被修改了
fmt.Println(m["a"]) // 输出:[100 2 3]
1.6.5 struct
1.6.5.1 struct特点
1.struct结构中的类型可以是任意类型
2.struct的存储空间是连续的,其字段按照声明时的顺序存放
1.6.5.2 struct形式
struct的两种形式:
1.struct类型的字面量
struct {
FeildName FeildType
FeildName FeildType
FeildName FeildType
}
2.自定义的struct类型
type TypeName struct {
FeildName FeildType
FeildName FeildType
FeildName FeildType
}
1.6.5.3 struct初始化
type Person struct{
Name string
Age int
}
type Student struct{
*Person
Number int
}
不推荐
a:=Person{"Tom",12}
推荐
p := &Person{
Name : "dada",
age : 15,
}
s := Student{
Person : p,
Number : 110,
}
1.6.6 接口
第四章介绍
1.6.7 通道
第五章介绍
1.7 控制结构
1.7.1 if语句
Go没有条件运算符 a>b?a:b
err,file := os.Open("xxx")
if err != nil {
return nil,err
}
defer file.Close()
//do something
1.7.2 switch语句
1.switch支持default语句,当所有case分支都不满足时,执行default语句,并且default语句可以放到任意位置,不影响switch的判断逻辑
2.通过fallthrough语句强制执行下个case子句,不用判断下一个case子句能否满足
3.switch后面可以跟一个可选的简单的初始化语句
第1点的演示
package main
import "fmt"
func main() {
switch i := "x"; i {
default:
fmt.Printf("default")
case "y", "Y":
fmt.Println("yes")
case "n", "N":
fmt.Println("no")
}
}
default
---------------------------------------------
第2点和第3点的演示
package main
import "fmt"
func main() {
switch i:="y"; i{
case "y","Y":
fmt.Println("yes")
fallthrough
case "n","N":
fmt.Println("no")
}
}
yes
no
1.7.3 for语句
第一种:死循环,类似while(1)
for{}
第二种:类似于while(condition)
for condition{}
第三种:正常for循环语句
for init;condition;post{}
第四种:对数组、切片、字符串、map和chanel的访问
访问数组
for index,value := range arry{}
访问切片
for index,value := range slice{}
访问通道
for value := range chanel{}
访问字符串
for index,value := range string
访问map
for key,value := range map{}
1.7.4 标签和跳转
1.7.4.1 标签
标签:用来标示一个语句的位置。
基本语法 labelName:
代码示例:
package main
func main() {
i := 1
start:
fmt.Print(i)
i++
if i <= 5 {
goto start
}
}
12345
1.7.4.2 goto语句
goto用于函数内部的跳转,需要配合标签一起使用
基本语法 goto Label
特点:
1.只能函数内部使用。
2.不能跳过内部变量声明语句。
3.只能跳到上级作用域或者同级作用域,不能跳到内部作用域。
1.只能函数内部使用。
正确用法:
func main() {
goto outside
outside:
fmt.Println("This is outside the function.")
}
-----------------------------------------------------
错误语法:不能跨函数使用
func toexample() {
goto outside
}
func example() {
outside:
fmt.Println("This is outside the function.")
}
2.不能跳过内部变量声明语句。
package main
import "fmt"
func main() {
i := 1
goto skipDeclaration // 错误:不能跳过变量声明语句
j := 2
skipDeclaration:
fmt.Printf("i: %d, j: %d\n", i, j)
}
3.只能跳到上级作用域或者同级作用域,不能跳到内部作用域。
package main
import "fmt"
func main() {
i := 1
inner:
j := 2
fmt.Printf("i: %d, j: %d\n", i, j)
return
outer:
i = 10
goto inner // 错误:不能跳到内部作用域,j := 2仅在inner中生效,因此报错
}
1.7.4.3 break语句
两种用法
1.单独使用
2.结合标签使用,跳出标识符所标示的for、switch和select语句的执行,同时要求标签必须和break在同一个函数内。
package main
import "fmt"
func main() {
outerLoop:
for i := 1; i <= 3; i++ {
fmt.Printf("Outer loop: %d\n", i)
for j := 1; j <= 3; j++ {
fmt.Printf("Print j: %d\n", j)
if j == 2 {
break outerLoop // 使用 break + 标签 中断外部循环
}
}
}
}
Outer loop: 1
Print j: 1
Print j: 2
1.7.4.4 continue语句
两种用法
1.单独使用
2.结合标签使用,跳出标识符所标示的fo语句的本次执行,要求标签必须和continue在同一个函数内。
package main
import "fmt"
func main() {
outerLoop:
for i := 1; i <= 3; i++ {
fmt.Printf("Outer loop: %d\n", i)
for j := 1; j <= 3; j++ {
fmt.Printf("Print j: %d\n", j)
if j == 2 {
continue outerLoop // 使用 break + 标签 中断外部循环
}
}
}
}
Outer loop: 1
Print j: 1
Print j: 2
Outer loop: 2
Print j: 1
Print j: 2
Outer loop: 3
Print j: 1
Print j: 2
1.7.4.5 return和函数调用
return语句和函数调用也能引起程序控制流的跳转
return 语句用于从函数中提前返回,并可以选择返回一个或多个值。当执行 return 语句时,函数的执行将立即终止,控制流将返回到函数的调用点,同时传递指定的返回值。
func add(a, b int) int {
result := a + b
return result
}
当一个函数在 Go 中被调用时,控制流会从调用点跳转到被调用函数内部执行。一旦被调用函数执行完毕,控制流将返回到调用函数的地方,继续执行调用函数的剩余部分。
func main() {
fmt.Println("Start of main")
someOtherFunction()
fmt.Println("End of main")
}
func someOtherFunction() {
fmt.Println("Inside someOtherFunction")
}
参考书籍:Go语言核心编程
该博客聚焦Go语言基础知识学习。介绍了Go语言诞生背景与特性,讲解词法单元、常量变量、基本与复合数据类型,如指针、数组、切片等,还阐述了控制结构,包括if、switch、for语句及标签跳转等内容。






