文章目录
golang基础语法
变量申明
var a int
var b string
var c []float32
var d func() bool //申明一个返回值为bool的函数变量,一般用于回调函数,即将函数以变量的形式保存下来
var e struct{x int}//结构体变量
<==> 等价于
var (
a int
b string
...
)
变量初始化
var a int = 10 <==> var a = 10
//快捷变量定义
hp := 100 // 这种方式定义变量只能在函数中使用
匿名变量
匿名变量使用 ‘_’ 表示, 匿名变量不占命名空间,也不会分配内存
func GetData()(int, int) {
return 100, 100
}
a, _ := GetData()
_, b := GetData()
golang数据类型
整型、浮点型、字符串、字符(byte)、布尔类型、数组、切片、结构体、map、指针、通道
注意: 多行字符串可以使用 反引号
& 表示取地址操作
var a int = 10
pv := &a //指正pv
(*) 表示 对变量进行取值操作
func Swap(a, b *int) {
t := *a
*a = *b
*b = t
}
func TestSwap() {
x, y := 1,2
Swap(&x, &y)
fmt.Println(x, y)
}
-----------------------------
2 1
func Swap1(a, b *int) {
b, a = a, b
}
func TestSwap() {
x, y := 1,2
Swap1(&x, &y)
fmt.Println(x, y)
}
---------------------------
1 2
不会进行数据交换, 只是交换了数据的地址
new()方法创建指针
str := new(string)
*str = "hello wprld"
变量生命周期(堆和栈–变量逃逸)
栈:一种线性表数据结构, 先入后出
一般情况下局部变量使用的内存分配在栈上,分配和回收速度非常快
堆:全局变量一般分配在堆上
变量被取地址时,内存逃逸到堆上
字符串的应用
str := "hello world"
len(str) //计算字符串中的字符长度
str[i] //表示第i位 的字符
//使用for循环遍历字符串
for i:=0;i<len(str);i++ {
fmt.Printf("%c", str[i])
}
-------------------
hello world
修改字符串的值:go无法直接修改每个字符元素,只能重新构建新的字符串并赋值给原来字符串变量
str := "hello world"
newStr := []byte(str)
for i:=0;i<len(newStr);i++{
newStr[i] = 'y'
}
fmt.Println(string(newStr))
-------------------------------
yyyyyyyyyyy
数组、切片、map,列表
数组定义
固定大小的连续空间 定义为数组
//数组申明
var arr [3]string
//数组初始化
var arr = [3]string{"1", "3", "2"}
或者
var arr = [...]string{"1", "3", "2"}
//... 表示编译器在编译时根据元素个数确定数组大小
切片的定义
动态分配内存大小的连续空间
//申明字符串切片
var slice []string //slice == nil
var slice []int // slice == nil
//申明空切片
var slice = []int{} // slice != nil
//申明有数据的切片
var arr = []string{"1", "3", "2"}
//使用make函数构造切片
var slice = make([]int, size, cap)
var a = make([]int, 2)
var b = make([]int, 2, 10)
//注意: cap的大小不会影响 切片的长度,只是提前分配空间,降低分配空间造成的性能问题
//因此。 len(a) == len(b)
var a []int //nil切片,和nil相等,一般用来表示一个不存在的切片
var b []int{} //空切片,和nil不相等,一般用来表示一个空的集合
var c []int{1, 2, 3} //有3个元素的切片,len和cap都为3
var d = c[:2] //有2个元素的切片,len为2,cap为3
var e = c[:2:cap(c)] //有2个元素的切片,len为2,cap为3
var f = c[:0] //有0个元素的切片,len为0,cap为3
var g = make([]int, 3) //创建一个切片,len和cap均为3
var h = make([]int, 3, 6) //创建一个切片,len为3,cap为5
var i = make([]int, 0, 3) //创建一个切片,len为0,cap为3
从数组或切片中生成新切片
slice[开始位置:结束位置]
slice元素个数:结束位置 - 开始位置
开始位置
var arr1 = []string{"1", "3", "2"}
fmt.Println(arr1[:]) // [1 3 2]
fmt.Println(arr1[:len(arr1)-1]) // [1 3]
fmt.Println(arr1[:len(arr1)-2]) //[1]
fmt.Println(arr1[1:2]) // [3] --范围arr[1]- arr[2-1]
fmt.Println(arr1[1:]) // [3 2] ---范围 arr[1] -- arr[2]
fmt.Println(arr1[0:0]) //[] 清空切片
append为切片添加元素
var car []string
car = append(car,"olddriver") //添加一个元素
car = append(car, "Ice", "Snoper", "Monl") //添加多个元素
team := []string{"Pig", "Chicken"}
car = append(car, team...) //添加切片需要使用 ...
数组和切片的区别
- Go 中的数组是值类型,换句话说,如果你将一个数组赋值给另外一个数组,那么,实际上就是将整个数组拷贝一份。因此,在 Go 中如果将数组作为函数的参数传递的话,那效率就肯定没有传递指针高了。
- 数组的长度也是类型的一部分,这就说明[10]int和[20]int不是同一种数据类型。并且Go 语言中数组的长度是固定的,且不同长度的数组是不同类型,这样的限制带来不少局限性。
- 而切片则不同,切片(slice)是一个拥有相同类型元素的可变长序列,可以方便地进行扩容和传递,实际使用时比数组更加灵活,这也正是切片存在的意义。而且切片是引用类型,因此在当传递切片时将引用同一指针,修改值将会影响其他的对象。
golang map
map的定义
var scen = make(map[string]string)
scen["rout"] = "route"
v, ok := scen["rout"]
//ok 为bool,判断map中是否存在 键值为 rout 的值 , v 为value值
//map 的遍历
for k, v := range scen {
}
//delete() 函数删除 map中的值
delete(scen, "rout")
// golang 中不需要清除map, 支持新建map 就行,不用的map 会被gc
sync.Map (并发情况下使用的map)
func MapTest() {
m := make(map[int]int)
go func() {
for{
m[1] = 1
}
}()
go func(){
for {
_ = m[1]
}
}()
for {
}
}
--------------------------------------------
fatal error: concurrent map read and map write
使用sync.Map
func syncMapTest() {
var scene sync.Map
scene.Store("green", 9)
scene.Store("london", "100")
fmt.Println(scene.Load("london"))
scene.Delete("london")
scene.Range(func(k, v interface{}) bool {
fmt.Println("iii", k, v)
return true
})
}
列表
列表的定义
ll := list.New()
var lll list.List
//两种定义方式效果一样
列表的操作实例
func listTest() {
l := list.New() //var lll list.List
//尾部添加元素
l.PushBack("canon")
//头部添加数据
l.PushFront(67)
// 尾部添加元素后保存元素句柄
element := l.PushBack("first")
l.InsertAfter("HIGH", element)
l.InsertBefore("noon", element)
l.Remove(element)
// list 遍历
for i:= l.Front();i != nil; i = i.Next() {
fmt.Println(i.Value)
}
}
流程控制(if and for and switch)
条件判断if
if 表达式1 {
分支1
} else if 表达式2 {
分支2
} else {
分支3
}
--------------------------
特殊写法实例
if err := COontent(); err != nil {
fmt.Println(err)
return err
}
构建循环for
for语句的写法总结
for 初始语句; 条件表达式;结束语句 {
循环代码
}
for 循环可以通过 break / goto / return / panic 语句退出循环
----------------------------------------------------------------------------
for i:=0;i<10;i++ {
fmt.Println(i)
}
---------------------------------------------------------------------------
//初始语句可省略, 但初始语句后面的分号必须写
step := 2
for ; step > 0; step-- {
fmt.Println(step)
}
---------------------------------------------------------------------------
//条件表达式可以被忽略,但是分号不能省略;默认无限循环
var i int
for ; ; i++ {
if i > 10 {
break
}
}
-------------------------------------------------------------------------
// 完美无限循环
var i int
for {
if i > 10 {
break
}
i++
}
--------------------------------------------------------------------------
// 只有一个循环条件的循环 可以替换while
var i int
for i < 10 {
i++
}
for range 的使用
注意: 可以使用 _ 匿名变量替换其中的返回值参数
- 遍历数组和切片
for k, v := range []int{1, 2, 3, 4} {
fmt.Println(k, v)
}
----------------------------------------
0 1
1 2
2 3
3 4
- 遍历字符串,获得字符
//实际存放的是int32型数据
var str = "hello world"
for k, v := range str {
fmt.Printf("k:%d v:0x%x-%v-%T\n",k, v,v,v)
}
-------------------------------------------------
k:0 v:0x68-104-int32
k:1 v:0x65-101-int32
k:2 v:0x6c-108-int32
k:3 v:0x6c-108-int32
k:4 v:0x6f-111-int32
k:5 v:0x6211-25105-int32
k:8 v:0x4f60-20320-int32
k:11 v:0x4ed6-20182-int32
- 遍历map
m := make(map[string]int)
m["hello"] = 100
m["world"] = 200
for k, v := range m {
fmt.Println(k, v)
}
--------------------------
hello 100
world 200
- 遍历channel-接收通道信息
//根据channel阻塞原理分析打印结果
c := make(chan int)
go func() {
c <- 1
fmt.Println("write")
c <- 2
fmt.Println("write")
c <- 3
fmt.Println("write")
close(c)
}()
for v := range c {
fmt.Println("read ",v)
}
--------------------------------
write
read 1
read 2
write
write
read 3
有一个缓冲的channel
c1 := make(chan int, 1)
go func() {
c1 <- 1
fmt.Println("write1")
c1 <- 2
fmt.Println("write2")
c1 <- 3
fmt.Println("write3")
close(c1)
}()
for v := range c1 {
fmt.Println("read ",v)
}
--------
write1
write2
read 1
read 2
read 3
write3
switch
基本写法
//多个case可以放一起, 每个case中不需要写 break, 每一个case是独立的条件模块
var a = "hello"
switch a {
case "hello", "test": // case a == "hello" case s > 10 && s < 20:
fmt.Println("1")
fallthrough // 通过此关键词实现执行完本次case之后顺序往下执行其他case
case "world":
fmt.Println("2")
default:
fmt.Println("3")
}
goto、break、continue
goto 标签 – 跳转到指定标签位置运行
break 标签 – 跳出标签的循环
continue 标签 – 继续执行标签开始的循环
break 和continue 默认1层, 添加标签可以实现操作多层循环
//goto
err := CheckErr()
if err != nil {
goto onExit
}
onExit:
return err
// break
Outbreak:
for {
for {
break Outbreak
}
}
// continue
Outcontinue:
for {
for {
continue Outcontinue
}
}
函数
声明函数
func 函数名(参数列表)(返回参数列表){
函数体
}
func foo(a int, b string){}
func foo(a, b int){}
func add(a, b int) int{}
func add(a, b int) (c int) {}
匿名函数-没有名字的函数
匿名函数只有函数体,没有函数名,函数可以作为一种类型被复制给函数类型的变量, 匿名函数也往往可以以变量的方式传递。
匿名函数常常被定义与实现回调函数,闭包等。
//func(函数参数)(返回参数) {
// 函数体
//}
// 1、匿名函数在定义时调用
func(data int) {
fmt.Println("hello", data)
}(100)
//2、匿名函数赋值给变量
f := func(data int) {
fmt.Println("hello", data)
}
//调用函数
f(100)
// 3、匿名函数作为回调函数
func visit(list []int, f func(int)) {
for _,v := range list {
f(v)
}
}
func main() {
visit([]int{1, 2,,3,5}, func(v int) {
fmt.Println(v)
})
}
return 和defer
func test() (res int) {
res = 1
defer func() {
res++
}()
return 0
}
此情况下,return时res赋值为0,执行defer函数 res++,res为1,最后返回值为1
func main() {
fmt.Println("defer begin")
defer fmt.Println("1")
defer fmt.Println("2")
defer fmt.Println("3")
fmt.Println("defer end")
}
---------------------------------------------------------------------
defer begin
defer end
3
2
1
错误格式
自定义错误格式
var err = errors.New("this is an error")
// error类型的信息可以通过err.Error() 转换为字符串格式的错误输出
var err error
err.Error()
程序终止运行-宕机(panic)
手动触发宕机
panic()的参数可以是任意类型,recover 参数会接受从panic 中的内容
func main() {
panic("crash")
}
宕机触发defer函数执行
func main() {
defer fmt.Println("宕机做 的事情1 ")
defer fmt.Println("宕机做 的事情2 ")
panic("宕机")
}
宕机恢复
go中 recover() 可以和panic 配合使用, 捕获异常信息
结构体
- 使用new 或 & 构造的类型实例的类型是类型指针
golang结构体
type Bag struct() {
items []int
}
func Insert(b *Bag, itemid int) {
b.items = append(b.items, itemid)
}
func main() {
b := new(Bag)
Insert(b, 100)
}
-------------------------------------------------------------------
func (b *Bag) Insert(itemid int) {
b.Items = append(b.items, itemid)
}
func main() {
b := new(Bag)
b.Insert(100)
}
接收器-方法作用的目标
接收器结构
func (接收器变量 接收器类型) 方法名(参数列表)返回值列表 {
函数体
}
指针类型接收器 和非指针类型接收器
修改指针接收器的任意成员变量,方法结束之后,修改有效
type Property struct {
value int
}
func (p *Property) SetValue (v int ) {
p.value = v
}
func (p *Property) GetValue() int {
return p.value
}
func Test() {
p := new(Property)
p.SetValue(100)
v := p.GetValue()
fmt.Println(v)
}
-------------------------------------------
//非指针类型接收器, go 会复制一份 接收器的值,进行操作
// 在方法中修改接收器的成员值 不生效
type Point struct {
X int
Y int
}
func (p Point) Add(op Point) Point {
return Point{p.X+op.X, p.Y + op.Y}
}
func Test1() {
p1 := Point{1, 1}
p2 := Point{2,2}
res := p1.Add(p2)
fmt.Println(res)
}
接口-interface
- go 语言的接口计是非侵入式的,接口编写者无需知道接口被哪些类型实现,接口实现者只需知道实现的是什么样子的接口。
- 接口是双方约定的一种合作协议,接口实现者不需要关心接口被怎样使用,调用者也不需要关心接口的实现细节。
- 接口是一种类型,也是一种抽象结构,不会暴露所含数据的格式,类型及结构
接口的声明
每个接口由数个方法组成,形式如下:
type 接口类型名 interface {
方法名1(参数列表1)返回值列表1
方法名2(参数列表2)返回值列表2
…
}
//方法名首字母大写且接口类型名首字母也大写时,方法可以被接口所在包之外的代码访问
type writer interface {
Write([]byte) error
}
type Stringer interface {
String() string
}
type Test interface {
Tester()
}
type MyFloat float64
func (m MyFloat) Tester() {
fmt.Println(m)
}
func describe(t Test) {
fmt.Printf("Interface 类型 %T , 值: %v\n", t, t)
}
func main() {
var t Test
f := MyFloat(89.7)
t = f
describe(t)
t.Tester()
}
具有0个方法的接口称为空接口。它表示为interface {}。由于空接口有0个方法,所有类型都实现了空接口。
package main
import (
"fmt"
)
// 定义一个接口
type People interface {
ReturnName() string
}
// 定义一个结构体
type Student struct {
Name string
}
// 定义结构体的一个方法。
// 突然发现这个方法同接口People的所有方法(就一个),此时可直接认为结构体Student实现了接口People
func (s Student) ReturnName() string {
return s.Name
}
func main() {
cbs := Student{Name:"咖啡色的羊驼"}
var a People
// 因为Students实现了接口所以直接赋值没问题
// 如果没实现会报错:cannot use cbs (type Student) as type People in assignment:Student does not implement People (missing ReturnName method)
a = cbs
name := a.ReturnName()
fmt.Println(name) // 输出"咖啡色的羊驼"
}
//接口的零值是nil
var cbs People
if cbs == nil {
fmt.Println("cbs is nil 类型")
}
golang内网包管理(引入第三方的包)
//生成模块管理文件
go mod init genconf
在源码中放置第三方包文件, golang 源码中使用的包名可以使用本地的文件目录替换,
可以实现第三方库的二次开发
golang中的init初始化函数
golang 执行main函数之前会先执行常量初始化、变量初始化、init函数初始化。
参考:https://blog.csdn.net/liuyuede123/article/details/127394496