Golang基础二
Golang基础复习
1.Go-常用命令
直接再终端输入 go help 即可显示所有的go命令以及相应命令功能简介,主要有下面这些。
- build: 编译包和依赖,生成.exe
go build xxx.go ->./hello.exe💥 - clean:移除对象文件
- doc:显示包或者符号的文档
- env:打印go的环境信息
- bug:启动错误报告
- fix : 运行go tool fix
- fmt: 运行gofmt进行格式化
- generate: 从processing source生成go文件
- get: 下载并安装依赖
go get xxxx💥 - install 编译并安装包和依赖
go list - list: 列出包
- run: 编译并运行go程序 💥
- test: 运行测试
go run xxx.go - tool: 运行go提供的工具
- version: 显示go的版本
- vet: 运行go tool vet
Go语言的所有包全在这里搜索: pkg.go.dev/
(1).添加依赖-(配置代理)
go env -w GO111MODULE=on
go env -w GOPROXY=https://goproxy.cn #一定要添加代理,要不然
go get xxxx
实战演练:
PS C:\Environment\GoWorks\src\demo> go get github.com/go-sql-driver/mysql
go: added github.com/go-sql-driver/mysql v1.7.0
添加完毕之后会在 go.mod中新增
2.创建一个项目的步骤
- 创建项目
- 初始化项目
go mod init 项目名
- 创建包
- 创建模块
创建一个包,然后下面放入一些.go文件
- 相互调用
test包下hello.go文件
方法名开头大写就是公有方法。小写就是私有方法
package test
// 方法名为小写的话,那么就是私有的
func hello() string {
return "hello"
}
// 方法名为大写的话,那么就是公用
func Hello() string {
return "Hello"
}
主项目下创建一个main.go文件。通过包名进行调用方法
package main
import "demo/test" //导入包
func main() {
// 注意我们这里用的是 包名. 进行调用的,因为Go语言中没有类的概念,
println(test.Hello()) //调用共有的,私有的调用不了
}
3.golang标识符、关键字、命名规则
(1).标识符
标识符的英文是: identifer.通俗的讲:就是给变量、常量、函数、方法、结构体、数组、切片、接口起名字。
标识符的组成
- 标识符只能由数字、字母和下划线组成。
- 只能以字母和下划线开头
- 标识符区分大小写
(2).关键字
25个关键字
(3).命名规则
- 包名称:
保持package的名字和目录一致,尽量能够见名识意。包名应该小写单词,不要使用下划线或则混合大小写。
package demo
- 文件命名:
见名识意应该为小写单词,使用下划线分割每一个单词
user_mapper.go
- 结构体命名
采用驼峰式命名,首字母根据公有私有访问控制大写或者小写
type student struct{} //私有结构体
type Student struct{} //公有结构体
- 接口命名
采用驼峰式命名,首字母根据公有私有访问控制大写或者小写。单个函数的结构名以 “er” 作为后缀。
type Reader interface{
Read(p []byte)
}
- 变量命名
采用驼峰式命名,首字母根据公有私有访问控制大写或者小写。但遇到特有名字的时候,要遵循以下的规则:
如果变量为私有,且特有名词为首个单词,则使用小写,如appService若变量类型为bool类型,则名称应为Hash,is,Can或Allow开头。
var isExist bool
var hasConfict bool
var canMange bool
var allowGitHook bool
- 单元测试
单元测试文件命名规范为 example_test.go。测试用例额的函数名必须以Test开头。例如: TestExample。
4.变量
- 变量的定义
var identify type
---
var(
idetify type
idetify type
)
--- 语法糖,这里只能在函数的内部我们才能使用,函数外部不能使用!!!
identy:=value
---
- 变量的初始化
var identify type=value
---
var(
identify type=value
)
---
// 类型会对其进行自动寻找,可以不用可以设置变量的类型的
var identify1,identify2,identifyN =value1,value2,value3
---
// 函数返回多个值时
identify1,identify2=function3()
---
// 匿名变量
_,identify=function3()
5.常量
- 常量的定义且赋值
const identify type=value
常量一旦定义之后,我们就不能对其进行修改的操作了。
---
const(
identify type=value
)
- iota
iota比较特殊,可以被认为是一个可被编译器修改的常量,它默认开始的值是0,每调用一次加1.遇到const关键字的时候被重置为0.
1. 连续会一直增加
const(
a1=iota //0
a2=iota //1
a3=iota //2
)-> 0 1 2
2.使用匿名变量跳过某些值
const(
a1=iota //0
_
a2=iota //2
)-> 0 2
2. 连续的时候被打断
3. const(
a1=iota //0
a2=100 //100 1
a3=iota //2
)->0 100 2
6.数据类型
字符串、数字型、布尔型、数组、切片、接口、函数、管道、结构体。
(1).字符串类型
Go语言中的字符串是被双引号括起来的值。被反引号`引起来的字符串用来创建多行的消息,HTML以及正则表达式.
%T 查看类型 %c 查看字符 %v 万能取值 %s 取字符串
1. 字符串-双引号
var identify string="value"
var identify=""
identify:=""
2. 字符串-反引号
var identify string=`
line1
line2
`
3.字符串的拼接可以使用+号进行拼接
4.可以使用printf进行格式化进行拼接
5.使用strings.Join([]string{identify1,identify},",")
6.也可以使用bytes.Buffer进行字符串链接 (效率较高)
var buffer bytes.Buffer
buffer.WriteString("hello")
buffer.WriteString(",")
buffer.WriteString("jsxs")
s := buffer.String()
7. 转义字符
\n ->换行 \t ->4个空格 \\ ->网络路径
8.字符串切片操作
var identify="xxxxx"
| 获取->m-n的字符(包头不包尾) identify[m:n]
| 获取->m- 到最后一个的字符 identify[m:]
| 获取-> -n 从开始到n的字符 identify[:n]
9.获取字符串的长度 len(str)
10.字符串进行切割 strings.Split(str,"切割字符")->输出的是一个数组
11.是否包含字符串 strings.Contains(str,"xxx") -》bool
12.strings.ToLower(str) ->全部小写 strings.ToUpper(str) ->全部大写
13.strings.HahsPrefix(str,"xx")->是否以xx开头 string.HashSuffix("xxx")->是否以xx结尾
14. 查看某个字符串所在的索引位置 strings.Index(str,"xx")
(2).指针类型->引用类型
Go语言中的函数传参都是值拷贝,当我们想要修改某一个变量的时候,我么可以创建一个指向该变量的地址的指针变量。传递数据使用的指针,而无需拷贝数据。
类型指针不能进行偏移和运算。
Go语言中的指针操作非常简单,只需记住两个符号: &(取地址)和 *(根据地址取值)
指针地址和指针类型
每个变量在运行时都有一个地址,这个地址代表变量在内存中的位置。Go语言中使用 & 字符放在变量前面对变量进行取地址操作。Go语言中的值类型(int、float、bool、string、array、struct) 都有对应的指针类型,如" *int 、 *int64、 *string等
var identify *type=&idenify
指针地址和指针类型
一个指针指向了一个值得内存地址。(也就是我们声明了一个指针之后,可以像变量赋值一样,把一个值的内存放入到指针当中)
类似于变量和常量,在使用指针前我们需要声明指针。
1.指针的定义语法
var var_name *var_type
package main
import "fmt"
func main() {
var ip *int
fmt.Printf("%T\n", ip) //*int
fmt.Printf("%v\n", ip) //<nil>
var num int = 10 //创建一个变量,也就是分配了一个地址
fmt.Printf("%T\n", &num) //查看取地址的类型
ip = &num // ip指向num的地址
fmt.Printf("%v", *ip) //获取值
}
- 指向数组的指针
需要借助数组进行遍历取数组的地址
1.定义语法
var ptr [n]*int;
2. 指针数组的取地址需要通过遍历的方式进行遍历赋值。
package main
import "fmt"
func main() {
var ip [4]*int
fmt.Printf("%T\n", ip)
fmt.Printf("%v\n", ip)
num := [4]int{1, 2, 3}
fmt.Printf("%T\n", &num)
for i := 0; i < len(num); i++ {
ip[i] = &num[i] // 数组指针的赋值
fmt.Printf("%v\t", *ip[i])
}
}
(3).数组类型
数组是相同数据类型的一组数据的集合,数组一旦定义长度就不能修改,数组可以通过下标(或者索引)来访问元素。
1.数组的定义
var identify [n]type=[n]type{value}
2.假如说定义的数组长度没有超过定义的长度,那么就补空
3.数组的初始化,用"..."可以不用设置固定长度,会自动根据我们添加的值进行分配空间
var identify=[...]int{xxx}
4.可以指定索引赋值 arr := [...]int{索引:value,索引:value}
arr := [...]int{0: 1, 2: 2, 5: 3}
for _, i2 := range arr {
println(i2)
}
-> 1 0 2 0 0 3
5.数组的值可以被修改,不像字符串那样不可以被修改。
6.获取数组的长度 len(identify)
7.数组的遍历: 普通for 增强for
-----------
(4).切片类型 -> 引用类型
- 切片的定义和初始化
切片同属的讲:就是一个动态数组,增加了自动扩容的功能。切片是一个拥有相同类型元素的可变长度的序列。
1.切片的定义: 声明一个数组只要不添加长度即可
(1). var identify []type=[]type{}
(2). identify []type
(3). identify:=[]type{}
2.切片是引用类型,可以使用make函数来创建切片。
var slice []type=make([]type,len)
可以简写为:
slice:=make([]type,len)
make([]T,len,capacity) ->这里的len是数组的长度并且是切片的初始长度
3.获取长度 len(str) csp(str)
-------------------------------------
4.切片的初始化->和数组的初始化一样
identify:=[]type{xxxx}
5.切片的初始化使用数组->可以先设置一个数组然后切片切取
s:=[3]int{1,2,3} -> slice:=s[:]
6.切片的遍历 和数组的遍历一样 for 和 增强for
切片是一个动态数组,可以使用append()函数添加元素,go语言中并没有删除元素的专用方法,我们可以使用切片本身的特性来删除元素,由于,切片是引用类型,通过赋值的方式,会修改原有的内容,go提供copy()函数来拷贝切片
- 切片的CRUD
make() 用于分配地址
1.添加元素
append(数组,添加的元素)
(1).添加到元素的尾部
var str = []int{}
ints := append(str, 10, 20, 30) //进行添加的操作
(2).添加到指定元素的后面
---- 整形指定位置插入
func insertByIndex(index int, value int, slice []int) []int {
var str2 = make([]int, len(slice))
copy(str2, slice) //把ints拷贝给str2
str3 := []int{} //定义一个空切片
str3 = append(slice[:index], value) //先获取头部
str3 = append(str3, str2[index:]...)
return str3
}
----------
2.删除元素
(1).利用空切片进行删除的操作 ints->是一个数组,删除索引为1的值
var slice []int = []int{}
intss := append(slice, ints[:1]...)
intss = append(intss, ints[2:]...)
fmt.Printf("%d", intss)
(2).直接追加的方式删除 A=append(a[:index],a[index+1]...)
ints = append(ints[:1], ints[2:]...)
fmt.Printf("%d", intss)
3.修改 ->
直接 slice[index]=?
4.查找
(1).通过下标进行寻找 -> slice[index]
(2).通过值进行查找索引 ->遍历
----------------------------
5. copy()函数
(1).如果是直接赋值的话,那么内存地址一样,修改s2的时候同一个地址的s1也会被修改
直接赋值: ---->s1:=s2
(2).如果利用copy()函数的话,那么内存地址不一样。修改s2那么s1不会改动
基本步骤:
先分配给s1地址-> var s2=make([]int,4) //切片类型,四个空间
开始拷贝 copy(s1,s2) -> 意思就是把s2拷贝给s1
(5).函数类型 ✅
函数在go语言中属于 一级公民 ,我们把所有的功能单元都定义在函数中,可以重复使用。函数包含函数的名称、参数列表和返回值类型,这些构成了函数的签名。
1 Go语言中函数的特性
- go语言中有3中函数:
普通函数,匿名函数,方法(定义在struct上的函数) - Go语言中
不允许函数重载,也就是说不允许函数同名 - Go语言中的
函数不能嵌套函数,但可以嵌套匿名函数 - 函数
可以作为参数传递给另一个函数 - 函数的返回值可以是一个
函数 - 函数是一个值,
可以将函数赋值给变量,使得这个变量也成为函数 - 函数调用的时候,如果
有参数传递给函数,则先拷贝参数的副本,再讲副本传递给函数 函数参数可以没有名称。
- Go语言函数的定义
1. 定义一个普通函数
func function_name([parameter list] type) [return_type]{
函数体
}
- 函数的返回值
函数可以有0个或则多个返回值,返回值需要指定数据类型,返回值通过return关键字来指定。
- return 关键字中指定了参数时,返回值可以不用名称。如果 return 省略参数,则返回值部分必须带名称。
- 当返回值有名称时,必须使用括号包围,逗号分隔,即使只有一个返回值
- 即使返回值重名了,return中也可以强制指定其他返回值的名称,也就是说return的优先级更高。
- 命名的返回值时预先声明好的,在函数内部可以直接使用,无需再次声明。命名返回值的名称不能和函数参数名称相同,否则报错提示变量不能重复定义。
- return 中可以有表达式,但不能出现赋值表达式,这和其他语言可能有所不同。列如 return a+b是正确的,但return c=a+b是错误的。
1. 没有返回值
func test1(){}
2. 有参数且返回值有名字
func test(a int) (sum int){ sum=1+a return sum}
3.有参数但返回值没有名字
func test(a int) (int){sum:=1+a return sum}
4有参数且有多个返回值
func test()(sum int,sum2 int){return sum,sum2}
5.覆盖命名返回值
func test()(a int){return sum} ->sum会覆盖a
6.如果返回值不想要,可以用匿名变量进行接受
- Go函数的参数
- go函数可以有0个或多个参数,且参数需要指定数据类型
- 声明函数时的参数列表叫做形参,调用时传递的参数叫做实参
- go语言的函数是通过值传递进行传递的,意味着传递给函数的参数是一个拷贝的副本,形参改变实参不会变化。
- go语言的函数可以使用可变长参数,有时候并不能呢确定参数的个数。
1. 可变参数 ->可变参数只能放在最后且只有一个
func test(args...int){
函数体
}
- 函数类型和函数变量
可以使用type 关键字来定义一个函数类型
1.定义一个函数类型的f1,f1是一个类型并不是一个变量名
type f1 func(int,int) int
2.相同类型的函数赋值给函数变量
package main
import "fmt"
func test(a int) int {
return a
}
func main() {
type f1 func(int) int // 这里的就是定义一个f1这样的类型
var ff f1 //定义f1类型的变量ff
ff = test // 把test变量赋值给ff变量
fmt.Printf("%v", ff(2)) //开始调用
}
---------------------**********-------------------------
6.高阶函数
go语言的函数,可以作为函数的参数,传递给另外一个函数,可以作为另外一个函数的返回值返回,高阶函数就是参数类型带函数参数的函数。
1.go语言函数作为参数
package main
func test(a int) int {
return a
}
func test2(b int, f func(int) int) int {
return b + f(b)
}
func main() {
println(test2(1, test))
}
-------------------
7.匿名函数
go语言函数不能嵌套,但是在函数内部可以定义匿名函数,实现以一下简单功能的调用。所谓匿名的函数就是: 没有名称的函数。
语法格式如下:
1.匿名函数的调用和执行
func(){函数体}()
-------
// 直接执行
func() {
println("您好,我是小明")
}()
2.匿名函数设置变量名 max,只有匿名函数才能设置变量名。
// 给匿名设置一个变量名
max := func(a int, b int) {
if a > b {
fmt.Printf("%v", a)
} else {
fmt.Printf("%v", b)
}
}
max(1, 2)
- 闭包
闭包可以理解成: 定义在一个函数内部的函数。在本质上,闭包是将函数内部和函数外部链接起来的桥梁。或者说是函数和其引用环境的组合体。
闭包指的是一个函数和与其相关的引用环境组合而成的实体,简单说,闭包+函数+引用环境。
闭包: 函数内部定义一个函数,且外层函数的返回值类型是内层函数的函数类型。特性: 内层函数可以调用外层变量。外层变量一直在内层函数的作用域里面
外层变量的作用域扩展至内层函数中。前提是函数没有再次被从新分配空间
package main
import "fmt"
func test(a, b int) func(c, d int) int {
sum1 := a + b //这里是闭包,它只要函数没有再次被定义,那么他的作用域就被内层函数所包含
sum := func(c, d int) int {
return sum1 + c + d
}
return sum //返回值是内层函数类型
}
func main() {
f := test(1, 2) //赋值变量类型
fmt.Printf("%T\n", f)
fmt.Printf("%v", f(1, 2))
}
- 递归
函数内部调用函数自身的函数称为递归函数。
递归函数的最重要的三点:
- 递归是自己调用自己
- 必须先定义一个函数的退出条件,没有推出条件,递归将称为死循环
- go语言递归函数很可能会产生一大堆的栈溢出问题,也很可能会出现占空间内存溢出问题。
package main
func test(a int) int {
if a == 1 { //直接返回这个值,不会再向下执行了。
return 1
}
return a + test(a-1)
}
func main() {
println(test(5))
}
- defer 语句
Go语言中的defer语句会将其后面跟随的语句进行延迟处理。在defer归属的函数即将返回时,即延迟处理的语句按defer定义的逆序进行执行,也就是说: 先被defer的居于最后执行,最后defer的语句最先执行。
defer特性:
- 关键字defer用于注册延迟调用
- 这些调用直到return前才被执行,因此,可以用来做资源清理。
- 多个defer语句,按先进后出的方式执行。
- defer语句中变量,在defer声明就决定了。
1.如何定义一个defer语句
defer 语句[可以是输出语句/特殊函数]
2.如果有多个defer会按照先定义后执行的顺序进行处理
3.defer,只是进行了延迟的处理,但里面的参数在声明的时候就已经配置好了
- init函数
Go语言有一个特殊的函数 init 函数,先于main 函数执行,实现包级别的一些初始化操作
init函数的特点
- init先于main函数 自动执行,不能被其他函数所调用
- init函数没有输入参数、返回值
- 每个包可以有多个init函数
- 包的每个源文件也可以有多个init函数,这点比较特殊
- 同一个包的init执行顺序,Go没有明确定义,编程时要注意程序不要依赖这个执行顺序
- 不同包的init函数按照包导入的依赖关系决定执行顺序
GO初始化顺序
初始化顺序: 变量初始化->init()->main()
1. init函数的定义
func init() { //没有参数、没有返回值 不能被调用
函数体
}
2.go语言的执行顺序 | 变量初始化->init()->main()
package main
func init() { //没有参数、没有返回值
println("init........") // ->2
}
var value int = test() //变量的初始化 ->1
func test() int {
println("初始化数据....")
return 100
}
func main() {
println("main....") // ->3
}
3.在一个包中可以定义多个init()函数,执行顺序是从上到下
(6).布尔类型
var identify bool=flag
identify:=flag
(7).数字类型
Go语言支持整数和浮点型数字,并且原生支持复数,其中位的运算采用补码。
1.整数类型
有符号(xxx)、无符号(uxxx)。
%d ->十进制取值
%b->二进制取值
%o->八进制取值
%x/%X->十六进制
2.浮点型
%f ->取值
%.nf%->保留n位小数
(8).map类型 -> 引用类型
map是一种 key:value 键值对的数据结构容器。map内部实现是哈希表(hash)。map最重要的一定是通过key来快速检索数据,key类似于索引,指向数据的值。map是引用类型的。
map的定义与初始化
可以使用内建函数make, 也可以使用map关键字来定义map
1. 使用map来定义
var 变量名称 map[key的数据类型]value的数据类型
2. 使用make函数
变量名称:=make(map[key的数据类型]value的数据类型)
(1).两种初始化方式以及两种定义方式
var m1 map[string]string // 利用map定义一个map 初始化为空
m2 := make(map[string]string) //利用make定义一个map 初始化为空
var m3 = map[string]string{"name": "李明", "age": "18", "high": "1.75"} //定义并初始化数据
var m4 = map[string]string{"age": "18"} //定义并初始化
*******************************
3.通过key值进行查找数据
变量名[key]
fmt.Printf("%v", m1["name"])
4.判断某一个key是否存在💥
v,ok:=变量名[key] v是value ok是布尔类型
v, ok := m1["name"]
map的遍历
1. 可以只获取key
2. 可以获取key和value
3. 可以只获取value
for key,value:=range 变量名{}
-----
m1 = map[string]string{"name": "李明", "age": "18", "high": "1.75"} //初始化数据
for k := range m1 {
fmt.Printf("%v\t", k)
}
for k, v := range m1 {
fmt.Printf("%v %v ", k, v)
}
for _, v := range m1 {
fmt.Printf("%v\t", v)
}
(9).结构体类型
- 类型定义和类型别名
1. 类型定义语法
type NewType Type -》等同于Type
------------测试使用
package main
func main() {
// 类型定义->实际上就是说 MyInt 等同于 int
type MyInt int
// i 为MyInt类型的变量,实际上等同于int类型的变量
var i MyInt
i=100
println(i)
}
-------------
2. 类型别名定义
type NewType=Type ->就是起一个别名
-------测试
package main
func main() {
// 类型别名定义->实际上就是说 MyInt 等同于 int
type MyInt=int
// i 为MyInt类型的变量,实际上等同于int类型的变量
var i MyInt
i = 100
println(i)
}
- 结构体的定义
go语言没有面向对象的概念,结构体的定义与类型定义类似,只不过多了一个struct关键字。
1. 结构体的定义
type 结构体类型名称 struct{
成员定义;
成员定义;
成员定义;
}
-----------------测试
package main
import "fmt"
type Student struct {
name string
age int
sex int
teacher string
}
func main() {
var s1 Student
s1.name = "李明"
s1.age = 20
s1.sex = 0
s1.teacher = "王先生"
fmt.Printf("%T\n", s1)
fmt.Printf("%v\n", s1)
}
-----------------测试
2.匿名结构体定义:
var 结构体名字 struct{
xxxx
xxxx
}
-----------------测试
package main
import "fmt"
func main() {
var Student struct {
name string
age int
sex int
teacher string
}
Student.age = 20
Student.name = "jsxs"
Student.sex = 1
Student.teacher = "丽丽"
fmt.Printf("%v\n", Student)
}
-----------------测试
- 结构体的初始化
1.第一种: 以键值对的方式进行初始化
type Person struct {
id int
name string
age int
email string
}
var jsxs Person //类型定义
// 1.第一种定义方式 -> 使用键值对
jsxs = Person{ //进行初始化
id: 1,
name: "heh",
age: 0,
email: "22123121", //最后一个也要添加逗号
}
2.第二种:按照顺序的方式进行初始化。顺序一定要保持一致
type Person struct {
id int
name string
age int
email string
}
var jsxs Person
jsxs = Person{1, "dsds", 0, "dssdsd"}
3.第三种:利用语法糖方式
jsxs:=Person{
xx:xx
}
- 结构体指针
结构体指针和普通的变量指针相同.
1.普通方法创建结构体指针
type Person struct {
id int
name string
age int
email string
}
jsxs := Person{1, "dsds", 0, "dssdsd"}
var p1 *Person // 定义一个结构体指针
p1 = &jsxs //指针指向地址
fmt.Printf("%v\n", jsxs)
fmt.Printf("%v\n", *p1)
2. new创建结构体指针 new(结构体名字)
type Person struct {
id int
name string
age int
email string
}
jsxs := Person{1, "dsds", 0, "dssdsd"}
var p1 = new(Person) ///---》利用new
//var p1 *Person // 定义一个结构体指针
p1 = &jsxs //指针指向地址
fmt.Printf("%v\n", jsxs)
fmt.Printf("%v\n", *p1)
3.结构体指针的访问-》 指针变量名.属性/ (*指针变量名).属性
type Person struct {
id int
name string
age int
email string
}
jsxs := Person{1, "dsds", 0, "dssdsd"}
var p1 = new(Person)
//var p1 *Person // 定义一个结构体指针
p1 = &jsxs //指针指向地址
fmt.Printf("%v\n", jsxs)
fmt.Printf("%v\n", *p1)
fmt.Printf("%v", p1.id) //与下面的结果一致
fmt.Printf("%v", (*p1).id) //与上面的结果一致 (*)可以省略
4.结构体作为函数参数
go结构体可以像普通变量一样,作为函数的参数,这里分为两种情况
- 直接传递结构体,这是一个副本(拷贝),在函数内部不会改变外面结构体内容
- 传递结构体指针,这时在函数内部,能够改变外部结构体内容
1. 值传递: 形参改变实参不变
package main
import "fmt"
type Person struct {
id int
name string
age int
email string
}
func test(p1 Person) {
p1.id = 1
p1.name = "sdsds"
p1.age = 20
p1.email = "sdsd"
fmt.Printf("%v\t", p1)
}
func main() {
person := Person{2, "SDDF", 0, "GFDG"}
test(person) //值传递
fmt.Printf("%v\t", person)
}
--------------
2.指针传递: 形参改变实参也改变
package main
import "fmt"
type Person struct {
id int
name string
age int
email string
}
func test(p1 *Person) { //指针接受
p1.id = 1
p1.name = "sdsds"
p1.age = 20
p1.email = "sdsd"
fmt.Printf("%v\t", p1)
}
func main() {
person := Person{2, "SDDF", 0, "GFDG"}
p1 := new(Person) // 创建一个指针
p1 = &person
test(p1) //传递一个地址
fmt.Printf("%v\t", *p1)
}
- 结构体的嵌套
package main
import "fmt"
type Person struct {
name string
age int
pie Pie //人类拥有宠物
}
type Pie struct {
name string //姓名
sort string //种类
}
func main() {
p := Person{
name: "jsxs",
age: 20,
pie: Pie{ //初始化结构体
name: "小黄",
sort: "金毛",
},
}
fmt.Printf("%v", p)
}
(10).方法 ✔™✅
Go语言没有面向对象的特性,也没有类对象的概念。但是,可以使用结构体来模拟这些特性,我们都知道面向对象里面有类方法概念。我们也可以声明一些方法,属于某一个结构体。
Go语言方法的语法
Go中的方法是一种特殊的函数,定义struct之上(与struct关联,绑定),被称为struct的接收者(receive)。通俗的讲: "方法就是有接收者的函数"
- 方法的定义
1.方法的定义 -> 一个方法和一个函数非常相似,多了一个接受类型
type 结构体名 struct{}
func (接受该方法的结构体 结构体名) 方法名称(参数列表) 返回值类型 {}
func (接受该方法的结构体 *结构体名) 方法名称(参数列表) 返回值类型 {}
***********************************************************
值传递
package main
import "fmt"
type Person struct { // 1.定义一个结构体
name string
}
func (per Person) eat() { //2. 给结构体Person添加一个eat()的方法
fmt.Printf("%v,eat....", per.name)
}
func (per Person) sleep() { //3. 给结构体Person添加一个sleep()的方法
fmt.Printf("%v,sleep....", per.name)
}
func (per Person) sum(a, b int) int {
return a + b
}
func main() {
person := Person{ // 4.初始化结构体
name: "李明",
}
person.eat() // 5.结构体本身调用自己的属性
person.sleep()
println(person.sum(1, 2))
}
***********************************************************
- 方法接收者类型
结构体实列,有值类型和指针类型,那么方法的接受者是结构体,那么有值类型和指针类型。区别就是接收者是否赋值结构体副本。值类型拷贝,指针类型不拷贝。
指针传递
package main
import "fmt"
type Person struct { // 1.定义一个结构体
name string
}
func (per *Person) test() { //2.添加一个指针
per.name = "sssss"
fmt.Printf("%v", per.name)
}
func main() {
person := Person{ // 4.初始化结构体
name: "李明",
}
p := new(Person)
p = &person
fmt.Printf("%v", person)
p.test() // 通过指针进行调用
fmt.Printf("%v", person)
//println(person)
}
(11).接口类型
接口像是一个公司里面的领导,他会定义一些通用规范,只设计规范,而不实现规范。Go语言的接口是一种新的类型定义,他把所有的具有共性的方法定义在一起,任何具有其他类型只要实现了这些方法就是实现了这个接口。
1.接口的初始化
1.定义接口
type interface_name interface{
方法名 [返回值]
}
2. 定义结构体
type struct_name struct{
成员变量
}
3.实现接口的方法
func(结构体变量 结构体变量名) 方法名() [返回值类型]{
方法实现
}
4.声明接口有哪个结构体来实现
在接口中定义,若干个空方法。这些方法都具有通用性。
假如说一个结构体要实现一个接口,那么他要实现结构体的所有方法;否则会报错
package main
import "fmt"
// 定义一个接口存在两个方法
type USB interface {
read() //定义一个读的方法
write() //定义一个写的方法
}
// 定义一个结构体
type Computer struct {
name string
}
// 给结构体添加一个方法-》实现接口的方法
func (c Computer) read() {
fmt.Printf("%v\n,read......", c.name)
}
// 给结构体添加一个方法-》实现接口的方法
func (c Computer) write() {
fmt.Printf("%v\n,write......", c.name)
}
func main() {
var usb USB // 定义一个接口变量
computer := Computer{
name: "HP",
}
usb = computer //声明这个接口将有computer这个结构体来实现
usb.read() //接口调用
usb.write()
}
- 接口值类型接收者和指针类型接收者
值接受就是一个拷贝,指针接受就是非拷贝
1.值传递
******************************************************
package main
import "fmt"
// 定义一个接口存在两个方法
type USB interface {
read() //定义一个读的方法
write() //定义一个写的方法
}
// 定义一个结构体
type Computer struct {
name string
}
// 给结构体添加一个方法
func (c Computer) read() {
c.name = "sds" // 在里面惊进行修改 ------- 值操作
fmt.Printf("%v,reading......\n", c.name)
}
// 给结构体添加一个方法
func (c Computer) write() {
c.name = "sds" // 在里面惊进行修改 ------- 值操作
fmt.Printf("%v,write......\n", c.name)
}
func main() {
var usb USB // 定义一个接口变量
computer := Computer{
name: "HP",
}
usb = computer //声明这个接口将有computer这个结构体来实现
usb.read() //接口调用
usb.write()
fmt.Printf("%v", computer.name)
}
******************************************************
2.指针传递
******************************************************
package main
import "fmt"
// 定义一个接口存在两个方法
type USB interface {
read() //定义一个读的方法
write() //定义一个写的方法
}
// 定义一个结构体
type Computer struct {
name string
}
// 给结构体添加一个方法
func (c *Computer) read() { // 指针传递 -----
c.name = "sds" // 在里面惊进行修改 ------- 值操作
fmt.Printf("%v,reading......\n", c.name)
}
// 给结构体添加一个方法
func (c *Computer) write() { // 指针传递 -----
c.name = "sds" // 在里面惊进行修改 ------- 值操作
fmt.Printf("%v,write......\n", c.name)
}
func main() {
var usb USB // 定义一个接口变量
computer := Computer{
name: "HP",
}
usb = &computer //声明这个接口将有computer这个结构体来实现 ---指针传递
usb.read() //接口调用
usb.write()
fmt.Printf("%v", computer.name)
}
- 接口和类型(结构体)的关系
- 一类型可以实现多个接口
- 多个类型可以实现同一个接口 (多态)
1..一个类型可以实现多个接口: 有一个player接口可以播放音乐,有一个vido接口可以播放视频,一个Phome可以同时实现这俩接口,边听儿歌变看视频。
******************************************************
package main
import (
"fmt"
)
type Music interface {
audio()
}
type Video interface {
TV()
}
type Phone struct {
name string
}
func (p Phone) audio() {
fmt.Printf("%v,正在听音乐...", p.name)
}
func (p Phone) TV() {
fmt.Printf("%v,正在看视频...", p.name)
}
func main() {
var music Music
var tv Video
phone := Phone{
name: "iqoo",
}
music = phone //实体类赋值接口
tv = phone
music.audio()
tv.TV()
}
******************************************************
2.多个类型可以实现同一个接口 (多态)
package main
import (
"fmt"
)
type Music interface {
audio()
}
type Phone struct {
name string
}
func (p Phone) audio() {
fmt.Printf("%v,正在听音乐...", p.name)
}
type Computer struct {
name string
}
func (c Computer) audio() {
fmt.Printf("%v,正在听音乐...", c.name)
}
func main() {
var music Music
phone := Phone{
name: "iqoo",
}
music = phone //实体类赋值接口
music.audio()
computer := Computer{
name: "HP",
}
music = computer //实体类赋值接口
music.audio()
}
- 接口嵌套
接口可以通过嵌套,创建新的接口。列如: 飞鱼,既可以飞,又可以游泳。我们创建一个飞Fly接口,创建一个游泳接口,飞鱼接口有这两个接口组成。
package main
import "fmt"
type Fly interface {
flying()
}
type Swim interface {
swimming()
}
type FlyFish interface {
Fly
Swim
}
type Fish struct {
name string
}
func (f Fish) flying() { //实现飞的方法
fmt.Printf("%v,正在飞.....", f.name)
}
func (f Fish) swimming() { //实现游泳的方法
fmt.Printf("%v,正在游泳.....", f.name)
}
func main() {
var fish FlyFish //定义接口
f := Fish{
name: "飞鱼",
}
fish = f //对接口进行赋值
fish.swimming()
fish.flying()
}
- 通过接口实现OCP设计原则
而面向对象的可复用设计的第一块基石,便是所谓的 开-闭(Open-Closed Principle) 原则。虽然,go不是面向对象的语言,但也是可以模拟实现这个原则。对扩展是开放的,对修改是关闭的。
在本次测试中: 我们初设两个实现方法cat和dog,猫和狗此时并没有率属于人类结构体下,我们可以实现对人类结构体进行扩展的操作。
package main
type Pet interface {
eat()
sleep()
}
type Dog struct {
}
type Cat struct {
}
//Dog实现接口
func (d Dog) eat() {
println("Dog在eat....")
}
func (d Dog) sleep() {
println("Dog在sleep....")
}
// Cat实现接口
func (c Cat) eat() {
println("Cat在eat....")
}
func (c Cat) sleep() {
println("Cat在sleep....")
}
type Person struct {
}
// pet既可以传递Dog也可以传递Cat
func (p Person) care(pet Pet) { //接口接受
pet.eat()
pet.sleep()
}
func main() {
dog := Dog{}
cat := Cat{}
person := Person{}
person.care(dog) //我们这里传递实体
person.care(cat) //我们这里传递实体
}
- 模拟OOP的属性和方法
Go没有面向对象的概念,也没有封装的概念。但是可以通过结构体 struct和函数绑定来实现OOP的属性和方法特性。接收者receiver方法。通俗的讲: 就是属性和方法分开定义
列如: 想要定义一个Person类,有name和age属性,有 eat/sleep/work方法
package main
import "fmt"
type Person struct {
name string
age int
}
func (p Person) play() {
fmt.Printf("%v\n正在玩游戏...", p.name)
}
func (p Person) Listener() {
fmt.Printf("%v\n正在听歌...", p.name)
}
func main() {
person := Person{
name: "吉士先生",
age: 20,
}
person.play()
person.Listener()
}
7.Go继承
本质上没有oop的概念,也没有继承的概念,但是可以通过 结构体嵌套 实现这个特性。
内嵌结构体的话,我们可以省略调用内嵌结构体的变量名
package main
import "fmt"
type Animal struct {
name string
}
func (a Animal) eat() {
println("正在吃...")
}
func (a Animal) sleep() {
fmt.Printf("%v正在睡...", a.name)
}
type Cat struct {
//animal Animal //内嵌可以理解成继承的关系
Animal //内嵌结构体我们可以直接省略掉变量名
}
func main() {
cat := Cat{
Animal{ //因为上面省略了,所以我们这里也可以省略变量名 animal:=Animal{}
name: "花花",
},
}
cat.sleep() // 直接调用,因为省掉变量名了
//cat.animal.sleep() //这里是不省略
}
- 构造函数
Go没有构造函数的概念,可以使用函数来模拟构造函数的功能。
利用函数来模拟构造函数...
package main
import "fmt"
type Person struct {
name string
age int
}
func NewPerson(name string, age int) (*Person, error) {
if name == "" {
return nil, fmt.Errorf("name 不能为空")
}
if age < 0 {
return nil, fmt.Errorf("age 不能为负")
}
return &Person{
name: name,
age: age,
}, nil
}
func main() {
person, err := NewPerson("李明", 20)
fmt.Printf("%v", *person)
fmt.Printf("%v", err)
}
7.输出函数
格式化输出
%v 万能变量 %d 十进制整数 %f 浮点型 %s 字符串 %c 字符 %T 数据类型
%#v 详细万能变量 %p 指针 %t 布尔 &取地址
fmt.printf("%v",identify) ->格式化输出
换行输出
fmt.println()
不换行输出
fmt.print()
8.关系运算符和逻辑运算符和位运算符
- 关系运算符
1. ++和-- 不能放到表达式里面。只能先输出再调用。
++ -- + - * / += -= /=
> = < >= <=
2.逻辑运算符
&& 一个为假全为假
|| 一个为真全为真
! 真为假 假为真
3.位运算符
& : 一个为假全为假
| : 一个为真全为真
^ : 不同为0,相同为1
<< 左移 : 左边非0的保留,右边添加0
>> 右移 : 右边非0也不保留,
9.Go语言的流程控制
1.顺序
从上->倒下
- 条件判断
1. if语句判断执行流程
if 布尔表达式 {
}else if 布尔表达式{
} else{
}
(1).在Go语言中,表达式不需要用括号括起来。
(2).左括号{}必须存在if或else的同一行
(3).在if之后,条件语句之前,可以添加变量初始化语句,使用分割进行分割。
eg:
if idenfied:=value;idenfied>20{}
----------------------
2. switch进行条件判断默认是不穿透的。
3. Go语言switch语句,可以同时匹配多个条件,中间用逗号分割,有其中一个匹配成功即可。
switch idenfied{
case 1,2,3,4,5: println("工作日")
default: println("休息日")
}
4. Go语言的switch语句的case可以放置:判断表达式
5. fallthroug 设置穿透,Go语言默认是不穿透的。break打断穿透。0
- 循环
GO语言中只有for没有while{},do while{}
1. for 初始条件;条件表达式;结束语句{
循环体
}
2. for表达式不需要加括号
3. 初始条件我们可以在外部定义。
4. 永真循环,就是我们的条件都不要。类似于while循环
for{} 或者 for ; ;{}
-----------
1. for index,value:=range 数组/字符串/map/chan{函数体}
2. 如果不需要的话,可以用匿名变量进行占位置
for i,v:=range identify{
}
10.Break与Continue与goto与标签💥
- Break
1. 单独在select中使用break和不使用break没有啥区别
2. 单独在表达式switch语句,并且没有fallthrough,使用break和不适用没有啥区别
3. 单独在表达式switch语句中,并且有fallthrough,使用break能够终止穿透
4. 带标签的break,可以跳出多层select/switch作用域,让break更加零花,不需要使用控制变量一层层的跳出循环,没有带break的只能跳出当前语句块。
(1).可以终止for循环
(2).可以打破switch的fallthrough穿透
(3).可以跳出标签
package main
func main() {
/**
* 正常情况下,我们需要俩个break才能跳出双层循环。如果我们添加了标签就会直接跳转到外面,无需俩break
*/
MY_LABEL: //标签
for i := 0; i < 5; i++ {
for j := 0; j < 5; j++ {
if i == 2 {
break MY_LABEL //标签
}
println("你好")
}
println(i)
}
println("end....")
}
- continue
continue只能在循环中,在go中只能用for循环中,它可以终止本次循环,进行下一次循环。在continue语句后添加标签时,表示开始标签对应的循环。
1. 在for循环中使用continue,会跳过此次循环,但不会终止后面的循环
3.goto
goto语句通过标签进行代码间的无条件跳转,goto语句可以在快速跳转循环、避免重复退出上有一定的帮助.Go语言中使用goto语句能够简化一些代码的实现过程。比如双层嵌套的for循环要退出时。
1. goto 标签名 与 break 标签名的区别
(1).break的标签名只能放在for循环上面,不能放在其他位置,goto的标签名可以随便放,不必只放在for寻黄傻瓜
(2).break的标签必须先定义再使用,goto可以先定义再使用也可以先使用再定义
goto 进行跳转
package main
func main() {
for i := 0; i < 5; i++ {
for j := 0; j < 5; j++ {
if i == 2 {
goto first_range //标签的使用
}
println("你好")
}
println(i)
}
println("end....") //这里是不会打印的,直接跳转
first_range: //标签的定义
println("end2....")
}
- 标签的定义和使用
- break调用标签的话,必须使用在for循环之上。必须先定义后使用
- goto调用标签的话,可以先使用再调用,不必须在for循环下。位置随便放
- goto标签,如果goto跳转之后会直接跳转到标签所在的位置
- break标签,会退出循环。
1. 定义
标签名:
2. 使用
break 标签名
goto 标签名