变量
命名规范
变量需要以小驼峰方式来命名,列如:
var studentName string
声明方式
- 直接声明类型
var childName string
childName = "mark"
- 直接赋值
var childName="mark"
- 简短赋值
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语言不存在指针操作,只有两个符号
&
表示取地址*
根据地址取值
//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函数不是原子操作是分为两步进行:
- 返回值赋值
- 真正的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的关系:
- 一个操作系统线程对应用户态多个goroutine。
- go程序可以同时使用多个操作系统线程。
- goroutine和OS线程是多对多的关系,即m:n
来源:CSDN
作者:soga_ht
链接:https://blog.csdn.net/u013352037/article/details/104671801