go:数组和切片、可变长参数、maps、字符串、指针、结构体、方法、接口
数组和切片
数组
# 1 定义,初始化,使用 # 2 数组是值类型 数字,字符串,布尔,数组,都是值类型,真正直接存数据 切片,map,指针 引用类型,是个地址,指向了具体的值 # 3 数组长度 # 4 循环打印数组 # 5 多纬数组 # 6 数组定义并赋初值 ,把第99 赋值为1 ,其他都是0 # 数组的长度也是类型的一部分 package main import "fmt" // 1 数组是值类型,go语言中函数传参是 copy 传递,复制一份参数,传入 当参数传递,在函数中修改,不会影响原来的 //2 数组长度 func main() { //1 数组是值类型 //var a [3]int = [3]int{3, 4, 4} //test(a) //fmt.Println("外面的", a) // 会不会影响? // 2 数组长度 //var a =[3]int{1,2,3} //a :=[3]int{1,2,3} //a := [...]int{3, 4, 5, 4, 5, 6, 67} //fmt.Println(a) //fmt.Printf("%T\n", a) //fmt.Println(len(a)) // 3 循环打印数组 //a := [...]int{3, 4, 5, 4, 5, 6, 67} // 基于索引的 //for i := 0; i < len(a); i++ { // fmt.Println(a[i]) //} // range 循环,基于迭代的 //for i,_ := range a { // i是索引,v是值 // fmt.Println(i) // //fmt.Println(v) //} // 4 多纬数组 //var a [3][2]int //var a [3][2]int = [3][2]int{{2, 2}, {3, 4}} //fmt.Println(a) //fmt.Println(a[0]) //fmt.Println(a[0][1]) // 循环多维数组 //for i := 0; i < len(a); i++ { // for j := 0; j < len(a[i]); j++ { // fmt.Println(a[i][j]) // } //} //for _, v := range a { // for _, v1 := range v { // fmt.Println(v1) // } //} // 5 数组定义并赋初值 ,把第99 赋值为1 ,其他都是0 //var a [100]int8 = [100]int8{98: 1, 66: 88} //fmt.Println(a) } func test(a [3]int) { a[0] = 999 fmt.Println(a) }
切片
# slice:切片是由数组建立的一种方便、灵活且功能强大的包装。切片本身不拥有任何数据。它们只是对现有数组的【引用】 本身不存储数据,是对底层数组的引用 package main import "fmt" // 切片 func main() { //1 切片的定义 中括号中不放任何东西,是切片类型,放个数字,就是数组类型 //var a []int //只定义,没有初始化,是引用类型 是nil类型,等同于python中的None //if a == nil { // fmt.Println("我是空") //} //fmt.Println(a) // [] //fmt.Println(a[0]) // 1.1 定义并初始化 //var b [10]int //值类型,有默认值 //var a []int = b[:] // 表示a这个切片,对数组进行引用,引用了从0到最后 //fmt.Println(a) //a[8] = 88 //fmt.Println(a[8]) //fmt.Println(a) // //fmt.Println(b) // 2 切片的取值赋值 //fmt.Println(a[8]) // 3 切片的修改,会影响底层数组; 数组的修改也会影响切片 //b[0] = 11 //fmt.Println(b) //fmt.Println(a) // 4 基于数组,获得切片 //var b [10]int = [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 0} //值类型,有默认值 ////var a []int = b[0:3] // 表示a这个切片,对数组进行引用,引用了从0到最后 ////fmt.Println(a) ////a[0] = 99 ////fmt.Println(a) ////fmt.Println(b) //var a []int = b[6:9] //fmt.Println(a) //a[0] = 99 //fmt.Println(a) //fmt.Println(b) //b[0] = 88 //fmt.Println(b) // 5 切片追加值 //var b [10]int = [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 0} //var a []int = b[6:9] //a[0] = 99 //fmt.Println(b) //[1 2 3 4 5 6 99 8 9 0] //fmt.Println(a) //[99 8 9] // //a = append(a, 66) //fmt.Println(a) //[99 8 9 66] //fmt.Println(b) //[1 2 3 4 5 6 99 8 9 66] // 6 切片的长度和容量 //var b [10]int = [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 0} //var a []int = b[6:9] //fmt.Println(len(a)) // 3 //fmt.Println(cap(a)) //容量,能存多少 4 ,不是底层数组的大小,取决于切片切数组的位置 // 7 切片如果追加值超过了底层数组长度,会自动扩容 //var b [10]int = [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 0} //var a []int = b[6:9] //a[0] = 99 //fmt.Println(b) //[1 2 3 4 5 6 99 8 9 0] //fmt.Println(a) //[99 8 9] //a = append(a, 66) //fmt.Println(a) //[99 8 9 66] //fmt.Println(b) //[1 2 3 4 5 6 99 8 9 66] //fmt.Println(len(a)) //fmt.Println(cap(a)) // 超过底层数组,追加值 //a = append(a, 77) //fmt.Println(a) //[99 8 9 66 77] //fmt.Println(len(a)) // 5 //fmt.Println(cap(a)) // 8 ,超过容量,翻倍扩容(容量在1024内) //a = append(a, 11, 222, 33, 55) //fmt.Println(len(a)) // 9 //fmt.Println(cap(a)) // 16 ,超过容量,翻倍扩容(容量在1024内) // 改底层数组 //b[9] = 999 //fmt.Println(b) //fmt.Println(a) // 8 切片定义并初始化 使用数组初始化 // 使用make初始化 //var a []int // 是 nil,没有初始化 //var a []int = make([]int, 3, 4) // 要初始化 ,长度为3,容量为4 //fmt.Println(a) // [0 0 0] //fmt.Println(len(a)) //fmt.Println(cap(a)) //var a []int = make([]int, 0, 4) // 要初始化 ,长度为0,容量为4 //fmt.Println(a) // [] 有可能是nil,有可能已经初始化完成了,只有初始化完成了才能用 //fmt.Println(len(a)) //fmt.Println(cap(a)) //a = append(a, 5) //fmt.Println(a) //fmt.Println(len(a)) //1 //fmt.Println(cap(a)) // 4 // 9 切片的参数传递 引用类型,函数中修改值,会影响原来的 //var a []int = make([]int, 3, 5) ////a[1] = 99 //[0 99 0] //test2(a) //fmt.Println(a) // [99 0 0] // 10 多纬切片 切片定义并初始 //var a []int = []int{2, 3, 4, 45} //fmt.Println(a) //fmt.Println(len(a)) //fmt.Println(cap(a)) //a = append(a, 55) //fmt.Println(len(a)) //fmt.Println(cap(a)) //var a [][]string = [][]string{{"1", "3"}, {"o"}, {"5", "o", "99"}} //fmt.Println(a) var a [][]string = make([][]string, 3, 3) fmt.Println(a[2]) a[2] = make([]string, 3, 3) fmt.Println(a[2][0]) } func test2(a []int) { a[0] = 99 //fmt.Println(a) //[99 99 0] a = append(a, 33) a[0] = 88 fmt.Println(a) }
可变长参数
package main import "fmt" // 可变长参数 func main() { //test3("1", "lqz") var a []string = []string{"lqqz", "pyy"} test3(a...) // 相当于打散了传入 } func test3(a ...string) { fmt.Println(a) fmt.Printf("%T", a) fmt.Println(cap(a)) }
maps
package main import "fmt" //map func main() { //1 key -value 形式存储 定义一个map //var userInfo map[int]string // 没有初始化,它是nil,但是打印出来不是nil //fmt.Println(userInfo) //map[] //if userInfo == nil { // fmt.Println("asdfasdf") //} // 2 map 初始化 方式一 //var userInfo map[int]string = map[int]string{1: "lqz", 3: "pyy"} //fmt.Println(userInfo) //if userInfo == nil { // fmt.Println("asdfasdf") //} // 2 map 初始化 方式二 make初始化 //var userInfo map[int]string = make(map[int]string) //fmt.Println(userInfo) //map[] //if userInfo == nil { // fmt.Println("asdfasdf") //} // 3 初始化后才能取值赋值 //var userInfo map[int]string = make(map[int]string) //var userInfo map[int]string ////fmt.Println(userInfo[1]) //userInfo[1] = "pyy" //fmt.Println(userInfo) // 以后所有的引用类型,都需要初始化才能用,值类型不需要,有默认值 //4 取值赋值 //var userInfo map[string]string = make(map[string]string) //userInfo["age"] = "19" //userInfo["name"] = "lqz" //fmt.Println(userInfo) // //fmt.Println(userInfo["age"]) //// 取不存在的----》显示vaule值的默认值 //fmt.Println("--", userInfo["hobby"]) // //// 如何判断一个key在不在map中 按key取值,能返回一个布尔值,根据布尔值判断 //v, ok := userInfo["name"] //fmt.Println(v) //fmt.Println(ok) //false // //if v, ok := userInfo["name"]; ok { // fmt.Println(v) //} // 5 删除map元素 //var userInfo map[string]string = make(map[string]string) //userInfo["age"] = "19" //userInfo["name"] = "lqz" //delete(userInfo, "name") //fmt.Println(userInfo) // 6 map的长度 //var userInfo map[string]string = make(map[string]string) //fmt.Println(len(userInfo)) //userInfo["age"] = "19" //userInfo["name"] = "lqz" //fmt.Println(len(userInfo)) // 7 引用类型 var userInfo map[string]string = make(map[string]string) fmt.Println(len(userInfo)) userInfo["age"] = "19" userInfo["name"] = "lqz" test6(userInfo) fmt.Println(userInfo) } func test6(u map[string]string) { u["name"] = "pyy" fmt.Println(u) }
字符串
package main // 字符串 func main() { // 1 定义字符串 //var s = "中alqz" // 2 字符串可以按下标取值,不能改 //s[0]=98 //fmt.Println(s[0]) // 取字节,是个数字 //fmt.Println(s[3]) //fmt.Printf("%T\n", s[3]) //uint8 类型 byte 取字符才是int32也就是rune // 3 循环字符串 //for _, v := range s { // fmt.Println(string(v)) //字符 //} //for i := 0; i < len(s); i++ { // fmt.Println(string(s[i])) // 字节 //} // 4 字符串长度 //fmt.Println(len(s)) // 7 字节长度 //fmt.Println(utf8.RuneCountInString(s)) // 5 个字符 // 5 字符串是由字节或字符切片构成的 // 5.1 字符串和字节切片相互转化 //var s []byte = []byte{99, 97, 98, 'a'} //fmt.Println(s) // 字节切片,转成字符串 //fmt.Println(string(s)) //s := "abac" // 把字符串转成字节切片 //var sss []byte = []byte(s) //fmt.Println(sss) //5.2 字符切片和字符串相互转化 //var s []rune = []rune{99, 97, 98, 'a', '你', '好', 20013} //fmt.Println(string(s)) //s := "lqz中国" //fmt.Println([]rune(s)) //22269 //s := "你好中国" //fmt.Println(s) //r := []rune(s) //r[0] = 22269 //fmt.Println(string(r)) }
指针
# 指针:指针是一种存储变量内存地址的【变量】 # 指针规定 按照这个肯定每错 1 定义变量时,类型前加 * *int *string *[3]int ,表示类型,指向这个类型的指针类型 2 在变量前加 & 表示取这个变量的地址 3 在指针变量前加 * ,表示解引用,反解出内存地址指向的值
package main import "fmt" func main() { // 指针是引用类型 // 1 定义指针 //var p *int //fmt.Println(p) //<nil> // 2 定义并赋初值 //var a int = 10 //var p *int = &a //fmt.Println(p) // 3 指针的解引用 ---》 //fmt.Println(*p) // 4 取指针变量的地址 //var a int = 10 //var p *int = &a //var p1 **int = &p // 取p的地址 //fmt.Println(p) //fmt.Println(p1) //var p2 ***int = &p1 //fmt.Println(p2) //// 解引用 ////fmt.Println(*p2) //fmt.Println(**p2) //fmt.Println(***p2) // 5 向函数传递指针类型参数 //var a int = 10 ////test1(a) ////fmt.Println(a) //var p *int = &a //test2(p) //fmt.Println(a) // 6 不要向函数传递 【数组的指针】,而应该使用切片 //var a [3]int //var p *[3]int = &a //fmt.Println(p) // &[0 0 0] //test4(p) //fmt.Println(a) ////test5(p) // //test6(a[:]) // 7 数组指针:数组的指针 指针数组: 数组里面的值是指针类型 // 数组的指针 //var a=[3]int{4,5,6} //var p *[3]int = &a //var p *[3]int = &[3]int{4, 5, 6} //fmt.Println(p) //指针数组 //var p1 [3]*int //var a, b, c int = 9, 4, 6 //p1[0] = &a //p1[1] = &b //p1[2] = &c //fmt.Println(p1) //fmt.Println(*(p1[0])) // 8 指针不允许运算 //var p *[3]int = &[3]int{4, 5, 6} //fmt.Println(p) //fmt.Println(p++) } func test1(a int) { a = 99 fmt.Println(a) } func test2(p *int) { *p = 99 fmt.Println(*p) } func test3(a [3]int) { a[0] = 99 fmt.Println(a) } func test4(a *[3]int) { //(*a)[0] = 99 a[0] = 99 // 可以简写成,等同于上面 fmt.Println(a) } func test5(a *[4]int) { //(*a)[0] = 99 a[0] = 99 // 可以简写成,等同于上面 fmt.Println(a) } func test6(a []int) { a[0] = 99 fmt.Println(a) }
结构体
# go 的面向对象,go中没有面向对象的语法(class),但有面向对象概念(继承封装多态) #什么是结构体(就是类的概念:类是若干方法和属性的集合) 结构体是用户定义的【类型】,表示若干个字段(Field)的集合 package main import ( "fmt" ) // 1 定义一个结构体体,定义个人结构体 type Person struct { name string age int address string hobby Hobby } //type Hobby struct { // hobbyName string // hobbyId int //} // 9 匿名字段 type Hobby struct { string int } func main() { // 2 使用结构体。定义结构体变量 //var p Person = Person{name: "lqz", age: 19, address: "上海"} // 3 结构体属性取值,赋值 //p.name = "彭于晏" //fmt.Println(p) //fmt.Println(p.age) // 4 结构体是值类型--->不需要初始化,就有默认值,当参数传入函数中,修改,不会影响原来的,想修改原来的,要取地址 //var p Person //p.name = "lqz" //fmt.Println(p.name) // 5 匿名结构体 ---》定义在函数内部,结构体没名字---》只用一次 --->没有名字,没有type就是匿名结构体 //var cat = struct { // name string // age int //}{age: 19} //fmt.Println(cat.name) //fmt.Println(cat.age) // 6 结构体初始化 //var p Person //var p Person = Person{name: "lqz", age: 19, address: "上海"} //var p Person = Person{name: "lqz"} // 没传的就是默认值 //var p Person = Person{"lqz",19,"上海"} //var p Person = Person{"lqz"} //var p Person = Person{} // 7 结构体零值 ---》值类型,有默认值,不初始化也行 //var p Person ////fmt.Println(p) ////test7(p) ////fmt.Println(p) //test8(&p) //fmt.Println(p) // 8 通过 . 访问和修改,结构体字段 (在包内,一定要导出) //d := entity.Dog{Name: "小狗"} //fmt.Println(d.Name) //fmt.Println(d.Age) // 9 匿名字段 初始化 匿名字段,类型名就是字段名 //var h Hobby = Hobby{"篮球", 100} //var h Hobby = Hobby{string: "篮球", int: 19} //fmt.Println(h) //fmt.Println(h.string) //fmt.Println(h.int) // 10 结构体嵌套 //var p =Person{"lqz",19,"上海",Hobby{"篮球",100}} //var p = Person{name: "lqz", age: 19, hobby: Hobby{string: "乒乓球", int: 1002}} //fmt.Println(p.name) //fmt.Println(p.hobby.string) // 11 结构体嵌套+匿名字段,玩点高级的---》面向对象的继承 type Animal struct { age int name string } type Dog struct { name string Animal // 结构体嵌套+匿名字段 提升字段,Animal中的字段,可以直接在dog对象中使用---》相当于Dog继承了Animal,dog可以直接使用Animal的属性 } //var d = Dog{"小野狗", Animal{age: 19}} //fmt.Println(d.name) //fmt.Println(d.Animal.age) //// 提升字段 //fmt.Println(d.age) //d.age = 22 //fmt.Println(d.Animal.age) var d = Dog{"小野狗", Animal{age: 19, name: "动物"}} // 提升字段,有冲突,用自己的,如果要取到父类的,需要指名道姓 fmt.Println(d.name) fmt.Println(d.Animal.name) } func test7(p Person) { p.name = "xxx" fmt.Println(p) } func test8(p *Person) { //(*p).name = "xxx" p.name = "xxx" // 简写成 fmt.Println(p) }
方法
# 方法和函数? 方法可以自动传值 # 方法其实就是一个函数,在 func 这个关键字和方法名中间加入了一个特殊的接收器类型。接收器可以是结构体类型或者是非结构体类型。接收器是可以在方法的内部访问的 # 方法是绑定给结构体的,结构体有了一系列字段,又有了方法 ,那他就是类的概念了 package main import "fmt" // 方法 type Dog struct { name string age int Wife } type Wife struct { wifeName string } func (w Wife) printName() { fmt.Println(w.wifeName) } func (d Dog) printName() { fmt.Println(d.name) } // 给结构体绑定一个方法 func (d Dog) speak() { fmt.Println(d.name, "在旺旺叫") } func (d Dog) run() { fmt.Println(d.name, "在走路") } // 值类型接收器 //func (d Dog) changeName(name string) { // d.name = name // fmt.Println(d) // 改了 //} // 指针类型接收器 func (d *Dog) changeName(name string) { d.name = name fmt.Println(d) // 改了 } // 函数 // // func speak(d Dog) { // fmt.Println(d.name, "在旺旺叫") // // } //func main() { // 1 使用方法 //var dog = Dog{"小榔头", 12} //dog.speak() //dog.run() // 2 为什么有了函数还要方法 方法是绑定给结构体的,可以自动传值,函数有几个值就要传几个值 //speak(dog) //dog.speak() // 3 值类型接收器和指针类型接收器 //var dog = Dog{"小榔头", 12} //dog.changeName("小狼狗") //fmt.Println(dog.name) // 4 想修改外部的对象使用指针类型接收器,如果值很占内存,也可以使用指针类型接收器----》python中的self其实就是指针 // 5 匿名字段的方法,同理,也会提升 //var dog = Dog{"小榔头", 18, Wife{"小母狗"}} //dog.printName() //dog.Wife.printName() // 6 在方法中使用值接收器 与 在函数中使用值参数 //var cat =Cat{"小野猫"} //cat.speak() //speak(cat) // 使用指针 //var cat *Cat = &Cat{"小野猫"} //cat.speak() // 值可以调用,指针也可以调用 ////speak(cat) // 函数,参数是什么类型,就必须传什么类型 //cat.changeName("xx") // 方法中能不能改值,跟谁调用没关系,跟接收器类型有关系,只要是指针类型接收器,都能改,只要是值类型接收器,都不改 //fmt.Println(cat) // 7 在方法中使用指针接收器 与 在函数中使用指针参数 //var cat = Cat{"小野猫"} //cat.changeName("xxx") //fmt.Println(cat) //changeName(&cat,"xxx") //var cat = &Cat{"小野猫"} //cat.changeName("xxx") //fmt.Println(cat) //changeName(cat, "xxx") // 1 方法可以值来调,也可以指针来调用 // 2 如果想改结构体对象的属性,必须用指针类型接收器来改 // 8 非结构体上的方法 //} // 6 在方法中使用值接收器 与 在函数中使用值参数 //type Cat struct { // name string //} // //func (c Cat) speak() { // fmt.Println(c.name, "喵喵叫") //} //func (c Cat) changeName(name string) { // c.name = name //} //func speak(c Cat) { // fmt.Println(c.name, "喵喵叫") //} // 7 在方法中使用指针接收器 与 在函数中使用指针参数 type Cat struct { name string } func (c *Cat) changeName(name string) { c.name = name } func changeName(c *Cat, name string) { c.name = name } // 8 非结构体上的方法 int 内置的类型是不能绑定方法的,但是自定义的类型,可以绑定方法 //var a int =10 //a.Add() //fmt.Println(a) // 11 type MyInt int func (i *MyInt) Add() { (*i)++ } func main() { var i MyInt = 10 i.Add() i.Add() i.Add() i.Add() fmt.Println(i) }
接口
# 在面向对象的领域里,接口一般这样定义:接口定义一个对象的行为。接口只指定了对象应该做什么,至于如何实现这个行为(即实现细节),则由对象本身去确定。 class Animal: def sepak(): pass def run(): pass class Dog(Animal): def sepak(): print('ss') def run(): print('ss') class Cat(Animal): def sepak(): print('ss') def run(): print('ss') #在 Go 语言中,接口就是方法的集合。当一个类型实现了接口中的所有方法,我们称它实现了该接口。这与面向对象编程(OOP)的说法很类似。接口指定了一个类型应该具有的方法,并由该类型决定如何实现这些方法 # GO也是鸭子类型 重载 :只用名字,其他可以变 重写:必须和原来一样,传参数,有返回值 package main import ( "fmt" ) // 接口 // 1 定义一个接口 type Duck interface { speak() // 没有具体实现,只是声明 run() } // 2 写一个结构体,实现接口 type PDuck struct { age int } func (d PDuck) speak() { fmt.Println("普通鸭子嘎嘎叫") } func (d PDuck) run() { fmt.Println("普通鸭子歪歪扭扭走路") } type TDuck struct { name string age int wife string } func (d TDuck) speak() { fmt.Println("唐老鸭说人话") } func (d TDuck) run() { fmt.Println("唐老鸭人走路") } func main() { // 1 多态:不考虑对象具体类型的情况下使用对象,把对象当成接口类型,只要实现了接口,就直接使用 //var p = PDuck{1} //var t = TDuck{"唐老鸭", 13, "女唐老鸭"} //test9(p) //test9(t) // 2 接口也是一种类型 //var ddd Duck //ddd = PDuck{1} //ddd = TDuck{"唐老鸭", 13, "女唐老鸭"} //fmt.Println(ddd) // 3 一旦把具体对象赋值给接口类型---》只能使用接口中的方法, 其他方法或属性,都取出出来了 //var ddd Duck //ddd = TDuck{"唐老鸭", 13, "女唐老鸭"} //fmt.Println(ddd.name) // 4 把接口类型, 转换成具体类型 类型断言 //var ddd Duck //var t = TDuck{"唐老鸭", 13, "女唐老鸭"} ////var t = ddd.(TDuck) //ddd接口类型.(具体类型) ---》把这个接口类型转成具体类型 ////fmt.Println(t.age) //test9(t) // 5 直接强转可能会报错,类型选择 //var t = TDuck{"唐老鸭", 13, "女唐老鸭"} //var p = PDuck{1} //test10(p) // 6 空接口类型 所有类型都实现了空接口 //var a int =10 //var t = TDuck{"唐老鸭", 13, "女唐老鸭"} //test11(a) //test11("lqz") //test11(t) // 7 匿名空接口 fmt.Println() } // func test9(p Duck) { // p.speak() // // p 到底是普通鸭子呢,还是唐老鸭 // //var t = p.(PDuck) // 会报错 // var t = p.(TDuck) // 会报错 // fmt.Println(t.name) // } //func test10(p Duck) { // switch v := p.(type) { // case PDuck: // fmt.Println(v.age) // case TDuck: // fmt.Println(v.name) // } //} //type Empty interface { //} // //func test11(p Empty) { // fmt.Println(p) //} // 匿名,空接口 func test12(p interface{}) { }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步