01. 基本类型和声明

1. 内置类型

go和其他很多语言一样,有多个内置类型:布尔型、整型、浮点型和字符串类型。

1.1 零值

像大多数现代语言一样,将声明但没有赋值的变量默认赋值为0。

1.2 字面值

在go中,字面值指的是一个数字、字符或字符串。在Go程序中有四种常见的字面量(在讨论复数时,我们将讨论罕见的第五种字面值)

整数字面值是数字序列;它们通常以10为基数,但不同的前缀用于表示其他基数:0b表示二进制(以2为基数),000表示八进制(以8为基数),0x表示十六进制(以16为基数)。您可以使用大写或小写字母作为前缀。前导0后面没有字母是表示八进制文字的另一种方式(不要使用这种方式)。
为了更容易地读取较长的整数字面值,Go允许您在字面值的中间放置下划线。例如,这允许您以10为基数(1_234)按千位分组。这些下划线对数字的值没有影响。下划线的唯一限制是它们不能出现在数字的开头或结尾,并且它们不能相邻出现。甚至可以在文本中的每个数字(1_2_3_4)之间加上下划线,但不能这样做。

浮点数字面值有小数点来表示值的小数部分。它们还可以具有用字母e和正数或负数指定的指数(例如6.03e23)。还可以选择使用0x前缀和字母p来表示任何指数,从而将它们写成十六进制。与整数字面值一样,可以使用下划线来格式化浮点字面值。

rune 字面量表示字符,并由单引号括起。不像许多其他语言,在Go中单引号和双引号是不能互换的。Rune字面量可以写成单个Unicode字符('a'), 8位八进制数('\141'),8位十六进制数('\x61'), 16位十六进制数('\u0061')或32位Unicode数字('\U00000061')。还有一些反斜杠转义的rune字面量,最有用的是换行符('\n'),制表符('\t'),单引号('"),双引号('"')和反斜杠('\')。

有两种不同的方式来表示字符串字面值。大多数情况下,应该使用双引号来创建一个字符串字面值(例如,键入"hello")。它们以任何允许的形式包含零个或多个rune字面量。唯一不能出现的字符是未转义的反斜杠、未转义的换行和未转义的双引号。

如果需要在字符串中包含反斜杠、双引号或换行符,请使用原始字符串字面量。它们用反引号(')分隔,可以包含除反引号以外的任何文字字符。

1.3 布尔型(Booleans)

bool类型表示布尔变量。bool类型的变量可以是两个值之一:true或false。bool值的零值为false:

var flag bool // no value assigned, set to false
var isAwesome = true

1.4 数值类型(Numeric Types)

1.4.1 整型

Go提供了各种大小的有符号和无符号整数,从1字节到4字节不等。

1.4.1.1 The special integer types

Go确有一些整数类型的特殊名称。字节是uint8的别名;在字节和uint8类型之间赋值、比较或执行数学运算是合法的。然而,很少看到在Go代码中使用uint8。

1.4.1.2 Choosing which integer to use

Go提供了比其他语言更多的整数类型。考虑到所有这些选择,什么时候应该使用它们。有三条规则可以遵循:

  1. 如果您正在使用具有特定大小或符号的整数的二进制文件格式或网络协议,请使用相应的整数类型。
  2. 如果您正在编写一个可以使用任何整数类型的库函数,请编写一对函数,一个用int64作为参数和变量,另一个用uint64作为参数和变量。

int64和uint64之所以是这种情况下惯用的选择,是因为Go没有泛型(还没有),也没有函数重载。如果没有这些特性,就需要编写许多名称稍有不同的函数来实现算法。使用int64和uint64意味着只需编写一次代码,就可以让调用者使用类型转换来传递值并转换返回的数据。
3. 在所有其他情况下,只使用int。

1.4.1.3 整数运算符

Go支持常用的算术运算符:+,-,*,/,模为%。整数除法的结果是整数;如果希望获得浮点数结果,则需要使用类型转换将整数转换为浮点数。另外,要注意不要将整数除以0。

可以将任意算术运算符与=组合起来修改变量:+=、-=、*=、/=和%=。例如,下面的代码导致x的值为20:

var x int = 10
x *= 2

整数比较: ==, !=, >, >=, <, 和<=。
Go也有整数的位操作符。您可以使用<<和>>向左和向右进行位移位,或者使用&(逻辑与),|(逻辑或),(逻辑异或)和&(逻辑与非)进行位掩码(bit mask)。就像算术运算符一样,您也可以使用=组合所有逻辑运算符来修改变量:&=、|=、=、&=、<<=和>>=。

1.4.2 浮点型

在Go中有两种浮点类型,

选择使用哪种浮点类型很简单:除非必须与现有格式兼容,否则使用float64。浮点字面值的默认类型为float64,因此始终使用float64是最简单的选择。它还有助于缓解浮点精度问题,因为float32只有六位或七位小数的精度。

更大的问题是是否应该使用浮点数。在大多数情况下,答案是否定的。就像其他语言一样,Go的浮点数有很大的范围,但它们不能存储该范围内的每个值;它们存储最接近的近似值。因为浮点数不是精确的,所以它们只能在可以接受不精确值或很好地理解浮点规则的情况下使用。这将它们限制在图形和科学操作等领域。

除了%之外,可以对浮点数使用所有标准的数学和比较操作符。非零浮点变量除以0返回+Inf -Inf(正无穷大或负无穷大),具体取决于数字的符号。将设置为0的浮点变量除以0将返回NaN (Not a Number)。

虽然Go允许使用==和!=来比较浮点数,但不要这样做。由于浮点数的不精确性质,当认为两个浮点值应该相等时,它们可能不相等。相反,定义允许的最大方差,并查看两个浮点数之间的差异是否小于该值。这个值(有时称为epsilon)取决于对精确度的需求、

1.4.3 复数类型(Complex types)

Go定义了两种复数类型。Complex64使用float32值表示实部和虚部,complex128使用float64值表示实部和虚部。两者都是用复杂的内置函数声明的。Go使用一些规则来确定函数输出的类型:

  1. 如果对两个函数参数都使用无类型常量或字面量,那么将创建一个 untyped complex literal,其默认类型为complex128。
  2. 如果传递给complex的两个值都是float32类型,那么将创建一个complex64类型的复数。
  3. 如果一个值是float32,而是literal that can fit within a float32 或 untyped constant,那么将创建一个complex64。
  4. 否则,将创建一个complex128类型的复数。

所有的标准算术运算符都适用于复数。就像浮点数一样,可以使用==或!=来比较它们,但它们具有相同的精度限制,因此最好使用epsilon。可以分别使用real和image内置函数提取复数的实部和虚部。math/ complex包中还有一些用于操作complex128值的附加函数。

这两种类型的复数的零值都将0赋给该数的实部和虚部。

func main() {
 x := complex(2.5, 3.1)
 y := complex(10.2, 2)
 fmt.Println(x + y)
 fmt.Println(x - y)
 fmt.Println(x * y)
 fmt.Println(x / y)
 fmt.Println(real(x))
 fmt.Println(imag(x))
 fmt.Println(cmplx.Abs(x))
 }

对于第五种字面量,Go支持 imaginary literals来表示复数的虚数部分。它们看起来就像浮点数,但是有一个i作为后缀。

1.5 A Taste of Strings and Runes

像大多数现代语言一样,Go包含字符串作为内置类型。字符串的零值是空字符串。Go支持Unicode;可以将任何Unicode字符放入字符串中。与整数和浮点数一样,使用==比较字符串是否相等,使用!=比较差异,或者使用>、>=、<或<=排序。它们通过+操作符连接起来

Go中的字符串是不可变的;可以重新分配字符串变量的值,但不能更改分配给它的字符串的值。
Go还有一个表示单个代码点(single code point)的类型。rune类型是int32类型的别名,就像byte是uint8类型的别名一样。rune字面量的默认类型是rune,而字符串字面量的默认类型是字符串。

1.6 显式类型转换

大多数具有多种数字类型的语言都会在需要时自动从一种转换为另一种。这被称为自动类型提升,虽然它看起来非常方便,但事实证明,正确地将一种类型转换为另一种类型的规则可能会变得复杂并产生意想不到的结果。作为一种重视意图清晰度和可读性的语言,Go不允许变量之间的自动类型提升。当变量类型不匹配时,必须使用类型转换。即使大小不同的整数和浮点数也必须转换为相同的类型才能相互作用。因此无需记住任何类型转换规则就可以清楚地知道想要的类型。

var x int = 10
var y float64 = 30.2
var z float64 = float64(x) + y
var d int = x + int(y)

对于z,使用float64类型转换将x转换为float64类型,对于d,使用int类型转换将y转换为int类型。打印出40.2 40。

这种对类型的严格限制还有其他含义。由于Go中的所有类型转换都是显式的,因此不能将其他Go类型视为布尔类型。在许多语言中,非零数字或非空字符串可以解释为布尔值true。就像自动类型提升一样,“真值”的规则因语言而异,可能令人困惑。Go doesn’t allow truthiness。实际上,没有其他类型可以隐式或显式地转换为bool类型。如果要从另一种数据类型转换为布尔类型,则必须使用比较操作符之一(==、!=、>、<、<=或>=)。例如,要检查变量x是否等于0,代码将是x == 0。如果要检查字符串s是否为空,请使用s == ""。

2. var 和 :=

Go有很多声明变量的方法。每种声明风格都传达了如何使用变量的信息。在Go中声明变量的最详细的方法是使用var关键字、显式类型和赋值。它是这样的:

var x int = 10

如果=右侧的类型是变量的预期类型,则可以省略=左侧的类型。由于整型文字的默认类型是int,下面的语句将x声明为int类型的变量:

var x = 10

相反,如果你想声明一个变量并为其赋零值,你可以保留该类型并在右侧删除=:

var x int

可以用var一次声明多个变量,它们可以是相同的类型:

var x, y int = 10, 20

相同类型的所有零值:

var x, y int

或不同类型的:

var x, y = 10, "hello"

还有一种使用var的方法,如果一次声明多个变量,可以把它们包装在一个声明列表中:

var(
    x  int
    y      = 20
    z  int = 30
    d,e    = 15, "hello"
    f, g string
)

Go还支持一种简短的声明格式。当在函数内时,可以使用:=操作符替换使用类型推断的var声明。下面两个语句做了完全相同的事情:它们将x声明为值为10的int型:

var x = 10
x := 10

与var一样,可以使用:=一次声明多个变量。这两行都将10赋给x,将" hello "赋给y:

var x, y = 10, "hello"
x, y := 10, "hello"

:=操作符可以实现var无法实现的一个技巧:它允许为现有变量赋值。只要在:=的左边有一个新变量,那么任何其他变量都可以已经存在:

x := 10
x, y := 30, "hello"

:=有一个限制。如果在包级别声明变量,则必须使用var,因为:=在函数之外是不合法的。
在某些情况下,你应该避免使用:= :

  1. 当将变量初始化为零值时,使用var x int。这清楚地表明,零值是有意为之。
  2. 当将无类型常量或字面值赋值给变量,并且常量或字面值的默认类型不是想要的变量类型时,请使用带有指定类型的长var形式。虽然使用类型转换指定值的类型并使用:=写入x:= byte(20)是合法的,但习惯做法是写入var x byte = 20。
  3. 因为:=允许对新变量和现有变量进行赋值,所以当认为正在重用现有变量时,它有时会创建新变量。在这种情况下,用var显式声明所有新变量,以明确哪些变量是新变量,然后使用赋值操作符(=)为新变量和旧变量赋值。

虽然var和:=允许在同一行声明多个变量,但只有在分配从函数返回的多个值或逗号ok习惯用法时才使用这种风格。

应该很少在函数之外声明变量,在所谓的包块中。值改变的包级变量是个坏主意。当您在函数之外有一个变量时,可能很难跟踪对它所做的更改,这使得很难理解数据如何流经程序。这可能会导致一些微妙的bug。作为一般规则,您应该只在包块中声明有效不可变的变量。

3. 使用const

许多语言都有一种方法来声明一个值是不可变的。在Go中,这是通过const关键字完成的。

const x = 10

const (
	idKey   = "id"
	nameKey = "name"
)

const z = 20 * 10

func main() {
	const y = "hello"

	fmt.Println(x)
	fmt.Println(y)

	x = x + 1
	y = "bye"

	fmt.Println(x)
	fmt.Println(y)
}

直接运行上述代码会出现错误。

如上所述,在包级别或在函数中声明常量。就像var一样,可以(也应该)在一组括号中声明一组相关的常量。

然而,Go中的const非常受限。Go中的常量是一种给字面量命名的方法。它们只能保存编译器在编译时可以计算出的值。这意味着它们可以被分配:

  1. 数字字面值
  2. true 或 false
  3. 字符串
  4. Runes
  5. 内置函数complex, real, image, len和cap
  6. 由运算符和上述值组成的表达式

Go中的常量是一种给字面量命名的方法。在Go中没有办法声明一个变量是不可变的。

4. Typed and Untyped Constants

常量可以是类型化的或非类型化的。无类型常量的工作原理与字面量完全相同;它没有自己的类型,但有一个默认类型,当无法推断其他类型时使用。类型常量只能直接赋值给该类型的变量。

是否使常量具有类型取决于声明常量的原因。如果要给一个可以与多个数字类型一起使用的数学常量命名,那么请保持该常量为非类型。一般来说,保留未类型化的常量可以提供更大的灵活性。在某些情况下,需要一个常量来强制执行类型。
下面是一个无类型的常量声明:

const x = 10

以下所有都是合法的:

var y int = x
var z float64 = x
var d byte = x

类型化常量声明是这样的:

const typedX int = 10

这个常量只能直接赋值给int类型。将其赋值给任何其他类型都会产生编译时错误

5. Unused Variables

Go的目标之一是使大型团队更容易在程序上进行协作。为此,Go有一些在编程语言中独一无二的规则。Go程序需要使用Go fmt以特定的方式格式化,以便更容易编写代码操作工具并提供编码标准。Go的另一个要求是必须读取每个声明的局部变量。声明局部变量而不读取其值是编译时错误。
编译器的未使用变量检查不是详尽的。只要变量被读取一次,编译器就不会报错,即使对变量的写操作从未被读取。

func main() {
	x := 10
	x = 20
	fmt.Println(x)
	x = 30
}

Go编译器允许使用const创建Unused Variables。这是因为Go中的常量是在编译时计算的,不会有任何副作用。这使得它们很容易消除:如果没有使用常量,它就不会包含在编译的二进制文件中。

6. Naming Variables and Constants

Go的变量命名规则与Go开发人员在命名变量和常量时遵循的模式之间存在差异。像大多数语言一样,Go要求标识符名称以字母或下划线开头,并且名称可以包含数字、下划线和字母。Go对“字母”和“数字”的定义比许多语言都要宽泛。任何被认为是字母或数字的Unicode字符都是允许的。这使得示例中显示的所有变量定义都完全有效。

虽然这段代码可以工作,但不要这样命名变量。

尽管下划线在变量名中是一个有效的字符,但它很少被使用,因为习惯的Go不使用蛇形大小写(像index_counter或number_tries这样的名称)。相反,当标识符名称由多个单词组成时,Go使用驼峰式命名法(像indexCounter或numberTries这样的名称)。

在许多语言中,常量总是全部用大写字母书写,单词之间用下划线分隔(例如INDEX_COUNTER或NUMBER_TRIES)。go不遵循这种模式。这是因为Go使用包级声明名称中第一个字母的大小写来确定该项是否可以在包外部访问。

在函数中,建议使用短变量名。变量的作用域越小,它所使用的名称就越短。在Go语言中,单字母变量名是很常见的。例如,名称k和v(键和值的缩写)用作For -range循环中的变量名。如果使用标准的for循环,则i和j是索引变量的常用名称。还有其他习惯用法来命名常见类型的变量

一些具有较弱类型系统的语言鼓励开发人员在变量名中包含变量的预期类型。由于Go是强类型的,不需要这样做来跟踪底层类型。但是,仍然有关于变量类型和单字母名称的约定。将使用类型的第一个字母作为变量名(例如,i表示整数,f表示浮点数,b表示布尔值)。当定义自己的类型时,应该使用类似的模式。

posted @ 2024-09-14 16:33  yyyyyllll  阅读(52)  评论(0)    收藏  举报