Go基本语法 程序入口 
必须是main包 package main 
必须是main方法 func main() 
文件名不一定是main.go 
文件夹名不一定是main 
 
获取返回值 Go语言的main函数不支持任何返回值 因此return是不可以使用的 通过使用os.Exit()来返回状态
 
获取命令行参数 Go语言的main函数不支持传入参数 在程序中直接通过os.Args获取命令行参数
1 2 3 4 5 func  main ()   {    if  len (as.Args) > 1  {         fmt.Println("It's" , os.Args[1 ])     } } 
 
声明变量 1 2 3 4 5 6 7 8 var  name string  = "Ruojhen" var  id int  = 123 var  name = "shaoyuanhang@outlook.com"  var  decimal = 0.06  var  decimal float32  = 0.03 
 
  
1 2 3 4 5 6 7 8 9 var (		id int  =132456  		name = "syh"  		gender = "男"  	)     fmt.Print(id,name,gender)      
 
1 2 3 4 5 6 7 8 9 name:="syh"    var  name = "syh"  var  name string  = "syh"  id,name:= 123 ,"syh"   var  a int  =100 var  b int  =200 b,a=a,b fmt.Print(a," " ,b) 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 var  age int  =100 var  address  =&agefmt.Print(age," " ,address) ptr:=new (int ) fmt.Println("ptr address" ,ptr)  fmt.Println("ptr value" ,*ptr)   
 
环境变量 三个环境变量 GOROOT GOPATH GOBIN     GOROOT Go语言的安装路径     GOPATH 若干工作区目录的路径 是自己定义的工作空间     GOBIN  Go程序生成的可执行文件的路径
其中 GOPATH的概念最多 GOPATH的意义也是被询问的最多的
回答是: 可以把GOPATH简单理解成Go语言的工作目录 GOPATH的值是一个目录的路径 每个目录都代表Go语言的一个工作区 利用工作区下的文件夹src去存放源码文件(.go) pkg存放平台相关目录(linux_amd64)内存放归档文件( .a) bin存可执行文件(**.exe)
1 2 归档文件就是在linux下是.a的文件 是archive文件 是程序编译后生成的静态库文件 与Java的jar包不同 jar属于动态链接库 
 
Go命令 1 2 3 go build    go run      go install  
 
数据类型 fmt输出格式 1 2 3 4 5 6 7 8 9 10 %b    表示为二进制 %c    该值对应的unicode码值 %d    表示为十进制 %o    表示为八进制 %q    该值对应的单引号括起来的go语法字符字面值,必要时会采用安全的转义表示 %x    表示为十六进制,使用a-f %X    表示为十六进制,使用A-F %U    表示为Unicode格式:U+1234,等价于"U+%04X"  %E    用科学计数法表示 %f    用浮点数表示 
 
整型 int int8 int16 int32 int64 有符号整型uint uint8 uint16 uint32 uint64 无符号整型
不同进制整型表示 十进制 
 
二进制 0b前缀
 
八进制 0o前缀
 
十六进制
 
输出格式对比
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package  mainimport  (	"fmt"  ) func  main ()  {	var  num01 int  = 0 b1100     var  num02 int  = 0 o14     var  num03 int  = 0xC      fmt.Printf("2进制数 %b 表示的是: %d \n" , num01, num01)     fmt.Printf("8进制数 %o 表示的是: %d \n" , num02, num02)     fmt.Printf("16进制数 %X 表示的是: %d \n" , num03, num03) } 
 
浮点型 浮点型表示有两种表示方法 以0.037为例 可以直接用0.037 亦或使用3.7E-2表示0.037 同理3.7E+1 = 37 有时候浮点型类型值可以被简化 37.0可以简化为37 0.037可以简化为.037 浮点数的整数部分和小数部分只能由十进制表示 
Go提供了两种精度的浮点数float32 float64
float32 单精度 存储占用4个字节 32位float64 双精度 存储占用8个字节 64位
byte/rune byte byte 占用一个字节 8个比特位 2^8=256 所以byte类型的范围是 0-255 和uint8本质上没有区别 byte表示的是ACSII表中的一个字符
1 2 3 4 5 6 7 8 9 10 11 12 13 import  "fmt" func  main ()   {    var  a byte  = 65                var  b uint8  = 66      fmt.Printf("a 的值: %c \nb 的值: %c" , a, b)           } 
 
输出结果为
 
在 ASCII 表中,由于字母 A 的ASCII 的编号为 65 字母 B 的ASCII 编号为 66 所以上面的代码也可以写成这样
1 2 3 4 5 6 7 import  "fmt" func  main ()   {    var  a byte  = 'A'      var  b uint8  = 'B'      fmt.Printf("a 的值: %c \nb 的值: %c" , a, b) } 
 
rune rune 占用四个字节 32个比特位 所以和uint32本质上没有区别 rune表示的是一个Unicode字符
1 2 3 4 5 6 7 8 9 10 import  (    "fmt"      "unsafe"  ) func  main ()   {    var  a byte  = 'A'      var  b rune  = 'B'      fmt.Printf("a 占用 %d 个字节数\nb 占用 %d 个字节数" , unsafe.Sizeof(a), unsafe.Sizeof(b)) } 
 
输出结果为
 
表示中文字符要用rune类型 
 
既然 byte 和 uint8 没有区别 rune 和 uint32 没有区别 那为何还要多设置这两个类型呢 
因为uint8 uint32 直观上让人理解为这是一个整型变量 是一个数值  但实际上它也可以表示一个字符 为了消除这个直观错觉 应运而生了byte rune
字符串 1 var  mystr string  = "hello" 
 
byte和rune都是字符类型 多个字符放在一起就组成了字符串 即string
hello在ASCII表中对应的数字分别是 104 101 108 108 111
1 2 3 4 5 6 7 8 9 10 import  (    "fmt"  ) func  main ()   {    var  mystr01 string  = "hello"      var  mystr02 [5 ]byte  = [5 ]byte {104 , 101 , 108 , 108 , 111 }     fmt.Printf("mystr01: %s\n" , mystr01)     fmt.Printf("mystr02: %s" , mystr02) } 
 
输出结果为
1 2 mystr01: hello mystr02: hello 
 
mystr01 mystr02输出内容一样 说明string的本质是byte数组
Go语言的string使用utf-8进行编码 英文字母占1个字节 中文汉字占3个字节
字符串不仅仅可以使用双引号来表示 还可以使用反引号 反引号通常应用于存在转移字符\的情况下 
被反引号包裹的字符串会被忽略掉其中的转义字符 
1 2 var  mystr01 string  = "\\r\\n" var  mystr02 string  = `\r\n` 
 
这两个字符串的打印结果都是\r\n
1 2 3 4 5 6 7 8 9 import  (    "fmt"  ) func  main ()   {    var  mystr01 string  = `\r\n`      fmt.Printf(`\r\n` )     fmt.Printf("的解释型字符串是: %q" , mystr01) } 
 
输出结果是 
 
同时反引号可以不写换行符\n来表示一个多行的字符串 因为换行符会被忽略掉
1 2 3 4 5 6 7 8 9 10 import  (    "fmt"  ) func  main ()   {    var  mystr01 string  = `Hello  Shaoyuanhangyes Ruojhen`     fmt.Println(mystr01) } 
 
输出结果
1 2 Hello Shaoyuanhangyes Ruojhen 
 
数组 数组声明 1 2 3 4 var  arr [3 ]int arr[0 ] = 1  arr[1 ] = 2  arr[2 ] = 3  
 
1 2 3 4 5 var  arr [3 ] = [3 ]int {1 ,2 ,3 }arr := [3 ]int {1 ,2 ,3 }, 
 
3表示数组元素个数 可以使用...来让系统自己根据实际情况来分配空间
 
值得注意的是 [3]int [4]int虽然都是数组 但类型却不同 使用fmt的%T来查得
1 2 3 4 5 6 7 8 9 10 import  (    "fmt"  ) func  main ()   {    arr01 := [...]int {1 , 2 , 3 }     arr02 := [...]int {1 , 2 , 3 , 4 }     fmt.Printf("%d 的类型是: %T\n" , arr01, arr01)     fmt.Printf("%d 的类型是: %T" , arr02, arr02) } 
 
输出结果为
1 2 [1 2 3] 的类型是: [3]int [1 2 3 4] 的类型是: [4]int 
 
type 使用关键字type来起别名
1 2 3 4 5 6 7 8 9 10 import  (    "fmt"  ) func  main ()   {    type  arr3 [3 ]int      myarr := arr3{1 ,2 ,3 }     fmt.Printf("%d 的类型是: %T" , myarr, myarr) } 
 
输出结果为
 
只初始化部分位置  
打印arr的结果为 [0 0 3 0]
切片 切片是对数组的一个连续片段的引用 所以切片是一个引用类型 这个片段可以是整个数组 也可以是数组的子集 但这个片段是一个左闭右开的区间
即终止索引对应的项不在这个片段中
1 2 3 4 5 6 7 8 import  (    "fmt"  ) func  main ()   {    myarr := [...]int {1 , 2 , 3 }     fmt.Printf("%d 的类型是: %T" , myarr[0 :2 ], myarr[0 :2 ]) } 
 
输出结果为
 
切片是引用类型 所以未给予初值的切片的默认值是nil
切片构造方式 对数组进行片段截取 1 2 3 4 5 myarr := [5 ]int {1 ,2 ,3 ,4 ,5 } mysli1 := myarr[1 :3 ]  mysli2 := myarr[1 :3 :4 ] 
 
mysli1 mysli2打印结果一模一样 其中mysli2中的4是做什么的呢 
在切片时 若不指定第三个数 那么切片的终止索引会一直到原数组的最后一个数 若指定了第三个数 那么切片的终止索引就到这第三个数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 package  mainimport  "fmt" func  main ()  {    myarr := [5 ]int {1 ,2 ,3 ,4 ,5 }     fmt.Printf("myarr 的长度为:%d,容量为:%d\n" , len (myarr), cap (myarr))     mysli1 := myarr[1 :3 ]     fmt.Printf("mysli1 的长度为:%d,容量为:%d\n" , len (mysli1), cap (mysli1))     fmt.Println(mysli1)     mysli2 := myarr[1 :3 :4 ]     fmt.Printf("mysli2 的长度为:%d,容量为:%d\n" , len (mysli2), cap (mysli2))     fmt.Println(mysli2) } 
 
输出结果为
1 2 3 4 5 myarr 的长度为:5,容量为:5 mysli1 的长度为:2,容量为:4 [2 3] mysli2 的长度为:2,容量为:3 [2 3] 
 
所以切片的第三个数只影响切片的容量 不影响切片的长度
从头声明赋值 1 2 3 4 5 var  strList[]string  var  numList[]int  var  numListEmpty = []int {} 
 
可以看到 声明切片类型和声明数组类型基本上是一模一样 除了不需要说明长度
make函数构造切片 make([]Type,size,cap) Type类型 size长度 cap容量
1 2 3 4 5 6 7 8 9 10 11 import  ( "fmt"  ) func  main ()   { a := make ([]int , 2 )  b := make ([]int , 2 , 10 )  fmt.Println(a, b)  fmt.Println(len (a), len (b))  fmt.Println(cap (a), cap (b)) } 
 
输出结果为
 
因此使用make函数构造切片的时候不指定容量的时候 容量等于长度值
只初始化部分位置 和数组一样
1 2 3 4 5 6 7 8 9 import  ( "fmt"  ) func  main ()   {    a := []int {4 :2 }     fmt.Println(a)     fmt.Println(len (a), cap (a)) } 
 
输出结果为
 
数组与切片 数组与切片都是可容纳若干类型相同元素的容器 不同点在于 数组容器大小固定 而切片本身是引用类型 可以append进行元素添加
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 import  (    "fmt"  ) func  main ()   {    myarr := []int {1 }          myarr = append (myarr, 2 )          myarr = append (myarr, 3 , 4 )          myarr = append (myarr, []int {7 , 8 }...)          myarr = append ([]int {0 }, myarr...)          myarr = append (myarr[:5 ], append ([]int {5 ,6 }, myarr[5 :]...)...)          fmt.Println(myarr) } 
 
输出结果为
 
可以如此理解数组与切片的区别     切片是引用类型 数组是值类型     数组长度固定 切片是动态的数组     切片比数组多一个属性 容量cap     切片的底层实现是数组     切片不能进行等值判断 只能和nil判断
字典 字典 Map 若干个key:value组建在一起的无序键值对 要求每一个key都是唯一的 可以使用== !=来进行判断 key不能是切片 不能是字典 不能是函数 map是一个集合 可以迭代它 不过map是无序的 无法决定他的返回顺序 因为map是使用hash表实现的 声明map时必须指定好key和value的类型
声明初始化字典 1 2 3 4 5 6 7 8 var  scores map [string ]int  = map [string ]int {"English" :80 ,"Chinese" :90 }scores := map [string ]int {"English" :80 ,"Chinese" :90 } scores := make (map [string ]int ) scores["English" ] = 80  scores["Chinese" ] = 90  
 
需要注意的是 只声明不初始化的map类型的初值为nil 无法直接进行赋值 所以需要使用make函数先对其初始化 然后才能直接赋值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import  "fmt" func  main ()  {         var  scores map [string ]int           if  scores == nil  {                  scores = make (map [string ]int )     }          scores["chinese" ] = 90      fmt.Println(scores) } 
 
打印结果为 map[chinese:90]
对字典的操作 1 2 3 4 5 6 7 scores := make (map [string ]int ) scores["math" ] = 150  fmt.Print(scores["math" ]) delete (scores,"math" )
 
判断key是否存在 因为有可能key对应的value恰好为零值 所以想要判断key是否存在不能够使用判断value零值的办法
字典的下标读取会返回两个值 可以使用第二个返回值来表示对应的key是否存在 若存在 ok则为true 若不存在 ok为false
1 2 3 4 5 6 7 8 9 10 import  "fmt" func  main ()   {    scores := map [string ]int {"english" : 80 , "chinese" : 85 }     if  math, ok := scores["math" ]; ok {         fmt.Printf("math 的值是: %d" , math)     } else  {         fmt.Println("math 不存在" )     } } 
 
对字典进行循环 1 2 3 4 5 6 7 8 9 import  "fmt" func  main ()   {    scores := map [string ]int {"english" : 80 , "chinese" : 85 }     for  subject, score := range  scores {         fmt.Printf("key: %s, value: %d\n" , subject, score)     } } 
 
1 2 3 4 5 6 7 8 9 import  "fmt" func  main ()   {    scores := map [string ]int {"english" : 80 , "chinese" : 85 }     for  subject := range  scores {         fmt.Printf("key: %s\n" , subject)     } } 
 
1 2 3 4 5 6 7 8 9 import  "fmt" func  main ()   {    scores := map [string ]int {"english" : 80 , "chinese" : 85 }     for  _, score := range  scores {         fmt.Printf("value: %d\n" , score)     } } 
 
布尔类型 与其他语言不同的一点 go的true不等于1 false不等于0 即 bool和int不能直接转换 需要自己实现转换函数
1 2 3 4 5 6 7 func  bool2int (b bool )   int  {    if  b {         return  1      }     return  0  } 
 
1 2 3 4 func  int2bool (i int )   bool  {    return  i != 0  } 
 
指针 创建指针 1 2 3 4 5 6 7 8 9 10 11 12 aint :=1   ptr1 := &aint  astr := new (string )  *astr ="Ruojhen"   aint := 1  var  bint *int bint = &aint 
 
以防混淆
1 2 3 4 5 6 7 8 9 10 import  "fmt" func  main ()   {    aint := 1           ptr := &aint       fmt.Println("普通变量存储的是:" , aint)     fmt.Println("普通变量存储的是:" , *ptr)     fmt.Println("指针变量存储的是:" , &aint)     fmt.Println("指针变量存储的是:" , ptr) } 
 
打印结果为
1 2 3 4 普通变量存储的是: 1 普通变量存储的是: 1 指针变量存储的是: 0xc0000100a0 指针变量存储的是: 0xc0000100a0 
 
打印指针指向的内存地址
1 2 3 4 5 fmt.Printf("%p" , ptr) fmt.Println(ptr) 
 
指针的类型 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import  "fmt" func  main ()   {    astr := "hello"      aint := 1      abool := false      arune := 'a'      afloat := 1.2      fmt.Printf("astr 指针类型是:%T\n" , &astr)     fmt.Printf("aint 指针类型是:%T\n" , &aint)     fmt.Printf("abool 指针类型是:%T\n" , &abool)     fmt.Printf("arune 指针类型是:%T\n" , &arune)     fmt.Printf("afloat 指针类型是:%T\n" , &afloat) } 
 
打印结果为
1 2 3 4 5 astr 指针类型是:*string  aint 指针类型是:*int  abool 指针类型是:*bool  arune 指针类型是:*int32  afloat 指针类型是:*float64  
 
可以发现用 *+所指向变量值的数据类型就是对应的指针类型
指针的初始值 指针声明后不初始化赋值的话 指针的值就会是nil
切片与指针 指针和切片一样都是引用类型 
若想通过一个函数改变一个数组内的值 有两种方式
1.将数组的切片作为参数传给函数 2.将数组的指针作为参数传给函数
按照go的语言习惯 使用切片将会更加简洁
1 2 3 4 5 6 7 8 9 10 func  modify (sls []int )   {    sls[0 ] = 90  } func  main ()   {    a := [3 ]int {89 , 90 , 91 }     modify(a[:])     fmt.Println(a) } 
 
1 2 3 4 5 6 7 8 9 10 func  modify (arr *[3]int )   {    (*arr)[0 ] = 90  } func  main ()   {    a := [3 ]int {89 , 90 , 91 }     modify(&a)     fmt.Println(a) } 
 
go指针限制 相较于cpp的指针 go指针有着许多限制 目的是避免指针操作的危险
限制一 指针不能参与数学运算
限制二 不同类型的指针不允许相互转换
限制三 不同类型的指针不能比较和相互赋值
流程控制 For循环 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 a := 1  for  a<=5  {    fmt.Println(a)     a++ } for  i := 1 ; i <= 5 ;i++ {    fmt.Println(i) } for  {} for  ;; {} 
 
for range 遍历一个可迭代的数据结构用for range语句实现 range后可接数组 切片 字符串等
按照以往的编程习惯 随便写一个循环
1 2 3 4 5 6 7 8 9 10 11 12 13 package  mainimport  (	"fmt"  ) func  main ()  {	vector := [...]string {"C++" ,"Go" ,"Git" ,"Redis" } 	for  i := range  vector { 		fmt.Println("I will learn " ,i) 	} } 
 
输出结果为
1 2 3 4 I will learn  0 I will learn  1 I will learn  2 I will learn  3 
 
发现与我们期望的不符 做一些小更改
1 2 3 4 5 6 7 8 9 10 11 12 13 package  mainimport  (	"fmt"  ) func  main ()  {	vector := [...]string {"C++" ,"Go" ,"Git" ,"Redis" } 	for  _, i := range  vector { 		fmt.Println("I will learn " ,i) 	} } 
 
输出结果为
1 2 3 4 I will learn  C++ I will learn  Go I will learn  Git I will learn  Redis 
 
这是因为range返回两个值 第一个是索引 第二个是数据
defer 延迟语句 defer后接函数名 就能实现将这个函数的调用延迟到当前函数执行完再调用
理解defer的使用效果
1 2 3 4 5 6 7 func  syhprint ()  {	fmt.Println("B" ) } func  main ()  {	defer  syhprint() 	fmt.Println("A" ) } 
 
输出结果为 
 
代码也可以简写为
1 2 3 4 func  main ()  {	defer  fmt.Println("B" ) 	fmt.Println("A" ) } 
 
defer的快照作用 使用defer只是延时调用了函数 此时已经传递给函数的变量 就像一个快照一样被保存了下来 不会受到后续程序执行的影响
1 2 3 4 5 6 func  main ()  {	language := "C++"   	defer  fmt.Println(language) 	language = "Go"  	fmt.Println(language) } 
 
输出结果为
 
由此可见 在变量被修改前延时调用打印函数 即使变量值被修改了 最后延迟调用的函数的变量值依旧是修改前的值 就像是defer给这个函数的变量值做了一个快照
多个defer调用的顺序 1 2 3 4 5 6 7 8 func  main ()  {	language := "C++"   	defer  fmt.Println(language) 	language = "Go"  	defer  fmt.Println(language) 	language = "Rust"  	fmt.Println(language) } 
 
输出结果为
 
由此可见 多个defer一起调用的时候 输出的结果是反序的 类似栈的后进先出
defer/return defer和return同时存在的话 先调用谁呢 看下面的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import  (	"fmt"  ) var  name string  = "C++" func  rename ()   string  {	defer  func ()   { 		name = "Go"  	}() 	fmt.Println("rename函数内的name为" ,name) 	return  name } func  main ()  {	myname := rename() 	fmt.Println("main函数里的name为" ,name) 	fmt.Println("main函数里的myname为" ,myname) } 
 
输出结果为
1 2 3 rename函数内的name为 C++ main函数里的name为 Go main函数里的myname为 C++ 
 
对代码进行剖析 理解为何是这个输出结果
1 2 3 4 5 6 7 8 9 10 11 在main函数里初始化myname的时候 调用了rename函数  rename函数内输出了name值为全局变量的"C++"  同时rename函数内延迟修改了全局变量name的值为"Go"  main函数输出name值 为修改后的"Go"  main函数输出myname值 为rename函数的返回值"C++"  因此我们发现在rename函数调用完毕后 才会延迟修改全局变量name的值 所以return 回的值为修改前的"C++"   因此return 的调用在defer之前  毕竟要完成函数后才会继续延迟调用 
 
为什么要有defer 能够缩减代码量 使代码不那么臃肿 下面是一段经典的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 func  f ()   {    r := getResource()       ......     if  ... {         r.release()           return      }     ......     if  ... {         r.release()           return      }     ......     if  ... {         r.release()           return      }     ......     r.release()          return  } func  f ()   {    r := getResource()       defer  r.release()       ......     if  ... {         ...         return      }     ......     if  ... {         ...         return      }     ......     if  ... {         ...         return      }     ......     return  } 
 
异常 Go语言中 异常的抛出和捕获依赖两个内置函数
panic 抛出异常 使程序崩溃
recover 捕获异常 恢复程序 recover调用后 抛出的panic将会在此处终结 不会再向外抛出异常 并且recover必须在defer修饰下才能发挥作用
panic 手动抛出异常
1 2 3 func  main ()  {	panic ("程序出错了" ) } 
 
控制台输出效果为
1 2 3 4 5 6 panic: 程序出错了 goroutine 1 [running]: main.main() 	d:/Code/My Code/Golang/Hexomd/src/github.com/Ruojhen/hexomd01/test.go:8 +0x40 exit  status 2
 
recover 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 func  data (x int )   {	defer  func ()   { 		if  err := recover (); err != nil  { 			fmt.Println(err) 		} 	}() 	 	var  arr [10 ]int  	arr[x] = 25  } func  main ()   {	data(20 )  	 	fmt.Println("It is fine" ) } `` `      控制台输出结果为 ` `` bashruntime error : index out of range  [20 ] with length 10  It is fine