编程基础 0001_基础教程

Translation is not yet available. Showing original content.

go

什么是 Go
是一门并发支持、垃圾加收的编译型系统编程语言,具有静态编译语言的高性能和动态语言的,主要特点如下

  • 类型安全和内存安全
  • 以非常直观和极低代价的方案实现高并发
  • 高效的垃圾回收机制
  • 快速编译(同时解决了 C 语言中头文件太多的问题)
  • UTF-8 支持

安装

  • 源码安装
  • 标准包安装
  • 第三方安装

标准包安装,一路下一步。安装完后,会自动添加如下环境变量,使用go env查看

GOARCH="amd64" # cup架构
GOBIN="/usr/local/go/bin"
GOCACHE="/Users/willzhao/Library/Caches/go-build"
GOEXE=""
GOFLAGS=""
GOHOSTARCH="amd64"
GOHOSTOS="darwin"
GOOS="darwin"
GOPATH="/Users/willzhao/go"
GOPROXY=""
GORACE=""
GOROOT="/usr/local/go"
GOTMPDIR=""
GOTOOLDIR="/usr/local/go/pkg/tool/darwin_amd64"
GCCGO="gccgo"
CC="clang"
CXX="clang++"
CGO_ENABLED="1"
GOMOD=""
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=/var/folders/3q/z9zfyn256718rwvw071831tr0000gn/T/go-build200798760=/tmp/go-build -gno-record-gcc-switches -fno-common"

根据约定,GOPATH 下需要建立 3 个目录

  • bin (存放编译后生成的可执行文件,这个文件里生成的文件一般拿到上一级去执行,要不可能会提示找不到依赖包)
  • pkg (存放编译后生成的包文件)
  • src (存放项目源码)

常用命令

  • go get 获取远程包(如果在 git 上获取要先安装 git,如果是在 google code 上下载远程包的话需要安装 hg
  • go run 直接运行程序
  • go build 测试编译,检查是否有编译错误(什么样的源码可以编译成可执行文件呢? package 名称为 main 的,如果不是就不会生成可执行文件)
  • go fmt 格式化源码(部分 ide 在保存时自动调用)
  • go install 编译包文件并编译整个程序(会找 main 的包,在 bin 文件夹中生成可执行文件)
  • go install是建立在 GOPATH 上的,无法在独立的目录里使用go install
  • GOPATH下的 bin 目录放置的是使用go install 生成的可执行文件,可执行文件的名称来自于编译时的包名
  • go install输出目录始终为 GOPATH 下的 bin 目录,无法使用-o 附加参数进行自定义
  • GOPATH下的 pkg 目录放置的是编译期间的中间文件
  • go test 运行测试文件,名字形如 xxx_test.go,正常的文件不在命名成这样
  • godoc 查看文档 godoc fmt,可以建立本地的网站来查看 godoc -http:8080

hello world

package main
import (
    "fmt"
)
func main(){
    fmt.Print("Hello World! 你好世界!") // 注意Print
}
// 好多的教程没提,这里最好是使用idear的开发环境来做 它可以设置一个project path
// 目录结构 如上面所提src pkg bin
// 通过查看idear的run configuration确认,编译命令执行如下
/*
GOROOT=/usr/local/go #gosetup
GOPATH=/Users/willzhao/Documents/Develop/go/hello_world:/usr/local/go #gosetup
/usr/local/go/bin/go build -i -o /Users/willzhao/Documents/Develop/go/hello_world/bin/HelloWorldRun main #gosetup 注意main是包名
/Users/willzhao/Documents/Develop/go/hello_world/bin/HelloWorldRun #gosetup
*/

Go 的内置关键字(25 个)

25 个,并且都是小写

break default func interface select
case defer go map struct
chan else goto package switch
const fallthrough if range type
continue for import return var

注释

  • // 单行
  • /* */ 多行

程序一般结构

  • Go 程序是通过 package 来组织(与 Python 类似) 必须放在非注释的第一行。
  • 只有 package 名称为 main 的包可以包含 main 函数,一般建议 package 的名称和目录名一致 package 名称不要使用驼峰标记法,使用下划线
  • 别名调用 如import std "fmt"
  • 省略调用 如 import . "fmt" 使用时可以直接使用Print来调用(不建议这样使用,导致功能不知道是哪个包导入的)
  • 省略调用和别名调用不能同时使用
  • 一个可执行程序有且仅有一个 main 包
  • 通过 import 关键字来导入其它非 main 包,导入多个包时可以用一个 import 加括号,导入包如果没有用,编译时会报错
  • 通过 const 关键字来进行常量定义
  • 通过函数体外部使用 var 关键字来进行全局变量的声明与赋值
  • 通过 type 关键字来进行结构 struct 或接口 interface 声明
  • 通过 func 关键字来进行函数声明

可见性规则

Go 语言中,使用大小写来决定常量、变量、类型、接口、结构或函数是否可以被外部包所调用,可以快速确定是否调用错误

  • 首字母小写即为 private
  • 首字母大写即为 public

声明多个常量变量

// 常量定义
const (
    PI = 3.14
    const1 = "1"
    const2 = 2
    const3 = 3
)
// 全局变量声明与赋值,不能在函数体内声明
var (
    name = "gopher"
    name1 = "1"
    name2 = 2
    name3 = 3
)
// 一般类型声明
type (
    newType int
    type1 float32
    type2 string
    type3 byte
)

数据类型

  • 布尔型 bool
  • 长度 1 个字节
  • 取值范围 true, false
  • 不能用其它值代表 true 或 false
  • 整型 int/uint
  • 根据平台可能为 32 或 64 位
  • 8 位整型 int8/uint8
  • 长度 1 字节
  • -128~127/0~255
  • 字节型 byte(uint8 别名)
  • 16 位整型 int16/uint16
  • 长度 2 字节
  • -32768~32767/0~65536
  • 32 位整型 int32(rune)/uint32
  • 长度 4 字节
  • 64 位整型 int64/uint64
  • 浮点型 float32/float64
  • 长度 4/8 字节
  • 小数位精确到 7/15 小数位
  • 复数 complex64/complex128
  • 长度 8/16 字节
  • 足够保存指针的 32 位或 64 位整数型 uintptr
  • 其它值类型
  • array/struct/string
  • 引用类型
  • slice/map/chan
  • 接口类型 inteface
  • 函数类型 func

以上类型的默认类型

// 数值为0 bool是false string为"" 数组则根据它所指定的类型
// math.MinInt8 math有一些常量来定义值范围边界
package main
import (
    "fmt"
    "math"
)
type (
    byte int8
    rune int32
)

单个变量的声明与赋值

  • 变量声明格式:var 变量名 变量类型
  • 变量赋值格式:变量名称=表达式
  • 声明的同时赋值:var 变量名称 变量类型 = 表达式 这里的变量类型可以省略,编译器可以推断出来类型
package main

import (
    "fmt
)
func main(){
    var b int;
    // 或 变量声明与赋值的最简写法
    a:=1
}

多个变量的声明与赋

  • 全局变量的声明可以使用 var()的方式进和简写
  • 全局变量的声明不可以省略 var,但可以并行方式 即 var ()
  • 所有变量都可以使用类型推断
  • 局部变量不可以使用 var()的方式简写,只能使用并行方式
package main

import (
    "fmt"
)
var (
    aaa = "hello"
    sss,bbb= 1,2
    // ccc := 3 这方式是错的
)

func Test(){
    // 多变量声明
    var a,b,c,d int
    // 多个变量的赋值
    a,b,c,d = 1,2,3,4
    // 多个变量声明的同时赋值
    var e,f,g,h int = 5,6,7,8
    // 省略变量类型,由系统推断
    var i,j,k,l = 9,10,11,12
    // 多个变量声明与赋值的最简写法
    i,m,n,o :=13,14,15,16
    aa,_,cc,dd = 1,2,3,4 //_是忽略符号
}

类型转换:不存在隐式转换,所有类型转换必须显式声明,转换必须在两咱兼容的之间

类型与变量

package main
import (
    "fmt"
    "strconv"
)
func main(){
    var a int = 65
    b:=string(a)
    fmt.Print(b) // A
    b = strconv.Itoa(a)
    a = strconv.Atoi(b)
    fmt.Print(b)
    fmt.Print(a)
}

常量定义

  • 常量的值在编译时就已经确定
  • 常量定义格式与变量基本相同
  • 等号右侧必须是常量或者常量表达式
  • 常量表达式中的函数必须是内置函数
const a int = 1
const b = 'A'
const (
    c = a
    d = b
)
const (
    e = 1
    f
    g
)
// f g 等一上行定义的值
const (
    a,b = 1,"2"
    c,d
)
// c = 1 d="2" 格式要一致(与上一行)
// iota 从第一个计数
const (
    a = 'A'
    b = iota
    c = 'B'
    d = iota
)
// 65
// 1
// 66
// 3
// 在每个组中都是从0开始加1的
// 常量不想被外包使用,则定义时不要以大写母开头,比如使用划线或小写字母如c
// 可以做一个常量枚举
const (
    Monday = iota
    Tuesday
    Wednesday
    Thursday
    Friday
    Saturday
    Sunday
)

运算符

  • ^ ! (一元)
  • * / % \<\< >> & &^
  • + - | ^ (二元)
  • == != \< \<= >= >
  • \<- (专门用于 channel)
  • 位运算符 & | ^异或 &^(位清空 and not)
  • &&
  • ||

这里说明一下^&

// 计算x&^y 首先我们先换算成2进制  0000 0010 &^ 0000 0100 = 0000 0010 如果ybit位上的数是0则取x上对应位置的值, 如果ybit位上为1则取结果位上取0
package main

import (
    "fmt"
)

func main(){
   var x,y int = 2,4
   fmt.Print(x^&y) // 2
}

实现一个计算机存储单位枚举

const (
    _ = iota // B = iota
    KB float64 = 1 << (iota*10)
    MB float64 = 1 << (iota*10)
    GB float64 = 1 << (iota*10)
    TB float64 = 1 << (iota*10)
    PB float64 = 1 << (iota*10)
    EB float64 = 1 << (iota*10)
    ZB float64 = 1 << (iota*10)
    YB float64 = 1 << (iota*10)
)

指针

Go 虽然保留了指针,但与其它编程语言不同的是,在 Go 当中不支持指针运算”->“运算符,而直接采用"."选择符来操作指针目标对象的成员

  • 操作符”&“取变量地址,使”*“通过指针间接访问目标对象
  • 默认值为 nill 而 NULL
package main

import (
    "fmt"
)

func main(){
    a:=1;
    var b *int = &a
    fmt.Print(*b) // 1
}

递增递减

++ —— 在 Go 语言中是不能作为表达式而是作为语句,所以只能写在单独一行

条件表达式

  • if 没有括号,可以使用初始化语句,并且是块级作用域
  • 左括号必须放在 if 语句同一行
package main

import (
    "fmt"
)

func main(){
    a := 10
    if a :=4; a > 0 {
        fmt.Println(a)
    }
    fmt.Println(a)
}

循环语句 for

只有 for 一个循环语句,但它支持 3 种形式,

  • 初始化和步进表达式可以是多个值
  • 条件语句每次都会被重新检查,因此不建议在条件语句中使用函数,尽量提前计算好并以变量或常量代替
  • 左大括号必须和条件语句一行
func main(){
    a:=1
    for {
        a++
        if a>3 {
            break
        }
    }
    fmt.Printf(a)
}
// 2
func main() {
    a := 1
    for a <= 3 {
        a++
    }
    fmt.Println(a)
}
// 3
func main() {
    a := 1
    for i := 0;i<3;i++ {
        a++
    }
    fmt.Println(a)
}

switch

  • 可以使用任何类型或表达式作为条件语句
  • 不需要写 break,一旦条件符合自动终止
  • 如希望继续执行下一个 case,需要使用 fallthrough 语句
  • 支持一个初始化表达式(可以是并行方式),右侧需要跟分号
  • 左大括号必须和条件语句在同一行
func main() {
    a := 1
    switch a {
        case 0:
            fmt.Println("a=0")
        case 1:
            fmt.Println("a=1")
    }
    fmt.Println(a)
}
func main() {
    a := 1
    switch {
        case a>=0:
            fmt.Println("a=0")
            fallthrough
        case a>=1:
            fmt.Println("a=1")
        default:
            fmt.Println("None")
    }
    fmt.Println(a)
}
func main() {
    switch a := 1; { //注意右侧的分号,a的作用域范围只在这个switch块中
        case a>=0:
            fmt.Println("a=0")
            fallthrough
        case a>=1:
            fmt.Println("a=1")
    }
}

跳转语句

goto break continue

  • 三个语法都可以配合标签使用
  • 标签名区分大小写,若不使用会造成编译错误
  • break 和 continue 配合标签可以用于多层循环的跳出,跳出 LABEL 标识的
  • goto 是调整执行位置,与其它 2 语句配合标签的结果并不相同
func main() {
    LABEL:
        for i:=0;i<10;i++ {
            if i>2 {
                break LABEL
            } else {
                fmt.Println(i)
            }
        }
}

func main() {
    LABEL:
        for i:=0;i<10;i++ {
            for {
                fmt.Println(i)
                continue LABEL
            }
        }
}

func gotest() {
    i := 0
LABEL:
    for i < 10 {
        for {
            fmt.Println(i)
            i++
            //goto LABEL
            continue LABEL
        }
    }
}

数组

  • 定义var <varName>[n]<type> = 0
  • 长度也是类型的一部分,因此具有不同长度的数组为不同类型
  • 注意区分指向数组的指针和指针数组
  • 数组在 Go 中为值类型
  • 数组之间可以使用==或!=进行比较,但不能使用
  • 可以使用 new 来创建数组,此方法返回一个指向数组的指针
  • 支持多维数组
var a [2]int
var b [2]int
var c [2]string
a := [2]{1,2}
b := [20]{19:1} //第20个位置设置成1,其它设置成0值
c := [...]int{1,2,3,4,5} //长度为5的数组
d := [...]int{0:1,1:2,2:3}
e := [...]int(19:1) //长度20
// 指向数组的指针
var p *[20]int = &e
// 指针数组
x,y := 1,2
a := [...]*int{&x,&y} //保存的是指针
// 类型的不同不能进行直接比较
// 如果用new 创建的数组它返回的就是一个指向数组的指针
p := new([10]int) //p可以直接用索引来操作数组
p[1] = 2
fmt.Println(p)
// 多维数组
a :=[2][3]{
    {1,1,1},
    {2,2,2},
}
fmt.Println(a)
// 冒泡排序
func main() {
    a := [...]int(5,6,3,9)
    fmt.Println(a)
    num := len(a)
    for i:=0;i<num;i++ {
        for j:=i+1;j<num;j++ {
            if a[i]<a[j] {
                tmp:=a[i]
                a[i] = a[j]
                a[j] = tmp
            }
        }
    }
    fmt.Println(a)
}

切片

  • 本身并不是数组,它指向底层的数组
  • 作为为变长数组的替代方案,可以关联底层数组的局部或全部
  • 为引用类型
  • 可以直接创建或从底层数组获取生成
  • 使用 len()获取元素个数,cap()获取容量
  • 一般使用 make()创建
  • 如果多个 slice 指向相同的底层数组,其中一个的值改变会影响全部
  • make([]T,len,cap),其中 cap 可以省略,则和 len 值相同
  • len 表示存数的元素个数,cap 表示容量
func main() {
    //这样就是一个slice
    var s1 []int
    fmt.Println(s1)
    a:=[10]int{}
    fmt.Println(a)
    s2:=a[5:10] //索引开始(包含)结束(不包含)
    s3:=a[5:len(a)] //索引开始(包含)结束(不包含)
    s4:=a[5:] //索引开始(包含)结束(不包含)
    fmt.Println(s2)
    fmt.Println(s3)
    fmt.Println(s4)
    // make
    s5 := make([]int,22,1) //参数2是多少个,参数3 初始容量,当容量超出初始容量,它每次都会增加一倍
}

func t1() {
    s1:=make([]int,3,10)
    fmt.Println(len(s1),cap(s1))
    a:=[]byte{'a','b','c','d','e','f','g','h','i','j','k'}
    sa:=a[2:5]
    fmt.Println(string(sa))
    sb:=a[3:5]
    fmt.Println(string(sb))
}
  • reslice
  • reslice 时索引被 slice 的切片为准
  • 索引不可以超过被 slice 切片的容量 cap()值
  • 索引越界不会导致底层数组的重新分配而是引发错误
  • append
  • 可以在 slice 尾部追回元素
  • 可以将一个 slice 追回在另一个 slice 尾部
  • 如果最终长度未超过追回到 slice 容量则返回原始 slice
  • 如果超过追回到的 slice 容量则将重新分配数组并拷贝原始数据
  • copy
// reslice
 a:=[]byte{'a','b','c','d','e','f','g','h','i','j','k'}
 sa:=a[2:5] //cde
 sb:=sa[1:3] //de
 fmt.Println(string(sb))
//  append
s1:=make([]int,3,6)
fmt.Printf("%p\n",s1)
s1 = append(s1,1,2,3)
fmt.Printf("%v %p\n",s1,s1)
s1 = append(s1,1,2,3)
fmt.Printf("%v %p\n",s1,s1)
/*
0xc000018120
[0 0 0 1 2 3] 0xc000018120
[0 0 0 1 2 3 1 2 3] 0xc00006e060
*/
// copy

map

  • 类似其它语言中的哈希或者字典,以 key-value 形式存储数据
  • key 必须是支持==或!==比较运算类型,不可以是函数,map 或 slice
  • map 查找比线性搜索快很多,但比使用索引访问数据的类型慢 100 倍
  • map 使用 make()创建,支持:=这种简写方式
  • make([keyType]valueType,cap) cap 表示容量,可省略
  • 超出容量时会自动扩容,但尽量提供一个合理的初始值
  • 使用 len()获取元素个数
  • 键值对不存在时自动添加,使用 delete()删除某键值对
  • 使用用for range对 map 和 slice 进行迭代操作
func main() {
    var m map[int]string
    m = make(map[int]string)
    // var m map[int]string = make(map[int]string)
    // m:= make(map[int]string)
    m[1] = "OK"
    a = m[1]
    delete(m,1)
    // 更复杂的map
    var m = map[int]map[int]string //m 的value又是一个map
    m = make(map[int]map[int]string)
    a,ok := m[2][1]
    fmt.Println(ok)
    if !ok {
        m[2] = make(map[int]string)
    }
    m[2][1] = "GOOD"
    a, ok = m[2][1]
    fmt.Println(ok)
    // for range
    for k,v := range map {

    }
}

func t1() {
    sm:=make([]map[int]string,5) //声明一个slice 值是map[int]string
    for _,v:=range sm {
        v=make(map[int]string,1) //v每次都是一个copy,所以不能这样写
        fmt.Println(v)
    }
    fmt.Println(sm)
}

func t2() {
    sm:=make([]map[int]string,5) //声明一个slice 值是map[int]string
    for i :=range sm {
        sm[i]=make(map[int]string,1) //v每次都是一个copy,所以不能这样写
        sm[i][1] = "OK"
        fmt.Println(sm[i])
    }
    fmt.Println(sm)
}
// 对map的间接排序

对 map 的间接排序

package main

import (
    "fmt"
    "sort"
)

func main() {
    m:=map[int]string{1:"a",2:"b",3:"c",4:"d",5:"e"}
    s:=make([]int,len(m))
    i:=0
    for k,_ := range m {
        s[i] = k
        i++
    }
    // sort.Ints(s) // 没有这句,可以看一下打印结构
    fmt.Println(s)
}
// map 键值对调
func main() {
    m1:=map[int]string{1:"a",2:"b",3:"c",4:"d",5:"e"}
    fmt.Println(m1)
    m2:=make(map[string]int)
    for k,v := range m1 {
        m2[v] = k
    }
    fmt.Println(m2)
}

函数 function

  • Go 函数不支持嵌套,重载,默认参数
  • 定义函数使用关键字 func,且左大括号不能另起一行
  • 函数也可以作为一种类型使用
  • 支持以以下特性
  • 无需声明原型
  • 不定长变参
  • 多返回值
  • 命名返回值参数
  • 匿名函数
  • 闭包
func A(a ...int)(int,int,int){
    // ...不定长可变参
    a,b,c = 1,2,4
    return //默认返回a b c // 返回参数可以写成a,b,c int
    // 注意传入的时候都是拷贝 一种情况是拷贝值 另一种情况是拷贝地址(还是指向同一个位置)
}
// 匿名函数
func main(){
    a:=func(){
        fmt.Println("func A")
    }
    a()
    // 调用闭包
    f:=closure(10)
    fmt.Println(f(1))
    fmt.Println(f(2))
}
// 闭包
func closure(x int) func(int)int{
    return func (y int)int {
        return x*y
    }
}

defer

类似析构,执行方式类似其它语言中的析构函数,在函数体执行结束后按照调用顺序的相反顺序逐个执行。

  • 即使函数发生严重错误也会执行
  • 支持匿名函数的调用
  • 常用的资源清理、文件关闭、解锁以及记录时间等操作
  • 通过与匿名函数配合可在 return 之后修改函数计算结果
  • 如果函数体内某变量作为 defer 时匿名函数的参数,则在定义 defer 即已经获得了拷贝,否则则是引用某个变量的地址
  • Go 没有异常机制,但有 panic/recover 模式来处理错误
  • panic 可以在任何地方引发,但 recover 只有在 defer 调用的函数中有效
func main() {
    for i:=0;i<3;i++ {
        // 当main函数执行完闭后才开始执行,而且i是(因为是闭包,是地址的拷贝)每次输出3
        defer func() {
            fmt.Println(i)
        }()
    }
    // 如果在传入i会怎么样?
    for i:=0;i<3;i++ {
        defer func(i int) {
            fmt.Println(i)
        }(i)
    }
}
// panic之前注册defer来恢复到正常执行
func main() {
    A()
    B()
    C()
}
func A() {
    fmt.Println("Func A")
}
func B() {
    defer func(){
        if err:=reocver(); err!=nil {
            fmt.Println("Recover in B")
        }
    }()
    panic("Panic in B")
}
func C() {
    fmt.Println("func C")
}

闭包,最外层不能是一个匿名函数

// 分析结果
package main

import (
    "fmt"
)

func main() {
    var fs = [4]func(){}
    for i:=0;i<4;i++ {
        defer fmt.Println("defer i=",i)
        defer func(){
           fmt.Println("defer_closure i=", i) // 闭包 引用值拷贝
        }()
        fs[i] = func(){
            fmt.Println("closure i = ",i) // 闭包 引用值拷贝
        }
    }
    for _,f:=range fs {
        f()
    }
}

结构 struct

  • Go 中的 struct 与 c 中的 struct 非常相似,并且 Go 没有 class
  • 使用type <name> struct{}定义结构,名称遵循可见规则
  • 支持指向自身的指针类型成员
  • 支持匿名结构,可用作成员或定义成员变量
  • 匿名结构也可以用于 map 的值
  • 可以使用字面值对结构进行初始化
  • 允许直接通过指针来读写结构成员
  • 相同类型的成员可进行直接拷贝赋值
  • 支持==!=比较运算符,但不支持><
  • 支持匿名字段,本质上是定义了某个类型名为名称字段
  • 嵌入结构作为匿名字段看起来像继承,但不是继承
  • 可以使用匿名指针
// 定义结构
type test struct{}

// 定义人
type person struct{
    Name string,
    Age int
}

func main() {
    // a:=test{}
    a:=person{}
    a.Name = "joe"
    a.Age = 19
    fmt.Println(a)
    // 可以使用字面值初始化
    b:=person{
        Name:"Will",
        Age:18
    }
    fmt.Println(b)
    A(b) // 值的拷贝
    fmt.Perintln(b) //age 18
    B(&b) //第次都要对b取地址是不是很麻烦,我可以在定义b时就写成指针 如b:=&person{Name:"Will",Age:18}, b就是指向结构的指针
    // 使用属性时也不用像c下样*b.Name 而是直接写b.Name="OK"
    fmt.Perintln(b) //age 13
}
func A(per person) {
    per.Age=13
    fmt.Println("A",per)
}
// 如果想修改传入的结构类型,则需要传入地址拷贝
func B(per *person) {
    per.Age=13
    fmt.Println("A",per)
}
// 匿名结构
func main() {
    a: = struct {
        Name string
        Age int
    }{
        Name:"Joe",
        Age:19
    }
    fmt.Println(a)
}
// 如下
type person struct {
    Name string
    Age int
    // 匿名结构
    Contact struct {
        Phone,City string
    }
}
func main() {
    a:=person{Name:"joe",Age:18}
    a.Contact.Phone = "12xxx"
    a.Contact.City = "Bei Jing"
    fmt.Println(a)
}
// 字段匿名,必须严格按照声明的顺序
type person struct {
    string,
    int
}
func main(){
    a:=person{"joe",19} //调换顺序会报错
    fmt.Println(a)
    // 相同结构可以赋值 相同类型之间
    b:=person{}
    b=a
    fmt.Println(b)
}
// 怎样实现类似继承的功能 嵌入
type human struct{
    Sex int
}
type teacher struct {
    human
    Name string
    Age int
}
type student struct{
    human
    Name string
    Age int
}
func main() {
    a:=teacher{Name:"Joe",Age:18,human:human{Sex:0}}
    b:=student{Name:"Jay",Age:19,human:human{Sex:1}}
    a.Name = "joe2"
    a.Age=13
    a.human.Sex = 2
    // a.Sex=3 可以直接使用
    fmt.Println(a,b)
    // 嵌入结构怎么赋值
}

如果匿名字段和外层结构有同名字段,应该如何进行操作?

type A struct {
    B
    // C 会报错 编译器无法定你要使用哪个Name
    Name string
}
type B struct {
    Name string
}
type C struct {
    Name string
}
func main(){
    a:=A{Name:"A",B:B{Name:"B"}}
    fmt.Println(a.Name,a.B.Name)
}

https://gowalker.org/

方法 method

  • Go 中虽然没有 class,但依旧有 method
  • 通过显示说明 receiver 来实现与某个类型的组合
  • 只能为同一个包中的类型定义方法
  • receiver 可以是类型的值或指针
  • 不存在方法重载
  • 可以使用值或指针来调用方法,编译器会自动完成转换
  • 从某种意义上说,方法是函数的语法糖,因为 receiver 其实就是方法所接收的第一个参数
  • 如果外部结构和嵌入结构存在同名方法,则优先调用外部结构方法
  • 类型别名不会拥有底层类型所附带的方法
  • 方法可以调用结构中的非公开字段
type A struct {
    Name string
}
type B struct {
    Name string
}
func main() {
    a:=A{}
    a.Print()
    fmt.Println(a.Name)
    b:=B{}
    b.Print()
    fmt.Println(b.Name)
}
// 定义方法,先写括号定义了A这个类型有一个方法Pring
func (a *A) Print(){ //引入拷贝
    a.Name="AA"
    fmt.Println("A")
}
func (b B) Print(){
    fmt.Println("A")
}
// 类型别名和方法的组合
type TZ int
func main(){
    var a TZ
    a.Print()
}
// 第一个括号就是receiver 就是谁来接收它
func (a *TZ) Print(){
    fmt.Println("TZ")
}

方法和函数

type TZ int
func main(){
    var a TZ
    a.Print() //method value
    (*TZ).Print(&a) // method expression
}
func (a *TZ) Print(){
    fmt.Println("TZ")
}
// 方法是可以访问结构的私有字段,大写小写只对package的限制的。在本包内都是可见的
// 根据为结构增加方法的知识,尝试声明一个底层类型为int的类型,并实现调用某个方法就递增100
// 如 a:=0 调用a.Increase()之后,a从0变成100
type TZ int
func (tz *TZ) Increase(num int){
    *tz+=TZ(num) //要做强制类型转换
}
func main() {
    // 因为不是结构类型所以要使用var
    var a TZ
    a.Increase(100)
    fmt.Println(a)
}

接口 interface

  • 不需要显式说明你实现了哪个接口,只要实现了相同签名的功能,编译器就会认是实现了相应的接口
  • 接口是一个或多个方法签名的集合
  • 只要某个类型拥有该接口的所有方法签名,即算实现该接口,无需显式声明实现了哪个接口,这称为 Structural Typing
  • 接口可以匿名嵌入其它接口,或嵌入到结构中
  • 将对象赋值给接口时,会发生拷贝,而接口内部存储的是指向这个复制品的指针,既无法修改复制器的状态,也无法获取指针
  • 只有当接口存储的类型和对象都为 nil 时,接口才等于 nil
  • 接口调用不会做 receiver 的自动转换
  • 接口同样支持匿名字段方法
  • 接口也可以实现类似 OOP 中的多态
  • 空接口可以作为任何类型数据的容器
type USB interface {
    Name() string
    Connect()
}
type PhoneConnector struct{
    name string
}

func (pc PhoneConnector) Name() string {
    return pc.name
}
func (pc PhoneConnector) Connect() {
    fmt.Println("Connect:", pc.name)
}
func main() {
    var a USB
    a = PhoneConnect{"PhoneConnector"}
    // 可以简写成a:= PhoneConnect{"PhoneConnector"} , 但这样写不能直观的看出来是否是实现了接口USB
    a.Connect()
    Disconnect(a)
}

func Disconnect(usb USB){
    // 判断类型
    if pc,ok:=usb.(PhoneConnect);ok {
        fmt.Println("Disconnected")
        return
    }
    fmt.Println("Unknown device")
}
// 可以允许传递空接口
func Disconnect(usb interface{}){
    // 判断类型
    if pc,ok:=usb.(PhoneConnect);ok {
        fmt.Println("Disconnected")
        return
    }
    fmt.Println("Unknown device")
    // type switch
    switch v:=usb.(type){
        case PhoneConnector:
        fmt.Println("Disconnected",v.name)
        default:
        fmt.Println("Unknown device.")
    }
}
// 嵌入接口
type USB interface {
    Name() string
    Connecter
}
type Connecter interface {
    Connect()
}
// 类型转换 USB可以转换成Connecter 但Connecter不能转换成USB

反射 reflection

  • 反射可大大提高程序的灵活性,使得 interface{}有更大的发挥余地
  • 反射使用 TypeOf 和 ValueOf 函数从接口中获取目标对象信息
  • 反射会将匿名字段作为独立字段(匿名字段本质)
  • 想要利用反射修改对象状态,前提是 interface.data 是 settable,即 point-interface
  • 通过反射可以“动态”调用方法
package main
import (
    "fmt"
    "reflect"
)
type User struct {
    Id int
    Name string
    Age int
}
func (u User) Hello() {
    fmt.Println("Hello world.")
}
type Manger struct{
    User
    title string
}
func main() {
   u:=User(1,"OK",12)
   Info(u)
   // 取manager的匿名字段
   m:=Manager{User:User{1,"ok",32},title:"boss"1}
   t:=reflect.TypeOf(m)
   fmt.Printf("%#v\n",t.Field(0))
   fmt.Printf("%#v\n",t.Field(1))
   //也可以使用下面这种方法
   fmt.Printf("%#v\n",t.FieldByIndex([]int{0,1})) //取匿名字段的第二个字段
}
func Info(o interface{}) {
    t:=reflect.TypeOf(o)
    fmt.Println("Type:",t.Name())
    // 判断类型
    if k:=t.Kind();k!=reflect.Struct{
        fmt.Println("XX")
        return
    }
    v:=reflect.ValueOf(o)
    fmt.Println("Fields:")
    for i:=0;i<t.NumField();i++ {
        f:=t.Field(i)
        val:=v.Field(i).Interface()
        fmt.Printf("%6s:%v=%v\n",f.Name,f.Type,val)
    }
    // 打印方法
    for i:=0;i<t.NumMethod();i++{
        m:=t.Method(i)
        fmt.Printf("%6s:%v\n",m.Name,m.Type)
    }
}
type User struct {
    Id int
    Name string
    Age int
}
func (u User) Hello(name string){
    fmt.Println("Hello",name," my name is ",u.Name)
}
func main() {
    x:=123
    v:=reflect.ValueOf(x)
    v.Elem().SetInt(999)
    fmt.Println(x)
    // 处理复杂结构
    u:=User{1,"OK",12}
    Set(&u)
    fmt.Println(u)
    // 反射调用方法
    v:=reflect.ValueOf(u)
    mv:=v.MethodByName("Hello")
    args:=[]reflect.Value{reflect.ValueOf("joe")}
    mv.Call(args)
}
func Set(o interface{}){
    v:=reflect.ValueOf(o)
    if v.Kind()==reflect.Ptr&&!v.Elem().CanSet() {
        fmt.Println("不能修改")
        return
    }else{
        v=v.Elem()
    }
    f:=v.FieldByName("Name")
    if !f.IsValid() {
        fmt.Println("BAD")
        return
    }
    if f.Kind()==reflect.String {
        f.SetString("HaHa")
    }
}

并发 concurrency

并发不是并行,并发主要由切换时间片来实现“同时”运行,在并行则是直接利用多核实现多线程的运行,但 go 可以设置使用核数,以发挥多枋计算机的能力,Goroutine 奉行通过通信来共享内存,而不是共享内存来通信;
定义一个函数,使用 go 命令

// 怎么运行一个go routine
package main

import (
    "fmt"
    "time"
)

func main() {
    go Go()
    time.Sleep(2*time.Second)
}
func Go() {
    fmt.Println("Go! go! go!")
}
// 以上的是最简单的go routine
// 以上这种方式无法做一Go()执行完毕后,通知main函数,“我执行完毕了,可以结束了”
// 要做到这种通信的能力,我们使用channel

channel

  • channel 是 goroutine 的沟通桥梁,大都是阻塞同步的
  • 通过 make 创建,close 关闭
  • 可以使用 for range 来迭代不断操作 channel
  • 可以设备单向或双向通道
  • 可以设置缓存大小,在未被填满前不会发生阻塞
func main() {
    c:=make(chan bool)
    go func(){
        fmt.Println("GO GO GO!!!")
        c <- true //执行go go go 打印,把channel的一个返回值通知main可以结束
    }()
    <-c //main函数执行到这个等待channel的返回值,当有返回值结束
}
// 使用for range来迭代
func main() {
    c:=make(chan bool)
    go func(){
        fmt.Println("go go go")
        c <- true
        close(c)
    }()
    for v :=range c {
        fmt.Println(v)
    }
}
// 使用channel迭代时,必须有一个地方明确关闭它,并且是关闭成功的。否则程序会变成死锁的
// 有缓存与无缓存的主要区别
//   无缓存时 是阻塞的
//   有缓存时
func main() {
    c:=make(chan bool)
    go func() {
        fmt.Println("Go Go Go")
        <-c
    }()
    c <- true //无缓存时它会等到这个值被读掉,再结束
}
// 而有缓存时,
func main() {
    c:make(chan bool,1)
    go func(){
        fmt.Println("Go Go Go")
        <-c
    }()
     c <- true //有缓存时它会把值放到缓存中,爱读不读:);不会执行匿名函数,
}
// 即有缓存是异步的,无缓存是同步阻塞的
package main

import (
    "fmt"
    "runtime"
)
func main() {
    runtime.GOMAXPROCS(runtime.NumCPU()) //使用多核,提升效率
    c:=make(chan bool)
    for i:=0;i<10;i++ {
        go Go(c,i)
    }
    <-c
}
func Go(c chan bool,index int) {
    a:=1
    for i:=0;i<100000000;i++ {
        a+=i
    }
    fmt.Prinln(i,a)
    if index == 9 {
       c <- true
    }
}
// 上例中,在使用多核提交效率时,用最后一个channel的值来判断结束是不对的
// 导致有些channel没有执行就退出了
// 要解决这个问题可以使用缓存 ,如下
package main

import (
    "fmt"
    "runtime"
)
func main() {
    runtime.GOMAXPROCS(runtime.NumCPU()) //使用多核,提升效率
    c:=make(chan bool,10) //有10个channel就创建10个缓存
    for i:=0;i<10;i++ {
        go Go(c,i)
    }
    for i:=0;i<10;i++{
        <-c //取10次channel的值就结束了
    }
}
func Go() {
    a:=1
    for i:=0;i<100000000;i++ {
        a+=i
    }
    fmt.Prinln(i,a)
     c <- true
}
// 第二种解决方案,通过sync包来解决的,通过wait group
package main

import (
    "fmt"
    "runtime"
    "sync"
)
func main() {
    runtime.GOMAXPROCS(runtime.NumCPU()) //使用多核,提升效率
    // 创建waitGroup来实现,main函数主要是来判断waitgroup的完成数来结束
    wg:=sync.WaitGroup{}
    wg.Add(10)
    for i:=0;i<10;i++ {
        go Go(wg,i)
    }
   wg.Wait()
}
func Go(wg *sync.WaitGroup,index int) {
    a:=1
    for i:=0;i<100000000;i++ {
        a+=i
    }
    fmt.Prinln(i,a)
    wg.Done() //标记完成一个
}

select

  • 可以处理一个或多个 channel 的发送与接收
  • 同时有多个可用的 channel 时按Random顺序处理
  • 可用空的 select 来阻塞 main 函数
  • 可设置超时
package main

import(
    "fmt"
)

func main() {
    c1,c2:=make(chan int),make(chan string)
    o:=make(chan bool)
    go func(){
        for{
            select{
                case v,ok:=<-c1:
                if !ok {
                    o<-true
                    break
                }
                fmt.Println("c1",v)
                case v,ok:=<-c2
                if !ok{
                    o<-true
                    break
                }
                fmt.Println("c2",v)
            }
        }
    }()
    c1<-1
    c2<-"Hi"
    c1<-3
    c2<-"hello"

    close(c1)
    close(c2)

    <-o
}

将 select 作为发送者来实现一个应用

package main

import (
    "fmt"
)

func main() {
    c:=make(chan int)
    go func(){
        for v:=range c{
            fmt.Println(v)
        }
    }()
    for{
        select {
            case c<-0:
            case c<-1:
        }
    }
}
// 超时 c一直没有输入值
func main() {
    c:=make(chan bool)
    select{
        case v:<-c:
        fmt.Println(v)
        case <-time.After(3*time.Second):
        fmt.Println("")
    }
}

项目

主题测试文章,只做测试使用。发布者:Walker,转转请注明出处:https://walker-learn.xyz/archives/6714

(0)
Walker的头像Walker
上一篇 12 hours ago
下一篇 Mar 27, 2025 15:01

Related Posts

EN
简体中文 繁體中文 English