GO语言基础梳理

常用命令

检查go语言版本:go version

go语言的环境信息:go env

go编译:go build main

go执行: go run main

注释

单行注释 //

多行注释 /* */

变量

变量的声明:var 变量名称 变量类型

变量的初始化:var 变量名称 变量类型 = 赋值

或者采用先声明后赋值

最常用的自动推导类型 :=

多重赋值 a,b,c := 1,2,3

匿名变量: 丢弃数据不进行使用 _ := 1

输出格式

标准库中的fmt.Print() 格式化为字符串后直接输出

fmt.Println() 自带换行符

fmt.Printf() 格式化字符串带占位符

接受输入

fmt.Scanf()使用这种写法 fmt.Scanf(“%d”,&age)

fmt.Scan()直接获取用户输入fmt.Scan(&age)

变量命名规范

必须以一个字母或者下划线开头,后面可以跟任意数量的字母,数字或下划线

go中的关键字

break default func interface select

case defer go map struct

chan else goto package switch

const fallthrough if range type

continue for import return var

true false iota nil

int

uint

float

bool byte rune string error

make len cap new append copy close delete

complex real imag

panic recover

recover函数没有任何参数,它会返回一个接口类型的值。如果当前的 goroutine 处于 panic 状态,那么recover将会终止该 panic 状态,并返回通过panic函数传递的值。如果当前的 goroutine 不是处于 panic 状态,那么recover将会返回 nil。在实际编程中,recover通常与defer结合使用,用于在程序遇到无法修复的错误时,保证程序能够正常退出并进行必要的清理工作。需要注意的是,recover只能在defer延迟函数中有效,无法在普通的函数中使用。 package main

import "fmt"

func main() {

defer func() {

if r := recover(); r != nil {

fmt.Println("捕获到了panic:", r)

}

}()

fmt.Println("开始处理")

doSomething()

fmt.Println("处理完成")

}

func doSomething() {

fmt.Println("进行一些操作")

panic("发生了一个严重错误")

fmt.Println("操作完成")

}

好的命名 :见名知意 驼峰命名法

基础数据类型

bool byte rune int uint float complex uintptr string

bool 类型真假

byte 字符类型单个字符 转义字符

string 字符串类型

格式化的输入输出

常量

定义常量用const 字面常量

iota枚举 自动++

运算符:算术运算符 优先级:先乘除后加减,有括号先算括号里的,相同级别的从左至右运算

类型转换

Go语言中不允许隐式转换,所有类型转换必须显式声明(强制转换),而且转换只能发生在两种相互兼容的类型之间

强制类型转换的语法格式:数据类型名(待转换的值)

赋值运算符: = += -= *= /= %=

关系运算符:== != < > <= >=

逻辑运算符:! && || & *

流程控制

顺序结构,选择结构,循环结构

if结构:一种的判断

if -else 结构适合:两种情况的判断

if -else if 结构适合: 多种情况判断,并且判断都是对区间中的数据进行判断

switch结构

switch-case 自带break

想执行完成某个case后,强制执行后面的case,可以使用fallthrough

循环结构:

for 表达式1;表达式2;表达式3 {

​ 循环体

​ }

表达式1:定义一个循环的变量,记录循环的次数

表达式2:一般为循环条件,循环多少次

表达式3:一般为改变循环条件的代码,使循环条件终有一天不再成立

跳转语句:break continue goto

循环嵌套:尽量不要多层嵌套,性能比较低

函数

传参:

func Test(a int, b int) {

fmt.Printf("a=%d,b=%d",a,b)

}

func main() {|

Test(3,5)

}

传不定个数参数列表:不定参数一定要放在最后

(a int,args …int)

在函数进行调用时,固定参数必须传值,不定参数可以根据需要来决定是否要传值.

函数的嵌套调用

函数的返回值:定义,返回多个返回值

函数类型:

将函数作为一种类型可以用来定义变量

func Test(a int, b int) (sum int) {

sum = a+b

return sum

}

type FuncType func(a int, b int) int

func main(){

var s int

var result FuncType

result = Test

s = result(3,6)

}

type关键字后面跟着类型的名字(FunType),FunType就是一个类型,那么FunType是一个函数类型,因为FunType后面跟着func(用来定义函数的),但是注意的是没有函数的名字.那么FunType是一个需要传递两个整型参数,有一个整型返回值的函数类型

既然函数类型类似于我们前面学习过的 int ,string 等类型,那么函数类型可以用来定义变量。

var result FuncType //表示定义了一个变量叫result,该变量的 类型是FuncType类型,而该类型是一个函数类型。

下面我们可以使用result这个函数类型的变量来调用函数了。

result=Test //将要调用的函数的名字赋值给result变量(也可以理解成将result变量指向了要调用的函数),这里要注意的是:第一:Test后面不能加括号。第二:函数类型变量result要和将要调用的函数Test保持一致,所谓一致就是我们定义的函数类型FuncType的变量result,只能调用参数是两个整型的,并且有一个返回值,而且也是整型的函数。那么Test函数完全满足要求。

现在已经完成了函数类型变量result指向了函数Test,那么我们可以使用函数类型的变量result调用函数:

result(3,6)//完成函数的调用。

函数的作用域:

局部变量 全局变量

匿名函数: 没有函数名字

匿名函数可以直接访问main()函数中的定义的局部变量,并且在匿名函数中对变量的进行修改是有效的

匿名函数的其他调用方法:(花活)

var num int

num = 9

f := func(){

num++

fmt.Println("niminghanshu :",num)

}

type FuncType func() //函数无参数无返回值与匿名函数相同

var f1 FuncType

f1 = f

f1()

fmt.Println("main函数",num)

实际上匿名函数只要在末尾加()即可实现调用

在括号中加入()参数即可实现传参

匿名函数最主要的功能就是实现了闭包

闭包:指有权访问另一个函数作用域中的变量的函数,就是在一个函数内部创建另一个函数

匿名函数(闭包),有一个很重要的特点:

它不关心这些捕获了的变量和常量是否有已经超出了作用域,所以只有闭包还在使用它,这些变量就会依然存在

延迟调用defer

代码逻辑越复杂,defer使用也越重要

同理,进行网络编程时,最后也要关闭整个网络的链接,也会用到defer

defer的执行顺序

一个函数中有多个defer语句,他们会以LIFO(后进先出)的顺序执行

defer+匿名函数

func f1() (r int) {

defer func(){

r++

}()

r = 0

return

}

func main() {

i := f1()

fmt.Println(i)

}

递归函数:

不断调用自身

工程管理

主要是go Modules之前

pkg 目录: pkg 目录用于存放编译后的包文件(.a 文件)。每个包都会被编译成一个独立的目录,该0目录的名称是包的导入路径。这些编译后的包文件可以供其他项目使用。src 目录: src 目录是存放源代码的主目录。每个项目都应该在 src 目录下创建一个对应的目录,并将项目的源代码放在其中。例如,如果你的项目是 github.com/user/myproject,那么你的项目目录结构应该是 $GOPATH/src/github.com/user/myproject。bin 目录: bin 目录用于存放可执行文件。当你使用 go install 命令构建项目时,生成的可执行文件会被放置在 bin 目录中。

———————已经废弃了解即可

复合类型

数组,指针,切片,结构体

数组

定义: var a [10]int

数组的长度只能是常量数组定义时要明确长度和类型

数组赋值: for循环,未赋值直接输出则全为0或空字符或false

数组初始化:先定义再赋值

b := [5]int{1,2,3,4,5}

数组逆置

将一个字符串数组的元素的顺序进行反转.第i个和第length-i-1个进行交换

names := [...]string{"wo","shi","haoren"}

var temp string

for i := 0;i

temp = name[i]

names[i] = names[len(names)-1-i]

names[len(names)-1-i] = temp

}

for i := 0;i

fmt.Printkn(names[i])

}

数组中的冒泡排序:

var num [10]int = [10]int{9,8,7,6,5,4,3,2,1,0}

var temp int

for i:= 0; i

if num[j] > num[j+1] {

temp = num[j]

num[j] = num[j+1]

num[j+1] = temp

}

}

for i := 0;i

fmt.Println(num[i])

}

数组作为函数参数

数组也可以像变量一样,作为参数传递给函数

二维数组

切片

切片:数组的plus版

数组定义完长度是固定的,切片与数组相比切片的长度是不固定的,可以追加元素的,在追加时可能使切片的容量增大

所以切片近似等于“动态数组”

切片的定义:s:= []int{}//定义空切片

切片的第二种定义: var s1 []int//声明切片和声明数组一样,只是少了长度,此为空切片

切片的第三种定义方式,通过make()函数实现 make(切片类型,长度,容量)

s := make([]int,5,10)

切片的优势:切片的长度和容量是可以不固定的

什么是切片的长度与容量:

长度是已经初始化的空间,容量是已经开辟的空间,包括已经初始化的空间和空闲的空间

len()和cap()函数

for k,v := range s {

}//循环方式

切片的截取

s[low:high:max]

high不包含该位置

cap = max-low 用第三个数减去第一个数

s[n]切片s中索引位置为n的项

s[:]从切片s的索引位置0到len(s)-1处所获得的切片

s[low:]从切片s的索引位置到len(S)-1处所获得的切片

无默认为0 len(s)-1

append函数末尾追加超过容量,以2倍容量扩容

copy函数使用 copy(切片1,切片2)

copy容量不同,拷贝小容量其他不变

在函数传递中,数组作为参数进行传递是值传递,而切片作为参数进行传递是引用传递.

Map

字典结构就是有键和值构成层的

字典中的键是不允许被重复的

字典结构定义:

map [keyType] valueType

map关键字,[]中指定的是键(key)的类型,后面是值的类型

键的类型,必须是支持==和! =操作符的类型,切片,函数以及包含切片的结构类型不能作为字典的键,使用这些类型会造成编译错误:

字典中不能使用cap函数,只能使用len()函数.len()函数返回map拥有的键值对的数量

map是无序的,我们无法决定它的返回顺序

直接初始化

m := map[int]string{1:"mike",2:"go",3:"c++"}

打印字典中的值,通过循环遍历的方式输出

for key,value := range m{

}

删除delete(m,1)

map也是引用传递

结构体

定义:

type Student struct {

id int

name string

sex byte

age int

addr string

}

type 名字 struct 定义一个结构体

结构体的初始化:在初始化时,值的顺序与结构体成员的顺序保持一致

结构体定义完成后,结构体的使用

var s Student

s.id = 1001

两个结构体可以用 == 或 != 运算符进行比较

同类型的两个结构体变量可以相互赋值

s3 := Student{2,"mike","m",18,"bj"}

var tmp Student

tmp = s3

fmt.Println("tmp = ",tmp)

结构体数组: 把结构体看作是数组的一个元素

students := []Student{

Student{

101,

"张三",

100,

},

}

结构体作为函数参数

是值传递

指针

变量内存与地址

存储数据的方式,可以通过变量,或者复合类型中的数组,切片,Map,结构体

使用变量存储数据还是使用复合类型存储数据,都有两层含义

存储的数据(内存),对应的地址。

var i int

i = 100

fmt.Printf("i=%d\n",i)

fmt.Printf("&i = %v\n",&i)

第一个输出,实际上就是输出内存中存储的数据

定义一个变量,就是在内存中开辟一个空间,用来存储数据

第二个输出,实际上是输出变量i在内存中的地址

指针变量

可以通过指针变量来存储,所谓的指针变量:就是用来存储任何一个值的内存地址

指针变量的定义:

var i int = 100

var p *int

p = &i

指针变量的P的定义是通过这个符号来定义,指针变量p的类型为Int,表示存储的是一个整型变量的地址

p = &i 的意思是将变量i的 地址取出来,并且赋值给指针变量p,也就是指针变量p 指向了变量i的存储单元

指针变量p存储的是变量i的地址

var i int = 100

var p *int

p = &i

*p = 80

*p的作用就是根据存储的变量的地址,来操作变量的存储单元(包括输出变量存储单元中的值,和对值进行修改)

指针变量默认值为nil

不要操作没有合法指向的内存

new()函数

指针变量,除了以上介绍的指向以外还可以通过new()函数来指向

var p *int

p = new(int)

*p = 57

fmt.Println(*p)

new(int)的作用就是创建一个整型大小(4字节)的空间

然后让指针变量P指向了该空间,所以通过指针变量p 进行赋值后,该空间中的值就是57

new()函数的作用就是C语言中的动态分配空间但不需要手动释放,GO语言会自动释放

q := new(int)

*q = 787

指针做函数参数

指针传递时是引用传递

数组指针

p *[5]int

一个指针指向数组,然后可以通过该指针来操作数组

指针数组

指针数组指的是一个数组中存储的都是指针(也就是地址),也就是一个存储了地址的数组

指针数组定义方式: var p [2]*int

结构体指针变量

type Student struct {

id int

name string

score float64

}

func main() {

var p *Student = &Student{1,"zhangsan",90}

}

也可以使用自动推导类型

p1 := &Student{1,"zhangsan",90}

用结构体作为函数的参数,默认传的是值传递,通过结构体指针实现结构体的引用传递

面向对象

go语言的面向对象没有类

可以将结构体比作为类,因为在结构体中可以添加属性(成员)方法函数

继承

所谓继承指的是,我们可能会在一些类(结构体)中,写一些重复的成员,我们可以将这些重复的成员,单独封装到一个类(结构体)中,作为这些类的父类

通过匿名组合来实现继承效果

type Person struct{

id int

name string

age int

}

type Student struct {

Person

score float64

}

以上代码通过匿名字段实现了继承,将公共的属性封装在Person中,在Student中直接包含Person,那么Student中就有Person中的所有成员,Person就是匿名字段。注意Person匿名字段,只有类型,没有名字。

那么接下来我们就可以给Student赋值了,具体初始话的方式如下:

func main() {

//顺序初始化

var s1 Student = Student{Person{101,”mike”,18},98.5}

}

自动推导类型:

s2 := Student{Person{102,"zhangsan",18},90}

指定初始化成员

s3 := Student{score:97}

//未赋值的自动赋值为0

对Persoon中指定的成员进行初始化

s4 := Student{Person:Person{name:"mike},score:100}

成员操作

创建完成对象后,可以根据对象来操作对应成员属性

根据结构体可以创建出很多的对象,这些对象的成员属性是一样的,但是成员(属性)的值是可以完全不一样的

同名字段

现在将Student结构体与Person结构体,进行如下修改:

type Person Struct {

id int

name string

age int

}

type Student struct {

Person

name string

score float64

}

就近原则赋值赋给student而不是它的父类

指针类型匿名字段

结构体中的匿名字段的类型也可以是指针

type Person struct {

id int

name string

age int

}

type Student struct {

*Person

score float64

}

func main() {

var s Student

s.Person = new(Person)

s.id = 103

}

new()的作用就是分配空间,对s进行初始化,只给s分配了空间,Person还是没有空间,因此需要用new也给Person进行分配、

多重继承

与前面的操作是一样的

尽量在程序中,减少多重继承,否则会增加程序的复杂度

封装

通过函数来实现封装

函数的作用就是将重复的代码封装起来,用的时候直接调用就可以了

在面向对象中可以通过属性和方法(函数)来描述对象

方法可以理解成就是函数

定义:

//函数的定义:

func Test(a,b int)int {

return a+b

}

//方法的定义

type Interger int//为Int 定义别名integer

func (a Integer) Test(b Interger) Integer {

return a+b

}

func main() {

var result Integer = 3

r := result.Test(3)

fmt.Println(r)

}

type Interger int表示的意思是给int类型指定了一个别名叫Integer,

指定别名后,后面可以用interger 来代替int来使用

func (a Integer) Test(b Integer) Integer {

}

表示定义了一个方法,方法的定义与函数的区别:

第一: 在关键字后面加上(a Integer ),这个在方法中称之为接收者,所谓的接收者就是接收传递过来的第一个参数,然后复制a,a 的类型是integer,由于integer是int的别名,所以a 的类型为int

第二:在表示参数的类型时,都使用了对应的别名

通过方法的定义,可以看出方法其实就是给某个类型绑定的函数,在该案例中,是为整型绑定的函数,只不过在给整型绑定函数(方法)时,一定要通过type来指定一个别名,因为int类型是系统已经规定好了,无法直接绑定函数,所以只能通过别名的方式

第三:调用方式的不同

var result interger = 3

表示定义一个整型变量result,并赋值为3

通过result变量,完成方法的调用,因为Test()方法,是为int类型绑定的函数,而result变量为int类型,所以可以调用Test()方法。result变量的值会传递给Test()方法的接收者,也就是参数a。而实参Test(3),会传递形参b

当然我们也可以将Test()方法,理解成是为int类型扩展了,追加了的方法,因为系统在int类型时,是没有改方法的

方法就是函数的语法糖

在以上案例中,Test()方法是为int类型绑定的函数,所以任何一个整型变量,都可以调用该方法

var sum Integer = 6

t := sum.Test(8)

给结构体添加方法

type Student struct {

id int

name string

age int

score float64

}

func (stu student) PrintShow() {

fmt.Println(stu)

}

func main() {

s := Student{101,"zhangsan",19,90}

s.PrintShow

}

给结构体添加方法的方式与前面给int类型添加方法的方式,基本一致。唯一不同的是,不需要给结构体指定别名,因为结构体Student就相当于其所有成员属性的别名,所以这里不要再给结构体Student创建别名

调用方式:根据结构体(类)创建的对象,完成了方法的调用

PrintShow()方法的作用,只是将结构体的成员(属性)值打印出来,如果要修改其对应的值

应该使用指针作为方法的接收者,修改成对应的指针类型

type Student struct {

id int

name string

age int

score float64

}

func (stu Student) PrintShow() {

fmt.Println(stu)

}

func (p *Student) EditInfo (id int,name string,age int,score float64) {

p.id = id

p.name = name

p.age =age

p.score = score

}

func main() {

var stu Student//定义一个结构体类型的变量

(&stu).EditInfo(102,"list",19,88)//将结构体的指针传递给接收者,同时将要修改的数据传递到方法中

stu.Printshow()

}

在创建方法时,接收者类型为指针类型,所以在调用方法时,创建一个结构体变量,同时将结构体变量的地址,传递给方法的接收者,然后调用EditInfo()方法,完成要修改的数据的传递

使用方法时要注意:

只要接收者类型不一样,这个方法就算同名,也是不同的方法,不会出现重复定义函数的错误。但是如果如果接受者类型一样,但是方法的参数不一样,是会出现错误的。也就是说GO方法中没有方法重载 接受者不能为指针类型 type long int

func (tmp *long) test02() {

}

以上是正确的 但不可以是下面这种 type pointer *int

//pointer为接收者类型,它本身不能是指针类型

func (tmp pointer) test() {

}

指针变量的方法值

接受者为普通变量,非指针,值传递

func (stu student) Printshow(id int,name string,age int,score float64) {

stu.id = id

stu.name = name

stu.age = age

stu.score = score

}

func main() {

var stu Student//定义一个结构体类型的变量

stu.PrintShow(106,"laowang",26,99)

}

接收者为指针变量,引用传递

func (p *Student) EditInfo (id int,name string,age int,score float64) {

p.id = id

p.name = name

p.age = age

p.score = score

}

func main() {

var stu Student//定义一个结构体类型的变量

(&stu).EditInfo(102,"list",19,88)

}

定义了两个方法,一个是Printshow(),该方法的接受者为普通方法,一个EditInfo()方法,该方法的接收者为指针变量

定义一个结构体指针变量,能否调用PrintSho()方法

type student struct {

id int

name string

age int

score float64

}

func (stu Student) PrintShow(id int,name string,age int,score float64) {

stu.id = id

stu.name = name

stu.age = age

stu.score = score

}

func (p *Student) EditInfo(id int,name string,age int,score float64) {

p.id = id

p.name= name

p.age = age

p.score = score

}

func main() {

var stu Student//定义一个结构体变量

(&stu).Printshow(101."zhangsan",19,20)

($stu).EditInfo(102,"list",20,67)

}

结构体指针变量也可以调用普通方法

原因: 先将指针stu,转换成*stu在调用

等价于

(*(&stu))

如果结构体变量是一个指针变量,他所能调用的方法就是一个集合,简称方法集

普通的结构体变量也能调用EditInfo方法

type student struct {

id int

name string

age int

score float64

}

func (stu Student) PrintShow(id int,name string,age int,score float64) {

stu.id = id

stu.name = name

stu.age = age

stu.score = score

}

func (p *Student) EditInfo(id int,name string,age int,score float64) {

p.id = id

p.name= name

p.age = age

p.score = score

}

func main() {

var stu Student//定义一个结构体变量

stu.EditInfo(109,"laowang",29,99)

}

将普通结构体类型的变量转换成(&stu)在调用EditInfo()方法

这样的好处是非常灵活,创建完对应的对象后,可以随意调用方法,不需要考虑指针问题

方法继承

type Person struct {

name string //名字

sex byte //性别,字符类型

age int

}

//Person类型,实现了一个方法

func (tmp *Person) PrintInfo() {

fmt.Pritnf

}

//有个学生,继承Person字段,成员和方法都继承了

type Student struct {

Person //匿名字段

id int

addr string

}

func main() {

s := Student{

Person{

"mike","m",18

},

666,"bj"

}

}

s.PrintInfo()

结构体可以继承父类中的方法

方法重写

如果子类中的方法名与父类中的方法名同名,在调用的时候是先调用子类(结构体)中的方法

接口

接口就是一种规范和标准

接口告诉开发人员需要实现什么功能

接口定义的语法:

//定义接口类型

type Humaner interface {

//方法,只有声明,没有实现,由别的类型(自定义类型)实现

sayhi()

}

type Student struct{

name string

id int

}

//Student实现了此方法

func (tmp *Student) sayhi() {

fmt.Printf()

}

type Teacher struct{

addr string

group string

}

//Teacher实现了此方法

func (tmp *Teacher) sayhi() {

fmt.Println()

}

func main(){

//定义接口类型的变量

var i Humaner

//只是实现了此接口方法的类型,那么这个类型的变量(接收者类型)就可以给I赋值

s := &Student("mike",666)

i = s

i.sayhi()

t := &Teacher{"bj","go"}

i = t

i.sayhi()

}

多态 多态就是多种表现形式

使用接口实现多态的方式如下:

//定义接口类型

type Humanner interface {

//方法,只有声明,没有实现,由别的类型(自定义类型)实现

sayhi()

}

type Student struct {

name string

id int

}

type Teacher truct {

addr string

group string

}

func (tmp *Student) sayhi() {

//Student实现了此方法

}

func (tmp *Teacher) sayhi() {

//Teacher实现了此方法

}

//定义一个普通函数,函数的参数为接口类型

//只有一个函数,可以有不同的表现,多态

func WhoSayHi(i Humaner) {

i.sayhi()

}

func main() {

//只是实现了此接口方法的类型,那么这个类型的变量(接收者类型)就可以给i赋值

s := &Student{"mike",666}

t := &Teacher{"bj","go"}

WhoSayHi(

s)//调用同一函数

WhoSayHi(t)

}

体会接口和多态

package main

import "fmt"

type CarTyper interface {

GetCar()

}

type CarStore struct {

}

func (c *CarStore) Order(money float64, carType string) {

switch carType {

case "BMW":

CarWho(&BMWCar{Car{carType, money}})

case "Audi":

CarWho(&AudiCar{Car{carType, money}})

}

}

type Car struct {

carType string

money float64

}

func (p *Car) Move() {

fmt.Println(p.carType + "汽车移动")

}

func (p *Car) Stop() {

fmt.Println(p.carType + "汽车停止")

}

type BMWCar struct {

Car

}

func (p *BMWCar) GetCar() {

if p.money >= 60000 {

p.Move()

p.Stop()

} else {

fmt.Println("钱不够,无法买宝马!!")

}

}

type AudiCar struct {

Car

}

func (p *AudiCar) GetCar() {

if p.money >= 70000 {

p.Move()

p.Stop()

} else {

fmt.Println("钱不够,无法买奥迪!!")

}

}

func CarWho(i CarTyper) {

i.GetCar()

}

func main() {

var carStore CarStore

carStore.Order(30000, "Audi")

}

空接口不包含任何的方法,正因为如此,所有的类型都实现了空接口,因此空接口可以存储任意类型的数值

异常处理

error接口:

Go语言引入了一个关于错误处理的标准模式

panic函数

返回的是让程序奔溃的错误

recover函数

让从当前程序从panic的状态中恢复并重新获得流程控制权

recover只有在defer调用的函数中有效

标准库

推荐链接

评论可见,请评论后查看内容,谢谢!!!
 您阅读本篇文章共花了: