1. 变量
声明单个变量
func main() {
// 1. 指定变量类型,若不赋值,则默认值。(int => 0)
var a int
a = 10
fmt.Println("a =",a)
// var a int = 10 声明变量并初始化
// 2. 类型推导,系统自动推导变量类型
var b = 10
fmt.Println("b =",b)
// 3. 省略var, 用 : 替代
c := 10
fmt.Println("c =",c)
var n1 float32 = -123.0000901
var n2 float64 = -123.0000901
fmt.Println("n1=",n1,"n2=",n2)
}
声明多个变量
func main() {
var width, height int = 100, 50
fmt.Println("width = ", width, "height = ", height)
// 在一个语句中声明不同类型的变量
var (
name = "golang"
age = 10
sex = "man"
)
fmt.Println(name, age, sex)
// 简短声明
width, h := 500, 100 //错误,要求 := 操作符的左边至少有一个变量是尚未声明的
fmt.Println(width, h)
// 变量也可以在运行时进行赋值
a, b := 145.8, 543.8
c := math.Min(a, b)
fmt.Println("minimum value is ", c)
}
2. 类型
Go支持的基本类型:
- bool
- 数字类型
- 有符号整型 : int8, int16, int32, int64, int
- 无符号整型 : uint8, uint16, unint32, unint64, uint
- 浮点型 : float32, float64
- 复数 : complex64, complex128
- int8的别名 : byte
- int32的别名 : rune
- string
类型转换
把 v 转换为 T 类型的语法是 T(v)
func main() {
i := 55
j := 67.8
//sum = i + j 错误
sum := i + int(j)
fmt.Println(sum) // 122
}
3. 常量
定义
var a int = 50
var b string = "I love Go"
在上面的代码中,变量 a 和 b 分别被赋值为常量 50 和 I love GO。
关键字 const 被用于表示常量,比如 50 和 I love Go。
即使在上面的代码中我们没有明确的使用关键字 const,但是在 Go 的内部,它们是常量。
package main
func main() {
const a = 55 // 允许
a = 89 // 不允许重新赋值
}
常量的值会在编译的时候确定。
因为函数调用发生在运行时,所以不能将函数的返回值赋值给常量。
package main
import (
"fmt"
"math"
)
func main() {
fmt.Println("Hello, playground")
var a = math.Sqrt(4) // 允许
const b = math.Sqrt(4) // 不允许
}
查看常量类型
func main() {
var name = "Sam"
fmt.Printf("type %T value %v", name, name)
// type string value Sam
}
4. 函数
函数声明
func functionname(parameter1 type, parameter2 type) returntype {
// 函数体
}
[可选] 参数,返回值
例如:计算 商品总价 = 商品单价 * 商品数量
func totalPrice(price int, number int) int {
var res = price * number
return res
}
func main() {
price, number := 2, 3
ans := totalprice(price, number)
fmt.Println(ans)
}
如果有连续若干个参数,它们的类型一致,那么我们无须一一罗列,只需在最后一个参数后添加该类型。 例如,price int, no int 可以简写为 price, no int
多个返回值
Go 语言支持一个函数可以有多个返回值。
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)
}
命名返回值
从函数中可以返回一个命名值。
一旦命名了返回值,可以认为这些值在函数第一行就被声明为变量了。
上面的 rectProps 函数也可用这个方式写成:
func rectProps(length, width float64)(area, perimeter float64) {
area = length * width
perimeter = (length + width) * 2
return // 不需要明确指定返回值,默认返回 area, perimeter 的值
}
请注意, 函数中的 return 语句没有显式返回任何值。由于 area 和 perimeter 在函数声明中指定为返回值, 因此当遇到 return 语句时, 它们将自动从函数返回。
空白符
_ 在 Go 中被用作空白符,可以用作表示任何类型的任何值。
我们继续以 rectProps 函数为例,该函数计算的是面积和周长。假使我们只需要计算面积,而并不关心周长的计算结果,该怎么调用这个函数呢?这时,空白符 _ 就上场了。
下面的程序我们只用到了函数 rectProps 的一个返回值 area
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)
}
5. 包
所有可执行的 Go 程序都必须包含一个 main 函数。
这个函数是程序运行的入口。
main 函数应该放置于 main 包中。
创建自定义的包
package rectangle
import "math"
// 计算矩形面积
func Area(len, wid float64) float64 {
area := len * wid
return area
}
// 计算矩形对角线的长度
func Diagonal(len, wid float64) float64 {
diagonal := math.Sqrt((len * len) + (wid * wid)) // 平方根
return diagonal
}
在 Go 中,任何以大写字母开头的变量或者函数都是被导出的名字。
其它包只能访问被导出的函数和变量。
因此,Area 和 Diagonal 函数的首字母大写。
导入自定义包
package main // 指定该文件属于main包
import "fmt" // 导入其他包
import "5包/geometry/rectangle"
func main() {
var rectLen, rectWidth float64 = 6, 7
fmt.Println("几何参数")
fmt.Printf("area %.2f \n", rectangle.Area(rectLen, rectWidth))
fmt.Printf("rectangle %.2f", rectangle.Diagonal(rectLen, rectWidth))
}
init 函数
init 函数可用于执行初始化任务,也可用于在开始执行之前验证程序的正确性。
包的初始化顺序如下:
- 首先初始化包级别(Package Level)的变量
- 紧接着调用 init 函数。包可以有多个 init 函数(在一个文件或分布于多个文件中),它们按照编译器解析它们的顺序进行调用。
package main // 指定该文件属于main包
// 导入其他包
import (
"5包/geometry/rectangle"
"fmt"
"log"
)
// 包级别变量
var rectLen, rectWidth float64 = 6, 7
// init 函数
func init() {
println("main package initialized")
if rectLen < 0{
log.Fatal("length is less than zero")
}
if rectWidth < 0{
log.Fatal("width is less than zero")
}
}
func main() {
fmt.Println("几何参数")
fmt.Printf("area %.2f \n", rectangle.Area(rectLen, rectWidth))
fmt.Printf("rectangle %.2f", rectangle.Diagonal(rectLen, rectWidth))
}
main 包的初始化顺序为:
- 首先初始化被导入的包。因此,首先初始化了 rectangle 包。
- 接着初始化了包级别的变量 rectLen 和 rectWidth。
- 调用 init 函数。
- 最后调用 main 函数。
空白标识符
导入了包,却不在代码中使用它,这在 Go 中是非法的。
然而,在程序开发的活跃阶段,又常常会先导入包,而暂不使用它。遇到这种情况就可以使用空白标识符 _。
package main
import (
"geometry/rectangle"
_ "fmt" // 错误屏蔽器
)
var _ = rectangle.Area // 错误屏蔽器
func main() {
}
6. if else
语法
if condition {
// 代码
} else if condition {
// 代码
} else {
// }和else 必须在同一行,避免插入分号
// 代码
}
在 Go 语言规则中,它指定在 } 之后插入一个分号,如果这是该行的最终标记。因此,在if语句后面的 } 会自动插入一个分号。
实际上我们的程序变成了
if num%2 == 0 {
fmt.Println("the number is even")
}; //semicolon inserted by Go
else {
fmt.Println("the number is odd")
}
分号插入之后。从上面代码片段可以看出第三行插入了分号。因此会报错。
举例:
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")
}
}
7. 循环
for 是 Go 语言唯一的循环语句。
Go 语言中并没有其他语言比如 C 语言中的 while 和 do while 循环。
for 循环语法
for 循环的三部分,初始化语句、条件语句、post 语句都是可选的。
for initialisation; condition; post {
}
eg.
func main() {
for i := 1; i <= 10; i++ {
fmt.Printf(" %d",i)
}
}
or.
func main() {
i := 0
for i <= 10 {
fmt.Printf("%d ", i)
i += 2
}
}
多个变量
在 for 循环中可以声明和操作多个变量。
func main() {
for no, i := 10, 1; i <= 10 && no <= 19; i, no = i+1, no+1{
fmt.Printf("%d * %d = %d \n", no, i ,no*i)
}
}
break
break 语句用于在完成正常执行之前突然终止 for 循环,之后程序将会在 for 循环下一行代码开始执行。
continue
continue 语句用来跳出 for 循环中当前循环。在 continue 语句后的所有的 for 循环语句都不会在本次循环中执行。循环体会在一下次循环中继续执行。
无限循环
无限循环的语法是:
for {
}
8. switch
switch是一个条件语句,用于将表达式的值与可能匹配的选项列表进行比较,并根据匹配情况执行相应的代码块。
func main(){
num := 3
switch num {
case 1:
fmt.Println(1)
case 2:
fmt.Println(2)
case 3:
fmt.Println(3)
default:
fmt.Println("5")
}
}
当匹配不成功时,执行default.
多表达式判断
通过用逗号分隔,可以在一个 case 中包含多个表达式。
func main() {
letter := "i"
switch letter {
case "a", "e", "i", "o", "u": // 一个选项多个表达式
fmt.Println("vowel")
default:
fmt.Println("not a vowel")
}
}
无表达式
在 switch 语句中,表达式是可选的,可以被省略。
如果省略表达式,则表示这个 switch 语句等同于 switch true,并且每个 case 表达式都被认定为有效,相应的代码块也会被执行。
func main() {
num := 75
switch {
// 表达式被省略了
case num >= 0 && num <= 50:
fmt.Println("num is greater than 0 and less than 50")
case num >= 51 && num <= 100:
fmt.Println("num is greater than 51 and less than 100")
case num >= 101:
fmt.Println("num is greater than 100")
}
}
Fallthrough 语句
在 Go 中,每执行完一个 case 后,会从 switch 语句中跳出来,不再做后续 case 的判断和执行。
使用 fallthrough 语句可以在已经执行完成的 case 之后,把控制权转移到下一个 case 的执行代码中。
func main() {
switch num := number(); {
// num is not a constant
case num < 50:
fmt.Printf("%d is lesser than 50\n", num)
fallthrough
case num < 100:
fmt.Printf("%d is lesser than 100\n", num)
fallthrough
case num < 200:
fmt.Printf("%d is lesser than 200", num)
}
}
fallthrough语句应该是 case 子句的最后一个语句。
9. 数组和切片
数组
数组是同一类型元素的集合。Go 语言中不允许混合不同类型的元素。
如果是 interface{} 类型数组,可以包含任意类型
数组的声明
一个数组的表示形式为 [n]T。n 表示数组中元素的数量,T 代表每个元素的类型。
func main() {
var a [3]int
fmt.Println(a) // [0 0 0]
a[0] = 1
a[1] = 2
a[2] = 3
fmt.Println(a) // [1 2 3]
}
简略声明:
func main() {
a := [3]int{
1,2,3}
fmt.Println(a) // [1 2 3]
b := [3]int{
1}
fmt.Println(b) // [1 0 0]
}
省略数组长度,用 ... 代替:
func main() {
a := [...]int{1,2,3}
fmt.Println(a) // [1 2 3]
}
数组的大小是类型的一部分。因此 [5]int 和 [25]int 是不同类型。数组不能调整大小。
Go 中的数组是值类型而不是引用类型。这意味着当数组赋值给一个新的变量时,该变量会得到一个原始数组的副本。如果对新变量进行更改,则不会影响原始数组。
func main() {
a := [...]int{
1,2,3,4,5}
b := a
b[0] = 6
fmt.Println(a) // [1 2 3 4 5]
fmt.Println(b) // [6 2 3 4 5]
}
同样,当数组作为参数传递给函数时,它们是按值传递,而原始数组保持不变。
func changeLocal(num [5]int) {
num[0] = 55
fmt.Println("inside function ", num) // [55 6 7 8 8]
}
func main() {
num := [...]int{
5, 6, 7, 8, 8}
fmt.Println("before passing to function ", num) // [5 6 7 8 8]
changeLocal(num)
fmt.Println("after passing to function ", num) // [5 6 7 8 8]
}
数组的长度
通过将数组作为参数传递给 len() 函数, len(Array)可以得到数组的长度。
range
通过使用 for 循环的 range 方法来遍历数组,返回索引和该索引处的值
func main() {
a := [...]float64{
12.3, 23.4, 34.5, 45.6, 56.7}
sum := float64(0)
for i,v := range a{
fmt.Printf("数组a中第%d位元素是%.2f \n", i, v)
sum += v
}
fmt.Println("数组a的元素和为:", sum)
}
如果只需要值并希望忽略索引,则可以通过用 _ 空白标识符替换索引来执行。
for _, v := range a {
// ignores index
}
多维数组
func main() {
a := [3][2]string{
{
"lion", "tiger"},
{
"cat", "dog"},
{
"pigeon", "peacock"}, //逗号必须
}
}
切片
切片是由数组建立的一种方便、灵活且功能强大的包装(Wrapper)。切片本身不拥有任何数据。它们只是对现有数组的引用。
创建一个切片
带有 T 类型元素的切片由 []T 表示。
使用语法 a[start:end] 创建一个从 a 数组索引 start 开始到 end - 1 结束的切片:
func main() {
a := [5]int{
1,2,3,4,5}
var b []int = a[1:4]
fmt.Println(b)
}
or
创建一个有 3 个整型元素的数组,并返回一个存储在 c 中的切片引用:
func main() {
c := []int{
6, 7, 8}
fmt.Println(c)
}
切片的修改
切片自己不拥有任何数据。它只是底层数组的一种表示。对切片所做的任何修改都会反映在底层数组中。
func main() {
darr := [...]int{
57, 89, 90, 82, 100, 78, 67, 69, 59}
dslice := darr[2:5]
fmt.Println("array before", darr) // [57 89 90 82 100 78 67 69 59]
for i := range dslice {
dslice[i]++
}
fmt.Println("array after", darr) // [57 89 91 83 101 78 67 69 59]
}
当多个切片共享同一个数组时,每个切片所做的修改都会反映在数组中。
切片的长度和容量
切片的长度是切片中的元素数。切片的容量是从创建切片索引开始的底层数组中元素数。
func main() {
fruitarray := [...]string{
"apple", "orange", "grape", "mango", "water melon", "pine apple", "chikoo"}
fruitslice := fruitarray[1:3]
fmt.Println(len(fruitslice)) // 2
fmt.Println(cap(fruitslice)) // 6
}
使用 make 创建一个切片
make 函数创建一个数组,并返回引用该数组的切片:
func make([]类型,长度,容量)
容量是可选参数, 默认值为切片长度。
func main() {
i := make([]int, 5, 5)
fmt.Println(i) // [0 0 0 0 0]
}
追加切片元素
数组的长度是固定的,不能增加。
切片是动态的,使用 append 可以将新元素追加到切片上。append 函数的定义是
func append(s[]T,x ... T)。
如果切片由数组支持,并且数组本身的长度是固定的,那么切片如何具有动态长度。以及内部发生了什么?
当新的元素被添加到切片时,会创建一个新的数组。现有数组的元素被复制到这个新数组中,并返回这个新数组的新切片引用。现在新切片的容量是旧切片的两倍(不同版本扩容方式不同)。
func main() {
cars := []string{
"Ferrari", "Honda", "Ford"}
fmt.Println("长度是:", len(cars), ", 容量是:", cap(cars)) // 长度是: 3 , 容量是: 3
cars = append(cars, "Toyota")
fmt.Println("长度是:", len(cars), ", 容量是:", cap(cars)) // 长度是: 4 , 容量是: 6
}
切片类型的零值为 nil。一个 nil 切片的长度和容量为 0。
也可以使用 ... 运算符将一个切片添加到另一个切片。
func main() {
veggies := []string{"potatoes", "tomatoes", "brinjal"}
fruits := []string{"oranges", "apples"}
food := append(veggies, fruits...) //将fruits添加到veggies中
fmt.Println("food:",food) // food: [potatoes tomatoes brinjal oranges apples]
切片的函数传递
我们可以认为,切片在内部可由一个结构体类型表示。这是它的表现形式,
type slice struct {
Length int
Capacity int
ZerothElement *byte
}
切片包含长度、容量和指向数组第零个元素的指针。
当切片传递给函数时,即使它通过值传递,指针变量也将引用相同的底层数组。
因此,当切片作为参数传递给函数时,函数内所做的更改也会在函数外可见,区别于数组。
func subtactOne(numbers []int) {
for i := range numbers {
numbers[i] -= 2
}
}
func main() {
nos := []int{
8, 7, 6}
fmt.Println("slice before function call", nos) // [8 7 6]
subtactOne(nos)
fmt.Println("slice after function call", nos) // [6 5 4]
}
多维切片
类似于数组,切片可以有多个维度。
func main() {
pls := [][]string {
{
"C", "C++"},
{
"JavaScript"},
{
"Go", "Rust"},
}
for _, v1 := range pls {
for _, v2 := range v1 {
fmt.Printf("%s ", v2)
}
fmt.Printf("\n")
}
}
内存优化
切片持有对底层数组的引用。只要切片在内存中,数组就不能被垃圾回收。
假设有一个非常大的数组,我们只想处理它的一小部分。由这个数组创建一个切片,并开始处理切片,在切片引用时数组仍然存在内存中。
一种解决方法是使用 copy函数 func copy(dst,src[]T)int 来生成一个切片的副本。这样我们可以使用新的切片,原始数组可以被垃圾回收。
func countries() []string {
countries := []string{
"USA", "Singapore", "Germany", "India", "Australia"}
neededCountries := countries[:len(countries)-2]
countriesCpy := make([]string, len(neededCountries))
copy(countriesCpy, neededCountries) // 将 neededCountries 复制到 countriesCpy
return countriesCpy
}
func main() {
countriesNeeded := countries()
fmt.Println(countriesNeeded)
}
现在 countries 数组可以被垃圾回收, 因为 neededCountries 不再被引用。
10. 可变参数函数
可变参数函数是一种参数个数可变的函数。
语法
如果函数最后一个参数被记作 ...T ,这时函数可以接受任意个 T 类型参数作为最后一个参数。
请注意只有函数的最后一个参数才允许是可变的。
拿append函数举例:
func append(slice []Type, elems ...Type)
上面是 append 函数的定义。在定义中 elems 是可变参数。这样 append 函数可以接受可变化的参数。
可变参数函数的工作原理是把可变参数转换为一个新的切片。
func find(num int, nums ...int) {
fmt.Printf("type of nums is %T\n", nums)
found := false
for i, v := range nums {
if v == num {
fmt.Println(num, "found at index", i, "in", nums)
found = true
}
}
if !found {
fmt.Println(num, "not found in ", nums)
}
fmt.Printf("\n")
}
func main() {
find(89, 89, 90, 95)
find(45, 56, 67, 45, 90, 109)
find(78, 38, 56, 98)
find(87)
}
以上面程序中为例,find 函数中的可变参数是 89,90,95 。 find 函数接受一个 int 类型的可变参数。因此这三个参数被编译器转换为一个 int 类型切片 int []int{89, 90, 95} 然后被传入 find函数。
给可变参数函数传入切片
在切片后加上 ... 后缀,切片将直接传入函数,不再创建新的切片
func find(num int, nums ...int) {
fmt.Printf("type of nums is %T\n", nums)
found := false
for i, v := range nums {
if v == num {
fmt.Println(num, "found at index", i, "in", nums)
found = true
}
}
if !found {
fmt.Println(num, "not found in ", nums)
}
fmt.Printf("\n")
}
func main() {
nums := []int{
89, 90, 95}
find(89, nums...)
}
11. map
map 是在 Go 中将值(value)与键(key)关联的内置类型。通过相应的键可以获取到值。
创建map
语法:
make(map[type of key]type of value)
personSalary := make(map[string]int)
上面的代码创建了一个名为 personSalary 的 map,其中键是 string 类型,值是 int 类型。
map 必须使用 make 函数初始化,未初始化的 map 的值是 nil。
给 map 添加元素
func main() {
personSalary := make(map[string]int)
personSalary["steve"] = 12000
personSalary["jamie"] = 15000
personSalary["mike"] = 9000
fmt.Println(personSalary) //map[jamie:15000 mike:9000 steve:12000]
}
or:
func main() {
personSalary := map[string]int {
"steve": 12000,
"jamie": 15000,
}
personSalary["mike"] = 9000
fmt.Println(personSalary) // map[jamie:15000 mike:9000 steve:12000]
}
获取 map 中的元素
获取值
获取 map 元素的语法是 map[key]
func main() {
personSalary := map[string]int{
"steve": 12000,
"jamie": 15000,
}
personSalary["mike"] = 9000
employee := "jamie"
fmt.Println(employee, "is", personSalary[employee]) // jamie is 15000
fmt.Println("joe is", personSalary["joe"]) //joe is 0
}
如果我们获取一个不存在的元素,会返回
int类型的零值0
map 中 key 是否存在?
如果想知道 map 中到底是不是存在这个 key,该怎么做 ?
value, ok := map[key]
上面就是获取 map 中某个 key 是否存在的语法。如果 ok 是 true,表示 key 存在,key 对应的值就是 value ,反之表示 key 不存在。
func main() {
personSalary := map[string]int{
"steve": 12000,
"jamie": 15000,
}
personSalary["mike"] = 9000
newEmp := "joe"
value, ok := personSalary[newEmp]
if ok == true {
fmt.Println("Salary of", newEmp, "is", value)
} else

最低0.47元/天 解锁文章
4337

被折叠的 条评论
为什么被折叠?



