路漫漫其修远兮
吾将上下而求索

第4章总结:基本结构和基本数据类型

第4章
4.2
必须在源文件中非注释的第一行指明这个文件属于哪个包,如:package main

可以用一些较小的文件,并且在每个文件非注释的第一行都使用 package main 来指明这些文件都属于 main 包

Go 的标准库包含了大量的包(如:fmt 和 os),但是你也可以创建自己的包

如果对一个包进行更改或重新编译,所有引用了这个包的客户端程序都必须全部重新编译。

一个 Go 程序是通过 import 关键字将一组包链接在一起,import "fmt" 告诉 Go 编译器这个程序需要使用 fmt 包
(的函数,或其他元素),fmt 包实现了格式化 IO(输入/输出)的函数。包名被封闭在半角双引号 "" 中。

如果需要多个包,它们可以被分别导入:
import "fmt"
import "os"
或:
import "fmt"; import "os"
但是还有更短且更优雅的方法(被称为因式分解关键字,该方法同样适用于 const、var 和 type 的声明或定义):
import (
   "fmt"
   "os"
)

如果包名不是以 . 或 / 开头,如 "fmt" 或者 "container/list",则 Go 会在全局文件进行查找;如果包名以 ./ 开头,
则 Go 会在相对目录中查找;如果包名以 / 开头(在 Windows 下也可以这样使用),则会在系统的绝对路径中查找。

当标识符(包括常量、变量、类型、函数名、结构字段等等)以一个大写字母开头,如:Group1,那么使用这种形式的标识
符的对象就可以被外部包的代码所使用(客户端程序需要先导入这个包),这被称为导出(像面向对象语言中的 public);
标识符如果以小写字母开头,则对包外是不可见的,但是他们在"整个包的内部是可见并且可用的"(像面向对象语言中的 private )。

可以通过使用包的别名来解决包名之间的名称冲突,或者说根据你的个人喜好对包名进行重新设置,如:import fm "fmt",然后可以使用:fm.Printf函数

如果导入了一个包却没有使用它,则会在构建程序时引发错误,如 imported and not used: os,这正是遵循了 Go 的格言:“没有不必要的代码!“。需将未用的包含删除

你可以函数 func functionName() 的在括号 () 中写入 0 个或多个函数的参数(使用逗号 , 分隔),每个参数的名称后面必须紧跟着该参数的类型。

main 函数是每一个可执行程序所必须包含的,一般来说都是在启动后第一个执行的函数

函数里的代码(函数体)使用大括号 {} 括起来。左大括号 { 必须与方法的声明放在同一行,这是编译器的强制规定

如果你的函数非常简短,你也可以将它们放在同一行:func Sum(a, b int) int { return a + b }

因此符合规范的函数一般写成如下的形式:
func functionName(parameter_list) (return_value_list) {
   …
}
其中:
parameter_list 的形式为 (param1 type1, param2 type2, …)
return_value_list 的形式为 (ret1 type1, ret2 type2, …)

只有当某个函数需要被外部包调用的时候才使用大写字母开头,并遵循 Pascal 命名法;否则就遵循骆驼命名法,即第一个单词的首字母小写,其余单词的首字母大写。

程序正常退出的代码为 0 即 Program exited with code 0;如果程序因为异常而被终止,则会返回非零值,如:1。这个数值可以用来测试是否成功执行一个程序。

单行注释是最常见的注释形式,你可以在任何地方使用以 // 开头的单行注释。多行注释也叫块注释,均已以 /* 开头,并以 */ 结尾,
且不可以嵌套使用,多行注释一般用于包的文档描述或注释成块的代码片段。

一个函数可以拥有多返回值,返回类型之间需要使用逗号分割,并使用小括号 () 将它们括起来,如:func FunctionName (a typea, b typeb) (t1 type1, t2 type2)

使用 type 关键字可以定义你自己的类型,你可能想要定义一个结构体(第 10 章),但是也可以定义一个已经存在的类型的别名,如:
type IZ int
这里并不是真正意义上的别名,因为使用这种方法定义之后的类型可以拥有更多的特性,且在类型转换时必须显式转换。

如果你有多个类型需要定义,可以使用因式分解关键字的方式,例如:
type (
   IZ int
   FZ float64
   STR string
)

编译器不关心 main 函数在前还是变量的声明在前,但使用统一的结构能够在从上至下阅读 Go 代码时有更好的体验。

在必要以及可行的情况下,一个类型的值可以被转换成另一种类型的值。由于 Go 语言不存在隐式类型转换,因此所有的转换都必须显式说明,就像调用一个函数一样
类型 B 的值 = 类型 B(类型 A 的值)
示例:
a := 5.0
b := int(a)

Go 语言中对象的命名也应该是简洁且有意义的。就一直使用下面的方式进行命名:有必须要的话可以使用大小写混合的方式,如 MixedCaps 
或 mixedCaps,而不是使用下划线来分割多个名称。

4.3
存储在常量中的数据类型只可以是布尔型、数字型(整数型、浮点型和复数)和字符串型。
常量之所以为常量就是恒定不变的量,因此我们无法在程序运行过程中修改它的值;如果你在代码中试图修改常量的值则会引发编译错误。
常量的定义格式:const identifier [type] = value,例如:
const Pi = 3.14159

数字型的常量是没有大小和符号的,并且可以使用任何精度而不会导致溢出:反斜杠 \ 可以在常量表达式中作为多行的连接符使用。
const Ln2= 0.693147180559945309417232121458\
			176568075500134360255254120680009

常量也允许使用并行赋值的形式:
const beef, two, c = "eat", 2, "veg"
const Monday, Tuesday, Wednesday, Thursday, Friday, Saturday = 1, 2, 3, 4, 5, 6
const (
	Monday, Tuesday, Wednesday = 1, 2, 3
	Thursday, Friday, Saturday = 4, 5, 6
)
常量还可以用作枚举:
const (
	Unknown = 0
	Female = 1
	Male = 2
)

iota 也可以用在表达式中,如:iota + 50。在每遇到一个新的常量块或单个常量声明时, iota 都会重置为 0( 简单地讲,每遇到一次 const 关键字,iota 
就重置为 0 )。
const (
	a = iota
	b = iota
	c = iota
)
第一个 iota 等于 0,每当 iota 在新的一行被使用时,它的值都会自动加 1;所以 a=0, b=1, c=2 可以简写为如下形式:
const (
	a = iota
	b
	c
)
引用 time 包中的一段代码作为示例:一周中每天的名称。
const (
	Sunday = iota
	Monday
	Tuesday
	Wednesday
	Thursday
	Friday
	Saturday
)

4.4
声明变量的一般形式是使用 var 关键字:var identifier type。

示例:
var a int
var b bool
var str string
也可以改写成这种形式:
var (
	a int
	b bool
	str string
)

当一个变量被声明之后,系统自动赋予它该类型的零值:int 为 0,float 为 0.0,bool 为 false,string 为空字符串,指针为 nil

变量的命名规则遵循骆驼命名法,即首个单词小写,每个新单词的首字母大写,例如:numShips 和 startDate。

一个变量(常量、类型或函数)在程序中都有一定的作用范围,称之为作用域。如果一个变量在函数体外声明,则被认为是全局变量,可以在整个
包甚至外部包(被导出后)使用,不管你声明在哪个源文件里或在哪个源文件里调用该变量。

在函数体内声明的变量称之为局部变量,它们的作用域只在函数体内,参数和返回值变量也是局部变量。

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

声明与赋值(初始化)语句也可以组合起来。
var identifier [type] = value
var a int = 15
var i = 5
var b bool = false
var str string = "Go says hello to the world!"

 Go 编译器到可以根据变量的值来自动推断其类型
var a = 15
var b = false
var str = "Go says hello to the world!"
或:
var (
	a = 15
	b = false
	str = "Go says hello to the world!"
	numShips = 50
	city string
)


变量的初始化时省略变量的类型而由系统自动推断,最后一个声明语句写上 var 关键字就显得有些多余了,因此我们可以将它们简写为 a := 50 
或 b := false。a 和 b 的类型(int 和 bool)将由编译器自动推断。但是它只能被用在函数体内,而不可以用于全局变量的声明与赋值。

当使用等号 = 将一个变量的值赋值给另一个变量时,如:j = i,实际上是在内存中将 i 的值进行了拷贝:

可以通过 &i 来获取变量 i 的内存地址

局部变量:声明变量,但是未用,会报错。未声明,直接用,也会报错
全局变量是允许声明但不使用。

同一类型的多个变量可以声明在同一行,如:
var a, b, c int

多变量可以在同一行进行赋值,如:
a, b, c = 5, 7, "abc"
上面这行假设了变量 a,b 和 c 都已经被声明,否则的话应该这样使用:
a, b, c := 5, 7, "abc"

要交换两个变量的值,则可以简单地使用 a, b = b, a。(go中不用使用中间变量来交换)

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

并行赋值也被用于当一个函数返回多个返回值时,比如这里的 val 和错误 err 是通过调用 Func1 函数同时得到:val, err = Func1(var1)。

变量除了可以在全局声明中初始化,也可以在 init 函数中初始化。这是一类非常特殊的函数,它不能够被人为调用,而是在每个包完成初始化后
自动执行,并且执行优先级比 main 函数高。

每一个源文件都可以包含一个或多个 init 函数。初始化总是以单线程执行,并且按照包的依赖关系顺序执行。

4.5
一元运算符只可以用于一个值的操作,而二元运算符则可以和两个值或者操作数结合。

只有两个类型相同的值才可以和二元运算符结合,另外要注意的是,Go 是强类型语言,因此不会进行隐式转换,任何不同类型之间的转换都必须
显式说明。Go 不存在像 C 和 Java 那样的运算符重载,表达式的解析顺序是从左至右。

优先级越高的运算符在条件相同的情况下将被优先执行。但是你可以通过使用括号将其中的表达式括起来,以人为地提升某个表达式的运算优先级。

var b bool = true。布尔型的值只可以是常量 true 或者 false。

两个类型相同的值可以使用相等 == 或者不等 != 运算符来进行比较并获得一个布尔型的值。

当相等运算符两边的值是完全相同的值的时候会返回 true,否则返回 false,并且只有在两个的值的类型相同的情况下才可以使用。

 &&、或 || 与相等 == 或不等 != 属于二元运算符,而非 ! 属于一元运算符

在 Go 语言中,&& 和 || 是具有快捷性质的运算符,当运算符左边表达式的值已经能够决定整个表达式的值的时候(&& 左边的值为 false,|| 
左边的值为 true),运算符右边的表达式将不会被执行。利用这个性质,如果你有多个条件判断,应当将计算过程较为复杂的表达式放在运算符的右侧以减少不必要的运算。

对于布尔值的好的命名能够很好地提升代码的可读性,例如以 is 或者 Is 开头的 isSorted、isFinished、isVisible

int 和 uint 在 32 位操作系统上,它们均使用 32 位(4 个字节),在 64 位操作系统上,它们均使用 64 位(8 个字节)。

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

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

Printf函数说明
在格式化字符串里,%d 用于格式化整数(%x 和 %X 用于格式化 16 进制表示的数字),%g 用于格式化浮点型(%f 输出浮点数,%e 输出科学计数表
示法),%0d 用于规定输出定长的整数,其中开头的数字 0 是必须的。
%n.mg 用于表示数字 n 并精确到小数点后 m 位,除了使用 g 之外,还可以使用 e 或者 f,例如:使用格式化字符串 %5.2e 来输出 3.4 的结果为 3.40e+00。

位运算只能用于整数类型的变量,且需当它们拥有等长位模式时。
%b 是用于表示位的格式化标识符。

位左移常见实现存储单位的用例
使用位左移与 iota 计数配合可优雅地实现存储单位的常量枚举:
type ByteSize float64
const (
	_ = iota // 通过赋值给空白标识符来忽略值
	KB ByteSize = 1<<(10*iota)
	MB
	GB
	TB
	PB
	EB
	ZB
	YB
)

在通讯中使用位左移表示标识的用例
type BitFlag int
const (
	Active BitFlag = 1 << iota // 1 << 0 == 1
	Send // 1 << 1 == 2
	Receive // 1 << 2 == 4
)

flag := Active | Send // == 3

Go 中拥有以下逻辑运算符:==、!=(第 4.5.1 节)、<、<=、>、>=。
它们之所以被称为逻辑运算符是因为它们的运算结果总是为布尔值 bool。例如:
b3:= 10 > 5 // b3 is true

常见可用于整数和浮点数的二元运算符有 +、-、* 和 /。
/ 对于整数运算而言,结果依旧为整数,例如:9 / 4 -> 2。
取余运算符只能作用于整数:9 % 4 -> 1。

可以将语句 b = b + a 简写为 b+=a,同样的写法也可用于 -=、*=、/=、%=。

有些运算符拥有较高的优先级,二元运算符的运算方向均是从左至右。下表列出了所有运算符以及它们的优先级,由上至下代表优先级由高到低:
优先级 	运算符
 7 		^ !
 6 		* / % << >> & &^
 5 		+ - | ^
 4 		== != < <= >= >
 3 		<-
 2 		&&
 1 		||

字符只是整数的特殊用例。byte 类型是 uint8 的别名,对于只占用 1 个字节的传统 ASCII 编码的字符来说,完全没有问题。例如:var ch byte = 'A';
字符使用单引号括起来。
在 ASCII 码表中,A 的值是 65,而使用 16 进制表示则为 41,所以下面的写法是等效的:
var ch byte = 65 或 var ch byte = '\x41'

另外一种可能的写法是 \ 后面紧跟着长度为 3 的八进制数,例如:\377。
在书写 Unicode 字符时,需要在 16 进制数之前加上前缀 \u 或者 \U。

字符串是双引号,上面的变量:单个字符是单引号
字符串是一种值类型,且值不可变,即创建某个文本后你无法再次修改这个文本的内容;更深入地讲,字符串是字节的定长数组。
解释字符串:该类字符串使用双引号括起来,其中的相关的转义字符将被替换,这些转义字符包括:
\n:换行符
\r:回车符
\t:tab 键
\u 或 \U:Unicode 字符
\\:反斜杠自身

非解释字符串:类字符串使用反引号括起来,支持换行,例如:
`This is a raw string \n` 中的 `\n\` 会被原样输出`。

string 类型的零值为长度为零的字符串,即空字符串 ""。

可以通过函数 len() 来获取字符串所占的字节长度,例如:len(str)

字符串的内容(纯字节)可以通过标准索引法来获取,在中括号 [] 内写入索引,索引从 0 开始计数:
字符串 str 的第 1 个字节:str[0]
第 i 个字节:str[i - 1]
最后 1 个字节:str[len(str)-1]

两个字符串 s1 和 s2 可以通过 s := s1 + s2 拼接在一起。由于编译器行尾自动补全分号的缘故,加号 + 必须放在第一行。
s2 追加在 s1 尾部并生成一个新的字符串 s。你可以通过以下方式来对代码中多行的字符串进行拼接:
str := "Beginning of the string " +
	"second part of the string"

关于字符串的标准库函数可看原书

HasPrefix 判断字符串 s 是否以 prefix 开头:
strings.HasPrefix(s, prefix string) bool    

HasSuffix 判断字符串 s 是否以 suffix 结尾:
strings.HasSuffix(s, suffix string) bool    

Contains 判断字符串 s 是否包含 substr:
strings.Contains(s, substr string) bool    

Index 返回字符串 str 在字符串 s 中的索引(str 的第一个字符的索引),-1 表示字符串 s 不包含字符串 str:
strings.Index(s, str string) int

LastIndex 返回字符串 str 在字符串 s 中最后出现位置的索引(str 的第一个字符的索引),-1 表示字符串 s 不包含字符串 str:
strings.LastIndex(s, str string) int

如果 ch 是非 ASCII 编码的字符,建议使用以下函数来对字符进行定位:
strings.IndexRune(s string, r rune) int

Replace 用于将字符串 str 中的前 n 个字符串 old 替换为字符串 new,并返回一个新的字符串,如果 n = -1 则替换所有字符串 old 为字符串 new:
strings.Replace(str, old, new, n) string

Count 用于计算字符串 str 在字符串 s 中出现的非重叠次数:
strings.Count(s, str string) int

Repeat 用于重复 count 次字符串 s 并返回一个新的字符串:
strings.Repeat(s, count int) string

ToLower 将字符串中的 Unicode 字符全部转换为相应的小写字符:
strings.ToLower(s) string

ToUpper 将字符串中的 Unicode 字符全部转换为相应的大写字符:
strings.ToUpper(s) string

strings.TrimSpace(s) 来剔除字符串开头和结尾的空白符号;

如果你想要剔除指定字符,则可以使用 strings.Trim(s, "cut") 来将开头和结尾的 cut 去除掉。该函数的第二个参数可以包含任何字符,

如果你只想剔除开头或者结尾的字符串,则可以使用 TrimLeft 或者 TrimRight 来实现。

将会利用 1 个或多个空白符号来作为动态长度的分隔符将字符串分割成若干小块,并返回一个 slice,如果字符串只包含空白符号,则返回一个长度为 0 的 slice。
strings.Fields(s) 

用于自定义分割符号来对指定字符串进行分割,同样返回 slice。
strings.Split(s, sep) 

Join 用于将元素类型为 string 的 slice 使用分割符号来拼接组成一个字符串:
strings.Join(sl []string, sep string) string


与字符串相关的类型转换都是通过 strconv 包实现的。

获取程序运行的操作系统平台下 int 类型所占的位数,如:strconv.IntSize。

针对从数字类型转换到字符串,Go 提供了以下函数:
strconv.Itoa(i int) string 返回数字 i 所表示的字符串类型的十进制数。
strconv.FormatFloat(f float64, fmt byte, prec int, bitSize int) string 将 64 位浮点型的数字转换为字符串,其中 fmt 表示格式(其值可以
是 'b'、'e'、'f' 或 'g'),prec 表示精度,bitSize 则使用 32 表示 float32,用 64 表示 float64。

针对从字符串类型转换为数字类型,Go 提供了以下函数:
strconv.Atoi(s string) (i int, err error) 将字符串转换为 int 型。
strconv.ParseFloat(s string, bitSize int) (f float64, err error) 将字符串转换为 float64 型。

查看变量t的类型
fmt.Println(reflect.TypeOf(t),reflect.ValueOf(t).Kind())


4.9
go可以使用指针,但是不能进行指针运算,防止指针越界导致程序崩溃
Go 语言的取地址符是 &,放到一个变量前使用就会返回相应变量的内存地址。和c语言是一样的

地址可以存储在一个叫做指针的特殊数据类型中,在本例中这是一个指向 int 的指针,即 i1:此处使用 *int 表示。如果我们想调用指针 intP,我们可以这样声明它:
var intP *int
然后使用 intP = &i1 是合法的,此时 intP 指向 i1。

一个指针变量可以指向任何一个值的内存地址 它指向那个值的内存地址,在 32 位机器上占用 4 个字节,在 64 位机器上占用 8 个字节,并且与它所指向的值的大小无关。

当一个指针被定义后没有分配到任何变量时,它的值为 nil。

在书写表达式类似 var p *type 时,切记在 * 号和指针名称间留有一个空格,因为 - var p*type 是语法正确的,但是在更复杂的表达式中,它容易被误
认为是一个乘法表达式!

符号 * 可以放在一个指针前,如 *intP,那么它将得到这个指针指向地址上所存储的值;这被称为反引用(或者内容或者间接引用)操作符;另一种说法是指针转移。

对于任何一个变量 var, 如下表达式都是正确的:var == *(&var)。

未经允许不得转载:江哥架构师笔记 » 第4章总结:基本结构和基本数据类型

分享到:更多 ()

评论 抢沙发

评论前必须登录!