Go语言学习之路(一)

        从大学就听说了Go,但一直没有时间去了解这种高效简洁的语言,现在需要学习,就记录一下学习过程,以便日后查找。

        在之后还会继续了解Docker(基于Go进行开发),所以先记录一下Go的学习,先挖一个坑,到时候来填链接。

一、初识Go

1.1 背景

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

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

1.2 安装

Go 语言环境安装 | 菜鸟教程 (runoob.com)

1.3 结构

package main    //package main表示一个可独立执行的程序,每个 Go 应用程序都包含一个名为 main 的包。

import "fmt"    //告诉 Go 编译器这个程序需要使用 fmt 包(的函数,或其他元素),fmt 包实现了格式化 IO(输入/输出)的函数。



func main() {    //func main() 是程序开始执行的函数。main 函数是每一个可执行程序所必须包含的,一般来说都是在启动后第一个执行的函数(如果有 init() 函数则会先执行该函数)。
   /* 这是我的第一个简单的程序 */
   fmt.Println("Hello, World!")
}

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

2.需要注意的是 { 不能单独放在一行,所以以下代码在运行时会产生错误:

package main

import "fmt"

func main()  
{  // 错误,{ 不能在单独的行上
    fmt.Println("Hello, World!")
}

/*
结果:
command-line-arguments
./main.go:5:6: missing function body
./main.go:6:1: syntax error: unexpected semicolon or newline before {
*/

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

1.4 基础语法

1.和Python一样不需要以分号结尾,自动分行

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

3.Go 语言的字符串连接可以通过 + 实现:

package main
import "fmt"
func main() {
    fmt.Println("Google" + "Runoob")
}

//GoogleRunoob

4.Go 语言中使用 fmt.Sprintf 格式化字符串并赋值给新串:

package main

import (
    "fmt"
)

func main() {
   // %d 表示整型数字,%s 表示字符串
    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)
}

// Code = 123 & endDate = 2020-12-31

5.Go 程序的一般结构: basic_structure.go 

// 当前程序的包名
package main

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

// 常量定义
const PI = 3.14

// 全局变量的声明和赋值
var name = "gopher"

// 一般类型声明
type newType int

// 结构的声明
type gopher struct{}

// 接口的声明
type golang interface{}

// 由main函数作为程序入口点启动
func main() {
    Println("Hello World!")
}

Go 程序是通过 package 来组织的。

只有 package 名称为 main 的源码文件可以包含 main 函数。

一个可执行程序有且仅有一个 main 包。

通过 import 关键字来导入其他非 main 包。

可以通过 import 关键字单个导入:

import "fmt"
import "io"

也可以同时导入多个:

import (
    "fmt"
    "math"
)

例如:

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

使用 <PackageName>.<FunctionName> 调用:

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

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

// 调用的时候只需要Println(),而不需要fmt.Println()
import . "fmt"

前面加个点表示省略调用,那么调用该模块里面的函数,可以不用写模块名称了:

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

通过 const 关键字来进行常量的定义。

通过在函数体外部使用 var 关键字来进行全局变量的声明和赋值。

通过 type 关键字来进行结构(struct)和接口(interface)的声明。

通过 func 关键字来进行函数的声明。

可见性规则

Go语言中,使用大小写来决定该常量、变量、类型、接口、结构或函数是否可以被外部包所调用。

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

func getId() {}

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

func Printf() {}

6.Golang fmt 包

Print() 函数将参数列表 a 中的各个参数转换为字符串并写入到标准输出中。

非字符串参数之间会添加空格,返回写入的字节数。

func Print(a ...interface{}) (n int, err error)

Println() 函数功能类似 Print,只不过最后会添加一个换行符。

所有参数之间会添加空格,返回写入的字节数。

func Println(a ...interface{}) (n int, err error)

Printf() 函数将参数列表 a 填写到格式字符串 format 的占位符中。

填写后的结果写入到标准输出中,返回写入的字节数。

func Printf(format string, a ...interface{}) (n int, err error)

以下三个函数功能同上面三个函数,只不过将转换结果写入到 w 中。

func Fprint(w io.Writer, a ...interface{}) (n int, err error)
func Fprintln(w io.Writer, a ...interface{}) (n int, err error)
func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error)

以下三个函数功能同上面三个函数,只不过将转换结果以字符串形式返回。

func Sprint(a ...interface{}) string
func Sprintln(a ...interface{}) string
func Sprintf(format string, a ...interface{}) string

以下函数功能同 Sprintf() 函数,只不过结果字符串被包装成了 error 类型。

func Errorf(format string, a ...interface{}) error

实例:

func main() {
    fmt.Print("a", "b", 1, 2, 3, "c", "d", "\n")
    fmt.Println("a", "b", 1, 2, 3, "c", "d")
    fmt.Printf("ab %d %d %d cd\n", 1, 2, 3)
    // ab1 2 3cd
    // a b 1 2 3 c d
    // ab 1 2 3 cd

    if err := percent(30, 70, 90, 160); err != nil {
        fmt.Println(err)
    }
    // 30%
    // 70%
    // 90%
    // 数值 160 超出范围(100)
}

func percent(i ...int) error {
    for _, n := range i {
        if n > 100 {
            return fmt.Errorf("数值 %d 超出范围(100)", n)
        }
        fmt.Print(n, "%\n")
    }
    return nil
}

Formatter 由自定义类型实现,用于实现该类型的自定义格式化过程。

当格式化器需要格式化该类型的变量时,会调用其 Format 方法。

type Formatter interface {
    // f 用于获取占位符的旗标、宽度、精度等信息,也用于输出格式化的结果
    // c 是占位符中的动词
    Format(f State, c rune)
}

由格式化器(Print 之类的函数)实现,用于给自定义格式化过程提供信息:

type State interface {
    // Formatter 通过 Write 方法将格式化结果写入格式化器中,以便输出。
    Write(b []byte) (ret int, err error)
    // Formatter 通过 Width 方法获取占位符中的宽度信息及其是否被设置。
    Width() (wid int, ok bool)
    // Formatter 通过 Precision 方法获取占位符中的精度信息及其是否被设置。
    Precision() (prec int, ok bool)
    // Formatter 通过 Flag 方法获取占位符中的旗标[+- 0#]是否被设置。
    Flag(c int) bool
}

Stringer 由自定义类型实现,用于实现该类型的自定义格式化过程。

当格式化器需要输出该类型的字符串格式时就会调用其 String 方法。

type Stringer interface {
    String() string
}

Stringer 由自定义类型实现,用于实现该类型的自定义格式化过程。

当格式化器需要输出该类型的 Go 语法字符串(%#v)时就会调用其 String 方法。

type GoStringer interface {
    GoString() string
}

实例:

type Ustr string

func (us Ustr) String() string {
    return strings.ToUpper(string(us))
}

func (us Ustr) GoString() string {
    return `"` + strings.ToUpper(string(us)) + `"`
}

func (u Ustr) Format(f fmt.State, c rune) {
    write := func(s string) {
        f.Write([]byte(s))
    }
    switch c {
    case 'm', 'M':
        write("旗标:[")
        for s := "+- 0#"; len(s) > 0; s = s[1:] {
            if f.Flag(int(s[0])) {
                write(s[:1])
            }
        }
        write("]")
        if v, ok := f.Width(); ok {
            write(" | 宽度:" + strconv.FormatInt(int64(v), 10))
        }
        if v, ok := f.Precision(); ok {
            write(" | 精度:" + strconv.FormatInt(int64(v), 10))
        }
    case 's', 'v': // 如果使用 Format 函数,则必须自己处理所有格式,包括 %#v
        if c == 'v' && f.Flag('#') {
            write(u.GoString())
        } else {
            write(u.String())
        }
    default: // 如果使用 Format 函数,则必须自己处理默认输出
        write("无效格式:" + string(c))
    }
}

func main() {
    u := Ustr("Hello World!")
    // "-" 标记和 "0" 标记不能同时存在
    fmt.Printf("%-+ 0#8.5m\n", u) // 旗标:[+- #] | 宽度:8 | 精度:5
    fmt.Printf("%+ 0#8.5M\n", u)  // 旗标:[+ 0#] | 宽度:8 | 精度:5
    fmt.Println(u)                // HELLO WORLD!
    fmt.Printf("%s\n", u)         // HELLO WORLD!
    fmt.Printf("%#v\n", u)        // "HELLO WORLD!"
    fmt.Printf("%d\n", u)         // 无效格式:d
}

Scan 从标准输入中读取数据,并将数据用空白分割并解析后存入 a 提供的变量中(换行符会被当作空白处理),变量必须以指针传入。

当读到 EOF 或所有变量都填写完毕则停止扫描。

返回成功解析的参数数量。
func Scan(a ...interface{}) (n int, err error)

Scanln 和 Scan 类似,只不过遇到换行符就停止扫描。

func Scanln(a ...interface{}) (n int, err error)

Scanf 从标准输入中读取数据,并根据格式字符串 format 对数据进行解析,将解析结果存入参数 a 所提供的变量中,变量必须以指针传入。

输入端的换行符必须和 format 中的换行符相对应(如果格式字符串中有换行符,则输入端必须输入相应的换行符)。

占位符 %c 总是匹配下一个字符,包括空白,比如空格符、制表符、换行符。

返回成功解析的参数数量。

func Scanf(format string, a ...interface{}) (n int, err error)

以下三个函数功能同上面三个函数,只不过从 r 中读取数据。

func Fscan(r io.Reader, a ...interface{}) (n int, err error)
func Fscanln(r io.Reader, a ...interface{}) (n int, err error)
func Fscanf(r io.Reader, format string, a ...interface{}) (n int, err error)

以下三个函数功能同上面三个函数,只不过从 str 中读取数据。

func Sscan(str string, a ...interface{}) (n int, err error)
func Sscanln(str string, a ...interface{}) (n int, err error)
func Sscanf(str string, format string, a ...interface{}) (n int, err error)

实例:

// 对于 Scan 而言,回车视为空白
func main() {
    a, b, c := "", 0, false
    fmt.Scan(&a, &b, &c)
    fmt.Println(a, b, c)
    // 在终端执行后,输入 abc 1 回车 true 回车
    // 结果 abc 1 true
}

// 对于 Scanln 而言,回车结束扫描
func main() {
    a, b, c := "", 0, false
    fmt.Scanln(&a, &b, &c)
    fmt.Println(a, b, c)
    // 在终端执行后,输入 abc 1 true 回车
    // 结果 abc 1 true
}

// 格式字符串可以指定宽度
func main() {
    a, b, c := "", 0, false
    fmt.Scanf("%4s%d%t", &a, &b, &c)
    fmt.Println(a, b, c)
    // 在终端执行后,输入 1234567true 回车
    // 结果 1234 567 true
}

Scanner 由自定义类型实现,用于实现该类型的自定义扫描过程。

当扫描器需要解析该类型的数据时,会调用其 Scan 方法。

type Scanner interface {
    // state 用于获取占位符中的宽度信息,也用于从扫描器中读取数据进行解析。
    // verb 是占位符中的动词
    Scan(state ScanState, verb rune) error
}

由扫描器(Scan 之类的函数)实现,用于给自定义扫描过程提供数据和信息。

type ScanState interface {
    // ReadRune 从扫描器中读取一个字符,如果用在 Scanln 类的扫描器中,
    // 则该方法会在读到第一个换行符之后或读到指定宽度之后返回 EOF。
    // 返回“读取的字符”和“字符编码所占用的字节数”
    ReadRune() (r rune, size int, err error)
    // UnreadRune 撤消最后一次的 ReadRune 操作,
    // 使下次的 ReadRune 操作得到与前一次 ReadRune 相同的结果。
    UnreadRune() error
    // SkipSpace 为 Scan 方法提供跳过开头空白的能力。
    // 根据扫描器的不同(Scan 或 Scanln)决定是否跳过换行符。
    SkipSpace()
    // Token 用于从扫描器中读取符合要求的字符串,
    // Token 从扫描器中读取连续的符合 f(c) 的字符 c,准备解析。
    // 如果 f 为 nil,则使用 !unicode.IsSpace(c) 代替 f(c)。
    // skipSpace:是否跳过开头的连续空白。返回读取到的数据。
    // 注意:token 指向共享的数据,下次的 Token 操作可能会覆盖本次的结果。
    Token(skipSpace bool, f func(rune) bool) (token []byte, err error)
    // Width 返回占位符中的宽度值以及宽度值是否被设置
    Width() (wid int, ok bool)
    // 因为上面实现了 ReadRune 方法,所以 Read 方法永远不应该被调用。
    // 一个好的 ScanState 应该让 Read 直接返回相应的错误信息。
    Read(buf []byte) (n int, err error)
}

实例:

type Ustr string

func (u *Ustr) Scan(state fmt.ScanState, verb rune) (err error) {
    var s []byte
    switch verb {
    case 'S':
        s, err = state.Token(true, func(c rune) bool { return 'A' <= c && c <= 'Z' })
        if err != nil {
            return
        }
    case 's', 'v':
        s, err = state.Token(true, func(c rune) bool { return 'a' <= c && c <= 'z' })
        if err != nil {
            return
        }
    default:
        return fmt.Errorf("无效格式:%c", verb)
    }
    *u = Ustr(s)
    return nil
}

func main() {
    var a, b, c, d, e Ustr
    n, err := fmt.Scanf("%3S%S%3s%2v%x", &a, &b, &c, &d, &e)
    fmt.Println(a, b, c, d, e)
    fmt.Println(n, err)
    // 在终端执行后,输入 ABCDEFGabcdefg 回车
    // 结果:
    // ABC DEFG abc de
    // 4 无效格式:x
}

7.Go 语言的包引入一般为: 项目名/包名

import "test/controllers"

方法的调用为: 包名.方法名()

controllers.Test()

1.5 数据类型

1.布尔型
布尔型的值只可以是常量 true 或者 false。一个简单的例子:var b bool = true。

2.数字类型
整型 int 和浮点型 float32、float64,Go 语言支持整型和浮点型数字,并且支持复数,其中位的运算采用补码。

3.派生类型:
包括:(a) 指针类型(Pointer)(b) 数组类型(c) 结构化类型(struct)(d) Channel 类型(e) 函数类型(f) 切片类型(g) 接口类型(interface)(h) Map 类型

4.nil的理解:

在go语言中,nil可以代表下面这些类型的零值:

  • 指针类型(包括unsafe中的)
  • map类型
  • slice类型
  • function类型
  • channel类型
  • interface类型

但是要注意的一点就是,nil是指申明了变量,但没有赋值:在Go语言中,如果你声明了一个变量但是没有对它进行赋值操作,那么这个变量就会有一个类型的默认零值。这是每种类型对应的零值。

//Example1:
func main()  {
	var a []int
	if a==nil{
		fmt.Println(123)
	}
	fmt.Println(a)
// a为nil
// []
}


//Example2:
func main()  {
	a :=[]int{}
	if a==nil{
		fmt.Println("a为nil")
	}else {
		fmt.Println("a不是nil")
	}
	fmt.Println(a)
//a不是nil
//[]
}

/*
区别两者之间的关系:
在第一个代码块中,a声明了变量但没有进行赋值,因此为nil
在第二个代码块中,a声明了变量并进行了初始化操作,只不过里面是空的,因此就行了赋值,不为nil
*/

(2条消息) Go语言中nil的理解_一只安慕嘻的博客-CSDN博客_go nil

6.error:

GO语言中err接口及defer延迟异常处理分析_Golang_脚本之家 (jb51.net)

Go 语言数据类型 | 菜鸟教程 (runoob.com)

1.6 变量

Go 语言变量名由字母、数字、下划线组成,其中首个字符不能为数字。

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

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

注意:

1.第一种,指定变量类型,如果没有初始化,则变量默认为零值

数值类型(包括complex64/128)为 0

布尔类型为 false

字符串为 ""(空字符串)

以下几种类型为 nil

var a *int
var a []int
var a map[string] int
var a chan int
var a func(string) int
var a error // error 是接口

2.第二种,根据值自行判定变量类型

package main
import "fmt"
func main() {
    var d = true
    fmt.Println(d)
}

3.第三种,如果变量已经使用 var 声明过了,再使用 := 声明变量,就产生编译错误

intVal := 1 相等于:

var intVal

int intVal =1


可以将 var f string = "Runoob" 简写为 f := "Runoob":

4.多变量声明

//类型相同多个变量, 非全局变量
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
)

5.我们知道可以在变量的初始化时省略变量的类型而由系统自动推断,声明语句写上 var 关键字其实是显得有些多余了,因此我们可以将它们简写为 a := 50 或 b := false。

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

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

如果你声明了一个局部变量却没有在相同的代码块中使用它,同样会得到编译错误,例如下面这个例子当中的变量 a:(局部声明必须使用

6.多变量可以在同一行进行赋值

var a, b int
var c string
a, b, c = 5, 7, "abc" 
或者
a, b, c := 5, 7, "abc"

7.并行赋值:

a, b, c := 5, 7, "abc"

右边的这些值以相同的顺序赋值给左边的变量,所以 a 的值是 5, b 的值是 7,c 的值是 "abc"。

这被称为 并行 或 同时 赋值。

如果你想要交换两个变量的值,则可以简单地使用 a, b = b, a,两个变量的类型必须是相同。

空白标识符 _ 也被用于抛弃值,如值 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
}

//2 str

1.7 常量

1.显式类型定义: const b string = "abc"

隐式类型定义: const b = "abc"

多个相同类型的声明可以简写为:const c_name1, c_name2 = value1, value2

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

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

iota 可以被用作枚举值:

const (
    a = iota
    b = iota
    c = iota
)

第一个 iota 等于 0,每当 iota 在新的一行被使用时,它的值都会自动加 1;所以 a=0, b=1, c=2 可以简写为如下形式:

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

//0 1 2 ha ha 100 100 7 8

1.8 占位符

1)普通占位符

Go 语言的%d,%p,%v等占位符的使用_后端码匠的博客-CSDN博客_go 百分号

二、知识点总结

1. 使用函数的时候,不知道参数个数可以使用小数点代替

func percent(i ...int) error {
    for _, n := range i {
        if n > 100 {
            return fmt.Errorf("数值 %d 超出范围(100)", n)
        }
        fmt.Print(n, "%\n")
    }
    return nil
}

2.使用_可以将值丢掉

var a, b = 1, 2
_, b = 1, 2

3.理解error的用法

package main
import (
    "errors"
    "fmt"
)
func main() {
    //接收错误信息和正确信息
    result,err := test(5,0)
    //加了判断,如果没有错误err=nil
    if err!=nil{
        fmt.Println(err)
		fmt.Println(result)
    }else {
		fmt.Println(err)
        fmt.Println(result)
    }
}
//b为0时抛出异常
func test(a,b int) (result int, err error) {  //返回错误信息
    err = nil
    if b==0{
        err =errors.New("b不能为0")
    }else {
        result = a/b
    }
    return
}

/*
b不能为0
0
*/

猜你喜欢

转载自blog.csdn.net/qq_43681154/article/details/127365065