Golang笔记--基础篇汇总

Go程序结构:

  • 包名
  • 导入其他的包
  • 常量的定义
  • 全局变量的声明和负责
  • 一般类型声明
  • 结构的声明
  • 接口的声明
  • 由main函数作为程序入口启动点

Go别名和省略调用:

在导入包时可以使用别名来防止混淆,或者是使用省略调用来减少代码

别名:

//在导入包时定义别名
import io “fmt”
//使用时直接使用别名,而不是原来的包名
io.Println("Hello World")

省略调用(不建议使用,易混淆):

//在导入包时声明省略调用
import . “fmt”
//使用时直接使用要使用的函数,不用加包名
Println("Hello World")

Go中的可见性规则:

使用大小写来决定该常量,变量,类型,接口,结构或者函数是否可以被外部调用。

  • 函数名首字母小写为private函数

  • 函数名首字母大写为public函数

基本类型:

(布尔型)bool:

  • 长度:1字节
  • 取值范围:true,false(不可以用1,0表示)

(整形型)int/uint:

  • 长度:取决于所在平台,32或64位

(8位整型)int8/uint8:

  • 长度:1字节
  • 取值范围:-128~127/0~255

(字节型) byte(unit8):

  • 长度:1字节
  • 取值范围: 0~255

(16位整型)int16/uint16:

  • 长度:2字节
  • 取值范围:-32768~32768/0~65535

(32位整型)int32(rune)/uint32:

  • 长度:4字节
  • 取值范围:-(2^31)~(2^31)-1/0~(2^32)-1

(64位整型)int64/uint64:

  • 长度:8字节
  • 取值范围:-(2^64)~(2^64)-1/0~(2^64)-1

(浮点型)float32/64:

  • 长度:4/8字节
  • 小数位:精确到7/15位小数位

(复数)complex64/128:

  • 长度:8/16字节

(无符号指针)uintptr:

  • 长度:可以保存指针的32位或者是64位整型

引用类型:

  • slice,map,chan

其他值类型:

  • array ,struct,string

接口类型:

  • interface

函数类型:

  • func

变量的声明和赋值:

单个变量的声明和赋值:

//变量的声明
//(var <变量名称> <变量类型>)
var a int

//变量的赋值
//(<变量名称> = <表达式>)
a = 1

//变量声明同时赋值
//(var <变量名称> [变量类型] = <表达式>)
var a int = 65

//变量声明同时赋值
//(var <变量名称> = <表达式>)
var a = 2

//变量的声明同时赋值
//<变量名> := <表达式>
a := 2

多个变量的声明和赋值:

全局变量:

//多个变量的声明
//<var> [变量名1],[变量名2],[变量名3]... <类型名>
var a,b,c,d int

//多个变量的赋值
//[变量名1],[变量名2],[变量名3] = <表达式>,<表达式>,<表达式>...
a,b,c = 1,2,"string"

//多个变量的声明和赋值
//var(<变量名> = <表达式>...)
var(
    a = 1
    b = "string"
)

局部变量:


//多个变量的声明
//<var> [变量名1],[变量名2],[变量名3]... <类型名>
var a,b,c,d int

//多个变量的赋值
//[变量名1],[变量名2],[变量名3] = <表达式>,<表达式>,<表达式>...
a,b,c = 1,2,"string"

//多个变量的声明和赋值
//<变量名><变量名><变量名>... := <表达式><表达式><表达式>...
a,b,c := 1,2,3

变量的类型转换:

  • Go中不存在隐式转换,所有的类型转换都需要显式声明

  • 类型转换只能发生在两个相互兼容的类型之间

  • 类型转换格式:

    • < V alueA> [:]= < TypeOfValueA> (< ValueB >)

常量的初始化和枚举

  • 常量的值在编译时就已经确定

  • 常量得到的定义格式与变量基本相同

  • 常量右侧的表达式必须是常量或者是常量表达式

  • 常量表达式中的函数必须是内置函数

  • 在常量组中定义时,若是不提供初始值,则默认使用上一行的表达式

  • iota是常量的计数器,从0开始,组中每定义一个常量,就会自动增1

  • 每遇到一个const关键字,iota就会重置为0

const (
    FIRST  = iota //FIRST=0
    SECOND        //SECOND = 1
    THRID         //THRID = 2
)

运算符

  • 所有的运算符从左到右结合
  • 优先级从高到低依次是
运算符 优先级
^ ! 一元运算符
* / % << >> & &^ 二元运算符
+ - | ^ 二元运算符
== != < <= > >= 二元运算符
<- 用于channel
&&
||

指针

Go中的指针采用”.”选择符来操作指针对象的成员

  • 操作符”&”取地址变量,操作符”*”访问目标对象
  • 指针的默认值为nil而不是NULL
//指针的定义
var p *[]int
//只针的赋值
p = &a

自增/自减运算符

++(自增运算符)和–(自减运算符)不再是作为表达式,而是作为单独的语句出现。也就是说他们都不可以出现在运算符的右侧

//错误使用
a := 1++;
//正确使用
a := 1
a++

流程控制语句

if语句

在if之后没有括号,可以直接写判断表达式,而且大括号必须和if在同一行,支持在if语句中进行变量的初始化。

if a, b := 1, 2; a >= 0 || b >= 0 {
        fmt.Println(a)
        fmt.Println(b)
    }

for语句

只有for一个循环语句关键字,但是支持3种形式,for中的条件语句每次都会被检查,因此不建议在条件语句中使用函数,同if语句一样,左大括号必须和条件语句在同一行。可以通过break来跳出循环。

for a, b := 1, 2; a >= 0 || b >= 0; {
        fmt.Println(a + b)
        a--
        b--
}

switch语句

  • 可以使用任何类型或者是表达式作为条件语句
  • 不需要写break,符合条件时自动跳出
  • 若是希望执行下一个case,需要使用fallthrough语句
  • 支持一个初始化表达式(可以是并行方式),右侧需要跟分号
  • 左大括号必须和条件语句在一行
//传统使用方式
a := 1
    switch a {
    case 0:
        fmt.Println("a == 0")
    case 1:
        fmt.Println("a == 1")
    default:

    }

//在case中使用表达式
a := 1
    switch {
    case a >= 0:
        fmt.Println("a == 0")
        fallthrough //这样可以使满足case之后仍然进行判断,而不是直接跳出switch语句
    case a >= 1:
        fmt.Println("a == 1")
    default:
        fmt.Println("默认处理方式") //当所有的case都不满足使,会使用default来进行处理

    }

跳转语句(goto,break,continue)

  • 三个语句都可以配合标签使用
  • 标签名区分大小写,而且定义了的标签不使用的话会报编译错误
  • break和continue可以配合标签来跳出多层的循环
  • goto是调整执行的位置,与其他两个语句配合标签的结果不相同

Array数组

  • 定义数组的格式为:var <数组名> [数组长度]<数组类型> = 0
  • 数组长度也是类型的一部分,具有不同长度的数组为不同类型
  • 要区分指向数组的指针和指针数组的区别
  • 数组之间可以使用 == 和!=进行比较,但是不可以使用<或者是>比较
  • 可以使用new来创建数组,但是返回的只是一个指向数组的指针
  • Go支持多维数组
//可以使用数组的下标来指定具体某个数组元素的初始值,若不指定则全部使用类型零值
f := [20]int{19: 1}
    for i := 0; i < 20; i++ {
        fmt.Println(f[i])
    }

Slice切片

  • 切片本身不是数组,他是一种引用类型,他用来指向底层的数组
  • 他可以指向底层数组的全部或者是部分,可以作为可变长数组的替代方案
  • 使用len()获取元素个数,使用cap()获取容量大小
  • 一般使用make()来创建:make([]T,len,cap),len表示元素的个数,cap表示切片的容量,cap省略时默认和len值相同
L := [9]int{1, 2, 3, 4, 5, 6, 7, 8, 9}
s1 := L//指向底层数组L的全部
fmt.Println(s1)

//----------

L := [9]int{0, 1, 2, 3, 4, 5, 6, 7, 8}
s1 := L[3:6] //指向底层数组L的部分,指向的是数组中下标为3,4,5的数
fmt.Println(s1)

//-----------
sl := make([]int, 3, 9)//[]int----所指向的类型,3----切片的长度,9----切片容量

Reslice

  • Reslice时索引以被slice的切片为准
  • 索引不可以超过被slice的切片的容量值
  • 索引越界不会导致底层数组的重新分配,而是引发错误
L := [9]int{0, 1, 2, 3, 4, 5, 6, 7, 8}

    slice := L[2:7] //输出为2,3,4,5,6
    reslice := slice[1:4]//在slice的基础上再进行slice,输出为3,4,5
    fmt.Println(reslice)

Append

  • 可以在slice尾部追加元素
  • 可以将一个slice追加在另一个slice的尾部
  • 如果最终长度未超过追加到slice的容量,那么就返回原始slice
  • 如果最终长度超过最忌到的slice的容量,那么就重新分配数组并拷贝元素数据
L := [9]int{0, 1, 2, 3, 4, 5, 6, 7, 8}
slice := L[2:7]
fmt.Printf("%v %p", slice, slice)
slice = append(slice, 2, 3, 4, 5)
fmt.Printf("%v %p", slice, slice)

Copy

L := [9]int{0, 1, 2, 3, 4, 5, 6, 7, 8}
slice := L[2:7]
slice2 := []int{1, 2, 3}
copy(slice, slice2)//将slice2中的元素拷贝到slice中
fmt.Println(slice, slice2)
//slice输出为[1,2,3,3,4,5,6,7,8]

Map

  • 和其他语言中的哈希表类似,以Key-Value的形式来存储值
  • Key必须是支持==或者是!=比较运算的类型,不可以是函数,map,或者是slice
  • Map查找比线性搜索快很多,但是比使用索引访问慢很多
  • Map使用make创建,支持:=的声明赋值方式
  • 使用len()函数获取元素个数,在超出容量之后会自动扩容
  • 键值对不存在时自动添加,使用delete删除键值对
  • 使用for range对map和slice进行遍历操作
//声明map变量:var关键字 变量名 map关键字 [Key类型]Value类型
var m map[int]string 


//初始化map变量
m = map[int]string{1:"ok",2:"hello"}
//或者是
m = make(map[int]string)

//声明并赋值
var m map[int]string := make(map[int]string)

//其实map[int]string整体是作为一个类型名称的

//增加
m[1] = "ok"
//删除
delete(m, 1)

//多层map的嵌套
var m map[int]map[int]string     //map的value类型为map
    m = make(map[int]map[int]string) //对最外层的map进行初始化
    m[1] = make(map[int]string)      //对内层中Key为1的map进行初始化
    m[1][1] = "hello"

    fmt.Println(m[1][1])

//利用多返回值确定map是否初始化
data, ok := m[1][1]
    if !ok {
        m[1] = make(map[int]string)
    }
    m[1][1] = "hello"
    data, ok = m[1][1]
    fmt.Println(data, ok)

//通过for range来遍历map和slice,但是所有对value的操作都是对map和slice拷贝的操作
for k,v := range map{
        //k是key,v是value
}

Function函数

  • 定义函数使用关键字func,左起大括号不能另起一行
  • 不支持函数嵌套,重载和默认参数
  • 支持的特性:无需声明函数原型,不定长形参,多返回值,命名返回值参数,匿名函数,闭包
  • 函数也可以作为一种类型使用,赋值
//func关键字 函数名 形参列表,返回值列表
func A(data1 int,data2 int,data3 int) (a int, b int, c string) {
    a, b, c = 1, 2, "hello"
    fmt.Println(d)
    return a, b, c
}

//go中支持不定长参数
func A(d ...int) (a int, b int, c string) {
    //通过在类型名前加...来使用不定长参数,但是不定长参数只能作为参数列表的最后一个参数进行声明
    //不定长参数传入的其实是一个slice切片,但是其实际上是传入的一个值拷贝,在函数内修改不会影响原值

}

匿名函数

不给函数指定名称,而是直接将其作为变量类型使用

//匿名函数,直接将函数作为变量进行使用
    a := func() {
        fmt.Println("匿名函数")
    }
    a()

函数闭包

//定义返回值为一个函数
func closure(x int) func(int) int {
    return func(y int) int {
        return x + y
    }
}
//使用函数
f := closure(10)
fmt.Println(f(1))

defer

  • 在函数体执行结束之后按照调用顺序的相反顺序逐个执行
  • 即使函数发生严重错误也会执行
  • 支持匿名函数的调用
  • 常用于资源释放,清理等工作
  • 在函数return之后还可以对函数的计算结果进行修改
//打印结果会是a,c,b
func main{
    fmt.Println("a")
    defer fmt.Println("b")
    defer fmt.Println("c")
}

//打印结果是3,3,3,因为传入的是一个引用,所以当循环体执行完之后i指向的是3,然后开始反向的顺序执行defer,所以会是3,3,3
func main(){
    for i := 0; i < 3; i++ {
        defer func() {
            fmt.Println(i)
        }()
    }
}

Panic/Recover:

go中没有try/catch语句来进行异常处理,都是用panic和recover来就进行

  • panic可以在需要的地方将程序中断,不再执行
    ,可以在任何地方引发

  • recover可以配合defer函数来进行程序的恢复运行,但是只有在defer调用的函数中有效

func main(){
    B()
    C()
    D()
}
func B() {
    fmt.Println("B")
}

func C() {

    //defer函数必须在panic之前,不然无法注册的defer函数
    //recover只能在defer调用的函数内使用
    defer func() {
        if err := recover(); err != nil {
            fmt.Println("Recover in C")
        }
    }()
    panic("Panic in c")
}

func D() {
    fmt.Println("D")
}

Struct

  • 定义方法:type< Name > struct{},和C语言很相似
  • 结构之间可以相互的组合,但是没有继承
  • 同样遵循命名的可见性规则
type Person struct {
    Name string
    Age  int
}

//匿名结构,不需要为定义的结构命名,直接定义好结构之后使用
a := struct {
        Name  string
        Color string
    }{
        Name:  "cai",
        Color: "Red",
    }
//在初始化时,内部的匿名结构不可以通过指定字段名来命名,只能通过操作符'.'来初始化。
type Person struct {
    Name   string
    Age    int
    Family struct {
        Mom string
    }
}
//对结构体进行初始化
per := Person{
        Name: "wqc",
        Age:  10,
}
//只能这样初始化内部的结构体
per.Family.Mom = "妈妈"

struct中的面向对象

在Go中不存在继承关系,主要是通过组合的方式来实现一些公有属性的抽象。

type People struct {
    Name string
    Age  int
    Sex  int
}

type Student struct {
    People
    Sid int
}
//对使用结构组合的结构进行初始化
xiaoMing := Student{
        Sid: 123,
        People: People{
            Name: "xiaoMing",
            Age:  12,
            Sex:  1,
        },
    }

    xiaoMing.Age = 100
    xiaoMing.Name = "whh"
    xiaoMing.Sex = 11

    fmt.Println(xiaoMing)

Method方法

  • Go中通过显式说明receiver来实现与某个类型的组合

  • 只能为同一个包中的类型定义方法

  • Receiver可以是类型的值或者是指针

  • 不存在方法的重载

  • 可以使用值或者是指针来调用方法,编译器会自动完成转换

  • 如果外部结构和嵌入结构存在同名方法,则优先调用外部结构的方法

  • 类型别名不会拥有底层类型所附带的方法

  • 方法可以使用结构中非公开的字段

//方法的定义方式
type A struct {
    Name string
}

//func(要绑定的结构名) 函数名(参数列表)(返回值列表)

//值传递方式传入的只是一个拷贝,无法对外部的值产生影响
func (a A) Print(x int) (flag bool) {
    fmt.Println("1")
}
//指针传递,传入的是一个地址的拷贝,可以修改外部的值
func (a *A) Print(x int) (flag bool) {
    fmt.Println("1")
}

//在Go中不允许方法重载,以上边为例:也就是说结构体A只能有一个叫Print的方法。

Interface接口

  • 接口是一个或者是多个方法签名的集合

  • 只要某个类型拥有该接口的所有方法签名,计算实现该接口,无需显示声明实现了那个接口,称为structural Typing

  • 接口只有方法声明,没有实现,没有字段

  • 接口可以嵌入其他接口或者是结构中去

  • 只有当接口存储的类型和对象都为nil时接口才等于nil

  • 将对象赋值给接口时会发生拷贝,而接口内部存储的是指向这个复制品的指针,既然无法修改复制品的状态,也无法回去指针

  • 空接口可以最为任何类型数据的容器

//定义接口
type USB interface {
    Name() string
    Connect()
}

//定义结构
type PhoneConnector struct {
    name string
}

//为结构声明实现了接口的方法
func (pc PhoneConnector) Name() string {
    fmt.Println("Name ")
    return pc.name
}
//为结构声明实现了接口的方法
func (pc PhoneConnector) Connect() {
    fmt.Println("Connect...", pc.name)
}

类型断言

  • 通过类型断言的ok patteern可以判断接口中的数据类型
  • 使用type switch则可以针对空接口进行比较全面的类型判断

接口转换

  • 可以将拥有超集的接口转换为子集的接口

Reflecttion反射

  • 反射可以提高程序的灵活性,使得interface有更大的发挥余地
  • 方式使用TypeOf和ValueOf函数重接口中获取目标对象信息
  • 反射会将匿名字段作为独立字段
  • 想要利用反射修改对象状态,前提是interface.data是settable
  • 可以通过反射动态调用方法

package main

import (
    "fmt"
    "reflect"
)

type User struct {
    Id   int
    Name string
    Age  int
}

func (user User) Hello() {
    fmt.Println("Hello World")
}

func Info(o interface{}) {
    t := reflect.TypeOf(o)
    fmt.Println(t.Name())

    //获取所包含的字段的信息
    v := reflect.ValueOf(o)
    for i := 0; i < t.NumField(); i++ {
        f := t.Field(i)
        val := v.Field(i).Interface()
        fmt.Println(f.Name, f.Type, val)
    }
    //获取所包含的方法信息
    for i := 0; i < t.NumMethod(); i++ {
        m := t.Method(i)
        fmt.Println(m.Name, m.Type)

    }
}

func main() {
    u := User{1, "ok", 12}
    Info(u)
}

可以通过使用反射来获取变量的地址信息,然后直接对地址中所存的信息进行修改


x := 9
v := reflect.ValueOf(&x)
v.Elem().SetInt(999)
fmt.Println(x)

通过反射来修改接口中的字段值
- 首先判断是否获取到字段,而且值可以被修改
- 然后检验是否获取到所需要的字段
- 修改字段值

func Set(o interface{}) {
    //获取接口中的字段值
    v := reflect.ValueOf(o)
    //检验是否获取正确
    if v.Kind() == reflect.Ptr && !v.Elem().CanSet() {
        fmt.Println("XXX")
        return
    } else {
        v = v.Elem()
    }

    //获取具体某一字段
    f := v.FieldByName("Name")

    //检验是否成功获取到
    if !f.IsValid() {
        fmt.Println("错误")
    }

    //通过set方法修改字段值
    if f.Kind() == reflect.String {
        f.SetString("正确")
    }

}

Concurrency并发

高并发是Go的核心亮点,其实goroutine是官方实现的“超级线程池”,每个实例4-5KB的占内存占用和由于实现机制而大幅减少的创建和销毁开销。通过通讯来进行消息交互,而不是通过共享内存来通讯。

通讯方式Channel

  • Channel是goroutine沟通的桥梁,大都是阻塞同步的

  • 通过make创建,通过close关闭

  • 可以使用for range来迭代不断操作Channel

  • 可以设置通道为单项或者是双向

  • 可以设置缓存大小,在未被填满前不会发生阻塞

func main() {
    //声明一个通道
    channel := make(chan bool)

    go func() {
        fmt.Println("Go Go Go")
        //向通道中写入数据
        channel <- true
        //关闭通道
        close(channel)
    }()

    //通过for range从通道中读取数据
    for v := range channel {
        fmt.Println(v)
    }

}

Select

  • 可以处理一个或多个channel的发送和接收

  • 同时有多个可用的channel是按随机顺序处理

  • 可用空的select来阻塞main函数

  • 可设置超时机制

猜你喜欢

转载自blog.csdn.net/wqc_csdn/article/details/54605654