文章目录
- Hello,World
- 变量、指针及赋值
- 变量和常量
- 指针
- 赋值
- 选择和循环
- 选择
- 循环
- 基本数据类型
- 整型
- 整型的取值范围
- 运算符
- 二元运算符
- 一元运算符
- 浮点型
- 复数和布尔类型
- 字符串
- rune
- Unicode和UTF-8
- 按字节访问
- 按字符rune访问
- 特点
- 数组
- 数组的定义
- 1. 使用默认初始值
- 2. 定义并初始化
- 3. 省略数组长度
- 4. 索引赋值初始化
- 数组特点
- 数组是默认按值传递
- slice
- 原型
- 基本操作
- append
- slice的增长过程
- copy
- 其他
- map
- 基本操作
- 特点
- struct
- 参考
Hello,World
package main
import "fmt"
func main(){
fmt.Println("Hwllo,World!")
}
- 第 1 1 1行是包的声明。本例中是main包,main包是每个项目的入口。
- 第 3 3 3行,使用g键字import导入Go语言提供的标准包fmt。
- 第 5 5 5行定义了一个函数,func是定义函数的关键字。
- 第 6 6 6行使用fmt的Println函数输出字符串Hello,World!
注意:Go语言每行代码行尾是不需要加分号;
的,编译器会把换行符解析成分号,不能把函数的左扩号单独成行。
变量、指针及赋值
变量和常量
- 变量声明使用
var
关键字 - 常量声明使用
const
关键字 - 变量的定义形式:
var name 【类型】 = 【表达式】
注意:使用过程中,"类型"和"表达式"可以省略一个,但不能同时省略。Go语言能进行自动推导,根据类型推默认表达式,也能根据表达式推出类型。
类型默认值
- 数字类型默认为
0
- 布尔类型默认为
false
- 字符串类型默认为
""
- 接口、slice、指针、map、通道、函数等类型默认值为
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") }
循环
- Go语言只提供
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个字节。
-
字符串索引访问是按字节访问的,而不是字符。
rune
-
unicode
通常用4个字节来表示,对应Go语言的字符rune
占4个字节 -
rune
类型是一个衍生类型,在内存里面使用int32
类型的4各字节存储type rune int32
Unicode和UTF-8
-
Unicode支持超过一百种的语言和十万字符的码点。
-
UTF-8是Unicode标准的一种实现。
-
UTF-8特点:
- 字符长度可变,长度1到4字节不等。
- 第一个bit为0,那么长度为1字节,使用剩余7位存放字符,正好能覆盖ASCII码字符集。
- 前两位是10,则表示长度为两个字节,第二个字节以0开头。
- 对于三个字节的UTF-8码。这三个字节分别是110、10和0。
按字节访问
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
按字符rune访问
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
*/
数组
- 在Go语言中,数组的使用频率并不高,因为数组长度不可变,很多时候都是使用slice(切片)。slice比数组更灵活,长度是可变的。
数组的定义
1. 使用默认初始值
var a [3]int
- 定义了长度为3的整型数组,三个元素都是默认初始值0
2. 定义并初始化
var b [3]int = [3]int{1, 2, 3}
var b = [3]int{1, 2, 3}
3. 省略数组长度
c := [...]int{1, 2, 3}
- 数组长度由后面元素的个数决定。这里数组的长度为3
4. 索引赋值初始化
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
类型的变量会报错。- Go语言数组长度是数组的一部分,并非向C语言那样只存储数组的地址
- 在分配内存底层空间时,数组的元素是紧挨着分配到固定位置的,这也是数组长度不可变的原因。
数组是默认按值传递
- Go语言数组是传递值的,不同于其他语言的数组都是传递引用的。
- 值传递涉及数组的拷贝,会浪费性能,但形参数组的修改不会影响原数组。
给出一个实例,修改数组的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
}
slice
- slice(切片)是一个拥有相同类型元素的可变长序列,且slice定义和数组的定义很像。
原型
type SliceHeader struct {
Data uintptr // 指针
Len int // 长度
Cap int // 容量
}
说明
- 指针指向slice开始访问的第一个元素
- 长度是切片的长度,及元素个数
- 容量是切片能访问的最大元素个数。slice的容量大于等于silice的长度
切片的底层是数组,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}
- 左边界i不写,默认是0。右边界j不写,默认是切片的容量cap(slice)。
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默认是引用传递
append
- slice的长度是动态的。append可以在原来的切片上插入元素,可以在开头、结尾、指定位置插入元素或其他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的索引为1的位置插入
ss = append(ss[:1], append(a[:], ss[1:]...)...)
slice的增长过程
切片的底层是数组,如果使用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
*/
切片变换过程:
copy
- 切片元素的赋值可利用copy函数
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
-
map(映射)是Go语言提供的
key-value
(键值对)形式的无序集合。 -
map的底层是Hash(哈希)表。
-
键值对的键有唯一性的要求,通过键来获取值和更新值。
定义形式
map[k]v
- k是键,同一mao中所有的key是同一数据类型,而且必须能进行
==
的比较运算 。 - boll类型作为k并不灵活,而浮点型作为k,可能会因为精度问题而导致异常。
基本操作
创建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操作或查找元素时都不会报错。但未初始化之前进行元素赋值操作会出错。
struct
- struct是复合类型,而非引用类型。复合类型是值传递,而引用类型是引用传递。
结构体定义
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微服务实战