Go的基本介绍

特性

Go语言保证了既能到达静态编译语言的安全和性能,又达到了动态语言开发维护的高效率,使用一个表达式来形容Go语言:Go =C+ Python,说明Go语言既有c静态语言程
序的运行速度,又能达到Python动态语言的快速开发。

  1. 从c语言中继承了很多理念,包括表达式语法,控制结构,基础数据类型,调用参数传值,指
    针等等,也保留了和c语言一样的编保执行方式及弱化的指针

  2. 引入包的概念,用于组织程序结构,Go语言的一个文件都要归属于一个包,而不能单独存在。

  3. 垃圾回收机制,内存自动回收,不需开发人员管理

  4. 天然并发

    1. 从语言层面支持并发,实现简单
    2. goroutine,轻量级线程,可实现大并发处理,高效利用多核。
    3. 基于CPS并发模型(Communicating Sequential Processes )实现
  5. 吸收了管道通信机制,形成Go语言特有的管道channel,通过管道channel,可以实现不同的goroute之间的相互通信。

  6. 函数返回多个值

  7. 新的创新:比如切片、延时执行defer等

程序开发

1
2
3
4
5
6
//hello.go
package main
import "fmt"
func main() {
fmt.Println("hello,world!")
}

hello.go所在的包是main,在go中,文件必须归属一个包。

在命令行可以通过build命令进行编译,在main包下执行下面命令可以得到一个执行文件

1
go build hello.go

通过run也可以直接运行源码程序

1
go run hello.go

2种编译方式区别

如果我们先编译生成了可执行文件,那么我们可以将该可执行文件拷贝到没有go开发环境的机器上,仍然可以运行
如果我们是直接go fun go源代码,那么如果要在另外一个机器上这么运行,也需要go开发环境,否则无法执行。
在编译时,编译器会将程序运行依赖的库文件包含在可执行文件中,所以,可执行文件变大了很多。

go语言提供了大量的标准库:https://studygolang.com/pkgdoc

这些标准库在go语言的src目录下

GO语法

变量

变量的定义

  1. 指定变量类型,声明后若不赋值,使用默认值

    1
    var i int 
  2. 根据值自行判定变量类型(类型推导)

    1
    var num = 10.11
  3. 省略var,注意.:=左侧的变量不应该是已经声明过的,否则会导致编译错误

    1
    2
    //var string name = ""
    name := ""

一次性声明多个变量

1
2
3
4
5
6
7
var n1, n2 ,n3 int 
var n1, name, n3 = 100, "", 888
var (
n3 = 300
n4 = 900
name2 = "mary"
)

数据类型

基本数据类型

1
2
3
4
5
6
1.数值型
1.1 整数类型(int,int8,int16,int32,int64,uint,uint8,uint16,uint32,uint64,byte)
1.2 浮点型(float32,float64)默认float64
2.字符型(没有专门字符类型,使用byte保存单个字母字符)
3.布尔型(bool)
4.字符串(string)
字符变量

Go的字符串是由字节组成,对于ascii码的值可以保存到byte,大于255可以保存到int类型.

go语言字符使用utf-8,在Go中字符是一个整数,直接输出是该字符的码值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package main
import(
"fmt"
)
//演示golang中字符类型使用
func main() {
var c1 byte = 'a'
//当我们直接输出byte值,就是输出了的对应的字符的码值
fmt.Println("c1=", c1)
//如果我们希望输出对应字符,需要使用格式化输出
fmt.Printf("c1=%c c2=%c\n", c1,c2)
//var c3 byte =‘北’/ /overflow溢出
var c3 int ="北’
fmt.Printf("c3=%c %d", c3, c3)
}
字符串

Golang的字符串是不可变

字符串的表示形式

  • 双引号,识别转义。
  • 反引号,原生形式输出。

派生/复杂数据类型

1
2
3
4
5
6
7
8
1.指针(Pointer)
2.数组
3.结构体(struct)
4.管道(Channel)
5.函数(也是一种类型)
6.切片(slice)
7.接口(interface)
8.map

基本数据类型转换

需要显示转换

1
2
var i int32 = 100
var n1 = float32(i)
基本数据类型和string的转换
1
2
var num int = 90
str = fmt.Sprintf("%d", num1)

还可以使用strconv包的函数

1
2
3
4
str = strconv.FormatInt(int64(num3), 10); //把原类型转换为int64,以10进制转换

//浮点类型,f格式,10小数点后保留10位,float64
str = strconv.FormatFloat(num4, 'f', 10, 64);

指针

  • 基本数据类型,变量存的就是值,也叫值类型
  • 获取变量的地址,用&,比如: var nim int,获取num的地址:&num
  • 指针类型,变量存的是一个地址,这个地址指向的空间存的才是值比如: var ptr *int = &num
  • 获取指针类型所指向的值,使用: *,比如: var ptr int,使用ptr获取p指向的

使用细节

  • 值类型:基本数据类型int系列, float系列, bool, string、数组和结构体struct

    变量直接存储值,内存通常在栈中分配

  • 引用类型:指针、slice切片、map、管道chan、interface等都是引用类型

    变量存储的是一个地址,这个地址对应的空间才真正存储数据(值),内存通常在堆上分配,当没有任何变量引用这个地址时,该地址对应的数据空间就成为一个垃圾,由GC来回收。

标识符命名

  • 保持package的名字和目录保持一致,尽量采取有意义的包名,简短,有意义,不要和标准库不要冲突fmt。
  • 变量名、函数名、常量名:采用驼峰法。
  • 如果变量名、函数名、常量名首字母大写,则可以被其他的包访间;如果首字母小则只能在本包中使用(注:可以简单的理解成,首字母大写是公有的,首字母小写私有的)

模块化支持

1
go env -w GO111MODULE=off

go的每一个文件都是属于一个包的,也就是说go是以包的形式来管理文件和项目目录结构

  • 区分相同名字的函数、
  • 变量等标识符当程序文件很多时,可以很好的管理项目
  • 控制函数、变量等访问范围,即作用域
  1. 在给一个文件打包时,该包对应一个文件夹,比如这里的utils文件夫对应的包名就是utils,文件的包名通常和文件所在的文件夹名一致,—般为小写字母。

  2. 当一个文件要使用其它包函数或变量时,需要先引入对应的包

    1
    2
    3
    4
    5
    6
    import"包名"

    import (
    "包名"
    "包名"
    )

    package指令在文件第一行,然后是import指令。

    在import包时,路径从$GOPATH的 src下开始,不用带src ,编译器会自动从src下开始引入

  3. 为了让其它包的文件,可以访问到本包的函数,则该函数名的首字母需要大写,类似其它
    语言的public ,这样才能跨包访问。

  4. 主访问其它包函数时,其语法是包名.函数名

  5. 如果包名较长,Go支持给包取别名,注意细节。取别名后,原来的包名就不能使用了

    1
    2
    3
    import (
    别名 "包名"
    )
  6. 同一个包下不能有相同的函数名。

函数

简介

  • 函数的形参列表可以是多个,返回值列表也可以是多个。

  • 形参列表和返回值列表的数据类型可以是值类型和引用类型。

  • 函数的命名遵循标识符命名规范,首字母不能是数字,首字母大写该函数可以被本包文件和其它包文件使用,类似public ,首字母小写,只能被本包文件使用,其它包文件不能使用,类似private.

  • 函数中的变量是局部的,函数外不生效

  • 基本数据类型和数组默认都是值传递的,即进行值拷贝。在函数内修改,不会影响到原来的值。

  • 如果希望函数内的变量能修改函数外的变量,可以传入变量的地址&,函数内以指针的方式操作变量。从效果上看类似引用

  • Go函数不支持重载。空接口

  • 在Go中,函数也是一种数据类型,可以赋值给一个变量,则该变量就是一个函数类型的变量了。通过该变量可以对函数调用。

  • 函数既然是一种数据类型,因此在Go中,函数可以作为形参,并且调用!

  • 为了简化数据类型定义,Go支持自定义数据类型

    1
    2
    3
    //基本语法:type自定义数据类型名数据类型理解:相当于一个别名
    type mylnt int //这时mylnt就等价int来使用了.
    type mySum func (int, int) int//这时mySum就等价一个函数类型func(int, int) in
  • 支持对函数返回值命名

    1
    2
    3
    4
    5
    func cal(n1 int,n2 int)(sum int, sub int){
    sum = n1+n2
    sub = n1-n2
    return
    }
  • Go支持可变参数

    1
    2
    3
    4
    5
    6
    //支持0到多个参数
    func sum(args... int) sum int {}
    //支持1到多个参数
    func sum(n1 int, args... int) sum int {

    }

    args是slice,通过args[index]可以访间到各个值。

init函数

每一个源文件都可以包含一个init函数,该函数会在main函数执行前,被Go运行框架调用,也就是说init会在main函数前被调用。

  • 如果一个文件同时包含全局变量定义,init函数和main函数,则执行的流程是变量定义->init函数->main函数

  • init函数最主要的作用,就是完成一些初始化的工作

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    package main

    import (
    "fmt"
    )
    var age = test()
    func test() int {
    fmt.Println("test")
    return 90
    }

    func init(){
    fmt.Println("init")
    }

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

    test
    init
    main
  • 如果main.go和util.go定义了变量和init

    首先执行util.go的变量定义和init函数,然后执行main的变量定义和main函数。

闭包

闭包就是一个函数和与其相关的引用环境组合的一个整体(实体)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//累加器
func AddUpper() func(int) int{
var n int = 10
return func(x int) int {
n = n + x
return n
}
}
func main(){
f := AddUpper()
fmt.Println(f(1)) //11
fmt.Println(f(2)) //13
fmt.Println(f(3)) //16
}

返回的是一个匿名函数,但是这个匿名函数引用到函数外的n ,因此这个匿名函数就和n形成一个整体,构成闭包。

当我们反复的调用f函数时,因为n是初始化一次,因此每调用一次就进行累计。

我们要搞清楚闭包的关键,就是要分析出返回的函数它使用(引用)到哪些变量,因为函数和它引用到的变量共同构成闭包。l

1
2
3
编写一个函数makeSuffix(suffix string)可以接收一个文件后缀名(比如.jpg),并返回一个闭包
调用闭包,可以传入一个文件名,如果该文件名没有指定的后缀(比如.jpg) ,则返回文件名.jpg,如果已经有.jpg后缀,则返回原文件名。
strings.HasSuffix
1
2
3
4
5
6
7
8
9
10
11
12
13
func makeSuffix(suffix string) func (string) string{
return func(name string) string {
if !strings.HasSuffix(name,suffix){
return name + suffix
}
return name
}
}
func test2(){
f := makeSuffix(".jpg")
fmt.Println(f("winter"))
fmt.Println(f("bird.jpg"))
}

返回的匿名函数和makeSuffix (suffix string)的suffix变量组合成一个闭包,因为返回的函数引用到suffix这个变量
我们体会一下闭包的好处,如果使用传统的方法,也可以轻松实现这个功能,但是传统方法需要每次都传入后缀名,比如 .jpg ,而闭包因为可以保留上次引用的某个值,所以我们传入—次就可以反复使用。

defer

在函数中,程序员经常需要创建资源(比如,数据库连接、文件句柄、锁等),为了在函数执行完毕后,及时的释放资源,Go的设计者提供defer(延时机制)。

当执行到defer时,暂时不执行,会将defer后面的语句(包括数据)压入到独立的栈(defer栈)。当函数执行完毕后,再从defer栈,按照先入后出的方式出栈,执行

1
2
3
4
5
6
7
8
9
10
11
12
13
func sum(n1 int, n2 int)int {
defer fmt.Println("n1=", n1)
defer fmt.Println("n2=", n2)
n1++
n2++
res := n1 + n2
fmt.Println("res=", res)
return res
}
func main(){
fmt.Println(sum(10, 20))
}
//把数据也同时压入

变量作用域

  • 函数内部声明/定义的变量叫局部变量,作用域仅限于函数内部。
  • 函数外部声明/定义的变量叫全局变量,作用域在整个包都有效,如果其首字母为大写,则作用域在整个程序有效。
  • 如果变量是在一个代码块,比如 for / if中,那么这个变量的的作用域就在该代码块

常用函数

string
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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
package main

import (
"fmt"
"strconv"
"strings"
)

func used() {
//1.字符串长度
str := "hello北"
fmt.Println("str :", len(str))
//2.遍历中文
r := []rune(str)
for i := 0; i < len(r); i++ {
fmt.Printf("字符:=%c\n", r[i])
}
//3.字符串转整数,校验数据类型
n, err := strconv.Atoi("123")
if err != nil{
fmt.Println(err)
}else {
fmt.Println("转换的结果:", n)
}
//4. 整数转字符串
str2 := strconv.Itoa(123)
fmt.Printf("str=%v, str=%T\n", str2, str2)
//5.字符串转byte
var bytes = []byte("hello")
fmt.Printf("bytes=%v\n", bytes)
//6.byte转字符串
str = string([]byte{97,98,99})
fmt.Printf("str=%v\n", str)
//7. 10进制转2进制
str = strconv.FormatInt(123, 2)
fmt.Printf("123对应的二进制是=%v\n", str)
str = strconv.FormatInt(123, 16)
fmt.Printf("123对应的二进制是=%v\n", str)
//8. 是否包含
b := strings.Contains("seafood", "sea")
fmt.Printf("b=%v\n", b)
//9. 统计一个字符串有几个子串
num := strings.Count("ceheee", "e")
fmt.Printf("num=%v\n", num)
//10. 不区分大小写 ==区分字母大小写
b = strings.EqualFold("abc","ABC")
fmt.Printf("b=%v\n", b)
//11. 返回子串第一次出现的index值
index := strings.Index("NTL_abc","abc")
fmt.Printf("index=%v\n", index)
//12. 最后一次出现
index = strings.LastIndex("go golang","go")
fmt.Printf("index=%v\n", index)
//13.)将指定的子串替换成另外一个子串: n可以指定你希望替换几个,如果n=-1表示全部替换
str= strings.Replace("go go hello","go","go语言",1)
fmt.Printf("str=%v\n", str)
//14.按照指定的某个字符,为分割标识,将一个字符串拆分成字符串数组﹔
strArr := strings.Split("hello,world,ok",".")
fmt.Printf("strArr=%v\n", strArr)
//15.将字符串的字母进行大小写的转换: strings.ToLower("Go")
str = strings.ToUpper("Go")
fmt.Printf("str=%v\n", str)
}
时间
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
func usedTime(){
//1.获取当前时间
now := time.Now()

//2.通过now获取年日月
fmt.Printf("年=%v\n", now.Year())
fmt.Printf("月=%v\n", now.Month())
fmt.Printf("日=%v\n", now.Day())

//3, 格式化日期
dateStr := fmt.Sprintf("%d-%d-%d %d:%d:%d\n", now.Year(),now.Month(),
now.Day(),now.Hour(),now.Minute(),now.Second())
fmt.Printf("dateStr=%v",dateStr)
//只能用这个时间,根据这个时间然后可以输出对应的位置
fmt.Printf(now.Format("2006-01/02 15:04:05"))
fmt.Println()
fmt.Printf(now.Format("01")) //月
fmt.Println()
}

时间常量

1
2
3
4
5
6
7
8
9
10
11
12
const (
Nanosecond Duration = 1
Microsecond = 1000 * Nanosecond
Millisecond = 1000 * Microsecond
Second = 1000 * Millisecond
Minute = 60 * Second
Hour = 60 * Minute
)
100 * time.Millisecond

//休眠
func Sleep(d Duration)

获取unix时间戳

1
now.Unix(),nowUnixNano()
内置函数

Golang 设计者为了编程方便,提供了一些函数,这些函数可以直接使用,我们称为Go的内置函数。文档: https://studygolang.com/pkgdoc -> builtin

make:用来分配内存,主要用来分配引用类型,比如channel、map、slice。

错误处理

在默认情况下,当发生错误后(panic)程序就会退出(崩溃.)I

如果我们希望:当发生错误后,可以捕获到错误,并进行处理,保证程序可以继续执行。

Go语言追求简洁优雅,所以,Go语言不支持传统的try…catch…finally这种处理。

Go中引入的处理方式为: defer,panic, recover

这几个异常的使用场景可以这么简单描述:Go中可以抛出一个panic的异常,然后在defer中通过recover捕获这个异常,然后正常处理

1
2
3
4
5
6
7
8
9
10
11
12
13
func error(){
defer func() {
err := recover() //捕获异常
if err != nil { //说明捕获到错误
fmt.Println("err=",err)
}
}()
num1 := 10
num2 := 0
res := num1 / num2
fmt.Println("res=",res)
}
//err= runtime error: integer divide by zero
自定义错误

使用errors.New 和 panic内置函数。

1) errors.New(“错误说明””),会返回一个error类型的值,表示一个错误
2) panic内置函数,接收一个interface{}类型的值(也就是任何值了)作为参数。可以接收error类型的变量,输出错误信息,并退出程序.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func readConf(name string)(err error){
if name == "config.ini"{
return nil
}else {
return errors.New("读取文件错误...")
}
}

func test02(){
err := readConf("config.ini")
if err != nil {
panic(err)
}
fmt.Println("执行")
}