我是靠谱客的博主 迅速花瓣,这篇文章主要介绍Go语言学习(面向区块链)golang fmt格式“占位符”,现在分享给大家,希望可以做个参考。

第一个Go语言程序

复制代码
1
2
3
4
5
6
7
8
gopath目录 gopath目录就是我们存储我们所编写源代码的目录.该目录下有三个字目录:src, bin, pkg. src--->里面每一个子目录,就是一个包.包含Go的源码文件 pkg--->编译后生成的,包含目标文件 bin--->生成可执行文件
复制代码
1
2
3
4
5
6
7
8
9
10
GOPATH环境变量 把我们自己存放go语言的目录告诉计算机就可了 新建一个环境变量,然后告诉计算机 步骤 1.新建一个文件加,'goprojects',这个就是存放我们自己开发的程序 2.仿照go官方目录,创建'src(目录,用于存放go语言程序的源码)' 'pkg(目录,用于存放引用其他第三方的程序代码)' 'bin(目录,用于存放编译后的二进制文件(可执行文件))'

main函数和init函数

  1. init函数是用于程序执行前做包的初始化函数,比如初始化包里的变量等
  2. 每个包可以有多个init函数
  3. 包里的源文件也可以多个init函数
  4. 同一个包中多个init函数的执行顺序go语言没有明确的定义
  5. 不同包的init函数按照包的包导入的依赖关系决定该初始化函数的执行顺序
  6. init函数不能被其他函数调用,而是在main函数执行前,自动被调用

main函数和init函数异同

相同点:

​ 两个函数在定义时不能有任何的参数和返回值,且Go程序自动调用

不同点:

​ init可以在任意包,且可以定义多个

​ main函数只能在main包,只能定义一个

由于Go语言允许将一个包的代码分散在多个代码文件中(前提是这些文件必须处于同一级目录下),在编写代码时就有可能出现main函数重复定义的问题,显然,在一个应用程序中,main函数只允许出现一次.

做法:…

运算符

1.操作数

若某个对象与运算符一起出现并参与运算,便可以称这个对象为操作数.操作数与运算符一起组成了表达式

2.算术运算符

2.1四则运算符

复制代码
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
package main import ("fmt") func main(){ //声明两个变量m,n,类型为int,用于存放操作数 var m, n int fmt.Print("请输入第一个操作数:") fmt.Scan(&m) fmt.Print("请输入第二个操作数:") fmt.Scan(&n) //Scan函数会从标准输入流读取数据,然后存放在m,n变量中.这里采用引用传递参数(加上&符号,获取变量内存的地址),以保证函数调用后m,n变量能引用到所读的内容 r1 := m + n r2 := m - n r3 := m * n r4 := m / n //使用 := 运算符可以直接向新变量赋值,不需要var关键字声明 fmt.Printf("%d + %d = %dn", m, n, r1) }

2.2取余运算符

复制代码
1
2
%

2.3指数运算

math包中有两个函数可以用于指数运算

Pow函数的声明如下:

func Pow(x, y float64) float64 其中,参数x为底数,y为指数.即x ^ y.此外,还有一个Pow10函数:

func Pow10(n int) float64 10 ^ n.

复制代码
1
2
3
4
//计算5的立方 result := math.Pow(5, 3) fmt.Printf("5的3次方: %dn",int(result))

注意:

Pow与Pow10函数的返回值类型为float64类型,调用Printf函数输出格式化字符串,如果使用%d格式控制符,则需要先把计算结果转换为int类型,再传递给Printf函数,如果使用%f格式控制符,则不需要类型转换

2.4自增与自减运算符

3比较运算符

4逻辑运算符

5位运算符

5.1按位与

复制代码
1
2
3
4
5
6
7
8
var ( m int8 = 12 n int8 = 6 ) 将变量m, n进行与运算 result := m & n
复制代码
1
2
3
4
5
1 1 0 0 //12 0 1 1 0 //6 --------------------- 0 1 0 0 //4

5.2按位或

复制代码
1
2
3
4
var a uint8 = 220 var b uint8 = 89 var c = a | b
复制代码
1
2
3
4
5
1 1 0 1 1 1 0 0 //220 0 1 0 1 1 0 0 1 //89 ---------------------------- 1 1 0 1 1 1 0 1 // 221

5.3取反

取反运算符(^)可反转二进制位的值,0–>1,1–>0

复制代码
1
2
100101

取反

复制代码
1
2
01101

无符号整数

复制代码
1
2
3
4
5
6
7
8
var n uint8=27 00011011 var r = ^n 11100100 -->228 27+228=255,即uint8数据类型的最大值 var g uint16=150 0000000010010110 var q = ^g 1111111101101001 -->65385 150+65385=65535,即uint16数据类型的最大值

有符号数

复制代码
1
2
3
4
5
通用公式: c = -(n + 1) 7 ----> -8 -6 ----> 5

5.4位移

复制代码
1
2
3
4
10011101 >>3 结果:00010011 10101001 <<2 10100100 另外注意有符号位的移位

5.5按位异或

复制代码
1
2
3
4
5
6
不相等返回1,相等返回0 var x uint8 = 0b_1011_1101 1 0 1 1 1 1 0 1 var y uint8 = 0b_1110_0001 1 1 1 0 0 0 0 1 r = x ^ y ------------------ 0 1 0 1 1 1 0 0

5.6清除标志位

清楚标志位,就是将特定的二进制位的值变为0.其运算符是 &^

复制代码
1
2
3
4
5
例如,要将11011111最右边三位变为0,代码如下: var k uint8 = 0b_11011111 var r = k &^ 0b_00000111 结果为:11011000

6成员运算符

成员运算符就是(.),也称"筛选"运算符,用于访问某个对象的成员

成员运算符可以访问各种类型对象的成员,但要注意以下情况:

(1).如果对象是指针类型或者接口类型,并且它的值是空引用(nil),那么对此成员的访问会发生错误

程序包管理

1.package语句

2.程序包的结构

​ Go语言是以目录为单位来界定程序包的,因此,在同一级目录下只允许使用一个包名.一个包则可以分布在多个代码文件中

3.导入语句

复制代码
1
2
import <包所在的路径>
复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main import ( ."GoProjects/src/demo/test01" // 如果被导入包内的成员不会与当前代码的成员名称冲突,还可以直接把某包中的成员名称合并到当前文件中. // 实现方法用句点(.)作为被导入包的别名 mu "GoProjects/src/demo/test02" // 如果觉得包名太长,输入时不方便,可以起别名 ) func main() { StartPlay() StopPlay() mu.Play() mu.Pause() }

4.初始化函数

在程序包代码中,可以定义一个名为init的函数,当此包被其他代码导入时,init函数会被调用

init函数的功能仅限于初始化工作,例如给变量赋值.因此,不应该在init函数写过于复杂的代码,尤其是一些消耗时间的代码.

x.go

复制代码
1
2
3
4
5
6
7
8
package test import "fmt" func init() { fmt.Println("part1 - 初始化") }

y.go

复制代码
1
2
3
4
5
6
7
8
package test import "fmt" func init() { fmt.Println("part1 - 初始化") }

main.go

复制代码
1
2
3
4
5
6
7
8
9
package main import _ "GoProjects/src/test" //使用import语句导入test包时,为其分配一个由下划线作为别名.此做法的作用:test包仅仅执行初始化代码,而不会导入其成员.此方法会使init函数被调用 func main() { }
image-20211116233335950

5.模块

复制代码
1
2
3
4
5
若将项目代码放在GOPATH目录(源码位于src子目录下)之外,在使用import语句导入包时,可以使用相对路径 import ../test //父级目录下的test目录 import ../../test //父级的父级目录下的test目录 import ./test //当前目录下的test目录

这种方法不便于管理,一但项目中的结构改变,import语句均需要修改

5.1 go.mod文件的基本结构

5.6 成员的可访问性

Go语言通过成员名称的首字母来决定其可访问性.只有成员名称的首字母为大写时,其他包中的代码才能访问该成员

复制代码
1
2
3
4
5
6
7
8
9
10
package abc func Min(a, b int) int{ } func min_it(a, b int) int { }

Min函数可以被abc外包访问

min_it函数只能在abc内部包访问

以下两个结构体也是一样的规则:

复制代码
1
2
3
4
5
6
7
8
type person struct{ } type Person struct{ }

person结构体只能在当前包内部访问,Person结构体就…

对于结构体的字段成员,首写字母决定其访问性的规则同样适用

golang fmt格式“占位符”

https://studygolang.com/articles/2644

变量与常量

1.变量的初始化

​ (1).声明阶段

var <变量名> <变量类型>

var s string

​ (2).赋值阶段(变量声明后,应用程序会自动为其分配一个0值,对于字符串而言,默认值是nil)

复制代码
1
2
3
4
5
s = "你好" s = "早上好" s = "中午好"

可以对变量s的值进行不限次数的修改,只有最后一个值会被保留

声明时同时赋值

var b int16 = 680 此时,变量b的值为16位整数值680

也可以省略变量类型,由赋值内容自动推断变量类型

var c = 3.14159

程序将自动分析出变量c的值为float64.若担心有误,可以在赋值时进行明确的转换

var d = float32(3.14159)

或者

var d float32 = 3.14159

另外,还有一种更简便的写法,声明变量并初始化

复制代码
1
2
3
4
5
<变量名> := <变量值> f := "xyz" --->string z := 1.5e7 --->float64

2.组合赋值

复制代码
1
2
3
4
var a,b,c = 10,20,30 简约写法: x, y, z := "test", 5, 0.02

调用函数接收多个返回值:

复制代码
1
2
3
4
func test() (string, string, int){ return "abc","xyz",10000 }

调用函数:

复制代码
1
2
3
4
r1,r2,r3 := test() fmt.Println("函数的返回值为: ") fmt.Printf("r1: %vnr2: %vnr3: %vn", r1,r2,r3)

3.匿名变量

如果将变量命名为"_"(单个下划线),那么他就是匿名变量.赋给匿名变量的值会被丢弃,因为他在代码中无法访问.

值"opq"将会被丢弃

a,b,_ := “abc”,“lmn”,“opq”

随后的代码只能访问变量 a 和 b

4.常量

声明常量必须使用const关键字,初始化方法与变量相同

const Val1 int = 0

const Val2 int = 1

const Val3 string = “SPEED”

const Val4 bool = false

变量在其生命周期可以被修改,但常量一旦初始化是不允许修改,

常量的声明也可以省略类型标识,让程序根据初始化的值来进行自动推断 const LockMode = -1

5.批量声明

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//declare three variables var关键字 +" "+ (变量声明) var( k = 0.0001 j = 0.0021 m int16 = 5530 ) //declare three constants const ( XldFirst = "F" XldSecong = "G" XldThird = "H" )

6.变量的作用域(生命周期)

变量的作用域都属于同一个包下,因此可以跨文件访问变量.

代码在访问变量时会"由远及近"的原则

如果不同层次的作用域中存在名称相同的变量时,距离当前代码较近的变量会覆盖距离较远的变量

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
package main import "fmt" var x = "EFG" func main(){ //此处覆盖外部变量x var x = "XYZ" fmt.Println(x) }

7.变量的默认值

int8 0

int16 0

int32 0

int64 0

float32 0

float64 0

string

rune 0

结构体 {m:0 n:0}

接口 nil(空引用)

指针 nil(空引用)

基础类型

1.字符与字符串

与文本相关的数据类型有两个: rune 和 string.rune只能表示单个字符,string可以表示多个字符,称为字符串

1.1rune类型

在Go语言中,他表示单个字符.对于Unicode字符,例如单个汉字,也可以由rune类型表示

var x1 rune = ‘f’

var x2 = ‘G’

var x3 = ‘好’

var x4 rune = ‘@’

var x5 rune = ‘7’

rune常量表达式必须在一堆英文单引号之中,但如果要包含单引号本身,那就需要进行转义(“”),即

var x7 = ‘’’

从builtin包的源代码中能看到,rune类型的声明代码如下:

type rune = int32

这表明rune类型实际上是32为整数的别名.

rune类型不能赋值多个字符,下面的代码会发生错误

var d4 rune = ‘abc’ (x)

1.2string类型

字符串表达式需要写一对双引号(英文双引号),例如

var st string = “zyx”

若自身包含双引号,应当进行转义

var sc = “My name is “TOM””

字符串对象可以包含不定个数的字符,例如:

复制代码
1
2
3
4
1. 包含 0 个字符: 空字符串 1. 包含 1 个字符: 与rune类型表达式相同,但数据类型不同 1. 包含一个以上的字符:

双引号一般用于简单的字符,对于较为复杂的"段落式"字符串,可以使用"`"字符来表示

var sd = `---------------------This is title

…-2021-11-28

-----------------------------This is bottom

`

结果是"`"符号可以让字符串"原封不动"地输出,换行缩进均被保留

2.数值类型

类型描述范围示例
int88位有符号的整数-128~127-1, 50
uint88位无符号的整数0~255128
int1616位有符号的整数-32768~32767-321
uin1616位无符号的整数0~6553520005
int3232位有符号的整数-2147483648~2147483647-15000,3500005
uint3232位无符号的整数0~4294967295857857857
int6464位有符号的整数-9223372036854775808~9223372036854775807
uint6464位无符号的整数0~18446744073709551615
int有符号整数,至少32位. 在32位处理器为32位整数 在64位处理器为64位整数32位处理器与int32类似 64位处理器与int64类似
uint无符号整数,至少32位. 在32位处理器为32位整数 在64位处理器为64位整数32位处理器与int32类似 64位处理器与int64类似
byteuint8的别名,8位无符号整数0~255
float3232位浮点数符合IEEE-754标准
float6464位浮点数符合IEEE-754标准
complex6464位的复数复数的实部与虚部皆为 float32 数值
complex128128复数复数的实部与虚部皆为 float64 数值

2.1获取数值类型占用的内存的大小

单位是字节

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main import ( "unsafe" "fmt" ) var ( n1 uint8 = 122 n2 uin16 = 2000 n3 uin32 = 53530020 n4 uint64 = 4.33e+5 n5 uint = 99977723 ) //调用unsafe包中的Sizeof函数,获取变量所占用的内存大小 fmt.Printf("8位无符号整数: %dn",unsafe.Sizeof(n1)) //1 . //2 . .

2.2整数常量的表示方式

制式说明示例
十进制257_6888_453
二进制"0b"或"0B"前缀0b_10001_1111
八进制"0o"或者"0O"前缀0o7707
十六进制"0x"或者"0X"前缀,对应字母大小写0x5c0a6b,0X39E4D

为了便于阅读,可以分段"_",但是不能出现在数值的开头,也不能出现在结尾

2.3科学计数法

2.4复数

var ca complex128 = 50 + 2i

var cb complex128 = -0.05 - 3i

var cc complex64 = 1.0001 + 0.0005i

var cd complex64 = -300 + 12i

虚部用"i"表示,但是不能大写

3.日期与类型

与日期/时间相关的API都封装在time包中,使用前需要导入包

import “time”

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
package main import ( "fmt" "time" ) func main(){ var n = time.Now() fmt.println(n) }

golang 使用 iota

复制代码
1
2
3
4
5
6
7
8
iota是golang语言的常量计数器,只能在常量的表达式中使用。 iota在const关键字出现时将被重置为0(const内部的第一行之前),const中每新增一行常量声明将使iota计数一次(iota可理解为const语句块中的行索引)。 使用iota能简化定义,在定义枚举时很有用。

1.Month类型

Month类型实际上是以int为基础定义的新类型.time包公开了以下常量,表示一年中的十二个月

const (

​ January Month = 1 + iota

​ February

​ March

​ April

​ May

​ June

​ July

​ August

​ September

​ October

​ November

​ December

​ )

复制代码
1
2
3
//2021年12月2号 22:34:01 var thedate = time.Date(2021,time.December,2,22,34,1,0,time.Local)

var n = time.August

fmt.Printf(“n : %dn”,n)

运行结果:

n : 8

2Weekday类型

基于int定义,表示一个星期的一天

const (

​ Sunday Weekday = iota

​ Monday

​ Tuesday

​ Wednesday

​ Thursday

​ Friday

​ Saturday

)

3Duration类型

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Duration类型代码声明如下: type Duration int64 以64位整数为基础. "Duration"表示的是"时间段"------两个时间点之差 Duration类型以纳秒为单位 const ( Nanosecond Duration = 1 Microsecond = 1000 * Nanosecond Millisecond = 1000 * Microsecond Second = 1000 * Millisecond Minute = 60 * Second Hour = 60 * Minute ) eg: a := 25 * time.Second // 25秒
  1. Hours方法
  2. Minutes方法
  3. Seconds方法
  4. Milliseconds方法
  5. Microseconds方法
  6. Nanoseconds方法

4Time类型

精度为纳秒

  1. Now方法: 获取当前系统时间,返回Time实例 var ct = time.Now()
  2. Date: 通过向函数传入参数来初始化Time实例

var now = time.Now()

year, month, day := now.Date()

hour, minute, second := now.Clock()

Time类型支持时间差运算

var theTime = time.Date(2021,12,2,0,0,0,0,time.Local)

//30h之后

var newTime1 = theTime.Add(30 * time.Hour)

//四天之前

var newTime2 = theTime.Add(-4 * 24 * time.Hour)

5Sleep函数

调用Sleep函数会使当前协程(Go routine)暂停执行,并等待一段时间,然后恢复执行,等待时常由传递给Sleep函数的参数决定,类型为Duration

如果传递的参数值为0或为负值,Sleep函数就会立刻返回,不会等待

//等待3s

time.Sleep(3 * time.Second)

6Timer类型(待理解)

Timer是一种特殊的计时器,当指定的时间到期后,会将当前时间发送到其C字段中.C字段是只读的通道类型(<-chan Time),其他协程(Go routines)将通过C字段接收Time实例(计时器过期时所设置的时间)

Time类型的典型用途是在异步编程中处理操作超时的行为.C字段可视为单个事件的"信号灯",所有等待信号的协程会被阻止,直到C字段中读到Time实例为止

复制代码
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
package main import ("fmt" "math/rand" "time") func main() { //创建新的Timer实例 var timer = time.NewTimer(5 * time.Second) //创建一个通道实例,用于标识任务已完成 var completed = make(chan bool) //退出main函数时关闭通道 defer close(completed) //在新的协程执行任务 go func() { //随机生成任务所需的时间 //作用是模拟任务所消耗的时间 rand.Seed(time.Now().Unix()) var thelong = rand.Intn(10) //暂停当前协程 time.Sleep(time.Duration(thelong) * time.Second()) //发送信号,表示任务已完成 completed <- true }() //判断任务是顺利完成了还是超时了 select { case <- completed: fmt.Println("任务已完成") case <- timer.C: fmt.Println("任务超时") } }

函数

1.函数的定义

复制代码
1
2
3
4
func AreaFun(w, h int) int { return w * h }
复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//无输入参数,有返回值 func getANumber() float32 { return 0.00121 } //有输入参数,无返回值 func setInt(x int){ ... } //无输入参数,无返回值 func hello(){ ... }

格式:

func <函数名称> ([参数列表]) ([返回值列表]){

​ //内部代码

}

注意点:

  1. func关键字是必须的,不能省略
  2. 函数名称一般由字母与数字组成,但不能以数字开头
  3. 输入参数列表是可选的,如果没有参数,也要保留一对小括号
  4. 返回值列表是可选的,若无返回值,可以省略
  5. 函数体写在一对大括号内,与普通代码无异

2.调用函数

[变量列表] = <函数名称> ([参数列表])

  1. 变量列表用于接收函数的返回值
  2. 参数列表根据参数的定义依次传值即可
复制代码
1
2
3
4
func add(x, y int16) int16{ ... }

调用如下:

复制代码
1
2
var result = add(19, 54)

3.return语句

在函数体中,使用return语句可以跳出函数,并把代码执行权返回给调用者.对于无返回值的函数,函数最后的return语句可以省略

4.多个返回值

Go语言支持函数多个返回值

复制代码
1
2
3
4
func getThreeInt() (int, int, int){ return 100, 1001, 2002

调用:

复制代码
1
2
3
4
var a, b, c = getThreeInt() var a,_,_ = getThreeInt() //only need the first value

如果返回值已命名,可以选择性地为他们赋值(未赋值将使用类型的默认值,例如int默认值为0),但是函数体最后必须有return语句

复制代码
1
2
3
4
5
6
7
8
9
10
11
func getSomeStrings() (a, b string){ //为命名地返回值赋值 a = "Part1" b = "Part2" //必须使用return语句让函数返回 return } //调用如下: var s1, s2 = getSomeStrings()

5.可变参数的个数

可变参数的个数只能出现在参数列表的末尾

func test1(a uint8, b …string){

​ fmt.Printf(“参数a: %dn”,a)

​ fmt.Printf(“参数b: %dn”,b)

}

上述函数中,参数b为可变参数,其个数可以是0个或者多个.

test(16)

test(24, “abcd”)

test(3, “Jack”, “Rose”, “Lucifer”)

下面代码中,test2函数中可变个数参数的定义是错误的,因为他不是参数列表的末尾

func test2(p1 string, p2 …bool, p3 float32){ (✕)

}

可变参数的类型为"切片"(slice), 他是以数组为存储基础的集合类型.在函数体内部,可以使用len函数来获取可变参数的个数,也可以使用

for range 语句来循环访问每一个元素.例如:

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
func test3(args ...float32){ n := len(args) fmt.Printf("nn可变参数的个数: %dn",n) //打印元素 if n>0 { fmt.Println("参数内容:") for _,val := range args{ fmt.Printf("%f",val) } } }

6.匿名函数

匿名函数,即没有名称的函数.通常,运用以下两种方法能保证匿名函数可以被访问

  1. 定义一个变量,并且此变量应用匿名函数,当需要匿名函数时,可以通过访问变量来访问.
  2. 定义完立刻调用.这种方法使得匿名函数只能调用一次
复制代码
1
2
3
4
var myfun = func (x, y int) int{ return x*x + y*y }
复制代码
1
2
3
//因为函数没有名称,所以在调用时需要通过变量myfun来访问 res : = myfun(2, 4)

再看一个例子,定义立即调用

复制代码
1
2
3
4
func (who string){ fmt.Prinf("Hello, %sn",who) }("Jack")
复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package main import "fmt" func main() { //创建新的通道对象 var ch = make(chan byte) go func() { fmt.Println("新协程") //向通道对象发送数据 ch<-1 }() //从通道对象接收数据 <-ch fmt.Println("主协程") }

​ 执行一个新的协程,方法就是在函数调用代码前加上go关键字.加上通道对象(channel)来解决此问题.问题是向通道对象发送数据,数据在主协程中接收

​ 在启动新协程后,主协程代码执行到<-ch这一行,此时通道对象中没有数据,代码会一直处于等待状态.

7.将函数作为参数传递

流程控制

1.顺序执行

2.if语句

if <条件> {<代码块>}

复制代码
1
2
3
4
5
6
7
var k string = "check" if strings.Contains(k,"ch"){ fmt.Printf("字符串 %s 中包含chn", k) } else { fmt.Printf("字符串 %s 中不包含chn", k) }

3.switch语句

3.1基于表达式的switch语句

复制代码
1
2
3
4
5
6
7
8
9
10
var mode = 1 switch mode { case 0: fmt.Println("关机状态") case 1: fmt.Println("开机状态") case 2: fmt.Println("待机状态") }

3.2基于类型构建的switch语句

switch语句还可以用变量的类型作为参考表达式,只要某个case语句所指定的类型与表达式所返回的类型相同,该case语句所对应的分支代码就会执行

复制代码
1
2
3
4
5
6
var x interface{} = "hello" actvalue := x.(string) 变量 x 声明为interface{}类型(空白接口类型,可兼容任意类型),成为具有动态类型的变量.随后赋给他的实际值string类型,它将动态引用一个string实例. x.(string)表达式完成类型断言,并把变量x引用的值转换成string类型,赋值给actvalue变量.虽然在运行阶段变量x和actvalue的值相同,但他们的数据类型不同 var x interface{} = ...... var actvalue string = .....

而基于类型的switch,要求使用关键字type来代替具体类型,并且作为参考表达式的变量必须声明为接口类型

复制代码
1
2
3
4
5
6
7
8
9
10
11
var c interface{} = 12 switch v := c.(type) { case string: fmt.Printf("字符串 : %sn", v) case uint8: fmt.Printf("无符号整数 : %dn", v) case int: fmt.Printf("有符号整数 : %dn", v) }

对于自定义接口类型,同样可以用switch语句做类型分析

接口的实现原则是必须包含结构体的方法.

3.3fallthrough语句

switch语句在运行阶段只会选择一个case语句执行,即使有多个字句匹配成功,他也只会选择最先匹配的那个分支执行

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
n := 58 switch { case n<100: fmt.Println("该值小于100") case n<80: fmt.Println("该值小于80") case n<50: fmt.Println("该值小于50") case n<30: fmt.Println("该值小于30") }

输出结果: 该值小于100

加上fallthrough

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
n := 58 switch { case n<100: fmt.Println("该值小于100") fallthrough case n<80: fmt.Println("该值小于80") // fallthrough case n<50: fmt.Println("该值小于50") case n<30: fmt.Println("该值小于30") }

输出结果:

该值小于100
该值小于80

4.for语句

1.仅带条件子句的for

复制代码
1
2
3
4
5
6
var q = 1 for q <= 10 { fmt.Printf("q 的当前值为: %dn",q) q++ }

2.带三个子句的for

复制代码
1
2
3
4
for [初始化子句] ; [条件子句] ; [更新子句] { ...... }
复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
for i := 0; i < 12; i += 2 { fmt.Print(i," ") } fmt.Println() var cc = 'a' for ; cc <= 122 ; cc++ { fmt.Printf("%c", cc) } fmt.Println() //变量cc是rune类型(表示单个字符), 他是int32类型的别名,所以执行cc++运算并不会报错,122是z的ASCII码 for x := 'Z'; x >= 65; { fmt.Printf("%c",x) x-- }

0 2 4 6 8 10
abcdefghijklmnopqrstuvwxyz
ZYXWVUTSRQPONMLKJIHGFEDCBA

3.枚举集合元素语句

​ 当for语句带有 range 子句时,它可以通过循环依次从以下对象取出所有值: 字符串(string), 数组(array), 切片(slice), 映射(map)以及(channel)中接收到的值

复制代码
1
2
3
4
5
6
var str string = "天生我才必有用" for i, x := range str{ fmt.Printf("%2d --------> %cn",i, x) } fmt.Print(len(str))

0 --------> 天
3 --------> 生
6 --------> 我
9 --------> 才
12 --------> 必
15 --------> 有
18 --------> 用
21

解释: golang中string底层是通过byte数组实现的。中文字符在unicode下占2个字节,在utf-8编码下占3个字节,而golang默认编码正好是utf-8。

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var arr = [5]float32{ 1.00085, 7.001, 0.0095, 205.33, 0.213, } for index, element := range arr{ fmt.Printf("[%d]: %fn", index, element) } 结果: [0]: 1.000850 [1]: 7.001000 [2]: 0.009500 [3]: 205.330002 [4]: 0.213000

上面代码首先实例化一个float32数组,然后使用for…range循环语句枚举出所有元素,输出结果包含索引和索引对应的元素.

在使用range字句时,如果只有一个变量接收枚举出来的内容,那么该变量将存储索引值

复制代码
1
2
3
4
for index := range arr { fmt.Printf("[%d]: %fn",index, arr[index]) }

如果不需要索引,for…range循环也可以这样写:

复制代码
1
2
3
4
for _,element := range arr{ ... }

每一轮循环只在element变量中存储元素内容,而索引会被丢弃

映射(map)对象的元素由key和value组成,使用range子句在单次循环中会到两个值,即key和value

复制代码
1
2
3
4
5
var m = map[rune]string{'a':"at", 'b':"bee", 'c':"cut"} for key, value := range m { fmt.Printf("[%c]: %sn", key, value) }

在上面的代码,定义的map对象以rune类型key, string类型为value, 包含三个元素.for 循环会得到每个元素的key和value,然后将其输出

range子句也可以枚举通道对象的值,每一轮循环读取一个数值,直到通道对象被关闭.例如:

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//创建通道对象的实例 var ch = make (chan int) //启动新的协程 go func() { //当前代码退出该范围时关闭通道对象 defer close(ch) //向通道发送内容 ch <- 1 ch <- 2 ch <- 3 ch <- 4 ch <- 5 ch <- 6 }() //从通道对象中读出所有值 for v := range ch { fmt.Printf("从通道对象中读出: %dn", v) }

上面代码开启新的协程来向通道对象发送内容,并在主协程通过fo…range循环读出所有的值,调用close函数是关闭通道对象,在通道发送完内容必须显示关闭它,否则,for循环会无限等待新的内容,而通道对象自身也在等待写的内容写入,造成"死锁"现象.加上defer关键字后会使close函数的调用被延迟**(退出当前匿名函数时调用)**

4.contiue与break语句

contiue子句会跳过当前一轮的循环,并从下一轮循环更新的子句开始执行

复制代码
1
2
3
4
5
6
7
for a := 10; a > 0; a-- { if a == 6 || a == 5 || a == 4 { continue } fmt.Println(a) }

10
9
8
7
3
2
1

复制代码
1
2
3
4
5
6
7
for a := 10; a > 0; a-- { if a == 6 || a == 5 || a == 4 { break } fmt.Println(a) }

10
9
8
7

5.代码跳转

在函数内部可以为有特殊用途的代码分配一个标签(label),位于同一函数的其他代码可以使用goto语句进行代码的跳转,并从此处开始执行.

1.代码标签与goto语句
复制代码
1
2
3
4
5
6
7
8
9
10
11
12
var str = "uvwxyz" if len(str) >= 3 { goto L1 } else { goto L2 } L1: fmt.Println("字符串的长度符合要求") L2: fmt.Println("字符串的长度不足3字节")

字符串的长度符合要求
字符串的长度不足3字节

这样的结果与预期不符,解决方法在L1标签的代码块最后加上return语句

复制代码
1
2
3
4
5
6
L1: fmt.Println("字符串的长度符合要求") return L2: fmt.Println("字符串的长度不足3字节")
2.break,continue语句和代码跳转

接口与结构体

1.自定义类型

定义新类型要用到type关键字,和定义变量相似,type关键字可以单行使用,一行定义一个类型,也可以放到一堆小括号内,一次性定义多个类型.

复制代码
1
2
3
4
type myType1 ...... type myType2 ...... type myType3 ......
复制代码
1
2
3
4
5
6
type ( myType1 ...... myType2 ...... myType3 ...... )

可以基于现有的类型来定义新的类型,例如下面的代码基于string类型定义了新的类型name

type name string

name类型与string类型的用法相同,但他们是独立的类型,不妨通过下面的示例来验证

复制代码
1
2
3
4
5
6
7
type name string var a string = "abcde" var b name = "abcde" fmt.Printf("变量a的类型: %Tn",a) fmt.Printf("变量b的类型: %Tn",b)

变量a的类型: string
变量b的类型: main.name

尽管变量a, b引用的内容相同,但由于所属的类型不同,不能进行比较运算.a == b代码会发生错误

当基于现有类型所定义的新类型也无法满足需求时,还可以定义结构体,接口,函数等类型.

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//结构体 type car struct{ id uint color uint32 } //接口 type sender interface { writeTo(d string, len int, msg string) } //函数 type otherFunc func(x float32) float32

在定义类型时,如果使用了赋值运算符,那表明所定义的类型的仅仅是现有类型的别名,而不是全新的类型.正如下面例子rune 是 int32类型的别名,所以rune类型与int32类型的变量可以进行比较运算

复制代码
1
2
3
4
5
var x rune = 'H' var y int32 = 72 fmt.Printf("x和y的值相等吗? %t",x == y) //true

2.结构体

复制代码
1
2
3
4
5
6
7
8
type person struct { name string age uint8 weight float32 height float32 gender uint8 }

1.结构体的定义

type <结构体名称> struct { <字段名称> }

字段列表的内容可以是可选的,即可以定义没有字段成员的结构体

type atbWorker struct { }

即使字段为空,一对大括号也不能省略

若希望结构体的字段成员能被其他包的代码访问,除了结构体自身的名称大小写需要首字母大写外,其字段成员的名称也要首字母大写.

复制代码
1
2
3
4
5
6
7
type Student Struct { StdID uint Name string Age uint8 email string //eamil字段只能在当前包中使用 }

2.结构体的实例化

结构体的实例化有多种代码格式,总体可以归纳为两大类----默认初始化和手动初始化

假设有一个fileInfo结构体,用于封装一个数据文件的相关信息

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
type fileInfo struct { name string size uint64 isSysFile bool createTime int64 } //为字段分配默认值 var x fileInfo //输出字段的值 fmt.Printf("文件名: %+vn", x.name) // %+v 打印结构体时,会添加字段名 fmt.Printf("文件大小: %dn", x.size) // %d 十进制表示 fmt.Printf("是否为系统文件: %tn", x.isSysFile)// %t true 或 false。 fmt.Printf("创建时间: %sn", time.Unix(x.createTime,0))//%s 输出字符串表示(string类型或[]byte)

文件名:
文件大小: 0
是否为系统文件: false
创建时间: 1970-01-01 08:00:00 +0800 CST

也可以这样写

复制代码
1
2
3
4
var x = fileInfo{} x := fileInfo{}

要注意的是:如果声明变量时使用的是指针,那么变量的默认值是nil.此时若直接访问fileInfo就会发生错误,因为指针未引用任何对象,即空指针

复制代码
1
2
3
var px *fileInfo //nil fmt.Printf("文件名: %sn", px.name) //错误

结构体实例化通常需要为字段进行赋值,例如

复制代码
1
2
3
4
5
6
var y fileInfo y.name = "dmd.txt" y.isSysFile = false y.size = 6955236 y.createTime = time.Date(2022, 1, 22, 14, 57, 0, 0, time.Local).Unix()

这种方法是先定义变量,分配默认值,然后逐个进行赋值.当然,也可以在定义变量后直接赋值

复制代码
1
2
var g = fileInfo{ name:"abc.txt", size:128880, ....}

或者可以将代码分开,多行输入

在多行初始化语句中,最后一个末尾的逗号不能省略

在许多时候,某些字段的默认值正是所需要的值,这种情况就可以忽略部分字段的值

复制代码
1
2
3
4
5
6
K := fileInfo { name: "dex.txt", size: 3006265, createTime: time.Date(2020, 1 ,1, 23, 15, 4 ,0 ,time.Local).Unix() }

最后,最简洁的写法,但是要注意一一对应,既不能忽略部分字段

复制代码
1
2
var z = fileInfo{"text.dat",1172362,true,time.Now().Unix()}

如果变量的类型声明为指针类型,那么可以先创建fileInfo实例并完成初始化,然后再用取地址运算符获取地址,再将赋值给指针变量

复制代码
1
2
3
var c = fileInfo{"text.dll",1172362,true,time.Now().Unix()} var pc *fileInfo = &c

也可以一步完成

复制代码
1
2
var pc *fileInfo = &fileInfo{"text.dl",1172362,true,time.Now().Unix()}

3.方法

结构体的方法对象并不是在结构体内部定义的,而是在结构体外部以函数的形式定义的.

复制代码
1
2
3
4
5
6
7
8
type test struct { } func (o test) doSomething() string{ return "do nothing" }

方法与一般函数有一点不同,在方法名称前有一个接收参数(上面实例的o参数).该参数传递的是方法所属结构体的实例.

方法调用:

复制代码
1
2
3
var n test s := n.doSomething()

在定义方法时,接受的结构体实例可以是指针类型

复制代码
1
2
3
4
func (o *test) doSomething2() string{ return "do nothind - 2" }

接收结构体的实例的参数何时使用指针类型,这取决于应用场景.区别如下:

  1. 定义demo结构体,它包含data字段成员
复制代码
1
2
3
4
type demo struct { data int }
  1. 为demo结构体定义两个方法.其中setIntV1方法在接受对象参数时只复制demo实例,而setIntV2方法接受的是demo类型的指针,传递的是实例内存的地址
复制代码
1
2
3
4
5
6
7
8
func (x demo) setIntV1(n int){ x.data = n } func (x *demo) setIntV2(n int){ x.data = n }
  1. 初始化demo实例
复制代码
1
2
var a = demo{data:100}
  1. 分别调用setIntV1方法和setIntV2方法,并输出调用前后data的字段的值
复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//情况一: 非指针类型接收demo实例 fmt.Println("---------------传递demo实例的副本--------------------") fmt.Printf("调用setIntV1方法前,data字段的值: %dn", a.data) //调用setIntV1方法 a.setIntV1(200) fmt.Printf("调用setIntV1方法后,data字段的值: %dnn", a.data") //情况二: 以指针类型接收demo实例 fmt.Println("---------------传递demo实例的内存地址--------------------") fmt.Printf("调用setIntV2方法前,data字段的值: %dn", a.data) //调用setIntV2方法 a.setIntV2(200) fmt.Printf("调用setIntV2方法后,data字段的值: %dnn", a.data")

---------------传递demo实例的副本--------------------
调用setIntV1方法前,data字段的值: 100
调用setIntV1方法后,data字段的值: 100

---------------传递demo实例的内存地址------------------
调用setIntV2方法前,data字段的值: 100
调用setIntV2方法后,data字段的值: 200

结论: **当需要在方法内部修改结构体对象的字段时,应该传递该结构体的指针.**如果只是读取结构体字段的值,传递给方法的实例可以是指针类型也可以不用指针类型

3.接口

接口仅包含无实现代码的方法列表.接口能够起到约束和规范类型成员的作用.声明为接口类型的变量可以引用任何与该接口兼容的对象,即被应用的对象类型必须存在与接口类型一致的方法列表.

1.接口的定义

接口只有方法成员,不能包含字段,而且方法中不能包含实现代码.接口类型自身不能实例化,声明变量后默认分配的值是nil

格式:

复制代码
1
2
3
4
type <接口名称> interface { <方法列表> }

例如:

复制代码
1
2
3
4
5
6
type task interface { start() stop() uint16 timeout(long int64) bool }

要注意:在接口声明方法时不需要func关键字,也没有实例对象接收参数,只需要提供方法名称,参数,返回值等特征描述.

方法的命名必须是有效的,而且同一个接口中不能出现重复命名的方法.

复制代码
1
2
3
4
5
type runner interface{ getContext(key string) (uint64, bool) getContext(key int) (uint8, bool) }

runner接口中,两个getContext方法虽然参数类型与返回值不同,但是他们名称相同,在Go语言中无法通过编译,错误信息如下:

duplicate method getContext

使用空白标识符_作为方法名称是不允许的,因为这样的命名将无法访问.

复制代码
1
2
3
4
5
type musicHub interface { play(track uint)(stat int) _(title string) int //方法名称无效 }
复制代码
1
2
3
4
5
6
7
8
9
10
type test interface { sendMessage(head, body string) int } func main() { var ix test fmt.Printf("接口类型变量的默认值: %v",ix) }

接口类型变量的默认值:

2.接口的实现

如果类型T的方法列表与接口T完全一致(方法名称,参数,返回值类型皆相同),那么就可以说类型T实现了接口F.类型T的实例可以赋值给F类型变量.

下面代码中,interLockercustLocker结构体都实现了Locker接口.

复制代码
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
type Locker interface { Lock() uint16 Unlock(id uint16) } //下面两个结构体均实现了Locker接口 type interLocker struct { lockID uint16 } func (l *interLocker) Lock() uint16 { l.lockID++ fmt.Printf("系统已锁定") return l.lockID } func (l *interLocker) Unlock(id uint16) { if id!=l.lockID { fmt.Println("锁定标识不匹配") return } fmt.Println("系统已解锁") } type custLocker struct{ locked bool } func (l *custLocker) Lock() uint16{ fmt.Println("线程已锁定") return 0 } func (l *custLocker) Unlock(id uint16){ fmt.Println("线程已解锁") }

最后

以上就是迅速花瓣最近收集整理的关于Go语言学习(面向区块链)golang fmt格式“占位符”的全部内容,更多相关Go语言学习(面向区块链)golang内容请搜索靠谱客的其他文章。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(86)

评论列表共有 0 条评论

立即
投稿
返回
顶部