在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
1.什么是结构体结构体是用户定义的类型,表示若干个字段(Field)的集合。有时应该把数据整合在一起,而不是让这些数据没有联系。这种情况下可以使用结构体。 例如,一个职员有 总的来说:结构体就是一系列属性的集合
2.结构体语法语法 // type关键字 结构体名字 struct{} 一个基本的结构体,只包含属性 type Person struct { name string // 两个类型一样可以写一行 age,sex int }
3.结构体定义定义时没有初始化查看他的空值 // 定义时没有初始化查看他的空值
var person Person
fmt.Println(person) // 输出:{ 0 0},他的空值是结构体属性的空值
结构体的空值是他每个字段的空值 由此可得,结构体是值引用类型,修改结构体的属性不会影响原值
定义并初始化的时候有两种传参方式 // 定义并初始化的时候有两种传参方式,他们的不同 var person Person = Person{name:"sxc"} // 关键字传参,可以传指定数量的 var person Person = Person{"sxc",18,1} // 位置传参,所有的参数都必须传,并且传递位置固定 fmt.Println(person.name) 关键字传参可以传任意数量的,位置传参必须都传
4.匿名结构体匿名结构体:在main函数内部定义,只使用一次,可以用来存储经常使用的变量 // 分别定义两个变量并使用他们 name := "sxc" age := 18 fmt.Println(name,age) 当使用多次时,麻烦且不直观 //可以使用匿名结构体集成到一起,这样显示的更直观 student := struct { name string age int }{} student.name = "sxc" student.age = 18 fmt.Println(student.name,student.age)
5.结构体指针Go语言帮我们做好处理,使用指针也可以点出对应的字段 // 结构体的指针 var person *Person = &Person{"sxc",18,1} fmt.Println(person) // 输出&{sxc 18 1},直观的显示 fmt.Println((*person).name) // 反解之后可以输出属性值 fmt.Println(person.name) // go帮我们做好处理,可以直接使用指针.属性的方式输出
6.匿名字段匿名字段,类型名当做字段名,当我们创建结构体时,字段可以只有类型,而没有字段名。这样的字段称为匿名字段(Anonymous Field)。 // 匿名字段 type Test struct { string // 类型名当做字段名 int } 匿名字段的使用 // 匿名字段,类型名当做字段名 var test111 Test = Test{"hello",15} // 位置传参 var test123 Test = Test{string:"hello",int:15} // 关键字就是类型名 fmt.Println(test111.string) fmt.Println(test123.string)
7.结构体嵌套结构体的字段有可能也是一个结构体。这样的结构体称为嵌套结构体。 type Person struct { name string // 两个类型一样可以写一行 age,sex int //hobby Hobby // 结构体嵌套 } // 定义一个Hobby结构体,在Person结构体中使用 type Hobby struct { id int hobbyname string } 定义和使用 // 结构体嵌套 var person Person = Person{name:"sxc",hobby:Hobby{id:5,hobbyname:"sing"}} // 结构体嵌套的定义 fmt.Println(person.hobby.hobbyname) // 结构体嵌套的使用
8.结构体嵌套+匿名字段(变量的提升)type Person struct { name string // 两个类型一样可以写一行 age,sex int Hobby // 结构体嵌套+匿名字段 } // 定义一个Hobby结构体,在Person结构体中使用 type Hobby struct { id int //name string // 和上层结构体有同名字段 hobbyname string } 提升变量 // 结构体嵌套+匿名字段(用作变量提升) var person Person = Person{name:"sxc",Hobby:Hobby{id:5,hobbyname:"sing"}} fmt.Println(person.Hobby.hobbyname) fmt.Println(person.hobbyname) // 使用匿名字段后,我们可以在person这层就能直接点出Hobby的字段 fmt.Println(person.Hobby.name) // 注意当两个结构体中有同名字段时,内部结构体的字段不会提升 类似于面向对象的继承,子类可以使用父类的属性
9.结构体相等性(Structs Equality)结构体是值类型。如果它的每一个字段都是可比较的,则该结构体也是可比较的。如果两个结构体变量的对应字段相等,则这两个变量也是相等的。 package main import ( "fmt" ) type name struct { firstName string lastName string } func main() { name1 := name{"Steve", "Jobs"} name2 := name{"Steve", "Jobs"} if name1 == name2 { fmt.Println("name1 and name2 are equal") } else { fmt.Println("name1 and name2 are not equal") } name3 := name{firstName:"Steve", lastName:"Jobs"} name4 := name{} name4.firstName = "Steve" if name3 == name4 { fmt.Println("name3 and name4 are equal") } else { fmt.Println("name3 and name4 are not equal") } } 在上面的代码中,结构体类型 上面代码中 name1 and name2 are equal name3 and name4 are not equal 如果结构体包含不可比较的字段,则结构体变量也不可比较。 package main import ( "fmt" ) type image struct { data map[int]int } func main() { image1 := image{data: map[int]int{ 0: 155, }} image2 := image{data: map[int]int{ 0: 155, }} if image1 == image2 { fmt.Println("image1 and image2 are equal") } } 在上面代码中,结构体类型
二.方法1.什么是方法方法其实就是一个函数,在 下面就是创建一个方法的语法。 func (t Type) methodName(parameter list) {
}
2.给结构体绑定方法// 给Person结构体加一个打印名字的方法 func (p *Person)changeName(a string) { (*p).name = a fmt.Println(p) } person结构体 type Person struct {
name string
age,sex int
Hobby
}
3.为什么我们已经有函数了还需要方法呢?
有了方法之后,我们就能使用结构体生成的对象点出方法来 // 可以用结构体生成的对象点出来使用 var p = Person{name:"sxc",Hobby:Hobby{hobbyname:"sing"}} p.printName() 这样结构体中拥有自己的字段属性,并且我们给他增加了方法,这样这个结构体就类似于面向对象的类,可以通过生成的对象点出来属性和方法
4.值接收器和指针接收器,两者何时使用我们定义一个改名的方法 // 给Person结构体加一个打印名字的方法 func (p *Person)changeName(a string) { (*p).name = a fmt.Println(p) } 使用值和指针接收器调用该方法 // 值和指针类型都可以调方法 (&p).changeName("zzj") // 我们可以使用指针来调用该方法 p.changeName("zzj") fmt.Println(p.name) // 值调用时在方法内部改变了,但因为结构体是一个值类型,故没有真正的修改,指针调用时修改的是指针对应的内存地址,故真正的修改了 故: 当操作不需影响到结构体时使用值接收器 当操作需要影响到结构体时使用指针接收器
5.方法或函数,使用值或指针a.在方法中使用值接收器 b.在函数中使用值参数 c.在方法中使用指针接收器 d.在函数中使用指针参数 定义四个对应的方法或函数 // 方法中使用值接收器 func (p Person)printName1 () { fmt.Println(p.name) } // 方法中使用指针接收器 func (p *Person)printName2 () { fmt.Println(p.name) } // 函数中使用值参数 func printName3 (p Person) { fmt.Println(p.name) } // 函数中使用指针参数 func printName4 (p *Person) { fmt.Println(p.name) } 各自调用上述方法或函数 //调用值接收器方法 p.printName1() //调用指针接收器方法 p.printName2() //调用值参数函数 printName3(p) //调用指针参数函数 printName4(p) 得出结论: 从上面四个不同的方法或函数中可得,值或者指针接收器都能调值或者指针方法 而函数指定传什么参数就需要传什么参数
6.给非结构体定义方法首先尝试给int类型定义方法 //给非结构体定义方法 func (a *int)printNum () { *a++ } 发现int等基本类型都不支持自定义方法 我们使用起别名的方法 type MyInt int // 给int起别名 在给MyInt定义方法 func (a *MyInt) addNum () MyInt{ // 由于MyInt是值类型,故我们需要修改他的指针才能真正的修改值 *a++ return *a } 调用查看结果 var a MyInt = MyInt(6) // 初始化生成对象 fmt.Println(a.addNum()) // 调用add方法 fmt.Println(a) 两次打印的结果一致,故修改成功
三.接口1.什么是接口在面向对象的领域里,接口一般这样定义:接口定义一个对象的行为。接口只指定了对象应该做什么,至于如何实现这个行为(即实现细节),则由对象本身去确定。 在 Go 语言中,接口就是方法签名(Method Signature)的集合。当一个类型定义了接口中的所有方法,我们称它实现了该接口。这与面向对象编程(OOP)的说法很类似。接口指定了一个类型应该具有的方法,并由该类型决定如何实现这些方法。 简单来说:接口就是一系列方法的集合
2.接口的声明与实现接口的语法 // 接口的语法
type 接口名 interface {
方法一
方法二
}
声明一个接口 // 定义一个鸭子接口
type Duck interface {
run()
speak()
}
接口的实现:只要在结构体中实现了接口中所有的方法就是实现了接口 首先定义一个结构体 // 定义高级鸭子结构体
type GDuck struct {
name string
age int
wife bool
}
然后实现接口中的方法 // 实现接口 func (p PDuck)run(){ fmt.Println("我是普通鸭子,我的名字叫",p.name) } func (p PDuck)speak(){ fmt.Println("我是普通鸭子,我嘎嘎叫") }
3.类型断言我们需要不同的结构体实现接口中声明的speak方法 // 两种鸭子都要实现该方法,所以需要给鸭子这个接口类型写函数
func speak(d Duck){
d.speak()
}
这样只要调用这个函数,不同的鸭子类型都可以实现speak这个方法 但是当我们想要使用每个鸭子的具体的私有的属性时就不能完成了 这时候我们可以使用类型断言,我们断言他是某个类型的值 func speak(d Duck){ a := d.(GDuck) // 断言他是高级鸭子,这样就能使用高级鸭子的属性了 fmt.Println(a.name) a.speak() } 但是这样断言之后又出现了问题,如果是其他类型使用该函数,断言错误,就不能使用这个函数了 我们可以使用类型选择(Type Switch)来帮助我们完成断言 类型选择用于将接口的具体类型与很多 case 语句所指定的类型进行比较。它与一般的 switch 语句类似。唯一的区别在于类型选择指定的是类型,而一般的 switch 指定的是值。 // 上面这种方法虽然能使用某个鸭子的属性,但是只能使用具体的那一个鸭子,我们可以使用switch func speak(d Duck){ switch a:=d.(type) { case PDuck: fmt.Println(a.name) a.speak() case GDuck: fmt.Println(a.wife) a.speak() } } // 类型断言,使用switch
4.空接口没有包含方法的接口称为空接口。空接口表示为 package main import "fmt" // 空接口:一个方法都没有的接口 // 任何类型都可以使用该接口 type Empty interface { } func main() { var a = 5 var b = "sxc" var c = map[int]string{3:"sxc"} d := 5.95 //test(a) //test(b) Mytype(a) Mytype(b) Mytype(c) Mytype(d) var e Empty = 6 // 所有类型都赋值给了空接口类型 fmt.Println(e) } func test(e Empty){ fmt.Println(e) } 匿名空接口,可以接收任意类型的数据 func Mytype(e interface{}){ switch a:=e.(type) { case int: fmt.Println(a,"我是int类型") case string: fmt.Println(a,"我是sting类型") case map[int]string: fmt.Println(a,"我是map[int]string类型") default: fmt.Println(a,"不知道是什么类型") } }
5.指针接受者与值接受者在接口(一)上的所有示例中,我们都是使用值接受者(Value Receiver)来实现接口的。我们同样可以使用指针接受者(Pointer Receiver)来实现接口。只不过在用指针接受者实现接口时,还有一些细节需要注意。 package main import "fmt" type Describer interface { Describe() } type Person struct { name string age int } func (p Person) Describe() { // 使用值接受者实现 fmt.Printf("%s is %d years old\n", p.name, p.age) } type Address struct { state string country string } func (a *Address) Describe() { // 使用指针接受者实现 fmt.Printf("State %s Country %s", a.state, a.country) } func main() { var d1 Describer p1 := Person{"Sam", 25} d1 = p1 d1.Describe() p2 := Person{"James", 32} d1 = &p2 d1.Describe() var d2 Describer a := Address{"Washington", "USA"} /* 如果下面一行取消注释会导致编译错误: cannot use a (type Address) as type Describer in assignment: Address does not implement Describer (Describe method has pointer receiver) */ //d2 = a d2 = &a // 这是合法的 // 因为在第 22 行,Address 类型的指针实现了 Describer 接口 d2.Describe() } 在上面程序中的第 13 行,结构体 我们在讨论方法的时候就已经提到过,使用值接受者声明的方法,既可以用值来调用,也能用指针调用。不管是一个值,还是一个可以解引用的指针,调用这样的方法都是合法的。
接下来在第 32 行, 在 22 行,结构体 在上面程序里,如果去掉第 45 行的注释,我们会得到编译错误: 其原因是:对于使用指针接受者的方法,用一个指针或者一个可取得地址的值来调用都是合法的。但接口中存储的具体值(Concrete Value)并不能取到地址,因此在第 45 行,对于编译器无法自动获取 a 的地址,于是程序报错。 第 47 行就可以成功运行,因为我们将 程序的其他部分不言而喻。该程序会打印: Sam is 25 years old James is 32 years old State Washington Country USA
6.实现多个接口类型可以实现多个接口。 package main import ( "fmt" ) type SalaryCalculator interface { DisplaySalary() } type LeaveCalculator interface { CalculateLeavesLeft() int } type Employee struct { firstName string lastName string basicPay int pf int totalLeaves int leavesTaken int } func (e Employee) DisplaySalary() { fmt.Printf("%s %s has salary $%d", e.firstName, e.lastName, (e.basicPay + e.pf)) } func (e Employee) CalculateLeavesLeft() int { return e.totalLeaves - e.leavesTaken } func main() { e := Employee { firstName: "Naveen", lastName: "Ramanathan", basicPay: 5000, pf: 200, totalLeaves: 30, leavesTaken: 5, } var s SalaryCalculator = e s.DisplaySalary() var l LeaveCalculator = e fmt.Println("\nLeaves left =", l.CalculateLeavesLeft()) } 上述程序在第 7 行和第 11 行分别声明了两个接口: 第 15 行定义了结构体 第 41 行,我们把 该程序会输出: Naveen Ramanathan has salary $5200
Leaves left = 25
7.接口的嵌套尽管 Go 语言没有提供继承机制,但可以通过嵌套其他的接口,创建一个新接口。 package main import ( "fmt" ) type SalaryCalculator interface { DisplaySalary() } type LeaveCalculator interface { CalculateLeavesLeft() int } type EmployeeOperations interface { SalaryCalculator LeaveCalculator } type Employee struct { firstName string lastName string basicPay int pf int totalLeaves int leavesTaken int } func (e Employee) DisplaySalary() { fmt.Printf("%s %s has salary $%d", e.firstName, e.lastName, (e.basicPay + e.pf)) } func (e Employee) CalculateLeavesLeft() int { return e.totalLeaves - e.leavesTaken } func main() { e := Employee { firstName: "Naveen", lastName: "Ramanathan", basicPay: 5000, pf: 200, totalLeaves: 30, leavesTaken: 5, } var empOp EmployeeOperations = e empOp.DisplaySalary() fmt.Println("\nLeaves left =", empOp.CalculateLeavesLeft()) } 在上述程序的第 15 行,我们创建了一个新的接口 如果一个类型定义了 在第 29 行和第 33 行,由于 在 46 行, 该程序输出: Naveen Ramanathan has salary $5200
Leaves left = 25
8.接口的零值接口的零值是 package main import "fmt" type Describer interface { Describe() } func main() { var d1 Describer if d1 == nil { fmt.Printf("d1 is nil and has type %T value %v\n", d1, d1) } } 上面程序里的 d1 is nil and has type <nil> value <nil> 对于值为 package main
type Describer interface {
Describe()
}
func main() {
var d1 Describer
d1.Describe()
}
在上述程序中,
四.异常处理和错误处理1.异常处理异常处理需要知道的三个参数 // 异常处理 // defer:无论如何都会在最后执行 // panic:主动抛出异常 // recover:恢复执行 代码示例 package main import "fmt" func main() { t1() t2() t3() } func t1() { fmt.Println("t1") } func t2() { defer func() { if a:= recover();a!=nil{ // a是错误信息 fmt.Println(a) // 打印错误信息 } // finally最后都会执行的话 fmt.Println("无论如何都会在最后执行") }() fmt.Println("t2") panic("主动抛出异常") //var a = []int{3,3} //fmt.Println(a[4]) // 取不到值会报错 fmt.Println("异常后面的信息") } func t3() { fmt.Println("t3") } 故最后异常处理我们只需要写 异常处理范本 defer func() { if a:= recover();a!=nil{ // a是错误信息 fmt.Println(a) // 打印错误信息 } // finally最后都会执行的话 fmt.Println("无论如何都会在最后执行") }()
2.错误处理错误表示程序中出现了异常情况。比如当我们试图打开一个文件时,文件系统里却并没有这个文件。这就是异常情况,它用一个错误来表示。 代码示例 package main import ( "errors" "fmt" ) func main() { a,err := circle(-10) if err!=nil{ fmt.Println(err) } fmt.Println(a) } func circle(a int) (int, error){ if a < 0 { return 0,errors.New("出错了") // 出错返回正确类型的空值 } return 100,nil } 1.我们只需要在错误的情况中处理错误,并返回需要返回类型的零值和错误信息 2.在主代码中 判断传回来的err值是否为nil,如果不为nil就说明有错误,处理错误 如果为nil就表明没有错误,继续执行代码 105 在上述程序中, |
请发表评论