Go课堂笔记

Go

相关资源

Golang官网https://golang.org/
Golang官网镜像
Golang中国http://www.golangtc.com/
Go语言中文网http://studygolang.com/

安装

编译器

安装包下载: 稳定版→go1.14.4 测试版→go1.15beta1

  • 官网下载(需要翻墙): https://golang.org/dl/
  • 官网镜像下载: https://golang.google.cn/dl/
  • Go语言中文网下载: https://studygolang.com/dl

IDE

国产的LiteIDE,官网http://liteide.org/cn/

  1. 下载该IDE,然后解压..\bin\liteide.exe点击即可使用

  2. 点开后,菜单栏下面有一个Go按钮,右面那个system就是环境.可以配置多个环境,每个环境之间的环境变量互不影响.

  3. 编辑当前的环境可以点击它右面的黑电视.

    GOROOT值为go编译器的安装路径
    GOARCH值为go运行的CPU架构
    GOOS值为go运行的操作系统
    CGO_ENABLED值为go嵌入c代码开关
    PATH 值为各GCC编译器命令位置
    
  4. 然后新建工程之类的,就跟其他IDE相同了

  5. 编译运行:可以直接用那个[BR]按键,编译运行.

  6. 编译:那个B按钮;运行,那个R按钮.

单步调试

  1. 设置:菜单栏->调试->debugger/delve(这个可以更好的理解Go语言,官方说的)
  2. 编译代码,按那个B就可以编译了(检查语法错误)
  3. 设置断点,转到你要设置的行,按下F9即可
  4. 开始:按F5开始单步调试.再按F5执行到下一个断点;按F10,逐行运行;其他的调试指令可以点调试菜单就能用了!

好用的快捷键;

  1. Shift+Delete删除整行
  2. Ctrl+/快速注释
  3. Ctrl+R运行
  4. Ctrl+B编译
  5. Ctrl+F5 Ctrl+Shift+R运行

中文乱码

执行程序时,中文字符可能出现乱码.可以通过在命令行(终端)输入以下指令:chcp 65001.具体参考如下:

 chcp 65001 就是换成UTF-8代码页
 chcp 936 可以换回默认的GBK
 chcp 437 是美国英语
 chcp 20936 是中文简体字符集GB2312

语法

源文件

  1. 以".go"作为拓展名
  2. import导入标准库或第三方包
  3. 头部用package声明所属包名称,而且包名需要用双引号括起来
  4. main没有参数和返回值,且必须main包.想传参的话,可以在os.Args中保存
  5. {必须与函数声明或者控制结构放在同一行
  6. 分号可以省略,必须出现的典型位置for循环或类似处

变量

  • var定义变量,默认为零,避免意外.
  • 支持类型推断,通过初始值判定类型
  • 短变量符号:=,省略var.
var sum0 int
var sum1 int = 1
var label0 = "name"
label1 := "name"
  • 查看变量的类型

    fmt.Printf('%T',f)
    

if

与C大致架构相同,但条件表达式不用括起来

result := 1
if result > 0 {
    result = 100
}else{
    result = 1000
}

在条件之间可以包含一个简单语句,上述代码可以简化如下

if result := 1;result > 0{
    result = 100
}else{
    result = 1000
}
//此时result只在if里面有效

分支语句

  1. 默认fall-through,而不用手动加break.
  2. 简化跳转目标是相同的分支条件,用逗号空格
  3. 分支条件可以是任何有效的表达式
  4. 以true为switch条件的情况,可以省略true
result := 200
switch result {
case 100, 200:
    fmt.Println(123)
default:
    fmt.Println(321)
}

switch result := 300; true {
case result <= 100, result >= 1000:
    fmt.Println(123)
case result > 100 && result < 1000:
    fmt.Println(321)
}

switch result := 300; {
case result <= 100, result >= 1000:
    fmt.Println(123)
case result > 100 && result < 1000:
    fmt.Println(321)
}

随机数

package main
import (
    "fmt"
	"math/rand"
    "time"
)
func main(){
    rand.Seed(time.Now().Unix())
    m := rand.Intn(10)//[0,9]
    switch {
    case m > 3:
        fmt.Println(" > 3")
    case m <= 3:
        fmt.Println("<= 3")
    }
}

循环

  • go中只有for循环,而且不用括号括起来.
  • 几种常见形式如下
i := 0
for i < 5 {
    fmt.Println(i)
    i++
}
/*
for 条件{
	//do something
}
*/

/*
for 初始;条件;步进{
	//do something
}
跟C几乎一样
*/

/*
这个不行
for i:=0;i<5{
	fmt.Println(i)
	i++
}*/

for i := 0; i < 5; i++ {
    fmt.Println(i)
}

/*
for{
	//do something
}
省略了true而已
*/

for i := 0; ; i++ {
    if i > 5 {
        break
    }
    fmt.Println(i)
}

for i := 0; ;  {
    if i > 5 {
        break
    }
    fmt.Println(i)
	i++
}
//缺省值都是true
  • for…range,返回集合中数据的索引和值

    其中,索引是整数,值跟集合中元素相同

    range右边的表达式必须是array, slice, string, map或 是指向array的指针,也可以是channel

x := []int{100, 101, 102}
for i, v := range x {
    fmt.Println(i, ":", v)
    fmt.Printf("%d,%d\n", i, v)
}

for i, v := range "Hello 世界" {
    fmt.Printf("%d : %c\n", i, v)
}

fmt.Println(x)

函数

初代函数

  • go把返回值放在参数列表最后,左大括号之前.

    而且,参数列表中

    1. 如果只在最后说明变量类型,则所有变量都是这个类型
    2. 如果每个变量不同,则需要在每个后面单独说明
/* C language
int add(int a,int b){
	return a+b;
}
*/

//go language
func add(a,b int) int{
    return a+b
}

func add2(a int, b float32) float32 {
	return float32(a) + b
}
  • 多返回值

    返回参数列表用(int,int)这样类似的格式来声明

    func divide(a, b int) (int, int) {
    	quotient := a / b
    	remainder := a % b
    	return quotient, remainder
    }
    
    func main() {
        a,b := divide(5,3)
        //必须要对应,不能少
    }
    
  • 提前确定返回值

    相当于提前声明了这个变量,然后后面就不用再定义和return的时候也就不用再写具体的了

    func divide2(a,b int) (quotient,remainder int){
    	quotient = a/b
    	remainder = a%b
    	return
    }
    //其他都跟之前相同
    
  • 结合一波布尔值可以干异常判断

    func divide3(a, b int) (quotient, remainder int, result bool) {
    	if b == 0 {
    		result = false
    		return
    	}
    	result = true
    	quotient = a / b
    	remainder = a % b
    	return
    }
    
    func main() {
        a, b, ok := divide3(5, 0)
    	fmt.Println(a, b, ok)
    
    	a, b, ok = divide3(9, 4)
    	fmt.Println(a, b, ok)
    }
    
  • 函数顺序没有C那样的前后关系

    func main() {
        a,b := divide4(17,4)
        fmt.Println(a,b)
    }
    func divide4(a, b int) (int, int) {
    	quotient := a / b
    	remainder := a % b
    	return quotient, remainder
    }
    

匿名函数

  1. 函数可以被当作一个数据类型来使用
  2. 匿名函数只能依附在其他函数里面
f := func(a,b int) int{
    return a+b
}//声明了一个匿名函数f,直接把f搞成了一个函数类型
sum := f(2,3)//调用

sum2 := func(a,b int)int{
    return a+b
}(6,9)
//sum2 = 15,int类型.直接调用了
  • 闭包

    匿名函数被称为闭包,原因是它根据一定方式使得其可见范围超过了定义范围.

    go为声明匿名函数提供了简单的语法,像许多动态语言一样,这些匿名函数在它们被定义的范围内创建了词法闭包

    1. 闭包返回值是一个匿名函数
    2. 匿名函数嵌套在闭包内部
    3. 匿名函数引用了自身的外部变量x
    4. 闭包中的参数x是变量安全的,只有内部函数才能访问到
func makeAdder(x int) func(int) int {
	return func(y int) int { return x + y }
}

func main() {
	add5 := makeAdder(5)
	add36 := makeAdder(36)
	fmt.Println(add5(add36(1)))
}
//add5 : x+5

函数当作参数

函数可以当作参数进行传递,然后再其他函数内调用执行,一般称之为回调

func Add(a, b int) {
	fmt.Printf("The sum of %d and %d is %d\n", a, b, a+b)
}
func callback(y int, f func(int, int)) {
	f(y, 2) //Add(y,2)
}

func main() {
	callback(123, Add)
}

/*然后出一个结合版
func Add(a, b int) int {
	return a + b
	//fmt.Printf("The sum of %d and %d is %d\n", a, b, a+b)
}
func callback(y int, f func(int, int) int) int {
	return f(y, 2) //Add(y,2)
}

func main() {
	if result := callback(123, Add); result > 12345 {
		fmt.Println("So Big!")
	} else {
		fmt.Println("Not so big?")
	}
}
*/

延迟调用

defer语句,使用方法:defer 语句,则无论函数执行是否报错,都确保再结束前将其调用.一般用于数据清理工作

如果有很多defer,从后往前执行

func Add(a, b int) int {
	return a + b
}
func callback(y int, f func(int, int) int) int {
	return f(y, 2) //Add(y,2)
}

func main() {
	defer fmt.Println("This is a defer")
	if result := callback(123, Add); result > 12345 {
		fmt.Println("So Big!")
	} else {
		fmt.Println("Not so big?")
	}
}
/*
运行结果
Not so big?
This is a defer
*/

异常恢复机制

  1. 内置函数panic,中断现在的控制流(程序),抛出一个异常流。
  2. 内置函数recover,捕获到panic的异常值并恢复。recover函数没有参数,返回值是异常本身
  3. recover仅再延迟函数中有效,即再defer语句中调用才可以
func main() {
	f := func(x int) {
		if x > 100 {
			panic("参数超出范围")
		} else {
			fmt.Println("f成功调用")
		}
		fmt.Println("Oh?")
	}
	defer func() {
		if err := recover(); err != nil {
            //nil 就是C语言的 NULL
            //recover相当于捕获了所有的异常
			fmt.Println("程序异常退出:", err)
            fmt.Println("Oh!!!!")
		} else {
			fmt.Println("正常退出")
		}
	}()
	f(101)
}

程序输出为

程序异常退出: 参数超出范围
Oh!!!!

数组

留坑:数组名到底是啥东西?

原生数组

  1. 数组的数据类型是[len]type,可以套用先声明后采用的方法(采用Go的缺失值规则,默认都是0)
  2. 直接带初始化的定义方式:var name [len]type = [len]type{1,3,4,填够了就行}这里一定要对应相等的长度,不然可以选择切片
  3. 支持短变量定义方式b := [4]int{1,3,5,7}
  4. 支持索引初始化,也就是指定位置初始化.例如array3 := [4]int{5, 3: 10}此时的array3 = [5,0,0,10]
  5. 支持自动判断长度,同时兼并索引初始化,但该操作是相互影响的array4 := [...]int{5, 10: 0}此时的array4为[5 0 0 0 0 0 0 0 0 0 0].这里的[...]就是让编译器自动判断长度的意思,但此时不可以省略,不然成了切片.
  6. 下标从0开始,支持普通遍历
  7. 支持普通拷贝,也就是b := a
func main() {
	var array1 [4]int = [4]int{1, 3, 5, 7}
	fmt.Println(array1)
	array2 := [5]int{1, 3, 5, 7, 9}
	fmt.Println(array2)
	array3 := [4]int{5, 3: 10}
	fmt.Println(array3)
	array4 := [...]int{5, 10: 0}
	fmt.Println(array4)
    array5 := [...]int{
		5,
		19,
		10: 123,
	}
    //这里需要注意:如果分成多行进行声明+初始化,那么你需要在最后一行的最后加上','这样才能正常结束.如果在单行就没有这个问题
	fmt.Println(array5)
}

结构体数组

按照下面所说的结构体来定义,那么我们可以搞到下面的这种方式

type user struct {
	name string
	age  byte
}

func main() {
	d := [...]user{
		{"Tom", 20},
		{"Mary", 18},
	}
	fmt.Printf("%v\n%+v\n%#v\n", d, d, d)
}

输出分别为

[{Tom 20} {Mary 18}]
[{name:Tom age:20} {name:Mary age:18}]
[2]main.user{main.user{name:"Tom", age:0x14}, main.user{name:"Mary", age:0x12}}

说明:

  1. %#v以go语法格式输出,也就是最后一个输出
  2. %+v以字面值方式输出,中间那个输出

多维数组

  1. 最常规的那种定义仍然可用var a [2][3][4]int
  2. 省略长度只能用在第一维var a [...][3][4]int
  3. 初始化方式与之前完全相同.
  4. len(array),cap(array)可以获得第一维的长度
func main() {
	array1 := [...]int{
		5,
		19,
		10: 123,
	}
	fmt.Println(array1)

	array2 := [...][3]int{
		{1, 2, 3},
		{4, 5, 6},
		{7, 8, 9},
	}

	array3 := [...][3][4]int{
		{
			{1, 2, 3, 4},
			{5, 6, 7, 8},
			{9, 0, 1, 2},
		},
		3: {
			{11, 12, 13, 14},
			2: {19, 10, 11, 12},
		},
	}

	fmt.Println(len(array2), array2)
	fmt.Println(len(array3), array3)

	for i, v := range array2 {
		fmt.Println(i, v)
	}

	for i, v := range array3 {
		fmt.Println(i, v)
	}
}

输出结果

[5 19 0 0 0 0 0 0 0 0 123]
3 [[1 2 3] [4 5 6] [7 8 9]]
4 [[[1 2 3 4] [5 6 7 8] [9 0 1 2]] [[0 0 0 0] [0 0 0 0] [0 0 0 0]] [[0 0 0 0] [0 0 0 0] [0 0 0 0]] [[11 12 13 14] [0 0 0 0] [19 10 11 12]]]
0 [1 2 3]
1 [4 5 6]
2 [7 8 9]
0 [[1 2 3 4] [5 6 7 8] [9 0 1 2]]
1 [[0 0 0 0] [0 0 0 0] [0 0 0 0]]
2 [[0 0 0 0] [0 0 0 0] [0 0 0 0]]
3 [[11 12 13 14] [0 0 0 0] [19 10 11 12]]

数组与指针

  1. 数组名不是头指针,而是一个组.对数组名取地址之后是一个数组,对应是一个引用的模式
  2. %T输出的是数据类型,%p是指针.%v=value
func main() {
	x, y := 10, 20
	a := [...]*int{&x, &y}
	p := &a
	fmt.Printf("%T,%v\n", a, a)
	fmt.Printf("%T,%p,%v,%v,%p\n", p, p, p, *p, &p)

	b := [...]int{1, 2}
	fmt.Println(&b, &b[0], &b[1])

	pb := &b
	fmt.Println(pb)
	for i, v := range pb {
		fmt.Println(i, " : ", v)
	}
	fmt.Println(b)
	pb[1] += 10
	fmt.Println(b)
}

输出结果

[2]*int,[0xc0000a0090 0xc0000a0098]
*[2]*int,0xc0000881f0,&[0xc0000a0090 0xc0000a0098],[0xc0000a0090 0xc0000a0098],0xc0000ca018
&[1 2] 0xc0000a00d0 0xc0000a00d8
&[1 2]
0  :  1
1  :  2
[1 2]
[1 12]

数组与函数传参

  1. C的数组名传参是一种传指针(因为数组名是指针);Go是传拷贝,因为数组名也不代表是数组首地址.
  2. 与普通赋值中的拷贝类似.如果要使用引用,考虑使用C++的引用的传递参数模式(后面一节有介绍)
  3. 对一个数组a取地址得到的是一个数组的地址*[2]int类型
func test(x [2]int) {
	fmt.Printf("x : %p,%v\n", &x, x)
}
func main() {
	a := [2]int{10, 20}
	var b [2]int = a
	c := &a
    fmt.Println("Type of c is")
	fmt.Printf("%T\n", c)
	fmt.Printf("a : %p,%v\n", &a, a)
	fmt.Println(&a[0], &a[1], &c[0], &c[1])
	fmt.Printf("b : %p,%v\n", &b, b)
	fmt.Printf("c : %p,%v\n", &c, c)
	test(a)
}

输出结果

Type of c is
*[2]int
a : 0xc00000c200,[10 20]
0xc00000c200 0xc00000c208 0xc00000c200 0xc00000c208
b : 0xc00000c210,[10 20]
c : 0xc000006028,&[10 20]
x : 0xc00000c280,[10 20]

数组,函数,指针

func test2(x *[2]int) {
	x[1] += 120
    for i, v := range x {
		fmt.Println(i, v)
	}
	fmt.Println(x[1])
	fmt.Printf("x : %p,%v\n", x, *x)
}
//使用这种方式可以当成是一个引用,(除了整体[也就是直接用数组名的时候]上用法的不同,都可以看作是一个引用)

func main() {
	a := [2]int{10, 20}
	var b [2]int = a
	c := &a
	fmt.Println("Type of c is")
	fmt.Printf("%T\n", c)
	fmt.Printf("a : %p,%v\n", &a, a)
	fmt.Println(&a[0], &a[1], &c[0], &c[1])
	fmt.Printf("b : %p,%v\n", &b, b)
	fmt.Printf("c : %p,%v\n", &c, c)
	test(a)
	test2(&a)
	fmt.Printf("a : %p,%v\n", &a, a)
}

输出结果

Type of c is
*[2]int
a : 0xc00000c200,[10 20]
0xc00000c200 0xc00000c208 0xc00000c200 0xc00000c208
b : 0xc00000c210,[10 20]
c : 0xc000006028,&[10 20]
x : 0xc00000c280,[10 20]
0 10
1 140
140
x : 0xc00000c200,[10 140]
a : 0xc00000c200,[10 140]

结构体

  • 穿插一下定义,没有讲解
type user struct{
    name string
    age byte
}
func main() {
    var d [4]user
    fmt.Println(d)
}
  • 定义一个结构体
type Point struct{
    x,y float64
	mask byte
}
  • 创建对象
var p1 Point
var 
  • 初始化结构体
  1. 第一种 new的方式

    t := new(T) t现在是指向该结构体的指针 , 是 *T类型

  2. 第二种 var的方式

    var t T 结构体现在就已经被初始化了 , 是T类型

  3. 字面量的方式

    t := T{a, b} 
    t := &T{} //等效于 new(T)
    

切片

朴素切片

这个切片不是Python那个切片,它类似于C++的动态数组。

  1. 创建的语法是make([]type,len,cap),表示创建一个len长度的(初始是len长度),容量是cap的一个[]int,类型是[]type的一个切片
  2. len不一定等于cap.如果len即将大于cap,那么cap=cap*2.
  3. 可以通过append(x,i)往x最后添加i,返回值是一个切片(不是直接修改原先的)
func main() {
	x := make([]int, 0, 5)
	fmt.Println(x, len(x), cap(x))
	for i := 0; i < 10; i++ {
		x = append(x, i)
	}
	fmt.Println(x, len(x), cap(x))
	fmt.Printf("%T\n", x)

	x1 := make([]int, 1, 5)
	fmt.Println(x1)

	var x2 []int = make([]int, 4, 5)
	fmt.Println(x2)

	var x3 = [...]int{1, 2, 3, 4, 5}
	fmt.Printf("%T\n", x3)
	fmt.Println(x3, len(x3), cap(x3))
}
[] 0 5
[0 1 2 3 4 5 6 7 8 9] 10 10
[]int
[0]
[0 0 0 0]
[5]int
[1 2 3 4 5] 5 5

数组切片

s := x[begin=0:end=len(x)]创建一个数组x的切片,它的作用是提供一个数组的引用(高级引用?)

  1. 不填使用类缺失值
  2. cap(s)最小是len(x)-2len(s)与切片的实际长度相同.但是如果切片大于len(x)-2了,那么len(s) = cap(s)等于实际长度.
  3. s的改变会对应到x的改变.
  4. s可以通过append来增加长度,对应增加的也会影响到x.
  5. append如果过长,x不会变长,而是受影响到最长长度.
func main() {
	x := [...]int{0, 1, 2, 3, 4, 5, 6}
	s := x[2:4]
	fmt.Printf("%T\n", s)

	for i, v := range s {
		fmt.Println(i, ":", v)
	}
	fmt.Println(len(s), cap(s))
	s[0] = 5
	fmt.Println(x)

	s = append(s, 10)
	fmt.Println(x)
	s = append(s, 12)
	s = append(s, 123)
	fmt.Println(s, x)

	s2 := x[0:len(x)]
	fmt.Println(len(s2), cap(s2))

	s3 := x[1:]
	fmt.Println(s3, len(s3), cap(s3))
}
[]int
0 : 2
1 : 3
2 5
[0 1 5 3 4 5 6]
[0 1 5 3 10 5 6]
[5 3 10 12 123] [0 1 5 3 10 12 123]
7 7
[1 5 3 10 12 123] 6 6

map

跟别的map一样.

定义:gm := make(map[string]int)相当于map<string,int> gm

增添:gm["book"]=10;删除:delete(gm,"book")

遍历:for i,v := range gm

func main() {
	gm := make(map[string]int)
	gm["bed"] = 1000
	gm["desk"] = 500
	gm["chair"] = 300
	gm["computer"] = 5000
	gm["iphone"] = 6800

	for i, v := range gm {
		fmt.Println(i, v)
	}

	fmt.Println(gm["desk"])

	delete(gm, "desk")

	//fmt.Printf("%T\n", gm["bed"]) int?如何实现的两个返回值和一个返回值都有的?

	if price, ok := gm["computer"]; ok {
		fmt.Println(price, ok)
	} else {
		fmt.Println(price, ok)
	}

	if price, ok := gm["desk"]; ok {
		fmt.Println(price, ok)
	} else {
		fmt.Println(price, ok)
	}

	fmt.Println(gm)
	for i, v := range gm {
		fmt.Println(i, v)
	}

	gm["book"] = 10
	fmt.Println(gm)
}
bed 1000
desk 500
chair 300
computer 5000
iphone 6800
500
5000 true
0 false
map[bed:1000 chair:300 computer:5000 iphone:6800]
bed 1000
chair 300
computer 5000
iphone 6800
map[bed:1000 book:10 chair:300 computer:5000 iphone:6800]

对象

定义一个新的类

type 类名 struct{
    成员 类型
}
  1. 成员或者函数,大写是public,小写是private
  2. new可以创建对象,返回是一个指针,初始化为0
  3. 可以建立的时候直接初始化,跟C++强制初始化一样
func main() {
	type Point struct {
		x, y float64
		mask byte
	}
	var p1 Point
	var p2 Point = Point{3, 4, 'a'}
	var p3 *Point = &Point{30, 40, 'b'}
	var p4 *Point = new(Point)

	fmt.Println(p1, p1.x, p1.y, p1.mask)
	fmt.Println(p2, p2.x, p2.y, p2.mask)
	fmt.Println(p3, *p3, p3.x, p3.y, p3.mask)
	fmt.Println(p4, *p4, p4.x, p4.y, p4.mask)
}
{0 0 0} 0 0 0
{3 4 97} 3 4 97
&{30 40 98} {30 40 98} 30 40 98
&{0 0 0} {0 0 0} 0 0 0

方法

定义了一个类之后就可以搞它的方法。但是方法一定要在类外进行定义。

传递参数的时候,需要注意self是一个拷贝还是一个引用(可以参考指针那边的)

type Point struct {
	x, y, lens float64
}

func (self Point) Length() float64 {
	self.lens = math.Sqrt(self.x*self.x + self.y*self.y)
	return math.Sqrt(self.x*self.x + self.y*self.y)
}
func (self *Point) Scale(factor float64) {
	self.x = self.x * factor
	self.y = self.y * factor
}

func main() {

	var p1 Point = Point{3, 4, 0}
	fmt.Println(p1)
	d := p1.Length()
	fmt.Println(d)
	fmt.Println(p1)
	p1.Scale(2)
	fmt.Println(p1)

}
{3 4 0}
5
{3 4 0}
{6 8 0}

接口

抽象化公共行为,定义为接口并使用它。

go的接口不要像java一样进行声明,编译器能推断出来,这既给了动态类型的表达能力又保留了静态类型检查的安全

USB接口,由USB概念提出者定义接口类,然后厂家具体根据自己的产品实现提出者所讲的接口类的所有内容。

type Printer interface {
	Print()
}
//interface是任何类的父类,是一个接口类.
type users struct {
	name string
	age  byte
}
type good struct {
	name       string
	num, price byte
}

func (u users) Print() {
	fmt.Println(u)
}
func (g good) Print() {
	fmt.Println(g)
}

func main() {
	u := users{"Tom", 12}
	g := good{"JackL", 1, 100}
	var p Printer = u
	p.Print()
	p = g
	p.Print()
}

继承

go通过匿名组合实现类似继承的功能。

type user struct{
    name string
    age byte
}
type leader struct{
    user
    title string
}
//leader继承user
type father_people struct {
	name string
	age  byte
}

func (f father_people) Print() {
	fmt.Println(f)
}

type leader struct {
	father_people
	title string
}

func main() {
	var l leader
	l.name = "TIM"
	l.age = 19
	l.title = "Sale"
	l2 := leader{father_people{"ITM", 22}, "Make"}
	l.Print()
	l2.Print()
}

并发

go func执行的并发。每个函数之间相互不影响。但需要主函数撑到子函数运行完才可能看到子函数运行的结果。

  1. go程序整个运行时都是完全并发化设计的
  2. 凡是你看到的几乎都是goroutine方式运行
  3. goroutine是比普通协程或线程更高效的并发设计
  4. 轻松创建和处理成千上万的并发任务
  5. 通过go运行时映射到适当的操作系统原语
package main

import (
	"fmt"
	"time"
)

func taskone() {
	for i := 0; i < 10; i++ {
		fmt.Println("Task one print : ", i)
		time.Sleep(time.Second * 2)
	}
}

func tasktwo() {
	for i := 0; i < 10; i++ {
		fmt.Println("Task two print : ", i)
		time.Sleep(time.Second * 3)
	}
}

func main() {
	go taskone()
	go tasktwo()
	fmt.Println("Main print?!")
	time.Sleep(time.Second * 30)
}
Main print?!
Task two print :  0
Task one print :  0
Task one print :  1
Task two print :  1
Task one print :  2
Task two print :  2
Task one print :  3
Task one print :  4
Task two print :  3
Task one print :  5
Task two print :  4
Task one print :  6
Task one print :  7
Task two print :  5
Task one print :  8
Task two print :  6
Task one print :  9
Task two print :  7
Task two print :  8
Task two print :  9

并发的通讯

利用make(chan type)来创建一个可以传递type类型变量的通道。其中,可以使用make(chan interface{})传递所有类型变量。

func consumer(data chan int, done chan bool) {
	for x := range data {
		fmt.Println("rect : ", x, "\n")
	}
	done <- true
}
func producer(data chan int) {
	for i := 0; i < 4; i++ {
		fmt.Println("Send : ", i)
		data <- i
		time.Sleep(time.Second)
	}
	close(data)
}

func main() {
	done := make(chan bool)
	data := make(chan int)
	go producer(data)
	go consumer(data, done)
	<-done
}
  • 发送:
    1. 通过chan <- value来发送
    2. 发送会阻塞直到被接收
ch := make(chan interface{})
ch <- 0
ch <- 'hello'

不过这个代码会报错,因为没有接受通道的东西.

  • 接收:
    1. 通过variable = <-chan来接收.
    2. 阻塞式接收.data := <-chan.如果没有接受到,就一直阻塞.
    3. 非阻塞式接受.data,ok := <-chan如果接收到了(也就是信道是开着的,没被close)ok=true,不然为假.这东西也满足有信号才接收的性质,如果没有信号强制接收,ok会返回一个false。
    4. 阻塞等同步.<-chan
    5. 循环接收.for data := range ch遍历结果式接收到的数据,类型是通道的数据类型.

单向信道

var variable chan<- type只能发送的通道.var variable <-chan type只能接收的通道.而且他们可以这么用

ch := make(chan int)

var chSendOnly chan<- int = ch
var chRecvOnly <-chan int = ch
//实际上就把ch信道给分解开了

信道的缓冲

  • 无缓冲的信道

一次只能处理一个数据,发送方要等对面接收,等待过程是阻塞的。接收方的接收方式跟之前是一样的。

func consumer(data chan int, done chan bool) {

	for x := range data {
		time.Sleep(time.Microsecond)
        //加这个是防止两个同时的输出顺序不符合我们的预期
		fmt.Println("rect : ", x, "\n")
		time.Sleep(time.Second)
	}
	done <- true
}
func producer(data chan int) {
	for i := 0; i < 4; i++ {
		data <- i
		fmt.Println("Send : ", i, "\n")
	}
	close(data)
}

func main() {
	done := make(chan bool)
	data := make(chan int)
	go producer(data)
	go consumer(data, done)
	<-done
}
Send :  0 

rect :  0 

Send :  1 

rect :  1 

Send :  2 

rect :  2 

Send :  3 

rect :  3 
  • 有缓冲的信道

    无缓冲同时只能存0个,有缓冲的信道可以认为是可以暂存的信道。

    定义:ch := make(chan int,3)

    获取当前信道已有个数:len(ch)

    阻塞条件:

    • 被填满,再次发送数据
    • 为空,尝试接收数据
    func recv(ch chan int, done chan bool) {
    	for x := range ch {
    		time.Sleep(time.Microsecond)
    		fmt.Println("rect : ", x)
    		time.Sleep(time.Second * 2)
    	}
    	done <- true
    }
    
    func send(ch chan int) {
    	for i := 0; i <= 10; i++ {
    		ch <- i
    		fmt.Println("Send : ", i)
    	}
    	close(ch)
    }
    
    func main() {
    	ch := make(chan int, 3)
    	done := make(chan bool)
    	go send(ch)
    	go recv(ch, done)
    	<-done
    }
    
    Send :  0
    Send :  1
    Send :  2
    Send :  3 //这里阻塞了,然后就一直一个接收一个发送
    rect :  0
    Send :  4
    rect :  1
    Send :  5
    rect :  2
    Send :  6
    rect :  3
    Send :  7
    rect :  4
    Send :  8
    rect :  5
    Send :  9
    rect :  6
    Send :  10//最后没有发送了,只有接收
    rect :  7
    rect :  8
    rect :  9
    rect :  10
    
  • 超时响应机制

select{
case IO1:
    //...
case IO2:
    //...
default:
    //...
}

依次执行IO语句,如果有一个没被阻塞,那么执行对应的语法块。并且执行完后退出select语法块。如果没有default,那么select就一直被阻塞直到有一个IO可以进行了。

同信道同函数不传递

func check_rw(ch chan int) {
	for i := 0; i < 10; i++ {
		ch <- i
		time.Sleep(time.Second)
		fmt.Println("Wow\n")
		t := <-ch
		fmt.Println(t)
	}
}

func main() {
	ch := make(chan int)
	go check_rw(ch)
	time.Sleep(time.Second * 10)
}

运行结果:

c:/go/bin/go.exe build [C:/Users/123/go/src/并发]
成功: 进程退出代码 0.
C:/Users/123/go/src/并发/并发.exe  [C:/Users/123/go/src/并发]
成功: 进程退出代码 0.

样例程序-剪刀石头布

func playerone(data1 chan int, result1 chan int) {
	for x := 0; x < 5; x = <-result1 {
		time.Sleep(time.Second * 10)
		fmt.Println("Player one OK!")
		data1 <- rand.Intn(3)
	}
	close(data1)
	close(result1)
}
func playertwo(data2 chan int, result2 chan int) {
	for x := 0; x < 5; x = <-result2 {
		time.Sleep(time.Second * 10)
		fmt.Println("Player two OK!")
		data2 <- rand.Intn(3)
	}
	close(data2)
	close(result2)
}
func onetwopk(data [2]chan int, result [2]chan int, resultt chan int) {
	res1, res2 := 0, 0
	for {
		x1 := <-data[0]
		fmt.Println("Recieve player one!")
		x2 := <-data[1]
		fmt.Println("Recieve player two!")
		for j := 0; j < 3; j++ {
			fmt.Println("Show the choose in ", (3 - j), "Seconds!")
			time.Sleep(time.Second)
		}
		fmt.Println("Player one : ", x1, "\nPlayer two : ", x2)

		if (x1+1)%3 == x2 {
			res1 = res1 + 2
			fmt.Println("\nPlayer one win!\n")
		} else if x1 == x2 {
			res1 = res1 + 1
			res2 = res2 + 1
			fmt.Println("\nPlayers both win!\n")
		} else {
			res2 = res2 + 2
			fmt.Println("\nPlayer two win!\n")
		}

		result[0] <- res1
		result[1] <- res2

		if res1 >= 5 || res2 >= 5 {
			resultt <- res1 - res2
			break
		}
	}
}

func main() {
	rand.Seed(time.Now().Unix())
	data := [2]chan int{make(chan int), make(chan int)}
	result := [2]chan int{make(chan int), make(chan int)}
	resultt := make(chan int)
	go playerone(data[0], result[0])
	go playertwo(data[1], result[1])
	go onetwopk(data, result, resultt)
	ans := <-resultt
	fmt.Println(ans)
}

一种不可能重复的运行结果

Player two OK!
Player one OK!
Recieve player one!
Recieve player two!
Show the choose in  3 Seconds!
Show the choose in  2 Seconds!
Show the choose in  1 Seconds!
Player one :  2 
Player two :  2

Players both win!

Player one OK!
Recieve player one!
Player two OK!
Recieve player two!
Show the choose in  3 Seconds!
Show the choose in  2 Seconds!
Show the choose in  1 Seconds!
Player one :  2 
Player two :  0

Player one win!

Player one OK!
Recieve player one!
Player two OK!
Recieve player two!
Show the choose in  3 Seconds!
Show the choose in  2 Seconds!
Show the choose in  1 Seconds!
Player one :  1 
Player two :  2

Player one win!

4

数据类型

类型简述

数据类型表示大小取值注意
布尔型bool1字节true,false不能用数字代替
整型int/uint32/64位因平台而差
8位整型int8/uint81字节[-128,127],[0,255]
字节型byte1字节uint8别名
16位整型int16/uint162字节[-28,28-1],[0,2^16-1]
32位整型int32/uint324字节[-216,216-1],[0,2^32-1]int32(rune)
64位整型int64/uint648字节[-232,232-1],[0,2^64-1]
浮点型float32/float644/8字节精确到7/15小数位
复数complex64/1288/16字节
指针uintptr32/64位

其他数据类型:

  • 值类型:array,struct,string。
  • 引用类型:slice,map,chan。
  • 接口类型:interface
  • 函数类型:func。
func main() {
	var comp complex128 = 2 + 1i
	var comp1 complex128 = 2
	a := real(comp)
	b := imag(comp)
	c := cmplx.Sqrt(comp)
	fmt.Println(comp, "\n", a, "\n", b, "\n", c, "\n", comp1)
}

零值类型

零值不是空值,是默认值。值类型为0,bool为false,string为“”。

类型转换

Go不支持隐式转换,<ValueA> := <TypeofValueA>(<ValueB>).

关键字

内置关键字内置关键字内置关键字内置关键字内置关键字
breakdefaultfuncinterfaceselect
casedefergomapstruct
chanelsegotopackageswitch
constfallthroughifrangetype
continueforimportreturnvar

运算符

全都是从左往右结合。以下式它们的优先级,从高向低

  1. ^ ! (一元运算符)
  2. * / % << >> & &^
  3. + - | ^(二元运算符)
  4. == != < <= > >=
  5. <- (channel专用)
  6. &&
  7. ||

&是清除标志位,a&b相当于二进制集合差集,从a上清除b的标志位。^按位取反或者异或。

func main() {
	a := 127
	b := 1
	fmt.Println(a)
	c := a &^ b
	fmt.Println(c)//126

	a = 5
	b = 2
	c = a &^ b
	fmt.Println(c)//5
}

指针、递增递减

Go没有->,统一使用.来进行操作。默认值为nil。++和–是作为语句而不是表达式。

常量

常量的概念

常量的值编译的时候确定,定义时把var换成const即可。可以同时定义多个常量。

const a int = 1
const b = 'A'
const(
	text = "123"
    length = len(text)
    num = b *20
)
const i,j,k = 1,"2","333"

常量的初值

不提供初值则用上一行的表达式。

const(
	a = "A"
    b//b = "A"
)

可以使用iota,常量计数器,从0开始自动递增,遇到const关键字清零。可以结合表达式进行使用。

const(
	a = iota//0
    b//1
    c//2
    d//3
)
const(
	e = iota//0
    f//1
)
const(
	ca = iota + 10//10
    cb//11
    cc//12
)

内置函数

  • func append(slice []Type, elems ...Type) []Type
  • func cap(v Type) int
  • func close(c chan<- Type)
  • func complex(r, i FloatType) ComplexType
  • func copy(dst, src []Type) int
  • func delete(m map[Type]Type1, key Type)
  • func imag(c ComplexType) FloatType
  • func len(v Type) int
  • func make(Type, size IntegerType) Type
  • func new(Type) *Type
  • func panic(v interface{})
  • func real(c ComplexType) FloatType
  • func recover() interface{}

输入

fmt.Scanln(&c)//读一行
fmt.Scanf()//跟C是一样的

如何分工协作和功能复用

如何实现分工

  1. 多个文件多个package,然后import对应的package
  2. 函数名大写字母开头才能够进行调用

举个例子:

//分工/input/input.go
package input

import (
	"fmt"
)

func Input(a1, dis, n *int) (int, int, int) {
	var a, b, c int
	fmt.Println("Please input a:")
	fmt.Scanln(&a)
	fmt.Println("Please input b:")
	fmt.Scanln(&b)
	fmt.Println("Please input c:")
	fmt.Scanln(&c)
	return a, b, c
}
//分工/calc/calc.go
package calc

func Calc(a1, dis, n int) (a int) {
	a = (a1 + a1 + dis*(n-1)) * n / 2
	return
}
//分工/output/output.go
package output

import "fmt"

func Output(a1, dis, n, sum int) {
	fmt.Println("a1 = ", a1)
	fmt.Println("dis = ", dis)
	fmt.Println("n = ", n)
	fmt.Println("sum = ", sum)
}
//分工/main/main.go
package main

import (
	"分工/calc"
	"分工/input"
	"分工/output"
    //默认是同目录+编译器目录
)

func main() {
	var a1, dis, n, sum int
	a1, dis, n = input.Input(&a1, &dis, &n)
	sum = calc.Calc(a1, dis, n)
	output.Output(a1, dis, n, sum)
}

同包的文件必须在一个目录下,同目录下只能有一个包。举例:删掉之前那个input。然后换成下面的也可以。

//分工/main/input.go
package main

import (
	"fmt"
)

func Input(a1, dis, n *int) (int, int, int) {
	var a, b, c int
	fmt.Println("Please input a:")
	fmt.Scanln(&a)
	fmt.Println("Please input b:")
	fmt.Scanln(&b)
	fmt.Println("Please input c:")
	fmt.Scanln(&c)
	return a, b, c
}

单元测试

测试你的函数是不是对的。或者说测试你的某些模块是不是对的。

方法:建立一个*_test.go的文件,然后这个文件中所有的函数名都应该是Test_*开头,参数为*testing.T类型。

终端指令:go test或者go test -v.LiteIDE指令:T的下拉菜单第一个。

// calc_test.go
package calc

import "testing"

func Test_sum(t *testing.T) {
	if result := Calc(1, 1, 100); result != 5051 {
		t.Error("Wrong")
	}
}
//calc.go
package calc

func Calc(a1, dis, n int) (a int) {
	a = (a1 + a1 + dis*(n-1)) * n / 2
	return
}

性能测试

测试你的函数是不是对的。或者说测试你的某些模块是不是对的。

方法:建立一个*_test.go的文件,然后这个文件中所有的函数名都应该是Benchmark_*开头,参数为*testing.D类型。

终端指令:go test -bench=".*" -cpuprof=calc.prof或者go test -v -bench=".*" -cpuprof=calc.prof.LiteIDE指令:T的下拉菜单TestBench

查看结果(不过要先点那个TestBuildgo test -c -bench=".*" -cpuprofile=calc.prof):go tool pprof calc.test.exe calc.prof

然后输入

  • text.就可以用文本的方式查看性能测试的结果.

  • web以图片的形式查看图形,是一个svg文件.

//calc_b_test.go
package calc

import "testing"

func Benchmark_Sum(b *testing.B) {
	for i := 0; i < b.N; i++ {
		Calc(1, 1, 100)
	}
}
//calc.go
package calc

func Calc(a1, dis, n int) (a int) {
	a = (a1 + a1 + dis*(n-1)) * n / 2
	return
}

集成开发

思想:每个人完成自己的程序后提交到一个代码服务器上,然后需要调用的时候就从代码服务器进行调用。

协同开发工具的推荐

  • Source Control: CVS/SVN、 git 和github
  • Bug Tracking: Bugzilla, Trac, Roundup
  • 交流工具: maillist, IM, Forum, IRC, Wiki
  • 协同开发平台: sourceforge, BaseCamp

编译并执行整个系统?

go install 包源码存放路径的最后一个目录
生成可执行文件到bin,依赖的包文件到pkg

C:.
│  doc.go
│  main.go
│
├─calc
│      calc.go
│
├─input
│      input.go
│
└─output
        output.go

就在这个文件夹下面输入go install即可。

如果想要install一个具体的包,那么需要搞到他的那个目录。go install ./input

bin,pkg是存放install完成之后的主程序和包程序。

功能复用

max(max(a,b),c)这样的。

其他编译命令

  • go build 编译包,如果是main包则在当前目录生成可执行文件,其他包不会生成.a文件;

  • go install 编译包, 分别生成可执行文件和依赖包文件到%GOPATH%/bin,%GOPATH%/pkg

  • go run gofiles… 编译列出的文件,并生成可执行文件然后执行。注意只能用于main包,否则会出现go run: cannot run non-main package的错误。

  • go run是不需要设置GOPATH的,但go build和go install必须设置。

  • go run常用来测试一些功能,这些代码一般不包含在最终的项目中。

  • go test单元测试,对当前目录下的所有*_test.go文件进行编译并自动运行测试

  • go test –v。单元测试, “-v" 表示无论用例是否测试通过都会显示结果,不加"-v"表示只显示未通过的用例结果

  • go test –bench=“.*”。性能测试

  • go test –v –bench=“.*”。同时进行单元测试和性能测试,同上

  • go test -run=‘Test_xxx’。测试某个方法

Go与C交互

Go与C语言的互操作-参考文章

具体使用方法:把C语言中的一些句子搞成注释,然后在下面import"C",这时候想要使用C的一些东西,就可以搞成C.int,C.printf之类的了。

关于一些参数的传递可以参考上述的参考文章。

// UseC/main.go
package main

import (
	"fmt"
	"unsafe"
)

/*
#include"myprint.h"
*/
import "C"

func main() {
	fmt.Println("Hello World!")
	s := "Hello Cgo!"
	cs := C.CString(s)
	C.myprint(cs)
	//C.myprint(C.CString(s))
	//但我不知道这样内存是啥样的
	C.free(unsafe.Pointer(cs))
}
//UseC/myprint.c
#include"myprint.h"

void myprint(char* s){
    printf("myprint : %s\n",s);
}
//UseC/myprint.h
#include<stdio.h>
#include<stdlib.h>

void myprint(char* s);

不过我们发现了一个有趣的现象,那就是他的输出结果并不是像我们所期待那样顺序输出的。

// UseC/main.go
package main

import "fmt"

/*
#include<stdio.h>
#include<stdlib.h>
void pt(){
	printf("Hello cgo!\n");
}
int Add(int a,int b){
	printf("result = %d\n",a+b);
	return a+b;
}
*/
import "C"

func main() {
	fmt.Println("Hello1111")
	C.pt()
	fmt.Println("Hello2222")
	fmt.Println(C.Add(C.int(10), 12))
	fmt.Println("Hello3333")
}

输出结果:

Hello1111
Hello2222
22
Hello3333
Hello cgo!
result = 22

如何读写数据

如何读取用户输入

fmt.ScanX / fmt.SscanX

前者是从标准输入输出进行读取,后者是从String中进行读取.

fmt.Scanf(format,values...)
fmt.SScanf(stringinput,format,values...)
// 输出输出 project main.go
package main

import (
	"fmt"
)

var (
	first_name, last_name, s string
	fn, ln                   string
	i                        int
	f                        float32
	input                    = "56.12 / 5212 / Go"
	format                   = "%f / %d / %s"
)

func main() {
	fmt.Println("Please input your full name: ")
	fmt.Scanln(&first_name, &last_name)//回车结束
	fmt.Printf("Hi %s %s!\n", first_name, last_name)
	fmt.Scanf("%s %s", &fn, &ln)//回车结束
	fmt.Printf("Hello %s %s!\n", fn, ln)
	fmt.Sscanf(input, format, &f, &i, &s)
	fmt.Printf("From string : %f %d %s", f, i, s)
}
Please input your full name:
Sofan He
Hi Sofan He!
SOfan
Hello SOfan !
From string : 56.119999 5212 Go

利用bufio包的buffed reader来读取数据(缓冲读取)

需要引入的包bufio,osos使用来获取流的.os.Stdin,os.Stdout都是*os.File类型的.

import (
	"bufio"
	"fmt"
	"os"
)

var input_reader *bufio.Reader
var input string
var err error

func main() {
	input_reader = bufio.NewReader(os.Stdin)
	//创建一个读取器,与标准输入输出绑定

	fmt.Println("Please enter som input : ")
	input, err = input_reader.ReadString('\n')
	//一直读取到回车,括号是分隔符,String是指返回值
	if err == nil {
		fmt.Printf("The input was : %s\n", input)
	}

	fmt.Println("Please enter som input : ")
	inputb, errb := input_reader.ReadBytes('\n')
	//一直读取到回车,括号里面的是分隔符
	if errb == nil {
		fmt.Printf("The input was : %s\n", inputb)
	}
}

如何读写文件

所有文件的表示方式都是指向os.file的一个指针.

读文件,从磁盘中的文件流向计算机内存.

写文件,从计算机内存刘翔磁盘中的文件.

  • 按行读取文件
func main() {
	var input_file *os.File
	var input_error, reader_error error
	var input_reader *bufio.Reader
	var inputstring string

	input_file, input_error = os.Open("dat.txt")
	if input_error != nil {
		fmt.Println("Error")
		return
	}
	defer input_file.Close()
    
	input_reader = bufio.NewReader(input_file)
	for {
		inputstring, reader_error = input_reader.ReadString('\n')
		if reader_error == io.EOF {
			return
		}
        fmt.Printf("The input data is : %s\n", inputstring)
	}
    
}
  • 按照字节读取到一个[]byte
func main() {
	inputFile := "dat.txt"
	outputFile := "dat_cp.txt"
	buf, err := ioutil.ReadFile(inputFile)
	//buf : []byte
	if err != nil {
		panic(err.Error())
	}
	fmt.Printf("%s\n", string(buf))
	err = ioutil.WriteFile(outputFile, buf, 0644)//8 非 16?
	if err != nil {
		panic(err.Error())
	}
}

0644 是filemode 定义在os\types.go
type FileMode uint32
// The defined file mode bits are the most significant bits of the FileMode.
// The nine least-significant bits are the standard Unix rwxrwxrwx permissions.
// The values of these bits should be considered part of the public API and
// may be used in wire protocols or disk representations: they must not be
// changed, although new bits might be added.
linux文件权限一般都以8进制表示,格式为abc的形式,其中a,b,c各为一个数字,分别表示User、Group、及Other对该文件的操作权限;
如果文件权限用二进制表示那么是9位bit,从左至右,1-3位数字代表文件所有者的权限,4-6位数字代表同组用户的权限,7-9数字代表其他用户的权限;
而具体的权限是由数字来表示的,读取的权限等于4,用r表示;写入的权限等于2,用w表示;执行的权限等于1,用x表示;
通过4、2、1的组合,得到以下几种权限:0(没有权限);4(读取权限);5(4+1 | 读取+执行);6(4+2 | 读取+写入);7(4+2+1 | 读取+写入+执行)
常用的linux文件权限如下:
444 r–r--r–
600 rw-------
644 rw-r–r--
666 rw-rw-rw-
700 rwx------
744 rwxr–r--
755 rwxr-xr-x
777 rwxrwxrwx
这里以755为例:
1-3位7等于4+2+1,rwx,所有者具有读取、写入、执行权限;
4-6位5等于4+1+0,r-x,同组用户具有读取、执行权限但没有写入权限;
7-9位5,同上,也是r-x,其他用户具有读取、执行权限但没有写入权限。

  • 带缓冲区的读取
func main() {
	inputFile, input_error := os.Open("dat.txt")
	if input_error != nil {
		fmt.Println("Error")
		return
	}
	defer inputFile.Close()
	input_reader := bufio.NewReader(inputFile)
	buf := make([]byte, 3)
	str1 := ""
	for n, err := input_reader.Read(buf); n != 0; n, err = input_reader.Read(buf) {
		if err != nil {
			fmt.Println(err)
			return
		}
		fmt.Println(buf, len(buf), cap(buf))
        str1 = str1 + string(buf[:n])
        //加上这句话之后,可以有读进来多少就存多少
	}
	fmt.Println(str1)
}
//一次读取两个字节的长度
[49 50] 2 2
[51 52] 2 2
[53 13] 2 2
[10 65] 2 2
[66 67] 2 2
[68 69] 2 2
[13 10] 2 2
[33 64] 2 2
[35 36] 2 2
[37 13] 2 2
[10 13] 2 2
12345
ABCDE
!@#$%
  • 格式化读取,FscanX
func main() {
	file, err := os.Open("dat.txt")
	if err != nil {
		panic(err)
	}
	defer file.Close()

	var col1, col2, col3 []string
	fmt.Printf("%T %d %d\n", col1, len(col1), cap(col1))
	for {
		var v1, v2, v3 string
		_, err := fmt.Fscanln(file, &v1, &v2, &v3)
		if err != nil {
			break
		}
		col1 = append(col1, v1)
		col2 = append(col2, v2)
		col3 = append(col3, v3)
	}
	fmt.Printf("%T %d %d\n", col1, len(col1), cap(col1))
	fmt.Println(col1)
	fmt.Println(col2)
	fmt.Println(col3)
}
[]string 0 0
[]string 3 4
[12345 ABCDE !@#$%]
[100 300 500]
[200 400 600]
12345 100 200
ABCDE 300 400
!@#$% 500 600
  • 如何写文件(带缓冲区)
func main() {
	outputFile, outputerr := os.OpenFile("output.dat", os.O_WRONLY|os.O_CREATE, 0666)
    //os.O_RDONLY,os.O_WRONLY,os.O_CREATE,os.C_TRUNC
    //只读,只写,不存在就创建,存在就截断为0
	if outputerr != nil {
		fmt.Println("OH?")
		return
	}
	defer outputFile.Close()
	outputWriter := bufio.NewWriter(outputFile)
	outputString := "Hello World\n"
	for i := 0; i < 10; i++ {
		outputFile.WriteString(outputString)
        //写入缓存区
	}
	outputWriter.Flush()
    //缓存区内容紧接着被完全写入文件.相当于延时,等待同步
}
  • 如何写文件(不带缓冲区)

(*os.File).WriteString(string)就直接写进文件了!

特别注意:os.Stdout.WriteString("HE")就可以直接把"HE"写到屏幕上了!

补充 : 写入文件可以使用os.Create(filepath),无则创建,有则截断

如何拷贝文件

利用io包Copy(*os.File,*os.File)(int64,error)

传入target,source,返回是写入长度和错误信息

接口类型推断

啥是接口类型推断

如果一个类型实现了接口,那么接口类型变量里面就可以存储该类型的数据(把接口实现功能的对象插入到接口中)

如何反向知道接口类型变量里面实际保存的是哪一种类型的变量?这个问题就是接口类型推断.

方法有两种:

  1. comma-ok的模式
  2. switch测试
type People struct {
	Name string
	Age  int
}

type Tester interface{}

func main() {
	people := People{"张三", 20}
	it := make([]Tester, 4)
	it[0] = 1
	it[1] = "Hello"
	it[2] = people
	it[3] = true
	for i, e := range it {
		if val, ok := e.(int); ok {
			fmt.Printf("it[%d] type is int,val = %d\n", i, val)
		} else if val, ok := e.(string); ok {
			fmt.Printf("it[%d] type is string,val = %s\n", i, val)
		} else if val, ok := e.(People); ok {
			fmt.Printf("it[%d] type is People,val = %v\n", i, val)
		} else if val, ok := e.(bool); ok {
			fmt.Printf("it[%d] type is bool,val = %v\n", i, val)
		}
	}

	fmt.Println("\n")

	for i, e := range it {
		switch val := e.(type) {
		case int:
			fmt.Printf("it[%d] type is int,val = %d\n", i, val)
		case string:
			fmt.Printf("it[%d] type is string,val = %s\n", i, val)
		case People:
			fmt.Printf("it[%d] type is People,val = %v\n", i, val)
		case bool:
			fmt.Printf("it[%d] type is bool,val = %v\n", i, val)
		default:
			fmt.Println("Unknown type of it[", i, "]")
		}
	}
}
//e.(type):查询e的数据类型,不能在switch语句之外的任何语句内使用

反射

啥是反射?就是逆映射的意思.

正向 : (类型,值) -> 对象

反向 : 对象 -> (类型,值)

类型推断就是反射的一种?

反射是获取程序运行时类型信息的方式.

作用:

  1. 让静态语言具备更加多样的运行时的动态特征
  2. 让程序具备自省能力,使得interface接口对象的灵活性有更大的发挥余地

reflect包的功能

  1. 获取原对象的Type和Value值
  2. 修改元对象Value的值
  3. 动态调用原对象的方法
  • Type是被反射对象的类型信息.Typeof()函数进行获取.Type中的Kind()方法也可以获取该类的具体信息.
  • Value是该对象的值信息.Valueof()函数进行获取.Value中包含一系列相关方法,如Int(),Float(),Bool()用于返回对应类型的值.
func main() {
	pi := 3.14
	t := reflect.TypeOf(pi)
	v := reflect.ValueOf(pi)
	if t.Kind() == reflect.Float64 {
		fmt.Println("Type : float64", "Value", v.Float())
		fmt.Println(t, v)
	}
}

搞个结构体的那种

type Student struct {
	Id    int
	Name  string
	Sex   bool
	Grade float32
}

func (s Student) Myname() {
	fmt.Println("My name is ", s.Name)
}

func (s Student) Say() {
	fmt.Println("Hello! Nice to meet you!")
}

func StructInfo(o interface{}) {
	t := reflect.TypeOf(o)
	v := reflect.ValueOf(o)
	if k := t.Kind(); k != reflect.Struct {
		fmt.Println("It is not a struct ?!")
		return
	}
	fmt.Println("Struct name is :", t.Name())
	fmt.Println("Fields of the struct is : ")
	for i := 0; i < t.NumField(); i++ {
		y := t.Field(i)
		y1, y2 := t.Field(i).Type, v.Field(i).Interface()
		fmt.Println(i, ":", y, ":", y1, ":", y2)
	}

	fmt.Println("Method of the struct is : ")
	for i := 0; i < t.NumMethod(); i++ {
		method := t.Method(i)
		fmt.Printf("%s : %v\n", method.Name, method.Type)
	}
}

func main() {
	stu := Student{001, "这是个名字", true, 99.8}
	StructInfo(stu)
}

还可以修改原对象的值,然后动态进行调用.

排序

引入sort包,然后用以下的几个例子看吧

[下次再更]

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值