1 Go 的基础组成有以下几部分

  • 包声明
  • 引入包
  • 函数
  • 变量
  • 语句&表达式
  • 注释
package main

import "fmt"

func main(){
 /**/
   fmt.Println("hello,world!")
}
  • 每个 Go 应用程序都包含一个名为 main 的包
  • fmt 包实现了格式化 IO(输入/输出)的函数
  • 下一行 func main() 是程序开始执行的函数。main 函数是每一个可执行程序所必须包含的,一般来说都是在启动后第一个执行的函数(如果有 init() 函数则会先执行该函数)。
  • 当标识符(包括常量、变量、类型、函数名、结构字段等等)以一个大写字母开头,如:Group1,那么使用这种形式的标识符的对象就可以被外部包的代码所使用(客户端程序需要先导入这个包),这被称为导出(像面向对象语言中的 public);标识符如果以小写字母开头,则对包外是不可见的,但是他们在整个包的内部是可见并且可用的(像面向对象语言中的 protected )
  • 需要注意的是 { 不能单独放在一行,所以以下代码在运行时会产生错误:
  • image.png

···
文件名与包名没有直接关系,不一定要将文件名与包名定成同一个。
文件夹名与包名没有直接关系,并非需要一致。
同一个文件夹下的文件只能有一个包名,否则编译报错。
···

Go 语法基础

Go 程序可以由多个标记组成,可以是关键字,标识符,常量,字符串,符号。如以下 GO 语句由 6 个标记组成:

image.png

image.png

数据类型

Go语言变量

声明变量的一般形式是使用 var 关键字:

var identifier type
package main

import "fmt"
function main(){
    var a string = "Runoob"
    fmt.Println(a)
    
    var b,c int = 1,2
    fmt.Println(b,c)

变量声明

  • 第一种,指定变量类型,如果没有初始化,则变量默认为零值
  • 第二种,根据值自行判断变狼类型
  • 第三种,省略var,注意:=左侧如果没有声明新的变量,就产生编译错误
intVal := 1 // 此时不会产生编译错误,因为有声明新的变量,因为 := 是一个声明语句

相当于

var intVal int 
intVal =1 
多变量声明
//类型相同多个变量, 非全局变量
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
)

值类型和引用类型

Go语言常量

const identifier [type] = value

多个相同类型的声明可以简写为:

const c_name1, c_name2 = value1, value2

常量还可以用作枚举

const (
    Unknown = 0
    Female = 1
    Male = 2
)

iota

iota,特殊常量,可以认为是一个可以被编译器修改的常量。

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

iota 可以被用作枚举值:

const (
    a = iota
    b = iota
    c = iota
)
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)
}
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

Go 语言运算符

其他运算符
image.png

Go 语言条件语句

switch

switch 语句用于基于不同条件执行不同动作,每一个 case 分支都是唯一的,从上至下逐一测试,直到匹配为止。

switch 语句执行的过程从上至下,直到找到匹配项,匹配项后面也不需要再加 break。

switch 默认情况下 case 最后自带 break 语句,匹配成功后就不会执行其他 case,如果我们需要执行后面的 case,可以使用 fallthrough

switch var1 {
    case val1:
        ...
    case val2:
        ...
    default:
        ...
}

Go语言循环

for 循环

for init; condition; post{}
for condition{}
for{}

for 循环的range格式可以对slice、map、数组、字符串等进行迭代循环

for key,value := range oldMap{
    newMap[key] = vaule
}
package main
import "fmt"

func main() {
        strings := []string{"google", "runoob"}
        for i, s := range strings {
                fmt.Println(i, s)
        }


        numbers := [6]int{1, 2, 3, 5}
        for i,x:= range numbers {
                fmt.Printf("第 %d 位 x 的值 = %d\n", i,x)
        }  
}

Go 语言函数

func function_name( [parameter list] ) [return_types] {
   函数体
}

返回多个值

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

函数参数

默认情况下, Go语言使用的事值传递,即再调用过程中不会影响到实际参数

函数用法

  • 函数作为另外一个函数的实参

    package main
    
    import (
      "fmt"
      "math
    )
    
    func main(){
      getSquareRoot := func(x float64) float64{
        return math.Sqrt(x)
      }
      
      fmt.Println(getSquareRoot(9))
    }
  • 闭包
    Go 语言支持匿名函数,可作为闭包。 匿名函数是一个”内联“语句或表达式。匿名函数的优越性在于可以直接使用函数内部的变量,不必申明。
package main

import "fmt"

func getSequence() func() int {
  i:=0
  
  return func() int {
    i+=1
    return i
  }
}

func main(){
  nextNumber := getSequence()
  
  fmt.Println(nextNumber())
  fmt.Println(nextNumber())
  fmt.Println(nextNumber())
  
  nextNumber1 := getSequence()
  fmt.Println(nextNumber1())
  fmt.Println(nextNumber1())
  • 方法

Go语言变量作用域

作用域为以生命标识符所表示的常量、类型、变量、函数或包再源代码中的作用范围。

int -> 0
float32 -> 0
pointer -> nil

Go语言数组

声明数组

var variable_name [size] variable_type
var balance [10] float32

初始化数组

var balance = [5]float32{100.0,2.0.3.4,7.0,50.0}
balance := [5]float32{1000.0,2.0,3.4,6.0,50.0}

如果数组长度不确定,可以使用 ... 代替数组的长度,编译器会根据元素个数自行推断数组的长度:

var balance = [...]float32{1000.0, 2.0, 3.4, 7.0, 50.0}
或
balance := [...]float32{1000.0, 2.0, 3.4, 7.0, 50.0}

如果设置了数组的长度,我们还可以通过指定下标来初始化元素:

//  将索引为 1 和 3 的元素初始化
balance := [5]float32{1:2.0,3:7.0}

多维数组

var variable_name [size1][size2]...[sizeN] variable_type
var threadim [5][10][4]int
初始化二维数组
a := [3][4]int{
 {0,1,2,3},
 {4,5,6,7},
 {8,9,10,11},
}

注意:以上代码中倒数第二行的 } 必须要有逗号,因为最后一行的 } 不能单独一行,也可以写成这样:

a := [3][4]int{  
 {0, 1, 2, 3} ,   /*  第一行索引为 0 */
 {4, 5, 6, 7} ,   /*  第二行索引为 1 */
 {8, 9, 10, 11}}   /* 第三行索引为 2 */

向函数传递数组

方式一
设置形参大小

void myFunction(param [10]int)
{
...
}

方式二
未设置数组大小

void myFunction(param []int)
{
...
}

指针

如何使用指针:

  • 定义为指针变量
  • 为指针变量赋值
  • 访问指针变量中指向地址的值

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

nil和其他语言的null,None,NULL一样,代指一切零值或空值

package main
import "fmt"

func main(){
    var pte *int
    
    fmt.Printf("prt is:%x\n",ptr)
}

指针数组

var ptr [MAX]*int;
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++{
        prt[i] = &a[i]
    }
    for i=0;i<MAX;i++{
        fmt.Printf("a[%d] = %d\n",i,*ptr[i])
    }
}

指向指针的指针

image.png

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

var prt **int
package main 

import "fmt"

func main(){
    var a int
    var ptr *int
    var pptr **int
    
    a = 3000
    
    ptr = &a
    
    pptr = &ptr
    
    fmt.Printf("变量 a = %d\n",a)
    fmt.Printf("变量 *ptr = %d=n",*ptr)
    fmt.Printf("变量 **pptr = %d\n",**pptr)
}

Go语言指针作为函数参数

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 */
}

结构体

type struct_variable_type struct {
    member definition
    member definition
    ....
    member definition
}
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"})
}

如果要访问结构体成员,需要使用点号 . 操作符

结构体作为函数参数

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 = "www.runoob.com"
   Book1.subject = "Go 语言教程"
   Book1.book_id = 6495407

   /* book 2 描述 */
   Book2.title = "Python 教程"
   Book2.author = "www.runoob.com"
   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)
}

结构体指针

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 = "www.runoob.com"
   Book1.subject = "Go 语言教程"
   Book1.book_id = 6495407

   /* book 2 描述 */
   Book2.title = "Python 教程"
   Book2.author = "www.runoob.com"
   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)
}

Go 语言切片

Go 语言切片是对数组的抽象。
Go 数组的长度不可改变,在特定场景中这样的集合就不太适用,Go 中提供了一种灵活,功能强悍的内置类型切片("动态数组"),与数组相比切片的长度是不固定的,可以追加元素,在追加时可能使切片的容量增大。

定义切片

声明一个未指定大小的数组来定义切片:

var identifier []type

使用make()创建切片

var slice1 []type = make([]type, len)

slice1 := make([]type, len)

也可以指定容量

make([]T, length, capacity)

切片初始化

s :=[]int {1,2,3}
s := arr[:]
s := arr[startIndex:endIndex]
from startIndex -> endIndex-1
s :=make([]int, len, cap)

len()和cap()

切片是可索引的,并且可以有len()方法获取长度
切片提供了计算容量的方法

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

切片截取

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

append() 和 copy()

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

接口

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

}

错误处理

Go语言通过内置的错误接口提供了非常简单的错误处理机制
error类型是一个接口类型:

type error inteface{
    Error() string
}

我们可以再编码中通过时间error接口类型来生成错误信息
函数通常再最后的返回值中返回错误信息

func Sqrt(f float64) (float64, error) {
    if f < 0 {
        return 0, errors.New("math: square root of negative number")
    }
    // 实现
}
result, err:= Sqrt(-1)

if err != nil {
   fmt.Println(err)
}

Go并发

Go 语言支持并发,我们只需要通过 go 关键字来开启 goroutine 即可。

goroutine 是轻量级线程,goroutine 的调度是由 Golang 运行时进行管理的。

go func_name(parameter_list)

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
hello
world
world
hello
hello
world
world
hello

通道channel

通道(channel)是用来传递数据的一个数据结构。

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

ch <- v    // 把 v 发送到通道 ch
v := <-ch  // 从 ch 接收数据
           // 并把值赋给 v

声明一个通道很简单,我们使用chan关键字即可,通道在使用前必须先创建:

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

通道缓冲区

通道可以设置缓冲区, 通过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)
}

Go 遍历通道与关闭通道

Go通过range关键字来实现遍历读取到的数据,类似于数组切片。

v,ok := <-ch
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)
        }
}

沂狰码农
16 声望1 粉丝

CODE BETTER


« 上一篇
Kubernetes 笔记
下一篇 »
DOCKER