Go语言的基本概念与语法 - Java技术债务

2024-06-21 16:58:45 浏览数 (1)

包package

每个 Go 程序都是由包组成的.

程序从 main包开始运行.

本程序通过导入路径 "fmt" and "math/rand"来使用这两个包.

按照约定,包名与导入路径的最后一个元素相同。例如,"math/rand" 包中的源码均以packagerand` 语句开始.

注意: 执行这些程序的环境是确定性的 所以每次运行示例程序 rand.Intn 都会返回相同的数字.

代码语言:javascript复制
package main

import (
	"fmt"
	"math/rand"
)

func main() {
	fmt.Println("My favorite number is", rand.Intn(10))
}

Imports(导入)

此代码将导入分组到带括号的"factored(分解)"导入语句中.

您还可以编写多个导入语句,例如:

代码语言:javascript复制
import "fmt"
import "math"
或者
import (
	"fmt"
	"math"
)

但是使用factored import语句是一种很好的风格.

基本数据类型

bool

布尔值是一组布尔值,truefalse

默认值:false

string

字符串是所有 8 位字节字符串的集合,通常必须表示 UTF-8 编码的文本。字符串可能为空,但 不是零。字符串类型的值是不可变的。

默认值:""

int

int 是大小至少为 32 位的有符号整数类型。这是一个非重复类型,而不是 int32 的别名。

默认值:0

int类型还有其他相关类型:

  • int8:是所有有符号 8 位整数的集合。 范围:-2 ^8 到 2 ^8 - 1。
  • int16:是所有有符号 16 位整数的集合。 范围:-2 ^16 到 2 ^16 - 1。
  • int32:是所有有符号 32 位整数的集合。 范围:-2 ^32 到 2 ^32 - 1。
  • int64:是所有有符号 64 位整数的集合。 范围:-2 ^64 到 2 ^64 - 1。
  • uint:一种无符号整数类型,大小至少为32位。这是一个不同的类型,而不是 uint32 的别名。
  • uint8:是所有无符号 8 位整数的集合。 范围:0 到 2 ^8 - 1。
  • uint16:是所有无符号 16 位整数的集合。 范围:0 到 2 ^16 - 1。
  • uint32:是所有无符号 32 位整数的集合。 范围:0 到 2 ^32 - 1。
  • uint64:是所有无符号 64 位整数的集合。 范围:0 到 2 ^64 - 1。
  • uintptr:是一个整数类型,它足够大,可以容纳任何指针。

注意: int, uint, and uintptr 在 32 位系统上通常为 32 位宽,在 64 位系统上则为 64 位宽. 当你需要一个整数值时,你应该使用 int 除非你有特定的理由使用一个固定大小或无符号的整数类型。

byte

字节是 uint8 的别名,在所有方面都等效于 uint8。用于区分字节值和 8 位无符号 整数值。

rune

rune 是 Int32 的别名,在所有方面都等同于 Int32。用于区分字符值和整数值。

float

  • float32:float32 是所有 IEEE-754 32 位浮点数的集合。
  • float64:float64 是所有 IEEE-754 64 位浮点数的集合。

complex

复数是由两个浮点数表示的,其中一个表示实部(real),一个表示虚部(imag)。

  • complex64:是所有复数的集合,float32 实数和虚部。
  • complex128:是所有复数的集合,float64 实数和虚部。复数的默认类型。

变量

变量声明

var 语句用于声明一个变量列表,跟函数的参数列表一样,类型在最后。

var 语句可以出现在包或函数级别。

语法:var i int

var声明可以包含初始化程序,每个变量一个。如果存在初始化器,则可以省略类型;

在函数内部, 可以使用 := 短赋值语句来代替具有隐式类型的 var 声明。

但是,在函数外部, 每个语句都以关键字 (var, func, 等) 因此 := 结构不可用。

觉得每行都用 var 声明变量比较烦琐?没关系,还有一种为懒人提供的定义变量的方法:

代码语言:javascript复制
var (
    a int
    b string
    c []float32
    d func() bool
    e struct {
        x int
    }
)

使用关键字 var 和括号,可以将一组变量定义放在一起。

变量的初始化

可以这么写:var hp int = 100 也可以省略int直接这么写:var hp = 100 下边可以有为什么可以这么写的原因。还有短变量初始化操作: hp := 100

匿名变量

匿名变量的特点是一个下画线“”,“”本身就是一个特殊的标识符,被称为空白标识符。它可以像其他标识符那样用于变量的声明或赋值(任何类型都可以赋值给它),但任何赋给这个标识符的值都将被抛弃,因此这些值不能在后续的代码中使用,也不可以使用这个标识符作为变量对其它变量进行赋值或运算。使用匿名变量时,只需要在变量声明的地方使用下画线替换即可。例如:

代码语言:javascript复制
func GetData() (int, int) {
    return 100, 200
}
func main(){
    a, _ := GetData()
    _, b := GetData()
    fmt.Println(a, b)
}

常量

常量像变量一样声明,但使用 const 关键字。常量可以是字符、字符串、布尔值或数字值。不能使用 := 语法声明常量。

数字常量是高精度 。无类型常量采用其上下文所需的类型。(一个 int 最多可以存储 64 位整数,有时更少.)

和变量声明一样,可以批量声明多个常量:

代码语言:javascript复制
const (
    e  = 2.7182818
    pi = 3.1415926
)

iota 常量生成器

常量声明可以使用 iota 常量生成器初始化,它用于生成一组以相似规则初始化的常量,但是不用每行都写一遍初始化表达式。在一个 const 声明语句中,在第一个声明的常量所在的行,iota 将会被置为 0,然后在每一个有常量声明的行加一。

代码语言:javascript复制
type Weekday int
const (
    Sunday Weekday = iota
    Monday
    Tuesday
    Wednesday
    Thursday
    Friday
    Saturday
)

for语句

for语法结构

Go 只有一个循环结构,即 for 循环.

基本 for 循环包含三个由分号分隔的组件:

  • init 语句: 在第一次迭代之前执行
  • 条件表达式:在每次迭代前求值
  • post 语句:在每次迭代结束时执行

init语句通常是一个简短的变量声明,并且在那里声明的变量仅在该 for 语句的范围内可见.

一旦布尔条件评估为 false ,循环将停止迭代。init 和 post 语句是可选的;如果省略循环条件,它将永远循环,因此可以紧凑地表示无限循环.

注意: 与 C、Java 或 JavaScript 等其他语言不同,for 语句的三个组件周围没有括号,并且大括号 { } 始终需要。

代码语言:javascript复制
package main

import "fmt"

func main() {
	sum := 0
	for i := 0; i < 10; i   {
		sum  = i
	}
	for ; sum < 1000; {
		sum  = sum
	}
	fmt.Println(sum)
}

for 是Go中的while

C和Java 的 while 在 Go 中叫做 for

代码语言:javascript复制
package main

import "fmt"

func main() {
	sum := 1
	for sum < 1000 {
		sum  = sum
	}
	fmt.Println(sum)
}

if语句

Go 的 if 语句就像它的 for 循环;表达式不需要用括号 ( ) 括起来,但需要大括号 { } .

代码语言:javascript复制
package main

import (
	"fmt"
	"math"
)

func sqrt(x float64) string {
	if x < 0 {
		return sqrt(-x)   "i"
	}
	return fmt.Sprint(math.Sqrt(x))
}

func main() {
	fmt.Println(sqrt(2), sqrt(-4))
}

就像 forif 语句可以在条件之前以短语句执行。语句声明的变量作用域仅在 if 之内。

代码语言:javascript复制
package main

import (
	"fmt"
	"math"
)

func pow(x, n, lim float64) float64 {
	if v := math.Pow(x, n); v < lim {
		return v
	}
	return lim
}

func main() {
	fmt.Println(
		pow(3, 2, 10),
		pow(3, 3, 20),
	)
}

If 和 else

if 短语句中声明的变量也可以在任何 else 块中使用。

代码语言:javascript复制
package main

import (
	"fmt"
	"math"
)

func pow(x, n, lim float64) float64 {
	if v := math.Pow(x, n); v < lim {
		return v
	} else {
		fmt.Printf("%g >= %gn", v, lim)
	}
	// can't use v here, though
	return lim
}

func main() {
	fmt.Println(
		pow(3, 2, 10),
		pow(3, 3, 20),
	)
}

switch

switch 语句是编写一连串 if - else 语句的简便方法。它运行其值等于条件表达式的第一个 case。

Go 的 switch 类似于 C、C 、Java、JavaScriptPHP 中的 switch,只是 Go 只运行选定的case,而不是所有后续case。

实际上,Go 自动提供了在这些语言中每个 case 后面所需的 break 语句。另一个重要的区别是 Go 的 switchcase 不需要是常量,所涉及的值也不需要是整数。

代码语言:javascript复制
package main

import (
	"fmt"
	"runtime"
)

func main() {
	fmt.Print("Go runs on ")
	switch os := runtime.GOOS; os {
		case "darwin":
			fmt.Println("OS X.")
		case "linux":
			fmt.Println("Linux.")
		default:
			// freebsd, openbsd,
			// plan9, windows...
			fmt.Printf("%s.n", os)
	}
}

switch 的求值顺序

switch 的 case 语句从上到下顺次执行,直到匹配成功时停止。 如果 i==0不调用 f

代码语言:javascript复制
switch i {
case 0:
case f():
}

注意: Go playground 场中的时间总是从 2009-11-10 23:00:00 UTC 开始。

没有条件的switch

没有条件的 Switch 同 switch true 一样。这种构造可以是编写长 if-then-else 链的一种干净方式.

代码语言:javascript复制
package main

import (
	"fmt"
	"time"
)

func main() {
	t := time.Now()
	switch {
	case t.Hour() < 12:
		fmt.Println("Good morning!")
	case t.Hour() < 17:
		fmt.Println("Good afternoon.")
	default:
		fmt.Println("Good evening.")
	}
}

defer语句

defer 语句推迟函数的执行,直到周围的函数返回。

延迟调用的参数会立即计算,但直到周围的函数返回时才会执行函数调用。

代码语言:javascript复制
package main

import "fmt"

func main() {
	defer fmt.Println("world")

	fmt.Println("hello")
}

Stacking defers(defer栈)

延迟的函数调用被推送到栈上。当函数返回时,其延迟调用将按后进先出顺序执行。

代码语言:javascript复制
package main

import "fmt"

func main() {
	defer fmt.Println("world")
	fmt.Println("hello")
}

结果:

代码语言:javascript复制
hello
world

go中关键字

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 中不同类型的项之间的赋值需要显式转换。比如

代码语言:javascript复制
var i int = 42
var f float64 = float64(i)
var u uint = uint(f)

或者

i := 42
f := float64(i)
u := uint(f)

类型推断

当声明一个变量而不指定显式类型 (通过使用 := syntax 或 var = 表达式语法), 变量的类型是从右侧的值推断出来的.

当右值声明了类型时,新变量的类型与其相同:

代码语言:javascript复制
var i int
j := i // j is an int

但是,当右侧包含无类型的数字常量, 新的变量可以是 int, float64, 或 complex128 取决于常量的精度:

代码语言:javascript复制
i := 42           // int
f := 3.142        // float64
g := 0.867   0.5i // complex128

Go函数

一个函数可以接受零个或多个参数.

在本例中,add 接受两个 int 类型的参数.

请注意,类型在变量名 之后 .

代码语言:javascript复制
package main

import "fmt"

func add(x int, y int) int {
	return x   y
}

func main() {
	fmt.Println(add(42, 13))
}

当两个或多个连续命名函数参数共享一个类型时,除了最后一个之外,可以省略所有类型.

在这个例子中,我们缩短

代码语言:javascript复制
x int, y int
可以写成
x, y int

Multiple results(多值返回)

一个函数可以返回任意数量的结果。该 swap 函数返回两个字符串。

代码语言:javascript复制
package main

import "fmt"

func swap(x, y string) (string, string) {
	return y, x
}

func main() {
	a, b := swap("hello", "world")
	fmt.Println(a, b)
}

Named return values(命名的返回值)

Go 的返回值可以命名。如果是这样,它们将被视为在函数顶部定义的变量。

这些名称应用于记录返回值的含义。

不带参数的 return 语句返回命名的返回值,这被称为“裸”返回。

裸返回语句应该只在短函数中使用,就像这里显示的例子一样。它们会损害较长函数的可读性。

代码语言:javascript复制
package main

import "fmt"

func split(sum int) (x, y int) {
	x = sum * 4 / 9
	y = sum - x
	return
}

func main() {
	fmt.Println(split(17))
}

函数值

函数也是值。它们可以像其他值一样传递,函数值可以用作函数参数和返回值

代码语言:javascript复制
package main

import (
	"fmt"
	"math"
)

func compute(fn func(float64, float64) float64) float64 {
	return fn(3, 4)
}

func main() {
	hypot := func(x, y float64) float64 {
		return math.Sqrt(x*x   y*y)
	}
	fmt.Println(hypot(5, 12))

	fmt.Println(compute(hypot))
	fmt.Println(compute(math.Pow))
}

函数闭包

Go 函数可以是闭包。闭包是引用其主体外部的变量的函数值。该函数可以访问并分配给引用的变量;从这个意义上说,函数是“绑定”到变量的.

例如, adder 函数返回一个闭包。每个闭包都绑定到它自己的 sum 变量.

Go语言nil:空值/零值

在Go语言中,布尔类型的零值(初始值)为 false,数值类型的零值为 0,字符串类型的零值为空字符串"",而指针、切片、映射、通道、函数和接口的零值则是 nil。

nil 是Go语言中一个预定义好的标识符,有过其他编程语言开发经验的开发者也许会把 nil 看作其他语言中的 null(NULL),其实这并不是完全正确的,因为Go语言中的 nil 和其他语言中的 null 有很多不同点。

nil 标识符是不能比较的

这点和 python 等动态语言是不同的,在 python 中,两个 None 值永远相等。对于 nil 来说是一种未定义的操作。

nil 不是关键字或保留字

nil 并不是Go语言的关键字或者保留字,也就是说我们可以定义一个名称为 nil 的变量,比如下面这样:var nil = errors.New("my god")

虽然上面的声明语句可以通过编译,但是并不提倡这么做。

nil 没有默认类型

代码语言:javascript复制
package main
import (
    "fmt"
)
func main() {
    fmt.Printf("%T", nil)
    print(nil)
}

运行结果如下所示:

代码语言:javascript复制
.main.go:9:10: use of untyped nil

不同类型 nil 的指针是一样的

代码语言:javascript复制
package main
import (
    "fmt"
)
func main() {
    var arr []int
    var num *int
    fmt.Printf("%pn", arr)
    fmt.Printf("%p", num)
}

结果:
0x0
0x0

通过运行结果可以看出 arr 和 num 的指针都是 0x0。

nil 是 map、slice、pointer、channel、func、interface 的零值

零值是Go语言中变量在声明之后但是未初始化被赋予的该类型的一个默认值。

不同类型的 nil 值占用的内存大小可能是不一样的

一个类型的所有的值的内存布局都是一样的,nil 也不例外,nil 的大小与同类型中的非 nil 类型的大小是一样的。但是不同类型的 nil 值的大小可能不同。

具体的大小取决于编译器和架构,上面打印的结果是在 64 位架构和标准编译器下完成的,对应 32 位的架构的,打印的大小将减半。

0 人点赞