这篇博客为黑马《20个小时快速入门Go语言》的课程笔记,仅用于个人纪录,所以杂乱无章
Go语言优势
var a int
var a int = 10
a := 10
a, b := 10, 20
var (
a int
b float64
)
交换
i, j = j, i
匿名变量
tmp, _ = i, j
配合函数返回值使用,才有优势
常量
const a int = 10
const b = 11.2
const(
i = 10
j = 3.14
)
iota
基础数据类型
bool、byte、int、uint、float32、string
字符类型
var ch byte
ch = 97
ch = ‘a’
字符串
var str string
str = “abc”
len(str)
str[0]
复数
var t complex128
t = 2.1 + 3.14i
real(t)
imag(t)
输入
var a int
fmt.Scanf("%d", &a)
fmt.Scan(&a)
类型转换
bool类型不能转换为整型,整型也不能转换为bool
var ch byte
ch = 'a'
var t int
t = int(ch)
类型别名
type bigint int64
if
if a > 1 {
fmt.Println(a)
}
if支持一个初始化语句,初始化语句和判断条件以分号分隔
if a := 10; a == 10 {
fmt.Println(a)
}
a := 10
if a == 10{
//
} else if a > 10{
//
} else {
//
}
switch语句,fallthrough不跳出switch语句,后面无条件的执行
switch num {
case 1:
//
break // 默认包含break
case 2:
//
break
default:
//
}
score := 85
switch{
case score > 90:
//
case score > 80:
//
default:
//
}
循环
for i := 1; i <= 100; i++ {
//
}
range默认返回2个值:一个是元素的位置,一个是元素本身
str := "abc"
for i, data := range str {
//
}
//第2个返回值,默认丢弃
for i := range str {
//
}
//死循环
for {
}
goto跳转到标签
定义格式如下:
func FuncName(参数列表) (o1 type1, o2 type2) {
// 函数体
return 返回值
}
不定参数列表
func MyFunc(args ...int){
fmt.Println(len(args))
}
不定参数的传递
func MyFunc(args ...int){
}
func Test(args ...int){
MyFunc(args...)
MyFunc(args[2:]...)
}
带返回值的常用写法
func MyFunc() (result int) {
result = 666
return
}
多个返回值
func MyFunc() (int, int, int) {
}
//go 官方推荐
func MyFunc() (a int, b int, c int){
a, b, c = 1, 2, 3
return
}
函数类型
type FuncType func(int, int) int
var fTest FuncType
回调函数
函数有一个参数是函数类型
type FuncType func(int, int) int
func Calc(a, b int, fTest FuncType) (result int) {
result = fTest(a, b)
return
}
匿名函数与闭包
//定义匿名函数,同时调用
func() {
//
}()
//带参数的匿名函数
func(i, j int){
//
}(1, 2)
//有参数有返回值的匿名函数
func(i, j int)(max, min int){
//
return
}(1,2)
闭包以引用的方式捕获外部变量
它不关心这些捕获了的变量和常量是否已经超出了作用域,所以只有闭包还在使用它,这些变量就还会存在
延迟调用defer
func main() {
// defer在main函数结束前调用
defer fmt.Println("aaa")
fmt.Printlen("bbb")
}
// 输出:
// bbb
// aaa
如果一个函数中有多个defer语句,它们会以后进先出的顺序执行。哪怕函数或某个延迟调用发生错误,这些调用仍旧会被执行
defer和匿名函数的结合使用
func main() {
a := 10
b := 20
defer func() {
fmt.Printf("a = %d, b = %d\n", a, b)
}()
a = 111
b = 222
}
输出:
111
222
获取命令行参数
package main
import "fmt"
import "os"
func main() {
list := os.Args
n := len(list)
}
Go代码必须放在工作区。包含3个子目录:src、pkg、bin
设置GOPATH
环境变量
同一个目录,包名必须一样
go env
查看go相关的环境路径
同一个目录,调用别的文件的函数,直接调用即可,无需包名引用
调用不同包里面的函数,格式:包名.函数名()
如果包函数名首字母是小写,则其他包无法调用
常用导入方式
import (
"fmt"
"os"
)
.操作
调用函数,无需通过包名,但是这样方式不好
import . "fmt"
func main() {
Println("hello")
}
给包名起别名
import io "fmt"
忽略此包
_操作其实是引入该包,而不直接使用包里面的函数,而是调用了该包里面的init
函数
import _ "fmt"
main函数和init函数
导入包时,会调用该包的init
函数
go install
配置GOBIN
后,执行go install
,会生成pkg
和bin
目录,pkg存放平台相关的库,bin存放可执行文件
Go语言对指针的支持介于Java和C/C++语言之间,它既没有像Java那样取消了代码对指针直接操作的能力,也避免了C/C++语言中由于对指针的滥用而造成的安全和可靠性问题
nil
,没有NULL
常量->
运算符,直接用.
访问目标成员package main
func main() {
var a int = 10
// 每个变量有两层含义:变量的内存内容,变量的内存地址,a,&a
}
指针保存某个变量的地址,*int
保存int
的地址
var p *int
p = &a
*p = 666
new函数
a := 10
var p *int
p = new(int)
q := new(int)
我们只需要使用new()
函数,无需担心其内存的生命周期
数组长度必须是常量,[10]int
和[5]int
是不同类型
var id [50]int
初始化
// 全部初始化
var a [5]int = [5]int{
1, 2, 3, 4, 5}
b := [5]int{
1, 2, 3, 4, 5}
c := [...]int{
1, 2, 3, 4, 5}
// 部分初始化,没有初始化的元素,自动赋值为0
c := [5]int{
1, 2, 3}
// 指定某个元素初始化
d := [5]int{
2:10, 4:20}
二维数组
var a [3][4]int
b := [3][4]int{
{
1, 2, 3, 4},
{
5, 6, 7, 8},
{
9, 10, 11, 12}
}
// 指定元素初始化
c := [3][4]int{
1:{
1,2,3,4}}
数组比较和赋值
// 支持比较,只支持 == 或 !=,比较每一个元素
a := [5]int{
1,2,3,4,5}
b := [5]int{
1,2,3,4,5}
fmt.Println(a == b)
输出:true
随机数的使用
import "math/rand"
// 设置种子,只需一次
rand.Seed(666)
fmt.Println(rand.Int())
fmt.Println(rand.Intn(100))
数组做函数参数是值传递
数组的长度在定义之后无法再次修改,并且数组是值类型,每次传递都将产生一份副本
切片并不是数组或数组指针,它通过内部指针和相关属性引用数组片段,以实现变长方案
slice并不是在真正意义上的动态数组,而是一个引用类型。slice总是指向一个底层array,slice的声明也可以像array一样,只是不需要长度
slice := array[low][high][max]
low:下标的起点
hith:下标的终点(不包括此下标)
cap:容量,cap = max - low
// 数组
a := [5]int{
}
// 切片初始化
s := []int{
}
s.append(s, 1)
// 自动推导类型,同时初始化
s1 := []int{
1, 2, 3, 4}
// 借助make函数,格式make(切片类型,长度,容量)
s2 := make([]int, 5, 10)
// 没有指定容量,容量和长度一样
s3 := make([]int, 5)
切片的截取
array := []int{
0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
// [low:high:max]
s1 := array[:] //[0:len(array):len(array)]
s2 := array[3:6:7]
s3 := array[:6] //从0开始,长度为6,容量也为6
s4 := array[3:] //从下标3开始,到结尾
// 操作某个元素,和数组操作方式一样
data := array[1]
切片和底层数组的关系
a := []int{
0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
s1 := a[2:5]
s1[1] = 666 // 原数组a的值也会改变
s2 := a[2:7]
s2[2] = 777 // 原数组a的值也改变
切片内建函数
append
s1 := []int{
}
s1 = append(s1, 1)
s1 = append(s1, 2)
s2 := []int{
1, 2, 3}
s2 = append(s2, 4)
append函数会智能地控制底层数组的容量增长,一旦超过原底层数组容量,通常以2倍容量重新分配底层数组,并复制原来的数据
copy
srcSlice := []int{
1, 2}
dstSlice := []int{
6, 6, 6, 6, 6, 6}
copy(dstSlice, srcSlice)
fmt.Println(dstSlice)
输出:
[1 2 6 6 6 6]
切片做函数参数
引用传递
猜数字游戏
package main
import(
"fmt"
"math/rand"
"time"
)
func CreateNum(p *int) {
rand.Seed(time.Now().UnixNano())
var num int
for {
num = rand.Intn(10000)
if num >= 1000 {
break
}
}
*p = num
}
func GetNum(s []int, num int){
s[0] = num / 1000;
s[1] = num % 1000 / 100
s[2] = num % 100 / 10
s[3] = num % 10
}
func OnGame(s []int){
var num int
for{
for{
fmt.Println("请输入一个4位数:")
fmt.Scan(&num)
if 999 <= num && num < 10000{
break
}
fmt.Println("输入的数不符合要求")
}
keySlice := make([]int, 4)
GetNum(keySlice, num)
...
}
}
func main() {
var randNum int
CreateNum(&randNum)
randSlice := make(int[], 4)
// 保存这个4位数的每一位
GetNum(randSlice, randNum)
OnGame(randSlice)
}
无序的key-value集合
info := map[int]string{
1 : "hello"
}
注意:切片、函数以及包含切片的数据结构这些类型由于具有引用语义,不能作为映射的键,使用这些类型后编译错误
var m1 map[int]string
// 可以通过make创建
m2 := make(map[int]string)
// 可以通过make创建,并且指定长度,相当于指定容量
m3 := make(map[int]string, 10)
m3[1] = "mike"
// 初始化
m4 := map[int]string{
1:"mike", 2:"go"}
map遍历
m := map[int]string{
1:"mike", 2:"yoyo", 3:"go"}
for key, value := range m {
...
}
// 如何判断一个key值是否存在
// 第一个返回值位key对应的value,第二个返回值位key是否存在
value, ok := m[0]
map删除
m := map[int]string{
1:"mike", 2:"yoyo", 3:"go"}
delete(m, 1) // 删除key为1的内容
map做函数参数
引用传递
type Student struct{
id int
name string
sex byte
age int
addr string
}
结构体初始化
func main() {
// 顺序初始化,每个成员必须初始化
var s1 Student = Student{
1, "mike", 'm', 18, "beijing"}
// 指定成员初始化
s1 := Student{
name: "mike", addr: "beijing"}
}
结构体指针变量初始化
var p1 *Student = &Student{
}
结构体成员的使用
var s Student
var p *Student = &s
var p1 = new(Student)
// 操作成员,使用(.)运算符
s.id = 1;
s.name = "mike"
s.sex = 'm'
p.age = 18
结构体作为函数参数
值传递
可见性
如果想使用别的包的函数、结构体类型、结构体成员,函数名,类型名,结构体成员变量名的首字母必须大写,如果首字母是小写,只能在同一个包里使用
没有继承(尽管匿名字段的内存布局和行为类似继承,但它并不是继承)、虚函数、构造函数和析构函数、隐藏的this
指针
但是通过别的方式实现:
匿名字段初始化
type Person struct{
name string
sex byte
age int
}
type Student struct{
Person // 匿名字段,只有类型,没有名字,继承了Person的成员
id int
addr string
}
func main() {
// 顺序初始化
var s1 Student = Student{
Person{
"mike", 'm', 18}, 1, "beijing"}
// 自动推导类型
s2 := Student{
Person{
"mike", 'm', 18}, 1, "beijing"}
// 指定成员初始化
s3 := Student{
id: 1}
}
成员的操作
s1 := Student{
Person{
"mike", 'm', 18}, 1, "beijing"}
s1.name = "yoyo"
s1.sex = 'f'
s1.Person = Person{
"go", 'm', 18}
同名字段
type Person struct{
name string
sex byte
age int
}
type Student struct{
Person // 匿名字段,只有类型,没有名字,继承了Person的成员
id int
addr string
name string // 和Person中的name同名
}
func main() {
var s Student
s.name = "mike" // 操作的是Student结构体中的name,而不是Person中的name,就近原则
s.sex = 'm'
s.age = 18
s.addr = "beijing"
s.Person.name = "yoyo"
}
非结构体匿名字段
type mystr string
type Student struct {
Person // 结构体匿名字段
int // 基础类型的匿名字段
addr string
mystr
}
func main() {
s := Student{
Person{
}, 1, "hello"}
fmt.Println(s.name, s.age, s.sex, s.int, s.mystr)
fmt.Println(s.Person, s.int, s.mystr)
}
结构体指针类型匿名字段
type Student struct{
*Person
id int
addr string
}
func main() {
s1 := Student{
&Person{
"mike", 'm', 18}, 666, "beijing"}
// 先定义变量
var s2 Student
s2.Person = new(Person)
}
本质上,一个方法则是一个和特殊类型关联的函数
在Go语言中,可以给任意自定义类型(包括内置类型,但不包括指针类型)添加相应的方法
方法总是绑定对象实例,并隐式将实例作为第一实参(receiver)
func (receiver ReceiverType) funcName(parameters) (results)
接收者类型不同的函数可以同名,是不同的方法
// 实现2数相加
// 面向过程
func Add1(a, b int) int {
return a + b
}
// 面向对象方法:给某个类型绑定一个函数
type long int
// tmp叫接收者,接收者就是传递的一个参数
func(tmp long) Add02(other long) long {
return tmp + other
}
func main() {
var result int
result := Add01(1, 1) // 普通函数
// 定义一个变量
var a long = 2;
// 调用方法格式:变量名.函数
res := a.Add02(3)
//面向对象只是换了一种表现形式
}
为结构体类型添加方法
type Person struct{
name string
sex byte
age int
}
// 带有接收者的函数叫方法
func (tmp Person) PrintInfo() {
fmt.Println(tmp)
}
// 通过一个函数,给成员赋值
func (p *Person) SetInfo(n string, s byte, a int){
p.name = n
p.sex = s
p.age = a
}
func main() {
p := Person{
"mike", 'm', 18}
p.PrintInfo()
var p2 Person
(&p2).SetInfo("yoyo", 'f', 22)
p2.PrintInfo()
}
方法集
用实例value和pointer调用方法(含匿名字段)不受方法集约束,编译器总是查找全部方法,并自动转换receiver实参
type Person struct{
name string
sex byte
age int
}
func (p Person) SetInfoValue(){
...
}
func (p *Person) SetInfoPointer(){
...
}
func main() {
// 结构体变量是一个指针变量,它能够调用那些方法,这些方法的集合就是方法集
p := &Person{
"mike", 'm', 18}
p.SetInfoPointer()
p.SetInfoValue()
(*p).SetInfoPointer()
}
匿名字段
方法的继承
type Person struct{
name string
sex byte
age int
}
// Person类型,实现了一个方法
func (tmp *Person) PrintInfo() {
...
}
type Student struct{
Person
id int
addr string
}
func main() {
s := Student{
Person{
"mike", 'm', 18}, 666, "beijing"}
}
方法的重写
type Person struct{
name string
sex byte
age int
}
// Person类型,实现了一个方法
func (tmp *Person) PrintInfo() {
...
}
type Student struct{
Person
id int
addr string
}
func (tmp *Student) PrintInfo() {
...
}
func main() {
s := Student{
Person{
"mike", 'm', 18}, 666, "beijing"}
s.PrintfInfo() // 就近原则,调用Student的PrintInfo()方法
s.Person.PrintInfo()
}
方法值
type Person struct{
name string
sex byte
age int
}
func (p Person) SetInfoValue(){
...
}
func (p *Person) SetInfoPointer(){
...
}
func main() {
// 结构体变量是一个指针变量,它能够调用那些方法,这些方法的集合就是方法集
p := Person{
"mike", 'm', 18}
p.SetInfoPointer()
// 保存方法入口地址
pFunc := p.SetInfoPointer
pFunc() // 等价于p.SetInfoPointer
}
方法表达式
type Person struct{
name string
sex byte
age int
}
func (p Person) SetInfoValue(){
...
}
func (p *Person) SetInfoPointer(){
...
}
func main() {
// 结构体变量是一个指针变量,它能够调用那些方法,这些方法的集合就是方法集
p := Person{
"mike", 'm', 18}
p.SetInfoPointer()
f := (*Person).SetInfoPointer
f(&p) // 显式把接收者传递过去
f2 = (Person).SetInfoValue
f2(p)
}
Go通过接口实现了鸭子类型(duck-typing):“当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子”。我们并不关心对象是什么类型,到底是不是鸭子,只关心行为
er
结尾type Humaner interface {
SayHi()
}
type Studetn struct {
name string
id int
}
type Teacher struct {
addr string
group string
}
// Student实现了SayHi()方法
func (tmp *Student) sayHi() {
...
}
// Teacher实现了SayHi()方法
func (tmp *Teacher) sayHi() {
...
}
// 定义一个普通函数,函数的参数为接口类型
// 只有一个函数,可以有不同表现,多态
func WhoSayHi(i Humaner){
i.sayHi()
}
func main() {
// 定义接口类型的变量
var i Humaner
// 只要实现了此接口方法的类型,那么这个类型的变量就可以给i赋值
s := Student{
"mike", 666}
i = s
i.sayHi()
t := Teacher("beijing", "go")
i = t
t.sayHi()
// 创建一个切片
x := make([]Humaner, 3)
x[0] = s
x[1] = t
for
}
接口的继承
type Humaner interface {
sayHi()
}
type Personer interface {
Humaner
sing(lrc string)
}
type Studetn struct {
name string
id int
}
// Student实现了SayHi()方法
func (tmp *Student) sayHi() {
...
}
func (tmp *Student) sing(lrc string) {
...
}
func main() {
var i Personer
s := &Student{
"mike", 666}
i = s
s.sayHi()
s.sing()
}
接口转换
type Humaner interface {
sayHi()
}
type Personer interface {
Humaner
sing(lrc string)
}
type Studetn struct {
name string
id int
}
// Student实现了SayHi()方法
func (tmp *Student) sayHi() {
...
}
func (tmp *Student) sing(lrc string) {
...
}
func main() {
var i Personer
s := &Student{
"mike", 666}
i = s
s.sayHi()
s.sing()
// 超集可以转换为子集,反过来不可以
var iPro Personer
var i Humaner
iPro := &Student{
"mike", 666}
iPro = i //err
i = iPro //success
i.sayHi()
}
空接口
空接口没有任何方法,任何方法都实现了空接口
当函数可以接受任意的对象实例时,我们会将其声明为interface{},最典型的例子是标准库fmt中PrintXXX系列的函数,例如:
func Println(args ...interface{})
func main() {
// 空接口万能类型,保存任意类型的值
var i interface{
} = 1 // 将int类型赋值给interface{}
}
类型查询
struct Student struct{
name string
id int
}
func main() {
i := make([]interface{
}, 3)
i[0] = 1
i[1] = "hello go"
i[2] = Student{
"mike", 666}
// 类型查询(通过if实现)
for index, data := range i {
// 第一个返回值,第二个返回判断结果的真假
if value, ok := data.(int); ok == true {
...
} else if value, ok := data.(string); ok == true {
...
} else if value, ok := data(Struct); ok == true {
...
}
}
// 类型查询(通过switch实现)
for index, data := range i {
switch value := data.(type) {
case int:
...
case string:
...
case Student:
...
default:
...
}
}
}
Go语言引入了一个关于错误处理的标准模式,即error接口,它是Go语言内建的接口类型
type error interface {
Error() string
}
func main() {
err1 := fmt.Errorf("%s", "this is normal err1")
err2 := errors.New("this is normal err2")
}
error接口应用
func MyDiv(a, b int) (result int, err error) {
if b == 0 {
err = errors.New("分母不能为0")
}else{
result = a/b
}
return
}
func main() {
result, err := MyDiv(10, 2)
if err != nil {
fmt.Println(err)
} else {
fmt.Println(result)
}
}
panic
当遇到不可恢复的错误状态的时候,如数组访问越界、空指针引用,这些运行时错误会引起panic异常。
panic("this is a panic test") // 显式调用panic
recover
recover必须放在defer
func test(x int){
defer func() {
//recover()
//fmt.Println(recover())
if err := recover(); err != nil {
fmt.Println(err)
}
}()
var a [10]int
a[x] = 111 // 当x为10的时候,导致数组越界
}
字符串操作
package main
import (
"fmt"
"strings"
}
func main() {
// "hellogo"中是否包含"hello",包含返回true,不包含返回false
fmt.Println(strings.Contains("hellogo", "hello"))
// Joins组合
s := []string{
"abc", "hello", "mike"}
buf := strings.Join(s, "@")
fmt.Println(buf)
//Index 查找子串的位置
fmt.Println(strings.Index("abcdhello", hello))
// Repeat 重复
buf = strings.Repeat("go", 3)
fmt.Println(buf) // "gogogo"
// Split 以指定的分隔符拆分
buf = "hello@abc@go@mike"
s2 := strings.Split(buf, "@")
fmt.Println(s2) // ["hello" "abc" "go" "mike"]
// Trim 去掉两头的空格
// Fields 去掉两头空格,并把元素放入切片中
}
字符串转换