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
}

 

目录
相关文章
|
5月前
|
存储 Go
Go语言之接口与多态 -《Go语言实战指南》
Go 语言中的接口是实现多态的核心机制,通过一组方法签名定义行为。任何类型只要实现接口的所有方法即视为实现该接口,无需显式声明。本文从接口定义、使用、底层机制、组合、动态行为到工厂模式全面解析其特性与应用,帮助理解 Go 的面向接口编程思想及注意事项(如 `nil` 陷阱)。
155 22
|
8月前
|
编译器 Go
揭秘 Go 语言中空结构体的强大用法
Go 语言中的空结构体 `struct{}` 不包含任何字段,不占用内存空间。它在实际编程中有多种典型用法:1) 结合 map 实现集合(set)类型;2) 与 channel 搭配用于信号通知;3) 申请超大容量的 Slice 和 Array 以节省内存;4) 作为接口实现时明确表示不关注值。此外,需要注意的是,空结构体作为字段时可能会因内存对齐原因占用额外空间。建议将空结构体放在外层结构体的第一个字段以优化内存使用。
|
8月前
|
运维 监控 算法
监控局域网其他电脑:Go 语言迪杰斯特拉算法的高效应用
在信息化时代,监控局域网成为网络管理与安全防护的关键需求。本文探讨了迪杰斯特拉(Dijkstra)算法在监控局域网中的应用,通过计算最短路径优化数据传输和故障检测。文中提供了使用Go语言实现的代码例程,展示了如何高效地进行网络监控,确保局域网的稳定运行和数据安全。迪杰斯特拉算法能减少传输延迟和带宽消耗,及时发现并处理网络故障,适用于复杂网络环境下的管理和维护。
|
2月前
|
数据采集 Go API
Go语言实战案例:多协程并发下载网页内容
本文是《Go语言100个实战案例 · 网络与并发篇》第6篇,讲解如何使用 Goroutine 和 Channel 实现多协程并发抓取网页内容,提升网络请求效率。通过实战掌握高并发编程技巧,构建爬虫、内容聚合器等工具,涵盖 WaitGroup、超时控制、错误处理等核心知识点。
|
2月前
|
数据采集 JSON Go
Go语言实战案例:实现HTTP客户端请求并解析响应
本文是 Go 网络与并发实战系列的第 2 篇,详细介绍如何使用 Go 构建 HTTP 客户端,涵盖请求发送、响应解析、错误处理、Header 与 Body 提取等流程,并通过实战代码演示如何并发请求多个 URL,适合希望掌握 Go 网络编程基础的开发者。
|
3月前
|
JSON 前端开发 Go
Go语言实战:创建一个简单的 HTTP 服务器
本篇是《Go语言101实战》系列之一,讲解如何使用Go构建基础HTTP服务器。涵盖Go语言并发优势、HTTP服务搭建、路由处理、日志记录及测试方法,助你掌握高性能Web服务开发核心技能。
|
3月前
|
Go
如何在Go语言的HTTP请求中设置使用代理服务器
当使用特定的代理时,在某些情况下可能需要认证信息,认证信息可以在代理URL中提供,格式通常是:
261 0
|
4月前
|
JSON 编解码 API
Go语言网络编程:使用 net/http 构建 RESTful API
本章介绍如何使用 Go 语言的 `net/http` 标准库构建 RESTful API。内容涵盖 RESTful API 的基本概念及规范,包括 GET、POST、PUT 和 DELETE 方法的实现。通过定义用户数据结构和模拟数据库,逐步实现获取用户列表、创建用户、更新用户、删除用户的 HTTP 路由处理函数。同时提供辅助函数用于路径参数解析,并展示如何设置路由器启动服务。最后通过 curl 或 Postman 测试接口功能。章节总结了路由分发、JSON 编解码、方法区分、并发安全管理和路径参数解析等关键点,为更复杂需求推荐第三方框架如 Gin、Echo 和 Chi。
|
5月前
|
存储 JSON Go
Go语言之空接口与类型断言
本文介绍了 Go 语言中空接口(`interface{}`)和类型断言的核心概念及其应用。空接口可存储任意类型数据,适用于通用函数、动态数据结构与 JSON 解析等场景;类型断言用于将接口变量还原为具体类型,推荐使用带 `ok` 的写法以避免程序崩溃。此外,文章通过示例讲解了 `type switch` 类型判断与 JSON 处理技巧,并总结了空接口的注意事项,强调滥用可能导致类型安全性降低。内容深入浅出,帮助开发者灵活运用这些特性。
115 15
|
5月前
|
Go
Go语言接口的定义与实现
Go 语言的接口提供了一种灵活的多态机制,支持隐式实现和抽象编程。本文介绍了接口的基本定义、实现方式、空接口的使用、类型断言以及接口组合等核心概念,并探讨了接口与 nil 的关系及应用场景。通过示例代码详细说明了如何利用接口提升代码的可扩展性和可测试性,总结了接口的关键特性及其在依赖注入、规范定义和多态调用中的重要作用。
196 14