go语言在多核并发上拥有原生的设计优势,兼顾性能和开发效率
号称:Python的开发速度,C/C++的性能和安全
php迁go属于大的架构调整,从架构角度  个人认为go符合,简单、合适的原则
1、架构设计的主要目的
是为了解决软件系统复杂度带来的问题
2、系统复杂度的主要来源
高性能、高可用、可扩展性、低成本、安全、规模
3、架构设计三原则
合适原则、简单原则、演化原则

菜鸟教程:https://www.runoob.com/go/go-tutorial.html

go:开源、可靠高效、多核并发、编译型语言、2012发布Go1稳定版本。
特点:简洁、快读、安全、并行、有趣、开源、内存管理、数组安全、编译迅速
适用:Go 语言被设计成一门应用于搭载 Web 服务器,存储集群或类似用途的巨型中央服务器的系统编程语言。
对于高性能分布式系统领域而言,Go 语言无疑比大多数其它语言有着更高的开发效率。它提供了海量并行的支持,这对于游戏服务端的开发而言是再好不过了。

计算机软件经历了数十年的发展,形成了多种学术流派,有面向过程编程、面向对象编程、函数式编程、面向消息编程等,这些思想究竟孰优孰劣,众说纷纭。
除了OOP外,近年出现了一些小众的编程哲学,Go语言对这些思想亦有所吸收。例如,Go语言接受了函数式编程的一些想法,支持匿名函数与闭包。再如,Go语言接受了以Erlang语言为代表的面向消息编程思想,支持goroutine和通道,并推荐使用消息而不是共享内存来进行并发编程。总体来说,Go语言是一个非常现代化的语言,精小但非常强大。
Go 语言最主要的特性:
自动垃圾回收
更丰富的内置类型
函数多返回值
错误处理
匿名函数和闭包
类型和接口
并发编程
反射
语言交互性
go安装 菜鸟方案
1.下载二进制安装包:https://golang.google.cn/dl/
go1.16.linux-amd64.tar.gz
问题:.pkg和.tar.gz只是压缩方式的不同吗
2.解压安装包到目录
tar -C /usr/local -xzf go1.4.linux-amd64.tar.gz
3.添加PATH环境变量
export PATH=$PATH:/usr/local/go/bin
MAC 系统下你可以使用 .pkg 结尾的安装包直接双击来完成安装,安装目录在 /usr/local/go/ 下。

问题:go安装到哪?

go安装 GDP2方案
1.配置环境变量
建议把 go 本身安装在 ~/sdk/ 目录下,如 ~/sdk/go1.16。
先设置环境变量(建议写入到 ~/.bash_profile 文件 )?
export GOBIN=$HOME/go/bin  
export PATH=$PATH:$GOBIN:$HOME/sdk/go1.16/bin
作用:
后续使用 go 命令安装的二进制程序都在 $HOME/go/bin 目录里
设置 $PATH以让 go 和 使用 go 安装的二进制程序能直接使用
2.安装:哪种方式安装?
MAC直接安装:brew install go
内网测试机deck环境安装:deck install go-1.16
go1.13开始的依赖管理工具Go Module

问导师:直接安装pkg的,自动安装到一个目录,环境变量也不需要自己配了。然后学一下语法,可以在终端编译。装一下vscode。配一下Go module,镜像。

brew是MacOS上的包管理工具,可以简化操作系统上软件的安装。
brew是ruby开发的,需要确认ruby是否已安装,默认是已经安装的。
which命令:查看安装路径
安装brew:
ruby -e "$(curl -fsSL 
https://raw.githubusercontent.com/Homebrew/install/master/install)"  (没反应)
另一条命令:
/bin/zsh -c "$(curl -fsSL https://gitee.com/cunkai/HomebrewCN/raw/master/Homebrew.sh)"

(MAC安装和使用go:https://www.jianshu.com/p/67dcf4e828e9)
安装好了brew再go version就有了。
go env 查看环境变量
which go 查看安装目录:/usr/local/go/bin/go
go的开发项目路径修改:  ??
export GOPATH=/Users/liuxuan14/work/go_project

vim一个hello.go
go run hello.go //直接运行
或者:
go build hello.go //先编译
./hello  //运行编译文件 ./是运行的意思
go语言结构:
go语言的基础组成:包声明、引入包、函数、变量、语句 & 表达式、注释

package main  //第一行,表明该文件属于main包,表示一个可独立执行的程序。
import "fmt"  //导包:fmt包实现格式化IO
func main() {  //init()函数?
   /* 这是我的第一个简单的程序 */
   fmt.Println("Hello, World!")
  //在最后自动增加换行字符\n
}

注意:{不能单独放在一行

标识符(包括常量、变量、类型、函数名、结构字段等等)如果以一个大写字母开头,如:Group1,那么使用这种形式的标识符的对象就可以被外部包的代码所使用(客户端程序需要先导入这个包),这被称为导出(像面向对象语言中的 public);标识符如果以小写字母开头,则对包外是不可见的,但是他们在整个包的内部是可见并且可用的(像面向对象语言中的 protected )。

包结构,文件名与包名
go语言基础语法:
一行代表一个语句结束,无需;
除非非要写在一行,用;人为区分
标识符:第一个字符需要是字母或下划线不能是数字,不能是go关键字,不能有运算符。
字符串连接:fmt.Println("Google" + "Runoob")
go变量的申明必须使用空格隔开:
var age int;
fmt.
Sprintf 格式化字符串并赋值给新串。
var stockcode=123
var enddate="2020-12-31"
var url="Code=%d & endDate=%s"
var target_url=fmt.Sprintf(url,stockcode,enddate)
fmt.Println(target_url)

go数据类型:
var b bool=true
布尔型、数字类型、字符串类型、派生类型(指针、数组、结构化、channel、函数、切片、接口interface、map)
uint8(0-255)uint16 uint32 uint64 int8(-128-127) 
float32:32位浮点数
float64:64位浮点数
complex64:32位实数和虚数
byte 类似uint8
rune 类似int32
uintptr 无符号整型,用于存放一个指针。

go变量:
var a,b type //一次定义两个变量
var b,c int=1,2
fmt.Println(b,c)
变量只声明但是不初始化,默认为零值。
可以根据值自行判定变量类型。
定义变量可省略var
a:=1  //一定是需要定义新的变量,如果变量存在会编译错误。省略申明格式的写法只能在函数体中出现。
var a,b =1,2 //和python很像,不需要显示声明类型,自动判断。非全局变量
var(
  a int
  b int
)  //因式分解关键字的写法一般用于声明全局变量

值类型和引用类型
值类型的变量的值存储在栈中。
一个引用类型的变量 r1 存储的是 r1 的值所在的内存地址(数字),或内存地址中第一个字所在的位置。
这个内存地址称之为指针,这个指针实际上也被存在另外的某一个值中。
同一个引用类型的指针指向的多个字可以是在连续的内存地址中(内存布局是连续的),这也是计算效率最高的一种存储形式;也可以将这些字分散存放在内存中,每个字都指示了下一个字所在的内存地址。
当使用赋值语句 r2 = r1 时,只有引用(地址)被复制。
如果 r1 的值被改变了,那么这个值的所有引用都会指向被修改后的内容,在这个例子中,r2 也会受到影响。

//思考:在go中,引用类型中存放的是指针,指针相当于内存地址。用一个引用类型给另一个引用类型赋值,地址被赋值,地址一样,值变动,两个引用类型都指向这个修改后的内容。

函数中如果你声明了一个局部变量却没有在相同的代码块中使用它,会编译错误。
多个变量可以同时声明、初始化。
_ 实际上是一个只写变量,你不能得到它的值。这样做是因为 Go 语言中你必须使用所有被声明的变量,但有时你并不需要使用从一个函数得到的所有返回值。

go常量:
常量是一个简单值的标识符,在程序运行时,不会被修改的量。
常量中的数据类型只可以是布尔型、数字型(整数型、浮点型和复数)和字符串型。
const b string = "abc"
常量还可用作枚举
const (
    Unknown = 0
    Female = 1
    Male = 2
)
iota:特殊常量,可以认为是一个可以被编译器修改的常量。
iota 在 const关键字出现时将被重置为 0(const 内部的第一行之前),const 中每新增一行常量声明将使 iota 计数一次(iota 可理解为 const 语句块中的行索引)。
package main
import "fmt"
func main() {
    const (
            a = iota   //0
            b          //1
            c          //2
            d = "ha"   //独立值,iota += 1
            e          //"ha"   iota += 1
            f = 100    //iota +=1
            g          //100  iota +=1
            h = iota   //7,恢复计数
            i          //8
    )
    fmt.Println(a,b,c,d,e,f,g,h,i)
}
发现:下一行如果啥都没写的话,就跟上一行一样。
左移<<n 相当于*(2^n)
fmt.Printf() 不会自动换行,需要\n
fmt.Println() 会自动换行
&按位与
|按位或
^按位异或,不同为1
var ptr *int 定义指针变量
ptr=&a  
疑问:引用类型和指针类型?区别在哪
循环控制语句多一个:
goto语句:将控制转移到被标记的语句。
len()函数:返回该类型长度
函数模板:
package main
import "fmt"
func main() {
   /* 定义局部变量 */
   var a int = 100
   var b int = 200
   var ret int
   /* 调用函数并返回最大值 */
   ret = max(a, b)
   fmt.Printf( "最大值是 : %d\n", ret )
}
/* 函数返回两个数的最大值 */
func max(num1, num2 int) int {
   /* 定义局部变量 */
   var result int
   if (num1 > num2) {
      result = num1
   } else {
      result = num2
   }
   return result
}
多个返回值:
package main
import "fmt"
func swap(x, y string) (string, string) {
   return y, x
}
func main() {
   a, b := swap("Google", "Runoob")
   fmt.Println(a, b)
}
指针和引用的理解:
c++中:*解引用 &取地址
int a,b,*p=&a,&r=a;//正确
r=3;//正确:等价于a=3
int &rr;//出错:引用必须初始化
p=&a;//正确:p中存储a的地址,即p指向a
*p=4;//正确:p中存的是a的地址,对a所对应的存储空间存入值4
p=&b//正确:p可以多次赋值,p存储b的地址

go中:
有的变量是引用类型,而不是叫变量的别名。指针可以赋值给引用类型,引用类型可以看做是对指针的封装。
指针变量指向一个值的内存地址。
go语言中的值类型:
  int、float、bool、array、sturct等
  值传递是指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数
  声明一个值类型变量时,编译器会在栈中分配一个空间,空间里存储的就是该变量的值    
go语言中的引用类型:
  slice,map,channel,interface,func,string等
  声明一个引用类型的变量,编译器会把实例的内存分配在堆上
  string和其他语言一样,是引用类型,string的底层实现struct String { byte* str; intgo len; }; 但是因为string不允许修改,每次操作string只能生成新的对象,所以在看起来使用时像值类型。
  所谓引用传递是指在调用函数时将实际参数的地址传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数。
  需要注意的是:引用类型在函数的内部可以对它的值进行修改,但是如果给形参重新赋值,重新赋值后的形参再怎么修改都不会影响外面的实参了
  nil可以赋值给引用类型(除string外)、error类型和指针类型
go语言中的指针类型:
  一个指针变量指向了一个值的内存地址
   当一个指针被定义后没有分配到任何变量时,它的值为 nil。nil 指针也称为空指针
  一个指针变量通常缩写为 ptr
  其实引用类型可以看作对指针的封装
PS.go和C++中指针的区别:
指针是用来指向任何一个变量的内存地址的类型变量,它所指向的变量的内存地址在 32 和 64 位机器上分别占用 4 或者 8 个字节,占用字节的大小与所指向的值的大小无关。
1.用法不同:
func main() {
    var a int = 1 // 一个整型变量a
    var b string = "abcdefg" // 一个字符串变量b
    ptr:=&b
    // 打印ptr的类型
    fmt.Printf("ptr type: %T\n", ptr)
    // 打印ptr的地址
    fmt.Printf("address: %p\n", ptr)
    //对指针进行取值
    value:=*ptr
    fmt.Printf("%p %p", &a, &b) // 分别输出a和b的地址
}
--
int main() {
    int a  = 1; // 一个整型变量a
    string b  = "abcdefg"; // 一个字符串变量b
    cout<< &a <<" "<< &b <<endl; // 分别输出a和b的地址
    return 0;
}
2.C++语言中可以直接对指针做算术运算(+、-、++、--),但是Golang中是不行的。
引用传递
/* 定义交换值函数*/
func swap(x *int, y *int) {
   var temp int
   temp = *x    /* 保持 x 地址上的值 */
   *x = *y      /* 将 y 值赋给 x */
   *y = temp    /* 将 temp 值赋给 y */
}
swap(&a,&b)
和指针作为参数传递一模一样,一个更简洁的:
func swap(x *int, y *int){
    *x, *y = *y, *x
}
go函数用法:
1.可作为另一个函数的实参
package main
import (
   "fmt"
   "math"
)
func main(){
   /* 声明函数变量 */
   getSquareRoot := func(x float64) float64 {
      return math.Sqrt(x)
   }
   /* 使用函数 */
   fmt.Println(getSquareRoot(9))
}
感觉像是在main函数中定义了一个函数,然后调用,不需要再main函数外定义。像是定义一个变量一样。
2.匿名函数,可作为闭包。
内联在函数中,优越性在于可以直接使用函数内的变量,不必声明。
package main
import "fmt"
func getSequence() func() int {
   i:=0
   return func() int {
      i+=1
     return i  
   }
}  //返回一个函数
func main(){
   /* nextNumber 为一个函数,函数 i 为 0 */
   nextNumber := getSequence()  
   /* 调用 nextNumber 函数,i 变量自增 1 并返回 */
   fmt.Println(nextNumber())
   fmt.Println(nextNumber())
   fmt.Println(nextNumber())
   /* 创建新的函数 nextNumber1,并查看结果 */
   nextNumber1 := getSequence()  
   fmt.Println(nextNumber1())
   fmt.Println(nextNumber1())
}
输出:1 2 3 1 2
像是给变量取别名,两次使用是独立的。
3.函数方法
Go 语言中同时有函数和方法。一个方法就是一个包含了接受者的函数,接受者可以是命名类型或者结构体类型的一个值或者是一个指针。所有给定类型的方法属于该类型的方法集。语法格式如下:
func (variable_name variable_data_type) function_name() [return_type]{
   /* 函数体*/
}
--
package main
import (
   "fmt"  
)
/* 定义结构体 */
type Circle struct {
  radius float64
}
func main() {
  var c1 Circle
  c1.radius = 10.00
  fmt.Println("圆的面积 = ", c1.getArea())
}
//该 method 属于 Circle 类型对象中的方法
func (c Circle) getArea() float64 {
  //c.radius 即为 Circle 类型对象中的属性
  return 3.14 * c.radius * c.radius
}
感觉把本来应该在函数名后面括号中的变量,也就是要传进去的变量放在了func后的括号中。
然后使用的时候像是使用c++中类的一个方法。
go变量作用域:
Go 语言中变量可以在三个地方声明:
函数内定义的变量称为局部变量(包括main函数)
函数外定义的变量称为全局变量
函数定义中的变量称为形式参数
Go 语言程序中全局变量与局部变量名称可以相同,但是函数内的局部变量会被优先考虑。
go数组
val nums [10] int
val nums=[5]int{1,2,3,4,5}
nums:=[5]int{1,2,3,4,5}
val nums=[...]int{1,2,3}
nums:=[5]int{0:1,3:3} //只修改部分
nums[0]
多维数组:
nums:=[][]int{}  //创建
row1:=[]int{1,2,3}
row2:=[]int{4,5,6}
nums=append(nums,row1) //添加一行
fmt.Println(nums[0])  //输出一行,有别于c++
也可以直接输出二维数组nums
初始化二维数组:
a := [3][4]int{  
 {0, 1, 2, 3} ,   
 {4, 5, 6, 7} ,   
 {8, 9, 10, 11},  
}  //最后或者}}  也可以写在一行2
作为函数参数传递:
func name(x []int){}
go语言结构体
结构体是由一系列具有相同类型或不同类型的数据构成的数据集合。
结构体定义需要使用 type 和 struct 语句。struct 语句定义一个新的数据类型,结构体中有一个或多个成员。type 语句设定了结构体的名称。结构体的格式如下:
package main
import "fmt"
type Books struct {
   title string
   author string
   subject string
   book_id int
}
func main() {
    // 创建一个新的结构体
    fmt.Println(Books{"Go 语言", "www.runoob.com", "Go 语言教程", 6495407})
    // 也可以使用 key => value 格式
    fmt.Println(Books{title: "Go 语言", author: "www.runoob.com", subject: "Go 语言教程", book_id: 6495407})
    // 忽略的字段为 0 或 空
   fmt.Println(Books{title: "Go 语言", author: "www.runoob.com"})
}
定义结构体变量:
var BOOK1 Books
访问结构体成员:
BOOK1.title="go"
结构体作为函数参数:
func printBook( book Books ) {
   fmt.Printf( "Book title : %s\n", book.title)
}
调用函数:
var Book1 Books 
再访问结构体成员进行赋值
printBook(Book1)
结构体指针:
var struct_pointer *Books
struct_pointer = &Book1
使用结构体指针访问结构体成员:
struct_pointer.title
go语言切片
Go 语言切片是对数组的抽象。
Go 数组的长度不可改变,在特定场景中这样的集合就不太适用,Go 中提供了一种灵活,功能强悍的内置类型切片("动态数组"),与数组相比切片的长度是不固定的,可以追加元素,在追加时可能使切片的容量增大。
数组:
val nums=[5]int{1,2,3,4,5}
nums:=[5]int{1,2,3,4,5}
定义切片:
可通过未指定大小的数组来定义切片:
var s []int
或使用make()函数来创建切片:
var s []int=make([]int,len,cap)
s:=make([]int,len,cap)
容量cap为可选参数
定义+初始化切片:
s:=[]int{1,2,3}
s:=arr[:] //arr为数组
s:=arr[startIndex:endIndex]
切片是可索引的,并且可以由 len() 方法获取长度。
切片提供了计算容量的方法 cap() 可以测量切片最长可以达到多少。
注:切片make指定len和cap条件下,只定义没初始化的话,len长度的数初始化为0,而len之外cap之内还没有数。而只是定义的话,len=0,cap=0
一个切片未初始化之前(var s[]int)默认nil,长度为0
切片可通过设置上下限来截取切片,左闭右开的
s[0:len(s)] 全部
s[1:] 除了0
如果想增加切片的容量,我们必须创建一个新的更大的切片并把原分片的内容都拷贝过来。
append()函数:增加切片内容:
var s[]int
s=append(s,0,1)  可一次增加多个内容
创建另一个切片
s1:=make([]int,len(s),(cap(s))*2)
拷贝s的内容到s1
copy(s1,s)
疑问:go语言中数组和切片的定义没有明确的区别?还是说[]中指定大小就是数组,[]中无数就是切片?
go语言范围range:
Go 语言中 range 关键字用于 for 循环中迭代数组(array)、切片(slice)、通道(channel)或集合(map)的元素。在数组和切片中它返回元素的索引和索引对应的值,在集合中返回 key-value 对。
for _,num:=range nums{}
for i,num:=range nums{}
kvs := map[string]string{"a": "apple", "b": "banana"}
for k, v := range kvs {
    fmt.Printf("%s -> %s\n", k, v)
}
//range也可以用来枚举Unicode字符串。第一个参数是字符的索引,第二个是字符(Unicode的值)本身。
for i, c := range "go" {
    fmt.Println(i, c)
}
Map 是一种无序的键值对的集合。Map 最重要的一点是通过 key 来快速检索数据,key 类似于索引,指向数据的值。
Map 是一种集合,所以我们可以像迭代数组和切片那样迭代它。不过,Map 是无序的,我们无法决定它的返回顺序,这是因为 Map 是使用 hash 表来实现的。
可以使用内建函数 make 也可以使用 map 关键字来定义 Map:
var m map[int]int //声明变量,默认map是nil
m=make(map[int]int) //如果不初始化,nil不能存放键值对
或者:
m:=make(map[int]string)
m[1]="liu" //插入键值对
for i,s:=range m{  //如果:前只有一个变量,那么就key
  fmt.Println("key:",i,"value:",s)
}
查看某个键i是不是存在值
s,ok:=m[i]
if(ok){存在,是s}
else{不存在}
delete()删除元素:参数是原map和要删除的key
delete(m,i)
go语言支持递归函数
go语言类型转换,go不支持隐试转换类型
var i int=1
var f float32=float32(i)
go语言接口
Go 语言提供了另外一种数据类型即接口,它把所有的具有共性的方法定义在一起,任何其他类型只要实现了这些方法就是实现了这个接口。
思考:有点类似C++的多态,在接口中定义一个方法,之后会给出不同的实现方式。向内传入的变量不同,方法的具体实现也不同。
接口内的方法可以多个,传入的都是结构体变量?根据传入的结构体变量类型决定采用哪个方法。不同方法的输出可以不一样?每一种方法根据输入有很多种实现?
package main
import ("fmt")
type Phone interface {
    call()
}
type NokiaPhone struct {
}
func (nokiaPhone NokiaPhone) call() {
    fmt.Println("I am Nokia, I can call you!")
}
type IPhone struct {
}
func (iPhone IPhone) call() {   //实现error接口跟函数方法一样?
    fmt.Println("I am iPhone, I can call you!")
}
func main() {
    var phone Phone  //定义接口变量
    phone = new(NokiaPhone)  //为接口变量赋值,看方法的实现中要传入什么变量
    phone.call()  //跟函数方法一样的实现
    phone = new(IPhone)
    phone.call()
}
在上面的例子中,我们定义了一个接口Phone,接口里面有一个方法call()。然后我们在main函数里面定义了一个Phone类型变量,并分别为之赋值为NokiaPhone和IPhone。然后调用call()方法。
go错误处理
Go 语言通过内置的错误接口提供了非常简单的错误处理机制。
error类型是一个接口类型,这是它的定义:
type error interface {
    Error() string
}
我们可以在编码中通过实现 error 接口类型来生成错误信息。
package main
import ("fmt")
// 定义一个 DivideError 结构
type DivideError struct {
    dividee int
    divider int
}
// 实现 `error` 接口
func (de *DivideError) Error() string {
    strFormat := `
    Cannot proceed, the divider is zero.
    dividee: %d
    divider: 0
`
    return fmt.Sprintf(strFormat, de.dividee)
}
// 定义 `int` 类型除法运算的函数
func Divide(varDividee int, varDivider int) (result int, errorMsg string) {
    if varDivider == 0 {
            dData := DivideError{
                    dividee: varDividee,
                    divider: varDivider,
            }   //定义结构体
            errorMsg = dData.Error()  //使用方法
            return
    } else {
            return varDividee / varDivider, ""
    }
}
func main() {
    // 正常情况
    if result, errorMsg := Divide(100, 10); errorMsg == "" {
            fmt.Println("100/10 = ", result)
    }
    // 当除数为零的时候会返回错误信息
    if _, errorMsg := Divide(100, 0); errorMsg != "" {
            fmt.Println("errorMsg is: ", errorMsg)
    }
}  //条件语句
Go 语言支持并发,我们只需要通过 go 关键字来开启 goroutine 即可。
goroutine 是轻量级线程,goroutine 的调度是由 Golang 运行时进行管理的。
Go 允许使用 go 语句开启一个新的运行期线程, 即 goroutine,以一个不同的、新创建的 goroutine 来执行一个函数。 同一个程序中的所有 goroutine 共享同一个地址空间。
package main
import (
        "fmt"
        "time"
)
func say(s string) {
        for i := 0; i < 5; i++ {
                time.Sleep(100 * time.Millisecond)
                fmt.Println(s)
        }   //for循环语句
}
func main() {
        go say("world")
        say("hello")
}   
输出的hello和world没有固定的先后顺序,因为它们是两个goroutine在执行。
通道channel
通道(channel)是用来传递数据的一个数据结构。
通道可用于两个 goroutine 之间通过传递一个指定类型的值来同步运行和通讯。操作符 <- 用于指定通道的方向,发送或接收。如果未指定方向,则为双向通道。
ch <- v    // 把 v 发送到通道 ch
v := <-ch  // 从 ch 接收数据
           // 并把值赋给 v
创建通道:使用前必须要先创建
var ch chan int
ch:=make(chan int)
package main
import "fmt"
func sum(s []int, c chan int) {
        sum := 0
        for _, v := range s {
                sum += v
        }
        c <- sum // 把 sum 发送到通道 c
}
func main() {
        s := []int{7, 2, 8, -9, 4, 0}
        c := make(chan int)
        go sum(s[:len(s)/2], c)
        go sum(s[len(s)/2:], c)
        x, y := <-c, <-c // 从通道 c 中接收
        fmt.Println(x, y, x+y)
}
思考:实现了一个数组分两部分,分别求和,把结果放入通道,从结果来看,通道像是一个栈?后进先出?不是,应该是由于goroutine没有先后顺序。感觉实际实现的是函数返回的问题,不加return,把要返回的值放入通道中。这个用go线程并行执行。
疑问,如果函数有返回值,怎么用go。
默认情况下,通道是不带缓冲区的。发送端发送数据,同时必须有接收端相应的接收数据。
设置缓冲区:
ch:=make(chan int,100)
带缓冲区的通道允许发送端的数据发送和接收端的数据获取处于异步状态,就是说发送端发送的数据可以放在缓冲区里面,可以等待接收端去获取数据,而不是立刻需要接收端去获取数据。
不过由于缓冲区的大小是有限的,所以还是必须有接收端来接收数据的,否则缓冲区一满,数据发送端就无法再发送数据了。
注意:如果通道不带缓冲,发送方会阻塞直到接收方从通道中接收了值。如果通道带缓冲,发送方则会阻塞直到发送的值被拷贝到缓冲区内;如果缓冲区已满,则意味着需要等待直到某个接收方获取到一个值。接收方在有值可以接收之前会一直阻塞。
ch<-1
ch<-2
fmt.Println(<-ch)
fmt.Println(<-ch)  //输出:1 2
go遍历通道和关闭通道:
Go 通过 range 关键字来实现遍历读取到的数据,类似于与数组或切片。
v, ok := <-ch
如果通道接收不到数据后 ok 就为 false,这时通道就可以使用 close() 函数来关闭。
斐波那契数组:
package main
import (
        "fmt"
)
func fibonacci(n int, c chan int) {
        x, y := 0, 1
        for i := 0; i < n; i++ {
                c <- x
                x, y = y, x+y
        }
        close(c)
}
func main() {
        c := make(chan int, 10)
        go fibonacci(cap(c), c)
        // range 函数遍历每个从通道接收到的数据,因为 c 在发送完 10 个
        // 数据之后就关闭了通道,所以这里我们 range 函数在接收到 10 个数据
        // 之后就结束了。如果上面的 c 通道不关闭,那么 range 函数就不
        // 会结束,从而在接收第 11 个数据的时候就阻塞了。
        for i := range c {
                fmt.Println(i)
        }
}
go环境配置问题:
GOPATH 自己的go语言工作目录,下有src存源码,bin存可执行文件,发布go build时生成,pkg编译包,debug时生成的可执行文件,理解为中间文件。
GOROOT是go的安装目录
要执行go命令和go工具,就必须将go的可执行文件/usr/local/go/bin添加到系统的PATH环境变量中。这样,无论在终端的任何路径,都可识别go命令和go相关工具。
环境变量是为了告诉系统一个路径
cd ~
vim .bash_profile 中可配置环境变量
source .bash_profile //使配置生效
GOBIN需要吗?
export GOBIN=$GOPATH/bin
事实证明 没有用
export GOROOT=/usr/local/go
export PATH=$PATH:$GOROOT/bin  //已有,这个的作用要懂
goland安装与激活:
首先安装官方包,使用。
下载压缩包得到jetbrains-agent-latest.zip
打开IDE,将该zip拖入IDE窗口,激活即可。
goland中左下角terminal可打开终端
配置GOPATH,这一步非常重要,否则代码中的依赖包将无法识别。一般GOPATH都配置到项目名这一层
配置教程:
GOPATH是什么路径???
关于环境变量的理解:
认为配置PATH是为了在终端的任何位置都可以识别go命令和go相关工具。
配置GOPATH相当于是go项目的一个工作目录。
!!在终端中export配置是当前终端临时的,关闭无效? 还是说goland的终端?
所以在文件中配置。
GOPATH 是 Go语言中使用的一个环境变量,它使用绝对路径提供项目的工作目录。
GOPATH 适合处理大量 Go语言源码、多个包组合而成的复杂工程。
在 GOPATH 指定的工作目录下,代码总是会保存在 $GOPATH/src 目录下。在工程经过 go build、go install 或 go get 等指令后,会将产生的二进制可执行文件放在 $GOPATH/bin 目录下,生成的中间缓存文件会被保存在 $GOPATH/pkg 下。
如果需要将整个源码添加到版本管理工具(Version Control System,VCS)中时,只需要添加 $GOPATH/src 目录的源码即可。bin 和 pkg 目录的内容都可以由 src 目录生成。

设置全局 GOPATH 的方法可能会导致当前项目错误引用了其他目录的 Go 源码文件从而造成编译输出错误的版本或编译报出一些无法理解的错误提示。
因此,建议大家无论是使用命令行或者使用集成开发环境编译 Go 源码时,GOPATH 跟随项目设定。在 Jetbrains 公司的 GoLand 集成开发环境(IDE)中的 GOPATH 设置分为全局 GOPATH 和项目 GOPATH。

Global GOPATH 代表全局 GOPATH,一般来源于系统环境变量中的 GOPATH;Project GOPATH 代表项目所使用的 GOPATH,该设置会被保存在工作目录的 .idea 目录下,不会被设置到环境变量的 GOPATH 中,但会在编译时使用到这个目录。建议在开发时只填写项目 GOPATH,每一个项目尽量只设置一个 GOPATH,不使用多个 GOPATH 和全局的 GOPATH。
遗留问题:
gopath除了作为工作目录之外什么用处?
编译为什么不会自动在bin目录下生成可执行文件?
不设置gopath会怎样?
gopath是为了什么?