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


包package

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

程序从 main包开始运行.

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

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

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

package main

import (
	"fmt"
	"math/rand"
)

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

Imports(导入)

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

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

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 声明变量比较烦琐?没关系,还有一种为懒人提供的定义变量的方法:

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

匿名变量

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

func GetData() (int, int) {
    return 100, 200
}
func main(){
    a, _ := GetData()
    _, b := GetData()
    fmt.Println(a, b)
}

常量

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

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

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

const (
    e  = 2.7182818
    pi = 3.1415926
)

iota 常量生成器

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

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 语句的三个组件周围没有括号,并且大括号 { } 始终需要。

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

package main

import "fmt"

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

if语句

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

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 之内。

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 块中使用。

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 >= %g\n", 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 不需要是常量,所涉及的值也不需要是整数。

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

switch i {
case 0:
case f():
}

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

没有条件的switch

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

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 语句推迟函数的执行,直到周围的函数返回。

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

package main

import "fmt"

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

	fmt.Println("hello")
}

Stacking defers(defer栈)

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

package main

import "fmt"

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

结果:

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

var i int = 42
var f float64 = float64(i)
var u uint = uint(f)

或者

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

类型推断

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

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

var i int
j := i // j is an int

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

i := 42           // int
f := 3.142        // float64
g := 0.867 + 0.5i // complex128

Go函数

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

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

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

package main

import "fmt"

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

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

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

在这个例子中,我们缩短

x int, y int
可以写成
x, y int

Multiple results(多值返回)

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

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 语句返回命名的返回值,这被称为“裸”返回。

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

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))
}

函数值

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

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 没有默认类型

package main
import (
    "fmt"
)
func main() {
    fmt.Printf("%T", nil)
    print(nil)
}

运行结果如下所示:

.\main.go:9:10: use of untyped nil

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

package main
import (
    "fmt"
)
func main() {
    var arr []int
    var num *int
    fmt.Printf("%p\n", 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 位的架构的,打印的大小将减半。

   登录后才可以发表呦...

专注分享Java技术干货,包括
但不仅限于多线程、JVM、Spring Boot
Spring Cloud、 Redis、微服务、
消息队列、Git、面试题 最新动态等。

想交个朋友吗
那就快扫下面吧


微信

Java技术债务

你还可以关注我的公众号

会分享一些干货或者好文章

Java技术债务