package main
import "fmt"
func main(){
fmt.Println("Hwllo,World!")
}
注意:Go语言每行代码行尾是不需要加分号;
的,编译器会把换行符解析成分号,不能把函数的左扩号单独成行。
var
关键字const
关键字var name 【类型】 = 【表达式】
注意:使用过程中,"类型"和"表达式"可以省略一个,但不能同时省略。Go语言能进行自动推导,根据类型推默认表达式,也能根据表达式推出类型。
类型默认值
0
false
""
nil
变量定义示例
// 省略类型
var m = 2
// 省略表达式
var n int
// 多个变量一起声明
var a, b, c int
// 多个变量一同通过表达式声明
var i, j, k = 1, "hello", true
常量定义示例
const LEN = 10
// 一次定义多个常量
const (
SYS = "Windows"
Type = "string"
)
:=
和 =
n1 := 10 // 正确
n2 = 10 // 错误
var n3 int
n3 = 10 // 正确
:=
是声明并赋值=
是给声明后的变量赋值Go语言支持指针,指针的默认初始值为nil
。
var int x
p := &x // & 为取地址符号。 p为指向x的整型指针
*p = 1
fmt.Println(x)
Go也支持new函数
p := new(int)
=
为赋值符号。等号左边是变量名,等号右边是表达式。
Go语言也提供了自增、自减和*=
、+=
等先运算后赋值的操作
m := 1
m++ // 相当于 m = m + 1
m -= 1 // 相当于 m = m - 1
Go同样支持多重赋值
m := 2
n := 3
m,n = n,m // 实现交换m,n的值
var a, b, c int // a, b, c 同时声明
a,b,c = 1,2,3 // a,b, c 同时赋值
Go语言也提供if else
、switch
语句
switch语句的每个case
不需要额外的break
。
if expression == true {
fmt.Println("条件成立1")
}
if expression == false {
fmt.Println("条件成立2")
}
for
关键字,没有while
等其他关键字。for形式
// 一般形式
for i := 0; i < 100; i++ {
}
// 相当于 while
for {
}
// 相当于 do ... while
for ok := true; ok; ok = expression{
}
// for循环也支持range函数
arr := []int{1, 2, 3, 4} // 定义切片slice,类似c的数组
for i, v : range arr{
fmt.Println("index: ",i, "value: ", v)
}
int8
、int8
、int6
、int32
分别对应8、16、32、64位整数。整型分带符号整数和无符号整数。int
为带符号整型,uint
为无符号类型。
// 带符号整数
int8 (-128 ~ 127)
int16 (-32768 ~ 32767)
int32 (-2,147,483,648 ~ 2,147,483,647)
int64 (-9,233,372,036,854,775,808 ~ 9,233,372,036,854,775,807)
// 无符号整数
unit8 (0 ~ 255)
unit16 (0 ~ 65,535)
unit32 (0 ~ 4,294,967,295)
unit64 (0 ~ 18,446,744,073,709,551,615)
运算符优先级从上到下的递减顺序排列:
* / % << >> & &^
+ - | ^
== != < <= > >=
&&
||
一元运算符包括正负号、bit位运算
& 与 AND
| 或 OR
^ 异或 XOR
&^ 位清空 (AND NOT)
<< 左移
>> 右移
左移<<
:左移运算用零填充右边空缺的bit位
右移>>
:无符号数右移运算用0填充左边空缺的bit位,有符号数的右移运算用符号位的值填充左边空缺的bit位。
浮点型分float32
和float64
。推荐使用float64
。
float32表示小数可能会出现精度丢失。下面给出示例。
package main
import "fmt"
func main() {
var f1 float32 = 9.90
fmt.Println(f1*100)
var f2 float64 = 9.90
fmt.Println(f2*100)
}
/* output
989.99994
990
*/
复数类型
复数分为complex64
和complex128
,它们分别有float32
和float64
组成。
复数由实部和虚部组成,内置的real
和imag
函数用于获取复数的实部和虚部
var a complex128 = complex(2,3) // 2+3i
fmt.Println(real(a)) // 2
fmt.Println(imag(a)) // 3
布尔类型
bool的值用true和false表示
Go语言的布尔类型没有强制类型转换,无法把0转换成false
var b bool
b = 0 // 报错
Go语言字符串按字节存储,不同字符占用不同数目的字节。
字符串的字符按unicode编码存储,不同的字符按1~4个字节存储。其中,中文汉字占用3个字节,英文占用1个字节。
字符串索引访问是按字节访问的,而不是字符。
unicode
通常用4个字节来表示,对应Go语言的字符rune
占4个字节
rune
类型是一个衍生类型,在内存里面使用int32
类型的4各字节存储
type rune int32
Unicode支持超过一百种的语言和十万字符的码点。
UTF-8是Unicode标准的一种实现。
UTF-8特点:
package main
import "fmt"
func main() {
var s = "嘻哈china"
for i:=0;i<len(s);i++ {
fmt.Printf("%d %x ", i, s[i])
}
}
-----------
0 e5 1 98 2 bb 3 e5 4 93 5 88 6 63 7 68 8 69 9 6e 10 61
package main
import "fmt"
func main() {
var s = "嘻哈china"
for codepoint, runeValue := range s {
fmt.Printf("%d %d ", codepoint, int32(runeValue))
}
}
-----------
0 22075 3 21704 6 99 7 104 8 105 9 110 10 97
对字符串进行range遍历,每次迭代出两个变量codepoint和runeValue。
codepoint表示字符起始位置,runeValue表示对于的unicode编码(类型是rune)
字节数组和字符串的转换
借助[]byte()
和string()
即可
var s1 = "hello world"
var b = []byte(s1) // 字符串转字节数组
var s2 = string(b) // 字节数组转字符串
/*
b = [104 101 108 108 111 32 119 111 114 108 100]
s2 = hello world
*/
var a [3]int
var b [3]int = [3]int{1, 2, 3}
var b = [3]int{1, 2, 3}
c := [...]int{1, 2, 3}
d := [...]int{4, 4:1, 1:2}
// d : {4, 2, 0, 0, 1}
index:value
的方式。4:1的意思是给下标为4的元素赋值为1,。[3]int
和[4]int
是两种不同的数据类型,这两种不同的数组在编译时就已经确定。把[4]int
类型的变量赋值给[3]int
类型的变量会报错。给出一个实例,修改数组的0索引位置的元素
一种是直接传数组,此时是值传递。另一种是传数组地址,此时是引用传递(指针)。
package main
import "fmt"
func main() {
var a [3]int = [3]int{1, 2, 3}
change_arr_by_value(a)
fmt.Println(a[0]) // 1 按值传递,修改失败
change_arr_by_reference(&a)
fmt.Println(a[0]) // 999 按索引传递,修改成功
}
func change_arr_by_value(arr [3]int) {
arr[0] = 999
}
func change_arr_by_reference(arr *[3]int) {
arr[0] = 999
}
type SliceHeader struct {
Data uintptr // 指针
Len int // 长度
Cap int // 容量
}
说明
切片的底层是数组,Go语言的切片对应着底层数组。一个底层数组可以对应多个slice。
定义
s := []int{1, 2, 3, 4, 5}
如果在方括号中指定了长度或写入省略号,那就是一个数组。
ss := make([]int, 10)
make函数定义了[ ]int类型的切片,长度为10,元素默认值为0
释放slice
ss = nil
如果将slice赋值为nil,垃圾回收机制将会回收slice的空间。
获取长度和容量
len(slice)
cap(slice)
切片
s[i:j]
是左闭右开,索引区间是[i, j)
s := []int{0, 1, 2, 3, 4, 5}
s[0:3] // {0, 1, 2}
s[:]
是整个切片。数组的切片
a := [...]int{1, 2, 3, 4, 5}
ss := a[1:3]
ss[0] = 99
ss[1] = -99
fmt.Println(ss) // [99, -99]
fmt.Println(a) // [1, 99, -99, 4, 5]
slice默认是引用传递
s = []int{1, 2, 3} // 定义切片
arr = [3]int{66, 77, 88} // 定义数组
头插
ss = append(a[:], ss...)
ss = append([]int{1}, ss...)
注意:ss...
意思是把切片ss,拆解成一个个元素。之后将依次插入。 a[:]
是取数组的切片。
尾插
ss = append(ss, 1)
ss = append(ss, a[:]...)
任意位置插入
ss = append(ss[:1], append(a[:], ss[1:]...)...)
切片的底层是数组,如果使用append函数向切片中添加元素是,实际上是把元素放到切片的底层数组。
如果底层数组满了,没有空间,这时再向切片添加元素,Go只能创建一个更大的新数组,将原来的数组复制到新数组,并把新数组作为切片的底层数组。
代码演示
s1 := []int{1, 2, 3}
fmt.Printf("长度:%d,容量:%d,数组地址:%p\n", len(s1), cap(s1), s1)
s1 = append(s1, 4)
fmt.Printf("长度:%d,容量:%d,数组地址:%p\n", len(s1), cap(s1), s1)
s1 = append(s1, 5)
fmt.Printf("长度:%d,容量:%d,数组地址:%p\n", len(s1), cap(s1), s1)
s1 = append(s1, 6)
fmt.Printf("长度:%d,容量:%d,数组地址:%p\n", len(s1), cap(s1), s1)
s1 = append(s1, 7)
fmt.Printf("长度:%d,容量:%d,数组地址:%p\n", len(s1), cap(s1), s1)
s1 = append(s1, 8)
fmt.Printf("长度:%d,容量:%d,数组地址:%p\n", len(s1), cap(s1), s1)
/*output
长度:3,容量:3,数组地址:0xc00001e0c0
长度:4,容量:6,数组地址:0xc00001a120
长度:5,容量:6,数组地址:0xc00001a120
长度:6,容量:6,数组地址:0xc00001a120
长度:7,容量:12,数组地址:0xc00005c060
长度:8,容量:12,数组地址:0xc00005c060
*/
切片变换过程:
a1 := []{1,1,1,1,1}
b1 := []{-1,-1,-1}
copy(a1, b1) // 将 b1 复制到 a1
// a1 : [-1,-1,-1,1,1]
a2 := []{2,2,2,2,2}
b2 := []{-2,-2,-2}
copy(b2, a2) // 将 b1 复制到 a1
// b2 : [2,2,2]
注意:要理解复制到的含义。
copy
函数只涉及元素值的拷贝,不涉新空间的创造
copy的两个参数必须是slice,不能是数组
slice是通过指向底层数组来存储数据,而且可能有多个slice指向同一个底层数组。如果有任一切片指向这个数组,底层数组就因处于使用状态而无法被删除。
极端情况,很小的切片指向很大底层数组,会造成空间的浪费。
map(映射)是Go语言提供的key-value
(键值对)形式的无序集合。
map的底层是Hash(哈希)表。
键值对的键有唯一性的要求,通过键来获取值和更新值。
定义形式
map[k]v
==
的比较运算 。创建map
// 方式一
m1 := make(map[string]int)
// 方式二
m2 := map[string]int{
"k1" : 11,
"k2" : 22,
}
访问元素
map[k]
, 比如val,ok = m2["k1"]
。根据key找val,如果key不存在,ok=false。
添加元素
map[key] = value
注意:key如果存在,则用value更新值。如果不存在,则添加key-value对。
删除元素
delete(map, k)
,比如delete[m2, "k2"]
注意:delete函数删除不存在的key时,不会出错。
遍历map
for k, v := range m2 {
fmt.Println(k, v)
}
map在元素赋值之前必须初始化。使用上面两种方式均可。如果只声明map,使用则会出错。
var m1 map[string]int
m1["k1"] = 12 // error: assignment to nil map
map的默认初始值是nil,未初始化的map是nil。尽管如此,未初始化的map执行删除元素、len操作、range操作或查找元素时都不会报错。但未初始化之前进行元素赋值操作会出错。
结构体定义
type Person struct {
name string
gender int
age int
}
var p1 Person
p2 := Person{"Jack", 1, 18}
p3 := Person{name:"Scott", gender:1, age:18}
注意:struct成员的可见性是通过首字母大小写控制,首字母小写仅本报可见,首字母大写则包外也可访问。
成员访问
p3.name
结构体指针
var pp *Person
注意:结构体指针必须初始化后才可以使用,因为如果仅仅声明结构体指针类型变量,其默认值初始值是nil。
pp := new(Person)
,创建指向Person结构体的指针pp。
pp.name = "Json"
,结构体指针访问成员也是直接.
运算符,这是Go的语法糖。
make和new
make函数用于slice、map和chan进行内存分配,它返回的不是指针,而是上面三个类型中的某一个类型本身。
new函数返回初始化的类型对应的指针,new函数主要用于struct初始化中,其他场景英语较少。
《快学 Go 语言》第 7 课
重学Go语言 | Slice全面讲解
Go微服务实战