go基础(类型,接口,结构,以及常用关键字)

65 阅读11分钟

go基础类型的声明与赋值

变量声明

go语言的变量声明主要就是 var name type,var关键字声明,name就是变量名,type就是变量的类型。

  • 标准声明
    //var name type
    var variate string
    variate = "vari"
  • 多行声明
    var (
        a int
        b string
        c []float32
        //d func() bool
        e struct {
           x int
        }
    )

可以看到用括号()进行包裹,声明多个不同类型的变量

  • 简写声明
   x:=100
   a,s:=1, "abc"

这是go语言特有的语法,即 variate := value,左边是变量名,右边是值,go会自动的做类型推断。并且可以左右都是多个来多变量声明。

  • 变量交换值
a:=1
b:=4
a,b=b,a  
//a=4  b=1

一大特色,java,js的swap都要

temp=a;
a=b;
b=temp;

而go只需要使用 :=来进行交换。

需要注意: 使用:=其实也是声明一个新的变量,所以也不能重复声明,重复的话编辑器会报错的。

  • 匿名变量
func GetData() (int, int) {
   return 100, 200
}

m, _ := GetData()
_, n := GetData()
fmt.Print(m, n)


//100 200   _是匿名变量,不使用内存

匿名变量,也就是将位置占了,从而获取到我们想要的位置的值。

常量与iota

常量是一个简单值的标识符,在程序运行时,不会被修改的量。

常量的值必须是能够在编译时就能够确定的,可以在其赋值表达式中涉及计算过程,但是所有用于计算的值必须在编译期间就能获得。

const (
   a = 1
   b
   c = 2
   d
)
//const 批量声明时,除了第一个变量需要赋值,后面都可以省略

const c1 = 2 / 3 //正确的做法
//const c2 = getNumber()   错误的做法 引发构建错误: getNumber() 用做值
//const 变量的值需要能在编译阶段确定

const 多变量声明可以在有前值时省略后面的常量的赋值,它的值会等于最后一个赋了值的常量。在这里,b的值就是1,d的值就是2

func main() {  
    const (  
            a = iota   //0  
            b          //1  
            c          //2  
            d = "ha"   //独立值,iota += 1  
            e          //"ha"   iota += 1  
            f = 100    //iota +=1  
            g          //100  iota +=1  
            h = iota   //7,恢复计数  
            i          //8  
    )  
    fmt.Println(a,b,c,d,e,f,g,h,i)  
}

iota 在 const关键字出现时将被重置为 0(const 内部的第一行之前),const 中每新增一行常量声明将使 iota 计数一次(iota 可理解为 const 语句块中的行索引)。

这和ts的enum枚举是一样的。它不关心单个常量的赋值是多少,itoa总是标识着index(下标)。可以通过再次将常量赋值iota来恢复计数。

类型别名

类型别名:type typename = Type,顾名思义就是为类型取一个别名,但实际上这个别名和真名是一致的。区别于类型定义:type typename Type,它实际上是声明一个新的类型,它的类型和原类型比较的话是不一致的。举一个例子:

type newType int
type aliasType = int
var a newType
var b aliasType
fmt.Printf("newType typeof: %T ,aliasType typeof: %T", a, b)
//newType typeof: main.newType ,aliasType typeof: int

关键字

breakdefault funcinterfaceselect
casedefergomapstruct
chanelsegotopackageswitch
constfallthroughifrangetype
continueforimportreturnvar

标识符

appendboolbytecapclosecomplexcomplex64complex128uint16
copyfalsefloat32float64imagintint8int16uint32
int32int64iotalenmakenewnilpanicuint64
printprintlnrealrecoverstringtrueuintuint8uintptr

数组与切片

数组是一个由固定长度的特定类型元素组成的序列,一个数组可以由零个或多个元素组成。因为数组的长度是固定的,所以在Go语言中很少直接使用数组。而是使用切片

var a [3]int // 定义三个整数的数组
//不赋值的话就默认值为0
fmt.Println(a[0])        // 打印第一个元素
fmt.Println(a[len(a)-1]) // 打印最后一个元素
// 打印索引和元素
for i, v := range a {
   fmt.Printf("%d %d\n", i, v)
}
// 仅打印元素
for _, v := range a {
   fmt.Printf("%d\n", v)
}

数组没什么好说的。注意遍历数组使用range进行遍历。

for index,value:= range arrar{
    //do something with index and value
}

切片

切片(slice)是对数组的一个连续片段的引用,所以切片是一个引用类型(因此更类似于 C/C++中的数组类型,或者 Python中的 list 类型),这个片段可以是整个数组,也可以是由起始和终止索引标识的一些项的子集,需要注意的是,终止索引标识的项不包括在切片内

通过数组生成切片:

func main() {
   var arr1 = [3]int{2, 3, 4}
   fmt.Println(arr1[1:], arr1[:], arr1[1:2], arr1[1:1])
   //[3 4] [2 3 4] [3] []
}

声明新切片:

var slice1 []int
println(len(slice1))

make生成新的切片

s2 := make([]int, 3)
fmt.Println(len(s2))

切片的使用和数组基本上是一致的,包括range遍历,append添加元素等。只是切片是动态的,而数组是固定的,切片动态扩容,而数组需要手动扩容。

map

map,对应就是java的hashmap,js的map。它的定义是 var mapName map[keyType]valueType。

func main() {
   //var map2 map[string]string  //声明,并没有生成实例
   map1 := make(map[string]string) //make构造,返回map实例
   map1["key1"] = "value1"
   map1["key2"] = "value2"
   map1["key3"] = "value3"
   fmt.Println(map1)
   for s, s2 := range map1 {
      fmt.Println(s, s2)
   }
   //map[key1:value1 key2:value2 key3:value3]
   //key1 value1
   //key2 value2
   //key3 value3
}

关于map的delete方法等看看就会用了,语义化的方法名。

sync.map

为了解决多线程map的读取而产生的异步的map,就是说map的增删是异步了,不会产生线程冲突。但是如果是单线程的使用map结果,肯定还是选用map更快而不是sync.map

var scene sync.Map
scene.Store("1", 1)
scene.Store("2", "2")
scene.Store(3, "v3")
scene.Store(4, 4)
//println(syncmap1.Load("key1"))
fmt.Println(scene.Load("2"))
fmt.Println(scene.LoadAndDelete(4))

goto语句

goto就是c不提倡的go语句,在golang里面可以很好的使用。用于跳出当前执行上下文,并跳转到goto的函数。

func main() {
   for i := 0; i < 10; i++ {
      for j := 0; j < 10; j++ {
         if j == 8 {
            fmt.Println(i, j)
            goto breakAt
         }
      }
   }

breakAt:
   fmt.Println("break")
}

//0 8
//break

for

func main() {

   //normal loop  循环和java/c类似,只是不需要写(;;)
   for i := 0; i < 100; i++ {
      fmt.Printf("i%d   ", i)
   }
   fmt.Println()

   //infinite loop  /   endless loop   无线循坏
   n := 0
   for {
      n++
      fmt.Println("this is do...")
      if n > 100 {
         fmt.Println("break...")
         break
      }
   }

   // 带goto,可以不对i初始化
   var i int
   for {
      fmt.Printf("%d  ", i)
      if i > 10 {
         goto breakHere
      }
      i++
   }

breakHere:
   fmt.Println("break")

}

函数

注意多返回值即可,另外还可以命名返回值,为返回值赋值,且return的时候可以省略。

func namedRetValues() (a, b int) {
   a = 1
   b = 2
   return
}

func doubleRe(a int, b string) (i int, j string) {
   return a, b
}
func main() {
   fmt.Println(namedRetValues())
   fmt.Println(doubleRe(2, "3"))
}

匿名函数

func(word string) {
   fmt.Println("word is : ", word)
}("niganma")

在匿名函数后面加(arg...)就是自调用

f := func(word string) {
   fmt.Println("word is : ", word)
}

f("niganma")

和js一样,也可以匿名函数赋值。

匿名函数的另一个使用是作为回调函数

// a func with a func parameter
func userCallBackFunc(i int, f func(int2 int)) {
   f(i)
}

func main() {
   //在这里传入匿名函数
   userCallBackFunc(3, func(int2 int) {
      fmt.Printf("do callBack and the number is  %d  \n", int2)
   })
}

closure(闭包)

通过函数可以实现闭包,将函数作为返回值

// 匿名函数作为返回值
func Add(i int) func() int {
   return func() int {
      i++
      return i
   }
}

func main() {
   //获取到返回的函数
   a1 := Add(10)

   //然后调用返回的函数,得到操作之后的结果,这就是go的函数实现的闭包
   fmt.Println(a1()) // 13
}

可变参数

很实用的参数传递方式,而且可以通过迭代器遍历。这是因为可变参数的实现就是切片。以及arg...来将x可变参数传递给另一个函数。

func ddd(int1 ...int) {
   for _, i2 := range int1 {
      fmt.Println(i2)
   }
}

func ddd2(int2 ...int) {
   ddd(int2...)
   //通过这个传回可变参数
}

func main() {
   ddd2(1, 2, 3, 4)
}

//1 2 3 4 

method

在golang中method和function是不一样的,function就是通过func定义然后直接调用的,而method则不能直接调用

方法(method)与函数(function)的区别是,函数不属于任何类型,方法属于特定的类型。

// Person 定义接收者结构体
type Person struct {
   name string
   age  int
}

// func (接收者变量 接收者类型) 方法名(参数列表) (返回参数) {
//函数体
//}
func (p Person) pMSG() { //这里的意义就是Person这个结构体接收到了pMSG这个方法
   fmt.Printf("this person's name is: %s and age is %d\n", p.name, p.age)
}

//定义多个方法到接收者上
func (p Person) pName() {
   fmt.Println("name is " + p.name)
}

func main() {
   p := Person{
      name: "mio",
      age:  18,
   }
   //p.就可以看到Person接收到的方法
   p.pMSG()
   p.pName()
}

defer

defer延迟调用是 Go语言中的一个关键字,一般用于释放资源和连接、关闭文件、释放锁等。就是熟知的异步调用。

需要注意的是,defer的调用是栈结构的,即先进后出,与之相反的是js的微任务队列。

func main() {
   defer fmt.Println(1)
   defer fmt.Println(2)
   defer fmt.Println(3)
   //3
   //2
   //1
   //1先进反而后出,可以看出是栈结构的延迟调用
}

panic_recover

go语言的panic和recover就是类似try...catch的处理异常的语句。通过panic来让执行宕机(抛出异常),然后通过recover来捕获并处理。

//一定是通过defer来处理recover语句,因为不能先执行,又要等panic之后再来调用recover。
defer func() {
   if err := recover(); err != nil {
      fmt.Printf("this is a word with defer&recover%s\n", err)
   }
}()
fmt.Println("this is a work without defer")

panic("err")

ps:panic 并不会第一时间宕机,而是栈的方式依次执行前面的defer语句。

struct

struct,类似于java的类的接口。

type Person struct {
   name string
   age  int
}

var person1 Person
person1.age = 19
person1.name = "mio"

fmt.Println(person1, person1.name)
//{mio 19} mio

struct也是可以嵌套的。

type Person struct {
   name struct {
      firstName string
      lastName  string
   }
   age int
}
var person1 Person
person1.age = 19
person1.name.firstName = "m"
person1.name.lastName = "io"

fmt.Println(person1, person1.name.firstName+person1.name.lastName)
//{{m io} 19} mio

当然也可以导入type作为类型

type Name struct {
   firstName string
   lastName  string
}
type Person struct {
   name Name
   age  int
}

& 是取地址符号 , 即取得某个变量的地址 , 如 ; &a *是指针运算符 , 可以表示一个变量是指针类型 , 也可以表示一个指针变量所指向的存储单元 , 也就是这个地址所存储的值 .

理解上面看代码

relation := &People{
   //取到这个结构体实例的地址
   name: "爷爷",
   child: &People{
      name: "爸爸",
      child: &People{
         name: "我",
      },
   },
}

fmt.Println(relation, *relation.child, *(*relation.child).child)
//&{爷爷 0xc000004090} {爸爸 0xc0000040a8} {我 <nil>}
// 值肯定是地址,但是地址指向的区域包括name和另一个地址

反正就是套娃。

同时可以通过&对结构体进行实例化(这是最通用的实例化方法)

type Str struct {
   a int
   b int
}

//作为返回值
func newStr(a int, b int) *Str {
   return &Str{a: a, b: b}
}


str33 := newStr(10, 20)
type People struct {
   name  string
   child *People
   // People结构体的指针
}

func tost(p1 *People) {
   fmt.Println(p1)
}

var p1 People
p1.name = "ss"
tost(&p1)

通过指针完成函数的参数传递。

看到内嵌结构体的写法

type Str struct {
   a int
   b int
}
type Str2 struct {
   Str
   c, d float32
}
s := Str2{Str{3, 4}, 1.0, 2.0}
fmt.Println(s,s)  //{{3 4} 1 2} {3 4} 3

内嵌的结构体有时会出现结构体的属性重名的情况,这个时候适当使用链式获取能够避免问题

type per1 struct {
   name string
   sex  string
}

type per2 struct {
   name string
   age  int
}

type per struct {
   per1
   per2
}
p := per{per1{name: "syh", sex: "男"}, per2{name: "syh2", age: 22}}

//fmt.Println(p.name)   //错误的,编辑器能够识别这个错误--不能识别name是谁的name
fmt.Println(p.sex, p.per2.name) //链式的获取,正确的

interface

go也注重类型,接口就是强化类型的重要性

//type 接口类型名 interface{
// 方法名1( 参数列表1 ) 返回值列表1
// 方法名2( 参数列表2 ) 返回值列表2
//}

// Word 定义一个接口,有outWord方法
type Word interface {
   outWord(data string) string
}

// Words 定义一个结构,他将要实现接口
type Words struct {
}

//定义一个方法,它和interface里的outWord定义的函数一致 (注意:这是一个方法而不是函数,words作为接收着)
func (w *Words) outWord(data string) string {
   fmt.Println("outWords: ", data)
   return " s "
}

func main() {
   //定义好一个结构体变量,使用new分配空间
   w := new(Words)

   //定义一个接口变量
   var word Word

   //结构体实现接口,能够赋值的前提是w (words)实现了interface里定义的成员 (注意:必须全部实现才可以)
   word = w

   //那么这个被实现后的接口变量就可以调用outWord方法
   word.outWord("data")

}

go 语言的接口和类型(结构)是多对多的关系的,也就是说一个接口可以被多个类型实现,一个类型也可以实现多个接口

assert

assert-断言和interface息息相关,断言类型,通常断言的就是interface类型

var x interface{}
x = 10
//断言能够得到value和Boolean(断言是否正确)的返回值
value, ok := x.(int)
fmt.Print(value, ",", ok, "\n")

var y interface{}

y = "string"
//通过变量.(type)来switch...case
switch y.(type) {
case int:
   fmt.Println("int")
case string:
   fmt.Println("string")
case float32:
   fmt.Println("float32")
default:
   fmt.Println("?")
}

sort

sort是c内置的排序包,可以通过通过实现接口来调用。

import (
   "fmt"
   "sort"
)

// MyStringList 将[]string定义为MyStringList类型
type MyStringList []string

// Len 实现sort.Interface接口的获取元素数量方法
func (m MyStringList) Len() int {
   return len(m)
}

// Less 实现sort.Interface接口的比较元素方法
func (m MyStringList) Less(i, j int) bool {
   return m[i] < m[j]
}

// Swap 实现sort.Interface接口的交换元素方法
func (m MyStringList) Swap(i, j int) {
   m[i], m[j] = m[j], m[i]
}

type HeroKind int

type Hero struct {
   Name string
   Kind HeroKind
}

func main() {
   // 准备一个内容被打乱顺序的字符串切片
   names := MyStringList{
      "3. Triple Kill",
      "5. Penta Kill",
      "2. Double Kill",
      "4. Quadra Kill",
      "1. First Blood",
   }

   // 使用sort包进行排序
   sort.Sort(names)
   // 遍历打印结果
   for _, v := range names {
      fmt.Printf("%s\n", v)
   }

   const (
      fir = iota
      sec
      thi
      fou
      fiv
   )

   arr := []*Hero{
      {"men", fir},
      {"men2", fou},
      {"men4", sec},
      {"men3", fiv},
      {"men5", thi},
   }
   sort.Slice(arr, func(i, j int) bool {
      // 编写实现 less 接口
      if arr[i].Kind != arr[j].Kind {
         return arr[i].Kind < arr[j].Kind
      }
      return arr[i].Name < arr[j].Name
   })

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

总结

本次文章学习了golang的基础,包括基础类型,接口,结构,以及一些关键字。

结语

本次的文章到这里就结束啦!♥♥♥读者大大们认为写的不错的话点个赞再走哦 ♥♥♥

每天一个知识点,每天都在进步!♥♥