Go趣学指南

Go趣学指南

备注:

内置函数

  • len()
  • cap()
  • append()
  • delete()
  • new()
  • copy()

本书未涉及:

  • iota表示声明连续的常量
  • 移位操作符« » 位运算符 & |
  • 关键字continue和关键字goto和标签
  • 遮蔽变量的具体规则
  • 复数和虚数
  • 裸返回(bare return)
  • 空借口 interface{} 浅尝即止
  • 介绍了类型断言但没有介绍类型判断
  • 双向通道
  • 初始化操作的init特殊函数
  • 每个内置函数没有详细介绍,例如分配内存并返回指针的new函数和复制切片的copy函数
  • 没有说明如何将自己的包分享给别人
  • git版本控制
  • 丰富的工具和库组成的生态系统

入门

并非所有编程语言都需要编译才能运行,如python ruby和其他一些流行语言就选择了在程序运行的时候,通过解释器一条接一条地转换代码中的声明,但这也意味着bug可能会隐藏在测试尚未触及的代码当中.

解释器不仅能够让开发过程变得迅速且具有交互性,还能够让语言本身变得灵活,相反编译语言因为缓慢的编译速度常常为人诟病,但实际上并非所有的编译语言都是如此.

go想要构造地像c++和java这类静态编译语言一样安全高效,还可以像python这类动态型解释性语言一样轻巧且充满乐趣.

go编译器的优点

go的编译可以在极短的时间内完成,只需要一条命令,排除了可能会导致歧义的特性,为传统语言死板的数据结构提供了轻量级别的代替品.它可以在程序运行之前找出代码中一些人为失误,如拼写错误等.

go能够利用多核机器商的每个核心获得额外的性能优势.

包和函数

package关键字声明了代码所属的包,编写代码都会被组织称各式各样的包.go语言本身就提供了这些包,标准库.

import关键字导入自己将要用到的包,math包提供了sin cos tan sqrt 等等函数,fmt包则提供了用于格式化输入和输出的函数.

func关键字用于声明函数,每个函数的函数体body都要用大括号{}包围起来

main标识符具有特殊意义,从main包的main函数开始执行,如果main不存在,将报告一个错误

每次用到被导入包中的某个函数时,我们都需要在函数的名字前面加上包的名字以及一个点号座位前缀.

go语言的这一特性可以让用户在阅读代码的时候立即弄清楚各个函数分别来自哪些包.

速查
  • fmt包提供了格式化输入输出的功能
  • go程序从main包的main函数开始执行
唯一允许的大括号放置风格

go对于大括号{}的摆放位置非常挑剔.左大括号与func关键字位于同一行,而右大括号单独占一行,除此之外,其他的放置风格都是不允许的.

不需要加封号,go编译器将自动代劳,代价是遵守大括号放置风格

小结
  • 每个go程序都有包中包含的函数组成
  • 编程语言中的标点符号也是至关重要的
  • go关键字 package import func

命令式编程

被美化的计算器

执行计算

算术操作符 + - * / %

注释 \\

格式化输出

与print和println不一样,printf接受第一个参数总是string,第二个参数是表达式,格式化变量%v会在之后被替换成表达式的值

换行符 \n

可以指定多个格式化变量,按顺序把他们替换成相应的值

printf还能够调整文本的对齐位置 用户通过指定带有宽度的格式化变量%4v 当宽度为正数时,空格将被填充至文本左边,而当宽度为负数时,空格将被填充至文本右边.

func main() {
	fmt.Printf("%-15v\n", "SpaceX")
	fmt.Printf("%15v\n", "Vigin Galactic")
}
SpaceX         
 Vigin Galactic

进程 已完成,退出代码为 0
常量和变量

两个新的关键字var 和const

常量是不能被修改的,变量必须先声明后使用

可以一次声明多个变量,或者在同一行里声明多个变量

func main() {
   fmt.Printf("%-15v\n", "SpaceX")
   fmt.Printf("%15v\n", "Vigin Galactic")

   var (
      name = "ljs"
      age = 15
   )
   var name1 ,age1 = "ljs",10
   fmt.Println(name , age)
   fmt.Println(name1,age1)
}
SpaceX         
 Vigin Galactic
ljs 15
ljs 10

进程 已完成,退出代码为 0
增量和赋值操作符

赋值的同时执行一些操作 i++ i–

go并不支持++i这种前置增量操作

数字游戏

如果我们在写代码的时候忘记对伪随机数执行加一操作,那么程序将返回一个0-n-1数字,而不是我们想要的0-n的数字,这是典型的"差一错误 off-by-one error"的例子

func main() {
	rand.Seed(time.Now().UnixNano())
	intn := rand.Intn(10)+1
	fmt.Println(intn)
	fmt.Println("apppppple">"banana")
}
10
false

进程 已完成,退出代码为 0

故此,生成56000000至401000000的随机距离,应该是

var distance = rand.Intn(345000001)+56000000
小结
  • print println printf都可以将文本和数值显示到屏幕上

  • const关键字 声明常量 var关键字 声明变量

  • rand包的导入路径为math/rand 包中的Intn可以生成伪随机数

  • 认识关键字 import package func const var

循环与分支

  • if 和 switch
  • for循环
  • 基于条件实现循环和分支处理
真或假

python和js中就把空文本"“和数字零看作是"假”,但是ruby和elixir却把这两个值看作"真".对go来说,true是唯一的真值,而false是唯一的假值.

strings.Contains函数返回真假

运算符既可以比较数值,也可以比较文本

js和PHP都提供了特殊的三等号===运算符来实现严格的相等性检查,go只提供了一个相等运算符,而且她不允许直接将文本和数值进行比较.

apple和banana两个单词哪个更大,显然是比较字母 banana更大

逻辑运算符

go也采用了短路逻辑

使用switch实现分支

switch a {

case “helloworld”: …

case “hello ljs”: …

default: …

}

switch {

case a==“helloworld”: …

case a==“hello ljs”: …

default: …

}

func main() {
	//说明fallthrough只能将下降延续到下一个case语句中 下下个就不行了 并且啊 fallthrough的时候还不需要判断条件就可以执行
	score := 79
	switch {
	case score < 100:
		fmt.Println("fuck")
		fallthrough
	case score == 90:
		fmt.Println("okok")
		fallthrough
	case score < 80:
		fmt.Println("just so so")
	case score < 60:
		fmt.Println("shit shit shit")
	}
}
fuck
okok
just so so

进程 已完成,退出代码为 0

**注意: **在C Java Js等语言中,下降是switch语句各个分支的默认行为,而go对此此案用了更为谨慎的做法,即用户需要显式地使用fallthrough关键字才会引发下降

使用循环实现重复执行
func main() {
   count := 10
   for count > 0 {
      intn := rand.Intn(100)
      if intn == 0 {
         fmt.Println("发射失败")
         break
      } else {
         fmt.Println(count)
         count--
         time.Sleep(1 * time.Second)
      }
   }
}

通过不为for语句设置任何条件来产生无限循环,然后在有需要的时候通过在循环体内部使用break来跳出循环

func main() {
   var i int
   fmt.Println("请输入100以内要猜的数字")
   fmt.Scanf("%d",&i)
   now := time.Now()
   rand.Seed(time.Now().UnixNano())
   for  {
      intn:= rand.Intn(100)
      if intn==i {
         fmt.Println(i)
         break
      }
      fmt.Println(intn)
      time.Sleep(10*time.Millisecond)
   }
   fmt.Println("用时: ",time.Now().Sub(now).String())
}
小结
  • go通过if switch for 来实现分支判断和重复执行代码

变量作用域

变量在短暂使用之后会被丢弃

请考虑这一点

虽然计算机的随机访问存储器RAM可以记住大量值,但是程序代码除了被计算机读取之外,还需要被人类阅读,所以应该尽可能保持简洁

如果可以随时修改或者在任何位置随意访问程序中的变量,那么光是跟踪大型程序中的变量就足以让人手忙脚乱.

变量作用域的好处就是让程序员聚焦于给定函数或者部分代码的相关变量,而不需要考虑除此之外的其他变量.

审视作用域

变量一旦脱离作用域,那么尝试继续访问它将引发错误.

变量作用域的另一个好处就是我们可以为不同的变量复用相同的名字

除此之外,变量作用域可以帮助我们更好的阅读代码,我们无需在脑海里记住所有变量

简短声明

简短声明为var关键字提供了另一种备选语法,该语法可以在一些无法使用var关键字的地方使用.

func main() {
   //短声明就是在var不能使用的时候,替代var来声明变量,同时提高可读性,另外缩小变量的作用域范围,提高变量名的复用性
   //可以在case里放置if语句诶
   for i := 0; i < 1; i++ {
      fmt.Println(i)
   }

   if i := 1; i > 0 {
      fmt.Println("大于0")
   }

   switch i := 1; i {
   case 1 :fmt.Println("这是1诶!")
   }

   switch i := 1; i > 0 {
   case true:fmt.Println("真的诶!")
   }

   switch i := 1; {
   case i ==1 :fmt.Println("真的是1诶!!!")
   }
}

在使用简短声明的情况下,对i变量的声明和初始化将成为for循环的一部分,该变量在循环结束之后脱离作用域,外部无法访问

**提示: **从代码可读性考虑,声明变量的位置和使用变量的位置应该尽可能邻近

除了for循环之外,简短声明还可以在if语句 switch语句中使用

作用域的范围

**注意: **包作用域在声明变量时不允许使用简短声明,函数作用域比包作用域的范围狭窄.

switch分支的作用域是唯一一种无需使用大括号标识的作用域.

如果作用域约束太死,就会导致代码重复,我们应该根据这种现象判断是否变量太过约束,并实施重构.

小结
  • 简短声明不仅仅是var声明的快捷方式,他还可以用在var声明无法使用的地方

类型

字符 文本 数字 基本类型

实数

浮点数的二进制比位被一分为二,其中一部分用于表示杯子或者说桶bucket,另一部分则用于表示桶中的硬币或者说偏移量

尽管每个被子能够容纳的最大硬币数量是一致的,但是每个杯子能够表示的数字却是各不相同的.通过控制杯子中的数量可以让一些杯子以较高精度表示小范围数字,或者以较低精度表示大范围数字.

声明浮点型类型变量

go编译器支持自动推断,所有带小数点的数字在默认情况下都会被设置为float64类型

go语言拥有两种浮点类型,默认为float64,很多语言中使用术语双精度浮点数double来描述这种浮点数;另一种浮点类型是float32,又称单精度浮点数,4字节,提供的精度不如float64,使用时需要指定变量类型

**注意: **诸如三位游戏中数量庞大的数据时,使用float32牺牲精度的代价来降低内存占用,是有意义的.而math包的函数处理都是float64类型的值,除非有特殊理由,否则应该优先使用float64类型.

零值

每种类型都有对应的默认值,我们称为零值.当声明一个变量但是却没有为它设置初始值的时候,它的值就是零值.float32零值就是0.0

打印浮点类型

使用格式化变量%f来指定被打印小数的位数,甚至可以根据给定的宽度和精度格式化变量值

精度用于指定小数点之后应该出现的数字数量

宽度指定了打印整个实数(包括整数部分/小数部分/小数点在内)需要显示的最小字符数量

长了就被截断了truncate

如果用户给定的宽度比打印实数所需的字符数量要大,那么将使用空格填出输出的左侧

如果想使用0而不是空格来填充输出的左侧,在宽度前面加上一个0即可

func main() {

   third:=1.0/3
   fmt.Println(third)
   fmt.Printf("%v\n",third)
   fmt.Printf("%f\n",third)
   fmt.Printf("%.3f\n",third)
   fmt.Printf("%4.2f\n",third)
   fmt.Printf("%5.2f\n",third)
   fmt.Printf("%05.2f\n",third)
}
0.3333333333333333
0.3333333333333333
0.333333
0.333
0.33
 0.33
00.33
浮点数的精确性

浮点数计算经常会受到舍入错误的影响.为了尽可能减少舍入错误,我们可以将乘法放到出除法的前面执行,类似数值分析防止误差放大.

比较浮点数

为了避免舍入错误,我们不直接比较两个浮点数,而是计算他们之间的差,是否足够小来判断两个浮点数是否相等.因为我们可以使用math包提供的Abs函数来计算绝对值

fmt.Println(math.Abs(0.1+0.2 -0.3)<0.0001)

**提示: **引发浮点数错误的上限值被称为机械最小值,float64是2^-52,float32是2^-23.不幸的是,浮点数错误累积的相当快,因为为了对浮点数进行比较,必须想上列中设定的0.0001那样,根据自己的应用选择一个合适的容差.

小结
  • 浮点类型的应用范围非常广,但是她的精确性在某些情况下是值得商榷的.

整数

不能存储分数,不会出现浮点类型的精度问题,但每种类型的取值范围各不相同

声明整数类型变量

众多整数类型中,有5种整数类型是有符号的signed,这意味着他们可以表示正整数也可以表示负整数

除了有符号整数之外,go还提供了5种只能表示非负整数的无符号unsigned整数类型.例如uint.

go进行类型推断的时候总是会选择int类型座位整数值的类型

为不同场合而设的整数类型
类型 取值范围 内存占用情况
int8 -128-127 8位
uint8 0-255 8位
int16 -32768-32767 16位
uint16 0-65535 16位
int32 -2147483648-2147483647 32位
uint32 0-4294967295 32位
int64 64位
uint64 64位

int类型和uint类型会根据目标硬件选择最合适的位长

**注意: **int不是任何类型的别名,int int32和int64实际上是三种不同的类型

了解类型

可以使用printf函数提供的格式化变量%T去查看指定变量的类型

	//复用格式化变量
	day := 365
	fmt.Printf("one year has %05d days , day is %[1]T type \n", day)
	fmt.Printf("day is %T type , one year has %05[1]d days\n", day)

我们可以将[1]添加到第二个格式化变量%v中,以此来复用第一个格式化变量的值,从而避免代码重复.

one year has 00365 days , day is int type 
day is int type , one year has 00365 days
十六进制数

十进制对于拥有十根手指的人类来说是一种非常棒的数字系统,但与之相比,十六进制更适合计算机.因为一个十六进制需要消耗4个二进制位,也就是半字节byte,而2个十六进制数则正好需要消耗8个二进制位,也就是1字节.

go语言要求十六进制数字必须带有0x前缀.

var red , green , blue unit8 = 0,141,213
var red , green , bule unit8 = 0x00 , 0x8d , 0xd5  //这两都一样的

在使用printf函数打印十六进制数字的时候,你可以使用%x或者%X作为格式化变量:

//十六进制打印成二进制
fmt.Printf("%05b \n", 0xa)

为了能够完美适配层叠样式表文件的颜色和数字,我们需要用到格式化变量%2x.和之前一样0表示用0填充,2表示宽度为2.

fmt.Printf("color: #%02x%02x%02x;",red,green,blue) //打印出"color: #008dd5"
整数回绕

整数类型虽然不会像浮点类型那样因为舍入错误而导致不精确,但它也有自己的局限.那就是有限的取值范围.

在go中,当超过整数类型的取值范围时,会出现整数回绕wrap around现象.

例如uint8=255 255+1=>0 int8=127 127+1=>-128

聚焦二进制位

同样的,使用格式化变量%b,且可以选择启用零填充并指定格式化输出的最小长度。

	fmt.Printf("%08b\n", green)   //00000011

由于int类型和uint类型的位长在不同硬件上可能会有所不同,因此math包没有定义这两种类型的最大值常量和最小值常量.

避免时间回绕

基于unix的操作系统都使用协调世界时UTC 1970.1.1以来的秒数来表示时间,但这个秒数在2038年将超过20亿,大致相当于int32类型的最大值.幸运的是,这个问题可以通过使用64位整数来解决,使用int64或uint64都可以存储>20亿的数字.

//使用int64解决int32只能表示到2038年的问题
unix := time.Unix(12622780800, 0)
fmt.Println(unix)
小结
  • 除非回绕正是你所需要的,否则就应该谨慎的选择合适的整数类型以避免回绕.

大数

  • 学会使用指数来减少键入0的次数
  • 学会使用big包处理非常大的数
  • 学会使用大常量和字面值

计算机编程经常需要权衡利弊,取舍折中.例如浮点数虽然可以存储任意大小的数字,但是有时候会不精确和不准确,相反,整数虽然准确,但是会收到取值范围的限制.本章将介绍两种方案,可以代替float和int,提供数值巨大并且计算精确的数字.

请考虑这一点

CPU都会为整数运算和浮点数运算提供优化,并且这种优化有时候也适用于其他数值表示.

指数形式写法
//利用指数来减少0的键入次数  
fmt.Println(10e2)  //1000

如果:如果用户没有显式的为包含指数的数值变量指定类型,那么go将推断其类型为float64

big包
  • big.Int
  • big.Float
  • big.Rat 存储1/3

一旦决定使用big.Int,就需要在等式的每个部分都是用这种类型,即使对于已经存在的常量来说也是如此.big.Int类型的最基本的方法就是使用NewInt函数,函数接受一个int64类型的值作为输入,返回一个big.Int类型的值作为输出.

newInt := big.NewInt(240000000)
fmt.Println(newInt)

此外,对于>int64取值范围的数字,我们可以通过给定一个string来创建响应的big.Int类型的值

//大数
b := new(big.Int)
b.SetString("2400000000", 10)
fmt.Println(b)

因为数值是基于十进制的,所以第二个参数是10

**注意: **方法跟函数非常相似,至于内置函数new则是为指针而设的.

可以使用Div方法去执行相应的除法操作

//大数
b := new(big.Int)
b.SetString("2400000000", 10)
fmt.Println(b)

newInt := big.NewInt(240000000)
fmt.Println(newInt)

b.Div(b, newInt)
fmt.Println(b)   //10

正如所示,big.Int这样的大类型虽然能够精确地表示任意大小的数值,但代价是用起来比int/float等原生类型要麻烦,而且运行速度也会相对较慢.

大小非同寻常的常量

常量声明可以跟变量声明一样带有类型,但常量也无法用uint64来表示巨大的数值

所以go语言在处理常量时的做法与处理变量时的做法并不相同.go语言不会为常量推断类型,而是直接将其标识为无类型untyped,这样就不会引发溢出错误.

const name = 1 //不会自动推断类型 类型是untyped
//字面量和常量底层将由big包提供支持 不会为常量和字面量推断类型
//常量和bigint无法转换

const distance = 236000000000000000
const guangnian = 100000000
//b2 := new(big.Int)
//setString, _ := b2.SetString("236000000000000000", 10)
//setString.Div(setString,guangnian)
fmt.Println(distance / guangnian)

常量通过关键字const来进行声明,除此之外,程序里的每个字面量值literal value也都是常量.

因为go的编译器就是用go语言编写的,并且在底层实现中,无类型的数值常量将由big包提供支持.

变量也可以使用常量作为值,只要变量的大小能够容纳常量即可.

尽管go编译器使用big包处理无类型的数值常量,但常量和big.Int值是无法互换的

小结
  • 无类型常量可以存储非常大的数值,并且所有数值型字面量都是无类型常量.
  • 无类型常量在被用作函数参数的时候,必须转换为有类型变量.

多语言文本

计算机在表示文本的时候使用了一些特殊技巧,从而使这种表示既节省存储空间又足够灵活.

如果你声明了一个变量,但是没有为它赋值,那么go语言将使用变量类型的零值对其进行初始化,而string类型的零值就是空字符串""

原始字符串字面量

字符串字面量可以包含转义字符,可以使用反引号`而不是双引号来包围文本

原始字符串字面量可以在代码里跨越多个文本行

//使用反引号包围的叫做原始字符字面量
fmt.Printf("hello world \t\t")
fmt.Printf(`hello world \t\t`)
字符/代码点/符文和字节

go语言提供了rune(符文)类型,该类型是int32的别名,此外go语言还提供了uint8类型的别名byte,这种类型可以很好地表示二进制数据,也可以表示ASCII的英文字符,刚好128个

**类型别名: **因为类型别名实际上就是同一类型的不同名字,所以rune和int32是可以互换的,当然,用户也可以自行声明类型别名

type byte = uint8

使用格式化变量%c来打印字符而不是数字本身

fmt.Printf("\n%c", 65)
fmt.Printf("\n%c", 128515)
var rune = 65
fmt.Printf("\n%c", rune)
fmt.Println(strconv.Atoi(string('😃'))) //这样不行 只能给string进行转换成int 比如"110"->110
fmt.Printf("%d\n", '😃')           
A
😃
A0 strconv.Atoi: parsing "😃": invalid syntax
128515

**提示: **虽然任意一种整数类型都可以使用格式化变量%c,但是通过使用别名rune可以表明数字960的用途是用来表示字符的,而不是用来表示数字.

go提供了相应的字符字面量句法,用户只需要向’A’这样使用单引号将字符包围起来,就可以取得该字符的代码点.如果用户声明了一个字符字面量却没有为其指定类型,那么go将推断该变量的类型为rune

虽然rune类型代表的是一个字符,但它实际上存储的仍然是数字值.

字符串操作

go的字符串并不容易被操纵.我们可以将不同字符串赋值给同一个变量,但是无法对字符串本身进行修改

可以通过方括号[]指定指向字符串的索引,但是不能修改这些字符

Ruby中的字符串和C的字符数组允许被修改,而go中的字符串与python/java/js中的字符串一样,都是不可变的,你不能修改go中的字符串.

s := "shalom"
for _, c := range s {
   fmt.Printf("%d\n", c)
}

fmt.Println('A' - 'a')
fmt.Printf("%c\n", 'x'+3-26)
115
104
97
108
111
109
-32
a
将字符串解码为符文

以往我们访问字符串通常都是访问字符串的每个字节(8位),但是没有考虑到各个字符可能会由多个字节组成(如16位或32位),例如中文/韩文/日语等.所以处理字符串前,先将他们解码为rune类型,也就是4字节32位.

**注意: **go语言和很多编程语言不同的一点在于,go允许函数返回多个值

forrange关键字可以迭代各种不同的收集器,还可以解码UTF-8编码的字符串,而fori就不行.

不需要某个返回值的时候,使用_来省略即可.

//forrange可以解析UTF-8的编码 所以这里遍历是没有问题的
for _, v := range "镜中花,水中月" {
   fmt.Printf("%c %[1]T \n", v)
}

//fori不能解析UTF-8编码 求len的时候就已经出问题了
shi := "镜中花,水中月"
for i := 0; i < len(shi); i++ {
   fmt.Printf("%c %[1]T \n", shi[i])
}
fmt.Println(len("镜中花,水中月")) //len返回的是字符串占位的字节长度 这里返回19
镜 int32
中 int32
花 int32
, int32
水 int32
中 int32
月 int32
é uint8


ä uint8
¸ uint8
­ uint8
è uint8

± uint8
, uint8
æ uint8
° uint8
´ uint8
ä uint8
¸ uint8
­ uint8
æ uint8



19

此外,可以使用utf8包的函数来处理字符串

//使用utf8包和函数
//解析第一个字占几字节  具体值是多少
decodeRune, size := utf8.DecodeRune([]byte(shi))
fmt.Println(size)
fmt.Printf("%c \n", decodeRune)
//查看一共解析了几个字
inString := utf8.RuneCountInString(shi)
fmt.Println(inString)
//解析第一个字占几字节  具体值是多少
runeInString, size1 := utf8.DecodeRuneInString(shi)
fmt.Printf("%c %d bytes\n", runeInString, size1)

s3 := "abcdefghijklmnopqrstuvwxyz"
decodeRuneInString, i := utf8.DecodeRuneInString(s3)
fmt.Println(decodeRuneInString, " ", i)

//判断它占了几个字节
r, size2 := utf8.DecodeRuneInString("¿")
fmt.Println(r, " ", size2)

//go使用UTF-8可变长度编码,每个字符根据需要占用1-4个字节的内存空间
//可能会问fori和forr的区别吧
3
7
3 bytes
97   1
191   2
小结
  • 使用反引号` 可以包围原始字符串字面量,像\n这样的转义字符将原样保留
  • 字符串是不可变的,可以独立访问字符串中的每个字符,但是不能修改他们
  • 字符串使用UTF-8可变长度编码,每个字符需要占用1-4个字节内存空间
  • byte是uint8类型的别名,而rune是int32类型的别名
  • 关键字range可以将UTF-8编码的字符串解码为符文

类型转换

  • 学会在数值、字符串和布尔值之间实施类型转换
类型不能混合使用

如果尝试拼接数值和字符串,那么go编译器将报告一个错误(无效操作:不匹配的类型)

在其他语言中混合使用多种类型

有些编程语言在程序员同时给定两种或多种不同类型的值时,会尽可能猜测程序员的意图.对于实施隐式类型转换的语言来说,不能熟记各种隐式转换规则的人将难以预测代码行为.对go编译器而言,它不会这样,只会引发一个类型不匹配的错误.

混合使用整数类型和浮点类型同样会引发类型不匹配的错误.

go不会对你的意图做任何的假设,你必须通过显式的类型转换来解决这个问题.

数字类型转换

虽然go语言不允许混合使用不同类型的变量,但是通过类型转换,可以顺利运行.

将浮点转为整数,将直接被截断而不会做任何舍入操作.

此外,各种长度不同的类型之间的转换.从取值范围小的类型转换为取值范围较大的类型总是安全的,但其他方式的类型转换则存在风险.

类型转换的危险之处

之所以go希望我们进行显式的类型转换,就是让我们三思而后行,思考类型转换可能引发的后果.

例如整数类型变量转化的时候会不会出现数值过大产生回绕行为.

我们可以通过math包提供的最小常量和最大常量,来检测值的转换是否得到了无效值.

	var bh float64 = 32768
	if bh > math.MaxInt16 || bh < math.MinInt16 {
		fmt.Println(bh > math.MaxInt16 || bh < math.MinInt16)
	}

**注意: **因为math包提供的最小常量和最大常量都是无类型的,所以程序可以直接使用浮点数bh去跟整数maxint16做比较.

字符串转换

将rune或者byte转换为string,和使用格式化变量%c将符文和字节显示成字符得到的结果是一样的

var pi rune = 960
var alpha rune = 940
var omega rune = 969
var bang byte = 33
//直接对数字执行string 不会把33=>"33" 而是会把33代表的字符char找出来
fmt.Println(string(pi), string(alpha), string(omega), string(bang))
fmt.Printf("%c %c %c %c \n", pi, alpha, omega, bang)
π ά ω !
π ά ω !   

如前所述,数字代码点转换为字符串的方法实际山适用于所有整数类型

countdown := 10
//将数字直接变成字符串 而不是char 使用itoa或者sprint
fmt.Println("launch in T minus " + strconv.Itoa(countdown) + " seconds.")
fmt.Println("launch in T minus " + fmt.Sprintf("%v", countdown) + " seconds.")

另外,如果我们需要把字符串转换为数值,那么可以使用strconv包提供的atoi函数.需要注意的是,因为字符串里面可能含有无法转换为数字的奇怪文字,或者非常大以至于无法用整数类型表示的数字,那么atoi会返回一个错误,我们需要对错误进行处理.

静态类型

在go中,变量一旦被声明,就有了类型并且无法改变它的类型,这种机制成为静态类型.尝试在go中使用同一个变量操纵多个不同类型的值,将引发go编译器报告错误.

与静态类型相反,js/python/ruby等语言都采用了名为动态类型的机制.

go也提供了一些特殊的机制来应对类型不确定的情况.我猜的,例如反射,断言,空接口类型.

转换布尔值

在go语言中,布尔值并没有与之相等的数字值或字符串值,因此尝试使用string(false) int(false)huozhe bool(1) bool(“yes”) 都会导致编译器报错.

小结
  • 显式的类型转换能够避免编程中的歧义
Licensed under CC BY-NC-SA 4.0
Built with Hugo
Theme Stack designed by Jimmy