常量
常量使用一个名称来绑定一块内存地址,该内存地址中存放的数据类型由定义常量时指定的类型决定,而且该内存地址里面存放的内容不可以改变 。 Go 中常量分为布尔型、宇符串型和数值型常量。常量存储在程序的只读段里( .rodata section ) 。
预声明标识符 iota 用在常量声明中,其初始值为 0。一组多个常量同时声明时其值逐行增加, iota 可以看作自增的枚举变量,专 门用来初始化常量。
// 类似枚举的iota const ( c0 = iota // c0 == 0 c1 = iota // c1 == 1 c2 = iota // c2 == 2 ) // 简写模式 const ( a = iota // a == 0 b // b == 1 c // c == 2 ) // 注意iota 逐行递增 const ( // << 右移多少就是乘多少个2 d = 1 << iota // d == 1 (iota == 0) e = 1 << iota // e == 2 (iota == 1) f = 3 // f == 3 (iota == 2, unused) g = 1 << iota // g == 8 (iota == 3) ) const ( u = iota * 42 // u == 0 (untyped integer constant) v float64 = iota * 42 // v == 42.0 (float64 constant) w = iota * 42 // w == 84 (untyped integer constant) ) // 分开的const语句,iota计数会被重置为0 const x = iota // x == 0 const y = iota // y == 0
基本数据类型
Go 是一种强类型的静态编译语言,类型是高级语言的基础,有了类型,高级语言才能对不同类型抽象出不同的运算,编程者才能在更高的抽象层次上操纵数据,而不用关注具体存储和运算细节。
- Golang 是强类型语言
在赋值过程中, 类型必须保持一致
变量必须先定义后使用, 且必须被用到
Golang 会为每个变量设置默认值
变量不能重名
Golang 会根据值类型做变量类型推断
Go 语言内置七类基本数据类型( 20 个具体子类型)。
布尔类型: bool
整型 : byte int int8 intl6 init32 int64 uint uint8 uintl6 uint32 uint64 uintptr
浮点型 : float32 float64
复数: comlex64 complexl28
字符 : rune
字符串: string
错误类型:error
布尔类型
布尔类型关键字是 bool,布尔类型只有两个值: true 和 fasle,阳e 和 fals巳 是 Go 内置的两
个预声明标识符 。
var ok bool ok = true
或
ok := false
布尔型数据和整型数据不能进行相互转换。
var a bool a= 1 //error 1是整型字面量
比较表达式和逻辑表达式的结果都是布尔类型数据 。
var b bool = (x > y) && (x >0)
if 和 for i吾句的条件部分一定是布尔类型的值或表达式 。
if a <= b { print(b) else { print(a) } for ; true ; { //等价于 C 语言的 while (1) }
声明的布尔型变量如不指定初始化值,则默认为 false 。
var b bool // b is fals e
整型
Go 语言内置了12种整数类型,分别是 byte、int 、int8、int16、init32、int64、uint、uint8、uintl6、uint32、uint 64、uintptr。其 中 byte 是 uint8 的别名,不同类型的整型必须进行强制类型转换。
var a int = 1 var b int32 = 2 b = a //error
整型支持算术运算和位操作,算术表达式和位操作表达式的结果还是整型。
var a int = (1+2)*3 var b int = 1000>>2
浮点型
浮点型用于表示包含小数点的数据 , Go 语言内置两种浮点数类型,分别是 float32 和 float64 。浮点数有两个注意事项:
- 浮点数字面量被自动类型推断为 float64 类型。
- 计算机很难进行浮点数的精确表示和存储 , 因此两个浮点数之间不应该使用=或 != 进行比较操作,高精度科学计算应该使用 math 标准库 。
复数类型
Go 语言内置的复数类型有两种,分别是 complex64 和 complex l28,复数在计算机里面使用两个浮点数表示 , 一个表示实部, 一个表示虚部。 complex64 是由两个 float32 构成的, complex l28是 由两个 float64 构成的。复数的字面量表示和数学表示法一样。
var value1 complex64 = 3.2 + 12i value2 := 3.1 + 6i //Go有三个内置函数处理复数 //complex、real和imag,分别返回复数的复数、实部和虚部 fmt.Println(complex(3, 2)) // "3+2i" fmt.Println(real(3 + 2i)) // "3" fmt.Println(imag(3 + 2i)) // "2"
字符串
Go 语言将字符串作为一种原生的基本数据类型, 字符串的初始化可以使用字符串字面量。
例如 :
var a = "hello,world"
- 字符串是常量,可以通过类似数组 的索引访问其字节单元,但是不能修改某个字节的值。
例如 :
var a = "hello,world " b := a[0] a[1] ='a' //error
- 宇符串转换为切片 []byte(s) 要慎用,尤其是当数据量较大时(每转换一次都需复制内容)。
例如:
a := "hello,world!" b : = []byte(a)
- 字符串尾部不包含 NULL 字符,这一点和 C/C++不一样。
- 字符串类型底层实现是一个二元的数据结构,一个是指针指 向字节数组的起点,另 一个是长度 。
- 基于字符串创建的切片和原字符串指向相同的底层字符数组 , 一样不能修改 , 对字符串的切片操作返回的子串仍然是string,而非 slice。
例如 :
a := "hello,world!" b := a[0 : 4] c := a[1 : ] d := a[ : 4)
- 字符串和切片的转换: 字符串可以转换为字节数组 ,也可以转换为 Unicode 的字数组。
例如 :
a := "hello,世界!" b := []byte(a) c := []rune(a)
- 字符串的运算。
例如 :
a := "hello" b := "world" // 字符串拼接 c := a + b println(c) e := len(a) // 字符串长度 println(e) d := "hello 世界!" for i := 0; i < len(d); i++ { // 遍历字节数组 fmt.Println(d[i]) } for i, ch := range d { // 遍历rune字符数组 fmt.Println(i, ch) }
rune类型
Go 内置两种字符类型 : 一种是 byte 的字节类类型( byte 是 uint 的别名),另一种是表示Unicode 编码的字符 rune。 rune 在 Go 内部是 int32 类型的别名,占用 4 个字节。 Go 语言默认的字符编码就是 UTF-8 类型的,如果需要特殊的编码转换,则使用 Unicode/UTF-8 标准包。
复合数据类型
复合数据类型就是由其他类型组合而成的类型。 Go 语言基本的复合数据类型有指针、数组、切片、字典( map )、通道、结构和接口,它们的字面量格式如下 :
* pointerType //指针类型使用*后面跟其指向的类型名 [n] elementType //数组类型使用[n]后面跟数组元素类型来表示,n表示该数组的长度 [] elementType //切片类型使用[]后面跟切片元素类型来表示 map [keyType]valueType //map 类型使用 map[键类型]值类型来表示 chan valueType //通道使用 chan 后面跟通道元素类型来表示 struct { //结构类型使用 struct{}将各个结构字段扩起来表示 feildType feildType feildType feildYype ··· } interface { //接口类型使用 interface{}将各个方法括起来表示 method1(inputParams)(returnParams) method1(inputParams)(returnParams) ··· }
指针
Go 语言支持指针,指针的声明类型为*T, Go 同样支持多级指针**T 。通过在变量名前加&来获取变量的地址。指针的特点如下。
- 在赋值语句中,*T 出现在 “=” 左边表示指针声明,*T 出现在 “=” 右边表示取指针指向的值( varName 为变量名)。示例如下 :
var a = 11 p : = &a // *p 和 a 的值都是 11
- 结构体指针访问结构体字段仍然使用“ . ”点操作符, Go 语言没有“ ->”操作符 。 例如 :
type User struct { name string age int } andes := User{ name : "andes", age : 18, } p := &andes fmt.Println(p.name) //通过“.”操作符来访问结构体的字段
- Go不支持指针的运算
Go 由于支持垃圾回收,如果支持指针运算,则会给垃圾回收的实现带来很多不便,在 C和 C++里面指针运算很容易出现问题 , 因此 Go 直接在语言层面禁止指针运算。
a := 1234 p := &a p++ //不允许,报non-numeric type *int 错误
- 函数中允许返回局部变量的地址 。
Go 编译器使用“栈逃逸 ” 机制将这种局部变量的空间分配在堆上。 例如:
func sum(a, b int) *int { sum := a + b return &sum //允许,sum 会分配在heap上 }
数组
数组的类型名是[n]elemetType,其中n是数组长度,elementType是数组元素类型。比如一个包含2个int类型元素的数组类型可表示为[2]int。数组一般在创建时通过字面量初始化,单独声明一个数组类型变量而不进行初始化是没有意义的。
数组初始化
var arr [2]int //声明一个有两个整型的数组,但元素默认值都是0,一很少这样使用 array := [...]int{1,2,3} //不指定长度,但是由后面的初始化列表数量来确定其长度 a := [3]int{1:1, 2:3} //指定总长度,并通过索引值进行初始化,没有初始化元素时使用类型默认值 a := [...]int{1:1, 2:3} //不指定总长度,通过索引值进行初始化,数组长度由最后一个索引值确定,没有指定索引的元素被初始化为类型的零值。
数组的特点
- 数组创建完长度就固定了,不可以再追加元素。
- 数组是值类型的,数组赋值或作为函数参数都是值拷贝。
- 数组长度是数组类型的组成部分,[10]int 和 [20]int 表示不同的类型。
- 可以根据数组创建切片。
数组相关操作
- 数组元素访问
a := [...]int{1,2,3} b := a[0] for i,v := range a { }
- 数组长度
a := [...]int{1,2,3} alengh := len(a) for i := O; i < alengh ; i++ { }
切片
Go 语言的数组的定长性和值拷贝限制了其使用场景, Go 提供了另一种数据类型 slice (切片),这是一种变长数组,其数据结构中有指向数组的指针,所以是一种引用类型 。
Go 为切片维护三个元素一一指向底层数组的指针、切片的元素数量和底层数组的容量。
切片的创建
由数组创建
创建语法如下:array[b:e],其中,array表示开始索引,可以不指定,默认是0;e表示结束索引,可以不指定,默认是len(array)。array[b:e]表示创建一个包含e-b个元素的切片,第一个元素是array[b],最后一个元素是array[e-1]。例如:
var array = [...]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} //创建有10个int类型的数组 var slice = array[2:5] //创建一个slice,从array[2]开始,到array[5]结束,但不包含array[5] //slice的长度是3,容量是8 //slice的容量是从它的第一个元素开始,到其底层数组元素末尾的个数 var slice1 = array[2:5:7] //slice的长度是3,容量是5 var slice2 = array[2:] //slice的长度是8,容量是8 var slice3 = array[:5] //slice的长度是5,容量是10 var slice4 = array[:] //slice的长度是10,容量是10 fmt.Println(slice) //输出[3 4 5] fmt.Println(slice1) //输出[3 4 5] fmt.Println(slice2) //输出[3 4 5 6 7 8 9 10] fmt.Println(slice3) //输出[1 2 3 4 5] fmt.Println(slice4) //输出[1 2 3 4 5 6 7 8 9 10]
- 通过内置函数make创建切片
注意:由make创建的切片各元素被默认初始化为切片元素类型的零值。例如:
//len = 10, cap = 10 a := make([]int, 10) //len = 5, cap = 10 b := make([]int, 5, 10) fmt.Println(a) //输出[0 0 0 0 0 0 0 0 0 0] fmt.Println(b) //输出[0 0 0 0 0] // 直接声明切片类型变量是没有意义的 var c []int fmt.Println(c) //输出[]
切片支持的操作
- 内置函数len()返回切片长度
- 内置函数cap()返回切片底层数组容量
- 内置函数append()对切片追加元素
- 内置函数copy()用于复制一个切片
a := [...]int{0,1,2,3,4,5,6} b := make([]int,2,3) c := a[2:5] fmt.Println(len(b)) //2 fmt.Println(cap(b)) //3 b = append(b, 1) fmt.Println(b) //[0 0 1] fmt.Println(len(b)) //3 fmt.Println(cap(b)) //3 b = append(b, c...) fmt.Println(b) //[0 0 1 2 3 4] fmt.Println(len(b)) //6 fmt.Println(cap(b)) //6 //容量不够,扩容成原来的2倍,底层的数组也会变成原来的2倍 d := make([]int, 2, 2) copy(d,c) //copy函数,将c中的元素复制到d中,只会复制长度较小的那个slice的长度 fmt.Println(d) //[2 3] fmt.Println(len(d)) //2 fmt.Println(cap(d)) //2
字符串和切片的相关转换
str := "hello,世界" //通过字符串字面量初始化一个字符串str a := []byte(str) //将字符串转换为[]byte类型切片 b := []rune(str) //将字符串转换为[]rune类型切片