当前位置: 首页 > article >正文

Golang--面向对象

Golang语言面向对象编程说明:

  • Golang也支持面向对象编程(OOP),但是和传统的面向对象编程有区别,并不是纯粹的面向对象语言。所以我们说Golang支持面向对象编程特性是比较准确的。
  • Golang没有类(class),Go语言的结构体(struct)和其它编程语言的类(class)有同等的地位,可以理解Golang是基于struct来实现OOP特性的。
  • Golang面向对象编程非常简洁,去掉了传统OOP语言的方法重载、构造函数和析构函数、隐藏的this指针等等
  • Golang仍然有面向对象编程的继承,封装和多态的特性,只是实现的方式和其它OOP语言不一样,比如继承:Golang没有extends 关键字,继承是通过匿名字段来实现。 

1、结构体

package main
import "fmt"

//定义结构体
type Stu struct{
	//变量名字大写外界可以访问,小写外界不能访问
	Name string
	Age int
	Num string
}

func main(){
	//创建结构体实例
	var stu1 Stu
	fmt.Println(stu1)//在未赋值时默认值:{ 0 }	
	stu1.Name = "张三"	
	stu1.Age = 18
	stu1.Num = "1001"
	fmt.Println(stu1)
	//创建结构体实例
	var stu2 Stu = Stu{"李四",19,"1002"}
	fmt.Println(stu2)
	//创建结构体实例
	var stu3 = Stu{"王五",20,"1003"}
	fmt.Println(stu3)
	//创建结构体实例
	stu4 := Stu{"赵六",21,"1004"}
	fmt.Println(stu4)
	//创建结构体实例   指针类型
	stu5 := &Stu{"田七",22,"1005"}
	fmt.Println(stu5)
	//创建结构体实例   指针类型
	stu6 := &Stu{"周八",23,"1006"}
	fmt.Println(stu6)
	//创建结构体实例
	stu7 := new(Stu)
	stu7.Name = "吴九"
	stu7.Age = 24
	stu7.Num = "1007"
	fmt.Println(stu7)
}

1.1 结构体实例创建

package main
import "fmt"

//定义结构体
type Stu struct{
	//变量名字大写外界可以访问,小写外界不能访问
	Name string
	Age int
	Num string
}

func main(){
	//方式一:直接创建
	var s1 Stu
	s1.Name = "张三"
	s1.Age = 18
	s1.Num = "1001"
	fmt.Println(s1)
	fmt.Printf("%T\n",s1) // 输出的是结构体的类型--> main.Stu

	//方式二:创建时直接赋值
	var s2 = Stu{"李四",19,"1002"}
	fmt.Println(s2)

	//方式三:new关键字创建,返回结构体指针
	var p *Stu = new(Stu)
	(*p).Name = "王五"
	(*p).Age = 20
	p.Num = "1003"     // 结构体指针可以直接访问结构体成员(简化)
	fmt.Println(*p)    // 输出的是结构体的值
	fmt.Println(p)     // 输出的是结构体的地址

	//方式四:返回结构体指针
	var p2 = &Stu{"赵六",21,"1004"}
	p2.Age = 11
	fmt.Println(*p2)    //输出的是结构体的值
	fmt.Println(p2)     //输出的是结构体的地址
	fmt.Printf("%p\n",p2)  // 输出的是结构体的地址
	fmt.Printf("%T\n",p2)  // 输出的是结构体的类型--> *main.Stu
}

1.2 创建结构体实例时指定字段值

package main
import "fmt"

type Stu struct{
	Age int
	Name string
}


func main(){
	//方式一:按照顺序赋值--缺点:如果顺序改变,就会报错
	s := Stu{10,"张三"}
	fmt.Println(s)
	//方式二:按照指定变量赋值,可以不按照顺序赋值
	s1 := Stu{
		Name:"李四",
		Age:11,     // 分行赋值,最后一行需要加逗号
	}
	fmt.Println(s1)
	//方式三:想要返回结构体的指针类型
	s3 := &Stu{12,"王五"}
	fmt.Println(s3) // &{12 王五}
	fmt.Println(*s3) //{12 王五}
}

1.3 跨包创建结构体实例

  1. 创建不同的包
  2. student --> package model
  3. main.go --> package main


     

结构体名字首字母小写,并可以进行跨包访问  -->  工厂模式


 

1.4 结构体之间的转换

1、结构体是用户单独定义的类型,和其它类型进行转换时需要有完全相同的字段(名字、个数、类型、顺序需要完全一致)

package main
import "fmt"

type Stu struct{
	Age int
	Name string
}

type Person struct{
	Age int
	Name string
}


func main(){
	s1 := Stu{18,"张三"}
	p1 := Person{19,"李四"}
	p1 = Person(s1)
	fmt.Println(s1)
	fmt.Println(p1)
}

2、 结构体进行type重新定义(相当于取别名),Golang认为是新的数据类型,但是相互间可以强转

package main
import "fmt"

type Stu struct{
	Age int
	Name string
}
type Per Stu


func main(){
	var s1 Stu = Stu{20,"张三"}
	var p1 Per = Per{19,"李四"}
	p1 = Per(s1)
	fmt.Println(s1)       // 输出的是结构体的值
	fmt.Printf("%T\n",s1) // 输出的是结构体的类型--> main.Stu
	fmt.Println(p1)       // 输出的是结构体的值
	fmt.Printf("%T\n",p1) // 输出的是结构体的类型--> main.Per
}

2、方法

方法:指作用在指定的数据类型上,和指定的数据类型绑定,因此自定义类型都可以方法,不仅仅时struct。

函数:函数是独立的代码块,可以接受参数并返回结果。

区别:

  • 函数:不需要接收者,可以被独立调用。
  • 方法:必须有一个接收者,该接收者可以是一个值(值接收者)或指针(指针接收者),并且在调用方法时,需要通过接收者的实例来调用。

调用:

package main
import "fmt"

type Stu struct{
	Age int
	Name string
}

//(s Stu)用于给Stu绑定Print方法,s Stu表示接收者
//结构体传递是值传递,不影响原结构体的值
func (s Stu) Print(){
	s.Age = 100
	fmt.Println(s.Age,s.Name) // {100 张三}
}


func main(){
	var s1 Stu = Stu{20,"张三"}
	s1.Print()      //{100 张三}
	fmt.Println(s1) //{20 张三}
}

注意:

  • 结构体和方法绑定,调用该方法时必须靠指定的类型(其他类型变量调用会报错)
  • 结构体对象传入方法中,是进行了值传递(副本),副本改变不影响原结构体
  • 结构体对象传入方法中,如果需要在方法中改变原结构体的值,可以通过结构体指针的方式来处理
    package main
    import "fmt"
    
    type Stu struct{
    	Age int
    	Name string
    }
    
    //(s Stu)用于给Stu绑定Print方法,s Stu表示接收者
    //结构体传递是值传递,不影响原结构体的值
    func (s *Stu) Print(){
    	s.Age = 100 // 结构体指针可以直接访问结构体成员(简化)
    	fmt.Println(s.Age,s.Name) // {100 张三}
    }
    
    
    func main(){
    	var s1 Stu = Stu{20,"张三"}
    	(&s1).Print()      //或者是s1.Print(),都可以实现调用
    	fmt.Println(s1) //{100 张三}
    }
  • Golang中的方法作用在指定的数据类型上的,和指定的数据类型绑定,因此自定义类型都可以有方法,而不仅仅时struct,比如int,float32等都可以有方法(借用type来给基本数据类型取别名,Golang认为是新的数据类型,用这个特性来绑定方法)

    package main
    import "fmt"
    
    type interger int
    
    func (i interger) Print(){
    	i = 11111111
    	fmt.Println(i) // 11111111
    	//fmt.Printf("%T\n",i) // main.interger
    }
    func (i *interger) change(){
    	*i = 1111
    	fmt.Println(*i)
    }
    
    func main(){
    	var i interger = 100
    	i.Print() // 11111111
    	fmt.Println(i) // 100--不改变
    	i.change() //1111
    	fmt.Println(i) //1111--改变
    }
  • 方法的访问范围控制的规则,和函数一样。方法名首字母小写,只能在本包访问;方法首字母大写,可以在本包和其他包访问
  • 如果一个类型实现了String()这个方法,那么fmt.Println默认会调用这个变量的String()进行输出
    package main
    import "fmt"
    
    type Stu struct{
    	Age int
    	Name string
    }
    func (s *Stu) String() string{
    	str := fmt.Sprintf("Age:%d Name:%s",s.Age,s.Name)
    	return str
    }
    
    // func (s Stu) String() string{
    // 	str := fmt.Sprintf("Age:%d Name:%s",s.Age,s.Name)
    // 	return str
    // }
    
    func main(){
    	var s Stu = Stu{10,"张三"}
    	fmt.Println(&s)
    	//fmt.Println(s)
    }

简化:

  • 使用结构体或结构体指针调用变量或方法时,指针调用可以不用*
  • 使用结构体或结构体指针作为参数传参时,结构体传给结构体指针可以不用&(对于方法来说,接收者为值类型,可以传入指针类型(但还是值传递),接收者为指针类型,可以传入值类型)

3、封装

什么是封装:
封装就是把抽象出的字段和对字段的操作封装在一起,数据被保护在内部,程序的其它包只有通过被授权的操作方法,才能对字段进行操作

封装的好处:

  • 隐藏实现细节
  • 可以对数据进行验证,保证安全合理

Golang中如何实现封装:

  • 建议将结构体、字段(属性)的首字母小写(其它包不能使用,类似private,实际开发不小写也可能,因为封装没有那么严格)
  • 给结构体所在包提供一个工厂模式的函数,首字母大写(类似一个构造函数)
  • 提供一个首字母大写的Set方法(类似其它语言的public),用于对属性判断并赋值
  • 提供一个首字母大写的Get方法(类似其它语言的public),用于获取属性的值

4、继承

当多个结构体存在相同的属性(字段)和方法时,可以从这些结构体中抽象出结构体,在该结构体中定义这些相同的属性和方法,其它的结构体不需要重所定义这些属性和方法,只需嵌套一个匿名结构体即可在Golang中,如果一个struct嵌套了另一个匿名结构体,那么这个结构体可以直接访问匿名结构体的字段和方法,从而实现了继承特性。(提高代码复用性和扩展性)

package main
import "fmt"

type Animal struct{
	Age int;
	Wei int;
}

func (an *Animal) Eat(name string){
	fmt.Printf("%s: eat\n",name)
}
func (an *Animal) Run(name string){
	fmt.Printf("%s: run\n",name)
}

type Dog struct{
	Animal
	Name string
}
func (dog *Dog) Shout(){
	fmt.Println("汪")
}

type Cat struct{
	Animal
	Name string
}
func (cat *Cat)Shout(){
	fmt.Println("喵")
}



func main(){
	var cat Cat = Cat{Animal{10,10},"小白"}
	var dog Dog = Dog{Animal{6,20},"小黑"}
	//cat
	cat.Animal.Eat(cat.Name) // 小白: eat
	cat.Animal.Run(cat.Name) // 小白: run
	cat.Shout() // 喵
	//dog
	dog.Animal.Eat(dog.Name) // 小黑: eat
	dog.Animal.Run(dog.Name) // 小黑: run
	dog.Shout() // 汪
}

注意:

  • 结构体可以使用嵌套匿名结构体所有的字段和方法,即:首字母大写或者小写的字段、方法,都可以使用
  • 匿名结构体字段访问可以简化(例如:cat.Animal.Eat(cat.Name) -->  cat.Eat(car.Name) )
    调用过程:cat.Eat(car.Name)先在对应的Cat结构体中找是否有Eat方法,如果有则直接访问;如果没有则就去找嵌入的结构体中继续寻找(就近原则)
  • 当结构体和匿名结构体有相同的字段或者方法时,编译器采用就近访间原则访问,如希望访问匿名结构体的字段和方法,可以通过匿名结构体名来区分
  • Golang中支持多继承:如一个结构体嵌套了多个匿名结构体,那么该结构体可以直接访问嵌套的匿名结构体的字段和方法,从而实现了多重继承。为了保证代码的简洁性,建议大家尽量不使用多重继承,很多语言就将多重继承去除了(可能出现菱形继承的问题),但是Go中保留了
  • 嵌入的匿名结构体有相同的字段名或者方法名,则在访问时,需要通过指明匿名结构体类型名来区分。
  • 结构体的匿名字段可以是基本数据类型(这与既不继承没关系)
    package main
    import "fmt"
    
    type Animal struct{
    	int
    }	
    
    
    func main(){
    	var a Animal = Animal{10}
    	fmt.Println(a.int) // 10
    }
  • 嵌套匿名结构体后,也可以在创建结构体变量(实例)时,直接指定各个匿名结构体字段的值

  • 嵌入匿名结构体的指针也是可以的

  • 结构体的字段可以是结构体类型的(组合模式) 

5、接口

package main
import "fmt"

//接口的定义:定义规则,定义规范,定义某种能力
//interface接口类型
type SayHello interface{
	//只声明,没有实现的方法
	sayHello()
}
//接口的实现:定义一个结构体
type Chinese struct{
	
}
//实现接口的方法--具体的实现
func (ch Chinese) sayHello(){
	fmt.Println("你好")
}

//接口的实现:定义一个结构体
type American struct{

}
//实现接口的方法--具体的实现
func (am American) sayHello(){
	fmt.Println("hello")
}
//定义一个函数:接收具备SayHello接口的能力的变量、
func greet(s SayHello){
	s.sayHello()
}

func main(){
	c := Chinese{}
	a := American{}
	//调用函数
	greet(c)
	greet(a)
}
  • 接口中可以定义一组方法,但不需要实现。并且接口中不能包含任何变量。到某个自定义类型要使用的时候(实现接口的时候),再根据具体情况把这些方法具体实现出来。
  • 实现接口要实现所有的方法
  • Golang中实现接口是基于方法的,不是基于接口的(例如:A接口有a和b方法,B接口有a和b方法,C结构体实现了a和b方法,那么C实现了A接口,也实现了B接口。只要实现全部方法即可。和实际接口耦合性很低)
  • 接口目的是为了定义规范,具体由别人来实现即可
  • 接口本身不能创建实例,但是可以指向一个实现了该接口的自定义类型的变量
    //SayHello是接口类型
    
    //直接用接口创建实例,会出错:
    var s SayHello   //error
    s.sayHello()       //error
    
    
    //但是可以指向一个实现了该接口的自定义类型的变量
    //American -> 实现了接口的自定义类型
    var am American
    var s SayHello = am
    s.sayHello()
  • 只要是自定义数据类型,就可以实现接口,不仅仅是结构体类型
    package main
    import "fmt"
    
    type Print interface{
    	print()
    }
    
    type interger int
    func (i interger)print(){
    	fmt.Printf("%T:%d\n",i,i)
    }
    
    type str string
    func (s str)print(){
    	fmt.Printf("%T:%s\n",s,s)
    }
    
    func f(p Print){
    	p.print()
    }
    
    func main(){
    	var i interger = 11111
    	var s str = "hello"
    	f(i) //main.interger:11111
    	f(s) //main.str:hello
    }
  • 一个自定义类型可以实现多个接口
  • 一个接口(比如A接口)可以继承多个别的接口(比如B、C接口),这时如果要实现A接口,也必须将B、C接口的方法也全部实现
    package main
    import "fmt"
    
    type A interface{
    	a()
    }
    type B interface{
    	b()
    }
    //C接口继承AB接口
    type C interface{
    	A
    	B
    	c()
    }
    
    type D struct{
    	
    }
    func (d D)a(){
    	fmt.Println("a")
    }
    func (d D)b(){
    	fmt.Println("b")
    }
    func (d D)c(){
    	fmt.Println("c")
    }
    
    func Print(c C){
    	c.a()
    	c.b()
    	c.c()
    }
    
    func main(){
    	d := D{}
    	Print(d) //a b c
    }
  • interface类型默认是一个指针(引用类型),如果没有对interface初始化就使用,那么会输出nil
  • 空接口没有任何方法,所以可以理解为所有类型都实现了空接口,也可以理解为我们可以把任何一个变量赋值给空接口
    var s str = str{}
    var a interface{} = s


     

6、多态

多态:变量(实例)具有多种形态。面向对象的第三大特征之一,在Go语言,多态特征是通过接口实现可以按照统一的接口来调用不同的实现。这时接口变量就呈现不同的形态。

package main
import "fmt"

//接口的定义:定义规则,定义规范,定义某种能力
//interface{} 空接口,没有任何方法
type SayHello interface{
	//只声明,没有实现的方法
	sayHello()
}
//接口的实现:定义一个结构体
type Chinese struct{
	
}
//实现接口的方法--具体的实现
func (ch Chinese) sayHello(){
	fmt.Println("你好")
}

//接口的实现:定义一个结构体
type American struct{

}
//实现接口的方法--具体的实现
func (am American) sayHello(){
	fmt.Println("hello")
}
//定义一个函数:接收具备SayHello接口的能力的变量、
func greet(s SayHello){  
	s.sayHello()        //s可以通过上下文识别具体是什么类型的实例,这就体现了多态
}

func main(){
	c := Chinese{}
	a := American{}
	//调用函数
	greet(c)
	greet(a)
}

接口体现多态特征:

  1. 多态参数:例如上面greet函数中的s变量
  2. 多态数组:比如上面的示例中,定义SayHello数组,存放中国人结构体和美国人结构体
    package main
    import "fmt"
    
    //接口的定义:定义规则,定义规范,定义某种能力
    //interface{} 空接口,没有任何方法
    type SayHello interface{
    	//只声明,没有实现的方法
    	sayHello()
    }
    //接口的实现:定义一个结构体
    type Chinese struct{
    	
    }
    //实现接口的方法--具体的实现
    func (ch Chinese) sayHello(){
    	fmt.Println("你好")
    }
    
    //接口的实现:定义一个结构体
    type American struct{
    
    }
    //实现接口的方法--具体的实现
    func (am American) sayHello(){
    	fmt.Println("hello")
    }
    //定义一个函数:接收具备SayHello接口的能力的变量、
    func greet(s SayHello){
    	s.sayHello()
    }
    
    func main(){
    	//定义一个多态数组--接口类型可以存储任何实现了该接口的变量
    	var arr [3]SayHello
    	arr[0] = Chinese{}
    	arr[1] = American{}
    	arr[2] = Chinese{}
    	//遍历数组
    	for i := 0;i < len(arr);i++{
    		greet(arr[i]) // 你好 hello 你好
    	}
    }
    


     

7、断言

  • 在Go语言中,断言(Type Assertion)是一种用于检查接口变量的动态类型是否与指定类型相匹配的操作。它允许你将一个接口类型的变量转换为具体的类型以便访问该类型的特定方法或字段。
  • 断言的语法是 x.(T),其中 x 是一个接口类型的变量,T 是你想要断言的具体类型。
  • 如果 x 的动态类型确实是 T,那么断言会成功,并且返回 T 类型的值。如果断言失败,程序会抛出一个运行时错误。
  • 断言语法x.(T)的返回值 -> 变量,flag -> 变量用于接收T类型的值,flag用于判断类型是否转化成功 -> true--成功,false--失败。
  • 类型断言通常用于确保在调用特定方法或访问特定字段之前,接口变量确实是你所期望的类型。这有助于避免在运行时出现类型不匹配的错误。
package main
import "fmt"

//接口的定义:定义规则,定义规范,定义某种能力
//interface{} 空接口,没有任何方法
type SayHello interface{
	//只声明,没有实现的方法
	sayHello()
}
//接口的实现:定义一个结构体
type Chinese struct{
	
}
//实现接口的方法--具体的实现
func (ch Chinese) sayHello(){
	fmt.Println("你好")
}
func (ch Chinese) gongxi(){
	fmt.Println("恭喜发财")
}

//接口的实现:定义一个结构体
type American struct{

}
//实现接口的方法--具体的实现
func (am American) sayHello(){
	fmt.Println("hello")
}
//定义一个函数:接收具备SayHello接口的能力的变量、
func greet(s SayHello){
	s.sayHello()
	ch,flag := s.(Chinese)// 类型断言:看s是否能转成Chinese类型并赋给ch变量
	//就是把结构体转换成能实现当前接口的结构体,如果能转成,就赋值给ch,不能转成,就报错
	if flag==true {    //或者是 if ch,flag := s.(Chinese); flag{
		ch.gongxi()
	}else{
		fmt.Println("类型断言失败,美国人不会恭喜发财")
	}
}

func main(){
	var ch Chinese = Chinese{}
	var am American = American{}
	greet(ch) // 你好 恭喜发财
	greet(am) // hello 类型断言失败,美国人不会恭喜发财
}

Type Switch的基本用法

Type Switch是Go语言中一种特殊的switch语法,它比较的是类型而不是具体的值。它判断某个接口变量的类型,然后根据具体类型再做相应处理。

package main
import "fmt"

//接口的定义:定义规则,定义规范,定义某种能力
//interface{} 空接口,没有任何方法
type SayHello interface{
	//只声明,没有实现的方法
	sayHello()
}
//接口的实现:定义一个结构体
type Chinese struct{
	
}
//实现接口的方法--具体的实现
func (ch Chinese) sayHello(){
	fmt.Println("你好")
}
func (ch Chinese) gongxi(){
	fmt.Println("恭喜发财")
}

//接口的实现:定义一个结构体
type American struct{

}
//实现接口的方法--具体的实现
func (am American) sayHello(){
	fmt.Println("hello")
}
//定义一个函数:接收具备SayHello接口的能力的变量、
func greet(s SayHello){
	s.sayHello()
	//使用Type-Switch
	switch tp := s.(type) { //type属于go中的一个关键字,固定写法
		case Chinese:
			tp.gongxi()
		case American:
			tp.gun()
		default:
			fmt.Println("类型断言失败")
	}
}
func (am American) gun(){
	fmt.Println("gun之下出真知()")
}

func main(){
	var ch Chinese = Chinese{}
	var am American = American{}
	greet(ch) // 你好 恭喜发财
	greet(am) // hello gun之下出真知()
}

http://www.kler.cn/a/383400.html

相关文章:

  • SpringBoot源码解析(二):引导上下文DefaultBootstrapContext
  • 二次封装 el-pagination 组件存在的问题
  • 基于单片机的农业自动灌溉系统
  • 基于STM32的智能植物灌溉系统设计
  • 爱普生 SG–WriterⅡ 石英可编程手工烧录器
  • 【自用】时序数据库、时序数据库,IOTDB官方文档笔记
  • 智能指针std::shared_ptr
  • 音频怎么转换成二维码?扫码播放音频的制作方法
  • 《XGBoost算法的原理推导》12-7损失函数经验损失项二阶泰勒展开式 公式解析
  • 归并排序:高效算法的深度解析
  • 大模型LLama3!!!Ollama下载、部署和应用(保姆级详细教程)
  • K8s小白入门
  • 【C++笔记】模版的特化及其编译分离
  • 6.qsqlquerymodel源码分析
  • Java Agent使用、用途和优势
  • 第十九周机器学习笔记:GAN的数学理论知识与实际应用的操作
  • URL上的参数获取
  • C++之多态(3)
  • 鸿蒙next打包流程
  • 提升网站流量的有效网页优化方法指南
  • 力扣1 两数之和
  • 栈和队列相关题 , 用队列实现栈, 用栈实现队列 ,设计循环队列 C/C++双版本
  • C#字符串的不可变性:内存管理与线程安全的优势分析
  • 你要的增量更新来了:微软GraphRAG 0.4.0
  • DPDK(F-Stack) 实现UDP通信
  • 印刷质量检测笔记