文章目录
- 一、变量的使用
- 1.1 定义变量
- 1.2 常量
- 1.3 变量的赋值与内存相关
- 二、变量基本类型
- 2.1 有符号整型
- 2.2 无符号整型
- 2.3 string类型
- 2.4 bool类型
- 三、输出
- 3.1 常用打印功能
- 3.2 格式化输出
- 3.3 内置输出方法与fmt的区别
- 四、注释
- 五、运算符
- 六、条件语句
- 6.1 基本使用
- 6.2 条件嵌套
- 七、函数(Function)
- 7.1 函数的声明
- 7.2 多返回值
- 7.3 命名返回值
- 7.4 空白符
- 7.5 不定长参数
道阻且长,行则将至,行而不辍,未来可期🌟。人生是一条且漫长且充满荆棘的道路,一路上充斥着各种欲望与诱惑,不断学习,不断修炼,不悔昨日,不畏将来!
GO语言也被称为21世纪的C语言,在开发与性能效率上都占据优势(Python+C)🚀。让我们一起来了解这门语言的魅力吧,希望这篇能够带给你们或多或少的帮助!!
一、变量的使用
变量指定了某存储单元(Memory Location)的名称,该存储单元会存储特定类型的值,变量的关键在于变,这也意味着它代表并不是一个固定不变的数据。在 Go 中,有多种语法用于声明变量。
1.1 定义变量
在所有语言内,遍历必须是先声明,后使用的。而部分语言在声明变量的时候需要指定变量类型,这是区分静态与动态语言的关键。
1、变量声明方式:指定变量类型
变量定义语法:
var 变量名 变量类型 = 变量值
变量声明方式:
package main
import "fmt"
func main() {
var age int // 声明并未赋值,int类型变量值默认为0
// 注意:Go语言中变量定义了必须使用,否则报错
fmt.Println(age)
age = 18 // 赋值
fmt.Println(age)
var width int = 180 // 变量声明并赋值
fmt.Println(width)
// var width int 已经声明的变量不能重复声明,否则报错
}
以上代码输出结果:
0
18
180
2、变量声明方式:类型推断 (Type Inference)
如果变量有初始值,那么 Go 能够自动推断具有初始值的变量的类型。因此,如果变量有初始值,就可以在变量声明中省略 type。
如果变量声明的语法是 var name = initialvalue,Go 能够根据初始值自动推断变量的类型。
在下面的例子中,我们省略了变量 age 的 int 类型,Go 依然推断出了它是 int 类型。
package main
import "fmt"
func main() {
var age = 18
fmt.Println(age)
}
3、声明多个变量
Go 能够通过一条语句声明多个变量。
声明多个变量的语法是 var name1, name2 = initialvalue1, initialvalue2
package main
import "fmt"
func main() {
var age1 ,age2 = 18,28
fmt.Println(age1,age2)
// 声明多个变量但是不赋值
var a1,b1 int // 这就表示a/b变量都是int类型
fmt.Println(a1,b1)
// 声明多个变量,且类型不同
var (
a2 int
b2 string
)
fmt.Println(a2,b2)
// 声明多个变量,且赋值
var (
name = "jack"
height = 180
)
fmt.Println(name,height)
}
至于string类型,我们将会在下面说明
上面程序运行结果如下:
18 28
0 0
0
jack 180
4、简短声明
Go 也支持一种声明变量的简洁形式,称为简短声明(Short Hand Declaration),该声明使用了 :=
操作符。
声明变量的简短语法是 name := initialvalue,省略var关键字,类型由Go自行推断出来
package main
import "fmt"
func main() {
age := 10
fmt.Println(age)
// 多个变量使用简短声明
a,b := 1,2
fmt.Println(a,b)
}
简短声明要求 := 操作符左边的所有变量都有初始值。下面程序将会抛出错误 cannot assign 1 values to 2 variables,这是因为 age 没有被赋值。
package main
import "fmt"
func main() {
name, age := "naveen" // error
fmt.Println("my name is", name, "age is", age)
}
简短声明的语法要求 := 操作符的左边至少有一个变量是尚未声明的。考虑下面的程序:
package main
import "fmt"
func main() {
a, b := 20, 30 // 声明变量a和b
fmt.Println("a is", a, "b is", b)
b, c := 40, 50 // b已经声明,但c尚未声明
fmt.Println("b is", b, "c is", c)
b, c = 80, 90 // 给已经声明的变量b和c赋新值,注意:没有冒号!!!
fmt.Println("changed b is", b, "c is", c)
}
在上面程序中,由于 b 已经被声明,而 c 尚未声明,因此运行成功并且输出:
a is 20 b is 30
b is 40 c is 50
changed b is 80 c is 90
但是如果我们运行下面的程序:
package main
import "fmt"
func main() {
a, b := 20, 30 // 声明a和b
a, b := 40, 50 // 错误,:=左边没有尚未声明的变量
}
上面运行后会抛出 no new variables on left side of := 的错误,这是因为 a 和 b 的变量已经声明过了,:= 的左边并没有尚未声明的变量。
由于 Go 是强类型(Strongly Typed)语言,因此不允许某一类型的变量赋值为其它类型的值。下面的程序会抛出错误 cannot use "naveen" (type string) as type int in assignment
,这是因为 age 本来声明为 int 类型,而我们却尝试给它赋字符串类型的值。
package main
func main() {
age := 29 // age是int类型
age = "naveen" // 错误,尝试赋值一个字符串给int类型变量
}
1.2 常量
常量:顾名思义就是经常使用的变量。
在Go语言内,定义常量一经赋值就是不可改变的,而在其它语言内,如Python:通常是以大写字母来表示常量,但是由于Python比较自由,所以只是在约定方面建议不更改,但实际是可以改变的。
Go语言常量定义:
package main
func main() {
// 定义常量(在函数内定义只能在当前函数作用域使用)
const ip = "127.0.0.1"
// ip = "0.0.0." 不可以改变常量的值,会产生报错
}
1.3 变量的赋值与内存相关
关于这部分内容,深入理解对我们理解程序的底层会更有帮助。
这一部分内容我们可以了解到,变量在内存中的结构是怎样的。因为我们的数据都是先存储到内存里面的,最后才到硬盘里面。程序的运行也是在内存中的,程序中产生的数据都是在内存中有存储的,并且每个都会分配一个内存地址,实际内存地址可能是这种格式:0x7ffcad3b8f3c
,如果我们想拿到这个数据的话不可能把这一堆内存地址记录下来吧,那么变量则是与这个内存地址做绑定的。
那么我们调用这个a变量
等同于通过0x00001
这个内存地址获取它对应的数据。
变量名的本质是什么?
这是很多学习编程的人容易忽略的问题,他们只关注了变量名代指内存中某一块数据,却忽略了变量名是否也在内存中存储?那么它的地址又是什么?
实际上变量名就是为了让我们编程时更加方便,对人友好,可计算机可不认识什么变量 a,它只知道地址和指令。所以实际在编译后,变量名都被取而代之为其对应的内存地址
你可以认为编译器自动维护了一个映射表,变量名 -> 内存地址
,将变量名转换为内存地址,然后再根据这个地址去读写数据。
也就是有这样一个映射表存在,将变量名自动转化为地址:
a:0x000001
b:0x000002
c:0x000003
当我们了解以上内容的时候,对于变量的理解已经到位了。那么下面我们来看看Go语言中变量的赋值,以及内存的一些变化。
package main
import "fmt"
func main() {
a := 10
fmt.Println(a) // a这个变量关联了一个内存地址,调用这个a变量等于通过这个地址去内存中取值
fmt.Println(&a) // 在变量前面增加&符号,查看其关联的内存地址
}
打印结果:
10
0xc00001c068
如果我们修改数据的话,内存地址是不变的,根据这个地址去覆盖其原有的值
package main
import "fmt"
func main() {
name := "Jack"
fmt.Println(name, &name)
name = "Tom" // 修改name变量的值(根据内存地址,去到所在内存位置覆盖掉其原有的值)
fmt.Println(name, &name)
}
输出结果:
Jack 0xc00008e1e0
Tom 0xc00008e1e0
当一个变量指向另外一个变量时,它们的内存地址并不相同,只是将数据复制到了一块新的内存空间进行存储
package main
import "fmt"
func main() {
name := "Jack"
fmt.Println(name, &name)
nickname := name
fmt.Println(nickname, &nickname)
}
输出结果:
Jack 0xc000010200
Jack 0xc000010220 // 不完全相同
这与Python是有差异的,在Python中,如果一个变量指向另外一个变量,等同于复用了它的内存地址。
name = "jack"
nick_name = name
print(id(name))
print(id(nick_name))
输出结果:
140417703147248
140417703147248
关于Go语言的变量赋值,注意事项:
使用:int、string、bool这三种数据类型时,会将数据拷贝一份到到新的内存地址存储。也就是我们上面第三个实例的情况:name = “jack”;nickname = name
后续还会学习到其它数据类型,我们到时再来分析它们之间的关系;这些数据类型又分为两类:
- 值类型:上面三种数据类型属于值类型,每个数据都是独立的,不会被复用
- 引用类型:后续会了解到
二、变量基本类型
下面是 Go 支持的基本类型:
数字类型:
- int8, int16, int32, int64, int
- uint8, uint16, uint32, uint64, uint
- float32, float64
- complex64, complex128
- byte
- rune
string(字符串类型):Go 的字符串是由单个字节连接起来的。Go 语言的字符串的字节使用 UTF-8 编码标识 Unicode 文本。
bool(布尔类型):布尔型的值只可以是常量 true 或者 false。
派生类型:我们留到后续讲解
2.1 有符号整型
有符号数可以用来区分正负。而无符号数只有正数,没有负数。
int8:表示 8 位有符号整型
大小:8 位
范围:-128~127
iint16:表示 16 位有符号整型
大小:16 位
范围:-32768~32767
iint32:表示 32 位有符号整型
大小:32 位
范围:-2147483648~2147483647
iint64:表示 64 位有符号整型
大小:64 位
范围:-9223372036854775808~9223372036854775807
int:根据不同的底层平台(Underlying Platform),表示 32 或 64 位整型。除非对整型的大小有特定的需求,否则你通常应该使用 int 表示整型。
大小:在 32 位系统下是 32 位,而在 64 位系统下是 64 位。
范围:在 32 位系统下是 -2147483648~2147483647
,而在 64 位系统是 -9223372036854775808~9223372036854775807
。
package main
import "fmt"
func main() {
var a int = 89
b := 95
fmt.Println("value of a is", a, "and b is", b)
fmt.Printf("%T",b)
}
上面程序会输出如下结果:
value of a is 89 and b is 95
int
在上述程序中,a 是 int 类型,而 b 的类型通过赋值(95)推断得出。上面我们提到,int 类型的大小在 32 位系统下是 32 位,而在 64 位系统下是 64 位。接下来我们会证实这种说法。
在 Printf 方法中,使用 %T 格式说明符(Format Specifier),可以打印出变量的类型。Go 的 unsafe 包提供了一个 Sizeof 函数,该函数接收变量并返回它的字节大小。unsafe 包应该小心使用,因为使用 unsafe 包可能会带来可移植性问题。不过出于本教程的目的,我们是可以使用的。
下面程序会输出变量 a 和 b 的类型和大小。格式说明符 %T 用于打印类型,而 %d 用于打印字节大小。
package main
import (
"fmt"
"unsafe"
)
func main()
var a int = 89
b := 95
fmt.Println("value of a is", a, "and b is", b)
fmt.Printf("type of a is %T, size of a is %d", a, unsafe.Sizeof(a)) // a 的类型和大小
fmt.Printf("\ntype of b is %T, size of b is %d", b, unsafe.Sizeof(b)) // b 的类型和大小
}
以上程序会输出以下结果:
value of a is 89 and b is 95
type of a is int, size of a is 8
type of b is int, size of b is 8
从上面的输出,我们可以推断出 a 和 b 为 int 类型,且大小都是 32 位(4 字节)。如果你在 64 位系统上运行上面的代码,会有不同的输出。在 64 位系统下,a 和 b 会占用 64 位(8 字节)的大小。
不同整型之间是不能直接使用“算术运算符”的,例如:
package main
func main() {
var i1 int8 = 10
var i2 int16 = 10
v3 := int16(16) + i2 // 只有两个数据类型相同才能进行相加
}
上面的操作也就是整数类型间的转换,例如:将int8
转换成int16
,但是需要注意如下问题:
- 低位可以往位置转换(int8 -> int16)
- 高位往低位转可能会出现问题(int16 -> int8)
在Golang里面高位往地位转的话,当数据值大于低位的范围以后,则会从低位的起始开始往前递增
package main
import "fmt"
func main() {
// int8范围:-128 - 127
var i2 int16 = 128
fmt.Println(int8(i2))
// 打印:-128,128大于int8的范围后,则回到最起始
var i3 int16 = 129
fmt.Println(int8(i2))
// 打印:-127,129大于int8的范围后,则回到最起始+1
// 如果是130,转换为int8以后则是-126
}
2.2 无符号整型
uint8:表示 8 位无符号整型
大小:8 位
范围:0~255
uint16:表示 16 位无符号整型
大小:16 位
范围:0~65535
uint32:表示 32 位无符号整型
大小:32 位
范围:0~4294967295
uint64:表示 64 位无符号整型
大小:64 位
范围:0~18446744073709551615
uint:根据不同的底层平台,表示 32 或 64 位无符号整型。
大小:在 32 位系统下是 32 位,而在 64 位系统下是 64 位。
范围:在 32 位系统下是 0~4294967295,而在 64 位系统是 0~18446744073709551615。
2.3 string类型
在 Golang 中,字符串是字节的集合。如果你现在还不理解这个定义,也没有关系。我们可以暂且认为一个字符串就是由很多字符组成的。我们后面会在一个教程中深入学习字符串。 下面编写一个使用字符串的程序。
package main
import (
"fmt"
)
func main() {
first := "Naveen"
last := "Ramanathan"
name := first +" "+ last
fmt.Println("My name is",name)
}
上面程序中,first 赋值为字符串 “Naveen”,last 赋值为字符串 “Ramanathan”。+ 操作符可以用于拼接字符串。我们拼接了 first、空格和 last,并将其赋值给 name。
上述程序将打印输出:
My name is Naveen Ramanathan
还有许多应用于字符串上面的操作,我们将会在一个单独的教程里看见它们。
2.4 bool类型
bool 类型表示一个布尔值,值为 true 或者 false。表示正确、错误的
package main
import "fmt"
func main() {
a := 10
b := 20
fmt.Println(a > b)
fmt.Println(a < b)
}
在上面的程序中,a 赋值为 10,b 赋值为 20。
我们通过关系运算符进行比较,a 大于 b
结果是不正确的,所以为false
,a 小于 b
结果是正确的,所以为true
false
true
或者我们直接定义变量的值为:true
或者false
,通常这类情况都是在某个结构外面定义一个标识,这种情况在实际项目中使用较多,提高代码的可读性(搭配后面学习到的条件判断使用)。
package main
import "fmt"
func main() {
auth := false
}
三、输出
输出指的是将我们需要展示出来的内容打印到控制台。而输出也经常作用于:
- 程序运行的结果是否符合我们的预期
- 清除程序运行的进度
- 代码调试(也可以用于Debug代替)
在之前的程序里面,我们已经实际接触到了Go语言内输出相关的代码
import fmt
func main(){
fmt.Println("Hello Golang")
}
通过上面代码里面的fmt
包可以实现输出功能,将Hello Golang
打印在控制台上
3.1 常用打印功能
这里我们主要使用比较常用的打印功能,主要是换行与不换行的区别。
package main
import "fmt"
func main() {
fmt.Println("能够换行的输出1")
fmt.Println("能够换行的输出2") // 下一次打印的内容将会在这一行的下面
fmt.Print("不能够换行1")
fmt.Print("不能够换行2") // 下一次打印的内容将会和这一个行内容并排
fmt.Println("Hello", "Golang") // 在同一个输出内,我们可以通过 , 来进行多个内容拼接。这个行为可以搭配后续学习到的变量一起使用
fmt.Print("Hello\nWorld") // 我们也可以通过转义符内的:\n换行符来实现指定位置换行的一个效果
}
上面效果打印:
3.2 格式化输出
格式化输出的目的主要是在一堆文本中引用占位符,从而省去通过,
分隔的操作。代码中常用的占位符如下:
- 字符串占位符:
%s
- 整数数字占位符:
%d
- 百分号占位符:
%%
(比较特殊,因为在格式化输出方法内,不能直接使用%作为字符串展示,所以我们要想打印百分号,需得使用%%
实现) - 布尔占位符:
%t
- 字符串占位符:
%s
由于使用了占位符,那么我们需要使用,
在后面按照顺序将内容填充到使用占位符的位置
Go语言通过:fmt.Printf
方法来实现格式化输出
实例:
package main
import "fmt"
func main() {
// 而在下面实例中,我们需要注意的是,%%是不需要数据填充的,我们仅作为能够打印出%的用途
fmt.Printf("数字占位符%%d:%d \n", 123)
fmt.Printf("浮点类型占位符%%f:%f \n", 3.1415926)
fmt.Printf("布尔类型占位符%%t:%t \n", true)
fmt.Printf("字符串占位符%%s:%s \n", "Hello Golang")
fmt.Printf("我的名字是:%s;今年:%d岁;薪资:%0.1fw,相比去年上涨了80%%", "Jack", 20, 3.5)
// 上面在完整内容的结尾处,我们通过逗号,按顺序对占位符进行对应数据类型填充值
// 我们也可以观察到,浮点类型我们使用的%0.1f,这表示只保留小数点后1位,默认是6位
// 对%s也可以是:%0.1s,这表示只保留填充值的第一个字符,Jack -> J
}
上面实例结果:
3.3 内置输出方法与fmt的区别
Go语言内存在:print
、println
等内置打印方法。但是它们在我们实际代码中并不常见,这是因为它的作用是标准错误输出。
package main
func main() {
println("output1")
print("output2")
}
关于内置输出与fmt
包的常用打印区别:
- 内置的Println()/Print():都是标准错误输出,而fmt.Println()函数是标准输出
- 内置的Println()/Print():输出结果可能与预期结果顺序不一致,而fmt.Println()函数输出结果与预期结果完全一致。(这个特性是由标准错误输出和标准输出决定)
- 内置Println()/Print():不能接受数组和结构体类型的数据
- 内置Println()/Print():对于组合类型的数据输出的结果是参数值的地址,而fmt.Println()函数输出的是字面量
重点说一下标准错误输出和标准输出的区别;
- 标准错误输出:在Linux中是stderr,在Java中是System.err,在Golang中是Print()/Println()
- 标准输出:在Linux中是stdout,在Java中是System.out,在Golang中是fmt.Println()
其实从字面意思上就能看出,一个是专为输出错误用的,一个是通常输出用的,都是输出流。使用内置函数 println 和 print 的好处显而易见:不用引用任何包,并且代码编译更简单;但是也有一定的风险,因为 Go 语言官方文档中声明的这几个函数一般用于内部测试,不保证随着 Go 语言的版本升级会始终提供。一旦不提供,将意味着大量的代码修改工作,因此建议大家使用 fmt 包中的 Println 或 Print 函数。
四、注释
注释的作用也表示:注解说明,通过//
符号后面加上注解说明的内容来实现,在上面的部分实例中我们已经使用过,通常它的主要作用就是对代码进行解释用途,或者将某些暂不使用的代码进行注释,因为编译器是不会将注释过的内容进行编译的
注释也分为单行注释与多行注释
- 单行注释:主要多用于对某段代码进行解释
- 多行注释:对功能函数、文件、模块等大范围的内容进行一个作用说明
多行注释语法:
/*
该文件主要对用户数据进行处理
*/
五、运算符
运算符用于在程序运行时执行数学或逻辑运算。
这里介绍几个我们在Go语言内最常用的几个内置运算符:
- 算术运算符
- 关系运算符
- 逻辑运算符
- 赋值运算符
算术运算符
下表列出了所有Go语言的算术运算符。假定 A 值为 10,B 值为 20。
关系运算符
下表列出了所有Go语言的关系运算符。假定 A 值为 10,B 值为 20。
逻辑运算符
下表列出了所有Go语言的逻辑运算符。假定 A 值为 True,B 值为 False。
关系运算符与逻辑运算符在条件判断中最为常用
赋值运算符
下表列出了所有Go语言的赋值运算符。
六、条件语句
6.1 基本使用
if 是条件语句。if 语句的语法是
if condition {
// 触发条件执行的代码块
}
条件语句的作用是,当满足什么样的条件,执行什么样的代码。这就和人思考一样,满足了有钱的条件下,才能去买房、买车、旅游…
如果 condition
为true
,则执行 {
和 }
之间的代码。
不同于其他语言,例如 C 语言,Go 语言里的 { }
是必要的,即使在 { }
之间只有一条语句。
if 语句还有可选的 else if
和 else
部分。
if condition {
// 代码块
} else if condition {
// Code
} else {
// 当上面的if else if 不成立的时候才执行的Code
}
if-else
语句之间可以有任意数量的 else if
。条件判断顺序是从上到下。如果 if
或 else if
条件判断的结果为真,则执行相应的代码块。 如果没有条件为真,则 else
代码块被执行。
让我们编写一个简单的程序来检测一个数字是奇数还是偶数。
package main
import (
"fmt"
)
func main() {
num := 10
if num % 2 == 0 { //checks if number is even
fmt.Println("the number is even")
} else {
fmt.Println("the number is odd")
}
}
if num%2 == 0
语句检测 num 取 2 的余数是否为零。 如果是为零则打印输出 “the number is even”,如果不为零则打印输出 “the number is odd”。在上面的这个程序中,打印输出的是 the number is even
。
if
还有另外一种形式,它包含一个 statement
可选语句部分,该组件在条件判断之前运行。它的语法是
if statement; condition {
// Code
}
让我们重写程序,使用上面的语法来查找数字是偶数还是奇数。
package main
import (
"fmt"
)
func main() {
// 使用if的同时声明了num变量
if num := 10; num % 2 == 0 { // checks if number is even
fmt.Println(num,"is even")
} else {
fmt.Println(num,"is odd")
}
}
在上面的程序中,num
在 if
语句中进行初始化,num
只能从 if
和 else
中访问。也就是说 num
的范围仅限于 if else
代码块。如果我们试图从其他外部的 if
或者 else
访问 num
,编译器会不通过。
让我们再写一个使用 else if
的程序。
package main
import (
"fmt"
)
func main() {
num := 99
if num <= 50 {
fmt.Println("number is less than or equal to 50")
} else if num >= 51 && num <= 100 {
fmt.Println("number is between 51 and 100")
} else {
fmt.Println("number is greater than 100")
}
}
在上面的程序中,如果 else if num >= 51 && num <= 100
为真,程序将输出 number is between 51 and 100
。
需要注意的地方:
else
语句应该在 if
语句的大括号 }
之后的同一行中。如果不是,编译器会不通过。
让我们通过以下程序来理解它。
package main
import (
"fmt"
)
func main() {
num := 10
if num % 2 == 0 { //checks if number is even
fmt.Println("the number is even")
}
else {
fmt.Println("the number is odd")
}
}
在上面的程序中,else
语句不是从 if
语句结束后的 }
同一行开始。而是从下一行开始。这是不允许的。如果运行这个程序,编译器会输出错误,
main.go:12:5: syntax error: unexpected else, expecting }
出错的原因是 Go 语言的分号是自动插入。
在 Go 语言规则中,它指定在 }
之后插入一个分号,如果这是该行的最终标记。因此,在if语句后面的 }
会自动插入一个分号。
实际上我们的程序变成了
if num%2 == 0 {
fmt.Println("the number is even")
}; //semicolon inserted by Go
else {
fmt.Println("the number is odd")
}
分号插入之后。从上面代码片段可以看出第三行插入了分号。
由于 if{…} else {…}
是一个单独的语句,它的中间不应该出现分号。因此,需要将 else
语句放置在 }
之后处于同一行中。
我已经重写了程序,将 else
语句移动到 if
语句结束后 }
的后面,以防止分号的自动插入。
package main
import (
"fmt"
)
func main() {
num := 10
if num % 2 == 0 { //checks if number is even
fmt.Println("the number is even")
} else {
fmt.Println("the number is odd")
}
}
此时的if编写才符合Go语言的规范。
6.2 条件嵌套
任何条件语句之内都可以继续写条件语句。无限套娃!当然这样的写法也只是为了满足我们的功能需求,以及增加代码的可读性。
package main
import "fmt"
func main() {
money := 1500000
if money > 0 {
if money > 7000000 {
fmt.Println("700w买房、买车")
} else if money > 5000000 {
fmt.Println("500w买房")
} else if money > 2000000 {
fmt.Println("200w买车")
} else {
fmt.Println("小于200w,距离梦想太遥远,继续赚钱")
}
} else {
fmt.Println("口袋空空如也,赶紧去打工!")
}
}
上面的条件说明了,余额大于0才可以考虑什么样的事情。当然我们也可以拆分成if else if...
的形式,不过对于代码的可读性就没有那么好了。
七、函数(Function)
函数是什么?
函数是一块执行特定任务的代码。一个函数是在输入源基础上,通过执行一系列的算法,生成预期的输出。
7.1 函数的声明
在 Go 语言中,函数声明通用语法如下:
func functionname(parametername type) returntype {
// 函数体(具体实现的功能)
}
函数的声明以关键词 func
开始,后面紧跟自定义的函数名 functionname (函数名)
。函数的参数列表定义在 (
和 )
之间,返回值的类型则定义在之后的 returntype (返回值类型)
处。声明一个参数的语法采用 参数名 参数类型 的方式,任意多个参数采用类似 (parameter1 type, parameter2 type) 即(参数1 参数1的类型,参数2 参数2的类型)
的形式指定。之后包含在 {
和 }
之间的代码,就是函数体。
函数中的参数列表和返回值并非是必须的,所以下面这个函数的声明也是有效的
func functionname() {
// 译注: 表示这个函数不需要输入参数,且没有返回值
}
实例
我们以写一个计算商品价格的函数为例,输入参数是单件商品的价格和商品的个数,两者的乘积为商品总价,作为函数的输出值。
// 这两个参数我们可以称之为:位置参数;因为我们调用传递是根据位置来进行的
func calculateBill(price int, no int) int {
var totalPrice = price * no // 商品总价 = 商品单价 * 数量
return totalPrice // 返回总价
}
// 如:calculateBill(10,20) 10这个数据根据位置传递给了price参数,20传递给了no参数
上述函数有两个整型的输入 price
和 no
,返回值 totalPrice
为 price
和 no
的乘积,也是整数类型。
如果有连续若干个参数,它们的类型一致,那么我们无须一一罗列,只需在最后一个参数后添加该类型。 例如,price int,no int
可以简写为 price, no int
,所以示例函数也可写成
func calculateBill(price, no int) int {
var totalPrice = price * no
return totalPrice
}
现在我们已经定义了一个函数,我们要在代码中尝试着调用它。调用函数的语法为 functionname(parameters)
。调用示例函数的方法如下:
package main
import (
"fmt"
)
func calculateBill(price, no int) int {
var totalPrice = price * no
return totalPrice
}
func main() {
price, no := 90, 6 // 定义 price 和 no,默认类型为 int
totalPrice := calculateBill(price, no)
fmt.Println("Total price is", totalPrice) // 打印到控制台上
}
该程序在控制台上打印的结果为
Total price is 540
7.2 多返回值
Go 语言支持一个函数可以有多个返回值。我们来写个以矩形的长和宽为输入参数,计算并返回矩形面积和周长的函数 rectProps。矩形的面积是长度和宽度的乘积, 周长是长度和宽度之和的两倍。即:
- 面积 = 长 * 宽
- 周长 = 2 * ( 长 + 宽 )
package main
import (
"fmt"
)
func rectProps(length, width float64)(float64, float64) {
var area = length * width
var perimeter = (length + width) * 2
return area, perimeter
}
func main() {
area, perimeter := rectProps(10.8, 5.6)
fmt.Printf("Area %f Perimeter %f", area, perimeter)
}
如果一个函数有多个返回值,那么这些返回值必须用 (
和 )
括起来。func rectProps(length, width float64)(float64, float64)
示例函数有两个 float64
类型的输入参数 length
和 width
,并返回两个 float64
类型的值。该程序在控制台上打印结果为:
Area 60.480000 Perimeter 32.800000
7.3 命名返回值
从函数中可以返回一个命名值。一旦命名了返回值,可以认为这些值在函数第一行就被声明为变量了。
上面的 rectProps 函数也可用这个方式写成:
func rectProps(length, width float64) (area, perimeter float64) {
area = length * width
perimeter = (length + width) * 2
return // 不需要明确指定返回值,默认返回 area, perimeter 的值
}
请注意:函数中的 return 语句没有显式返回任何值。由于 area 和 perimeter 在函数声明中指定为返回值, 因此当遇到 return 语句时, 它们将自动从函数返回。
7.4 空白符
_
在 Go 中被用作空白符,可以用作表示任何类型的任何值。
我们继续以 rectProps
函数为例,该函数计算的是面积和周长。假使我们只需要计算面积,而并不关心周长的计算结果,该怎么调用这个函数呢?这时,空白符 _ 就上场了。
下面的程序我们只用到了函数 rectProps
的一个返回值 area
package main
import (
"fmt"
)
func rectProps(length, width float64) (float64, float64) {
var area = length * width
var perimeter = (length + width) * 2
return area, perimeter
}
func main() {
area, _ := rectProps(10.8, 5.6) // 该函数返回了两个值,但我们只需要第一个值
fmt.Printf("Area %f ", area)
}
在程序的 area, _ := rectProps(10.8, 5.6) 这一行,我们看到空白符
_
指的是:用来跳过不要的计算结果。
7.5 不定长参数
在此之前,我们如果需要向函数传递参数,只能一个值对应一个形参来接收,那如果我们的值非常多,那就需要对应更多的形参来接收,那么使用不定长形参可以接收任意值。
package main
import "fmt"
// 接收任意长度的int类型值
func testIndefiniteLength(numArr ...int) {
// 接收到的是一个数字类型的数组,关于数组我们留到后续讲解
// numArr[0] 可以取出数组里面的第一个结果
fmt.Println(numArr)
}
// 我们需要注意的是,不定长参数只能放到位置参数后面,否则程序产生错误
func test(name string, numArr ...int) {
fmt.Println(name)
fmt.Println(numArr)
}
// func test(numArr ...int, name string)
// 这样的程序会产生错误,因为numArr是不定长,程序无法区分哪条数据是要给name参数的
func main() {
testIndefiniteLength(1, 2, 3, 4, 5)
test("jack", 1, 2, 3, 4, 5)
// 第一个结果根据位置传递给了name参数,后面多余的结果都传递给了numArr
}
并且一个函数只能存在一个不定长参数,那么如果我们有更多数据类型的参数需要传递呢?这个我们后续可以通过数组来实现。