Go语言学习笔记(四)结构体struct & 接口Interface & 反射reflect

简介: 加 Golang学习 QQ群共同学习进步成家立业工作 ^-^ 群号:96933959结构体structstruct 用来自定义复杂数据结构,可以包含多个字段(属性),可以嵌套;go中的struct类型理解为类,可以定义方法,和函数定义有些许区别;struct类型是值类型。

加 Golang学习 QQ群共同学习进步成家立业工作 ^-^ 群号:96933959

结构体struct

struct 用来自定义复杂数据结构,可以包含多个字段(属性),可以嵌套;

go中的struct类型理解为类,可以定义方法,和函数定义有些许区别;

struct类型是值类型。

struct定义

type User struct {
    Name string
    Age  int32
    mess string
}
var user User
var user1 *User = &User{}
var user2 *User = new(User)

struct使用

下面示例中user1和user2为指针类型,访问的时候编译器会自动把 user1.Name 转为 (*user1).Name

func main() {
    var user User
    user.Name = "nick"
    user.Age = 18
    user.mess = "lover"

    var user1 *User = &User{
        Name: "dawn",
        Age:  21,
    }
    fmt.Println(*user1)                    //{dawn 21 }
    fmt.Println(user1.Name, (*user1).Name) //dawn dawn

    var user2 *User = new(User)
    user2.Name = "suoning"
    user2.Age = 18
    fmt.Println(user2)                     //&{suoning 18 }
    fmt.Println(user2.Name, (*user2).Name) //suoning suoning
}

构造函数

golang中的struct没有构造函数,可以伪造一个

type User struct {
    Name string
    Age  int32
    mess string
}

func NewUser(name string, age int32, mess string) *User {
    return &User{Name:name,Age:age,mess:mess}
}

func main() {
    //user := new(User)
    user := NewUser("suoning", 18, "lover")
    fmt.Println(user, user.mess, user.Name, user.Age)
}

内存布局

struct中的所有字段在内存是连续的,布局如下:

    var user User
    user.Name = "nick"
    user.Age = 18
    user.mess = "lover"

    fmt.Println(user)                   //{nick 18 lover}
    fmt.Printf("Name:%p\n", &user.Name) //Name:0xc420016180
    fmt.Printf("Age: %p\n", &user.Age)  //Age: 0xc420016190
    fmt.Printf("mess:%p\n", &user.mess) //mess:0xc420016198 8字节为内存对齐

方法

方法是作用在特定类型的变量上,因此自定义类型,都可以有方法,而不仅仅是struct。

方法的访问控制也是通过大小写控制。

init函数是通过传入指针实现,这样改变struct字段值,因为是值类型。

type User struct {
    Name string
    Age  int
    sex  string
}

func (this *User) init(name string, age int, sex string) {
    this.Name = name
    this.Age = age
    this.sex = sex
}

func (this User) GetName() string {
    return this.Name
}

func main() {
    var user User
    user.init("nick", 18, "man")
    //(&user).init("nick", 18, "man")
    name := user.GetName()
    fmt.Println(name)
}

匿名字段

如果有冲突的, 则最外的优先

type User struct {
    Name stirng
    Age int        
}

type Lover struct {
     User
     sex time.Time
     int
     Age int
}

继承 & 多重继承

一个结构体继承多个结构体,访问通过点。继承字段以及方法。

可以起别名,如下面 u1(user1),访问 user.u1.Age。

如果继承的结构体都拥有同一个字段,通过user.name访问就会报错,必须通过user.user1.name来访问。

type user1 struct {
    name string
    Age  int
}

type user2 struct {
    name string
    age  int
    sex time.Time
}

type User struct {
    u1   user1 //别名
    user2
    Name string
    Age  int
}

func main() {
    var user User
    user.Name = "nick"
    user.u1.Age = 18
    fmt.Println(user)    //{{ 18} { 0 {0 0 <nil>}} nick 0}
}

tag

在go中,首字母大小写有特殊的语法含义,小写包外无法引用。由于需要和其它的系统进行数据交互,例如转成json格式。这个时候如果用属性名来作为键值可能不一定会符合项目要求。tag在转换成其它数据格式的时候,会使用其中特定的字段作为键值。

import "encoding/json"

type User struct {
    Name string `json:"userName"`
    Age  int    `json:"userAge"`
}

func main() {
    var user User
    user.Name = "nick"
    user.Age = 18
    
    conJson, _ := json.Marshal(user)
    fmt.Println(string(conJson))    //{"userName":"nick","userAge":0}
}

String()

如果实现了String()这个方法,那么fmt默认会调用String()。

type name1 struct {
    int
    string
}

func (this *name1) String() string {
    return fmt.Sprintf("This is String(%s).", this.string)
}

func main() {
    n := new(name1)
    fmt.Println(n) //This is String().
    n.string = "suoning"
    d := fmt.Sprintf("%s", n) //This is String(suoning).
    fmt.Println(d)
}

 

接口Interface

Interface类型可以定义一组方法,但是这些不需要实现。并且interface不能包含任何变量。

interface类型默认是一个指针。

Interface定义

type Car interface {
    NameGet() string
    Run(n int)
    Stop()
}

Interface实现

  1. Golang中的接口,不需要显示的实现。只要一个变量,含有接口类型中的所有方法,那么这个变量就实现这个接口。因此,golang中没有implement类似的关键字;
  2. 如果一个变量含有了多个interface类型的方法,那么这个变量就实现了多个接口;如果一个变量只含有了1个interface的方部分方法,那么这个变量没有实现这个接口。
  3. 空接口 Interface{}:空接口没有任何方法,所以所有类型都实现了空接口。
var a int
var b interface{}    //空接口
b  = a

多态

一种事物的多种形态,都可以按照统一的接口进行操作。

栗子:

type Car interface {
    NameGet() string
    Run(n int)
    Stop()
}

type BMW struct {
    Name string
}
func (this *BMW) NameGet() string {
    return this.Name
}
func (this *BMW) Run(n int) {
    fmt.Printf("BMW is running of num is %d \n", n)
}
func (this *BMW) Stop() {
    fmt.Printf("BMW is stop \n")
}

type Benz struct {
    Name string
}
func (this *Benz) NameGet() string {
    return this.Name
}
func (this *Benz) Run(n int) {
    fmt.Printf("Benz is running of num is %d \n", n)
}
func (this *Benz) Stop() {
    fmt.Printf("Benz is stop \n")
}
func (this *Benz) ChatUp() {
    fmt.Printf("ChatUp \n")
}

func main() {
    var car Car
    fmt.Println(car) // <nil>

    var bmw BMW = BMW{Name: "宝马"}
    car = &bmw
    fmt.Println(car.NameGet()) //宝马
    car.Run(1)                 //BMW is running of num is 1
    car.Stop()                 //BMW is stop

    benz := &Benz{Name: "大奔"}
    car = benz
    fmt.Println(car.NameGet()) //大奔
    car.Run(2)                 //Benz is running of num is 2
    car.Stop()                 //Benz is stop
    //car.ChatUp()    //ERROR: car.ChatUp undefined (type Car has no field or method ChatUp)
}

Interface嵌套

一个接口可以嵌套在另外的接口。

即需要实现2个接口的方法。

type Car interface {
    NameGet() string
    Run(n int)
    Stop()
}

type Used interface {
    Car
    Cheap()
}

类型断言

类型断言,由于接口是一般类型,不知道具体类型,

如果要转成具体类型,可以采用以下方法进行转换:

var t int
var x interface{}
x = t

y = x.(int)       //转成int
y, ok = x.(int)   //转成int,不报错

栗子一:

func test(i interface{}) {
    // n := i.(int)
    n, ok := i.(int)
    if !ok {
        fmt.Println("error")
        return
    }
    n += 10
    fmt.Println(n)
}

func main() {
    var t1 int
    test(t1)
}

栗子二:

switch & type

type Student struct {
    Name string
}

func judgmentType(items ...interface{}) {
    for k, v := range items {
        switch v.(type) {
        case string:
            fmt.Printf("string, %d[%v]\n", k, v)
        case bool:
            fmt.Printf("bool, %d[%v]\n", k, v)
        case int, int32, int64:
            fmt.Printf("int, %d[%v]\n", k, v)
        case float32, float64:
            fmt.Printf("float, %d[%v]\n", k, v)
        case Student:
            fmt.Printf("Student, %d[%v]\n", k, v)
        case *Student:
            fmt.Printf("Student, %d[%p]\n", k, v)
        }
    }
}

func main() {
    stu1 := &Student{Name: "nick"}
    judgmentType(1, 2.2, "learing", stu1)
}

栗子三:

判断一个变量是否实现了指定接口

type Stringer interface {
    String() string
}

type Mystruct interface {

}
type Mystruct2 struct {

}
func (this *Mystruct2) String() string {
    return ""
}

func main()  {
    var v Mystruct
    var v2 Mystruct2
    v = &v2

    if sv, ok := v.(Stringer); ok {
        fmt.Printf("%v implements String(): %s\n", sv.String());
    }
}

 

反射 reflect

reflect包实现了运行时反射,允许程序操作任意类型的对象。

典型用法是用静态类型interface{}保存一个值,

  通过调用TypeOf获取其动态类型信息,该函数返回一个Type类型值。

  调用ValueOf函数返回一个Value类型值,该值代表运行时的数据。

func TypeOf(i interface{}) Type
TypeOf返回接口中保存的值的类型,TypeOf(nil)会返回nil。
func ValueOf(i interface{}) Value
ValueOf返回一个初始化为i接口保管的具体值的Value,ValueOf(nil)返回Value零值。
reflect.Value.Kind
获取变量的类别,返回一个常量
  reflect.Value.Kind()方法返回的常量
reflect.Value.Interface()
转换成interface{}类型
【变量<-->Interface{}<-->Reflect.Value】
获取变量的值:
reflect.ValueOf(x).Int()
reflect.ValueOf(x).Float() 
reflect.ValueOf(x).String()
reflect.ValueOf(x).Bool()
通过反射的来改变变量的值
reflect.Value.SetXX相关方法,比如:
reflect.Value.SetInt(),设置整数
reflect.Value.SetFloat(),设置浮点数
reflect.Value.SetString(),设置字符串

 

栗子一

import "reflect"

func main() {
    var x float64 = 5.21
    fmt.Println("type:", reflect.TypeOf(x)) //type: float64

    v := reflect.ValueOf(x)
    fmt.Println("value:", v)         //value: 5.21
    fmt.Println("type:", v.Type())   //type: float64
    fmt.Println("kind:", v.Kind())   //kind: float64
    fmt.Println("value:", v.Float()) //value: 5.21

    fmt.Println(v.Interface())                    //5.21
    fmt.Printf("value is %1.1e\n", v.Interface()) //value is 5.2e+00
    y := v.Interface().(float64)
    fmt.Println(y) //5.21
}

栗子二(修改值)

SetXX(x) 因为传递的是 x 的值的副本,所以SetXX不能够改 x,改动 x 必须向函数传递 x 的指针,SetXX(&x) 。

//错误代码!!!
//panic: reflect: reflect.Value.SetFloat using unaddressable value
func main() {
    var a float64
    fv := reflect.ValueOf(&a)
    fv.SetFloat(520.00)
    fmt.Printf("%v\n", a)
}
//正确的,传指针
func main() {
    var a2 float64
    fv2 := reflect.ValueOf(&a2)
    fv2.Elem().SetFloat(520.00)
    fmt.Printf("%v\n", a2)    //520
}

 

反射操作结构体

  1. reflect.Value.NumField()获取结构体中字段的个数
  2. reflect.Value.Method(n).Call(nil)来调用结构体中的方法

 

栗子一(通过反射操作结构体)

import "reflect"

type NotknownType struct {
    S1 string
    S2 string
    S3 string
}

func (n NotknownType) String() string {
    return n.S1 + " & " + n.S2 + " & " + n.S3
}

var secret interface{} = NotknownType{"Go", "C", "Python"}

func main() {
    value := reflect.ValueOf(secret)
    fmt.Println(value) //Go & C & Python
    typ := reflect.TypeOf(secret)
    fmt.Println(typ) //main.NotknownType

    knd := value.Kind()
    fmt.Println(knd) // struct

    for i := 0; i < value.NumField(); i++ {
        fmt.Printf("Field %d: %v\n", i, value.Field(i))
    }

    results := value.Method(0).Call(nil)
    fmt.Println(results) // [Go & C & Python]
}

 

栗子二(通过反射修改结构体)

import "reflect"

type T struct {
    A int
    B string
}

func main() {
    t := T{18, "nick"}
    s := reflect.ValueOf(&t).Elem()
    typeOfT := s.Type()

    for i := 0; i < s.NumField(); i++ {
        f := s.Field(i)
        fmt.Printf("%d: %s %s = %v\n", i,
            typeOfT.Field(i).Name, f.Type(), f.Interface())
    }

    s.Field(0).SetInt(25)
    s.Field(1).SetString("nicky")
    fmt.Println(t)
}

/*
输出:
0: A int = 18
1: B string = nick
{25 nicky}
*/
import "reflect"

type test struct {
    S1 string
    s2 string
    s3 string
}

var s interface{} = &test{
    S1: "s1",
    s2: "s2",
    s3: "s3",
}

func main() {
    val := reflect.ValueOf(s)
    fmt.Println(val)                      //&{s1 s2 s3}
    fmt.Println(val.Elem())               //{s1 s2 s3}
    fmt.Println(val.Elem().Field(0))      //s1
    val.Elem().Field(0).SetString("hehe") //S1大写
}

 

栗子三(struct tag 内部实现)

package main

import (
    "fmt"
    "reflect"
)

type User struct {
    Name string `json:"user_name"`
}

func main() {
    var user User
    userType := reflect.TypeOf(user)
    jsonString := userType.Field(0).Tag.Get("json")
    fmt.Println(jsonString)        //user_name
}

 

目录
相关文章
|
25天前
|
存储 JSON 监控
Viper,一个Go语言配置管理神器!
Viper 是一个功能强大的 Go 语言配置管理库,支持从多种来源读取配置,包括文件、环境变量、远程配置中心等。本文详细介绍了 Viper 的核心特性和使用方法,包括从本地 YAML 文件和 Consul 远程配置中心读取配置的示例。Viper 的多来源配置、动态配置和轻松集成特性使其成为管理复杂应用配置的理想选择。
38 2
|
23天前
|
Go 索引
go语言中的循环语句
【11月更文挑战第4天】
26 2
|
23天前
|
Go C++
go语言中的条件语句
【11月更文挑战第4天】
33 2
|
9天前
|
存储 Go 索引
go语言使用for循环遍历
go语言使用for循环遍历
23 7
|
12天前
|
存储 Go
go语言 遍历映射(map)
go语言 遍历映射(map)
24 2
|
13天前
|
Go 调度 开发者
Go语言中的并发编程:深入理解goroutines和channels####
本文旨在探讨Go语言中并发编程的核心概念——goroutines和channels。通过分析它们的工作原理、使用场景以及最佳实践,帮助开发者更好地理解和运用这两种强大的工具来构建高效、可扩展的应用程序。文章还将涵盖一些常见的陷阱和解决方案,以确保在实际应用中能够避免潜在的问题。 ####
|
13天前
|
测试技术 Go 索引
go语言使用 range 关键字遍历
go语言使用 range 关键字遍历
17 3
|
13天前
|
测试技术 Go 索引
go语言通过 for 循环遍历
go语言通过 for 循环遍历
22 3
|
15天前
|
安全 Go 数据处理
Go语言中的并发编程:掌握goroutine和channel的艺术####
本文深入探讨了Go语言在并发编程领域的核心概念——goroutine与channel。不同于传统的单线程执行模式,Go通过轻量级的goroutine实现了高效的并发处理,而channel作为goroutines之间通信的桥梁,确保了数据传递的安全性与高效性。文章首先简述了goroutine的基本特性及其创建方法,随后详细解析了channel的类型、操作以及它们如何协同工作以构建健壮的并发应用。此外,还介绍了select语句在多路复用中的应用,以及如何利用WaitGroup等待一组goroutine完成。最后,通过一个实际案例展示了如何在Go中设计并实现一个简单的并发程序,旨在帮助读者理解并掌
|
14天前
|
Go 索引
go语言按字符(Rune)遍历
go语言按字符(Rune)遍历
24 3