GO语言基础

2023-10-27 17:24:53 浏览数 (1)

GO语言基础

一、初识Go语言

1、go语言特性
  1. 垃圾回收:内存自动回收,再也不需要开发人员管理内存,只需要new分配内存,不需要释放
  2. 天然并发:从语言层面支持并发,非常简单,并且 goroute 轻量级线程,创建成千上万个goroute成为可能 channel:管道,类似unix/linux中的pipe,多个goroute之间通过channel进行通信,支持任何类型
  3. 多返回值:一个函数返回多个值
2、包的概念
  1. 和python一样,把相同功能的代码放到一个目录,称之为包
  2. 包可以被其他包引用
  3. main包是用来生成可执行文件,每个程序只有一个main包
  4. 包的主要用途是提高代码的可复用性

示例:

代码语言:javascript复制
package calc

func Add(a int, b int) int {
	return a   b
}
代码语言:javascript复制
package main

import (
	"awesomeProject/calc"
)

func main() {
	println(calc.Add(1, 2))
}

二、Go语言基础

1、文件名&关键字&标识符

所有go源码以.go结尾

标识符以字母或下划线开头,大小写敏感

可以用 _ 来接收对象,它是特殊的标识符,用来忽略结果

代码语言:javascript复制
	_ = calc.Add(1, 2)

保留关键字:

break

default

func

interface

select

case

defer

go

map

struct

chan

else

goto

package

switch

const

fallthough

if

range

type

continue

for

import

return

var

说明:

  1. var和const :变量和常量的声明
  2. var varName type 或者 varName : = value
  3. package and import: 包和导入
  4. func: 用于定义函数和方法
  5. return :用于从函数返回
  6. defer someCode :在函数退出之前执行
  7. go : 用于并行
  8. select 用于选择不同类型的通讯
  9. interface 用于定义接口
  10. struct 用于定义抽象数据类型
  11. chan用于channel通讯
  12. type用于声明自定义类型
  13. map用于声明map类型数据
  14. range用于读取slice、 map、 channel数据
  15. break、 case、 continue、 for、 fallthrough、 else、 if、 switch、 goto、 default 流程控制
  16. fallthrough: 1.加了fallthrough后,会直接运行 紧跟的后一个 case或default语句,不论条件是否满足都会执行 2.加了fallthrough语句后, 紧跟的后一个 case条件不能定义常量和变量 3.执行完fallthrough后直接跳到下一个条件语句,本条件执行语句后面的语句不执行
代码语言:javascript复制
str := "hello world,中国"
for i, v := range str {
    fmt.Printf("index[%d] val[%c]n", i, v)
}  
2、Go程序结构
代码语言:javascript复制
package main
import “fmt”
func main() {
    fmt.Println(“hello, world”)
}

任何一个代码文件隶属于一个包

import 关键字,引用其他包

golang可执行程序, package main,并且有且只有一个main入口函数

包中函数调用:同一个包中函数,直接调用;不同包中函数,通过 包名.函数名 进行调用

包访问控制规则:大写意味着这个函数/变量是可导出的;小写意味着这个函数/变量是私有的,包外部不能访问

3、常量和变量

常量使用const 修饰,代表永远是只读的,不能修改

const 只能修饰boolean, number(int相关类型、浮点类型、 complex)和string

语法: const identifier [type] = value,其中type可以省略

代码语言:javascript复制
const b string = “hello world” 
const b = “hello world” 
代码语言:javascript复制
const (
    a = 0
    b = 1
    c = 2
)
代码语言:javascript复制
const (
    a = iota//0
    b //1
    c //2
)

变量定义语法: var identifier type

在函数内部,可以使用更简略的 := 方式声明并初始化变量,但是不能用于全局变量声明

代码语言:javascript复制
var a string="hello"
var(
	b int //默认为0
    c string//默认为""
    d bool //默认为false
)

在函数内部声明的变量叫做局部变量,生命周期仅限于函数内部

在函数外部声明的变量叫做全局变量,生命周期作用于整个包,如果是大写的,则作用于整个程序。

4、数据类型和操作符
基本概念

Go 是强类型语言,因此不会进行隐式转换,任何不同类型之间的转换都必须显式说明

Go 不存在像 Cpp 那样的运算符重载,表达式的解析顺序是从左至右

Go 对于值之间的比较有非常严格的限制,只有两个类型相同的值才可以进行比较,如果值的类型是接口,它们也必须都实现了相同的接口。如果其中一个值是常量,那么另外一个值的类型必须和该常量类型相兼容的。如果以上条件都不满足,则其中一个值的类型必须在被转换为和另外一个值的类型相同之后才可以进行比较。

bool类型

只能存true和false

int类型

Go 也有基于架构的类型,例如:intuintuintptr

这些类型的长度都是根据运行程序所在的操作系统类型所决定的:

  • intuint 在 32 位操作系统上,它们均使用 32 位(4 个字节),在 64 位操作系统上,它们均使用 64 位(8 个字节)。
  • uintptr 的长度被设定为足够存放一个指针即可。

Go 中不允许不同类型之间的混合使用,但是对于常量的类型限制非常少,因此允许常量之间的混合使用

代码语言:javascript复制
func main() {
	var a int
	var b int32
	a = 15
	//b = a   a	 // 编译错误
	b = int32(a   a) // 编译ok
	b = b   5        // 因为 5 是常量,所以可以通过编译
}

注:如果你实际存的数字超出你要转换到的类型的取值范围的话,则会引发 panic

float类型

Go 语言中没有 float 类型。(Go语言中只有 float32float64)没有 double 类型。

float32 精确到小数点后 7 位,float64 精确到小数点后 15 位。由于精确度的缘故,你在使用 == 或者 != 来比较浮点数时应当非常小心。你最好在正式使用前测试对于精确度要求较高的运算。

尽可能地使用 float64,因为 math 包中所有有关数学运算的函数都会要求接收这个类型。

通过增加前缀 0 来表示 8 进制数(如:077),增加前缀 0x 来表示 16 进制数(如:0xFF),以及使用 e 来表示 10 的连乘(如: 1e3 = 1000,或者 6.022e23 = 6.022 x 1e23)

格式化输出

在格式化输出时,可以使用 %t 来表示你要输出的值为布尔型

在格式化字符串里,%d 用于格式化整数(%x%X 用于格式化 16 进制表示的数字),%g 用于格式化浮点型(%f 输出浮点数,%e 输出科学计数表示法),%0nd 用于规定输出长度为 n 的整数,其中开头的数字 0 是必须的,%n.mg 用于表示数字 n 并精确到小数点后 m 位

%b 是用于表示位的格式化标识符

字符类型

字符只是整数的特殊用例,byte 类型是 uint8 的别名

代码语言:javascript复制
var ch byte = 'A' 或 var ch byte = 65 或 var ch byte = 'x41'
字符串类型

字符串是字节的定长数组

字符串表示方式:

代码语言:javascript复制
var str = "hello world"

和 C/C 不一样,Go 中的字符串是根据长度限定,而非特殊字符

注:获取字符串中某个字节的地址的行为是非法的,例如:&str[i]

二元运算符

按位与 &:同为1相&结果为1

按位或 |:有一个为1结果为1

按位异或 ^:相异为1

位清除 &^:将指定位置上的值设置为 0

在 Go 语言中,&&|| 是具有快捷性质的运算符,当运算符左边表达式的值已经能够决定整个表达式的值的时候(&& 左边的值为 false|| 左边的值为 true),运算符右边的表达式将不会被执行。

其他

逻辑运算符:==!=<<=>>=

数学操作符: 、 -、 *、 /

类型转换, type(variable), 比如: var a int=8; var b int32=int32(a)

5、值类型和引用类型

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

引用类型:变量存储的是一个地址,这个地址存储最终的值,内存通常在堆上分配,通过GC回收

值类型:基本数据类型int、 float、 bool、 string以及数组和struct

引用类型:指针、 slice、 map、 chan等都是引用类型

三、Go函数

函数声明: func 函数名字 (参数列表) (返回值列表) {}

代码语言:javascript复制
func Add(a int, b int) int {
	return a   b
}

函数特点:

a. 不支持重载,一个包不能有两个名字一样的函数

b. 函数是一等公民,函数也是一种类型,一个函数可以赋值给变量

c. 多返回值

函数参数传递方式:值传递;引用传递

注:值传递是值的拷贝。引用传递是地址的拷贝,一般来说,地址拷贝更为高效

函数参数传递方式: 值传递;引用传递

注:map、 slice、 chan、指针、 interface默认以引用的方式传递

命名返回值的名字:return自动匹配返回对象的名字

代码语言:javascript复制
func calc(a, b int) (sum int, avg int) {
    sum = a   b
    avg = (a  b)/2
    return
}

_ 标识符,用来忽略返回值:

代码语言:javascript复制
func main() {
    sum, _ := calc(100, 200)
}

可变参数:

代码语言:javascript复制
func add(arg…int) int {
}//0个或多个参数
func add(a int, arg…int) int {
}//1个或多个参数

注:其中arg是一个slice,我们可以通过arg[index]依次访问所有参数,通过len(arg)来判断传递参数的个数

defer用途:

当函数返回时,执行defer语句,因此可以用来做资源清理(文件,数据库,锁资源等)

多个defer语句,按先进后出的方式执行

defer语句中的变量,在defer声明时就决定了

四、常用结构

1、内置函数、闭包
  1. close:主要用来关闭channel
  2. len:用来求长度,比如string、 array、 slice、 map、 channel
  3. new:用来分配内存,主要用来分配值类型,比如int、 struct。返回的是指针
  4. make:用来分配内存,主要用来分配引用类型,比如chan、 map、 slice
  5. append:用来追加元素到数组、 slice中
  6. panic和recover:用来做错误处理

闭包:一个函数和与其相关的引用环境组合而成的实体

代码语言:javascript复制
package main

import "fmt"

func Adder() func(int) int {
	var x int//只初始化一次
	return func(delta int) int {
		x  = delta
		return x
	}
}
//上述代码类似于cpp中在类中定义成员x,func函数对x进行 delta
func main() {
	var f = Adder()
	fmt.Println("1:", f(1))//0
	fmt.Println("2:", f(20))//21
	fmt.Println("3:", f(300))//321
}
2、数组与切片

数组:是同一种数据类型的固定长度的序列

数组定义: var a [len]int,比如: var a[5]int 一旦定义,长度不能变

长度是数组类型的一部分,因此, var a[5] int和var a[10]int是不同的类型

访问越界,如果下标在数组合法范围之外,则触发访问越界,会panic

数组是值类型,因此参数传递改变的是副本的值,不会改变本身的值

数组初始化及遍历:

多维数组及遍历:

代码语言:javascript复制
var age [5][3]int
var f [2][3]int = [...][3]int{{1, 2, 3}, {7, 8, 9}}
for k1, v1 := range f {
    for k2, v2 := range v1 {
        fmt.Printf("(%d,%d)=%d ", k1, k2, v2)
    }
    fmt.Println()
}

切片定义:

切片:切片是数组的一个引用,因此切片是引用类型

切片的长度可以改变,因此, 切片是一个可变的数组

切片遍历方式和数组一样,可以用len()求长度

cap可以求出slice最大的容量, 0 <= len(slice) <= (array), 其中array是slice引用的数组

make创建切片:

代码语言:javascript复制
slice := make([]type, len)
slice := make([]type, len, cap)

切片初始化:

代码语言:javascript复制
var slice1 []int = arr[start:end]//包含start到end之间的元素,但不包含end,[start,end)
var slice2 []int = arr[0:end]//可以简写为 var slice []int=arr[:end]
var slice3 []int = arr[start:len(arr)]//可以简写为 var slice[]int = arr[start:]
var slice4 []int = arr[0, len(arr)]//可以简写为 var slice[]int = arr[:]
Slice = slice[:len(slice)-1] //最后一个元素去掉 

切片的内存布局,类似C vector:

用append内置函数操作切片:

代码语言:javascript复制
slice := make([]int, 10)
slice=append(slice, 10)//增添元素,更新
var a = []int{1, 2, 3}
var b = []int{4, 5, 6}
a = append(a, b...)//增添数组

For range 遍历切片:

代码语言:javascript复制
for index, val := range slice {
}

切片resize:

代码语言:javascript复制
var a = []int {1,3,4,5}
b := a[1:2]
b = b[0:3]

切片拷贝:

代码语言:javascript复制
s1 := []int{1,2,3,4,5}
s2 := make([]int, 10)
copy(s2, s1)

string与slice:

string底层就是一个byte的数组,因此,也可以进行切片操作

代码语言:javascript复制
str := “hello world”
s1 := str[0:5]
fmt.Println(s1)
s2 := str[5:]
fmt.Println(s2)  
s := []byte(str)//修改str
s[0] = ‘o’
str = string(s)  

string的底层布局:

数组和切片的区别:

数组:类型 [n]T 表示拥有 n 个 T 类型的值的数组,指定空间大小

切片:类型 []T 表示一个元素类型为 T 的切片(动态开辟数组),不指定大小

new和make的区别:

代码语言:javascript复制
var i *int=new(int)
*i=10
var num []int = make([]int, 10)
num[1]=10

make也是用于内存分配的,但是和new不同,它只用于chan、 map以及切片的内存创建,而且它返回的类型就是这三个类型本身,而不是他们的指针类型,因为这三种类型就是引用类型,所以就没有必要返回他们的指针了

3、map

map是一种特殊的数据结构:一种元素对 (pair) 的无序集合,pair 的一个元素是 key,对应的另一个元素是 value,所以这个结构也称为关联数组或字典。这是一种快速寻找值的理想结构:给定 key,对应的 value 可以迅速定位。

map是引用类型,可以使用如下声明:

代码语言:javascript复制
var map1 map[keytype]valuetype
var map1 map[string]int

key 可以是任意可以用 == 或者 != 操作符比较的类型,比如 stringintfloat32(64)。所以数组、切片和结构体不能作为 key ,但是指针和接口类型可以。如果要用结构体作为 key 可以提供 Key()Hash() 方法,这样可以通过结构体的域计算出唯一的数字或者字符串的 key

value 可以是任意类型的;通过使用空接口类型,我们可以存储任意值,但是使用这种类型作为值时需要先做一次类型断言

map引用类型 的: 内存用 make() 方法来分配

map 的初始化:

代码语言:javascript复制
var map1 = make(map[keytype]valuetype)

你错误地使用 new() 分配了一个引用对象,你会获得一个空引用的指针

测试 map1 中是否存在 key1

代码语言:javascript复制
if _, ok := map1[key1]; ok {
	// ...
}

map1 中删除 key1

代码语言:javascript复制
delete(map1, key1)

注:如果 key1 不存在,该操作不会产生错误

使用 for 循环读取 map

代码语言:javascript复制
for key, value := range map1 {
	...
}

只想获取 key

代码语言:javascript复制
for key := range map1 {
	fmt.Printf("key is: %dn", key)
}

注意 map 不是按照 key 的顺序排列的,也不是按照 value 的序排列的

如果你想为 map 排序,需要将 key(或者 value)拷贝到一个切片,再对切片排序(使用 sort 包),然后可以使用切片的 for-range 方法打印出所有的 key 和 value

代码语言:javascript复制
package main
import (
	"fmt"
	"sort"


var (
	barVal = map[string]int{"alpha": 34, "bravo": 56, "charlie": 23,
							"delta": 87, "echo": 56, "foxtrot": 12,
							"golf": 34, "hotel": 16, "indio": 87,
							"juliet": 65, "kili": 43, "lima": 98}
)

func main() {
	fmt.Println("unsorted:")
	for k, v := range barVal {
		fmt.Printf("Key: %v, Value: %v / ", k, v)
	}
	keys := make([]string, len(barVal))
	i := 0
	for k, _ := range barVal {
		keys[i] = k
		i  
	}
	sort.Strings(keys)
	fmt.Println()
	fmt.Println("sorted:")
	for _, k := range keys {
		fmt.Printf("Key: %v, Value: %v / ", k, barVal[k])
	}
}

五、Go中包及go mod

1、go mod

实际项目开发中我们首先要在我们项目目录中用go mod命令生成一个go.mod文件管理我们项目的依赖

使用go mod命令生成一个go.mod文件

代码语言:javascript复制
go mod init goProject

生成一个 go.mod 的文件,里面的内容是go版本,以及以后添加的包

代码语言:javascript复制
module goProject

go 1.14
2、包

包(package)是多个Go源码的集合,一个包可以简单理解为一个存放多个.go文件的文件夹。该文件夹下面的所有go文件都要在代码的第一行添加如下代码,声明该文件归属的包

代码语言:javascript复制
package 包名

注意事项

  • 一个文件夹下面直接包含的文件只能归属一个package,同样一个package的文件不能在多个文件夹下。
  • 包名可以不和文件夹的名字一样,包名不能包含-符号。
  • 包名为main的包为应用程序的入口包,这种包编译后会得到一个可执行文件,而编译不包含main包的源代码则不会得到可执行文件。
3、init()初始化函数

在Go 语言程序执行时导入包语句会自动触发包内部init()函数的调用。

需要注意的是:init() 函数没有参数也没有返回值。init()函数在程序运行时自动被调用执行,不能在代码中主动调用它。

依赖

使用go mod命令生成一个go.mod文件

代码语言:javascript复制
go mod init goProject

生成一个 go.mod 的文件,里面的内容是go版本,以及以后添加的包

代码语言:javascript复制
module goProject

go 1.14
2、包

包(package)是多个Go源码的集合,一个包可以简单理解为一个存放多个.go文件的文件夹。该文件夹下面的所有go文件都要在代码的第一行添加如下代码,声明该文件归属的包

代码语言:javascript复制
package 包名

注意事项

  • 一个文件夹下面直接包含的文件只能归属一个package,同样一个package的文件不能在多个文件夹下。
  • 包名可以不和文件夹的名字一样,包名不能包含-符号。
  • 包名为main的包为应用程序的入口包,这种包编译后会得到一个可执行文件,而编译不包含main包的源代码则不会得到可执行文件。
3、init()初始化函数

在Go 语言程序执行时导入包语句会自动触发包内部init()函数的调用。

需要注意的是:init() 函数没有参数也没有返回值。init()函数在程序运行时自动被调用执行,不能在代码中主动调用它。

包初始化顺序:

[外链图片转存中…(img-jR5UCw7D-1698326264819)]

[外链图片转存中…(img-OPYWhU6s-1698326264819)]

0 人点赞