Mas0n
to be reverse engineer🐧
翻车鱼

Go学习笔记(1)

概述

https://cdn.shi1011.cn/2021/04/d9387cf85af67c7de2fe3b5469f89460.png?imageMogr2/format/webp/interlace/0/quality/90|watermark/2/text/wqlNYXMwbg/font/bXN5aGJkLnR0Zg/fontsize/14/fill/IzMzMzMzMw/dissolve/80/gravity/southeast/dx/5/dy/5

基于学习效率的考虑,我打算不再详细进行解释,所有举例以及笔记都将默认阅读者有一定的编程语言基础。因此,我不保证按基本流程书写和内容完整。

更多的,我将记录的是与其他编程语言的一些异同点

参考

变量

声明

在Go中,变量的定义有下列几种方式

var a string = "G" // 显式指定变量类型并赋值
var b = "G" // 自动推断类型 类似于Python,Ruby,JavaScript这类动态语言
c := "O" // 简短声明 一般用于声明局部变量

显而易见,var a 这种语法是不正确的,因为编译器并没有任何可以用于自动判断的依据。

变量的类型也可以在运行时实现自动推断,例如:

var (
	home = os.Getenv("HOME")
	user = os.Getenv("USER")
	goRoot = os.Getenv("GOROOT")
)

这种因式分解关键字的写法一般用于声明全局变量

Q:Go在声明变量时将变量的类型放在变量的名称之后,为什么?

A:首先,它是为了避免像C语言中那样含糊不清的声明形式,例如:int* a, b; 在这个例子中,只有 a 是指针而 b 不是。如果你想要这两个变量都是指针,则需要将它们分开书写。而在Go中则可以这样声明:var a, b *int

其次,这种语法能够按照从左至右的顺序阅读,使得代码更加容易理解。

变量作用域

尽管在Go中变量的标识符同样要求唯一,但你可以在某个代码块的内层代码块中使用相同名称的变量,则此时外部的同名变量将会暂时隐藏(结束内部代码块的执行后隐藏的外部同名变量又会出现,而内部同名变量则被释放),你任何的操作都只会影响内部代码块的局部变量。

那么,练习是必须的:推断以下程序的输出,并解释你的答案,然后编译并执行它们

练习1 local_scope.go

package main

var a = "G"

func main() {
   n()
   m()
   n()
}

func n() { print(a) }

func m() {
   a := "O"
   print(a)
}

练习2 global_scope.go

package main

var a = "G"

func main() {
   n()
   m()
   n()
}

func n() { print(a) }

func m() {
   a := "O"
   print(a)
}

练习3 function_calls_function.go

package main

var a string

func main() {
   a = "G"
   print(a)
   f1()
}

func f1() {
   a := "O"
   print(a)
   f2()
}

func f2() {
   print(a)
}

简短形式,:= 赋值操作符

这是使用变量的首选形式,但是它只能被用在函数体内,而不可以用于全局变量的声明与赋值。使用操作符 := 可以高效地创建一个新的变量,称之为初始化声明。

注意事项

如果在相同的代码块中,我们不可以再次对于相同名称的变量使用初始化声明,例如:a := 20 就是不被允许的,编译器会提示错误 no new variables on left side of :=,但是 a = 20 是可以的,因为这是给相同的变量赋予一个新的值。

如果你在定义变量 a 之前使用它,则会得到编译错误 undefined: a

如果你声明了一个局部变量却没有在相同的代码块中使用它,同样会得到编译错误,例如下面这个例子当中的变量 a:

func main() {
   var a string = "abc"
   fmt.Println("hello, world")
}

尝试编译这段代码将得到错误 a declared and not used

此外,单纯地给 a 赋值也是不够的,这个值必须被使用,所以使用 fmt.Println("hello, world", a) 会移除错误。

但是全局变量是允许声明但不使用。

其他的简短形式:

同一类型的多个变量可以声明在同一行,如:

var a, b, c int

(这是将类型写在标识符后面的一个重要原因)

多变量可以在同一行进行赋值(像极了Python),如:

a, b, c = 5, 7, "abc"

这被称为 并行 或 同时 赋值。

考虑一下,在C中我们如何交换变量?那么再看看Go如何交换: a, b = b, a

空白标识符 __ 实际上是一个只写变量,你不能得到它的值。这样做是因为 Go 语言中你必须使用所有被声明的变量,但有时你并不需要使用从一个函数得到的所有返回值,如值 5 在:_, b = 5, 7 中被抛弃。

init 函数

类比Python中Class的__init__函数,在Go中init函数先于main函数运行,可被用作初始化变量或是程序运行的校验

基本类型和运算符

布尔类型 bool

与C类似,不提要

数字类型

(与Python有一些共通之处)

Go 语言支持整型和浮点型数字,并且原生支持复数,其中位的运算采用补码

Go 也有基于架构的类型,例如:int、uint 和 uintptr

  • int 和 uint 在 32 位操作系统上,它们均使用 32 位(4 个字节),在 64 位操作系统上,它们均使用 64 位(8 个字节)
  • uintptr 的长度被设定为足够存放一个指针即可

Go 语言中没有 float 类型,(Go 语言中只有 float32 和 float64)没有 double 类型

一些注意点

int 型是计算最快的一种类型

整型的零值为 0,浮点型的零值为 0.0

float32 精确到小数点后 7 位,float64 精确到小数点后 15 位。由于精确度的缘故,你在使用 == 或者 != 来比较浮点数时应当非常小心。你最好在正式使用前测试对于精确度要求较高的运算

你应该尽可能地使用 float64,因为 math 包中所有有关数学运算的函数都会要求接收这个类型

你可以通过增加前缀 0 来表示 8 进制数(如:077),增加前缀 0x 来表示 16 进制数(如:0xFF),以及使用 e 来表示 10 的连乘(如: 1e3 = 1000,或者 6.022e23 = 6.022 x 1e23)

你可以使用 a := uint64(0) 来同时完成类型转换和赋值操作,这样 a 的类型就是 uint64

Go 中不允许不同类型之间的混合使用,但是对于常量的类型限制非常少,因此允许常量之间的混合使用,下面这个程序很好地解释了这个现象(该程序无法通过编译):

package main

func main() {
	var a int
	var b int32
	a = 15
	b = a + a	 // 编译错误
	b = b + 5    // 因为 5 是常量,所以可以通过编译
}

如果你尝试编译该程序,则将得到编译错误 cannot use a + a (type int) as type int32 in assignment

同样地,int16 也不能够被隐式转换为 int32。

下面这个程序展示了通过显式转换来避免这个问题

package main

import "fmt"

func main() {
	var n int16 = 34
	var m int32
	// compiler error: cannot use n (type int16) as type int32 in assignment
	//m = n
	m = int32(n)

	fmt.Printf("32 bit int is: %d\n", m)
	fmt.Printf("16 bit int is: %d\n", n)
}

输出:

32 bit int is: 34
16 bit int is: 34

复数

Go 拥有以下复数类型:

complex64 (32 位实数和虚数)
complex128 (64 位实数和虚数)

复数使用 re+imI 来表示,其中 re 代表实数部分,im 代表虚数部分,I 代表根号负 1

示例:

var c1 complex64 = 5 + 10i
fmt.Printf("The value is: %v", c1)
// 输出: 5 + 10i

如果 re 和 im 的类型均为 float32,那么类型为 complex64 的复数 c 可以通过以下方式来获得:

c = complex(re, im)

函数 real(c) 和 imag(c) 可以分别获得相应的实数和虚数部分。

在使用格式化说明符时,可以使用 %v 来表示复数,但当你希望只表示其中的一个部分的时候需要使用 %f

复数支持和其它数字类型一样的运算。当你使用等号 == 或者不等号 != 对复数进行比较运算时,注意对精确度的把握。cmath 包中包含了一些操作复数的公共方法。如果你对内存的要求不是特别高,最好使用 complex128 作为计算类型,因为相关函数都使用这个类型的参数。

算术运算符

带有 ++ 和 --的只能作为语句,而非表达式,因此 n = i++ 这种写法是无效的,其它像 f(i++) 或者 a[i]=b[i++] 这些可以用于 C、C++ 和 Java 中的写法在 Go 中也是不允许的。

浮点数除以 0.0 会返回一个无穷尽的结果,使用 +Inf 表示。

运算时 溢出 不会产生错误,Go 会简单地将超出位数抛弃。如果你需要范围无限大的整数或者有理数(意味着只被限制于计算机内存),你可以使用标准库中的 big 包,该包提供了类似 big.Int 和 big.Rat 这样的类型

类型别名

给某个类型起另一个名字(用于简化名称或解决名称冲突)

在 type TZ int 中,TZ 就是 int 类型的新名称(这里用于表示程序中的时区),然后就可以使用 TZ 来操作 int 类型的数据

实际上,类型别名得到的新类型并非和原类型完全相同,新类型不会拥有原类型所附带的方法(后续提要);TZ 可以自定义一个方法用来输出更加人性化的时区信息。

字符类型

除类似C语言char操作语法,Go 同样支持 Unicode(UTF-8),因此字符同样称为 Unicode 代码点或者 runes,并在内存中使用 int 来表示。在文档中,一般使用格式 U+hhhh 来表示,其中 h 表示一个 16 进制数。其实 rune 也是 Go 当中的一个类型,并且是 int32 的别名。

在书写 Unicode 字符时,需要在 16 进制数之前加上前缀 \u 或者 \U

因为 Unicode 至少占用 2 个字节,所以我们使用 int16 或者 int 类型来表示。如果需要使用到 4 字节,则会加上 \U 前缀;前缀 \u 则总是紧跟着长度为 4 的 16 进制数,前缀 \U 紧跟着长度为 8 的 16 进制数。

var ch int = '\u0041'
var ch2 int = '\u03B2'
var ch3 int = '\U00101234'
fmt.Printf("%d - %d - %d\n", ch, ch2, ch3) // integer
fmt.Printf("%c - %c - %c\n", ch, ch2, ch3) // character
fmt.Printf("%X - %X - %X\n", ch, ch2, ch3) // UTF-8 bytes
fmt.Printf("%U - %U - %U", ch, ch2, ch3) // UTF-8 code point

包 unicode 包含了一些针对测试字符的非常有用的函数(其中 ch 代表字符):

  • 判断是否为字母:unicode.IsLetter(ch)
  • 判断是否为数字:unicode.IsDigit(ch)
  • 判断是否为空白符号:unicode.IsSpace(ch)

这些函数返回一个布尔值。包 utf8 拥有更多与 rune 类型相关的函数。

本文链接:https://blog.shi1011.cn/learn/1283
本文采用 CC BY-NC-SA 4.0 Unported 协议进行许可

Mas0n

文章作者

发表回复

textsms
account_circle
email

翻车鱼

Go学习笔记(1)
概述 基于学习效率的考虑,我打算不再详细进行解释,所有举例以及笔记都将默认阅读者有一定的编程语言基础。因此,我不保证按基本流程书写和内容完整。 更多的,我将记录的是与其…
扫描二维码继续阅读
2021-04-04