GO笔记(一)

一、helloworld

1
2
3
4
5
6
7
package main

import "fmt"

func main() {  
    fmt.Println("Hello World")
}

二、变量声明

和C的完全相反,变量类型在后面,在变量名前面的是var

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
var area int
var age int = 29 // 声明变量并初始化
var width, height int
var width, height int = 100, 50 // 声明多个

var (
     name   = "naveen"
     age    = 29
     height int
    )
1
2
name, age := "naveen", 29 // 简短声明
name, age := "naveen" //error

简短声明的语法要求 := 操作符的左边至少有一个变量是尚未声明的

1
2
3
4
5
	a, b := 20, 30 // 声明变量a和b

    b, c := 40, 50 // b已经声明,但c尚未声明
    
    b, c = 80, 90 // 给已经声明的变量b和c赋新值
1
2
3
    a, b := 20, 30 // 声明a和b

    a, b := 40, 50 // 错误,没有尚未声明的变量

上面运行后会抛出 no new variables on left side of := 的错误,因为 a 和 b 的变量已经声明过了,:= 的左边并没有尚未声明的变量

1
2
    age := 29      // age是int类型
    age = "naveen" // 错误,尝试赋值一个字符串给int类型变量

Go 是强类型(Strongly Typed)语言,因此不允许某一类型的变量赋值为其他类型的值。下面的程序会抛出错误 cannot use "naveen" (type string) as type int in assignment,这是因为 age 本来声明为 int 类型,而我们却尝试给它赋字符串类型的值。

三、变量类型

下面是 Go 支持的基本类型:

  • bool
  • 数字类型
    • int8, int16, int32, int64, int
    • uint8, uint16, uint32, uint64, uint
    • float32, float64
    • complex64, complex128
    • byte
    • rune
  • string

各个类型的取值范围

类型 Golang中文社区

1
2
3
4
5
6
	const a = 5//常量变量
	var intVar int = a
	var int32Var int32 = a
	var float64Var float64 = a
	var complex64Var complex64 = a
	fmt.Println("intVar", intVar, "\nint32Var", int32Var, "\nfloat64Var", float64Var, "\ncomplex64Var", complex64Var)

输出结果如下:

intVar 5 int32Var 5 float64Var 5 complex64Var (5+0i)

fmt.Println可以做到一个语句里面换行输出。

如果不知道某个变量的类型可以用%T,能输出类型名。

常量的知识

四、函数

函数声明通用语法:

1
2
3
4
5
6
7
func functionname(parametername type) returntype {  
    // 函数体(具体实现的功能)
}

func functionname() {  
    // 译注: 表示这个函数不需要输入参数,且没有返回值
}

Go 语言支持一个函数可以有多个返回值

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
func rectProps(length, width float64)(float64, float64) {  
    var area = length * width
    var perimeter = (length + width) * 2
    return area, perimeter
}

func rectProps(length, width float64)(area, perimeter float64) {  
    area = length * width
    perimeter = (length + width) * 2
    return // 不需要明确指定返回值,默认返回 area, perimeter 的值
}

函数中的 return 语句没有显式返回任何值。由于 areaperimeter 在函数声明中指定为返回值, 因此当遇到 return 语句时, 它们将自动从函数返回

_ 在 Go 中被用作空白符,可以用作表示任何类型的任何值

1
2
3
4
func main() {  
    area, _ := rectProps(10.8, 5.6) // 返回值周长被丢弃
    fmt.Printf("Area %f ", area)
}

area, _ := rectProps(10.8, 5.6) 这一行,我们看到空白符 _ 用来跳过不要的计算结果。

五、包

所有可执行的 Go 程序都必须包含一个 main 函数。这个函数是程序运行的入口。main 函数应该放置于 main 包中。 package packagename 这行代码指定了某一源文件属于一个包。它应该放在每一个源文件的第一行。

package main 这一行指定该文件属于 main 包。import "packagename" 语句用于导入一个已存在的包。在这里我们导入了 fmt 包,包内含有 Println 方法。接下来是 main 函数,它会打印 Geometrical shape properties

1
2
3
4
5
6
package main 
import "fmt"

func main() {  
    fmt.Println("Geometrical shape properties")
}

创建自定义的包

包 - Go语言中文网 - Golang中文社区

注意到自定义包内的函数都是以大写字母开头的

六、if-else

else 语句应该在 if 语句的大括号 } 之后的同一行中。如果不是,编译器会不通过。 然后会报这个错误:

syntax error: unexpected else, expecting }

1
2
3
4
5
if condition {  
	} 
else if condition {
	}else {
}

在if里面也能进行赋值操作

1
2
3
4
5
6
7
func main() {  
    if num := 10; num % 2 == 0 { //checks if number is even
        fmt.Println(num,"is even") 
    }  else {
        fmt.Println(num,"is odd")
    }
}

在上面的程序中,numif 语句中进行初始化,num 只能从 ifelse 中访问。也就是说 num 的范围仅限于 if else 代码块。如果我们试图从其他外部的 if 或者 else 访问 num,编译器会不通过。

写的一个c语言转go,看区别

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
int main(void) {
	int a = 1, b = 0;
	if (!a)
		b++;
	else if (a == 0)
		if (a)
			b += 2;
		else
			b += 3;
	printf("%d", b);
	return 0;

}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
func main() {
	a, b := 1, 0
	if a == 0 {
		b = b + 1
	} else if a == 0 {
		if a == 1 {
			b += 2
		} else {
			b += 3
		}
	}
	fmt.Printf("%d\n", b)
}

七、循环

for循环

初始化语句只执行一次。循环初始化后,将检查循环条件。如果条件的计算结果为 true ,则 {} 内的循环体将执行,接着执行 post 语句。post 语句将在每次成功循环迭代后执行。在执行 post 语句后,条件将被再次检查。如果为 true, 则循环将继续执行,否则 for 循环将终止。(译注:这是典型的 for 循环三个表达式,第一个为初始化表达式或赋值语句;第二个为循环条件判定表达式;第三个为循环变量修正表达式,即此处的 post )

1
2
for initialisation; condition; post {  
}

break可以跳出循环,continue可以进入下一个循环

无限循环

1
2
3
4
5
func main() {  
    for {
        fmt.Println("Hello World")
    }
}

程序就会一直打印Hello World不会停止。

八、switch

switch 是一个条件语句,用于将表达式的值与可能匹配的选项列表进行比较,并根据匹配情况执行相应的代码块。它可以被认为是替代多个 if else 子句的常用方式。

值得记住的是

多表达式判断

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
func main() {
    letter := "i"
    switch letter {
    case "a", "e", "i", "o", "u": 
        // 一个选项多个表达式
        fmt.Println("vowel")
    default:
        fmt.Println("not a vowel")
    }
}

case "a","e","i","o","u": 这一行中,列举了所有的元音。只要匹配该项,则将输出 vowel

无表达式switch

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
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")
    }
}

在上述代码中,switch 中缺少表达式,因此默认它为 true,true 值会和每一个 case 的求值结果进行匹配。case num >= 51 && <= 100: 为 true,所以程序输出 num is greater than 51 and less than 100。这种类型的 switch 语句可以替代多个 if else 子句。

Fallthrough 语句

C的switch是执行完case后会接着执行下一个case,

go的case执行完会跳出switch,而且case是可以写表达式的,所以如果要继续执行下一个case就得用fallthrough语句

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
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)
    }
}

switch 和 case 的表达式不一定是常量。它们也可以在运行过程中通过计算得到。在上面的程序中,num 被初始化为函数 number() 的返回值。程序运行到 switch 中时,会计算出 case 的值。case num < 100: 的结果为 true,所以程序输出 75 is lesser than 100。当执行到下一句 fallthrough 时,程序控制直接跳转到下一个 case 的第一个执行逻辑中,所以打印出 75 is lesser than 200。最后这个程序的输出会是:

75 is lesser than 100 75 is lesser than 200

fallthrough 语句应该是 case 子句的最后一个语句。如果它出现在了 case 语句的中间,编译器将会报错:fallthrough statement out of place

九、数组和切片

数组是同一类型元素的集合。例如,整数集合 5,8,9,79,76 形成一个数组。Go 语言中不允许混合不同类型的元素,例如包含字符串和整数的数组。(译者注:当然,如果是 interface{} 类型数组,可以包含任意类型)

1
2
3
4
var a [3]int //int array with length 3
a := [3]int{12, 78, 50} // short hand declaration to create array
a := [...]int{12, 78, 50} 
// ... makes the compiler determine the length

Go 中的数组是值类型而不是引用类型。这意味着当数组赋值给一个新的变量时,该变量会得到一个原始数组的一个副本。如果对新变量进行更改,则不会影响原始数组。

数组的长度

1
2
    a := [...]float64{67.7, 89.8, 21, 78}
    fmt.Println("length of a is",len(a))//输出length of a is 4

数组可以直接输出

1
2
	a := [3]int{1, 2, 3}
	fmt.Println(a)//结果是[1 2 3]

数组的遍历

1
2
3
4
5
6
7
8
9
	a := [...]float64{67.7, 89.8, 21, 78}
    for i := 0; i < len(a); i++ { 
        // looping from 0 to the length of the array
        fmt.Printf("%d th element of a is %.2f\n", i, a[i])
    }
	for i, v := range a {
        //range returns both the index and value
        fmt.Printf("%d the element of a is %.2f\n", i, v)
    }

for i, v := range a 利用的是 for 循环 range 方式。 它将返回索引和该索引处的值。

多维数组

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
func main() {
    a := [3][2]string{
        {"lion", "tiger"},
        {"cat", "dog"},
        {"pigeon", "peacock"}, //逗号是必需的
        // this comma is necessary. The compiler will complain if you omit this comma
    }
    printarray(a)
    var b [3][2]string
    b[0][0] = "apple"
    b[0][1] = "samsung"
    b[1][0] = "microsoft"
    b[1][1] = "google"
    b[2][0] = "AT&T"
    b[2][1] = "T-Mobile"
    fmt.Printf("\n")
    printarray(b)
}

用简略语法声明一个二维字符串数组 a 。末尾的逗号是必需的。这是因为根据 Go 语言的规则自动插入分号。

切片

切片是由数组建立的一种方便、灵活且功能强大的包装(Wrapper)。切片本身不拥有任何数据。它们只是对现有数组的引用。

1
2
3
	a := [5]int{76, 77, 78, 79, 80}
	var b []int = a[1:4] // creates a slice from a[1] to a[3]
	fmt.Println(b)//[77 78 79]

修改切片

切片自己不拥有任何数据。它只是底层数组的一种表示。对切片所做的任何修改都会反映在底层数组中

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
darr := [...]int{57, 89, 90, 82, 100, 78, 67, 69, 59}
	dslice := darr[2:5]
	fmt.Println("array before", darr)
	for i := range dslice {
		dslice[i]++
	}
	fmt.Println("array after", darr)
输出结果
array before [57 89 90 82 100 78 67 69 59]
array after [57 89 91 83 101 78 67 69 59]

当多个切片共用相同的底层数组时,每个切片所做的更改都将反映在数组中。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
func main() {
    numa := [3]int{78, 79 ,80}
    nums1 := numa[:] 
    // creates a slice which contains all elements of the array
    nums2 := numa[:]
    nums1[0] = 100
    nums2[1] = 101
}
结果
[78 79 80]
[100 79 80]
[100 101 80]

numa [:] 缺少开始和结束值。开始和结束的默认值分别为 0len (numa)。两个切片 nums1nums2 共享相同的数组。

关于切片更多的知识: 数组和切片 Golang中文社区

十、可变函数参数

可变参数函数是一种参数个数可变的函数。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
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)
}

在上面程序中 func find(num int, nums ...int) 中的 nums 可接受任意数量的参数。在 find 函数中,参数 nums 相当于一个整型切片。

可变函数传切片语法糖

1
2
3
    nums := []int{89, 90, 95}
    find(89, nums...)//正确语法糖
	find(89, nums)//错误示范

将一个切片传给一个可变参数函数。 这种情况下无法通过编译,编译器报出错误 main.go:23: cannot use nums (type []int) as type int in argument to find

nums 作为可变参数传入 find 函数。前面我们知道,这些可变参数参数会被转换为 int 类型切片然后在传入 find 函数中。但是在这里 nums 已经是一个 int 类型切片,编译器试图在 nums 基础上再创建一个切片,

之所以会失败是因为 nums 是一个 []int类型 而不是 int类型。

不直观的错误

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
func change(s ...string) {
    s[0] = "Go"
    s = append(s, "playground")
    fmt.Println(s)
}

func main() {
    welcome := []string{"hello", "world"}
    change(welcome...)
    fmt.Println(welcome)
    //[Go world playground]
	//[Go world]
}

函数参数的传递是通过值传递进行的。当您传递一个切片作为参数给一个函数时,实际上传递的是切片的一个副本,而不是切片本身。因此,在函数内部对切片进行的任何修改都不会影响原始切片。这是因为在函数内部,您实际上是在操作切片副本,而不是原始切片。对切片副本的修改不会影响原始切片,因为它们是两个不同的对象

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
func modifySlice(s *[]int) {
	// 在函数内部,通过指针修改切片
	(*s) = append(*s, 1, 2, 3)
}

func main() {
	slice := []int{1, 2, 3}
	modifySlice(&slice) 
    // 将切片的指针传递给函数
	fmt.Println(slice)  
    // 输出 [1 2 3 1 2 3]
}

通过修改切片内容来修改原数组内容是因为切片和数组在内部结构上是紧密相关的。切片是数组的一个连续片段,它包含了指向数组的指针、切片的长度和容量。当您修改切片的内容时,实际上是在修改底层数组的内容。

由于切片和数组之间的紧密关系,对切片的修改会直接反映到数组上。这是因为切片的指针指向的是底层数组的起始位置,因此对切片的修改会直接影响到底层数组的相应位置。

例如,如果您有一个切片 s,它的底层数组是 a,那么 s[i] = x 实际上是在 a[i] = x。因此,当您修改切片的内容时,底层数组的内容也会相应地被修改。

学习参考内容来自: Go 系列教程(Golang tutorial series) - 专栏 - Go语言中文网 - Golang中文社区