类型

类型总览

分类类型长度默认值说明
布尔型bool1falsetrue / false
整型byte10uint8 的别名,两者完全等价
rune40int32 的别名,两者完全等价
int, uint4 或 8032位系统中是4字节,在64位系统中是8字节
int8, uint810-128 ~ 127, 0 ~ 255
int16, uint1620-32768 ~ 32767, 0 ~ 65535
int32, uint3240-231 ~ 231-1, 0 ~ 2^32-1
int64, uint6480-263 ~ 263-1, 0 ~ 2^64-1
浮点型float3240.0精确度较低,应优先使用 float64
float6480.0精确度更高的浮点型
复数complex6480+0i内部由float32实现
complex128160+0i内部由float64实现
其他uintptr4 或 80足以存储指针的 uint32uint64 整数
array 数组,值类型,后面内容细讲
struct 结构体,值类型,后面内容细讲
string ""UTF-8 字符串,值类型
slice nil引用类型,后面内容细讲
map nil引用类型,后面内容细讲
channel nil引用类型,后面内容细讲
interface nil接口
function nil函数

数值支持八进制、十六进制,以及科学记数法。标准库 math 定义了各数字类型取值范围。

a, b, c, d := 071, 0x1F, 1e9, math.MinInt16

空指针值为nil,而不是C/C++中的 NULL

引用类型

值类型与引用类型的区别
值类型:参数传递时传递的是内容,而非对象本身
引用类型:参数传递时传递的是地址,是对象本身

go 中的引用类型只有 pointerfunctionslicemap , channel 五种,它们有复杂的内部结构,除了申请内存外,还需要初始化相关属性。
和来自其他编程语言的固有印象不同,go 中的数组、字符串都是值类型


  • 内置函数 new 计算类型大小,为其分配零值内存,返回指针。

  • make 会被编译器翻译成具体的创建函数,由其分配内存和初始化成员结构,返回对象而非指针。

a := []int{0, 0, 0} // 提供初始化表达式。
a[1] = 10

b := make([]int, 3) // makeslice
b[1] = 10

c := new([]int)
c[1] = 10 // Error: invalid operation: c[1] (index of type *[]int) -> 不能给指针赋值

关于引用类型的其他细节,就说到这里,其他的后面再谈

类型转换

go不支持隐式类型转换,即便从窄转宽也不行

var b byte = 100
// var n int = b // Error: cannot use b (type byte) as type int in assignment
var n int = int(b) // 显式转换

var f float = 4.8
var i int = int(f) // 向下取整,i = 4

其他类型不能当作布尔型使用(许多编程语言中将0视作false,非0视作true,go中不允许这么做)

a := 100
if a { // Error: non-bool a (type int) used as if condition
	println("true")
}

字符串

字符串是不可变值类型,内部用指针指向 UTF-8 字节数组。

  • 本质上是一个数组
  • 默认值是空字符串 ""。
  • 用索引号访问某字节,如 s[i]
  • 不能用序号获取字节元素指针,&s[i] 非法。
  • 不可变类型,无法修改字节数组。
  • 字节数组尾部不包含 NULL
  • 在go中没有专门的字符类型

string的内部结构

struct String
{
	byte* str;
	intgo len;
};

单引号与双引号

在Go中,双引号是用来表示字符串string,其实质是一个byte类型的数组,单引号表示rune类型(字符)

package main

import "fmt"

func main() {
	testString := "测试字符串"
	bytes := []byte(testString) //将字符串转为byte数组,这也是string内部的的存储方式
	runes := []rune(testString) //将字符串转为rune数组
	fmt.Printf("%s:%d\n","string len",len(testString)) // 15,一个汉字被转义为3个byte,所以长度为15
	fmt.Printf("%s:%d\n","bytes len",len(bytes)) // 15,原因同上
	fmt.Printf("%s:%d\n","runes len",len(runes)) // 5
}

可见 rune 类型能做到一个值对应一个字符而byte做不到(byte范围太小)

访问字符串中的字符

可以使用索引号来访问字符(byte)

s := "abc"
println(s[0] == '\x61', s[1] == 'b', s[2] == 0x63)// true true true

处理原始字符串

使用 "`" 定义不做转义处理的原始字符串,支持跨行。

s := `a
b\r\n\x00
c`

println(s)

输出

a
b\r\n\x00
c

字符串拼接

与常见语言不通,链接跨行字符串时,+ 必须在上一行末尾,否则编译错误

s := "Hello, " +
"World!"

s2 := "Hello, "
+ "World!" // Error: invalid operation: + untyped string

获取子串

支持用两个索引号返回子串。子串依然指向原字节数组,仅修改了指针和长度属性。

s := "Hello, World!"
s1 := s[:5] // Hello
s2 := s[7:] // World!
s3 := s[1:5] // ello

字符表示

单引号字符常量表示 Unicode Code Point,支持 \uFFFF\U7FFFFFFF\xFF 格式。对应 rune 类型,UCS-4。

package main

import "fmt"

func main() {
    //输出变量的类型
	fmt.Printf("%T\n", 'a') //输出int32,也就是rune
	var c1, c2 rune = '\u6211', '们'

	fmt.Println(c1 == '我', string(c2) == "\xe4\xbb\xac") //true true
}

修改字符串

要修改字符串,可先将其转换成 []rune[]byte,完成后再转换为 string。无论哪种转换,都会重新分配内存,并复制字节数组。

由于这个原因,所以处理字符串我更建议转换为[]rune处理

package main

import "fmt"

func main() {
	s := "abcd"
	bs := []byte(s)
	bs[1] = 'B'
	fmt.Println(string(bs)) // aBcd
	u := "电脑"

	us := []rune(u)
	us[1] = '话' //因为字符串本质上是rune数组,所以此处是单引号,而不是双引号
	fmt.Println(string(us)) // 此处会重新分配内存,输出:电话
}

遍历字符

for 循环遍历字符串中的字符时,也有 byterune 两种方式。

package main

import "fmt"

func main() {
	s := "abc汉字"
    // byte方式
	for i := 0; i < len(s); i++ { 
		fmt.Printf("%c,", s[i])
	}

	// rune方式
    // range 关键字专门用于遍历,返回当前遍历元素
	for _, r := range s { 
		fmt.Printf("%c,", r)
	}
}

其他资料

Q.E.D.


此 生 无 悔 恋 真 白 ,来 世 愿 入 樱 花 庄 。