复合数据类型
数组
var a [3]int //[3]int 是一个数组类型,即使是同一type,但不同长度的数组,类型也不相同
for _, v := range a {
fmt.Println(v) // 0 0 0 数字会使用类型的默认值初始化
}
var b [3]int = [3]int{1, 2, 3} // 声明时初始化,数组的长度必须是常量表达式
var intArr [5]int
intArr = [5]int{1,2,3,4,5}
// 先声明后再初始化,即使已经声明过了,一次性初始化还是要用 [5]int{} 这样好像略为死板,但是这也提示了最好声明时直接初始化,否则使用 intArr[i] = val
c := [...]int{1, 2, 3, 4, 5} //根据初始化的值动态决定长度
fmt.Println(len(b), len(c)) // 3 5
// c = b 会报错,数组长度不同类型也不同
d := [...]int{99: 99} //根据索引位置动态赋值
fmt.Println(d[99]+1 == len(d)) // true
fmt.Println(d[0]) // 0
fmt.Println(a == b) //相同数组类型间可以比较,如果数组元素都相同,返回 true
func changeArr(arr [3]int) [3]int {
for i, _ := range arr {
arr[i] = i + 1
}
return arr
}
e := changeArr(a)
fmt.Println(a == e) // false 数组赋值是值传递,即,数组并非引用类型
f := [3]int{1,2,3}
p1 := &f
f = [3]int{4,5,6} //单纯的赋值操作
fmt.Println(&f == p1) // true
数组的定长类型和值传递,使得我们并不常用它,而是选择更好用的 slice
slice 切片
- 切片:相同类型元素的可变长度序列,[]T
- slice的底层还是数组,它有三个属性
- 指针:指向底层数组,多个切片可以指向同一个底层数组
- 长度:切片的长度,len(slice)得到长度
- 容量:从slice的起始位置,到最底层数组最后一个元素的长度,cap(slice)得到容量
- 由上面的定义可以很明显的看出,slice 是引用类型
- slice := s[i:j],0 <= i <= j <= cap(s), 省略 i 时,默认 i 为 0,省略 j 时,j 为 len(s)
- j <= cap(s) 是个很有意思的限制,它使得 s 向后的长度永远能的指向最底层的数组,但是超过了 cap(s) 则会宕机(非把死机说的这么高雅,我查了半天才知道这个字读 dang)
- 因为 slice 是引用类型,所以不能用 == 比较两个 slice 是否有相同的元素,这也使得 slice 无法作为 map 的 key,可以使用 bytes.Equal()比较 []byte,其他的需要自己写函数了
intArr := [10]int{5:0}
intSlice := intArr[2:5]
fmt.Println("len:",len(intSlice)) // len: 3
fmt.Println("cap:",cap(intSlice)) // cap: 8
这里有一个很容易让新手搞乱的地方(至少我曾乱过…)
- 声明数组:[10]int{}
- 得到切片:intArr[5:10]
我曾把切片写成 [5:10]intArr
golang 选择把类型的声明放在后面,这样就避免了前缀太多而造成识别命名困难,因为大多时候是想先找到名再看类型
int (*(*fp)(int (*)(int, int), int))(int, int)
//c 代码,至少我一眼难以看出函数名是 fp
public static List<Map<String, String>> funcName () {}
//java 代码,其实大多时候我们更想先找到函数名或变量名,再去看他便秘一样的前缀
操作则在变量后面放着,而切片是对数组的操作,所以是 intArr[5:10]
接着看切片的例子
func reverse(s []int) {
for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 {
s[i], s[j] = s[j], s[i]
}
}
arr := [...]int{1,2,3,4,5,6,7}
// reverse(arr) 错误,因为数组类型并不是slice类型
s := arr[:]
//或者直接可以写为 s := []int{1,2,3}
reverse(s)
fmt.Println(s)
str := "kanggege康搁搁"
fmt.Println(str[8])
strS := str[:]
fmt.Println(strS) //"kanggege康搁搁" 还是字符串,并不能算作 slice 类型
//fmt.Printf(strS == nil) 错误,对字符串做 slice 操作,得到的还是字符串,字符串不是引用类型不能和 nil 比较
fmt.Println(s == nil) // slice 唯一可以做的比较
fmt.Println(reflect.TypeOf(strS[13])) //uint8
slice 操作
make
s := make([]int,5,10)
for i := range s{
print(i) //01234
}
使用 make 创建 slice 的本质还是创建一个匿名数组,并返回一个指向该匿名数组的 slice
append
func appendInt(x []int, y int) []int {
var z []int
zlen := len(x) + 1
if zlen <= cap(x) {
z = x[:zlen]
} else {
zcap := zlen
if zcap < 2*len(x) {
zcap = 2 * len(x)
}
z = make([]int, zlen, zcap)
copy(z, x)
}
z[len(x)] = y
return z
}
s := make([]int, 0)
s = append(s,1,2,3,4,5)
s2 := []int{6,7,8,9}
s = append(s,s2...)
print(len(s)) // 9
append([]Type, …Type)
map[k]v
//下面两种初始化方式相同
m1 := make(map[string]int)
m1["index1"] = 1
m1["index2"] = 2
m1["index3"] = 3
m2 := map[string]int{
"index1":1,
"index2":2,
}
delete(m2,"index1")
fmt.Println(len(m2)) // 1
fmt.Printf("m2[index3]=%v\n",m2["index3"]) // 访问不存在的下标会得到默认值
//_ = &m1["index1"] map 中的元素可能会因为数量的增长而重新分配地址,所以获取元素地址无任何意义
for key,val := range m1{
fmt.Printf("key:%v, val:%v\n",key,val)
//没有固定的迭代顺序,据说是为了安全,emmm,不太懂
}
//如果想要按一定顺序迭代,可以先对 key 排列,再得到值
keys := make([]string,0)
for key := range m1{
keys = append(keys, key)
}
sort.Strings(keys)
for _,key := range keys{
fmt.Printf("m1[%v]=%v",key,m1[key])
}
//因为 map 是引用类型,所以除了和 nil 做比较无法和其他的 map 直接做比较,可以迭代比较
func mapEqual(m1, m2 map[string]int) bool {
if len(m1) != len(m2) {
return false
}
for key := range m1 {
if _, ok := m2[key]; !(ok && (m1[key] == m2[key])) {
return false
}
}
return true
}
结构体
- 结构体是由基本类型组成的聚集数据类型
- 复制传递
- 通过首字符是否大写控制是否导出
- 结构体指针也可以直接使用结构体成员,且结构体是复制传递,所以经常使用结构体的指针
type Person struct {
Name string //public
Age int
Sex string
person *Person // 包内可访问
}
type Student struct {
Person //不能定义两个相同的匿名成员,否则会冲突
Id int
Score float64
}
func main() {
p1 := &Person{Name:"kanggege"}
fmt.Println(p1.Name)
p := Person{"kanggege",20,"man",p1}
s := Student{Person:p,Id:20,}
fmt.Println(p.person) //&{kanggege 0 <nil>}
fmt.Println(s.person) //&{kanggege 0 <nil>}
fmt.Println(s.Name) //dyk
}
JSON
type Movie struct{
Title string
Year int `json:"year"` //起别名,year
Color bool `json:",omitempty"` //如果未赋值,则不会转换,注意是未赋值,只要赋值了,哪怕和数据零值相同,都会转换
Actors []string
}
mv := Movie{Title:"success",Year:10}
mvJson,_ := json.MarshalIndent(mv,""," ")
//使用 json.Marshal() 将其转换为 json 字符串
//使用 json.Unmarshal() 将 json 转换为 go 类型,转换时会忽略 json 数据的大小写
fmt.Printf("%s",mvJson)
//{
// "Title": "success",
// "year": 10,
// "Actors": null
//}
// 字段被本包忽略
Field int `json:"-"`
// 字段在json里的键为"myName"
Field int `json:"myName"`
// 字段在json里的键为"myName"且如果字段为空值将在对象中省略掉
Field int `json:"myName,omitempty"`
// 字段在json里的键为"Field"(默认值),但如果字段为空值会跳过;注意前导的逗号
Field int `json:",omitempty"`
文本和 html 模板
补充一个知识点:
很多类型都定义了自己的 String 方法,当使用 fmt 包中的方法输出时,就是调用该类型的 String 方法
type T struct {
Name string
}
func (this T) String() string{
return "do string"
}
func main() {
t := T{Name:"dyk"}
fmt.Println(t) // do string
}
text/template 和 html/template 中的方法类似,但是 html/template 会将一切传入变量的字符串中特殊字符进行同义转义,避免被视作 html/js 解析,这样可以减少很多安全问题,如果想要对传入字符串添加信任,可以声明为 template.HTML
template.Must(template.New("tem").Funcs(template.FuncMap{"func",func}).Parse(tem)).Execute(os.Stdin,params)