第一个Go语言程序

gopath目录gopath目录就是我们存储我们所编写源代码的目录.该目录下有三个字目录:src, bin, pkg.
src--->里面每一个子目录,就是一个包.包含Go的源码文件
pkg--->编译后生成的,包含目标文件
bin--->生成可执行文件
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四则运算符

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

2.2取余运算符

%

2.3指数运算

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

Pow函数的声明如下:

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

func Pow10(n int) float64 10 ^ n.

//计算5的立方
result := math.Pow(5, 3)
fmt.Printf("5的3次方: %d\n",int(result))

注意:

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

2.4自增与自减运算符

3比较运算符

4逻辑运算符

5位运算符

5.1按位与

var (m int8 = 12n int8 = 6)将变量m, n进行与运算
result := m & n
1 1 0 0       //12
0 1 1 0       //6
---------------------
0 1 0 0       //4

5.2按位或

var a uint8 = 220
var b uint8 = 89
var c = a | b
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

100101

取反

01101

无符号整数

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数据类型的最大值

有符号数

通用公式:
c = -(n + 1)
7 ----> -8
-6 ---->  5

5.4位移

10011101 >>3  结果:00010011
10101001 <<2      10100100
另外注意有符号位的移位

5.5按位异或

不相等返回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.其运算符是 &^

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

6成员运算符

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

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

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

程序包管理

1.package语句

2.程序包的结构

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

3.导入语句

import <包所在的路径>
package mainimport (."GoProjects/src/demo/test01"   // 如果被导入包内的成员不会与当前代码的成员名称冲突,还可以直接把某包中的成员名称合并到当前文件中.// 实现方法用句点(.)作为被导入包的别名mu "GoProjects/src/demo/test02"  // 如果觉得包名太长,输入时不方便,可以起别名
)
func main() {StartPlay()StopPlay()mu.Play()mu.Pause()}

4.初始化函数

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

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

x.go

package testimport "fmt"func init()  {fmt.Println("part1 - 初始化")
}

y.go

package testimport "fmt"func init()  {fmt.Println("part1 - 初始化")
}

main.go

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

5.模块

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

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

5.1 go.mod文件的基本结构

5.6 成员的可访问性

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

package abcfunc Min(a, b int) int{}func min_it(a, b int) int {}

Min函数可以被abc外包访问

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

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

type person struct{}type Person struct{}

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

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

golang fmt格式“占位符”

https://studygolang.com/articles/2644

变量与常量

1.变量的初始化

​ (1).声明阶段

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

var s string

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

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

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

<变量名>  :=  <变量值>f := "xyz"   --->string
z := 1.5e7   --->float64

2.组合赋值

var a,b,c = 10,20,30
简约写法:
x, y, z := "test", 5, 0.02

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

func test() (string, string, int){return "abc","xyz",10000
}

调用函数:

r1,r2,r3 := test()
fmt.Println("函数的返回值为: ")
fmt.Printf("r1: %v\nr2: %v\nr3: %v\n", 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.批量声明

//declare three variables     var关键字 +" "+ (变量声明)
var(k = 0.0001j = 0.0021m int16 = 5530)//declare three constants
const (XldFirst = "F"XldSecong = "G"XldThird = "H")

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

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

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

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

package mainimport "fmt"var x = "EFG"func main(){//此处覆盖外部变量xvar 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. 包含 0 个字符: 空字符串1. 包含 1 个字符: 与rune类型表达式相同,但数据类型不同1. 包含一个以上的字符:

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

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

…-2021-11-28

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

`

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

2.数值类型

类型 描述 范围 示例
int8 8位有符号的整数 -128~127 -1, 50
uint8 8位无符号的整数 0~255 128
int16 16位有符号的整数 -32768~32767 -321
uin16 16位无符号的整数 0~65535 20005
int32 32位有符号的整数 -2147483648~2147483647 -15000,3500005
uint32 32位无符号的整数 0~4294967295 857857857
int64 64位有符号的整数 -9223372036854775808~9223372036854775807
uint64 64位无符号的整数 0~18446744073709551615
int 有符号整数,至少32位. 在32位处理器为32位整数 在64位处理器为64位整数 32位处理器与int32类似 64位处理器与int64类似
uint 无符号整数,至少32位. 在32位处理器为32位整数 在64位处理器为64位整数 32位处理器与int32类似 64位处理器与int64类似
byte uint8的别名,8位无符号整数 0~255
float32 32位浮点数 符合IEEE-754标准
float64 64位浮点数 符合IEEE-754标准
complex64 64位的复数 复数的实部与虚部皆为 float32 数值
complex128 128复数 复数的实部与虚部皆为 float64 数值

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

单位是字节

package mainimport ("unsafe""fmt")var ( n1 uint8 = 122n2 uin16 = 2000n3 uin32 = 53530020n4 uint64 = 4.33e+5n5 uint = 99977723)//调用unsafe包中的Sizeof函数,获取变量所占用的内存大小fmt.Printf("8位无符号整数: %d\n",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”

package mainimport ("fmt""time")func main(){var n = time.Now()fmt.println(n)
}

golang 使用 iota

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

​ )

//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 : %d\n”,n)

运行结果:

n : 8

2Weekday类型

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

const (

​ Sunday Weekday = iota

​ Monday

​ Tuesday

​ Wednesday

​ Thursday

​ Friday

​ Saturday

)

3Duration类型

Duration类型代码声明如下:
type Duration int64
以64位整数为基础.      "Duration"表示的是"时间段"------两个时间点之差
Duration类型以纳秒为单位
const (Nanosecond Duration = 1Microsecond = 1000 * NanosecondMillisecond = 1000 * MicrosecondSecond =      1000 * MillisecondMinute =      60 * SecondHour =        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实例为止

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.函数的定义

func AreaFun(w, h int) int {return w * h
}
//无输入参数,有返回值
func getANumber() float32 {return 0.00121
}//有输入参数,无返回值
func setInt(x int){...
}//无输入参数,无返回值
func hello(){...
}

格式:

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

​ //内部代码

}

注意点:

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

2.调用函数

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

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

调用如下:

var result = add(19, 54)

3.return语句

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

4.多个返回值

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

func getThreeInt() (int, int, int){return 100, 1001, 2002

调用:

var a, b, c = getThreeInt()var a,_,_ = getThreeInt()  //only need the first value

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

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: %d\n”,a)

​ fmt.Printf(“参数b: %d\n”,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 语句来循环访问每一个元素.例如:

func test3(args ...float32){n := len(args)fmt.Printf("\n\n可变参数的个数: %d\n",n)//打印元素if n>0 {fmt.Println("参数内容:")for _,val := range args{fmt.Printf("%f",val)}}
}

6.匿名函数

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

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

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

func (who string){fmt.Prinf("Hello, %s\n",who)
}("Jack")
package mainimport "fmt"func main() {//创建新的通道对象var ch = make(chan byte)go func() {fmt.Println("新协程")//向通道对象发送数据ch<-1}()//从通道对象接收数据<-chfmt.Println("主协程")
}

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

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

7.将函数作为参数传递

流程控制

1.顺序执行

2.if语句

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

var k string = "check"
if strings.Contains(k,"ch"){fmt.Printf("字符串 %s 中包含ch\n", k)
} else {fmt.Printf("字符串 %s 中不包含ch\n", k)
}

3.switch语句

3.1基于表达式的switch语句

var mode = 1switch mode {case 0:fmt.Println("关机状态")case 1:fmt.Println("开机状态")case 2:fmt.Println("待机状态")}

3.2基于类型构建的switch语句

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

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来代替具体类型,并且作为参考表达式的变量必须声明为接口类型

var c interface{} = 12switch v := c.(type) {case string:fmt.Printf("字符串 : %s\n", v)case uint8:fmt.Printf("无符号整数 : %d\n", v)case int:fmt.Printf("有符号整数 : %d\n", v)}

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

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

3.3fallthrough语句

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

n := 58switch  {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

n := 58switch  {case n<100:fmt.Println("该值小于100")fallthroughcase n<80:fmt.Println("该值小于80")// fallthroughcase n<50:fmt.Println("该值小于50")case n<30:fmt.Println("该值小于30")}

输出结果:

该值小于100
该值小于80

4.for语句

1.仅带条件子句的for

var q = 1for q <= 10 {fmt.Printf("q 的当前值为: %d\n",q)q++}

2.带三个子句的for

for [初始化子句] ; [条件子句] ; [更新子句] {......
}
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)中接收到的值

var str string = "天生我才必有用"for i, x := range str{fmt.Printf("%2d --------> %c\n",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。

var arr = [5]float32{1.00085,7.001,0.0095,205.33,0.213,}for index, element := range arr{fmt.Printf("[%d]: %f\n", index, element)}结果:
[0]: 1.000850
[1]: 7.001000
[2]: 0.009500
[3]: 205.330002
[4]: 0.213000

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

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

for index := range arr {fmt.Printf("[%d]: %f\n",index, arr[index])}

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

for _,element := range arr{...
}

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

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

var m = map[rune]string{'a':"at", 'b':"bee", 'c':"cut"}
for key, value := range m {fmt.Printf("[%c]: %s\n", key, value)
}

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

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

 //创建通道对象的实例var ch = make (chan int)//启动新的协程go func() {//当前代码退出该范围时关闭通道对象defer close(ch)//向通道发送内容ch <- 1ch <- 2 ch <- 3ch <- 4 ch <- 5ch <- 6}()//从通道对象中读出所有值for  v := range ch {fmt.Printf("从通道对象中读出: %d\n", v)}

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

4.contiue与break语句

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

for a := 10; a > 0; a-- {if a == 6 || a == 5 || a == 4 {continue}fmt.Println(a)}

10
9
8
7
3
2
1

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语句
 var str = "uvwxyz"if len(str) >= 3 {goto L1} else {goto L2}L1:fmt.Println("字符串的长度符合要求")
L2:fmt.Println("字符串的长度不足3字节")

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

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

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

接口与结构体

1.自定义类型

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

type myType1 ......
type myType2 ......
type myType3 ......
type (myType1 ......myType2 ......myType3 ......
)

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

type name string

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

type name stringvar a string = "abcde"
var b name = "abcde"
fmt.Printf("变量a的类型: %T\n",a)
fmt.Printf("变量b的类型: %T\n",b)

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

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

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

//结构体
type car struct{id uintcolor uint32
}//接口
type sender interface {writeTo(d string, len int, msg string)
}//函数
type otherFunc func(x float32) float32

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

var x rune = 'H'
var y int32 = 72fmt.Printf("x和y的值相等吗? %t",x == y)  //true

2.结构体

type person struct {name stringage uint8weight float32height float32gender uint8
}

1.结构体的定义

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

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

type atbWorker struct { }

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

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

type Student Struct {StdID uintName stringAge uint8email string   //eamil字段只能在当前包中使用
}

2.结构体的实例化

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

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

type fileInfo struct {name stringsize uint64isSysFile boolcreateTime int64
}//为字段分配默认值var x fileInfo//输出字段的值fmt.Printf("文件名: %+v\n", x.name)           //  %+v 打印结构体时,会添加字段名fmt.Printf("文件大小: %d\n", x.size)        //  %d      十进制表示fmt.Printf("是否为系统文件: %t\n", x.isSysFile)//  %t          true 或 false。fmt.Printf("创建时间: %s\n", time.Unix(x.createTime,0))//%s      输出字符串表示(string类型或[]byte)

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

也可以这样写

var x = fileInfo{}x := fileInfo{}

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

var px *fileInfo                          //nil
fmt.Printf("文件名: %s\n", px.name)        //错误

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

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()

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

var g = fileInfo{ name:"abc.txt", size:128880, ....}

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

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

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

K := fileInfo {name: "dex.txt",size: 3006265,createTime: time.Date(2020, 1 ,1, 23, 15, 4 ,0 ,time.Local).Unix()
}

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

var z = fileInfo{"text.dat",1172362,true,time.Now().Unix()}

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

var c = fileInfo{"text.dll",1172362,true,time.Now().Unix()}
var pc *fileInfo = &c

也可以一步完成

var pc *fileInfo = &fileInfo{"text.dl",1172362,true,time.Now().Unix()}

3.方法

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

type test struct {}func (o test) doSomething() string{return "do nothing"
}

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

方法调用:

var n test
s := n.doSomething()

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

func (o *test) doSomething2() string{return "do nothind - 2"
}

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

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

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

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

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

3.接口

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

1.接口的定义

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

格式:

type <接口名称> interface {<方法列表>
}

例如:

type task interface {start()stop() uint16timeout(long int64) bool
}

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

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

type runner interface{getContext(key string) (uint64, bool)getContext(key int) (uint8, bool)
}

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

duplicate method getContext

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

type musicHub interface {play(track uint)(stat int)_(title string) int                 //方法名称无效
}
type test interface {sendMessage(head, body string) int
}func main() {var ix testfmt.Printf("接口类型变量的默认值: %v",ix)}

接口类型变量的默认值:

2.接口的实现

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

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

type Locker interface {Lock() uint16Unlock(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语言学习(面向区块链)相关推荐

  1. 使用Java语言从零开始创建区块链

    使用Java语言从零开始创建区块链 2018年04月01日 17:08:12 大侠区块链 阅读数:1312 标签: java区块链java区块链 更多 个人分类: 区块链 Java区块链开发与交流群: ...

  2. 我们为什么用GO语言来做区块链?

    作者:思想的苇草 在区块链公链的开发圈子里,我们找到了一些流行的编程语言,有C++.Golang.Python和最近新起的Rust等等. 我们稍微对比较有名的项目采用的编程语言做个统计,如下图: 老一 ...

  3. 联邦学习vs区块链:谁是“可信媒介”技术领域最强王者?

    在互联网新浪潮中,联邦学习和区块链是最受关注的两项热门技术.联邦学习是一种在大数据服务中保护隐私的分布式机器学习技术,区块链是一种在去中心化网络中实现价值转移的分布式账本技术.那么问题来了,谁是可信媒 ...

  4. 【联邦学习+区块链】《联邦学习vs区块链:谁是“可信媒介”技术领域最强王者?》疑问解答

    联邦学习[1]VS 区块链 [问1]联邦学习,何为"联邦"? 作为一种分布式机器学习技术,联邦学习可以实现各个企业的自有数据不出本地,而是通过加密机制下的参数交换方式共建模型,即在 ...

  5. 【联邦学习 + 区块链】《联邦学习vs区块链:谁是“可信媒介”技术领域最强王者?》阅读记录与提问

    [注]块引用部分是博主自己的思考.. 题目:<联邦[1]学习vs区块链:谁是"可信媒介"技术领域最强王者?> [问1]联邦学习,何为"联邦"? 在互 ...

  6. 【面向区块链应用的Java编程】

    目录 面向区块链应用的Java编程 1.什么是区块链 2.如何验证区块链 3.如何挖掘区块 4.区块链的工作方式 5.区块链的应用 5.1.比特币 5.2.智能合约 5.3.医疗 5.4.制造业和供应 ...

  7. 面向区块链的高效物化视图维护和可信查询

    面向区块链的高效物化视图维护和可信查询 人工智能技术与咨询 来源:<软件学报> ,作者蔡 磊等 摘 要:区块链具有去中心化.不可篡改和可追溯等特性,可应用于金融.物流等诸多行业.由于所有交 ...

  8. C 语言实现简易区块链

    C 语言实现简易区块链 总结:C 语言真不是我这种菜鸡所能驾驭的- 无奈哈希函数太麻烦,就采用 base64 替代下哈希函数吧,其他符合区块链理论 #include <stdio.h> # ...

  9. 【联邦学习+区块链】联邦学习与区块链

    前言:联邦学习(Federated Learning)是在保障大数据交换时的信息安全.保护终端数据和个人数据隐私.保证合法合规的前提下,在多参与方或多计算节点之间开展高效率的机器学习的一种新兴人工智能 ...

  10. 通过JavaScript学习构建区块链

    通过JavaScript学习构建区块链 [中英双语]通过JavaScript 学习构建区块链 用 JavaScript 编程语言编写您自己的区块链和去中心化网络. 此教程共8.0小时,中英双语字幕,画 ...

最新文章

  1. 数据库专家Michael Stonebraker获得2014年图灵奖
  2. uva-10305-水题-拓扑排序
  3. MarkDown总结(适合初学者快速入门)
  4. Layui 获取表单提交数据
  5. JUnit5 测试套件示例
  6. 添加删除程序里面没有添加IIS服务的选项
  7. ROS防止外网DDOS的最有效方法
  8. 包邮送50本数据分析、MySQL、Python相关书籍!
  9. 正则系列1: re.match用法
  10. hotnets2018 Networking in Heaven as on Earth 阅读报告
  11. u盘容量变小了是什么原因?怎么恢复数据?
  12. php 什么是占位符,php中的占位符
  13. 微信公众平台nbsp;示例代码nbsp;分析
  14. 除了Kaggle,这里还有一些高质量的数据科学竞赛平台
  15. Unity_Lua_语法基础
  16. mysql字符集以及字符集错误
  17. [SAP ABAP开发技术总结]屏幕跳转
  18. 物联网下的RFID门禁,图书防盗新变革
  19. opencv幻灯片代码
  20. 三次握手和四次挥手(面试必问)

热门文章

  1. 服务器 备案 文档,备案需要备案服务器
  2. AI 重聚知名已故歌手,发布四首原创歌曲
  3. 期货交易理念有哪些?
  4. 自动化运维工具——ansile详解
  5. 哥德巴赫猜想 php,哥德巴赫猜想的程序验证
  6. 微信公众号申请、微信支付申请教程
  7. win10如何打开本地组策略编辑器
  8. 写给小温——当繁花落尽的一刻
  9. CityEngine中如何导出带有属性信息的slpk
  10. 【neusoft】 Linux 的学习与使用