go笔记

变量

命名规范

变量需要以小驼峰方式来命名,列如:

var studentName string
声明方式
  1. 直接声明类型
var childName string
childName = "mark"
  1. 直接赋值
var childName="mark"
  1. 简短赋值
childName:="mark"
  • 简短赋值只能用在func内

常量

定义方式

const pi = 3.1415926
const (
    n1 = 100
    n2
    n3 //n2,n3值与上面n1一致
)

iota

go语言中常量计数器,只能在常量中使用。初始化时为0,const中每新增一行常量声明将使iota计数一次

const (
    a=iota //0
    b //1
    c //2
    _
    d //4
)

字符串

go语言中字符串使用双引号,单引号是字符

name:="mark"
desc:="good"
personDesc := name+desc
personDesc:=fmt.Sprintf("%s%s",name,desc)
//字符串分割
info:="ddd/ddd/dddd/dd"
ret:=strings.Split(info,"/")
//包含
strings.Contains
//判断前缀和后缀
strings.HasPrefix
strings.HasSuffix
//判断字符串位置
strings.Index
strings.LastIndex
//拼接
strings.Join
//判断是否为中文字符
unicode.Is(unicode.Han, '中')

逻辑控制

if

if  表达式{
    分支1
} else if 表达式 {
    分支2
} else {
    分支3
}if作用域中声明的变量只能用于if判断中

for

for 初始语句;条件语句;结束语句 {
    循环体语句
    // break;continue与其他语言一致
}

//死循环
for {
    循环体
}
/*for range 键值循环
数组,字符串,切片返回索引和值
map返回key、value
channel返回通道内的值
*/
for key,value:=range list{
    循环体
}
// 九九乘法表
for i := 1; i < 10; i++ {     
    for j := 1; j <= i; j++ {          
        fmt.Printf("%d*%d=%d ", i, j, i*j)     
        }    
        fmt.Println()
}


switch case

switch{
    case "A":
        执行语句
    case "B":
        执行语句
    //fallthrough执行满足需求的下一个case,必须放在case的最后一行
}

goto

跳出循环直达下个,多层循环跳出时,可以使用goto直接跳出所有循环体;一般不建议使用

运算符

与所有语言相同

数组

var 变量名[元素数量]类型
//数组元素数量不同就不是同一个类型,不能做比较
a1 = [3]bool{true,false,false}
//自动计算长度数组
a:=[...]int{1,2,3,4}
//根据索引初始化
a3:=[5]int{0:1,4:2}
//多维数组
var a4 [3][2]int
a4 = {[2]int{1,2},[2]int{3,4},[2]int{5,6}}
//数组是值引用

切片

切片(slice)是一个具有相同类型元素的可变长度的序列

//定义一个存放int类型元素的切片
var s1 []int
var s2 []string
s1=[]int{1,2,3}
s2=[]string{"中国","江苏","南京"}
//len()长度  长度就是本身元素的个数,cap()容量,容量是指从切片指向的第一个元素到数组最后
// make函数创建切片
s3:=make([]int,3,10) //长度为3,容量为10
//支持遍历方法与数组相同
//赋值后为引用类型
s4:=s1
s4[0]=4 //s1=[]{4,2,3}
//append方法,调用append函数必须用原来的切片接收返回值
s1 = append(s1,4)// append追加,原来的底层数组长度不够的时,会新换一个底层数组
s5=[]int{3,4,5}
s1 = append(s1,s5...)//...表示拆开
//copy方法,copy方法是值拷贝,长度需要自己赋值,修改拷贝后的值没有影响
s6 = make([]int,3,3)
copy(s6,s5)
//切片排序
s7=[...]int{0,2,4,1,3,6,8,7}
sort.Ints(s7[:])

指针

Go语言不存在指针操作,只有两个符号

  1. & 表示取地址
  2. * 根据地址取值
//new(T)函数会申请一个新的内存地址,返回的是T的指针类型
//make也用于内存的分配,区别于new,它只能用于slice,map,chan的内存创建,而且返回的是类型本身,而不是他们的指针类型

map

map是引用类型,初始化的时候必须要申请内存空间

m:=make(map[string]int)
//查询建是否存在
value,ok=m["键名"]
//遍历
for k,v:=range m{
}
//删除
delete(m,"键名")
//排序,先将key排序,再通过key去取value
m := make(map[string]int, 4)
m["ss"] = 2
m["sss"] = 3
m["a"] = 1
m["b"] = 4
a := make([]string, 10, 12)
for key := range m { 
    a = append(a, key)
    }
sort.Strings(a)
for _, key := range a {
    fmt.Println(key, m[key])
    }
//元素类型为map的切片
s := make([]map[string]int, 2, 10)
s[0] = make(map[string]int)
s[0]["a"] = 1
fmt.Println(s[0])

函数

func sum(x int,y int)(ret int){
    ret=x+y
    return
}
//go中没有默认参数的概念
func f(x string,y ...int)(){
    //y是slice类型
}

defer语句

defer定义的语句会在函数返回之前再执行,多个defer函数按照使用的顺序反向执行

可以用在io关闭,socket关闭,数据库关闭等。

go语言中return函数不是原子操作是分为两步进行:

  1. 返回值赋值
  2. 真正的RET返回

函数中如果存在defer语句,执行时机在第一步和第二步之间


func f1() int {
        x := 5
        defer func() {
                x++
        }()
        return x
}

func f2() (x int) {
        defer func() {
                x++
        }()
        return 5}

func f3() (y int) {
        x := 5
        defer func() {
                x++
        }()
        return x
}func f4() (x int) {
        defer func(x int) {
                x++
        }(x)
        return 5}func main() {
        fmt.Println(f1())//5
        fmt.Println(f2())//6
        fmt.Println(f3())//5
        fmt.Println(f4())}//5

变量作用域

  • 函数查找变量的顺序:先在函数内部查找,找不到就往函数外面查找,一直找到全局。
  • 函数内的变量作用域只在函数内部。
  • 语句块定义变量作用域只在语句块内部。

函数类型与变量

  • 函数可以作为参数
  • 函数可以作为返回值

闭包

闭包指的是一个函数和与其相关的引用环境组合而成的实体。简单来说,闭包=函数+引用环境

递归函数

类型

type后面声明的是类型

type myInt int //定义类型
type muInt = int//类型别名

结构体

type 类型名 struct {
    字段名 字段类型
    字段名 字段类型
    …
}

//定义一个人结构体
type person struct {
        name string
        city string
        age  int8
 }

构造函数

  • 返回一个结构体变量的函数,用new开头
  • 返回结构体中各属性之间需要添加,

方法

  • 方法是作用于特定类型的函数
  • 接收者表示的是调用该方法的具体类型变量,用类型的首写字母小写表示
package main
import "fmt"
func main() {
    d1:=newDog("mimi")
    d1.wang()
}
type dog struct {
    name string
}
func newDog(name string) dog {
    return dog{
        name: name,
    }
}
func (d dog) wang() {
    fmt.Printf("%s:汪汪汪", d.name)
}


func (接收者变量 接收者类型) 方法名(参数列表) (返回参数) {
    函数体
}//优先使用指针接收者

结构体模拟实现继承


package main
import "fmt"
type animal struct {
    name string
}
type dog struct{
    feet int
    animal
}
func main() {
    d1:=dog{
        animal:animal{name:"dddd"},
        feet:4,
    }
    fmt.Println(d1.name)
    d1.run()
}
func (d dog)wang(){
    fmt.Printf("%s:wang",d.name)
}
func (a animal) run() {
    fmt.Printf("%s会动", a.name)
}

结构体与json


package main
import (
    "encoding/json"
    "fmt"
)
type person struct {
    Name string `json:"name"`
    Age  uint8  `json:"age"`
}
func main() {
    p := person{
        "aaa",
        12,
    }
    b, _ := json.Marshal(p)
    fmt.Println(string(b))
    c:=`{"name":"aaa","age":12}`
    var p2 person
    json.Unmarshal([]byte(c),&p2)
}


接口

接口是一种类型


type 接口类型名 interface{
    方法名1( 参数列表1 ) 返回值列表1
    方法名2( 参数列表2 ) 返回值列表2}
  • 接口名:使用type将接口定义为自定义的类型名。Go语言的接口在命名时,一般会在单词后面添加er,如有写操作的接口叫Writer,有字符串功能的接口叫Stringer等。
  • 接口名最好要能突出该接口的类型含义。方法名:当方法名首字母是大写且这个接口类型名首字母也是大写时,这个方法可以被接口所在的包(package)之外的代码访问。
  • 参数列表、返回值列表:参数列表和返回值列表中的参数变量名可以省略。

一个对象只要全部实现了接口中的方法,那么就实现了这个接口。换句话说,接口就是一个需要实现的方法列表

使用值接收者实现接口与使用指针接收者实现接口的区别?

  • 使用值接收者:结构体类型和结构体指针类型都能存
  • 指针接收者只能存结构体指针类型
    一般接口都传递指针类型

空接口

  • 使用空接口实现可以接收任意类型的函数参数。
//任意类型的map
    a := make(map[interface{}]interface{})
    a[1] = 2
    a["dd"] = "aaaa"

package

  • 包的路径从GOPATH下的src写起
  • 想被别的包调用函数名需要大写
  • 导入包后不想使用内部标识符,可以使用匿名导入
  • 在go语言中导入了包,默认会执行导入包的init()函数

文件操作

bufio读取

import (
    "bufio"
    "fmt"
    "io"
    "os"
)
func main() {
    file, err := os.Open("./aaa")
    defer file.Close()
    if err != nil {
        fmt.Println("文件打开发生错误:", err)
    }
    reader:=bufio.NewReader(file)
    for{
        line,err:=reader.ReadString('\n')
        if err==io.EOF{
            if len(line)!=0{
                fmt.Println(line)
            }
            fmt.Println("文件读完了")
            break
        }
        if err!=nil{
            fmt.Println("read file failed:",err)
        }
        fmt.Print(line)
    }
}
ioutil读取

一次性读出文件所有内容

import (
    "fmt"
    "io/ioutil"
)
func main() {
    file, err := ioutil.ReadFile("./aaa")
    if err != nil {
        fmt.Println("出现在错误:", err)
        return
    }
    fmt.Println(string(file))
}
写文件

os.OpenFile()函数能够以指定模式打开文件,从而实现文件写入相关功能。


func OpenFile(name string, flag int, perm FileMode) (*File, error) {
        ...}

name:要打开的文件名 flag:打开文件的模式。 模式有以下几种:

模式含义
os.O_WRONLY只写
os.O_CREATE创建文件
os.O_RDONLY只读
os.O_RDWR读写
os.O_TRUNC清空
os.O_APPEND追加

perm:文件权限,一个八进制数。r(读)04,w(写)02,x(执行)01。


import (
    "fmt"
    "os"
)
func main() {
    fileObject, err := os.OpenFile("./bbb", os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
    defer fileObject.Close()
    if err != nil {
        fmt.Println("发生错误:", err)
    }
    fileObject.WriteString("aaaaaaaaaaaaaa")
}


func main() {
        file, err := os.OpenFile("xx.txt", os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0666)
        if err != nil {
                fmt.Println("open file failed, err:", err)
                return
        }
        defer file.Close()
        writer := bufio.NewWriter(file)
        for i := 0; i < 10; i++ {
                writer.WriteString("hello沙河\n") //将数据先写入缓存
        }
        writer.Flush() //将缓存中的内容写入文件
        
func main() {
        str := "hello 沙河"
        err := ioutil.WriteFile("./xx.txt", []byte(str), 0666)
        if err != nil {
                fmt.Println("write file failed, err:", err)
                return
        }
        }
time
    
    //时间格式化
    now := time.Now()
    now.Format("2006-01-02 15:04:05")
strconv
//string转int
s := "12000"
i, err := strconv.Atoi(s)
//int转string
s,err:=strconv.Itoa(i)

并发

go为标识符

goroutine

goroutine对应的函数结束了,goroutine就结束了
main函数执行完了,由main函数创建的那些goroutine也都结束了

rand随机数
//  保证每次取的值不同
    rand.Seed(time.Now().UnixNano())
    i := rand.Int()
    j := rand.Intn(12)
等待goruntine执行完

var wg sync.WaitGroup
func f1(i int){
    defer wg.Done()
    time.Sleep(time.Microsecond*time.Duration(rand.Intn(10)))
    fmt.Println(i)
}
func main() {
    rand.Seed(time.Now().UnixNano())
    for i:=0;i<10;i++{
        wg.Add(1)
        go f1(i)
    }
    wg.Wait()
}
GPM
可增长的栈OS线程(操作系统线程)

一般都有固定的栈内存(通常为2MB),一个goroutine的栈在其生命周期开始时只有很小的栈(典型情况下2KB),goroutine的栈不是固定的,他可以按需增大和缩小,goroutine的栈大小限制可以达到1GB,虽然极少会用到这个大。所以在Go语言中一次创建十万左右的goroutine也是可以的。

goroutine调度

GPM是Go语言运行时(runtime)层面的实现,是go语言自己实现的一套调度系统。区别于操作系统调度OS线程。

  • G很好理解,就是个goroutine的,里面除了存放本goroutine信息外 还有与所在P的绑定等信息。
  • P管理着一组goroutine队列,P里面会存储当前goroutine运行的上下文环境(函数指针,堆栈地址及地址边界),P会对自己管理的goroutine队列做一些调度(比如把占用CPU时间较长的goroutine暂停、运行后续的goroutine等等)当自己的队列消费完了就去全局队列里取,如果全局队列里也消费完了会去其他P的队列里抢任务。
  • M(machine)是Go运行时(runtime)对操作系统内核线程的虚拟, M与内核线程一般是一一映射的关系, 一个groutine最终是要放到M上执行的;
    P与M一般也是一一对应的。他们关系是: P管理着一组G挂载在M上运行。当一个G长久阻塞在一个M上时,runtime会新建一个M,阻塞G所在的P会把其他的G 挂载在新建的M上。当旧的G阻塞完成或者认为其已经死掉时 回收旧的M。
    P的个数是通过runtime.GOMAXPROCS设定(最大256),Go1.5版本之后默认为物理线程数。 在并发量大的时候会增加一些P和M,但不会太多,切换太频繁的话得不偿失。单从线程调度讲,Go语言相比起其他语言的优势在于OS线程是由OS内核来调度的,goroutine则是由Go运行时(runtime)自己的调度器调度的,这个调度器使用一个称为m:n调度的技术(复用/调度m个goroutine到n个OS线程)。
    其一大特点是goroutine的调度是在用户态下完成的, 不涉及内核态与用户态之间的频繁切换,包括内存的分配与释放,都是在用户态维护着一块大的内存池, 不直接调用系统的malloc函数(除非内存池需要改变),成本比调度OS线程低很多。 另一方面充分利用了多核的硬件资源,近似的把若干goroutine均分在物理线程上, 再加上本身goroutine的超轻量,以上种种保证了go调度方面的性能。
    M:N:把m个goruntine分配给n个操作系统线程去执行
GOMAXPROCS

Go运行时的调度器使用GOMAXPROCS参数来确定需要使用多少个OS线程来同时执行Go代码。默认值是机器上的CPU核心数。例如在一个8核心的机器上,调度器会把Go代码同时调度到8个OS线程上(GOMAXPROCS是m:n调度中的n)。Go语言中可以通过runtime.GOMAXPROCS()函数设置当前程序并发时占用的CPU逻辑核心数。

Go语言中的操作系统线程和goroutine的关系:

  1. 一个操作系统线程对应用户态多个goroutine。
  2. go程序可以同时使用多个操作系统线程。
  3. goroutine和OS线程是多对多的关系,即m:n
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值