Golang 基础知识学习

Learning GoLang

文章目录

一、环境配置

(一)Mac 环境下使用 vsCode 搭建 Go 开发环境

  • 参考配置方案
  • 此方法比较轻便,但有可能由于墙的问题,一些插件未能装好。但可以根据博客中所述方案解决部分问题。

(二)Mac 环境的 Go 安装和配置 + GoLand 安装和永久使用

1、下载Go,可一键配置环境
  • 下载地址
    在这里插入图片描述
  • 下载后找到文件,只需双击 + 一路 next 完成,即可。
2、查看版本
$ go version
go version go1.12.7 darwin/amd64
3、写个脚本感受一下
//打开终端,随便cd到一个目录,比如我cd到我的xy目录
$ vim hello.go


//编辑hello.go文件
package main

import "fmt"

func main(){
	fmt.Println("hello world")
}


//通过:wq保存文件并退出。运行文件
$ go run hello.go
hello world
4、GoLand 安装和永久激活使用

二、语言学习框架

学习网址@菜鸟教程
学习书籍@The Go Programming Language @Alan A.A. Donovan

(一)语言简介

1、简介

Go 是一个开源的编程语言,它能让构造简单、可靠且高效的软件变得容易。
Go是从2007年末由Robert Griesemer, Rob Pike, Ken Thompson主持开发,后来还加入了Ian Lance Taylor, Russ Cox等人,并最终于2009年11月开源,在2012年早些时候发布了Go 1稳定版本。现在Go的开发已经是完全开放的,并且拥有一个活跃的社区。

2、最主要的特性
  • 自动垃圾回收
  • 更丰富的内置类型
  • 函数多返回值
  • 错误处理
  • 匿名函数和闭包
  • 类型和接口
  • 并发编程
  • 反射
  • 语言交互性
3、主要用途

Go 语言被设计成一门应用于搭载 Web 服务器,存储集群或类似用途的巨型中央服务器的系统编程语言。

对于高性能分布式系统领域而言,Go 语言无疑比大多数其它语言有着更高的开发效率。它提供了海量并行的支持,这对于游戏服务端的开发而言是再好不过了。

(二)语言结构

  • 包声明
  • 引入包
  • 函数
  • 变量
  • 语句 & 表达式
  • 注释
1、一个例子
package main  // 定义包名 

import "fmt"  // 引入包

func main() { 
   /* 这是我的第一个简单的程序 */
   fmt.Println("Hello, World!")
}
2、关于包名的注解
  • 文件名与包名没有直接关系,不一定要将文件名与包名定成同一个。
  • 文件夹名与包名没有直接关系,并非需要一致。
  • 同一个文件夹下的文件只能有一个包名,否则编译报错。

(1)文件结构

Test
--helloworld.go

myMath
--myMath1.go
--myMath2.go

(2)测试代码

// helloworld.go
package main

import (
"fmt"
"./myMath"
)

func main(){
    fmt.Println("Hello World!")
    fmt.Println(mathClass.Add(1,1))
    fmt.Println(mathClass.Sub(1,1))
}
// myMath1.go
package mathClass
func Add(x,y int) int {
    return x + y
}
// myMath2.go
package mathClass
func Sub(x,y int) int {
    return x - y
}
3、Go 程序的一般结构: basic_structure.go
// 当前程序的包名
package main

// 导入其他包
import . "fmt"

// 常量定义:通过 const 关键字来进行常量的定义。
const PI = 3.14

// 全局变量的声明和赋值:在函数体外部使用 var 关键字来进行全局变量的声明和赋值。
var name = "gopher"

// 一般类型声明
type newType int

// 结构的声明:通过 type 关键字来进行结构(struct)的声明。
type gopher struct{}

// 接口的声明:通过 type 关键字来进行接口(interface)的声明。
type golang interface{}

// 由main函数作为程序入口点启动
func main() {
    Println("Hello World!")
}
4、通过 package 来组织
  • 只有 package 名称为 main 的包可以包含 main 函数。
  • 一个可执行程序有且仅有一个 main 包。
  • 通过 import 关键字来导入其他非 main 包。

(1)可以通过 import 关键字单个导入

import "fmt"
import "io"

(2)可以同时导入多个

import (
    "fmt"
    "math"
)
func main() {
    fmt.Println(math.Exp2(10))  // 1024
}

(3)使用 <PackageName>.<FunctionName> 导入调用

package main
// 为fmt起别名为fmt2
import fmt2 "fmt"

(4)省略调用(不建议使用)

// 调用的时候只需要Println(),而不需要fmt.Println()
import . "fmt"
func main (){
    Println("hello,world")
}
5、可见性规则
  • Go语言中,使用大小写来决定该常量、变量、类型、接口、结构或函数是否可以被外部包所调用。

函数名首字母小写即为 private :

func getId() {}

函数名首字母大写即为 public :

func Printf() {}

三、基础语法

(一)Go 标记

Go 程序可以由多个标记组成,可以是关键字,标识符,常量,字符串,符号。

  • 一个例子
fmt.Println("Hello, World!")
  • 其中6个标记
1. fmt
2. .
3. Println
4. (
5. "Hello, World!"
6. )

(二)行分隔符

在 Go 程序中,一行代表一个语句结束。
每个语句不需要像 C 家族中的其它语言一样以分号 ; 结尾,因为这些工作都将由 Go 编译器自动完成(换行符被转换为分号)。
如果你打算将多个语句写在同一行,它们则必须使用 ; 人为区分。

(三)标识符

标识符用来命名变量、类型等程序实体。
一个标识符实际上就是一个或是多个字母(AZ和az)数字(0~9)、下划线_组成的序列。
但是第一个字符必须是字母或下划线而不能是数字。

(四)字符串连接

Go 语言的字符串可以通过 + 实现。

(五)数据类型

在 Go 编程语言中,数据类型用于声明函数和变量。
数据类型的出现是为了把数据分成所需内存大小不同的数据,编程的时候需要用大数据的时候才需要申请大内存,就可以充分利用内存。

在这里插入图片描述

  • 数字类型

在这里插入图片描述

  • 浮点型

在这里插入图片描述

  • 其他数字类型

在这里插入图片描述

重要:nil类型的详解

参考链接

  • nil的意思是无,或者是零值。
  • 在Go语言中,如果你声明了一个变量但是没有对它进行赋值操作,那么这个变量就会有一个类型的默认零值。

这是每种类型对应的零值:

bool      -> false                              
numbers -> 0                                 
string    -> ""      

pointers -> nil
slices -> nil
maps -> nil
channels -> nil
functions -> nil
interfaces -> nil

四、变量声明

####(一)一个普通实例

  • 声明单个变量 var identifier type
  • 声明多个变量 var identifier1, identifier2 type
package main
import "fmt"
func main() {
    var a string = "Runoob"
    fmt.Println(a)

    var b, c int = 1, 2
    fmt.Println(b, c)
}

(二)变量声明的方法

  • 第一种,指定变量类型,如果没有初始化,则变量默认为零值。
  • 第二种,根据值自行判定变量类型。
  • 第三种,省略 var, 注意 := 左侧如果没有声明新的变量,就产生编译错误。
    • 格式 v_name := value
    • 一个例子如下。
var intVal int 

intVal :=1 // 这时候会产生编译错误

intVal,intVal1 := 1,2
 // 此时不会产生编译错误,因为有声明新的变量,因为 := 是一个声明语句

(三)多变量声明

//类型相同多个变量, 非全局变量
var vname1, vname2, vname3 type
vname1, vname2, vname3 = v1, v2, v3

var vname1, vname2, vname3 = v1, v2, v3 // 和 python 很像,不需要显示声明类型,自动推断

vname1, vname2, vname3 := v1, v2, v3 // 出现在 := 左侧的变量不应该是已经被声明过的,否则会导致编译错误


// 这种因式分解关键字的写法一般用于声明全局变量
var (
    vname1 v_type1
    vname2 v_type2
)
  • 一个实例
package main

var x, y int
var (  // 这种因式分解关键字的写法一般用于声明全局变量
    a int
    b bool
)

var c, d int = 1, 2
var e, f = 123, "hello"

//这种不带声明格式的只能在函数体中出现
//g, h := 123, "hello"

func main(){
    g, h := 123, "hello"
    println(x, y, a, b, c, d, e, f, g, h)
}

//输出:0 0 0 false 1 2 123 hello 123 hello

(四)值类型和引用类型

1、值类型
  • 所有像 int、float、bool 和 string 这些基本类型都属于值类型,使用这些类型的变量直接指向存在内存中的值。

在这里插入图片描述

  • 当使用等号 = 将一个变量的值赋值给另一个变量时,如:j = i,实际上是在内存中将 i 的值进行了拷贝。

在这里插入图片描述

  • 可以通过 &i 来获取变量 i 的内存地址,例如:0xf840000040(每次的地址都可能不一样)。

内存地址会根据机器的不同而有所不同,甚至相同的程序在不同的机器上执行后也会有不同的内存地址。因为每台机器可能有不同的存储器布局,并且位置分配也可能不同。
指针变量 * 和地址值 & 的区别:
指针变量保存的是一个地址值,会分配独立的内存来存储一个整型数字。
当变量前面没有 * 标识时,才等同于 & 的用法,否则会直接输出一个整型数字。

func main() {
   var a int = 4
   var ptr *int
   ptr = &a
   println("a的值为", a);    // 4
   println("*ptr为", *ptr);  // 4
   println("ptr为", ptr);    // 824633794744
}
  • 值类型的变量的值存储在栈中。
2、引用类型
  • 一个引用类型的变量 r1 存储的是 r1 的值所在的内存地址(数字),或内存地址中第一个字所在的位置。

在这里插入图片描述

  • 这个内存地址为称之为指针,这个指针实际上也被存在另外的某一个变量中。
  • 同一个引用类型的指针指向的多个变量可以是在连续的内存地址中(内存布局是连续的),这也是计算效率最高的一种存储形式;也可以将这些变量分散存放在内存中,每个变量都指示了下一个变量所在的内存地址。
  • 当使用赋值语句 r2 = r1 时,只有引用(地址)被复制。
  • 如果 r1 的值被改变了,那么这个值的所有引用都会指向被修改后的内容,在这个例子中,r2 也会受到影响。
3、简短形式,使用 := 赋值操作符
  • 在变量的初始化时省略变量的类型而由系统自动推断,声明语句写上 var 关键字其实是显得有些多余了,因此可以将它们简写为 a := 50 或 b := false。

  • a 和 b 的类型(int 和 bool)将由编译器自动推断。

  • 这是使用变量的首选形式,但是它只能被用在函数体内,而不可以用于全局变量的声明与赋值。使用操作符 := 可以高效地创建一个新的变量,称之为初始化声明。

注意⚠️

4、空白标识符 _
  • 被用于抛弃值,如值 5 在:_, b = 5, 7 中被抛弃。
  • _ 实际上是一个只写变量,你不能得到它的值。
  • 这样做是因为 Go 语言中你必须使用所有被声明的变量,但有时你并不需要使用从一个函数得到的所有返回值。
  • 并行赋值也被用于当一个函数返回多个返回值时,比如这里的 val 和错误 err 是通过调用 Func1 函数同时得到:val, err = Func1(var1)。
package main

import "fmt"

func main() {
  _,numb,strs := numbers() //只获取函数返回值的后两个
  fmt.Println(numb,strs)
}

//一个可以返回多个值的函数
func numbers()(int,int,string){
  a , b , c := 1 , 2 , "str"
  return a,b,c
}
// output: 2 str

五、常量声明

常量是一个简单值的标识符,在程序运行时,不会被修改的量。
常量中的数据类型只可以是布尔型、数字型(整数型、浮点型和复数)和字符串型。

(一)一般常量声明

  • 常量的定义格式:const identifier [type] = value

    • 显式类型定义: const b string = “abc”
    • 隐式类型定义: const b = “abc”
package main

import "fmt"

func main() {
   const LENGTH int = 10
   const WIDTH int = 5   
   var area int
   const a, b, c = 1, false, "str" //多重赋值

   area = LENGTH * WIDTH
   fmt.Printf("面积为 : %d", area)
    fmt.Println()
    fmt.Println(a, b, c)
}

//output 
//50
//1 false str
  • 常量还可以用作枚举
package main

import "unsafe"
const (
    a = "abc"
    b = len(a)   // a字符串的长度
    c = unsafe.Sizeof(a)
    //字符串类型在 go 里是个结构, 包含指向底层数组的指针和长度,
    //这两部分每部分都是 8 个字节,所以字符串类型大小为 16 个字节。
)

func main(){
    println(a, b, c)
}

####(二)特殊常量 iota

iota,特殊常量,可以认为是一个可以被编译器修改的常量。
iota 在 const关键字出现时将被重置为 0(const 内部的第一行之前)。
const 中每新增一行常量声明将使 iota 计数一次(iota 可理解为 const 语句块中的行索引)。

  • 第一个 iota 等于 0,每当 iota 在新的一行被使用时,它的值都会自动加 1。
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)
}

// output: 0 1 2 ha ha 100 100 7 8
  • 区别对待,这里没有 iota
package main

import "fmt"

const (
    a = 1
    b
    c
    d
)

func main() {
    fmt.Println(a)
    // b、c、d没有初始化,使用上一行(即a)的值
    fmt.Println(b)   // 输出1
    fmt.Println(c)   // 输出1
    fmt.Println(d)   // 输出1
}
  • 有趣的例子
package main

import "fmt"
const (
    i=1<<iota
    j=3<<iota
    k
    l
)

func main() {
    fmt.Println("i=",i)
    fmt.Println("j=",j)
    fmt.Println("k=",k)
    fmt.Println("l=",l)
}

//i= 1
//j= 6
//k= 12
//l= 24
  • iota 只是在同一个 const 常量组内递增,每当有新的 const 关键字时,iota 计数会重新开始。
package main

const (
    i = iota
    j = iota
    x = iota
)
const xx = iota
const yy = iota
func main(){
    println(i, j, x, xx, yy)
}

// 输出是 0 1 2 0 0

六、条件语句、循环语句

(一)条件语句

1、if 语句

if 语句 由一个布尔表达式后紧跟一个或多个语句组成。

  • 用 If 语句判断偶数。
package main

import "fmt"

func main() {
    var s int ;    // 声明变量 s 是需要判断的数
    fmt.Println("输入一个数字:")
    fmt.Scan(&s)

    if s%2 == 0  { //     取 s 处以 2 的余数是否等于 0
        fmt.Print("s 是偶数\n") //如果成立
    }else {
        fmt.Print("s 不是偶数\n") //否则
    }
    fmt.Print("s 的值是:",s) 
}
  • Go 的 if 还有一个强大的地方就是条件判断语句里面允许声明一个变量,这个变量的作用域只能在该条件逻辑块内,其他地方就不起作用了。
package main
  
import "fmt"
func main() {
    if num := 9; num < 0 {
        fmt.Println(num, "is negative")
    } else if num < 10 {
        fmt.Println(num, "has 1 digit")
    } else {
        fmt.Println(num, "has multiple digits")
    }
}
  • 总结

    • 不需使用括号将条件包含起来
    • 大括号{ }必须存在,即使只有一行语句
    • 左括号必须在 if 或 else 的同一行
    • 在 if 之后,条件语句之前,可以添加变量初始化语句,使用;进行分隔
    • 在有返回值的函数中,最终的return不能在条件语句中
2、if…else 语句

if 语句 后可以使用可选的 else 语句, else 语句中的表达式在布尔表达式为 false 时执行。

if 布尔表达式 {
   /* 在布尔表达式为 true 时执行 */
} else {
  /* 在布尔表达式为 false 时执行 */
}
3、if 嵌套语句

可以在 if 或 else if 语句中嵌入一个或多个 if 或 else if 语句。

if 布尔表达式 1 {
   /* 在布尔表达式 1 为 true 时执行 */
   if 布尔表达式 2 {
      /* 在布尔表达式 2 为 true 时执行 */
   }
}
4、switch 语句

switch 语句用于基于不同条件执行不同动作。

(1)语法
switch var1 {
    case val1:
        ...
    case val2:
        ...
    default:
        ...
}
  • 变量 var1 可以是任何类型,而 val1 和 val2 则可以是同类型的任意值。
  • 类型不被局限于常量或整数,但必须是相同的类型;或者最终结果为相同类型的表达式。
  • 您可以同时测试多个可能符合条件的值,使用逗号分割它们。
    • 例如:case val1, val2, val3。
package main

import "fmt"

func main() {
   /* 定义局部变量 */
   var grade string = "B"
   var marks int = 90

// 变量
   switch marks {
      case 90: grade = "A"
      case 80: grade = "B"
      case 50,60,70 : grade = "C"
      default: grade = "D"  
   }

// 语句               
   switch {
      case grade == "A" :
         fmt.Printf("优秀!\n" )     
      case grade == "B", grade == "C" :
         fmt.Printf("良好\n" )      
      case grade == "D" :
         fmt.Printf("及格\n" )      
      case grade == "F":
         fmt.Printf("不及格\n" )
      default:
         fmt.Printf("差\n" );
   }
   fmt.Printf("你的等级是 %s\n", grade );      
}
(2)Type Switch

switch 语句还可以被用于 type-switch 来判断某个 interface 变量中实际存储的变量类型。

switch x.(type){
    case type:
       statement(s);      
    case type:
       statement(s); 
    /* 你可以定义任意个数的case */
    default: /* 可选 */
       statement(s);
}
package main

import "fmt"

func main() {
   var x interface{}
     
   switch i := x.(type) {
      case nil:   
         fmt.Printf(" x 的类型 :%T",i)                
      case int:   
         fmt.Printf("x 是 int 型")                       
      case float64:
         fmt.Printf("x 是 float64 型")           
      case func(int) float64:
         fmt.Printf("x 是 func(int) 型")                      
      case bool, string:
         fmt.Printf("x 是 bool 或 string 型" )       
      default:
         fmt.Printf("未知型")     
   }   
}

// output 
// x 的类型 :<nil>
(3)fallthrough

使用 fallthrough 会强制执行后面的 case 语句,fallthrough 不会判断下一条 case 的表达式结果是否为 true。

package main

import "fmt"

func main() {

    switch {
    case false: // 认为exp为true,不执行
            fmt.Println("1、case 条件语句为 false")
            fallthrough
    case true: // 认为exp为true,执行
            fmt.Println("2、case 条件语句为 true")
            fallthrough //继续执行
    case false: // 不判断exp的true or false,执行
            fmt.Println("3、case 条件语句为 false")
            fallthrough //继续执行
    case true: //不判断,继续执行
            fmt.Println("4、case 条件语句为 true")
    case false: // 停止
            fmt.Println("5、case 条件语句为 false")
            fallthrough
    default:
            fmt.Println("6、默认 case")
    }
}

//2、case 条件语句为 true
//3、case 条件语句为 false
//4、case 条件语句为 true

######(4)总结

  • 支持多条件匹配
switch{
    case 1,2,3,4:
    default:
}
  • 不同的 case 之间不使用 break 分隔,默认只会执行一个 case。
  • 如果想要执行多个 case,需要使用 fallthrough 关键字,也可用 break 终止。
switch{
    case 1:
    ...
    if(...){
        break
    }

    fallthrough // 此时switch(1)会执行case1和case2,
    // 但是如果满足if条件,则只执行case1

    case 2:
    ...
    case 3:
}
5、select 语句

select 语句类似于 switch 语句,但是select会随机执行一个可运行的case。如果没有case可运行,它将阻塞,直到有case可运行。

  • select 是 Go 中的一个控制结构,类似于用于通信的 switch 语句。每个 case 必须是一个通信操作,要么是发送要么是接收。

  • select 随机执行一个可运行的 case。如果没有 case 可运行,它将阻塞,直到有 case 可运行。一个默认的子句应该总是可运行的。

select {
    case communication clause  :
       statement(s);      
    case communication clause  :
       statement(s); 
    /* 你可以定义任意数量的 case */
    default : /* 可选 */
       statement(s);
}
  • select 语句的语法
    • 每个 case 都必须是一个通信。
    • 所有 channel 表达式都会被求值。
    • 所有被发送的表达式都会被求值。
    • 如果任意某个通信可以进行,它就执行,其他被忽略。
    • 如果有多个 case 都可以运行,Select 会随机公平地选出一个执行。其他不会执行。
      否则:
      • 如果有 default 子句,则执行该语句。
      • 如果没有 default 子句,select 将阻塞,直到某个通信可以运行;Go 不会重新对 channel 或值进行求值。
package main

import "fmt"

func main() {
   var c1, c2, c3 chan int
   var i1, i2 int
   select {
      case i1 = <-c1:
         fmt.Printf("received ", i1, " from c1\n")
      case c2 <- i2:
         fmt.Printf("sent ", i2, " to c2\n")
      case i3, ok := (<-c3):  // same as: i3, ok := <-c3
         if ok {
            fmt.Printf("received ", i3, " from c3\n")
         } else {
            fmt.Printf("c3 is closed\n")
         }
      default:
         fmt.Printf("no communication\n")
   }    
}

// no communication
  • select 会循环检测条件,如果有满足则执行并退出,否则一直循环检测。
package main

import (
    "fmt"
    "time"
)

func Chann(ch chan int, stopCh chan bool) {
    var i int
    i = 10
    for j := 0; j < 10; j++ {
        ch <- i
        time.Sleep(time.Second)
    }
    stopCh <- true
}

func main() {

    ch := make(chan int)
    c := 0
    stopCh := make(chan bool)

    go Chann(ch, stopCh)

    for {
        select {
        case c = <-ch:
            fmt.Println("Recvice", c)
            fmt.Println("channel")
        case s := <-ch:
            fmt.Println("Receive", s)
        case _ = <-stopCh:
            goto end
        }
    }
end:
}

//Recvice 10
//channel
//Receive 10
//Receive 10
//Recvice 10
//channel
//Recvice 10
//channel
//Recvice 10
//channel
//Recvice 10
//channel
//Receive 10
//Recvice 10
//channel
//Receive 10

(二)循环语句

1、for 循环
  • 和 C 语言的 for 一样
for init; condition; post { }
  • 和 C 的 while 一样
for condition { }
  • 和 C 的 for(;? 一样
for { }
  • for 循环的 range 格式可以对 slice、map、数组、字符串等进行迭代循环。
for key, value := range oldMap {
    newMap[key] = value
}
package main

import "fmt"

func main() {


	numbers := [6]int{1, 2, 3, 5}

	for i, x := range numbers {
		fmt.Printf("第 %d 位 x 的值 = %d\n", i,x)
	}
}
0 位 x 的值 = 11 位 x 的值 = 22 位 x 的值 = 33 位 x 的值 = 54 位 x 的值 = 05 位 x 的值 = 0
2、嵌套循环
for [condition |  ( init; condition; increment ) | Range]
{
   for [condition |  ( init; condition; increment ) | Range]
   {
      statement(s);
   }
   statement(s);
}
3、循环控制语句
(1)break 语句
  • 经常用于中断当前 for 循环或跳出 switch 语句
(2)continue 语句
  • 跳过当前循环的剩余语句,然后继续进行下一轮循环。
(3)goto 语句
goto label;
..
.
label: statement;
  • 在变量 a 等于 15 的时候跳过本次循环并回到循环的开始语句 LOOP 处。
package main

import "fmt"

func main() {
   /* 定义局部变量 */
   var a int = 10

   /* 循环 */
   LOOP: for a < 20 {
      if a == 15 {
         /* 跳过迭代 */
         a = a + 1
         goto LOOP
      }
      fmt.Printf("a的值为 : %d\n", a)
      a++     
   }  
}
a的值为 : 10
a的值为 : 11
a的值为 : 12
a的值为 : 13
a的值为 : 14
a的值为 : 16
a的值为 : 17
a的值为 : 18
a的值为 : 19
4、无限循环
package main

import "fmt"

func main() {
    for true  {
        fmt.Printf("这是无限循环。\n");
    }
}

七、函数

(一)函数定义

func function_name( [parameter list] ) [return_types] {
   函数体
}
/* 函数返回两个数的最大值 */
func max(num1, num2 int) int {
   /* 声明局部变量 */
   var result int

   if (num1 > num2) {
      result = num1
   } else {
      result = num2
   }
   return result 
}

(二)函数调用

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 
}

//最大值是 : 200

(三)返回值

Go 函数可以返回多个值。

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)
}

// Runoob Google

(四)函数参数

1、值传递

值传递是指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数。

package main

import "fmt"

func main() {
   /* 定义局部变量 */
   var a int = 100
   var b int = 200

   fmt.Printf("交换前 a 的值为 : %d\n", a )
   fmt.Printf("交换前 b 的值为 : %d\n", b )

   /* 通过调用函数来交换值 */
   swap(a, b)

   fmt.Printf("交换后 a 的值 : %d\n", a )
   fmt.Printf("交换后 b 的值 : %d\n", b )
}

/* 定义相互交换值的函数 */
func swap(x, y int) int {
   var temp int

   temp = x /* 保存 x 的值 */
   x = y    /* 将 y 值赋给 x */
   y = temp /* 将 temp 值赋给 y*/

   return temp;
}
交换前 a 的值为 : 100
交换前 b 的值为 : 200
交换后 a 的值 : 100
交换后 b 的值 : 200
2、引用传递

引用传递是指在调用函数时将实际参数的地址传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数。

package main

import "fmt"

func main() {
   /* 定义局部变量 */
   var a int = 100
   var b int = 200

   fmt.Printf("交换前,a 的值 : %d\n", a )
   fmt.Printf("交换前,b 的值 : %d\n", b )

   /* 调用 swap() 函数
   * &a 指向 a 指针,a 变量的地址
   * &b 指向 b 指针,b 变量的地址
   */
   swap(&a, &b)

   fmt.Printf("交换后,a 的值 : %d\n", a )
   fmt.Printf("交换后,b 的值 : %d\n", b )
}

func swap(x *int, y *int) {
   var temp int
   temp = *x    /* 保存 x 地址上的值 */
   *x = *y      /* 将 y 值赋给 x */
   *y = temp    /* 将 temp 值赋给 y */
}
交换前,a 的值 : 100
交换前,b 的值 : 200
交换后,a 的值 : 200
交换后,b 的值 : 100
3、简单交换值的写法
a := 100
b := 200
a, b = b, a
// a == 200
// b == 100

(五)函数用法

1、函数作为另一个函数的实参

Go 语言可以很灵活的创建函数,并作为另外一个函数的实参。

  • 以下实例中我们在定义的函数中初始化一个变量,该函数仅仅是为了使用内置函数 math.sqrt( )。
package main

import (
   "fmt"
   "math"
)

func main(){
   /* 声明函数变量 */
   getSquareRoot := func(x float64) float64 {
      return math.Sqrt(x)
   }

   /* 使用函数 */
   fmt.Println(getSquareRoot(9))

}
  • 一个复杂的例子
package main
import "fmt"

// 声明一个函数类型
type cb func(int) int

func main() {
	testCallBack(1, callBack)
	testCallBack(2, func(x int) int {
		fmt.Printf("我是回调,x:%d\n", x)
		return x
	})
}

func testCallBack(x int, f cb) {
	f(x)
}

func callBack(x int) int {
	fmt.Printf("我是回调,x:%d\n", x)
	return x
}
// 我是回调,x:1
// 我是回调,x:2
2、闭包

Go 语言支持匿名函数,可作为闭包。匿名函数是一个"内联"语句或表达式。
匿名函数的优越性在于可以直接使用函数内的变量,不必申明。

  • 以下实例中,我们创建了函数 getSequence() ,返回另外一个函数。该函数的目的是在闭包中递增 i 变量。
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
}

//圆的面积 =  314

八、变量作用域

(一)局部变量

在函数体内声明的变量称之为局部变量,它们的作用域只在函数体内,参数和返回值变量也是局部变量。

package main

import "fmt"

func main() {
   /* 声明局部变量 */
   var a, b, c int 

   /* 初始化参数 */
   a = 10
   b = 20
   c = a + b

   fmt.Printf ("结果: a = %d, b = %d and c = %d\n", a, b, c)
}
//结果: a = 10, b = 20 and c = 30

(二)全局变量

在函数体外声明的变量称之为全局变量,全局变量可以在整个包甚至外部包(被导出后)使用。

package main

import "fmt"

/* 声明全局变量 */
var g int

func main() {

   /* 声明局部变量 */
   var a, b int

   /* 初始化参数 */
   a = 10
   b = 20
   g = a + b

   fmt.Printf("结果: a = %d, b = %d and g = %d\n", a, b, g)
}
// 结果: a = 10, b = 20 and g = 30

(三)形式参数

形式参数会作为函数的局部变量来使用。

package main

import "fmt"

/* 声明全局变量 */
var a int = 20;

func main() {
   /* main 函数中声明局部变量 */
   var a int = 10
   var b int = 20
   var c int = 0

   fmt.Printf("main()函数中 a = %d\n",  a);
   c = sum( a, b);
   fmt.Printf("main()函数中 c = %d\n",  c);
}

/* 函数定义-两数相加 */
func sum(a, b int) int {
   fmt.Printf("sum() 函数中 a = %d\n",  a);
   fmt.Printf("sum() 函数中 b = %d\n",  b);

   return a + b;
}
main()函数中 a = 10
sum() 函数中 a = 10
sum() 函数中 b = 20
main()函数中 c = 30

(四)初始化局部和全局变量

不同类型的局部和全局变量默认值。

在这里插入图片描述

(五)注意

Go 语言程序中全局变量与局部变量名称可以相同,但是函数内的局部变量会被优先考虑。

package main

import "fmt"

/* 声明全局变量 */
var g int = 20

func main() {
   /* 声明局部变量 */
   var g int = 10

   fmt.Printf ("结果: g = %d\n",  g)
}

//结果: g = 10

九、数组、指针、结构体

(一)数组

1、声明数组
var variable_name [SIZE] variable_type
  • 例如以下定义了数组 balance 长度为 10 类型为 float32
var balance [10] float32
2、初始化数组
  • 初始化数组中 {} 中的元素个数不能大于 [] 中的数字。
var balance = [5]float32{1000.0, 2.0, 3.4, 7.0, 50.0}
  • 如果忽略 [] 中的数字不设置数组大小,Go 语言会根据元素的个数来设置数组的大小。
var balance = [...]float32{1000.0, 2.0, 3.4, 7.0, 50.0}
3、访问数组元素
  • 数组元素可以通过索引(位置)来读取。格式为数组名后加中括号,中括号中为索引的值。
var salary float32 = balance[9]
package main

import "fmt"

func main() {
   var n [10]int /* n 是一个长度为 10 的数组 */
   var i,j int

   /* 为数组 n 初始化元素 */         
   for i = 0; i < 10; i++ {
      n[i] = i + 100 /* 设置元素为 i + 100 */
   }

   /* 输出每个数组元素的值 */
   for j = 0; j < 10; j++ {
      fmt.Printf("Element[%d] = %d\n", j, n[j] )
   }
}
Element[0] = 100
Element[1] = 101
Element[2] = 102
Element[3] = 103
Element[4] = 104
Element[5] = 105
Element[6] = 106
Element[7] = 107
Element[8] = 108
Element[9] = 109
4、多维数组
(1)声明
var variable_name [SIZE1][SIZE2]...[SIZEN] variable_type
var threedim [5][10][4]int
(2)二维数组

初始化

a = [3][4]int{  
 {0, 1, 2, 3} ,   /*  第一行索引为 0 */
 {4, 5, 6, 7} ,   /*  第二行索引为 1 */
 {8, 9, 10, 11},   /* 第三行索引为 2 */
}
(3)访问二维数组

习惯性跳坑:多维数组初始化或赋值时需要注意 Go 语法规范,该写在一行就写在一行,一行一条语句。

package main

import "fmt"

func main() {
   /* 数组 - 5 行 2 列*/
   var a = [5][2]int{ {0,0}, {1,2}, {2,4}, {3,6},{4,8}}
   var i, j int

   /* 输出数组元素 */
   for  i = 0; i < 5; i++ {
      for j = 0; j < 2; j++ {
         fmt.Printf("a[%d][%d] = %d\n", i,j, a[i][j] )
      }
   }
}
a[0][0] = 0
a[0][1] = 0
a[1][0] = 1
a[1][1] = 2
a[2][0] = 2
a[2][1] = 4
a[3][0] = 3
a[3][1] = 6
a[4][0] = 4
a[4][1] = 8
5、向函数传递数组
package main

import "fmt"

func main() {
   /* 数组长度为 5 */
   var  balance = [5]int {1000, 2, 3, 17, 50}
   var avg float32

   /* 数组作为参数传递给函数 */
   avg = getAverage( balance, 5 ) ;

   /* 输出返回的平均值 */
   fmt.Printf( "平均值为: %f ", avg );
}
func getAverage(arr [5]int, size int) float32 {
   var i,sum int
   var avg float32  

   for i = 0; i < size;i++ {
      sum += arr[i]
   }

   avg = float32(sum) / float32(size)

   return avg;
}
平均值为: 214.399994

(二)指针

1、什么是指针

一个指针变量指向了一个值的内存地址。

  • 声明格式
var var_name *var-type
var ip *int        /* 指向整型*/
var fp *float32    /* 指向浮点型 */
2、如何使用指针
package main

import "fmt"

func main() {
   var a int= 20   /* 声明实际变量 */
   var ip *int        /* 声明指针变量 */

   ip = &a  /* 指针变量的存储地址 */

   fmt.Printf("a 变量的地址是: %x\n", &a  )

   /* 指针变量的存储地址 */
   fmt.Printf("ip 变量储存的指针地址: %x\n", ip )

   /* 使用指针访问值 */
   fmt.Printf("*ip 变量的值: %d\n", *ip )
}
a 变量的地址是: 20818a220
ip 变量储存的指针地址: 20818a220
*ip 变量的值: 20
3、空指针

当一个指针被定义后没有分配到任何变量时,它的值为 nil。
nil 指针也称为空指针。

package main

import "fmt"

func main() {
   var  ptr *int

   fmt.Printf("ptr 的值为 : %x\n", ptr  )
}
  • 空指针判断
if(ptr != nil)     /* ptr 不是空指针 */
if(ptr == nil)    /* ptr 是空指针 */
4、指针数组
  • 在我们了解指针数组前,先看个实例,定义了长度为 3 的整型数组。
package main

import "fmt"

const MAX int = 3

func main() {

   a := []int{10,100,200}
   var i int

   for i = 0; i < MAX; i++ {
      fmt.Printf("a[%d] = %d\n", i, a[i] )
   }
}
a[0] = 10
a[1] = 100
a[2] = 200
  • 以下实例的三个整数将存储在指针数组中。
package main

import "fmt"

const MAX int = 3

func main() {
   a := []int{10,100,200}
   var i int
   var ptr [MAX]*int;

   for  i = 0; i < MAX; i++ {
      ptr[i] = &a[i] /* 整数地址赋值给指针数组 */
   }

   for  i = 0; i < MAX; i++ {
      fmt.Printf("a[%d] = %d\n", i,*ptr[i] )
   }
}
5、指向指针的指针

如果一个指针变量存放的又是另一个指针变量的地址,则称这个指针变量为指向指针的指针变量。

  • 当定义一个指向指针的指针变量时,第一个指针存放第二个指针的地址,第二个指针存放变量的地址。

在这里插入图片描述

  • 指向指针的指针变量声明格式如下。
var ptr **int;
package main

import "fmt"

func main() {

   var a int
   var ptr *int
   var pptr **int

   a = 3000

   /* 指针 ptr 地址 */
   ptr = &a

   /* 指向指针 ptr 地址 */
   pptr = &ptr

   /* 获取 pptr 的值 */
   fmt.Printf("变量 a = %d\n", a )
   fmt.Printf("指针变量 *ptr = %d\n", *ptr )
   fmt.Printf("指向指针的指针变量 **pptr = %d\n", **pptr)
}
变量 a = 3000
指针变量 *ptr = 3000
指向指针的指针变量 **pptr = 3000
6、向函数传递指针参数
package main

import "fmt"

func main() {
   /* 定义局部变量 */
   var a int = 100
   var b int= 200

   fmt.Printf("交换前 a 的值 : %d\n", a )
   fmt.Printf("交换前 b 的值 : %d\n", b )

   /* 调用函数用于交换值
   * &a 指向 a 变量的地址
   * &b 指向 b 变量的地址
   */
   swap(&a, &b);

   fmt.Printf("交换后 a 的值 : %d\n", a )
   fmt.Printf("交换后 b 的值 : %d\n", b )
}

func swap(x *int, y *int) {
   var temp int
   temp = *x    /* 保存 x 地址的值 */
   *x = *y      /* 将 y 赋值给 x */
   *y = temp    /* 将 temp 赋值给 y */
}
交换前 a 的值 : 100
交换前 b 的值 : 200
交换后 a 的值 : 200
交换后 b 的值 : 100

(三)结构体

1、定义结构体
type struct_variable_type struct {
   member definition;
   member definition;
   ...
   member definition;
}
  • 一旦定义了结构体类型,它就能用于变量的声明,语法格式如下。
variable_name := structure_variable_type {value1, value2...valuen}
//
variable_name := structure_variable_type { key1: value1, key2: value2..., keyn: valuen}
  • 实例
package main

import "fmt"

type Books struct {
	title string
	author string
	subject string
	book_id int
}


func main() {

	// 创建一个新的结构体
	fmt.Println(Books{"HAHAHA", "PzLu", "Go", 6495407})

	// 也可以使用 key => value 格式
	fmt.Println(Books{title: "HAHAHA", author: "PzLu", subject: "Go", book_id: 6495407})

	// 忽略的字段为 0 或 空
	fmt.Println(Books{title: "HAHAHA", author: "PzLu"})
}
{HAHAHA PzLu Go 6495407}
{HAHAHA PzLu Go 6495407}
{HAHAHA PzLu  0}
2、访问结构体成员
package main

import "fmt"

type Books struct {
	title string
	author string
	subject string
	book_id int
}

func main() {
	var Book1 Books        /* 声明 Book1 为 Books 类型 */
	var Book2 Books        /* 声明 Book2 为 Books 类型 */

	/* book 1 描述 */
	Book1.title = "Go"
	Book1.author = "PzLu"
	Book1.subject = "Go"
	Book1.book_id = 6495407

	/* book 2 描述 */
	Book2.title = "Python"
	Book2.author = "PzLu1"
	Book2.subject = "Python"
	Book2.book_id = 6495700

	/* 打印 Book1 信息 */
	fmt.Printf( "Book 1 title : %s\n", Book1.title)
	fmt.Printf( "Book 1 author : %s\n", Book1.author)
	fmt.Printf( "Book 1 subject : %s\n", Book1.subject)
	fmt.Printf( "Book 1 book_id : %d\n", Book1.book_id)

	/* 打印 Book2 信息 */
	fmt.Printf( "Book 2 title : %s\n", Book2.title)
	fmt.Printf( "Book 2 author : %s\n", Book2.author)
	fmt.Printf( "Book 2 subject : %s\n", Book2.subject)
	fmt.Printf( "Book 2 book_id : %d\n", Book2.book_id)
}
Book 1 subject : Go
Book 1 book_id : 6495407
Book 2 title : Python
Book 2 author : PzLu1
Book 2 subject : Python
Book 2 book_id : 6495700
3、结构体作为参数传递

结构体是作为参数的值传递。

package main

import "fmt"

type Books struct {
	title string
	author string
	subject string
	book_id int
}

func main() {
	var Book1 Books        /* 声明 Book1 为 Books 类型 */
	var Book2 Books        /* 声明 Book2 为 Books 类型 */

	/* book 1 描述 */
	Book1.title = "Go"
	Book1.author = "PzLu"
	Book1.subject = "Go"
	Book1.book_id = 6495407

	/* book 2 描述 */
	Book2.title = "Python"
	Book2.author = "PzLu"
	Book2.subject = "Python"
	Book2.book_id = 6495700

	/* 打印 Book1 信息 */
	printBook(Book1)

	/* 打印 Book2 信息 */
	printBook(Book2)
}

func printBook( book Books ) {
	fmt.Printf( "Book title : %s\n", book.title);
	fmt.Printf( "Book author : %s\n", book.author);
	fmt.Printf( "Book subject : %s\n", book.subject);
	fmt.Printf( "Book book_id : %d\n", book.book_id);
}
Book subject : Go
Book book_id : 6495407
Book title : Python
Book author : PzLu
Book subject : Python
Book book_id : 6495700
4、结构体指针
  • 可以定义指向结构体的指针类似于其他指针变量。
var struct_pointer *Books
  • 查看结构体变量地址,可以将 & 符号放置于结构体变量前。
struct_pointer = &Book1;
  • 使用结构体指针访问结构体成员,使用 “.” 操作符。
struct_pointer.title;
  • 实例
package main

import "fmt"

type Books struct {
	title string
	author string
	subject string
	book_id int
}

func main() {
	var Book1 Books        /* 声明 Book1 为 Books 类型 */
	var Book2 Books        /* 声明 Book2 为 Books 类型 */

	/* book 1 描述 */
	Book1.title = "Go"
	Book1.author = "PzLu"
	Book1.subject = "Go"
	Book1.book_id = 6495407

	/* book 2 描述 */
	Book2.title = "Python"
	Book2.author = "PzLu"
	Book2.subject = "Python"
	Book2.book_id = 6495700

	/* 打印 Book1 信息 */
	printBook(&Book1)

	/* 打印 Book2 信息 */
	printBook(&Book2)
}

func printBook( book *Books ) {
	fmt.Printf( "Book title : %s\n", book.title);
	fmt.Printf( "Book author : %s\n", book.author);
	fmt.Printf( "Book subject : %s\n", book.subject);
	fmt.Printf( "Book book_id : %d\n", book.book_id);
}
Book subject : Go
Book book_id : 6495407
Book title : Python
Book author : PzLu
Book subject : Python
Book book_id : 6495700

十、切片(slice)、范围(range)、集合(map)

(一)切片(slice)

1、定义切片
  • 可以声明一个未指定大小的数组来定义切片(切片不需要说明长度)。
var identifier []type
  • 使用make()函数来创建切片。
var slice1 []type = make([]type, len)

也可以简写为
slice1 := make([]type, len)
2、切片初始化
  • 直接初始化切片,[ ]表示是切片类型,{1,2,3}初始化值依次是1,2,3,其cap=len=3。
s := [] int {1,2,3} 
  • 初始化切片s,是数组arr的引用。
s := arr[:] 
  • 将arr中从下标startIndex到endIndex-1 下的元素创建为一个新的切片。
s := arr[startIndex:endIndex] 
  • 缺省endIndex时将表示一直到arr的最后一个元素。
s := arr[startIndex:] 
  • 缺省startIndex时将表示从arr的第一个元素开始。
s := arr[:endIndex]
  • 通过切片s初始化切片s1。
s1 := s[startIndex:endIndex] 
  • 通过内置函数make()初始化切片s,[]int 标识为其元素类型为int的切片。
s :=make([]int,len,cap) 
3、len( ) 和 cap( ) 函数

切片是可索引的,并且可以由 len( ) 方法获取长度。
切片提供了计算容量的方法 ,cap( ) 可以测量切片最长可以达到多少。

package main

import "fmt"

func main() {
   var numbers = make([]int,3,5)

   printSlice(numbers)
}

func printSlice(x []int){
   fmt.Printf("len=%d cap=%d slice=%v\n",len(x),cap(x),x)
}
// len=3 cap=5 slice=[0 0 0]
4、空切片

一个切片在未初始化之前默认为 nil,长度为 0。

package main

import "fmt"

func main() {
   var numbers []int

   printSlice(numbers)

   if(numbers == nil){
      fmt.Printf("切片是空的")
   }
}

func printSlice(x []int){
   fmt.Printf("len=%d cap=%d slice=%v\n",len(x),cap(x),x)
}
// len=0 cap=0 slice=[]
// 切片是空的
5、切片截取
package main

import "fmt"

func main() {
	/* 创建切片 */
	numbers := []int{0,1,2,3,4,5,6,7,8}
	printSlice(numbers)

	/* 打印原始切片 */
	fmt.Println("numbers ==", numbers)

	/* 打印子切片从索引1(包含) 到索引4(不包含)*/
	fmt.Println("numbers[1:4] ==", numbers[1:4])

	/* 默认下限为 0*/
	fmt.Println("numbers[:3] ==", numbers[:3])

	/* 默认上限为 len(s)*/
	fmt.Println("numbers[4:] ==", numbers[4:])

	numbers1 := make([]int,0,5)
	printSlice(numbers1)

	/* 打印子切片从索引  0(包含) 到索引 2(不包含) */
	number2 := numbers[:2]
	printSlice(number2)

	/* 打印子切片从索引 2(包含) 到索引 5(不包含) */
	number3 := numbers[2:5]
	printSlice(number3)

}

func printSlice(x []int){
	fmt.Printf("len=%d cap=%d slice=%v\n",len(x),cap(x),x)
}
len=9 cap=9 slice=[0 1 2 3 4 5 6 7 8]
numbers == [0 1 2 3 4 5 6 7 8]
numbers[1:4] == [1 2 3]
numbers[:3] == [0 1 2]
numbers[4:] == [4 5 6 7 8]
len=0 cap=5 slice=[]
len=2 cap=9 slice=[0 1]
len=3 cap=7 slice=[2 3 4]
6、append( ) 和 copy( ) 函数

如果想增加切片的容量,我们必须创建一个新的更大的切片并把原分片的内容都拷贝过来。

  • 下面的代码描述了从拷贝切片的 copy 方法和向切片追加新元素的 append 方法。
package main

import "fmt"

func main() {
   var numbers []int
   printSlice(numbers)

   /* 允许追加空切片 */
   numbers = append(numbers, 0)
   printSlice(numbers)

   /* 向切片添加一个元素 */
   numbers = append(numbers, 1)
   printSlice(numbers)

   /* 同时添加多个元素 */
   numbers = append(numbers, 2,3,4)
   printSlice(numbers)

   /* 创建切片 numbers1 是之前切片的两倍容量*/
   numbers1 := make([]int, len(numbers), (cap(numbers))*2)

   /* 拷贝 numbers 的内容到 numbers1 */
   copy(numbers1,numbers)
   printSlice(numbers1)   
}

func printSlice(x []int){
   fmt.Printf("len=%d cap=%d slice=%v\n",len(x),cap(x),x)
}
len=0 cap=0 slice=[]
len=1 cap=1 slice=[0]
len=2 cap=2 slice=[0 1]
len=5 cap=6 slice=[0 1 2 3 4]
len=5 cap=12 slice=[0 1 2 3 4]

当 numbers = [0, 1] 时,append(numbers, 2, 3, 4) 为什么 cap 从 2 变成 6 ?

(二)范围(range)

在数组和切片中它返回元素的索引和索引对应的值,在集合中返回 key-value 对的 key 值。

package main
import "fmt"
func main() {
    //这是我们使用range去求一个slice的和。使用数组跟这个很类似
    nums := []int{2, 3, 4}
    sum := 0
    for _, num := range nums {
        sum += num
    }
    fmt.Println("sum:", sum)
    //在数组上使用range将传入index和值两个变量。
    //上面那个例子我们不需要使用该元素的序号,所以我们使用空白符"_"省略了。
    //有时侯我们确实需要知道它的索引。
    for i, num := range nums {
        if num == 3 {
            fmt.Println("index:", i)
        }
    }
    //range也可以用在map的键值对上。
    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)
    }
}
sum: 9
index: 1
a -> apple
b -> banana
0 103
1 111
  • Go Range 简单循环
package main

import "fmt"

func main(){
    nums := []int{1,2,3,4};
    length := 0;
    for range nums {                                                  
        length++;
    }
    fmt.Println( length);
}
// 4
  • 通过 range 获取参数列表。
package main

import (
    "fmt"
    "os"
)

func main() {
    fmt.Println(len(os.Args))
    for _, arg := range os.Args {
        fmt.Println(arg)
    }
}

(三)集合(map)

Map 是一种无序的键值对的集合。
Map 最重要的一点是通过 key 来快速检索数据,key 类似于索引,指向数据的值。
Map 是一种集合,所以我们可以像迭代数组和切片那样迭代它。
不过,Map 是无序的,我们无法决定它的返回顺序,这是因为 Map 是使用 hash 表来实现的。

1、定义 map

可以使用内建函数 make 也可以使用 map 关键字来定义 Map。

/* 声明变量,默认 map 是 nil */
var map_variable map[key_data_type]value_data_type

/* 使用 make 函数 */
map_variable := make(map[key_data_type]value_data_type)
  • 例子
package main

import "fmt"

func main() {
	var countryCapitalMap map[string]string /*创建集合 */
	countryCapitalMap = make(map[string]string)

	/* map插入key - value对,各个国家对应的首都 */
	countryCapitalMap [ "France" ] = "巴黎"
	countryCapitalMap [ "Italy" ] = "罗马"
	countryCapitalMap [ "Japan" ] = "东京"
	countryCapitalMap [ "India " ] = "新德里"

	/*使用键输出地图值 */
	for country := range countryCapitalMap {
		fmt.Println(country, "首都是", countryCapitalMap [country])
	}

	/*查看元素在集合中是否存在 */
	capital, ok := countryCapitalMap [ "American" ] 
	/*如果确定是真实的,则存在,否则不存在 */
	/*fmt.Println(capital) */
	/*fmt.Println(ok) */
	if (ok) {
		fmt.Println("American 的首都是", capital)
	} else {
		fmt.Println("American 的首都不存在")
	}
}
Italy 首都是 罗马
Japan 首都是 东京
India  首都是 新德里
France 首都是 巴黎
American 的首都不存在
2、delete( ) 函数

delete() 函数用于删除集合的元素, 参数为 map 和其对应的 key。

package main

import "fmt"

func main() {
        /* 创建map */
        countryCapitalMap := map[string]string{"France": "Paris", "Italy": "Rome", "Japan": "Tokyo", "India": "New delhi"}

        fmt.Println("原始地图")

        /* 打印地图 */
        for country := range countryCapitalMap {
                fmt.Println(country, "首都是", countryCapitalMap [ country ])
        }

        /*删除元素*/ delete(countryCapitalMap, "France")
        fmt.Println("法国条目被删除")

        fmt.Println("删除元素后地图")

        /*打印地图*/
        for country := range countryCapitalMap {
                fmt.Println(country, "首都是", countryCapitalMap [ country ])
        }
}
原始地图
India 首都是 New delhi
France 首都是 Paris
Italy 首都是 Rome
Japan 首都是 Tokyo
法国条目被删除
删除元素后地图
Italy 首都是 Rome
Japan 首都是 Tokyo
India 首都是 New delhi

十一、递归函数、类型转换

(一)递归函数

递归,就是在运行的过程中调用自己。

1、阶乘
package main

import "fmt"

func Factorial(n uint64)(result uint64) {
    if (n > 0) {
        result = n * Factorial(n-1)
        return result
    }
    return 1
}

func main() {  
    var i int = 15
    fmt.Printf("%d 的阶乘是 %d\n", i, Factorial(uint64(i)))
}
// 15 的阶乘是 1307674368000
2、斐波那契数列
package main

import "fmt"

func fibonacci(n int) int {
  if n < 2 {
   return n
  }
  return fibonacci(n-2) + fibonacci(n-1)
}

func main() {
    var i int
    for i = 0; i < 10; i++ {
       fmt.Printf("%d\t", fibonacci(i))
    }
}
0    1    1    2    3    5    8    13    21    34

(二)类型转换

type_name(expression)
package main

import "fmt"

func main() {
   var sum int = 17
   var count int = 5
   var mean float32
   
   mean = float32(sum)/float32(count)
   fmt.Printf("mean 的值为: %f\n",mean)
}
// mean 的值为: 3.400000

十二、语言接口

Go 语言提供了另外一种数据类型即接口,它把所有的具有共性的方法定义在一起,任何其他类型只要实现了这些方法就是实现了这个接口。

/* 定义接口 */
type interface_name interface {
   method_name1 [return_type]
   method_name2 [return_type]
   method_name3 [return_type]
   ...
   method_namen [return_type]
}

/* 定义结构体 */
type struct_name struct {
   /* variables */
}

/* 实现接口方法 */
func (struct_name_variable struct_name) method_name1() [return_type] {
   /* 方法实现 */
}
...
func (struct_name_variable struct_name) method_namen() [return_type] {
   /* 方法实现*/
}
  • 实例
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() {
    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()方法,输出结果如下。

I am Nokia, I can call you!
I am iPhone, I can call you!

十三、错误处理

Go 语言通过内置的错误接口提供了非常简单的错误处理机制。

type error interface {
    Error() string
}
  • 我们可以在编码中通过实现 error 接口类型来生成错误信息。
  • 函数通常在最后的返回值中返回错误信息。使用errors.New 可返回一个错误信息。
func Sqrt(f float64) (float64, error) {
    if f < 0 {
        return 0, errors.New("math: square root of negative number")
    }
    // 实现
}
  • 在下面的例子中,我们在调用Sqrt的时候传递的一个负数,然后就得到了non-nil的error对象,将此对象与nil比较,结果为true,所以fmt.Println(fmt包在处理error时会调用Error方法)被调用,以输出错误,请看下面调用的示例代码:
result, err:= Sqrt(-1)

if err != nil {
   fmt.Println(err)
}
  • 实例
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)
    }

}
100/10 =  10
errorMsg is:  
    Cannot proceed, the divider is zero.
    dividee: 100
    divider: 0

十四、并发

Go 语言支持并发,我们只需要通过 go 关键字来开启 goroutine 即可。
goroutine 是轻量级线程,goroutine 的调度是由 Golang 运行时进行管理的。

go 函数名( 参数列表 )
  • 例子
go f(x, y, z)

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)
        }
}

func main() {
        go say("world")
        say("hello")
}
world
hello
world
hello
world
hello
hello
world
hello
world

(一)通道(channel)

通道(channel)是用来传递数据的一个数据结构。
通道可用于两个 goroutine 之间通过传递一个指定类型的值来同步运行和通讯。
操作符 <- 用于指定通道的方向,发送或接收。如果未指定方向,则为双向通道。

ch <- v    // 把 v 发送到通道 ch
v := <-ch  // 从 ch 接收数据
           // 并把值赋给 v
  • 声明一个通道很简单,我们使用chan关键字即可,通道在使用前必须先创建。
ch := make(chan int)

注意:默认情况下,通道是不带缓冲区的。发送端发送数据,同时必须又接收端相应的接收数据。

  • 以下实例通过两个 goroutine 来计算数字之和,在 goroutine 完成计算后,它会计算两个结果的和。
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)
}
-5 17 12

(二)通道缓冲区

通道可以设置缓冲区,通过 make 的第二个参数指定缓冲区大小。

ch := make(chan int, 100)

带缓冲区的通道允许发送端的数据发送和接收端的数据获取处于异步状态,就是说发送端发送的数据可以放在缓冲区里面,可以等待接收端去获取数据,而不是立刻需要接收端去获取数据。
不过由于缓冲区的大小是有限的,所以还是必须有接收端来接收数据的,否则缓冲区一满,数据发送端就无法再发送数据了。
注意:如果通道不带缓冲,发送方会阻塞直到接收方从通道中接收了值。如果通道带缓冲,发送方则会阻塞直到发送的值被拷贝到缓冲区内;如果缓冲区已满,则意味着需要等待直到某个接收方获取到一个值。接收方在有值可以接收之前会一直阻塞。

package main

import "fmt"

func main() {
    // 这里我们定义了一个可以存储整数类型的带缓冲通道
        // 缓冲区大小为2
        ch := make(chan int, 2)

        // 因为 ch 是带缓冲的通道,我们可以同时发送两个数据
        // 而不用立刻需要去同步读取数据
        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)
        }
}
0
1
1
2
3
5
8
13
21
34

@author: PzLu
@data: 2019.08.02

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:撸撸猫 设计师:马嘣嘣 返回首页
评论

打赏作者

PzLu

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值