Go 基础 (一)

运行 hello world 报错

    package src
    
    import (
       "fmt"
    )
    
    func main() {
       fmt.Println("hello,world!Go!")
    }

go run test.go

go run: cannot run non-main package

packge main go中的package 不同于Java中的

Go Package Path

go get -v github.com/gpmgo/gopm

export GOPATH=/Users/liujifeng/go
export PATH="$GOPATH/bin"


go语言没有long 只有 int8 int16

uintptr 指针

rune类似java char 32位

complex64 complex128 复数(实部和虚部)欧拉公式

强制转换 float ->int int(float) java可以隐式转换

常量 const

const filename ="abc.text"
const(
  filename = "a"
  a = 1
  b= "2"
)   //小括号

枚举类型

const (
    cpp     = 1
    java    = 2
    android = 3
) 

const (
    kotlin = iota
    swift
    oc
)

const (
    kotlin = 1<<(10*iota)
    swift
    oc
)
//结果 1 1024 1048576

iota 表示自增

switch 不需要break

go java 备注
rune char
int64 long
strconv.Itoa String()
[]int int[]
interface{} Object

指针

值传递和引用传递
Go只有值传递一种方式
函数接受的参数都是copy一份传入函数中

func swpa(a,b *int){
    *a ,*b = *b,*a
}

swpa(&a,&b)
fmt.Println("a=%d, b=%d",a,b)

数组

数量写在数组前面。。

var arr [5]int
arr2:= [3]int{1,2,3}
arr3:=[...]int{1,2,3,4,5}

range 遍历

for k,v :range arr3{
  fmt,println("k= %d,v=%d",k,v)
}

可以通过_省略参数

调用func f(arr [10]int)会拷贝数组

Slice(切片)

FC541ED97069BDC66B6632C2112780FB.jpg

Slice是数组的一个视图
slice可以向后扩展,不可以向前扩展
s[i]不可以超越len(s),向后扩展不能超过cap的长度

    var arr = [...]int{1,2,3,4,5,6,7}
    var slice1 = arr[2:5]
    fmt.Println(slice1)
    fmt.Println(len(slice1),cap(slice1))

追加

切片每一次追加后都会形成新的切片变量,如果底层数组没有扩容,那么追加前后的两个切片变量共享底层数组,如果底层数组扩容了,那么追加前后的底层数组是分离的不共享的。如果底层数组是共享的,一个切片的内容变化就会影响到另一个切片,这点需要特别注意

func main() {
 var s1 = []int{1,2,3,4,5}
 fmt.Println(s1, len(s1), cap(s1))

 // 对满容的切片进行追加会分离底层数组
 var s2 = append(s1, 6)
 fmt.Println(s1, len(s1), cap(s1))
 fmt.Println(s2, len(s2), cap(s2))

 // 对非满容的切片进行追加会共享底层数组
 var s3 = append(s2, 7)
 fmt.Println(s2, len(s2), cap(s2))
 fmt.Println(s3, len(s3), cap(s3))
}

--------------------------
[1 2 3 4 5] 5 5
[1 2 3 4 5] 5 5
[1 2 3 4 5 6] 6 10
[1 2 3 4 5 6] 6 10
[1 2 3 4 5 6 7] 7 10

切片的扩容点

当比较短的切片扩容时,系统会多分配 100% 的空间,也就是说分配的数组容量是切片长度的2倍。但切片长度超过1024时,扩容策略调整为多分配 25% 的空间,这是为了避免空间的过多浪费。试试解释下面的运行结果。

s1 := make([]int, 6)
s2 := make([]int, 1024)
s1 = append(s1, 1)
s2 = append(s2, 2)
fmt.Println(len(s1), cap(s1))
fmt.Println(len(s2), cap(s2))
-------------------------------------------
7 12
1025 1344

上面的结果是在 goplayground 里面运行的,如果在本地的环境运行,结果却不一样

$ go run main.go
7 12
1033 1280

扩容是一个比较复杂的操作,内部的细节必须通过分析源码才能知晓,不去理解扩容的细节并不会影响到平时的使用,所以关于切片的源码我们后续在高级内容里面再仔细分析。

Map(字典)

-- go java
初始化 var m map[int]string = make(map[int]string)或者map[int]string{90:"优秀"} Map<Integer,String> map = new HasMap<>
取值 m[90] map.get(90)
删除 delete(m,90) map.remove(90)

如果map key不存在怎么办

delete函数没有返回值,如果key不存在,会做静默处理不会报错
正常的获取方法,当key不存在的时候,也会做静默处理,返回零值(int = 0 ,string = “”,boolean = false),所以不能通过返回值判断key是否存在
正确姿势如下

var score,ok = m[90]
if ok {

} else {

}

字典里面存的是啥

字典变量里存的只是一个地址指针,这个指针指向字典的头部对象。所以字典变量占用的空间是一个字,也就是一个指针的大小,64 位机器是 8 字节,32 位机器是 4 字节。


F98D4F5128B19DE9CD875A9AA792D83E.jpg
package main

import (
    "fmt"
    "unsafe"
)

func main() {
    var m = map[string]int{
        "apple":  2,
        "pear":   3,
        "banana": 5,
    }
    fmt.Println(unsafe.Sizeof(m))
}

------
8

思考题

package main

import "fmt"

func main() {
    var fruits = map[string]int {
        "apple": 2,
        "banana": 5,
        "orange": 8,
    }

    var names = make([]string, 0, len(fruits))
    var scores = make([]int, 0, len(fruits))

    for name, score := range fruits {
        names = append(names, name)
        scores = append(scores, score)
    }

    fmt.Println(names, scores)
}
---------------------
[apple banana orange] [2 5 8]

如果替换如下代码

var names = make([]string, len(fruits))
 var scores = make([]int,  len(fruits))

答案
[ apple banana orange] [0 0 0 2 5 8] 关系到切片 length 和cap的关系

结构体

结构体里面装的是基础类型、切片、字典、数组以及其它类型的结构体等等


4E4C999EF498CE829AFE9F0EEF234AD8.jpg

如果考虑效率的话,较大的结构体通常会用指针的方式传入和返回
如果要在函数内部修改结构体成员的话,用指针传入是必须的;因为在Go语言中,所有的函数参数都是值拷贝传入的,函数参数将不再是函数调用时的原始变量。

pp := &Point{1, 2}
pp := new(Point)
*pp = Point{1, 2}

make 和 new的区别

// The make built-in function allocates and initializes an object of type
// slice, map, or chan (only). Like new, the first argument is a type, not a
// value. Unlike new, make's return type is the same as the type of its
// argument, not a pointer to it. The specification of the result depends on
// the type:
//  Slice: The size specifies the length. The capacity of the slice is
//  equal to its length. A second integer argument may be provided to
//  specify a different capacity; it must be no smaller than the
//  length. For example, make([]int, 0, 10) allocates an underlying array
//  of size 10 and returns a slice of length 0 and capacity 10 that is
//  backed by this underlying array.
//  Map: An empty map is allocated with enough space to hold the
//  specified number of elements. The size may be omitted, in which case
//  a small starting size is allocated.
//  Channel: The channel's buffer is initialized with the specified
//  buffer capacity. If zero, or the size is omitted, the channel is
//  unbuffered.
func make(t Type, size ...IntegerType) Type

// The new built-in function allocates memory. The first argument is a type,
// not a value, and the value returned is a pointer to a newly
// allocated zero value of that type.
func new(Type) *Type

匿名成员

Go语言有一个特性让我们只声明一个成员对应的数据类型而不指名成员的名字;这类成员就叫匿名成员。匿名成员的数据类型必须是命名的类型或指向一个命名的类型的指针

type Point struct {
    X, Y int
}

type Circle struct {
    Center Point
    Radius int
}

type Wheel struct {
    Circle Circle
    Spokes int
}

var w Wheel
w.Circle.Center.X = 8
w.Circle.Center.Y = 8
w.Circle.Radius = 5
w.Spokes = 20

-----------------------------
------------------------------

type Circle struct {
    Point
    Radius int
}

type Wheel struct {
    Circle
    Spokes int
}
var w Wheel
w.X = 8            // equivalent to w.Circle.Point.X = 8
w.Y = 8            // equivalent to w.Circle.Point.Y = 8
w.Radius = 5       // equivalent to w.Circle.Radius = 5
w.Spokes = 20

函数

EBC13F95B5483C27E9C5BECA307E691E.jpg

1、闭包的定义

函数可以嵌套定义(嵌套的函数一般为匿名函数),即在一个函数内部可以定义另一个函数。Go语言通过匿名函数支持闭包
闭包是由函数及其相关引用环境组合而成的实体(即:闭包=函数+引用环境)。
闭包只是在形式和表现上像函数,但实际上不是函数。函数是一些可执行的代码,函数代码在函数被定义后就确定,不会在执行时发生变化,所以一个函数只有一个实例。闭包在运行时可以有多个实例,不同的引用环境和相同的函数组合可以产生不同的实例。

2、闭包的本质

闭包是包含自由变量的代码块,变量不在代码块内或者任何全局上下文中定义,而是在定义代码块的环境中定义。由于自由变量包含在代码块中,所以只要闭包还被使用,那么自由变量以及引用的对象就不会被释放,要执行的代码为自由变量提供绑定的计算环境。
闭包可以作为函数对象或者匿名函数。支持闭包的多数语言都将函数作为第一级对象,即函数可以存储到变量中作为参数传递给其它函数,能够被函数动态创建和返回。

可以返回多个参数

func div (a,b int)(int,int){
    return a/b,a%b
}

我们在调用的时候,如果只想使用其中几个参数,可以使用下划线屏蔽不需要的参数

a,_:=div(12,5)

函数可以接收函数作为参数

func forEachNode(n *html.Node, pre, post func(n *html.Node)) {
    if pre != nil {
        pre(n)
    }
    for c := n.FirstChild; c != nil; c = c.NextSibling {
        forEachNode(c, pre, post)
    }
    if post != nil {
        post(n)
    }
}

这个就会引入一个函数值
函数值调用方式

  func square(n int) int { return n * n }
  f := square
  fmt.Println(f(3)) // "9"

函数类型的零值是nil。调用值为nil的函数值会引起panic错误:

匿名函数

func squares() func() int {
    var x int
    return func() int {
        x++
        return x * x
    }
}
func main() {
    f := squares()
    fmt.Println(f()) 
    fmt.Println(f()) 
    fmt.Println(f()) 
    fmt.Println(f()) 
}

函数值不仅仅是一串代码,还记录了状态。在squares中定义的匿名内部函数可以访问和更新squares中的局部变量,这意味着匿名函数和squares中,存在变量引用。这就是函数值属于引用类型和函数值不可比较的原因。Go使用闭包(closures)技术实现函数值,Go程序员也把函数值叫做闭包。

可变参数

func sum(vals...int) int {
    total := 0
    for _, val := range vals {
        total += val
    }
    return total
}

fmt.Println(sum(1, 2, 3, 4)) // "10"

values := []int{1, 2, 3, 4}
fmt.Println(sum(values...)) // "10"

虽然在可变参数函数内部,...int 型参数的行为看起来很像切片类型,但实际上,可变参数函数和以切片作为参数的函数是不同的。

func f(...int) {}
func g([]int) {}
fmt.Printf("%T\n", f) // "func(...int)"
fmt.Printf("%T\n", g) // "func([]int)"

可变参数函数经常被用于格式化字符串。下面的errorf函数构造了一个以行号开头的,经过格式化的错误信息。函数名的后缀f是一种通用的命名规范,代表该可变参数函数可以接收Printf风格的格式化字符串。

Deferred函数

你只需要在调用普通函数或方法前加上关键字defer,就完成了defer所需要的语法。当defer语句被执行时,跟在defer后面的函数会被延迟执行。直到包含该defer语句的函数执行完毕时,defer后的函数才会被执行,不论包含defer语句的函数是通过return正常结束,还是由于panic导致的异常结束

func bigSlowOperation() {
    defer trace("bigSlowOperation")() // don't forget the
    extra parentheses
    // ...lots of work…
    time.Sleep(10 * time.Second) // simulate slow
    operation by sleeping
}
func trace(msg string) func() {
    start := time.Now()
    log.Printf("enter %s", msg)
    return func() { 
        log.Printf("exit %s (%s)", msg,time.Since(start)) 
    }
}

defer语句中的函数会在return语句更新返回值变量后再执行,又因为在函数中定义的匿名函数可以访问该函数包括返回值变量在内的所有变量,所以,对匿名函数采用defer机制,可以使其观察函数的返回值。

func double(x int) (result int) {
    defer func() { fmt.Printf("double(%d) = %d\n", x,result) }()
    return x + x
}
_=double(4)

接口

接口由使用者定义
接口的实现是隐式的 只需要实现具体的方法。

A,B 两个接口,A接口是B接口的子集,则B接口实例可以赋值给A接口

type Poster interface{
} 

type assertion

object.(Type)

if newObject,ok: = object.(Type); ok{

}else{
}

接口变量自带指针
接口变量同样采用值传递,几乎不需要使用接口的指针
指针接受者实现只能以指针方式使用;值接受者都可
interface{} 类似Java中的Object

接口组合

type Smellable interface {
  smell()
}

type Eatable interface {
  eat()
}

type Fruitable interface {
  Smellable
  Eatable
}

接口变量的赋值

变量赋值本质上是一次内存浅拷贝,切片的赋值是拷贝了切片头,字符串的赋值是拷贝了字符串的头部,而数组的赋值呢是直接拷贝整个数组。接口变量的赋值会不会不一样呢?接下来我们做一个实验

package main

import "fmt"

type Rect struct {
    Width int
    Height int
}

func main() {
    var a interface {}
    var r = Rect{50, 50}
    a = r

    var rx = a.(Rect)
    r.Width = 100
    r.Height = 100
    fmt.Println(rx)
}

------
{50 50}

从上面的输出结果中可以推断出结构体的内存发生了复制,这个复制可能是因为赋值(a = r)也可能是因为类型转换(rx = a.(Rect)),也可能是两者都进行了内存复制。那能不能判断出究竟在接口变量赋值时有没有发生内存复制呢?不好意思,就目前来说我们学到的知识点还办不到。到后面的高级阶段我们将会使用 unsafe 包来洞悉其中的更多细节。不过我可以提前告诉你们答案是什么,那就是两者都会发生数据内存的复制 —— 浅拷贝。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容

  • 出处---Go编程语言 欢迎来到 Go 编程语言指南。本指南涵盖了该语言的大部分重要特性 Go 语言的交互式简介,...
    Tuberose阅读 18,310评论 1 47
  • 1.安装 https://studygolang.com/dl 2.使用vscode编辑器安装go插件 3.go语...
    go含羞草阅读 1,496评论 0 6
  • 云染:“那我动手了,实在是太生气了。” 云华:“好。 (不能让他知道我变了,让他以为我还和以前一样,他才不会提防我...
    君兮阅读 330评论 0 4
  • 造化弄人 阿弥陀佛 我还是想写点什么。 也许不能写, 毕竟这是违背了很多规则的。 但是, 还是想写, 这里是精神的...
    菩提大叔阅读 142评论 0 1
  • 1 早上小区的微信群里,炸开锅了,十分钟不到,已经聚集了几百条微信。上班时间,大家怎么有时间聊天。打开一看,...
    人在江湖Violet阅读 329评论 0 0