Go语言基础

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Go语言基础相关的知识,希望对你有一定的参考价值。

一 简介

1 定义

Go 是一种开源的程序设计语言,它意在使得人们能够方便地构建简单,可靠,高效的软件。

2 产生原因

1 计算机硬件技术更新频繁,性能提高很快,目前主流编程语言发展落后,不能合理利用多核CPU优势来提高系统性能
2 软件设计复杂度高,维护成本大
3 C/C++编译速度过慢,需要解决提高速度

3 历史

Go是从2007年末由 Robert griesemer,rob pike,ken thompson 主持开发,后来加入了Ian Lance Taylor,Russ Cox,并最终于2009年11月开源,在2012年早些时候发布了Go 1稳定版本,现在Go完全开放。2015 年发布了Go 1.5 版本,完全移除了C代码

4 go 语言特点

1 继承C很多理念,包括表达式语法,控制结构,基础数据类型,调用参数传值,指针等

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

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

4 天然并发
原因:
A 从语言层面支持病啊,实现简单
B goroutine轻量级线程,可实现大并发处理,高效利用多核特性
C 基于CPS并发模型实现

5 吸收了管道通信机制,形成Go语言特有的管道channel,通过管道,实现了不同goroute之间的互相通信

6 函数返回多个值

7 新的创新,切片,延时执行等特性

5 学习方向

1 区块链开放,其核心是去中心化,公开透明


2 go服务器端/游戏开发软件工程师,其核心是支撑主站后台流量(排序,推荐,搜索等),提供负载均衡,cache缓存,容错,按条件分流等特点,运行统计指标


3 golang分布式/云计算工程师 ,其核心是CDN调度系统,分发系统

二 环境安装

1 Go语言环境安装

安装环境可见菜鸟教程,此处介绍也较为详细
https://www.runoob.com/go/go-environment.html

2 Go开发环境安装

1 简介

安装Go语言开发包后,可选择安装集成开发环境(Intergrated Development Environment,IDE)或者编译器来提高开发效率

集成开发环境推荐使用Jetbrains 公司的Goland,其公司共有多种开发环境和工具,其次可使用微软公司的visual studio code,其是一个高定制化的轻量编译器,能够根据自己需要制定Go语言的开发流程

2 集成环境 jebrains Goland

Goland 是Jetbrains公司在IntelliJ平台上开发的Go语言整合工具开发继承环境,提供Go语言的编辑,编译,调试,工程管理,重构等各种功能,其地址为:https://www.jetbrains.com/go/

安装过程本篇不在赘述。主要针对安装完成设置进行相关说明

1 设置GOROOT

GOROOT 是Go语言安装路径,其GoLand会自动识别,可手动设置GOROOT

技术图片

此处可进行编辑和修改其目录

技术图片

2 GOPATH

GOPATH 是Go语言编译时参考的工作路径,其默认是空,可选择对应的目录进行设置,默认会读取系统的GOPATH ,可增加多个

技术图片

技术图片

3 方便定义功能的编译器Visual Studio Code

Virsual Studio Code(简称VS Code)是一款由微软公司开发的,能运行再Mac OS X、Windows和Linux 上的跨平台开源代码编译器。

VS Code 使用JSON 格式的配置文件进行所有功能和特性的配置,VS Code 可以通过扩展程序为编译器实现语言高亮,参数提示,编译,调试,文档生成等各种功能。

1 切换语言

可见我之前的博客
https://blog.51cto.com/11233559/2442455

2 安装Go语言扩展

技术图片

搜索Go,找到Rich Go language support for Visual Studio Code 的字样扩展,单击安装即可

3 设置GOPATH

选择"文件----首选项----设置",在用户-----扩展----Go中进行修改和编辑相关所需的参数

技术图片

三 Go语言基本语法与使用

1 基本代码

package main //定义该文件所属的包,每个文件都必须属于一个包,一般这个包时该文件所在的目录名称

import "fmt" //引入库fmt,可用于打印和格式化

func main() { //定义main函数,一般的文件执行的入口函数都是main
    fmt.Println("hello world!") // 打印结果,使用fmt.Println进行打印 
}

2执行流程

1 简介

1 如果先编译成可执行文件,则可将可执行文件拷贝到没有go开发环境的机器上,仍可运行,但其必须和源文件使用同一种系统


2 如果直接使用go run,则如果需要在另一台设备上运行,则需要该设备安装go环境


3 在编译时,编译器会将程序运行以来的库文件包含在可执行文件中,所以,可执行文件变得很大

2 执行

1 开发工具执行

技术图片

2 命令行执行

技术图片

3 编译

1 有了xxx.go 源文件,通过编译器将其生成可识别的二进制码文件
2 在源文件目录下,通过 go build 进行编译,默认其名称是xxx.exe 或 其他(Linux中无后缀,Windows 中是 *.exe)
3 若程序没提示错误,则会生成可执行文件
4 程序错误,则会报告对应的行数

1 开发工具编译

技术图片

技术图片

在此执行run,即可看到生成的go_build_test_go.exe可执行文件

技术图片

技术图片

2 命令行编译

技术图片

其名称和源码文件相同,只是后缀发生了变化

3 go 程序开发注意事项

1 go 源文件必须是以".go"为扩展名
2 go 应用程序的执行入口时main()函数
3 go 语言严格区分大小写
4 go 方法是由一条条语句构成,每个语句不需要分号
5 go 编译器时一行行编译,一行只能写一个语句,若要写多个,则通过; 分号隔开
6 go语言定义变量或者import的包若没用到,则编译代码不能通过
7 go语言中大括号是成对出现的,缺一不可

4 注释

1 作用

提高代码阅读可读性

2 类型分类

1 单行注释
2 多行注释

3 代码示例

package main //定义该文件所属的包,每个文件都必须属于一个包,一般这个包时该文件所在的目录名称

import "fmt" //引入库fmt,可用于打印和格式化

// 这是一个程序执行入口

/*
这是一个多行注释,可用于说明较多的程序问题
*/
func main() { //定义main函数,一般的文件执行的入口函数都是main
    fmt.Println("hello world!") // 打印结果,使用fmt.Println进行打印
}

4 注意事项

1 注释的不会被执行
2 注释块中不能被嵌套
3 官方推荐使用单行注释

5 Go 开发常见问题和解决问题

1 使用一次tab 操作,实现缩进,默认整体向右移动,使用shift+tab 整体向左移动
2 或者使用gofmt来进行格式化
3 运算符两边习惯性各加上一个空格
4 一般的,大括号必须位于上一行的结尾而不是下一行的开始


如下

技术图片

四 Go 语言变量

1 简介

变量相当于内存中的一个数据存储空间的表示,可以把变量值看做是一个房间的门牌号,通过这个门牌号可找到这个房间,及通过变量名能找到内存中访问的变量值


变量的功能是存储用户数据的,不同的逻辑有不同的对象类型,也就是不同变量类型,经过半个多世纪的发展,编程语言已经形成一整套固定的类型,这些类型在编程语言中基本是相通的,常见变量的数据类型有:整型,浮点型,布尔型,结构体等。


Go 语言作为C语言家族代表,在C语言的定义方法和类型上进行了优化和调整,更加灵活易学。

Go语言的每一个变量都拥有自己的类型,必须经过声明才能开始使用

2 变量的基本使用步骤

1 声明变量

标准格式
var 变量名 变量类型

// 变量声明,此处声明的变量若不使用,则编译和运行是不能通过的
var a int  // 定义整形数据a
var b string  // 定义字符型数据b
var c float32  //定义浮点类型数据c
)

批量定义格式

// 变量声明,此处声明的变量若不使用,则编译和运行是不能通过的
var (
    a int         // 定义整形数据a
    b string      // 定义字符型数据b
    c float32     //定义浮点类型数据c
)

2 赋值变量和使用变量

Go 语言在声明变量时,自动对变量对应的内存区域进行初始化操作,每个变量会初始化成其类型的默认值,如


整形和浮点型变量的默认值为0
字符串变量的默认值为空字符串
布尔便来给你默认为bool
切片,函数,指针变量的默认类型为nil


在C 语言中,变量在声明时,并不对变量对应的内存区域进行清理操作,此时,变量值可能是完全不可预期的结果。

package main

import "fmt"

// 变量声明,此处声明的变量若不使用,则编译和运行是不能通过的
var (
    a int     // 定义整形数据a
    b string  // 定义字符型数据b
    c float32 //定义浮点类型数据c
)

func main() { //定义main函数,一般的文件执行的入口函数都是main
    fmt.Println(a, b, c) // 只定义,其会自动赋予初始的默认值
    // 变量的赋值
    a = 10
    b = "a"
    c = 10.10
    //变量使用
    fmt.Println(a, b, c) // 打印结果,使用fmt.Println进行打印
}

结果如下

技术图片

3 初始化变量

标准格式

var 变量名 类型= 表达式

package main

import "fmt"

// 变量声明,此处声明的变量若不使用,则编译和运行是不能通过的
var (
    a int     = 10    // 定义整形数据a并进行初始化
    b string  = "a"   // 定义字符型数据b并进行初始化 
    c float32 = 10.10 //定义浮点类型数据c并进行初始化
)

func main() { //定义main函数,一般的文件执行的入口函数都是main
    fmt.Println(a, b, c) // 只定义,其会自动赋予初始的默认值
    // 变量的赋值
}

编译器推导类型的格式

package main

import "fmt"

// 变量声明,此处声明的变量若不使用,则编译和运行是不能通过的
var (
    a = 10    // 定义整形数据a并进行初始化
    b = "a"   // 定义字符型数据b
    c = 10.10 //定义浮点类型数据c
)

func main() { //定义main函数,一般的文件执行的入口函数都是main
    fmt.Println(a, b, c) // 只定义,其会自动赋予初始的默认值
    // 变量的赋值
}

短变量声明并初始化

package main

import "fmt"

func main() { //定义main函数,一般的文件执行的入口函数都是main

    // 变量声明,此处声明的变量若不使用,则编译和运行是不能通过的
    a := 10
    b := "a"
    c := 10.10
    fmt.Println(a, b, c) // 只定义,其会自动赋予初始的默认值
    // 变量的赋值
}

4 多变量同时赋值

package main

import "fmt"

func main() { //定义main函数,一般的文件执行的入口函数都是main

    // 变量声明,此处声明的变量若不使用,则编译和运行是不能通过的
    a, b, c := 10, "a", 10.10
    fmt.Println(a, b, c) // 只定义,其会自动赋予初始的默认值
    // 变量的赋值
}

5 交换赋值 (起初的内存资源非常紧缺,计算机大牛通过一些算法来避免使用中间变量)

package main

import "fmt"

func main() { //定义main函数,一般的文件执行的入口函数都是main
    var a int = 100
    var b int = 200

    // 方法1

    a = a ^ b
    b = b ^ a
    a = a ^ b
    fmt.Println(a, b)

    //方法2

    a = a + b
    b = a - b
    a = a - b
    fmt.Println(a, b)
    //方法3
    a, b = b, a
    fmt.Println(a, b)
}

结果如下

技术图片

6 匿名变量

在使用多重赋值时,若不需要左值中接受变量,可使用匿名变量,匿名变量的表现是一个"_"下划线,使用匿名变量时,只需要在变量声明的地方使用下划线替换即可

package main

import "fmt"

func Test() (int, int) {  //其返回两个整形数据
    return 100, 200
}

func main() { //定义main函数,一般的文件执行的入口函数都是main
    a, _ := Test()
    _, b := Test()
    fmt.Println(a, b)
}

结果如下

技术图片

7 变量使用基本总结

1 变量表示内存中的一个存储区域,该区域内的数值可以在同一种类型范围内不断变化
2 该区域有自己的名称(变量名)和类型(数据类型)
3 golang变量的三种方式
A 指定变量类型,声明后,不赋值,使用默认值
B 根据值自行判断变量类型
C 使用:= 进行声明,左侧的变量不应该是已经声明的变量,否则会导致编译错误
4 变量在其作用域内不能重名
5 变量= 变量名+数值类型
6 go 变量若没给初始值,则编译器会使用默认值

3 变量数据类型

1 简介

Go 语言中有丰富的数据类型,除了基本的整形,浮点型,布尔型,字符串外,还有切片,结构体,函数,map,通道(channel)等,Go语言的基本类型和其他语言大体相同,切片类型有着指针的便利性,但比指针安全,很多高级语言都配有切片进行安全和高效率的内存操作。


复杂的数据类型,如结构体,函数,map和切片将会在后面介绍,此处不再介绍。

2 整形

基本两种分类,一种是按长度分类,另一种是按有无符号分类
1 按长度分为为int8 uint8 占用一个字节,int16 uint16 占用两个字节 int32 uint32占用四个字节 int64 和 uint64 占用八个字节
2 按有无符号分为有符号和无符号,有符号长度分为 int8 int16 int32 int64,无符号分为 uint8 uint16 uint32 uint64


使用细节
1 golang整形类型分为有符号和无符号,int 和uint 的大小和系统有关,32位系统int默认是 -2^31-1 到 2^31-1,64位系统-2^63-1到2^63-1 Uint默认32 位系统是0-2^32-1,64位系统是0-2^64-1
2 golang的整形默认声明为int 类型
3 golang程序中整形变量在使用时,遵循保小不保大原则,及:在保证程序正确运行时,应尽量使用占用空间小的数据类型
4 bit:计算机中的最小存储单元,byte 计算机中最基本的存储单元
5 uint8 也就是byte型,int16 对应C语言中的short型,int64对应C语言中的long型。

3 浮点型

GO 语言支持两种浮点型数:float32和float64,这两种浮点型数据遵循IEEE 754 标准

单精度 4 字节 float32 -3.403E38 到 3.403E38
双精度 8 字节 float64 -1.798E308 到 1.798E308


注意:
1 关于浮点数字在机器中存放形式是浮点数=符号位+指数位+尾数位
2 尾部部分可能丢失,造成精度丢失


浮点数使用细节
1 golang浮点类型有固定的范围和字段长度,不受具体的操作系统影响
2 golang的浮点数类型默认是float64
3 浮点型常量有两种表现形式
A 十进制表示,如 5.12 .512
B 科学计数法 5.12E2=5.12*10^2,5.12E-2=5.12/10^2
4 通常情况下,应该是float64,因为其比float32更精确


实例如下

package main

import (
    "fmt"
    "math"
)

func main() { //定义main函数,一般的文件执行的入口函数都是main
    fmt.Printf("%f
", math.Pi)   //格式化输出为浮点型
    fmt.Printf("%.2f
", math.Pi) // 指定小数点后面的位数
    fmt.Printf("%10f
", math.Pi) // 指定其输出长度
}

4 字符类型

基本介绍
golang中没有专门的字符类型,若需要存储单个字符(字母),一般使用byte来保存


字符串是一串固定长度的字符链接起来的字符序列,golang的字符是由单个字节链接起来的,也就是传统的字符串是由字符组成,而golang是由字节组成

package main

import (
    "fmt"
    "unsafe"
)

func main() { //定义main函数,一般的文件执行的入口函数都是main
    var c1 byte = ‘a‘ //    其必须是单引号,若是双引号,则是字符串,
    var c2 byte = ‘a‘
    c3 := ‘爱‘ //此处默认类型是Int
    c4 := 98
    c5 := "a"
    c6 := "abcd"
    c7 := ‘	‘                                                                        //特殊字符
    c8 := c1 + c2                                                                     //字符运算
    fmt.Println(c1, c2, c3, c4, c5, c6, c7, c8)                                       //打印值
    fmt.Printf("%T,%T
", c1, c5)                                                     //打印类型,通过"%T"来获取
    fmt.Printf("%v,%v,%v
", unsafe.Sizeof(c1), unsafe.Sizeof(c5), unsafe.Sizeof(c6)) //打印占用字节大小
    fmt.Printf("%c,%c,%c,%c,%c
", c1, c2, c3, c4, c8)                                //格式化输出原有的值
}

结果如下

技术图片

使用细节
1 字符类型常量是用单引号‘‘括起来的单个字符,""双引号括起来的叫字符串
2 golang中允许使用转义字符""来将后面的字符变为特殊字符
3 go语言使用utf-8编码,基本不存在乱码问题,字符是四字节,字符串是16字节大小
4 在go语言中,字符本质是一个整数,直接输出时,是该字符对应的utf-8编码对应的码值
5 可以直接给某个变量赋值一个数字,然后格式化输出使用%c,即可输出对应的Unicode字符
6 字符类型是可以进行运算的,相当于一个整数


相关说明
1 如果我们保存的字符ASCII表,如[0-9,a-z,A-Z],则可直接保存在byte中
2 若保存的字符对应码值大于255,则可使用int保存
3 若使用字符串输出,则使用%c


实例如下

package main

import "fmt"

func main() { //定义main函数,一般的文件执行的入口函数都是main
    var c1 byte = ‘a‘
    var c2 int = ‘中‘
    var c3 byte = 97
    fmt.Printf("%c,%c,%c", c1, c2, c3)
}

字符类型的本质

1 字符型存储到计算机中,需要将字符对应的码值找出来其顺序如下

存储:字符-----对应码值----二进制---存储
读取:二进制----码值---字符---读取

2 字符和码值的对应关系是通过字符编码表来决定的

3 go语言的编码都为utf8,不会产生乱码情况

5 字符串

字符串就是一串固定长度的字符链接起来的字符序列,go语言的字符串就是由单个字符链接

package main

import "fmt"

func main() { //定义main函数,一般的文件执行的入口函数都是main
    c1 := "1234"
    c1 = "6789"
    c2 := "124234234
13423542345" // 默认会转义

    c3 := `124234234
13423542345` //此处不会转义
,原生输出
    fmt.Println(c1, c2, c3)
}

字符串转义符

Go 语言的字符串常见转义符包含回车、换行、单双引号、指标符等

转义符 含义
回车符
换行符(直接跳到下一行的同列位置)
制表符
单引号
" 双引号
反斜杠
package main

import "fmt"

func main() { //定义main函数,一般的文件执行的入口函数都是main
    fmt.Println("str:="c:\Go\bin\go.exe"")
}

技术图片

**使用和注意事项

1 golang语言的字符串的字节使用utf-8编码表示Unicode文本,这样golang统一使用utf-8编码,不会产生乱码
2 字符串,不能修改,字符串是不可变数据类型,及不能通过修改某个字符来修改,整体是可以进行赋值的
3 字符串的两种表示形式
A 双引号,会识别转义字符
B 反引号,以字符串原生形式输出,包括换行符和特殊字符,可以防止输出源码等效果 **

6 布尔型

布尔型数据在Go语言中以bool 类型进行声明,布尔型数据只有true和false两个值,布尔类型占用一个字节,适用于逻辑运算,一般适用于流程控制。

package main

import (
    "fmt"
    "unsafe"
)

func main() { //定义main函数,一般的文件执行的入口函数都是main

    c1 := true
    var c2 bool //默认为false
    fmt.Printf("%v,%v,%v,%v", c1, c2, unsafe.Sizeof(c1), unsafe.Sizeof(c2))
}

结果如下

技术图片

细节说明

不可以是0或者非0的整数替代false或true,这点和C语言不同

4 基本数据类型转换

1 其他数据类型转换为string

介绍
golang和java/C 不同,go在不同类型的变量之间赋值需要显式转换,也就是golang中的数据类型不能自动转换


基本语法
表达式T(v),将v转换为类型T
T:就是数据类型,如int32,int64,float32等
v:就是需要转换的变量

package main

import (
    "fmt"
)

func main() { //定义main函数,一般的文件执行的入口函数都是main
    var i int = 32
    var j float32 = float32(i) //类型转换如下
    var x int32 = 1024
    var z int8 = int8(x) //其值会变为0
    fmt.Printf("类型分别为:%T   %T  %T  %T   %v
", i, j, x, z, z)
}

技术图片

细节说明

Go中,数据类型的转换是可以从范围小的--->表示范围大的,也可以从范围大的-->范围小的,被转换的是变量存储的数值,变量本身的数据类型并没有发生变化

在转换中,若将int64转换成int8,编译器本身不会报错,只是转换结果按溢出处理

2基本数据类型和string类型的转换

1 介绍

在程序开发中,我们经常需要将基本数据类型转换成string类型,或者将string类型转换成基本数据类型

2 基本数据类型转换成string数据类型

方式一
fmt.Sprintf("%参数",表达式) [建议使用]

方式二
使用strconv 包函数

package main

import (
    "fmt"
    "strconv"
)

func main() { //定义main函数,一般的文件执行的入口函数都是main
    st1 := fmt.Sprintf("%d", 11234) //%d为原有类型

    st2 := fmt.Sprintf("%t", false)
    fmt.Printf("转换值为%v,转换后的类型为%T
", st1, st1)
    fmt.Printf("转换值为%v,转换后的类型为%T
", st2, st2)
    st3 := strconv.FormatBool(true)
    fmt.Printf("转换值为%v,转换后的类型为%T
", st3, st3)
    st4 := strconv.FormatFloat(10.000, ‘f‘, -1, 64)
    /*
        func FormatFloat(f float64, fmt byte, prec, bitSize int) string
        f:表示要转换的浮点型的数,bitSize表示f的来源类型(32:float32、64:float64),会据此进行舍入。
        fmt表示格式:‘f‘(-ddd.dddd)、‘b‘(-ddddp±ddd,指数为二进制)、‘e‘(-d.dddde±dd,十进制指数)、‘E‘(-d.ddddE±dd,十进制指数)、‘g‘(指数很大时用‘e‘格式,否则‘f‘格式)、‘G‘(指数很大时用‘E‘格式,否则‘f‘格式)。
        prec控制精度(排除指数部分):对‘f‘、‘e‘、‘E‘,它表示小数点后的数字个数;对‘g‘、‘G‘,它控制总的数字个数。如果prec 为-1,则代表使用最少数量的、但又必需的数字来表示f。
    */
    fmt.Printf("转换值为%v,转换后的类型为%T
", st4, st4)
}

结果如下

技术图片

注意事项

将string转换为整数时,需要确保string类型能够转换成有效数据,如"hello",则不能转换为整形

package main

import (
    "fmt"
    "strconv"
)

func main() { //定义main函数,一般的文件执行的入口函数都是main
    t1, _ := strconv.ParseBool("false")
    f1, _ := strconv.ParseFloat("10.100", 64)
    l1, _ := strconv.ParseInt("11", 8, 64) //base 指定前面的数的进制数,后面返回默认是十进制,bitSize 表示指定进制的位数
    /*
        func ParseInt(s string, base int, bitSize int) (i int64, err error)
        返回字符串表示的整数值,接受正负号。
        base指定进制(2到36),如果base为0,则会从字符串前置判断,"0x"是16进制,"0"是8进制,否则是10进制;
        bitSize指定结果必须能无溢出赋值的整数类型,0、8、16、32、64 分别代表 int、int8、int16、int32、int64;返回的err是*NumErr类型的,如果语法有误,err.Error = ErrSyntax;如果结果超出类型范围err.Error = ErrRange。
    */

    fmt.Printf("%v,类型为%T 	  %v 类型为%T
", t1, t1, f1, f1)
    fmt.Printf("%v,类型为%T
", l1, l1)

}

结果如下

技术图片

5 字符串应用

1 计算字符串长度len() 函数

package main

import (
    "fmt"
    "unicode/utf8"
)

func main() { //定义main函数,一般的文件执行的入口函数都是main
    c1 := "ABCD"
    c2 := "中国"
    c3 := "hello中国"
    fmt.Printf("c1 长度为:%v,c2 长度为:%v,c3的长度为:%v
", len(c1), len(c2), len(c3))
    fmt.Printf("c1 长度为:%v,c2 长度为:%v,c3的长度为:%v
", utf8.RuneCountInString(c1), utf8.RuneCountInString(c2), utf8.RuneCountInString(c3))

}

结果如下

技术图片

总结
ASCII 字符串长度可使用len() 函数
Unicode 字符串长度使用utf8.RuneCountInString()函数

2 遍历字符串

package main

import (
    "fmt"
)

func main() { //定义main函数,一般的文件执行的入口函数都是main
    c3 := "hello中国"
    for i := 0; i < len(c3); i++ {
        fmt.Printf("%c
", c3[i])
    }

    fmt.Println("----------------------------")

    for _, value := range c3 {
        fmt.Printf("%c
", value)
    }
}

结果如下

技术图片

总结
ASCII 字符串遍历直接使用下表
Unicode字符串遍历需要使用for range进行

3 获取字符串某一段字符

package main

import (
    "fmt"
    "strings"
)

func main() { //定义main函数,一般的文件执行的入口函数都是main
    c3 := "hello 中国"
    c1 := strings.Index(c3, " ")      //通过指定的值获取对应的索引,其为空格,索引是从0开始,因此其为5
    c2 := strings.Index(c3[c1:], "中") //其从5开始,左闭右开,其中子为第二个,及为1,若后面的不存在,则为-1 
    fmt.Println(c1, c2, c3[c1+c2:])   //此处的c3[6:] 则为从中开始
}

结果如下

技术图片

总结

字符串索引比较常用的有以下几种方式

1 strings.Index: 正向搜索字符串
2 strings.LastIndex: 反向搜索字符串
3 搜索起始位置可通过切片偏移制作

4 修改字符串

Go语言的字符串无法直接修改每一个字符元素,只能重新构造新的字符串并赋值给原来的字符串变量实现

package main

import "fmt"

func main() { //定义main函数,一般的文件执行的入口函数都是main
    c1 := "hello 中国"
    c2 := []byte(c1)
    fmt.Println(len(c1))
    for i := 5; i <= 11; i++ {
        c2[i] = ‘ ‘ //此处修改为空串
    }
    fmt.Println(string(c2), len(string(c2)))
}

结果如下

技术图片

总结
GO语言中的字符串是不可修改的
修改字符串时,可将字符串转换成[]byte进行修改
[]byte个string可以通过强制类型转换互转

5 链接字符串

package main

import (
    "fmt"
    "strings"
)

func main() { //定义main函数,一般的文件执行的入口函数都是main
    c1 := "hello"
    c2 := "中国"
    c3 := c1 + c2  //简单的字符串相加进行连接 
    fmt.Println(c3)
    var c4 strings.Builder  //定义字节缓冲,
    c4.WriteString(c1) // 将字符串写入字节缓冲
    c4.WriteString(c2)
    fmt.Println(c4.String())  //缓冲字符的方式输出
}

结果如下

技术图片

6 格式化

相关样式和功能

动词 功能
%v 按值的本来值输出
%+v 在%v的基础上,对结构体字段名和值进行了展开
%#v 输出符合Go语言格式的值
%T 输出Go语言语法格式的类型的值
%% 输出%本体
%b 整形以二进制方式显示
%o 整形以八进制方式显示
%d 整形以十进制方式显示
%x 整形以十六进制方式显示
%X 整形以十六进制,字母大写方式显示
%U Unicode字符
%f 浮点数
%p 指针
%t bool类型

6 指针

1 简介

指针概念在Go语言中被拆分为两个核心概念

1 类型指针:允许对这个指针类型的数据进行修改,传递数据使用指针,而无需值拷贝,类型指针不能进行偏移和运算


2 切片:由指向起始元素的原始指针,元素数量和容量组成


Go语言的指针类型变量拥有指针的高效访问,但又不会发生指针偏移,从而避免非法修改关键性数据问题,同时,垃圾回收机制也比较容易对不会发生偏移的指针进行检索和回收。


切片比原始指针具备更强大的特性,更为安全,切片发生越界时,运行时会报出宕机,并打印出堆栈,而指针只会崩溃。

2 指针和指针类型

创建指针

package main

import "fmt"

func main() { //定义main函数,一般的文件执行的入口函数都是main
    ptr := new(string) // 创建指针变量,并指定其指针对应的值的类型为string
    *ptr = "ABCD"
    fmt.Printf("其变量类型为:%T,其变量值为:%v", ptr, *ptr)
}

结果如下

技术图片

每个变量在运行时都会拥有一个地址,这个地址代表变量在内存中的位置,Go语言中使用"&"操作符放在变量前面来对变量进行取值操作

如 ptr:=&v // v的类型为T
其中v代表被取地址的变量,被取地址的v使用ptr变量进行接收,ptr的类型就是"*T",称作T的指针类型,"*"表示指针

package main

import "fmt"

func main() { //定义main函数,一般的文件执行的入口函数都是main
    var l1 int = 10
    ptr := &l1 //指针类型,此处指针类型和源数据类型相似,为*int ,其值为地址
    fmt.Printf("值为%v,类型为%T, l1对应的地址为: %v", ptr, ptr, &l1)
}

结果为

技术图片

其每次运行的值是不同的,在32位平台上,将是32位地址,在64位平台上将是64位地址


变量,指针和地址的关系:每个变量都拥有地址,指针的值就是变量的地址。

3 取值和修改

package main

import "fmt"

func main() { //定义main函数,一般的文件执行的入口函数都是main
    var l int = 10
    ptr := &l                                      //指针类型,此处指针类型和源数据类型相似,为*int ,其值为地址
    fmt.Printf("l1修改前的值为%v, *ptr的值为%v
", l, *ptr) // *ptr  根据其地址取值
    *ptr = 200                                     //修改指针指向的值
    fmt.Printf("值为%v,类型为%T, l1对应的地址为: %v  	 l1 修改后的值为:%v", ptr, ptr, &l, l)
}

结果如下

技术图片

4 总结

取地址操作符"&" 和 取值操作符"*" 是一对互补操作符,"&"取出地址,"*" 根据地址取出地址指向的值。

变量,指针地址,指针变量,取地址,取值的关系如下

对变量进行取地址(&)操作,可获取这个变量的指针变量
指针变量的值是指针地址
对指针变量进行取值(*)操作,可以获得这个指针变量指向的原变量值,可进行赋值修改。

7 变量生命周期

1 栈

1 栈基础

栈(stack)是一种拥有特殊规则的线性表数据结构
概念
栈只允许放线性表的一段放入数据,之后在这一端取出数据,按照先进后出LIFO的顺序,如向箱子中放东西,放的越早,越是最后被拿出来的。

2 栈的概念

栈顶(Top)
入栈(Push)
出栈(Pop)
栈底(Bottom)

技术图片

往栈中放入元素的过程称为入栈,入栈会增加栈的元素数量,最后放入的元素总是位于栈的顶部,最先放入的元素总是位于栈的底部。


从栈中取出元素时,只能从栈顶取出,取出元素后,栈的数量会变少。最先放入的元素总是最后被取出,最后放入的元素总是最先被取出,不允许从栈底取出数据,也不允许对栈成员进行任何查看和修改操作。

2 变量和栈的关系

栈可用于内存分配,栈的分配和回收速度极快

func Test(a, b int) int {
    var c int   //此处会分配内存
    c = a * b
    var d int  //此处也会分配内存 
    d = c * a   
    return d
}

Go 默认情况下会将c和d 分配在栈上,这两个变量在Test()函数退出时就不再使用,函数结束时,保存在c和d的栈内存再出站释放内存,整个分配内存的过程通过栈的分配和回收都会非常迅速。

2 堆

堆在内存分配中类似于一个往房间里摆放各种家具,家具的尺寸有大有小,分配内存时,需要找一块足够大的空间进行存储,在多次分配内存后,其会导致在向其中分配空间,虽然有足够空间,但各空间分配不均,导致无法形成连续空间来存储数据,此时便需要对这些空间进行调整优化。


堆分配内存和栈分配内存相比,堆适合不可预知大小的内存分配,但为此付出的代价是分配速度较慢,且容易形成内存碎片。

3 变量逃逸(Escape Analysis)

1 概念

堆和栈各有优缺点,在C/C++语言中,需要开发者自己学习如何进行内存分配,选用不同的内存分配方式来适应不同的算法需求,如函数局部变量尽量使用栈,全局变量,结构体成员使用堆分配等,程序员需要在不同项目中学习,记忆并实践和使用


Go语言将这个过程整合到编译器中,命名为"变量逃逸分析",这个技术由编码分析代码的特征和代码生命周期,决定应该如何对堆还是栈进行内存分配,即使程序员使用Go语言完成了整个工程后也不会感受到这个过程


2 变量逃逸分析

package main

import "fmt"

func Test(b int) int {
    var c int
    c = b
    return c
}

func Test1() {

}
func main() { //定义main函数,一般的文件执行的入口函数都是main
    var a int
    Test1()
    fmt.Println(a, Test(0))
}

结果如下

技术图片

参数:

其中-goflags参数是编译参数,其中-m 表示内存分配分析,-l 表示避免程序内联,也就是避免对程序进行优化

第二行表示变量a逃逸到堆。
第三行表示Test(0) 调用逃逸到堆,由于Test()函数会返回一个整形值,这个值被fmt.Println使用后还是会在其声明后继续在main()函数中存在。

上面例子中变量c是整形,其值通过Test()的返回值"逃出"了Test()函数,c变量值被复制并作为Test()函数的返回,及时c变量在Test()函数中分配的内存被释放,也不会影响main()中使用Test()返回的值,c变量使用栈分配不会影响结果。

3 取地址发生逃逸

package main

import "fmt"

type Test struct {
}

func Test1() *Test { //此处表示返回是一个指针,且是类型为Test的指针
    var c Test
    return &c //此处取地址付
}
func main() { //定义main函数,一般的文件执行的入口函数都是main

    fmt.Println(Test1())
}

结果如下

技术图片

第一行出现了新提示,将c移动到堆中,这话表示,Go编译器已经确认如果将c变量分配在栈上是无法保证程序最终结果的,如果坚持这样做,则可能是引入一个局部变量的地址,Go最终选择将c的Test结构分配到堆上,然后由垃圾回收机制进行回收c的内存。

4 原则

在使用Go语言进行编译时,Go语言的设计者不希望开发者将精力放在内存应该分配在栈还是堆上的问题,编译器会自动帮助开发者完成这个选择。
其编译其觉得应该分配在堆和栈上的原则是:
1 变量是否被取地址
2 变量是否发生逃逸

8 标识符规范

1 概念

golang对各种变量,方法等命名时使用的字符序列称为标识符,凡是可以起名字的地方都叫标识符

2 命名规范

1 有26个大小写英文字母、数字0-9和下划线"_"组成
2 数字不可开头
3 golang中严格区分大小写
4 标识符不能包含空格
5 下划线本身在Go中是一种特殊的标识符,称为空标识符,其可以代表任何其他的标识符,但是其对应的值会被忽略,所以单独出现时仅表示为占位符,不能作为标识符使用
6 不能以系统关键字作为标识符

3 标识符命名注意事项

1 包名:保持package的名字和上级目录保持一致,尽量采用有意义的包名,简短,有意义,不要和标准库冲突


2 变量名,函数名,常量名均采用驼峰命名法


3 如果变量名,函数名,常量名首字母大写,则可被其他包访问,如果首字母小写,则只能在本包中使用(注: 可理解成,首字母大写是共有,首字母小写是私有)

4 保留关键字

在Go中为了简化代码编译过程中对代码的解析,定义的保留关键字有25个,如下

break   default   func    interface   select   
case    defer  go  map   struct    chan   
else     goto        package    switch     
const   fallthrough   if   range    type     
continue   for  import   return    var  

5 预定义标识符

除了保留关键字外,go还提供了36个预定义标识符,其包含基础数据类型和内嵌函数

append  bool    byte    cap close
complex complex64   complex128  uint16  copy
false   float32 float64 imag    int
int8    int16   uint32  int32   int64
iota    len make    new nil
panic   uint64  print   println real
recover string  true    uint    uint8
uintptr

6 值类型和引用类型

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

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

五 常量

1 简介

相对于变量,常量是恒定不变的值,如圆周率


可以在编译时,对常量表达式求值计算,并在运行期使用该计算结果,计算结果无法被修改

2 常量的定义和使用

常量的声明和变量非常类似,只是把var换成了const

package main

import "fmt"

//单个常量的定义
const l1 = "2345"
const l2 = 1234

//多个常量的定义
const (
    l3 = "goland"
    l4 = "golang"
    l5 = 5
)

func main() { //定义main函数,一般的文件执行的入口函数都是main
    var arr [l5]int //因为其是常量,因此可被直接使用
    fmt.Println(l1, l2, l3, l4, arr)
}

结果如下

技术图片

3 枚举--一组常量值

Go 语言中现阶段没有枚举,可以使用常量配合iota模拟枚举

package main

import "fmt"

type C1 int //将此定义为int 类型,枚举的本质和int相似

const (
    A C1 = iota //开始生成枚举值,默认为0,将A的类型标识为C1,这样标识后,const下方的常量默认可以是默认类型,
    // 默认使用前面的类型作为常量类型,此行的iota进行常量值自动生成,iota起始为0,一般建议从0开始
    // 一个const声明被的每一行常量声明,将会自动套用前面的iota格式,并自动增加,
    S
    R
    B
)

func main() {
    fmt.Println(A, S, R, B) //输出所有枚举类型
    fmt.Printf("%T
", A)

    var c1 C1 = B //使用枚举类型赋初始值
    fmt.Println(c1)
}

结果如下

技术图片

package main

import "fmt"

type C1 int //将此定义为int 类型,枚举的本质和int相似

const (
    A = 1 << iota //此处定义一个将其左移动1位的操作
    B             //10
    C             //100
    D             //1000
)

func main() {
    fmt.Printf("%d  %d  %d  %d
", A, B, C, D) //十进制打印
    fmt.Printf("%b  %b  %b  %b
", A, B, C, D) // 二进制打印
}

技术图片

六 类型别名

1 简介

类型别名是Go 1.9 版本添加的新功能,主要用于代码升级,迁移中类型兼容性的问题,在C/C++语言中,代码重构升级可以使用宏快速定义新的一段代码,Go语言中没有选择加宏,而是将解决代码重构中最麻烦的类型名变更问题。

2 类型别名定义

//Go  1.9 版本之前的内建类型定义的代码如下

type byte uint8
type rune int32

//Go  1.9 版本之后变为

type byte = uint8
type rune = int32

3 区分类型别名和类型定义

类型别名的写法为

type TypeAlias=Type


类型别名规范:TypeAlias 只是Type的别名,本质上和TypeAlias 与 Type 是同一个类型。


类型别名与类型定义表面上只是一个等号的差异,但实际区别比较大

如下

package main

import "fmt"

//定义新类型
type NewInt int

//定义类型别名
type IntAlias = int

func main() {
    var a NewInt //声明变量a类型为NewInt
    fmt.Printf("a 的类型为:%T
", a)
    var b IntAlias //将b声明为IntAlias 类型
    fmt.Printf("b 的类型为:%T
", b)

}

结果如下

技术图片

IntAlias 类型只会存在于代码中,在编译完成时,不会有IntAlias 类型。

4 非本地类型不能定义方法

能够随意为各种类型起名字,是否意味着可以在自己包里为这些类型任意添加方法,如下

package main

import "time"

type A = time.Duration

func (m A) Test(a string) {  //不能在一个非本地类型time.Duration上定义新方法,,非本地方法是指使用time.Duration 
//的代码所在的包,也就是main包,因为time.Duration 是在time中定义的,在main中使用,time.Duration 包和main包不在同一个
//包中,因此不能为不在同一个包中的类型定义方法

}
func main() {
}

结果如下

技术图片

解决方法

1 将type A = time.Duration修改为 type A time.Duration ,也就是从别名修改为类型

2 将 type A = time.Duration 定义到time中

以上是关于Go语言基础的主要内容,如果未能解决你的问题,请参考以下文章

解决go: go.mod file not found in current directory or any parent directory; see ‘go help modules‘(代码片段

windows通过Visual Studio Code中配置GO开发环境(转)

在Visual Studio Code中配置GO开发环境

Go语言切片

go语言怎么将二进制转为字符串

[Go] 并发和并行的区别