Golang基础笔记

前言

Go 简介

Google 开源

编译型语言

21 世界的 C 语言

2005 年出现多核处理器，其他语言都是单核时代诞生的。Go 天生考虑了多核并发。

特点：

语法简洁（只有 25 个关键字，比 Python 更简洁，自带格式化，互相阅读容易）

开发效率高

执行性能好（接近 java）

发展：

百度自动驾驶，小程序

腾讯蓝鲸，微服务框架

知乎最早用 python 写，后期承受不了负载，用 go 重构节约了 80% 资源。

课程简介

李文周博客

仓库

8 周基础

3 个实战项目

Go 项目结构

个人开发者

流行方式

Helloworld

Copy
go build

win 编译得 exe，macos 得可执行文件

Copy
go install

install 相当于 build 后再移到 bin

Copy
go run

当脚本运行

支持跨平台交叉编译

Copy
// wincmd SET, macos export
export CGO_ENABLED=0 //禁用CGO
export GOOS=linux //设置目标平台linux,windows,darwin
export GOARCH=amd64//目标处理器架构是amd64
go build

Copy
export CGO_ENABLED=0 GOOS=linux GOARCH=amd64
go build

变量与常量

函数外不能写语句

标识符：字母数字下划线，不以数字开头

关键字与保留字不建议用于变量名

变量

初始化

数字默认0，字符串默认空，布尔默认false，切片、函数、指针默认nil。

Copy
var 变量名 类型 = 表达式
var name string = "Q1mi"
var age int = 18
var name, age = "Q1mi", 20 //会根据值推导类型
var (
a string
b int
c bool
d float32
)

写在函数外为全局变量

函数内声明局部变量简写为

Copy
n := 10
m := 200
fmt.Println(m, n)

注意：在 Golang 里非全局变量声明必须使用，不然编译不通过！

Copy
fmt.Print()
fmt.Printf()
fmt.Println() //换行

保存时会自动格式化

命名规则

Copy
var studentName string

Golang 使用小驼峰命名

匿名变量

用短下划线接收，不占命名空间，不分配内存

Copy
x, _ = foo()
_, y = foo()

常量

Copy
const pi = 3.14

iota

常量计数器，每新增一行常量声明则计数，注意是一行

Copy
const (
n1 = iota //0
n2 //1
n3 //2
n4 //3
)

Copy
const (
n1 = iota //0
n2 //1
_ //2 但是被丢弃
n3 //3
)

定义数量级

Copy
const (
_ = iota
KB = 1 << (10 * iota)
MB = 1 << (10 * iota)
GB = 1 << (10 * iota)
TB = 1 << (10 * iota)
PB = 1 << (10 * iota)
)

<< 左移符号，二进制 1 左移 10 位是 1024

基本数据类型

整型分为以下两个大类：按长度分为：int8、int16、int32、int64

对应的无符号整型：uint8、uint16、uint32、uint64

uint8 就是 byte，int16 就是 short，int64 是 long

特殊整型

uint int 会根据系统判别是 32 还是 64

uintptr 指针，存放内存地址

进制

Golang 无法直接定义二进制数，八、十六均可

Copy
// 十进制
var a int = 10
fmt.Printf("%d \n", a) // 10
fmt.Printf("%b \n", a) // 1010 占位符%b表示二进制

// 八进制 以0开头
var b int = 077
fmt.Printf("%o \n", b) // 77

// 十六进制 以0x开头
var c int = 0xff
fmt.Printf("%x \n", c) // ff
fmt.Printf("%X \n", c) // FF
fmt.Printf("%T \n", c) // 输出类型
fmt.Printf("%v \n", c) // 输出变量值，任意类型

浮点数

golang 中小数默认float64

Copy
math.MaxFloat64 // float64最大值

布尔

默认 false，不允许转换

字符串

只能双引号，单引号为字符

转义 | 含义
\r | 返回行首
\n | 换行（下行同列）
\t | 制表

Copy
// 在win中路径转义
s := "D:\\Documents\\A"

// 反引号原样输出, 多行字符串
s := `
asda
asd
`
s := "D:\Documents\A"

Copy
len(str)
ss := s1 + s2
ret := strings.Split(s3, "\\")
ret = strings.Contains(s3, "abcd")
ret = strings.HasPrefix(s3, "abcd")
ret = strings.HasSufix(s3, "abcd")
ret = strings.Index(s3, "c")
ret = strings.LastIndex(s3, "c")
ret = strings.Join(a, b)

英文字符为byte，其他语系如中文字符为rune，实际为int32，占 3 位

字符串遍历

Copy
for _, char := range str {
fmt.Printf("%c", char)
}

字符串没法直接修改，只能转换为其他类型处理

Copy
s3 := []rune(s2) //切片
s3[0] = 'e' //修改
s4 := string(s3)

流程控制

if

Copy
if 表达式1 {
分支1
} else if 表达式2 {
分支2
} else{
分支3
}

Copy
// 局部变量score只在if中生效，减少内存占用
if score := 65; score >= 90 {
fmt.Println("A")
} else if score > 75 {
fmt.Println("B")
} else {
fmt.Println("C")
}

for

golang 只有 for

Copy
for 初始语句;条件表达式;结束语句{
循环体语句
}

Copy
for i := 0; i < 10; i++ {
fmt.Println(i)
}

初始语句和结束语句可省略，相当于 while

Copy
i := 0
for i < 10 {
fmt.Println(i)
i++
}

无限循环

Copy
for {
循环体语句
}

通过break、goto、return、panic语句强制退出循环

遍历

for range遍历数组、切片、字符串、map 及通道（channel）

Copy
for i,v := range s{
fmt.Println(i, v)
}

数组、切片、字符串返回索引和值。

map 返回键和值。

通道（channel）只返回通道内的值。

switch

Copy
finger := 3
switch finger {
case 1:
fmt.Println("大拇指")
fallthrough
case 2:
fmt.Println("食指")
case 3:
fmt.Println("中指")
case 4:
fmt.Println("无名指")
case 5:
fmt.Println("小拇指")
default:
fmt.Println("无效的输入！")
}

fallthrough语法可以执行满足条件的 case 的下一个 case，是为了兼容 C 语言中的 case 设计的

Copy
switch n := 7; n {
case 1, 3, 5, 7, 9:
fmt.Println("奇数")
case 2, 4, 6, 8:
fmt.Println("偶数")
default:
fmt.Println(n)
}

goto

goto语句通过标签进行代码间的无条件跳转。goto语句可以在快速跳出循环、避免重复退出上有一定的帮助。Go 语言中使用goto语句能简化一些代码的实现过程。 例如双层嵌套的 for 循环要退出时

Copy
var breakFlag bool
for i := 0; i < 10; i++ {
for j := 0; j < 10; j++ {
if j == 2 {
// 设置退出标签
breakFlag = true
break
}
fmt.Printf("%v-%v\n", i, j)
}
// 外层for循环判断
if breakFlag {
break
}
}

简化为

Copy
for i := 0; i < 10; i++ {
for j := 0; j < 10; j++ {
if j == 2 {
// 设置退出标签
goto breakTag
}
fmt.Printf("%v-%v\n", i, j)
}
}
return
// 标签
breakTag:
fmt.Println("结束for循环")

运算符

++（自增）和--（自减）在 Go 语言中是单独的语句，并不是运算符。

Copy
// 逻辑运算
&&
||
!

// 位运算
&
|
^
<<
>>

// 赋值
+=
-=
<<=

数组

初始化

数组从声明时就确定，使用时可以修改数组成员，但是数组大小不可变化

Copy
var a [3]int

var a [3]int
var b [4]int
a = b //不可以这样做，因为此时a和b是不同的类型

数组可以通过下标进行访问，下标是从0开始，最后一个元素下标是：len-1，访问越界（下标在合法范围之外），则触发访问越界，panic

Copy
var testArray [3]int //数组会初始化为int类型的零值
var numArray = [3]int{1, 2} //使用指定的初始值完成初始化
var cityArray = [3]string{"北京", "上海", "深圳"} //使用指定的初始值完成初始化
var numArray = [...]int{1, 2} //根据值推断数组长度
var cityArray = [...]string{"北京", "上海", "深圳"}

a := [...]int{1: 1, 3: 5} //指定索引初始化
fmt.Println(a) // [0 1 0 5]

Copy
for index, value := range a {
fmt.Println(index, value)
}

多维数组

Copy
a := [3][2]string{
{"北京", "上海"},
{"广州", "深圳"},
{"成都", "重庆"},
}

多维数组只有第一层可以使用...来让编译器推导数组长度

数组是值类型，赋值和传参会复制整个数组。因此改变副本的值，不会改变本身的值。

数组支持 “==“、”!=” 操作符，因为内存总是被初始化过的。

[n]*T表示指针数组，*[n]T表示数组指针 。

切片

数组的局限性，长度固定。

切片（Slice）是一个拥有相同类型元素的可变长度的序列。它是基于数组类型做的一层封装。它非常灵活，支持自动扩容。

切片是一个引用类型，它的内部结构包含地址、长度和容量。切片一般用于快速地操作一块数据集合。

初始化

Copy
var a = []string //声明一个字符串切片
var b = []int{} //声明一个整型切片并初始化
var c = []bool{false, true} //声明一个布尔切片并初始化
var d = []bool{false, true} //声明一个布尔切片并初始化

切片有指向时值就不为空了。

Copy
a1 := [...]int{1, 3, 5, 7, 9, 11, 13}
s3 := a1[0:4] //左包右不包，索引为0-3切片

len(s3) // 4 切片长度
cap(s3) // 7 容量=原数组切片点到末尾的长度

a[2:] // 等同于 a[2:len(a)]
a[:3] // 等同于 a[0:3]
a[:] // 等同于 a[0:len(a)]

原数组元素改了切片也变，引用类型。

Copy
a[low : high : max]
a := [5]int{1, 2, 3, 4, 5}
t := a[1:3:5] //t:[2 3] len(t):2 cap(t):4

构造与简单切片表达式a[low: high]相同类型、相同长度和元素的切片。另外，它会将得到的结果切片的容量设置为max-low。在完整切片表达式中只有第一个索引值（low）可以省略；它默认为 0。

make()

动态创建一个切片

Copy
make([]T, size, cap)

a := make([]int, 2, 10) // 初始化值为0

空切片判断

要检查切片是否为空，使用len(s) == 0来判断，而不应该使用s == nil来判断。

切片之间是不能比较的，我们不能使用==操作符来判断两个切片是否含有全部相等元素。 切片唯一合法的比较操作是和nil比较。 一个nil值的切片并没有底层数组，一个nil值的切片的长度和容量都是 0。但是我们不能说一个长度和容量都是 0 的切片一定是nil

赋值

Copy
s1 := make([]int, 3) //[0 0 0]
s2 := s1 //将s1直接赋值给s2，s1和s2共用一个底层数组
s2[0] = 100
fmt.Println(s1) //[100 0 0]
fmt.Println(s2) //[100 0 0]

append()

Copy
var s []int
s = append(s, 1) // [1]
s = append(s, 2, 3, 4) // [1 2 3 4
s2 := []int{5, 6, 7}
s = append(s, s2...) // [1 2 3 4 5 6 7]

var 声明的零值切片可以在append()函数直接使用，无需初始化

Copy
var s []int
s = append(s, 1, 2, 3)

每个切片会指向一个底层数组，这个数组的容量够用就添加新增元素。当底层数组不能容纳新增的元素时，切片就会自动按照一定的策略进行 “扩容”，此时该切片指向的底层数组就会更换。“扩容” 操作往往发生在append()函数调用时，所以我们通常都需要用原变量接收 append 函数的返回值。

Copy
func main() {
//append()添加元素和切片扩容
var numSlice []int
for i := 0; i < 10; i++ {
numSlice = append(numSlice, i)
fmt.Printf("%v len:%d cap:%d ptr:%p\n", numSlice, len(numSlice), cap(numSlice), numSlice)
}
}

输出

Copy
[0] len:1 cap:1 ptr:0xc0000a8000
[0 1] len:2 cap:2 ptr:0xc0000a8040
[0 1 2] len:3 cap:4 ptr:0xc0000b2020
[0 1 2 3] len:4 cap:4 ptr:0xc0000b2020
[0 1 2 3 4] len:5 cap:8 ptr:0xc0000b6000
[0 1 2 3 4 5] len:6 cap:8 ptr:0xc0000b6000
[0 1 2 3 4 5 6] len:7 cap:8 ptr:0xc0000b6000
[0 1 2 3 4 5 6 7] len:8 cap:8 ptr:0xc0000b6000
[0 1 2 3 4 5 6 7 8] len:9 cap:16 ptr:0xc0000b8000
[0 1 2 3 4 5 6 7 8 9] len:10 cap:16 ptr:0xc0000b8000

从上面的结果可以看出：

append()函数将元素追加到切片的最后并返回该切片。

切片 numSlice 的容量按照 1，2，4，8，16 这样的规则自动进行扩容，每次扩容后都是扩容前的 2 倍。

$GOROOT/src/runtime/slice.go源码:

Copy
newcap := old.cap
doublecap := newcap + newcap
if cap > doublecap {
newcap = cap
} else {
if old.len < 1024 {
newcap = doublecap
} else {
// Check 0 < newcap to detect overflow
// and prevent an infinite loop.
for 0 < newcap && newcap < cap {
newcap += newcap / 4
}
// Set newcap to the requested cap when
// the newcap calculation overflowed.
if newcap <= 0 {
newcap = cap
}
}
}

首先判断，如果新申请容量（cap）大于 2 倍的旧容量（old.cap），最终容量（newcap）就是新申请的容量（cap）。

否则判断，如果旧切片的长度小于 1024，则最终容量 (newcap) 就是旧容量 (old.cap) 的两倍，即（newcap=doublecap），

否则判断，如果旧切片长度大于等于 1024，则最终容量（newcap）从旧容量（old.cap）开始循环增加原来的 1/4，即（newcap=old.cap,for {newcap += newcap/4}）直到最终容量（newcap）大于等于新申请的容量 (cap)，即（newcap >= cap）

如果最终容量（cap）计算值溢出，则最终容量（cap）就是新申请容量（cap）。

中文字符串是 3*2^n

copy()

切片是引用类型，所以 a 和 b 其实都指向了同一块内存地址。修改 b 的同时 a 的值也会发生变化。

Go 语言内建的copy()函数可以迅速地将一个切片的数据复制到另外一个切片空间中。

Copy
a := []int{1, 2, 3, 4, 5}
c := make([]int, 5, 5)
copy(c, a) //使用copy()函数将切片a中的元素复制到切片c
fmt.Println(a) //[1 2 3 4 5]
fmt.Println(c) //[1 2 3 4 5]
c[0] = 1000
fmt.Println(a) //[1 2 3 4 5]
fmt.Println(c) //[1000 2 3 4 5]

删除元素

Copy
a = append(a[:index], a[index+1:]...)
a := []int{30, 31, 32, 33, 34, 35, 36, 37}
// 要删除索引为2的元素
a = append(a[:2], a[3:]...)
fmt.Println(a) //[30 31 33 34 35 36 37]

//底层数组长度不变，元素左移，右边的由最右元素补全

排序对切片排

Copy
sort.Ints(a[:])

指针

Copy
ptr := &v // v的类型为T 输出指针类型*T 如 *string *int

a := 10
b := &a
fmt.Printf("a:%d ptr:%p\n", a, &a) // a:10 ptr:0xc00001a078
fmt.Printf("b:%p type:%T\n", b, b) // b:0xc00001a078 type:*int
fmt.Println(&b) // 0xc00000e018

Copy
c := *b // 指针取值（根据指针去内存取值）
fmt.Printf("type of c:%T\n", c)
fmt.Printf("value of c:%v\n", c)

& 与 * 互补

Copy
func modify1(x int) {
x = 100
}

func modify2(x *int) {
*x = 100
}

func main() {
a := 10
modify1(a)
fmt.Println(a) // 10
modify2(&a)
fmt.Println(a) // 100
}

new 与 make

new 函数不太常用，使用 new 函数得到的是一个类型的指针，并且该指针对应的值为该类型的零值

Copy
a := new(int)
b := new(bool)
fmt.Printf("%T\n", a) // *int
fmt.Printf("%T\n", b) // *bool
fmt.Println(*a) // 0
fmt.Println(*b) // false

make 也是用于内存分配的，区别于 new，它只用于 slice、map 以及 chan 的内存创建，而且它返回的类型就是这三个类型变量本身，而不是他们的指针类型，因为这三种类型就是引用类型

Copy
var b map[string]int
b = make(map[string]int, 10)
b["沙河娜扎"] = 100
fmt.Println(b)

map

Go 语言中提供的映射关系容器为map，其内部使用散列表（hash）实现，类似 python 的字典

map 是一种无序的基于key-value的数据结构，Go 语言中的 map 是引用类型，必须初始化才能使用

map 类型的变量默认初始值为 nil，需要使用 make () 函数来分配内存

Copy
map[KeyType]ValueType

scoreMap := make(map[string]int, 8) // 初始化才能用，避免动态扩容！
scoreMap["张三"] = 90
scoreMap["小明"] = 100
fmt.Println(scoreMap)
fmt.Println(scoreMap["小明"])
fmt.Printf("type of a:%T\n", scoreMap)

userInfo := map[string]string{
"username": "沙河小王子",
"password": "123456",
}

判断键值是否存在

Copy
value, ok := map[key] // ok返回key是否存在的bool值

v, ok := scoreMap["张三"]
if ok {
fmt.Println(v)
} else {
fmt.Println("查无此人")
}

map 的遍历

Copy
for k, v := range scoreMap {
fmt.Println(k, v)
}

for k := range scoreMap {
fmt.Println(k)
}

for _, v := range scoreMap {
fmt.Println(v)
}

注意：遍历 map 时的元素顺序与添加键值对的顺序无关

删除键值对

Copy
delete(map, key)

按照指定顺序遍历

Copy
func main() {
rand.Seed(time.Now().UnixNano()) //初始化随机数种子

var scoreMap = make(map[string]int, 200)

for i := 0; i < 100; i++ {
key := fmt.Sprintf("stu%02d", i) //生成stu开头的字符串
value := rand.Intn(100) //生成0~99的随机整数
scoreMap[key] = value
}
//取出map中的所有key存入切片keys
var keys = make([]string, 0, 200)
for key := range scoreMap {
keys = append(keys, key)
}
//对切片进行排序
sort.Strings(keys)
//按照排序后的key遍历map
for _, key := range keys {
fmt.Println(key, scoreMap[key])
}
}

元素为 map 类型的切片

Copy
var mapSlice = make([]map[string]string, 3) // 切片初始化，每个元素都是一个map
for index, value := range mapSlice {
fmt.Printf("index:%d value:%v\n", index, value)
}
fmt.Println("after init")
// 对切片中的map元素进行初始化
mapSlice[0] = make(map[string]string, 10)
mapSlice[0]["name"] = "小王子"
mapSlice[0]["password"] = "123456"
mapSlice[0]["address"] = "沙河"
for index, value := range mapSlice {
fmt.Printf("index:%d value:%v\n", index, value)
}

值为切片类型的 map

Copy
func main() {
var sliceMap = make(map[string][]string, 3)
fmt.Println(sliceMap)
fmt.Println("after init")
key := "中国"
value, ok := sliceMap[key]
if !ok {
value = make([]string, 0, 2)
}
value = append(value, "北京", "上海")
sliceMap[key] = value
fmt.Println(sliceMap)
}

函数

Copy
func 函数名(参数 类型) 返回值类型 {
函数体
}

func intSum(x int, y int) int {
return x + y
}

参数同类型简写

Copy
func intSum(x, y int) int {
return x + y
}

可变参数

Copy
func intSum2(x ...int) int {
fmt.Println(x) //x是一个切片
sum := 0
for _, v := range x {
sum = sum + v
}
return sum
}

返回值

Copy
//有命名的返回
func calc(x, y int) (sum, sub int) {
sum = x + y
sub = x - y
return
}
//切片
func someFunc(x string) []int {
if x == "" {
return nil // 没必要返回[]int{}
}
...
}

如果局部变量和全局变量重名，优先访问局部变量

函数类型与变量

我们可以使用type关键字来定义一个函数类型，具体格式如下：

Copy
type calculation func(int, int) int

上面语句定义了一个calculation类型，它是一种函数类型，这种函数接收两个 int 类型的参数并且返回一个 int 类型的返回值。

Copy
func main() {
var c calculation // 声明一个calculation类型的变量c
c = add // 把add赋值给c
fmt.Printf("type of c:%T\n", c) // type of c:main.calculation
fmt.Println(c(1, 2)) // 像调用add一样调用c

f := add // 将函数add赋值给变量f1
fmt.Printf("type of f:%T\n", f) // type of f:func(int, int) int
fmt.Println(f(10, 20)) // 像调用add一样调用f
}

函数作参数与返回值

Copy
func add(x, y int) int {
return x + y
}
func calc(x, y int, op func(int, int) int) int {
return op(x, y)
}
func main() {
ret2 := calc(10, 20, add)
fmt.Println(ret2) //30
}

Copy
func do(s string) (func(int, int) int, error) {
switch s {
case "+":
return add, nil
case "-":
return sub, nil
default:
err := errors.New("无法识别的操作符")
return nil, err
}
}

匿名函数

函数内部定义函数

Copy
func main() {
// 将匿名函数保存到变量
add := func(x, y int) {
fmt.Println(x + y)
}
add(10, 20) // 通过变量调用匿名函数

//自执行函数：匿名函数定义完加()直接执行
func(x, y int) {
fmt.Println(x + y)
}(10, 20)
}

闭包

闭包指的是一个函数和与其相关的引用环境组合而成的实体。简单来说，闭包=函数+引用环境

Copy
func adder() func(int) int {
var x int
return func(y int) int {
x += y
return x
}
}
func main() {
var f = adder()
fmt.Println(f(10)) //10
fmt.Println(f(20)) //30
fmt.Println(f(30)) //60

f1 := adder()
fmt.Println(f1(40)) //40
fmt.Println(f1(50)) //90
}

defer

defer语句会将其后面跟随的语句进行延迟处理。在defer归属的函数即将返回时，将延迟处理的语句按defer定义的逆序进行执行，也就是说，先被defer的语句最后被执行，最后被defer的语句，最先被执行

Copy
func main() {
fmt.Println("start")
defer fmt.Println(1)
defer fmt.Println(2)
defer fmt.Println(3)
fmt.Println("end")
}

/*
start
end
3
2
1
*/

Copy
//面试题 defer注册要延迟执行的函数时,该函数所有的参数都需要确定其值
func calc(index string, a, b int) int {
ret := a + b
fmt.Println(index, a, b, ret)
return ret
}

func main() {
x := 1
y := 2
defer calc("AA", x, calc("A", x, y))
x = 10
defer calc("BB", x, calc("B", x, y))
y = 20
}

/*
A 1 2 3 //defer calc("AA", 1, 3)
B 10 2 12 //defer calc("BB", 10, 12)
BB 10 12 22
AA 1 3 4
*/

内置函数

内置函数 | 介绍
close | 主要用来关闭 channel
len | 用来求长度，比如 string、array、slice、map、channel
new | 用来分配内存，主要用来分配值类型，比如 int、struct。返回的是指针
make | 用来分配内存，主要用来分配引用类型，比如 chan、map、slice
append | 用来追加元素到数组、slice 中
panic 和 recover | 用来做错误处理

Go 语言中目前（Go1.12）是没有异常机制，但是使用panic/recover模式来处理错误。 panic可以在任何地方引发，但recover只有在defer调用的函数中有效

Copy
func funcA() {
fmt.Println("func A")
}

func funcB() {
defer func() {
err := recover()
//如果程序出出现了panic错误,可以通过recover恢复过来
if err != nil {
fmt.Println("recover in B")
}
}()
panic("panic in B")
}

func funcC() {
fmt.Println("func C")
}
func main() {
funcA()
funcB()
funcC()
}

recover()必须搭配defer使用。

defer一定要在可能引发panic的语句之前定义。

fmt 标准库

fmt 包实现了类似 C 语言 printf 和 scanf 的格式化 I/O。主要分为向外输出内容和获取输入内容两大部分

Print

Copy
func main() {
fmt.Print("在终端打印该信息。") //不换行
name := "沙河小王子"
fmt.Printf("我是：%s\n", name)
fmt.Println("在终端打印单独一行显示")
}

FPrint

Fprint系列函数会将内容输出到一个io.Writer接口类型的变量w中，我们通常用这个函数往文件中写入内容

Copy
// 向标准输出写入内容
fmt.Fprintln(os.Stdout, "向标准输出写入内容")
fileObj, err := os.OpenFile("./xx.txt", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
if err != nil {
fmt.Println("打开文件出错，err:", err)
return
}
name := "沙河小王子"
// 向打开的文件句柄中写入内容
fmt.Fprintf(fileObj, "往文件中写如信息：%s", name)

只要满足io.Writer接口的类型都支持写入

Sprint

Sprint系列函数会把传入的数据生成并返回一个字符串

Copy
s3 := fmt.Sprintln("沙河小王子")

Errorf

Copy
e := errors.New("原始错误e")
w := fmt.Errorf("Wrap了一个错误%w", e)

Scan

Copy
fmt.Scan(&name, &age, &married)
fmt.Scanf("1:%s 2:%d 3:%t", &name, &age, &married)
fmt.Scanln(&name, &age, &married)

另有 Fscan，Sscan

bufio.NewReader

Copy
func bufioDemo() {
reader := bufio.NewReader(os.Stdin) // 从标准输入生成读对象
fmt.Print("请输入内容：")
text, _ := reader.ReadString('\n') // 读到换行终止 空格也读入
text = strings.TrimSpace(text)
fmt.Printf("%#v\n", text)
}

结构体

Go 语言中没有 “类” 的概念，也不支持 “类” 的继承等面向对象的概念。Go 语言中通过结构体的内嵌再配合接口比面向对象具有更高的扩展性和灵活性。

自定义类型

自定义类型是定义了一个全新的类型。我们可以基于内置的基本类型定义，也可以通过 struct 定义

Copy
//将MyInt定义为int类型
type MyInt int

通过type关键字的定义，MyInt就是一种新的类型，它具有int的特性。

类型别名

类型别名规定：TypeAlias 只是 Type 的别名，本质上 TypeAlias 与 Type 是同一个类型

Copy
type TypeAlias = Type

我们之前见过的rune和byte就是类型别名

Copy
type byte = uint8
type rune = int32

结构体定义

使用type和struct关键字来定义结构体，具体代码格式如下：

Copy
type 类型名 struct {
字段名 字段类型
字段名 字段类型
…
}

type person struct {
name string
city string
age int8
}

type person1 struct {
name, city string
age int8
}

其中：

类型名：标识自定义结构体的名称，在同一个包内不能重复。

字段名：表示结构体字段名。结构体中的字段名必须唯一。

字段类型：表示结构体字段的具体类型。

实例化

只有当结构体实例化时，才会真正地分配内存。必须实例化后才能使用结构体的字段。

结构体本身也是一种类型，我们可以像声明内置类型一样使用var关键字声明结构体类型。

Copy
var 结构体实例 结构体类型

基本实例化

Copy
type person struct {
name string
city string
age int8
}

func main() {
var p1 person
p1.name = "沙河娜扎"
p1.city = "北京"
p1.age = 18
fmt.Printf("p1=%v\n", p1) //p1={沙河娜扎 北京 18}
fmt.Printf("p1=%#v\n", p1) //p1=main.person{name:"沙河娜扎", city:"北京", age:18}
}

匿名结构体，用于临时数据结构

Copy
func main() {
var user struct{Name string; Age int}
user.Name = "小王子"
user.Age = 18
fmt.Printf("%#v\n", user)
}

指针类型结构体，使用 new 分配地址

Copy
var p2 = new(person)
//使用&对结构体进行取地址操作相当于对该结构体类型进行了一次new实例化操作
p3 := &person{}

fmt.Printf("%T\n", p2) //*main.person
fmt.Printf("p2=%#v\n", p2) //p2=&main.person{name:"", city:"", age:0}

//支持对结构体指针直接使用.来访问结构体的成员
p2.name = "小王子"
p2.age = 28
p2.city = "上海"
fmt.Printf("p2=%#v\n", p2) //p2=&main.person{name:"小王子", city:"上海", age:28}

初始化

没有初始化的结构体，其成员变量都是对应其类型的零值。初始化是赋值的实例化。

使用键值对初始化

Copy
p5 := person{
name: "小王子",
city: "北京",
age: 18,
}

对结构体指针初始化

Copy
p6 := &person{
name: "小王子",
city: "北京",
age: 18,
}

用列表初始化

Copy
p8 := &person{
"沙河娜扎",
"北京",
28,
}

内存布局

结构体占用一块连续的内存，空结构体不占空间

构造函数

实现类似其他语言面向对象的构造函数，Go 是面向接口编程。