深入Go语言 - 1

命名和字面量

目录 [−]

  1. 奇怪的变量名
  2. 预定义标识符
  3. 整数字面量
  4. Rune
  5. Rune和字符串互转

准备写一个Go 语言深入开发的系列,分为三个部分。第一部分为Go 语言的深入剖析,第二部分为一些官方库的深入开发,第三部分为一些第三方库的介绍。

奇怪的变量名

标志符用来命名变量、类型、函数名等,最常规的,我们使用普通的拉丁字母和数字作为标志符,或者以下划线开始。

1
2
3
4
5
str := "hello world"
fmt.Println(str)
_str09 := "hello w0rld"
fmt.Println(_str09)

但是根据Go语言规范,任何Unicode编码的letter字符和下划线都可以作为标识符的第一个字母,之后可以是任意的Unicode的letter字符或者数字。
允许的unicode字符为Unicode分类中的Lu、Ll、Lt、Lm、Lo等字符,比如中文、希腊字母等。你可以在参考链接中查看相应的Unicode字符分类。

1
2
3
4
5
一个变量 := "hello 世界"
fmt.Println(一个变量)
ÆõĦǗΩצˮ𝟡 := "hello ¾"
fmt.Println(ÆõĦǗΩצˮ𝟡)

标识符业可以是类型名、函数名等:

1
2
3
4
5
6
7
8
9
10
11
12
type 学生 struct {
姓名 string
}
type 小学生 学生
type 조선말 interface {
}
func 函数名() {
fmt.Println("I am a function")
}

当然,我相信绝大部分的程序员都会以普通的拉丁字母和数字作为标识符的,这样阅读起来更符合大众的习惯。在搞怪的情况下,可以尝试一下这些"奇怪"字符。

预定义标识符

首先看下面一段代码,看看是否能变易成功:

1
2
3
4
5
6
7
8
func main() {
var i nil = 100
fmt.Println(i)
var isSuccess bool = 100
fmt.Println(isSuccess)
}

你肯定会说,不可能成功,类型不对呀。

没错,不可能将一个整数赋值给布尔类型的变量的。 那么你能不能加在方法外面加几行,让代码编译成功?

请注意,以下标识符实预先定义的标识符,而不是关键字,这意味着我们可以"覆盖"这些标识符的定义。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Types:
bool byte complex64 complex128 error float32 float64
int int8 int16 int32 int64 rune string
uint uint8 uint16 uint32 uint64 uintptr
Constants:
true false iota
Zero value:
nil
Functions:
append cap close complex copy delete imag len
make new panic print println real recover

比如加上下面几行代码,程序就可以编译通过。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package main
import "fmt"
type nil int
type bool uint8
func main() {
var i nil = 100
fmt.Println(i)
var isSuccess bool = 100
fmt.Println(isSuccess)
}

整数字面量

Go语言不像其它语言, 如C++、Java,在声明数值类型的时候可以通过后缀如ll等表明变量的类型, 目前Go语言不提供这个功能。

如果值以0开始,则代表8进制。

如果值以0x或者0X开始,则代表16进制。

浮点数和其它语言的表示法相同。

复数表示法业和其它语言一致。

Rune

其它语言如Java、C#,字符串的字符操作很直观,但是Go语言的字符串的实现比较特殊,这可能和Go设计者的几位大牛有关,它保留着Unix和C的痕迹。

Java语言规范规定,Java的char类型是UTF-16的code unit,也就是两个字节,字符串是UTF-16 code unit的序列,因此每个字符都是定长的,要想获得某个位置字符,很容易计算出它的字节在字符串中的位置。

Go语言使用UTF-8作为字符串的内部编码,所以在没有byte字面量的情况下,字符串都是使用UTF-8编码的。因此对于大部分字符串都是ascii字符的情况下,
占用的内存空间就会大大减少,但是带来的问题是,从字符串的字节slice中查找第n个字符比较麻烦,因为不能直接的计算出来。

这里通称所有字母都为字符,其实是不准确的,在Unicodde规范中,它们称之为code point, 比如code point U+2318代表
A既是一个字符,也是一个code point: U+00E0。

code point有点拗口,所以Go语言用rune来表示,你只需记住它们是等价的即可。

rune有单引号定义,它包含单一的一个 code point。

1
2
var r rune = '文'
fmt.Printf("%#U\n", r)

通过range可以遍历一个字符串中所有的rune:

1
2
3
4
const nihongo = "one world世界大同"
for index, runeValue := range nihongo {
fmt.Printf("%#U starts at byte position %d\n", runeValue, index)
}

因为字符串是以UTF-8编码的,通过输出可以看到ascii字母只用一个字节,而这几个中文汉字每个汉字用了3个字节。

要想获得字符串中包含几个字符(rune),下面的方法是不对的,它返回的是字符处内部的字slice的长度((9 + 4*3 =21):

1
2
const str = "one world世界大同"
fmt.Println(len(str))

我记得有个Go语言写的框架,在获取一篇文章的前N个字符的时候,就直接用len方法计算,这对于中文文章来说,肯定不对,截取的字符要少于期望的字符数,而且可能截取半个字符。

要想在字符串中操作rune,可以使用 package unicode/utf8,它提供了一组处理字符串和rune的方法,
比如我们正确计算一个字符串中包含的rune的数量:

1
fmt.Println(utf8.RuneCountInString(str))

字符串以两端用双引号包含的方式定义,允许使用转义字符存在或者"\"+byte方式包含rune。

1
2
3
4
5
6
"Hello, world!\n"
"世界"
"\u65e5本\U00008a9e"
"\xff\u00FF"
"\uD800" //非法
"\U00110000" //非法

Rune和字符串互转

直接通过T(x)类型转换即可。

1
2
3
4
r := []rune(str)
fmt.Printf("%#v\n", r)
str = string(r)
fmt.Printf("%#v\n", str)

另外 package strconv也提供了格式化rune为字符串的一些方法, 比如

1
2
s := strconv.QuoteRune('☺')
fmt.Println(s)