Go语言头秃之路(四)

更新系列

Go语言头秃之路(零)
Go语言头秃之路(一)
Go语言头秃之路(二)
Go语言头秃之路(三)
Go语言头秃之路(五)
Go语言头秃之路(六)
Go语言头秃之路(七)


  • 面向对象(结构体)

    • 结构体:自定义数据类型
      结构体是值类型。
      定义:
    type 结构体名 struct {
    	属性名1 类型1
    	属性名2 类型2
    }
    

    结构体四种实例创建方式:

    type Stu struct {
    	Name string
    	Age int
    }
    // 1. 
    var s1 Stu
    // 2. 
    s2 := Stu{"tony", 10}
    s2_2 := &Stu{"tony", 10}   // 实例化为指针类型变量,存的是地址,多用于工厂模式
    // 3. 这里s3.Name == (*s3).Name,go编译器会将s3转义成(*s3),s4同
    var s3 *Stu = new(Stu)
    // 4. 
    var s4 *Stu = &Stu{}
    

    如果结构体的字段类型(引用类型)是指针、slice、map,他们的零值是nil,即没有分配空间,在字段使用之前需要先make()

    结构体的所有字段在内存中是连续的

    两个结构体无法进行转换(赋值=),取别名也不行,因为golang会认为这是两种数据类型,强制转换时字段要完全一样(包括名称、个数)

    type A struct {num int}
    type B struct {n int}
    var a A
    var b B
    a = b // 报错
    a = B(b)  // 强制转换
    
  • 结构体的大小是声明中各元素指针大小之和(string指针占16字节,int指针占8字节),可以使用unsafe.SizeOf() 查看。(go语言中string本质是结构体)

    • 方法作用在指定数据类型上,所有自定义类型都可以有方法。
      自定义数据类型会作为参数传递给方法,如果数据类型是值类型则进行值拷贝,如果是引用类型则进行地址拷贝。
      结构体是值拷贝, 为了提高效率一般将方法和结构体的指针类型绑定
      func (结构体形参 *结构体名) 方法名(方法参数1,…) {
      fmt.Println("…")
      }
      饭粒:
      func (a *A) test() {
      fmt.Println("…")
      }
      test()方法与结构体A绑定,只能通过A的实例调用。
      如果自定义类型绑定了自定义String方法,那么在打印地址时会按照自定义的String返回值输出。

    • 方法和函数的区别:

    1. 调用方式不一样
    2. 对于函数,形参为值类型时不能将指针类型的数据作为实参传递,反之同理;
      对于方法(如struct的方法),形参和实参不论值类型还是指针类型都可以相互调用。
      注意:不管调用形式如何,真正决定是值拷贝还是地址拷贝得看这个方法是和哪个类型绑定。如果是和值类型绑定,比如(u User),则是值拷贝,如果是和指针类型绑定,如(u *User),则是地址拷贝。
    package main
    
    import "fmt"
    
    type User struct {
    	Name string
    }
    
    func (u User) change() {   // 因为形参是值类型,所以实参不论是值传递还是引用传递都是值拷贝
    	u.Name = "yiming"
    	fmt.Println("change() User.Name:", u.Name)
    }
    
    func main()  {
    	u1 := User{"fone"}
    	(&u1).change()   // 值拷贝
    	fmt.Println("main() User.Name:", u1.Name)
    }
    
    • 面向对象特性

      • 封装:逻辑写在方法中
      • 继承:消灭代码冗余
        通过嵌入匿名结构体实现:声明的时候加上一行需要继承的结构体名即可

      说明:

      1. 继承过来的结构体所有属性和方法都可以使用(不论首字母大小写都可以)
      2. 匿名结构体的属性和方法调用可以简化匿名结构体名,查找顺序是(就近原则):先找当前结构体的属性方法,如果没有就找继承结构体的属性方法。
      3. 结构体嵌入两个(或多个)匿名结构体时,如果两个匿名结构体有同名字段或方法,而当前结构体没有同名字段方法,在调用时必须指定匿名结构体名,否则编译报错。(为了代码简洁性,尽量不要使用多重继承)
      4. 如果嵌入的是有名结构体(匿名结构体前带上别名),则调用属性方法时必须带上有名结构体别名。
      5. 嵌入匿名结构体使用指针类型会比较高效。
        package main
        
        import "fmt"
        
        type Product struct {
        	Name string
        	Price float64
        }
        
        type SuperMarket struct {
        	Name string
        	Address string
        }
        
        type cup struct {
        	p *Product
        	*SuperMarket
        }
        
        func (p *Product) Card() {
        	fmt.Println("商品: ", p.Name, "\t价格:", p.Price)
        }
        
        func (sm *SuperMarket) MarketInfo() {
        	fmt.Println("商场: ", sm.Name, "\t地址:", sm.Address)
        }
        
        func main()  {
        	c1 := cup{
        		&Product{
        			Name: "杯具001", 
        			Price: 12.34,
        		}, 
        		&SuperMarket{
        			Name: "人人乐", 
        			Address: "学府路58号",
        		},
        	}
        	c1.p.Card()
        	c1.MarketInfo()
        	fmt.Println(c1.Name)
        }
        
    • 接口interface
      接口名一般使用er结尾;
      用于定义一组方法,但不需要实现。
      接口不能包含任何变量。
      定义接口函数时不能使用指针接收器。
      在自定义类型(如结构体)的时候,再把这些方法通通实现。
      接口定义中的所有方法都没有方法体(即都是没有实现的方法),接口体现了程序设计的多态高内聚低耦合思想。
      接口只要一个变量,含有接口类型中的所有方法,那么这个变量就实现了这个接口。
      语法:

      type 接口名 interface{
      	方法1(参数列表) [返回值列表]
      	方法2(参数列表) [返回值列表]
      }
      
      func (自定义类型别名 自定义类型) 方法1(参数列表) [返回值列表] {实现逻辑}...
      

      注意:

      1. 接口本身不能创建实例,但是可以指向一个实现了该接口的自定义类型变量(实例);
      2. 任何自定义类型都可以实现接口,一个自定义类型可以实现多个接口;
      3. 接口可以继承多个接口,此时自定义类型需要实现所有方法。但是不允许继承的接口存在相同的方法,即一个接口(包括继承过来的接口)中不能存在相同的方法。
      4. 接口类型默认是一个指针(引用类型),没有初始化时为nil;
      5. 空接口interface{}没有任何方法,所有类型都实现了空接口,任何变量都可以赋值给空接口。
      6. 接口可以在不破坏继承关系的基础上增加方法,实现接口是对继承的补充。
    • 接口&继承

    1. 继承的价值:解决代码复用性可维护性
    2. 接口的价值:设计好各种规范(方法),让其他自定义类型去实现这些方法。
    3. 接口比继承更加灵活:继承是满足 is - a 的关系,而接口只需满足 like - a 的关系。
    • 多态
      变量(实例)具有多种形态。
      多态通过接口实现。

      类型断言:当需要将一个接口变量赋值给自定义类型变量时使用类型断言。
      饭粒:

      type Point struct {
      	x int
      	y int
      }
      
      func main()  {
      	var a interface{}
      	var point Point = Point{1,2}
      	a = point  // 空接口可以接收任意类型
      
      	var b Point
      	// 类型断言
      	// 判断a是否指向Point类型,如果是就转成Point类型并赋值给b,否则不抛异常。
      	if b, ok = a.(Point); ok {
      		fmt.Println(a, b)
      	} else {
      		fmt.Println("panic..")
      	}
      	fmt.Println("exec continue..")
      }
      

      类型断言应用场景:

      1. 在实现多态时,自定义变量需要调用自己单独的方法时,使用断言判断变量类型。
      2. 用于判断数据类型,饭粒:
        package main
        
        import "fmt"
        
        type Student struct {}
        
        func GetType(items... interface{})  {
        	for i, x := range items {
        		switch x.(type) {
        			case bool:
        				fmt.Printf("第%v个值类型是 布尔, 值为:%v\n", i, x)
        			case int, int32, int64:
        				fmt.Printf("第%v个值类型是 整形, 值为:%v\n", i, x)
        			case float32, float64:
        				fmt.Printf("第%v个值类型是 浮点型, 值为:%v\n", i, x)
        			case string:
        				fmt.Printf("第%v个值类型是 字符串, 值为:%v\n", i, x)
        			case Student:
        				fmt.Printf("第%v个值类型是 学生, 值为:%v\n", i, x)
        			case *Student:
        				fmt.Printf("第%v个值类型是 学生指针, 值为:%v\n", i, x)
        			default:
        				fmt.Printf("第%v个值类型 不确定, 值为:%v\n", i, x)
        		}
        	}
        }
        
        func main()  {
        	n1 := 1
        	n2 := 1.1
        	n3 := "hello"
        	n4 := true
        	n5 := Student{}
        	n6 := &Student{}
        	GetType(n1, n2, n3, n4, n5, n6)
        }
        
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值