Go语言学习笔记(上)

教程网址

Go 语言教程 | 菜鸟教程 (runoob.com)

开发环境

系统:Win10 64位;
go版本:go1.14.windows-amd64;
go开发软件版本:GoLand 2020.1 x64;

环境部署

1、下载 go 1.14.windows-amd64 安装包,注意一定要下载这个版本,高版本的go安装后,GoLand 2020识别不了,会报“所选目录不是 Go SDK 的有效主路径”错误。

Downloads - go.dev

2、设置GoLand软件的环境变量 ;

File | Settings | Go | GOROOT,在列表中选择 Go 1.14

3、创建一个工程;

File | New | Project,选择项目文件保存路径。

Go 语言结构

Go 语言的基础组成有以下几个部分:

  • 包声明
  • 引入包
  • 函数
  • 变量
  • 语句 & 表达式
  • 注释

关于包,根据本地测试得出以下几点:

  • 文件名与包名没有直接关系,不一定要将文件名与包名定成同一个。
  • 文件夹名与包名没有直接关系,并非需要一致。
  • 同一个文件夹下的文件只能有一个包名,否则编译报错。
  • 每个 Go 应用程序都包含一个名为 main 的包。
  • 文件名和文件夹名称不能一样,否则运行代码时会提示“cannot find package "xxx" in any of:”错误。

Go 语言基础语法

Go 程序可以由多个标记组成,可以是关键字,标识符,常量,字符串,符号。

注释

注释不会被编译,每一个包应该有相关注释。

单行注释是最常见的注释形式,你可以在任何地方使用以 // 开头的单行注释。多行注释也叫块注释,均已以 /* 开头,并以 */ 结尾。如:

// 单行注释
/*
 Author by 菜鸟教程
 我是多行注释
 */

标识符

标识符用来命名变量、类型等程序实体。一个标识符实际上就是一个或是多个字母(AZ和az)数字(0~9)、下划线_组成的序列,但是第一个字符必须是字母或下划线而不能是数字,不能是go语言的关键字或保留字,不能包含运算符。

字符串连接

Go 语言的字符串可以通过 + 实现:

实例:
package main

import "fmt"
func main() {
  fmt.Println("Google" + "Runoob")
}

以上实例输出结果为:

GoogleRunoob

关键字

下面列举了 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

除了以上介绍的这些关键字,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

程序一般由关键字、常量、变量、运算符、类型和函数组成。

程序中可能会使用到这些分隔符:括号 (),中括号 [] 和大括号 {}。

程序中可能会使用到这些标点符号:.、,、;、: 和 …。

Go 语言格式化字符串

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

Go 语言数据类型

Go 语言按类别有以下几种数据类型:

序号 类型和描述
1 布尔型 布尔型的值只可以是常量 true 或者 false。一个简单的例子:var b bool = true。
2 数字类型 整型 int 和浮点型 float32、float64,Go 语言支持整型和浮点型数字,并且支持复数,其中位的运算采用补码。
3 字符串类型: 字符串就是一串固定长度的字符连接起来的字符序列。Go 的字符串是由单个字节连接起来的。Go 语言的字符串的字节使用 UTF-8 编码标识 Unicode 文本。
4 派生类型: 包括:(a) 指针类型(Pointer)(b) 数组类型(c) 结构化类型(struct)(d) Channel 类型(e) 函数类型(f) 切片类型(g) 接口类型(interface)(h) Map 类型

Go 语言变量

Go 语言变量名由字母、数字、下划线组成,其中首个字符不能为数字,字符串变量的赋值语句必须用双引号“”,不能使用单引号''。

声明变量的一般形式是使用 var 关键字:

var identifier type

可以一次声明多个变量:

var identifier1, identifier2 type

变量声明

第一种,指定变量类型,如果没有初始化,则变量默认为零值

var v_name v_type
v_name = value
例如:
var a = "RUNOOB"
var b int
var c bool

零值就是变量没有做初始化时系统默认设置的值。

  • 数值类型(包括complex64/128)为 0
  • 布尔类型为 false
  • 字符串为 ""(空字符串)
  • 以下几种类型为 nil
var a *int
var a []int
var a map[string] int
var a chan int
var a func(string) int
var a error // error 是接口

第二种,根据值自行判定变量类型。

var v_name = value
例如:
var d = true

第三种,如果变量已经使用 var 声明过了,再使用 *:=* 声明变量,就产生编译错误,格式:

v_name := value
例如:
var intVal int 
intVal :=1 // 这时候会产生编译错误,因为 intVal 已经声明,不需要重新声明

多变量声明

//类型相同多个变量, 非全局变量
var vname1, vname2, vname3 type
vname1, vname2, vname3 = v1, v2, v3

var vname1, vname2, vname3 = v1, v2, v3 // 和 python 很像,不需要显示声明类型,自动推断

vname1, vname2, vname3 := v1, v2, v3 // 出现在 := 左侧的变量不应该是已经被声明过的,否则会导致编译错误


// 这种因式分解关键字的写法一般用于声明全局变量
var (
    vname1 v_type1
    vname2 v_type2
)

//这种不带声明格式的只能在函数体中出现
//g, h := 123, "hello"

func main(){
    g, h := 123, "hello"
    println(x, y, a, b, c, d, e, f, g, h)
}

值类型和引用类型

所有像 int、float、bool 和 string 这些基本类型都属于值类型,使用这些类型的变量直接指向存在内存中的值。

当使用等号 = 将一个变量的值赋值给另一个变量时,如:j = i,实际上是在内存中将 i 的值进行了拷贝。

你可以通过 &i 来获取变量 i 的内存地址,例如:0xf840000040(每次的地址都可能不一样)。

同一个引用类型的指针指向的多个字可以是在连续的内存地址中(内存布局是连续的),这也是计算效率最高的一种存储形式;也可以将这些字分散存放在内存中,每个字都指示了下一个字所在的内存地址。

package main

import "fmt"

func main() {
	var name1 string
//	var age = 18
//	name := "xxxx"
	name1 = "xiang186"
	name2 := name1
	var name3  *string
	name3 = &name1
	fmt.Println("name1's var:",name1,"\nname1's mem adress is:",&name1)
	fmt.Println("name2's var:",name2,"\nname2's mem adress is:",&name2)
	fmt.Println("name3's var:",name3,"\n*name3's var is:",*name3,"\nname3's mem adress is:",&name3)
}

运行结果:
name1's var: xiang186
name1's mem adress is: 0xc0000881e0
name2's var: xiang186
name2's mem adress is: 0xc0000881f0
name3's var: 0xc0000881e0
*name3's var is: xiang186
name3's mem adress is: 0xc0000ca018

注:*string表示一个字符串指针的类型。
从运行结果可以看出:
1、使用“=”赋值时,只是将“=”符号右边变量的值赋值给了左边的变量,左边的变量使用单独的内存空间来储存右边变量当前的值,当右边的变量重新赋值后,左边变量的值不会跟着变化;
2、使用指针变量*赋值时,是将“=”符号右边变量的内存地址赋值给了左边变量,左边变量使用单独的内存空间来存储右边变量的内存地址,当右边变量被重新赋值后,左边变量的值也会跟着变化(因为值是储存在内存地址中的);

简短形式,使用 := 赋值操作符

我们可以在创建变量时简写为

a := 50 
或 
b := false

a 和 b 的类型(int 和 bool)将由编译器自动推断。这是使用变量的首选形式,但是它只能被用在函数体内,而不可以用于全局变量的声明与赋值。使用操作符 := 可以高效地创建一个新的变量,称之为初始化声明。

注意事项

在相同的代码块中,我们不可以再次对于相同名称的变量使用初始化声明。

例如:
var name string
name := "xxxx"

运行时会报错:
no new variables on left side of :=

如果你在定义变量 a 之前使用它,则会得到编译错误 undefined: a

如果你声明了一个局部变量却没有在相同的代码块中使用它,同样会得到编译错误,例如下面这个例子当中的变量 a:

package main

import "fmt"

func main() {
   var a string = "abc"
   fmt.Println("hello, world")
}

// 尝试编译这段代码将得到错误 a declared and not used。

全局变量是允许声明但不使用的。 同一类型的多个变量可以声明在同一行,如:

var a, b, c int

多变量可以在同一行进行赋值,如:

var a, b int
var c string
a, b, c = 5, 7, "abc"

上面这行假设了变量 a,b 和 c 都已经被声明,否则的话应该这样使用:

a, b, c := 5, 7, "abc"

右边的这些值以相同的顺序赋值给左边的变量,所以 a 的值是 5, b 的值是 7,c 的值是 "abc"。

这被称为 并行 或 同时 赋值。

如果你想要交换两个变量的值,则可以简单地使用 a, b = b, a,两个变量的类型必须是相同。

空白标识符 _ 也被用于抛弃值,如值 5 在:
_, b = 5, 7
中会被抛弃。

_ 实际上是一个只写变量,你不能得到它的值。这样做是因为 Go 语言中你必须使用所有被声明的变量,但有时你并不需要使用从一个函数得到的所有返回值。

并行赋值也被用于当一个函数返回多个返回值时,比如这里的 val 和错误 err 是通过调用 Func1 函数同时得到:val, err = Func1(var1)。

空白标识符在函数返回值时的使用:

package main

import "fmt"

func main() {
  _,numb,strs := numbers() //只获取函数返回值的后两个
  fmt.Println(numb,strs)
}

//一个可以返回多个值的函数
func numbers()(int,int,string){
  a , b , c := 1 , 2 , "str"
  return a,b,c
}

输出结果:
2 str

Go 语言常量

常量是一个简单值的标识符,在程序运行时,不会被修改的量。

const name = "jim"
name = "sam"

编译以上语句时会报错:
cannot assign to name

常量中的数据类型只可以是布尔型、数字型(整数型、浮点型和复数)和字符串型。

常量的定义格式:

const identifier [type] = value

可以省略类型说明符 [type],因为编译器可以根据变量的值来推断其类型。

  • 显式类型定义: const b string = "abc"
  • 隐式类型定义: const b = "abc"

多个相同类型的声明可以简写为:

const c_name1, c_name2 = value1, value2

例如:
const a, b, c = 1, false, "str" //多重赋值

常量还可以用作枚举:

const (
    Unknown = 0
    Female = 1
    Male = 2
)

常量可以用len(), cap(), unsafe.Sizeof()函数计算表达式的值。常量表达式中,函数必须是内置函数,否则编译不过:

package main

import "unsafe"
const (
    a = "abc"
    b = len(a)
    c = unsafe.Sizeof(a)
)

func main(){
    println(a, b, c)
}

运行结果:
abc 3 16

iota常量

iota,特殊常量,可以认为是一个可以被编译器修改的常量。

iota 在 const关键字出现时将被重置为 0(const 内部的第一行之前),const 中每新增一行常量声明将使 iota 计数一次(iota 可理解为 const 语句块中的行索引)。

iota 可以被用作枚举值:

const (
    a = iota
    b = iota
    c = iota
)

第一个 iota 等于 0,每当 iota 在新的一行被使用时,它的值都会自动加 1;所以 a=0, b=1, c=2 可以简写为如下形式:

const (
    a = iota
    b
    c
)

Go 语言运算符

运算符用于在程序运行时执行数学或逻辑运算。

Go 语言内置的运算符有:

  • 算术运算符
  • 关系运算符
  • 逻辑运算符
  • 位运算符
  • 赋值运算符
  • 其他运算符

算术运算符

下表列出了所有Go语言的算术运算符。假定 A 值为 10,B 值为 20。

运算符 描述 实例
+ 相加 A + B 输出结果 30
- 相减 A - B 输出结果 -10
* 相乘 A * B 输出结果 200
/ 相除 B / A 输出结果 2
% 求余 B % A 输出结果 0
++ 自增 A++ 输出结果 11
-- 自减 A-- 输出结果 9

关系运算符

下表列出了所有Go语言的关系运算符。假定 A 值为 10,B 值为 20。

运算符 描述 实例
== 检查两个值是否相等,如果相等返回 True 否则返回 False。 (A == B) 为 True
!= 检查两个值是否不相等,如果不相等返回 True 否则返回 False。 (A != B) 为 True
> 检查左边值是否大于右边值,如果是返回 True 否则返回 False。 (A > B) 为 True
< 检查左边值是否小于右边值,如果是返回 True 否则返回 False。 (A < B) 为 True
>= 检查左边值是否大于等于右边值,如果是返回 True 否则返回 False。 (A >= B) 为 True
<= 检查左边值是否小于等于右边值,如果是返回 True 否则返回 False。 (A <= B) 为 True

逻辑运算符

下表列出了所有Go语言的逻辑运算符。假定 A 值为 True,B 值为 False。

运算符 描述 实例
&& 逻辑 AND 运算符。 如果两边的操作数都是 True,则条件 True,否则为 False。 (A && B) 为 False
|| 逻辑 OR 运算符。 如果两边的操作数有一个 True,则条件 True,否则为 False。 (A || B) 为 True
! 逻辑 NOT 运算符。 如果条件为 True,则逻辑 NOT 条件 False,否则为 True。 !(A && B) 为 True

位运算符

位运算符对整数在内存中的二进制位进行操作。

Go 语言支持的位运算符如下表所示。假定 A 为60,B 为13:

运算符 描述 实例
& 按位与运算符"&"是双目运算符。 其功能是参与运算的两数各对应的二进位相与。通俗点讲就是将两个值的二进制按位进行比较,比较的两个位中有0,那么计算结果就是0,否则就是1,比如:0和0,计算结果为0,0和1,计算结果是0,1和1,计算结果是1。 (A & B) 结果为 12, 二进制为 0000 1100
| 按位或运算符"|"是双目运算符。 其功能是参与运算的两数各对应的二进位相或。通俗点讲就是将两个值的二进制按位进行比较,比较的两个位中有1,那么计算结果就是1,否则就是0,比如:0和0,计算结果为0,0和1,计算结果是1,1和1,计算结果是1。 (A | B) 结果为 61, 二进制为 0011 1101
^ 按位异或运算符"^"是双目运算符。 其功能是参与运算的两数各对应的二进位相异或,当两对应的二进位相异时,结果为1。通俗点讲就是将两个值的二进制按位进行比较,比较的两个位的值相同,那么计算结果就是0,否则就是1,比如:0和0,计算结果为0,0和1,计算结果是1,1和1,计算结果是0。 (A ^ B) 结果为 49, 二进制为 0011 0001
<< 左移运算符"<<"是双目运算符。左移n位就是乘以2的n次方。 其功能把"<<"左边的运算数的各二进位全部左移若干位,由"<<"右边的数指定移动的位数,高位丢弃,低位补0。通俗点讲就是:<<n==*(2^n)。 A << 2 结果为 240 ,二进制为 1111 0000
>> 右移运算符">>"是双目运算符。右移n位就是除以2的n次方。 其功能是把">>"左边的运算数的各二进位全部右移若干位,">>"右边的数指定移动的位数。通俗点讲就是:>>n==/(2^n)。 A >> 2 结果为 15 ,二进制为 0000 1111

赋值运算符

下表列出了所有Go语言的赋值运算符。

运算符 描述 实例
= 简单的赋值运算符,将一个表达式的值赋给一个左值 C = A + B 将 A + B 表达式结果赋值给 C
+= 相加后再赋值 C += A 等于 C = C + A
-= 相减后再赋值 C -= A 等于 C = C - A
*= 相乘后再赋值 C *= A 等于 C = C * A
/= 相除后再赋值 C /= A 等于 C = C / A
%= 求余后再赋值 C %= A 等于 C = C % A
<<= 左移后赋值 C <<= 2 等于 C = C << 2
>>= 右移后赋值 C >>= 2 等于 C = C >> 2
&= 按位与后赋值 C &= 2 等于 C = C & 2
^= 按位异或后赋值 C ^= 2 等于 C = C ^ 2
|= 按位或后赋值 C |= 2 等于 C = C | 2

其他运算符

下表列出了Go语言的其他运算符。

运算符 描述 实例
& 返回变量存储地址 &a; 将给出变量的实际地址。(即变量的内存地址)
* 指针变量。 *a; 是一个指针变量

以下实例演示了其他运算符的用法:

实例:
package main

import "fmt"

func main() {
   var a int = 4
   var b int32
   var c float32
   var ptr *int

   /* 运算符实例 */
   fmt.Printf("第 1 行 - a 变量类型为 = %T\n", a );
   fmt.Printf("第 2 行 - b 变量类型为 = %T\n", b );
   fmt.Printf("第 3 行 - c 变量类型为 = %T\n", c );

   /*  & 和 * 运算符实例 */
   ptr = &a     /* 'ptr' 包含了 'a' 变量的地址 */
   fmt.Printf("a 的值为  %d\n", a);
   fmt.Printf("*ptr 为 %d\n", *ptr);
}

以上实例运行结果:
第 1 行 - a 变量类型为 = int
第 2 行 - b 变量类型为 = int32
第 3 行 - c 变量类型为 = float32
a 的值为  4
*ptr 为 4

运算符优先级

有些运算符拥有较高的优先级,二元运算符的运算方向均是从左至右。下表列出了所有运算符以及它们的优先级,由上至下代表优先级由高到低:

优先级 运算符
5 * / % << >> & &^
4 + - | ^
3 == != < <= > >=
2 &&
1 ||

当然,你可以通过使用括号来临时提升某个表达式的整体运算优先级。

Go 语言条件语句

条件语句需要开发者通过指定一个或多个条件,并通过测试条件是否为 true 来决定是否执行指定语句,并在条件为 false 的情况在执行另外的语句。

Go 语言提供了以下几种条件判断语句:

语句 描述
if 语句 if 语句 由一个布尔表达式后紧跟一个或多个语句组成。
if...else 语句 if 语句 后可以使用可选的 else 语句, else 语句中的表达式在布尔表达式为 false 时执行。
if 嵌套语句 你可以在 ifelse if 语句中嵌入一个或多个 ifelse if 语句。
switch 语句 switch 语句用于基于不同条件执行不同动作。
select 语句 select 语句类似于 switch 语句,但是select会随机执行一个可运行的case。如果没有case可运行,它将阻塞,直到有case可运行。

注意:Go 没有三目运算符,所以不支持 ?: 形式的条件判断。

if 语句

语法

Go 编程语言中 if 语句的语法如下:

if 布尔表达式 {
   /* 在布尔表达式为 true 时执行 */
}

if...else 语句

语法

Go 编程语言中 if...else 语句的语法如下:

if 布尔表达式 {
   /* 在布尔表达式为 true 时执行 */
} else {
  /* 在布尔表达式为 false 时执行 */
}

if 语句嵌套

语法

Go 编程语言中 if...else 语句的语法如下:

if 布尔表达式 1 {
   /* 在布尔表达式 1 为 true 时执行 */
   if 布尔表达式 2 {
      /* 在布尔表达式 2 为 true 时执行 */
   }
}

你可以以同样的方式在 if 语句中嵌套 else if...else 语句

switch 语句

switch 语句用于基于不同条件执行不同动作,每一个 case 分支都是唯一的,从上至下逐一测试,直到匹配为止。

switch 语句执行的过程从上至下,直到找到匹配项,匹配项后面也不需要再加 break。

switch 默认情况下 case 最后自带 break 语句,匹配成功后就不会执行其他 case,如果我们需要执行后面的 case,可以使用 fallthrough

语法

Go 编程语言中 switch 语句的语法如下:

switch var1 {
    case val1:
        ...
    case val2:
        ...
    default:
        ...
}

变量 var1 可以是任何类型,而 val1 和 val2 则可以是同类型的任意值。类型不被局限于常量或整数,但必须是相同的类型;或者最终结果为相同类型的表达式。

您可以同时测试多个可能符合条件的值,使用逗号分割它们,例如:case val1, val2, val3。

实例:
package main

import "fmt"

func main() {
	/* 定义局部变量 */
	var grade string = "B"
	var marks int = 100

	switch marks {
	case 90 :
		grade = "A"
	case 80 : grade = "B"
	case 50,60,70 : grade = "C"
	default: grade = "D"
	}
	
	switch grade{
	case "A" :
		fmt.Printf("优秀!\n" )
	case "B", "C" :
		fmt.Printf("良好\n" )
	case "D" :
		fmt.Printf("及格\n" )
	case "F":
		fmt.Printf("不及格\n" )
	default:
		fmt.Printf("差\n" )
	}
	fmt.Printf("你的等级是 %s\n", grade )

}

以上代码运行结果:
及格
你的等级是 D

Type Switch

switch 语句还可以被用于 type-switch 来判断某个 interface 变量中实际存储的变量类型。

Type Switch 语法格式如下:

switch x.(type){
    case type:
       statement(s);      
    case type:
       statement(s); 
    /* 你可以定义任意个数的case */
    default: /* 可选 */
       statement(s);
}
实例:
package main

import "fmt"

func main() {
	var i  = 3.1515156633666636622
	var x interface{} = &i

	switch i := x.(type) {
	case nil:
		fmt.Printf(" x 的类型 :%T",i)
	case int:
		fmt.Printf("x 是 %T 型",i)
	case float64:
		fmt.Printf("x 是 %T 型",i)
	case func(int) float64:
		fmt.Printf("x 是 %T 型",i)
	case bool, string:
		fmt.Printf("x 是 %T 型",i )
	default:
		fmt.Printf("x 是未知型:%T",i)
	}
}

以上代码执行结果为:
x 是未知型:*float64

fallthrough

使用 fallthrough 会强制执行后面的 case 语句,fallthrough 不会判断下一条 case 的表达式结果是否为 true。

实例:

package main

import "fmt"

func main() {

	switch {
	case true:
		fmt.Println("1、case 条件语句为 true")
		fallthrough
	case false:
		fmt.Println("2、case 条件语句为 false")//这一行因为上一行的case中有fallthrough,所以会被打印出来
	case true:
		fmt.Println("3、case 条件语句为 true")/*这一行按理应该是要打印的,但是上一行是因为加
		了fallthrough才打印出来的,程序执行到上一行的时候语句就退出了,所以这一行不会被打印出来。*/
		fallthrough
	default:
		fmt.Println("4、默认 case")
	}
}

以上语句执行结果:
1、case 条件语句为 true
2、case 条件语句为 false

select 语句

select 是 Go 中的一个控制结构,类似于用于通信的 switch 语句。每个 case 必须是一个通信操作,要么是发送要么是接收。

select 随机执行一个可运行的 case。如果没有 case 可运行,它将阻塞,直到有 case 可运行。一个默认的子句应该总是可运行的。

语法

Go 编程语言中 select 语句的语法如下:

select {
    case communication clause  :
       statement(s);      
    case communication clause  :
       statement(s);
    /* 你可以定义任意数量的 case */
    default : /* 可选 */
       statement(s);
}

以下描述了 select 语句的语法:

  • 每个 case 都必须是一个通信

  • 所有 channel 表达式都会被求值

  • 所有被发送的表达式都会被求值

  • 如果任意某个通信可以进行,它就执行,其他被忽略。

  • 如果有多个 case 都可以运行,Select 会随机公平地选出一个执行。其他不会执行。

    否则:

    1. 如果有 default 子句,则执行该语句。
    2. 如果没有 default 子句,select 将阻塞,直到某个通信可以运行;Go 不会重新对 channel 或值进行求值。

关于这个语句的实例和详细说明见文档:golang-chan关键字的说明.md

Go 语言循环语句

Go 语言提供了以下几种类型循环处理语句:

循环类型 描述
for 循环 重复执行语句块
循环嵌套 在 for 循环中嵌套一个或多个 for 循环

for 循环

for 循环是一个循环控制结构,可以执行指定次数的循环。

语法

Go 语言的 For 循环有 3 种形式,只有其中的一种使用分号。

和 C 语言的 for 一样:

for init; condition; post { }

和 C 的 while 一样:

for condition { }

和 C 的 for(; 一样:

for { }
  • init: 一般为赋值表达式,给控制变量赋初值;
  • condition: 关系表达式或逻辑表达式,循环控制条件;
  • post: 一般为赋值表达式,给控制变量增量或减量。

for语句执行过程如下:

  • 1、先对表达式 1 赋初值;
  • 2、判别赋值表达式 init 是否满足给定条件,若其值为真,满足循环条件,则执行循环体内语句,然后执行 post,进入第二次循环,再判别 condition;否则判断 condition 的值为假,不满足条件,就终止for循环,执行循环体外语句。

for 循环的 range 格式可以对 slice、map、数组、字符串等进行迭代循环。格式如下:

for key, value := range oldMap {
    newMap[key] = value
}

For-each range 循环

这种格式的循环可以对字符串、数组、切片等进行迭代输出元素。

实例:

package main
import "fmt"

func main() {
	strings := []string{"google", "runoob"}
	for i, s := range strings {
		fmt.Println(i, s)//i是数据在数组中的序列,从0开始,s是数组中的数据的值
	}


	numbers := [6]int{1, 2, 3, 5}//[6]表示声明的变量包含6个序列,无数据的部分用零填充,int数据类型的零值是0
	for i,x:= range numbers {
		fmt.Printf("第 %d 位 x 的值 = %d\n", i,x)
	}
}

以上实例运行输出结果为:
0 google
1 runoob
第 0 位 x 的值 = 1
第 1 位 x 的值 = 2
第 2 位 x 的值 = 3
第 3 位 x 的值 = 5
第 4 位 x 的值 = 0
第 5 位 x 的值 = 0

循环嵌套

Go 语言允许用户在循环内使用循环。接下来我们将为大家介绍嵌套循环的使用。

语法

以下为 Go 语言嵌套循环的格式:

for [condition |  ( init; condition; increment ) | Range]
{
   for [condition |  ( init; condition; increment ) | Range]
   {
      statement(s);
   }
   statement(s);
}

实例1:

package main

import "fmt"

func main() {
   /* 定义局部变量 */
   var i, j int

   for i=2; i < 12; i++ {             //素数是大于1的自然数,1既不是素数也不是合数,所以要从2开始
      for j=2; j <= (i/j); j++ {     
         //println(i,j,i/j)
         if(i%j==0) {
            break // 如果发现因子,则不是素数,跳出循环
         }
      }
      if(j > (i/j)) {
         //fmt.Println(i,j,i/j)
         fmt.Printf("%d  是素数\n", i)
      }
   }
}

以上实例运行输出结果为:
2  是素数
3  是素数
5  是素数
7  是素数
11  是素数

实例1:

package main

import "fmt"

func main() {
   for m := 1; m < 10; m++ {
      /*    fmt.Printf("第%d次:\n",m) */
      for n := 1; n <= m; n++ {
         fmt.Printf("%dx%d=%d ",n,m,m*n)
      }
      fmt.Println("")
   }
}

以上实例运行输出结果为:
1x1=1
1x2=2 2x2=4
1x3=3 2x3=6 3x3=9
1x4=4 2x4=8 3x4=12 4x4=16
1x5=5 2x5=10 3x5=15 4x5=20 5x5=25
1x6=6 2x6=12 3x6=18 4x6=24 5x6=30 6x6=36
1x7=7 2x7=14 3x7=21 4x7=28 5x7=35 6x7=42 7x7=49
1x8=8 2x8=16 3x8=24 4x8=32 5x8=40 6x8=48 7x8=56 8x8=64
1x9=9 2x9=18 3x9=27 4x9=36 5x9=45 6x9=54 7x9=63 8x9=72 9x9=81

Go 循环控制语句

循环控制语句可以控制循环体内语句的执行过程。

GO 语言支持以下几种循环控制语句:

控制语句 描述
break 语句 经常用于中断当前 for 循环或跳出 switch 语句
continue 语句 跳过当前循环的剩余语句,然后继续进行下一轮循环。
goto 语句 将控制转移到被标记的语句。

break 语句

Go 语言中 break 语句用于以下两方面:

  • 用于循环语句中跳出循环,并开始执行循环之后的语句。
  • break 在 switch(开关语句)中在执行一条 case 后跳出语句的作用。
  • 在多重循环中,可以用标号 label 标出想 break 的循环。

语法

break 语法格式如下:

break;

实例:

package main

import "fmt"

func main() {

   // 不使用标记
   fmt.Println("---- break ----")
   for i := 1; i <= 3; i++ {
      fmt.Printf("i: %d\n", i)
      for i2 := 11; i2 <= 13; i2++ {
         fmt.Printf("i2: %d\n", i2)
         break
      }
   }

   // 使用标记
   fmt.Println("---- break label ----")
re://这个re只是一个label名称,可以为任意字符串,只要上下两个一样即可,比如这里也可以用xiang:,下面用break xiang
   for i := 1; i <= 3; i++ {
      fmt.Printf("i: %d\n", i)
      for i2 := 11; i2 <= 13; i2++ {
         fmt.Printf("i2: %d\n", i2)
         break re //直接从re:标识的位置跳出for循环
      }
   }
}

以上实例执行结果为:
---- break ----
i: 1
i2: 11
i: 2
i2: 11
i: 3
i2: 11
---- break label ----
i: 1
i2: 11

continue 语句

Go 语言的 continue 语句 有点像 break 语句。但是 continue 不是跳出循环,而是跳过当前循环执行下一次循环语句。

for 循环中,执行 continue 语句会触发 for 增量语句的执行。

在多重循环中,可以用标号 label 标出想 continue 的循环。

语法

continue 语法格式如下:

continue;

实例:

package main

import "fmt"

func main() {

   // 不使用标记
   fmt.Println("---- continue ---- ")
   for i := 1; i <= 3; i++ {
      fmt.Printf("i: %d\n", i)
      for i2 := 11; i2 <= 13; i2++ {
         fmt.Printf("i2: %d\n", i2)
         continue  /*跳出内部的的for循环,继续执行内部for循环的下一个循环,其实这里加不加这个continue输出的结果是一样的
         因为程序默认就是先执行完内部的循环后再去执行外面的循环*/
      }
   }

   // 使用标记
   fmt.Println("---- continue label ----")
re:
   for i := 1; i <= 3; i++ {
      fmt.Printf("i: %d\n", i)
      for i2 := 11; i2 <= 13; i2++ {
         fmt.Printf("i2: %d\n", i2)
         continue re/*这里是继续执行re:标记位置的循环,也就是最外层的for循环,所以内部的for循环只会执行一次*/
      }
   }
}

以上实例执行结果为:
---- continue ----
i: 1
i2: 11
i2: 12
i2: 13
i: 2
i2: 11
i2: 12
i2: 13
i: 3
i2: 11
i2: 12
i2: 13
---- continue label ----
i: 1
i2: 11
i: 2
i2: 11
i: 3
i2: 11

goto 语句

Go 语言的 goto 语句可以无条件地转移到过程中指定的行。

goto 语句通常与条件语句配合使用。可用来实现条件转移, 构成循环,跳出循环体等功能。

但是,在结构化程序设计中一般不主张使用 goto 语句, 以免造成程序流程的混乱,使理解和调试程序都产生困难。

语法

goto 语法格式如下:

goto label;
..
.
label: statement;

实例:

package main

import "fmt"

func main() {
   /* 定义局部变量 */
   var a int = 10

   /* 循环 */
LOOP1: for a < 20 { //声明一个LOOP1标签,LOOP1这个名称可以自定义
   if a == 15 {
      /* 跳过迭代 */
      a = a + 1
      goto LOOP1 //跳转到LOOP1标签
   }
   fmt.Printf("a的值为 : %d\n", a)
   a++
}
}

以上实例执行结果为:
a的值为 : 10
a的值为 : 11
a的值为 : 12
a的值为 : 13
a的值为 : 14
a的值为 : 16
a的值为 : 17
a的值为 : 18
a的值为 : 19

无限循环

如果循环中条件语句永远不为 false 则会进行无限循环,我们可以通过 for 循环语句中只设置一个条件表达式来执行无限循环。

实例:

package main

import "fmt"

func main() {
   wuxian()
}

func wuxian() {
	for true  {
		fmt.Printf("这是无限循环。\n");
	}
}

以上实例执行结果为:
这是无限循环。
这是无限循环。
这是无限循环。
这是无限循环。
这是无限循环。
...

Go 语言函数

函数是基本的代码块,用于执行一个任务。

Go 语言最少有个 main() 函数。

你可以通过函数来划分不同功能,逻辑上每个函数执行的是指定的任务。

函数声明告诉了编译器函数的名称,返回类型,和参数。

Go 语言标准库提供了多种可动用的内置的函数。例如,len() 函数可以接受不同类型参数并返回该类型的长度。如果我们传入的是字符串则返回字符串的长度,如果传入的是数组,则返回数组中包含的元素个数。


函数定义

Go 语言函数定义格式如下:

func function_name( [parameter list] ) [return_types] {
   函数体
}

函数定义解析:

  • func:函数由 func 开始声明
  • function_name:函数名称,参数列表和返回值类型构成了函数签名。
  • parameter list:参数列表,参数就像一个占位符,当函数被调用时,你可以将值传递给参数,这个值被称为实际参数。参数列表指定的是参数类型、顺序、及参数个数。参数是可选的,也就是说函数也可以不包含参数。
  • return_types:返回类型(注意这里返回的不是值,而是值的类型),函数返回一列值。return_types 是该列值的数据类型。有些功能不需要返回值,这种情况下 return_types 不是必须的。
  • 函数体:函数定义的代码集合。

函数调用

当创建函数时,你定义了函数需要做什么,通过调用该函数来执行指定任务。

实例:

package main

import "fmt"

func main() {
   /* 定义局部变量 */
   var a int = 100
   var b int = 200
   var ret int

   /* 调用函数并返回最大值 */
   ret = max(a, b)//这里是通过调用函数的方式,将变量a和b的值传入函数max,然后将函数max返回的结果赋值给变量ret

   fmt.Printf( "最大值是 : %d\n", ret )
}

/* 函数返回两个数的最大值 */
func max(num1, num2 int) int {
   /* 定义局部变量 */
   var result int

   if (num1 > num2) {
      result = num1
   } else {
      result = num2
   }
   return result
}

以上实例在 main() 函数中调用 max()函数,执行结果为:
最大值是 : 200

函数返回多个值

Go 函数可以返回多个值。

实例:

package main

import "fmt"

func swap(x, y string) (string, string) {  //声明函数swap包含x和y两个参数,返回结果为2个字符串类型的值
   return y, x
}

func main() {
   a, b := swap("Google", "Runoob") //通过调用函数swap的方式,将变量x的值和y的值传入,并将返回结果的值赋予到变量a和b
   fmt.Println(a, b)
}

以上实例在 main() 函数中调用 swap()函数,执行结果为:
Runoob Google

函数参数

函数如果使用参数,该变量可称为函数的形参。

形参就像定义在函数体内的局部变量。

调用函数,可以通过两种方式来传递参数:

传递类型 描述
值传递 值传递是指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数。
引用传递 引用传递是指在调用函数时将实际参数的地址传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数。

默认情况下,Go 语言使用的是值传递,即在调用过程中不会影响到实际参数。

值传递值

值传递方式传递参数的值,调用过程中不会影响到实际参数的值。

实例:

package main

import "fmt"

func main() {
	/* 定义局部变量 */
	var a int = 100
	var b int = 200

	fmt.Printf("交换前 a 的值为 : %d\n", a ) // 调用函数前a的值是100
	fmt.Printf("交换前 b 的值为 : %d\n", b ) // 调用函数前b的值是200

	/* 通过调用函数来交换值 */
	swap_ab(a, b)

	fmt.Printf("交换后 a 的值 : %d\n", a ) // 调用函数后a的值仍然是100
	fmt.Printf("交换后 b 的值 : %d\n", b ) // 调用函数后b的值仍然是200
}

/* 定义相互交换值的函数 */
func swap_ab(x, y int) int {
	var temp int
	/* 以下3行的代码也可以直接写成:x, y = y, x 这样的方式 */
	//x,y = y,x
	temp = x /* 保存 x 的值 */
	x = y    /* 将 y 值赋给 x */
	y = temp /* 将 temp 值赋给 y*/

	return temp
}

以上实例在 main() 函数中调用 swap_ab()函数,执行结果为:
交换前 a 的值为 : 100
交换前 b 的值为 : 200
交换后 a 的值 : 100
交换后 b 的值 : 200

引用传递值

引用传递指针参数传递到函数内。

实例:

package main

import "fmt"

func main() {
	/* 定义局部变量 */
	var a int = 100
	var b int= 200

	fmt.Printf("交换前,a 的值 : %d\n", a )
	fmt.Printf("交换前,b 的值 : %d\n", b )

	/* 调用 swap() 函数
	 * &a 指向 a 指针,a 变量的地址
	 * &b 指向 b 指针,b 变量的地址
	 */
	swap_ab(&a, &b)                 // 调用函数swap_ab,注意看这里传参是用的变量a和b指针地址

	fmt.Printf("交换后,a 的值 : %d\n", a )
	fmt.Printf("交换后,b 的值 : %d\n", b )
}

func swap_ab(x *int, y *int) *int {      // 定义函数参数时,使用指针类型,在传参是必须用指针类型来传参
	var temp int
	//*x,*y = *y,*x  //下面三行也可以这样写
	temp = *x    /* 保存 x 地址上的值 */
	*x = *y      /* 将 y 值赋给 x */
	*y = temp    /* 将 temp 值赋给 y */

	return &temp
}

以上实例在 main() 函数中调用 swap_ab()函数,执行结果为:
交换前,a 的值 : 100
交换前,b 的值 : 200
交换后,a 的值 : 200
交换后,b 的值 : 100

函数用法

Go语言函数有以下三种用法。

函数用法 描述
函数作为另外一个函数的实参 函数定义后可作为另外一个函数的实参数传入
闭包 闭包是匿名函数,可在动态编程中使用
方法 方法就是一个包含了接受者的函数

函数作为实参

Go 语言可以很灵活的创建函数,并作为另外一个函数的实参。

以下实例中我们在定义的函数中初始化一个变量,该函数仅仅是为了使用内置函数。

实例1:

package main

import (
   "fmt"
   "math"
)

func main(){
   /* 声明函数变量 */
   //getSquareRoot := func(x float64) float64 {
   var getSquareRoot = func(x float64) float64 { // getSquareRoot是函数名称,右边的两个语句分别是函数的参数和返回值类型
      return math.Sqrt(x)   // 计算x的平方,也即是计算√x的值
   }

   /* 使用函数 */
   fmt.Println(getSquareRoot(9))
   fmt.Println(test(16))

}

func test(y float64) float64{   //上面getSquareRoot函数的写法和test函数这种写法效果是一样的
   return math.Sqrt(y)
}

以上实例在 main() 函数中调用 getSquareRoot()和test()函数,执行结果为:
3
4

实例2(把函数作为参数来传递,实现回调):

package main

import (
   "fmt"
)

// 声明一个函数类型
type cb func(x,y int) (int,int) // 写成这样时:type cb func(int) int,表示声明一个只传入一个int类型的参数,返回一个int类型的结果的函数类型

func main() {
   /* 调用testCallBack函数将值1和2传值给第参数x和y,然后再通过第二个参数调用函数callBack,将x和y的值传给函数callBack中的参数x
   和y实现通过参数调用函数*/
   testCallBack(1,2, callBack)
   /* 下面这句是直接将第二个参数写成了函数的形式,跟上面调用函数的方式实现的效果是一样的 */
   testCallBack(3,4, func(x,y int) (int,int) {
      fmt.Printf("我是回调,x:%d\n我是回调,y:%d\n", x,y)
      return x,y
   })
}

func testCallBack(x,y int, f cb) {
   f(x,y)                             // 这一句相当于var f = func(x,y int) (int,int)
}

func callBack(x,y int) (int,int) {
   fmt.Printf("我是回调,x:%d\n我是回调,y:%d\n", x,y) // 打印x的值
   return x,y
}

以上代码执行结果为:
我是回调,x:1
我是回调,y:2
我是回调,x:3
我是回调,y:4

函数闭包

Go 语言支持匿名函数,可作为闭包。匿名函数是一个"内联"语句或表达式。

匿名函数的优越性在于可以直接使用函数内的变量,不必申明。

实例1(变量在函数表达式里面时):

package main

import "fmt"

/* 声明一个闭包函数,getSequence的返回类型是 func() (int,int) 这个函数 */
func getSequence() func() (int,int) {
   x:=0
/* getSequence函数的返回值是func() (int,int) ,func() int函数的返回值是x,y,
这里可以看到x是在函数getSequence中定义的,y是在函数中的函数定义的,x是全局变量,
y是局部变量 */
   return func() (int,int) {
      y:=0 // 变量y在函数的返回函数中被赋值,每次函数被调用时,y的值会被重置
      x++
      y++
      return x,y
   }
}

func main(){
	/* 以函数getSequence()作为模板声明nextNumber 为一个函数
	类似java中的实例对象,可以把getSequence函数看成一个类,nextNumber函数看成getSequence函数的一个实例化对象,
	*/
   nextNumber := getSequence()

   /* 调用 nextNumber 函数,每次调用函数时 x 变量的值自增 1 并返回,y变量值被重置 */
   fmt.Println(nextNumber())
   fmt.Println(nextNumber())
   fmt.Println(nextNumber())

   /* 创建新的函数 nextNumber1,创建新的函数后,x 变量的值会被重置为0,然后在调用时自增 */
   nextNumber1 := getSequence()
   fmt.Println(nextNumber1())
   fmt.Println(nextNumber1())
}

以上代码执行结果为:
1 1
2 1
3 1
1 1
2 1

实例2(变量在函数参数里面时):

package main

import "fmt"

/* 声明一个闭包函数,分别在函数的参数和函数的返回值函数的参数中赋值 */
/* 这里可以看到a和是在函数getSequence中定义的,b是在函数中的函数定义的,a是全局变量,
b是局部变量*/
func getSequence(a int) func(b int) (int,int) {

   return func(b int) (int,int) {
      a++              // 每次调用函数时a会自增1
      b++              // 每次调用函数时b会重置为传入的值,然后自增1
      return a,b
   }
}

func main(){
   /* 以函数getSequence()作为模板声明nextNumber 为一个函数
   类似java中的实例对象,可以把getSequence函数看成一个类,nextNumber函数看成getSequence函数的一个实例化对象,
   */
   nextNumber := getSequence(1)     // 这里的1是赋值给变量a的

   /* 调用 nextNumber 函数,每次调用函数时 a 变量的值自增 1(a++) 并返回,b变量值不变 */
   fmt.Println(nextNumber(2))       // 这里的2是赋值给变量b的
   fmt.Println(nextNumber(2))
   fmt.Println(nextNumber(2))

   /* 创建新的函数 nextNumber1,创建新的函数后,a 变量的值会按照新传入的值计算 */
   nextNumber1 := getSequence(3)
   fmt.Println(nextNumber1(4))
   fmt.Println(nextNumber1(4))
}

以上代码执行结果为:
2 3
3 3
4 3
4 5
5 5

函数方法

Go 语言中同时有函数和方法。一个方法就是一个包含了接受者的函数,接受者可以是命名类型或者结构体类型的一个值或者是一个指针。

所有给定类型的方法属于该类型的方法集。

语法格式如下:

func (variable_name variable_data_type) function_name() [return_type]{
   /* 函数体*/
}

实例1:

package main

import (
   "fmt"
)

/* 定义 Circle 类型结构体 */
type Circle struct {
   radius float64         //声明一个radius属性
}

func main() {
   var c1 Circle           //定义一个 Circle 类型对象: c1
   c1.radius = 10.00       //radius是Circle 类型对象中的一个属性
// fmt.Println("圆的面积 = ", c1.getArea())           //调用getArea()方法
// fmt.Println("圆的周长 = ", c1.getRound())          //调用getRound()方法
   fmt.Printf("圆的面积 = %0.0f\n圆的周长 = %0.0f\n", c1.getArea(), c1.getRound())  //格式化字符串,只保留结果的整数部分
}

//下面的语句是为 Circle 类型对象写一个getArea()方法,该 method 属于 Circle 类型对象中的方法
//这里的变量c1也可以是其他名称,但为了保持代码的易读性,所有Circle 类型对象中的方法尽量使用一样的
func (c1 Circle) getArea() float64 {
   //c.radius 即为 Circle 类型对象中的属性
   return 3.14 * c1.radius * c1.radius
}
//定义一个getRound()方法
func (c1 Circle ) getRound() float64{
   return 3.14 * c1.radius * 2
}

以上代码执行结果为:
圆的面积 = 314
圆的周长 = 63

实例2(使用指针在方法中改变结构体类型中的属性的值):

package main

import (
   "fmt"
)

/* 定义结构体 */
type Circle struct {
   radius float64         //为 Circle 类型声明一个属性radius
}


func main()  {
   var c Circle
   fmt.Println(c.radius)    //属性radius没有赋值时,打印该属性类型的初值,float64类型的变量初值为0
   c.radius = 10.00
   fmt.Println(c.getArea())     //调用getArea()方法
   c.changeRadius(20)    //向changeRadius(radius float64)方法中的参数传值
   fmt.Println(c.radius)       //打印c.radius属性的值,因为该方法传递的是指针,所以属性值会变成传递进来的值
   c.changeRadius2(30)  //向changeRadius2(radius float64)方法中的参数传值
   fmt.Println(c.radius) //打印c.radius属性的值,因为该方法传递的不是指针,所以该属性值不会变成传递进来的值
   change(&c, 40)  //调用change()方法,并向该方法传值
   fmt.Println(c.radius) //打印打印c.radius属性的值,因为该方法传递的是指针,所以属性值会变成传递进来的值
}
func (c Circle) getArea() float64  {
   return c.radius
}
// 注意如果想要更改成功c的值,这里需要传指针
func (c *Circle) changeRadius(radius float64)  {
   c.radius = radius
}

// 以下操作将不生效
func (c Circle) changeRadius2(radius float64)  {
   c.radius = radius
}
// 引用类型要想改变值需要传指针
func change(c *Circle, radius float64)  {
   c.radius = radius
}

以上代码执行结果为:
0
10
20
20
40
原文地址:https://www.cnblogs.com/xzy186/p/15771596.html