Golang快速上手01/Golang基础
-
最近有需求,需要使用go,这几天快速过一遍基础语法,这是今天的总结
-
项目结构
1.变量与常量
1.1变量声明
// 标准声明
var 变量名 类型 = 表达式
// 多变量声明
var (
name string = "Go"
version = 1.21 // 类型推断
)
// 短变量声明(仅函数内)
func main() {
a, b := 10, "text" // 同时声明多个变量
_ = 20 // 丢弃值
}
1.2常量声明
const PI = 3.1415 // 无类型常量
const (
STATUS_OK = 200
MAX_SIZE = 1024 << 3 // 允许表达式
)
1.3包与导入
package main // 可执行程序必须的包声明
// 单行导入
import "fmt"
// 多行导入(推荐)
import (
"time"
"log"
)
❗ 注意:导入的包必须被使用,否则编译报错
1.4函数特性
// 多返回值函数
func getData() (int, string) {
return 200, "success"
}
// 使用返回值
code, msg := getData()
_, status := getData() // 忽略第一个返回值
2.基本数据类型
类型 | 描述 | 范围示例 |
---|---|---|
int | 默认整数类型,系统相关 | 通常 32 或 64 位 |
int8 | 8 位有符号整数 | -128 至 127 |
int16 | 16 位有符号整数 | -32768 至 32767 |
int32 | 32 位有符号整数 | -2^31 至 2^31-1 |
int64 | 64 位有符号整数 | -2^63 至 2^63-1 |
uint | 无符号整数,系统相关 | 0 至 2^32-1 或 2^64-1 |
uint8 | 8 位无符号整数 | 0 至 255 |
uint16 | 16 位无符号整数 | 0 至 65535 |
uint32 | 32 位无符号整数 | 0 至 2^32-1 |
uint64 | 64 位无符号整数 | 0 至 2^64-1 |
byte | uint8 的别名, ASCII码 | 0 至 255 |
rune | int32 的别名,用于 Unicode 码点 | -2^31 至 2^31-1 |
-
复数类型
-
complex128:可以使用
a+bi
表示复数,如0.5+1.5i
var c complex128 = 0.5 + 1.5i
- 如果需要 complex64 类型,可以将 complex128 赋值给 complex64 变量,但可能会损失精度。
-
可以使用内置函数
func complex(r, i float64) complex128
构造复数类型。var c complex128 = complex(0.5, 1.5)
comple(r, i float64)
表明i和r都是float64
-
-
指针类型(uintptr/unsafe.Pointer)
任何类型不能强制转换为uintptr,uintptr可以与unsafe.Pointer,unsafe.Pointer可以转为任何数据类型(不安全)
a := "Hello World!" // string: "Hello World!" b := []byte(a) // []byte: [72 101 108 108 111 32 87 111 114 108 100 33] c := **(**int)(unsafe.Pointer(&a)) // int: 8022916924116329800
-
字符类型byte/rune
使用单引号,默认为rune类型,但如果再byte范围呢,也就是ASCII码范围内,可以相互转换两种类型,表达与
Clang
不同特性 byte (uint8) rune (int32) 底层类型 无符号8位整型 有符号32位整型 取值范围 0 ~ 255 -2^31 ~ 2^31-1 默认值 0 0 内存占用 1字节 4字节 主要用途 ASCII字符处理 Unicode字符处理 // 特殊表示法示例 a := '中' // Unicode字符直接表示 b := '\u4e2d' // Unicode码点表示(中文"中") c := '\377' // 八进制表示最大范围 d := '\x7F' // 十六进制表示ASCII字符 e := '\U0001F604' // 扩展Unicode表示(笑脸表情)
3.内置数据类型
特性 | 值类型 | 引用语义类型 | 指针类型 |
---|---|---|---|
内存分配位置 | 栈/数据段 | 堆 | 栈存储指针,堆存储数据 |
赋值操作 | 深度拷贝 | 浅拷贝(共享底层数据) | 地址拷贝 |
空值 | 类型零值 | nil | nil |
内存管理 | 自动回收 | GC管理 | GC管理 |
典型类型 | int, float, struct | slice, map, chan | *int, *struct |
函数参数传递 | 值副本传递 | 指针结构副本传递 | 地址副本传递 |
3.1 指针
-
code
package main var a int = 10 var ptr *int = &a func main() { ptr2 := &a println(*ptr2) // 不能使用:=初始化为空指针,因为编译器无法推断类型 var ptr1 *int = nil println(ptr1) // 结构体内方位属性中指针类型指向的指用. }
- 注意go中没有->
3.2结构体
-
code
package main import ( "fmt" ) type Book struct { name string author string subject *string } var a Book // 初始化默认结构体 var s1 string = "Go 语言" var subject1 *string = &s1 var b = Book{name: "Go", author: "www.runoob.com", subject: subject1} // 初始化结构体 var c = Book{author: "www.runoob.com", name: "Go"} // 局部初始化结构体 var e *Book = new(Book) // 使用指针 type Book1 struct { string int } var a1 Book1 = Book1{"Golang 入门经典", 10} var a2 = struct{ name string author string }{"Golang 入门经典", "Unknown"} type Cat struct { name string age int } type Dog struct { name string age int } func main() { d := Book{author: "www.runoob.com", name: "Go"} // := println(a.name) println(*b.subject) println(c.name) println(d.name) println(e.name) fmt.Println("Hello, playground") fmt.Println(a1) fmt.Println(a2) println(a1.string) var car1 Cat var car2 Dog fmt.Println(car1) fmt.Println(car2) // 如果两个结构体具有完全相同的定义,它们可以进行显式类型转换。 car1 = Cat(car2) fmt.Println(car1) }
-
结构体初始化方式
Go 提供多种初始化结构体的办法,代码片段中展示了多种示例:
-
零值初始化:
- 语法:var a Book
- 效果:所有字段被初始化为零值。根据字段类型,字符串为 “”,整数为 0,指针为 nil。
- 示例:var a Book 会让 a.name = “”,a.author = “”,a.subject = nil。
-
字段赋值初始化:
- 语法:var b = Book{name: “Go”, author: “www.runoob.com”, subject: subject1}
- 效果:指定字段名和值,未指定的字段为零值。代码中 subject1 是一个字符串指针,b.subject 被设置为该指针。
- 注意:字段顺序无关,Go 会根据字段名匹配。
-
部分初始化:
- 语法:var c = Book{author: “www.runoob.com”, name: “Go”}
- 效果:只指定部分字段,未指定的字段(如 subject)为零值,即 nil。
-
使用 new 函数:
- 语法:var e *Book = new(Book)
- 效果:new(Book) 分配内存,返回指向 Book 的指针,所有字段初始化为零值。访问字段用 e.name,Go 自动解引用。
- 与直接声明的区别:new 返回指针,适合需要传递指针的场景。
-
匿名结构体初始化:
- 语法:var a2 = struct{name string; author string}{“Golang 入门经典”, “Unknown”}
- 效果:定义并初始化一个匿名结构体,字段顺序初始化,适合临时使用的小型结构体。
-
-
匿名字段与访问
type Book1 struct { string int } var a1 Book1 = Book1{"Golang 入门经典", 10}
-
匿名字段:字段没有显式名称,直接使用类型名作为字段名。在 Book1 中,字段名为 string 和 int,可以直接访问 a1.string 和 a1.int。
-
限制:每个属性的类型必须唯一,否则会冲突。例如,不能有多个 string 字段。
-
-
类型转换与字段一致性
type Cat struct { name string age int } type Dog struct { name string age int } var car1 Cat var car2 Dog car1 = Cat(car2) // 这行无法编译
-
类型转换规则:在 Go 中,即使两个结构体的字段完全相同(如 Cat 和 Dog 都有 name string 和 age int),如果它们是独立定义的类型,则被视为不同类型,不能直接转换。
-
需要手动赋值或者建构造函数
car1.name = car2.name car1.age = car1.age car1 = Cat{name: car2.name, age: car2.age}
-
-
-
3.2.1嵌套与嵌入(类似继承)
-
嵌套结构体
-
定义:一个结构体包含另一个结构体作为字段。
-
示例:
type Address struct { street string city string } type Person struct { name string address Address }
-
访问:使用点号访问嵌套字段,例如 person.address.street。
-
用途:适合表示层次化数据,如地址信息嵌套在人员信息中。
-
-
嵌入结构体(类似继承)
-
定义:通过嵌入实现类似继承的效果,一个结构体包含另一个结构体的匿名字段。
-
示例:
type Animal struct { name string age int } type Cat struct { Animal color string }
-
访问:嵌入的字段可以直接访问,例如 cat.name 和 cat.age,无需通过 Animal 前缀。
-
方法继承:如果 Animal 有方法,Cat 也可以直接调用这些方法,类似于继承。
-
扩展:Cat 可以添加新字段(如 color)或覆盖方法(通过同名方法)。
-
3.3数组
-
code
package main import "fmt" // 定义数组 <定义变量关键字> <变量名> [数组长度] <数组元素类型> var a [3]int var b [3]int = [3]int{1, 2, 3} var c = [...]int{1, 2, 3} // [...]int是数组,不是切片 func main() { fmt.Println(a) fmt.Println(b) d := [3]int{1, 2, 3} fmt.Println(d) // 数组名不会转换为指向数组首元素的指针,类似于std::array,所以会传递数组的时候会产生很开销,建议使用指针 var a [3]int = [3]int{1, 2, 3} var p *[3]int = &a }
- 和结构体一样,如果直接初始化,就不要写类型声明
- 和cpp数组名的意义不同
3.4切片
3.4.1切片的定义
-
code
package main import "fmt" // 定义切片 var slice []int // 注意:[3]int / [...]int 都是数组不是切片 var a = []int{1, 2, 3, 4, 5} func main() { a := []int{1, 2, 3, 4, 5} fmt.Println(a) var b = make([]int, 5) // slice,map,channel,第一个参数为类型,第二个参数为长度,第三个参数为容量 fmt.Println(b) }
- 和数组类似,只是数组里不要写数字
3.4.2切片介绍
-
slice
是对数据的引用,而非拷贝 -
切片是一种数据类型也是一种操作
func main() { // 切片操作 var a = []int{1, 2, 3, 4, 5} fmt.Println(a[1:3]) // [2 3] fmt.Println(a[:3]) // [1 2 3] fmt.Println(a[1:]) // [2 3 4 5] fmt.Println(a[:]) // [1 2 3 4 5] }
-
append
和copy
-
都是对数据的拷贝,也叫深拷贝
var c = []int{1, 2} fmt.Println(c) // [1 2] var d = []int{3, 4, 5, 6} fmt.Println(d) // [3 4 5 6] var e []int = append(c, 3, 4, 5, 6) fmt.Println(e) // [1 2 3 4 5 6] var f []int = append(c, d...) // ...表示将切片d打散,解包 fmt.Println(f) // [1 2 3 4 5 6] var i int = copy(c, d) // 将切片d复制到切片c中,返回复制的元素个数,以长度短的为准 fmt.Println(i) // 2
-
-
原地删除操作
a = a[start:] // 删除前 start 个元素 a = a[:end] // 保留前 end 个元素 a = a[start:end] // 截取索引 [start, end) 元素 a = append(a[:start], a[end:]...) // 删除索引 [start, end) 元素
-
原地插入
a = append(a, b...) // 原地末尾追加 a = append(b, a...) // 原地首部插入 a = append(append(a[:st], b...), a[st:]...) // 原地中间插入 a := []int{1, 2, 3, 4, 5} b := []int{10, 20, 30} st := 2 // 要插入的位置是索引 2 位置 // a = [1, 2, 10, 20, 30, 3, 4, 5]
3.5字符串
3.5.1字符串初始化
var a string = "Hello World!" // 显式类型
var b = "Golang 入门经典" // 类型推断
c := "abcABC123" // 短声明
- 与数组、切片、结构体类似,如果确定类型,可以直接类型推断,就是第二中初始化方法
- 字符串可以认为是一个静态切片类型,可以进行相应的切片操作。不可使用
append
和copy
函数。还需注意多字节字符(如中文)可能导致切片不完整。
3.5.2字符串的属性与操作
-
字符串不可变,意味着创建后无法直接修改内容。内部使用字节数组存储,结构为 StringHeader(包含数据指针和长度),类似 Java 的字符串。
type StringHeader struct { Data uintptr Len int }
-
字符串拼接
有三种拼接方式:
-
使用
+
运算符:c := a + b
,简单但效率低,适合少量拼接。 -
使用
strings.Join
:c = strings.Join([]string{a, b}, "")
,适合多个字符串拼接。 -
使用
strings.Builder
:推荐高效方式,示例:package main import ( "fmt" "strings" ) func main() { var a string = "a" var b string = "b" var builder strings.Builder builder.WriteString(a) builder.WriteString(b) fmt.Println(builder.String()) }
-
-
字符串编码:把字符串转换为数组
c := []byte(a) fmt.Println(c) // [97] d := len(c) fmt.Println(d) // 1 e := []rune(a) fmt.Println(e) // [97]
3.5.3其它功能
-
字符串操作与转换功能
- 检查功能
以下是 strings 包中用于检查字符串的函数:
Function Description strings.Contains(s, substr) 返回 true 如果 s 包含 substr strings.HasPrefix(s, prefix) 返回 true 如果 s 以 prefix 开头 strings.HasSuffix(s, prefix) 返回 true 如果 s 以 prefix 结尾 strings.Index(s, substr) substr 在 s 中第一次出现的索引 strings.LastIndex(s, substr) substr 在 s 中最后次出现的索引 a := "hello, world" b := "hello" fmt.Println(strings.Contains(a, b)) // true fmt.Println(strings.Contains(a, "world")) // true fmt.Println(strings.HasPrefix(a, b)) // true fmt.Println(strings.HasSuffix(a, b)) // false fmt.Println(strings.Index(a, b)) // 0 fmt.Println(strings.LastIndex(a, b)) // 0
- 转换功能
以下是 strings 包中用于转换字符串大小写的函数:
Function Description strings.ToLower(s) 返回 s 的小写版本 strings.ToUpper(s) 返回 s 的大写版本 a := "hello, world" b := "hello" fmt.Println(strings.ToUpper(a)) // HELLO, WORLD fmt.Println(strings.ToLower(b)) // hello
- 分割与连接功能
以下是 strings 包中用于分割和连接字符串的函数:
Function Description strings.Split(s, sep) 根据 sep 将 s 分割为字符串切片 strings.Join(elem, sep) 使用 sep 连接 elem 中的元素 a := "hello, world" b := "hello" // split fmt.Println(strings.Split(a, ",")) // [hello world] // join fmt.Println(strings.Join([]string{"hello", "world"}, ",")) // hello,world
- 替换功能
以下是 strings 包中用于替换字符串的函数:
Function Description strings.Replace(s, old, new, n) 在 s 中替换前 n 次 old 为 new strings.ReplaceAll(s, old, new) 在 s 中替换所有 old 为 new a := "hello, world" b := "hello" // replace fmt.Println(strings.Replace(a, "world", "golang", 1)) // hello, golang // replace all fmt.Println(strings.ReplaceAll(b, "l", "x")) // hexxo
- 修剪功能
以下是 strings 包中用于修剪字符串的函数:
Function Description strings.TrimSpace(s) 去除 s 前后空白字符 strings.Trim(s, chars) 去除 s 前后 chars 中的字符 a := "hello, world" b := "hello" // trim fmt.Println(strings.Trim(" hello, world ", " ")) // hello, world // trim left fmt.Println(strings.TrimLeft(" hello, world ", " ")) // hello, world // trim right fmt.Println(strings.TrimRight(" hello, world ", " ")) // hello, world // TrimSpace fmt.Println(strings.TrimSpace(" hello, world ")) // hello, world // TrimPrefix fmt.Println(strings.TrimPrefix("hello, world", "hello")) // , world
-
字符串转换功能
以下是用于字符串转换的函数:
- 转为字符串
Function Description fmt.Sprint(a …any) 将任意类型转换为字符串 t := 1 // Sprint fmt.Sprint(t)
- 从字符串转换
Function Description strconv.ParseBool(s) 解析 s 为布尔值,返回 (bool, error) strconv.ParseComplex(s, bitSize) 解析 s 为复数,返回 (complex128, error) strconv.ParseFloat(s, bitSize) 解析 s 为浮点数,返回 (float64, error) strconv.ParseInt(s, base, bitSize) 解析 s 为整数,返回 (int64, error) strconv.ParseUint(s, base, bitSize) 解析 s 为无符号整数,返回 (uint64, error) a := "hello, world" b := "hello" // ParseBool fmt.Println(strconv.ParseBool("true")) // true <nil> // ParseInt fmt.Println(strconv.ParseInt("17", 10, 64)) // 17 <nil> // ParseFloat fmt.Println(strconv.ParseFloat("3.14", 64)) // 3.14 <nil> // ParseUint fmt.Println(strconv.ParseUint("17", 10, 64)) // 17 <nil> // ParseComplex fmt.Println(strconv.ParseComplex("1+2i", 64)) // (1+2i) <nil>
3.6channel
-
channel
用于多线程线程管道通信与同步package main import ( "fmt" ) func main() { // a := make(chan <type>, [<size>]) a := make(chan int, 2) // 不允许直接转换的定义方法 fmt.Println(a) a <- 1 a <- 2 }
- 直接使用
var a chan int
是错误的,其没有构造chan
结构。因为这是一个引用数据类型 - 发送数据:channel <- data,如 a <- 1。
- 接收数据:data := <- channel,如 x := <- a。
- 直接使用
-
无缓冲通道的阻塞
无缓冲通道没有存储空间,发送操作会阻塞直到有一个接收操作准备好接收数据;同样,接收操作会阻塞直到有一个发送操作准备好提供数据。这意味着发送方和接收方必须同时准备好,数据才能传递。
b := make(chan int) //有缓冲区大小为2的 int 通道 select { case x := <- b: fmt.Println(x) default: // 无数据时执行 }
- 这允许程序检查数据而不等待
-
有缓冲通道的阻塞
有缓冲通道有一个固定大小的缓冲区,发送操作只有在缓冲区满时才会阻塞;接收操作只有在缓冲区空时才会阻塞。缓冲区允许发送和接收操作在不同时间发生,只要缓冲区有空间或数据即可。
-
意外细节
你可能不知道的是,即使有缓冲通道,如果接收操作先于发送操作执行且缓冲区空,接收操作也会阻塞,直到发送操作填入数据。这与无缓冲通道的同步性质类似,但缓冲区提供了更多灵活性。
3.7map
-
初始化map
// a := make(map[<keyType>]<valueType>) // b := map[<keyType>]<valueType>{<key>: <value>...} a := make(map[string]int) b := map[string]int{"a": 1, "b": 2, "c": 3} fmt.Println(a) fmt.Println(b)
- 和channel类似
-
crud
// crud // 改 b["d"] = 4 fmt.Println(b) // map[a:1 b:2 c:3 d:4] // 删 delete(b, "d") fmt.Println(b) // map[a:1 b:2 c:3] // 查看是否存在这个key,存在返回value,否则返回0 x := b["a"] fmt.Println(x) // 1 // 遍历 for k, v := range b { fmt.Println(k, v) } for k := range b { fmt.Println(k) } for _, v := range b { fmt.Println(v) }
3.8函数
3.8.1函数的分类
- 具名函数
func <functionName>([parameterList...]) [returnTypes...] {
<block>
}
func add(x int, y int) (int a) {
a := x + y
return
}
-
匿名函数
<functionName>([parameterList...]) [returnTypes...] { <block> } a := add(x int, y int) (int a) { a := x + y return }
3.8.2函数传参
-
Golang中没有引用类型,所以避免拷贝请使用指针
-
Golang中
args...
是解包,...args
是封包package main import ( "fmt" ) func add(a ...int) int { s := 0 for _, v := range a { s += v } return s } func main() { fmt.Println(add(1, 2, 3, 4, 5)) // 15 fmt.Println(add([]int{1, 2, 3}...)) // 6 }
3.8.3函数返回
-
如果指定函数返回值名,直接使用
return
返回即可。此时函数返回值已定义,无需在函数体内定义。func <functionName>([parameterList...]) [returnTypes...] { <block> } func add(x int, y int) (int a) { a := x + y return }
这里直接return就好,因为指定了返回参数为a
3.9方法
-
golang实现了对象和方法的静态绑定,虽然golang没有类和对象的概念,方法是golang的类的面向对象实现
- 静态绑定:方法或函数的调用在编译时已确定,通常与方法重载或静态方法相关。
- 动态绑定:方法的调用在运行时决定,通常与方法重写(Override)相关。
-
方法的对象必须是本包结构体。例如
func (builder strings.Builder) <functionName>([parameterList...]) [returnTypes...]
是错误的,func (x int) <functionName>([parameterList...]) [returnTypes...]
是错误的。// 函数实现 type File struct { fd int flag uint } func open(file *File, fp string, mode string) { // ... } func close(file *File) { // ... } func main() { file := File{} open(&file, "data.txt", "w") close(&file) } // 方法实现 type File struct { fd int flag uint } func (file *File) open(fp string, mode string) error { // ... } func (file *File) close(fp string, mode string) error { // ... } func main() { file := File{} file.open("data.txt", "w") file.close() }
- 由此可见方法与函数绑定了
-
参考https://blog.csdn.net/TimeLies_Sea/article/details/131581569