首页 > 其他分享 >Go基础之结构体,接口

Go基础之结构体,接口

时间:2024-12-29 09:01:06浏览次数:22  
标签:string fmt struct 接口 func Go type 结构

目录

1 结构体

1.1 简介

Go 语言中数组可以存储同一类型的数据,但在结构体中我们可以为不同项定义不同的数据类型。
结构体是由一系列具有相同类型或不同类型的数据构成的数据集合。

1.2 定义结构体

结构体定义需要使用 typestruct 语句。struct 语句定义一个新的数据类型,结构体中有一个或多个成员。type 语句设定了结构体的名称。结构体的格式如下:

type struct_variable_type struct {
   member definition
   member definition
   ...
   member definition
}

一旦定义了结构体类型,它就能用于变量的声明,语法格式如下:

variable_name := structure_variable_type {value1, value2...valuen}
或
variable_name := structure_variable_type { key1: value1, key2: value2..., keyn: valuen}

实例如下:

package main

import "fmt"

type Books struct {
   title string
   author string
   subject string
   book_id int
}


func main() {

    // 创建一个新的结构体
    fmt.Println(Books{"Go 语言", "www.test.com", "Go 语言教程", 6495407})

    // 也可以使用 key => value 格式
    fmt.Println(Books{title: "Go 语言", author: "www.test.com", subject: "Go 语言教程", book_id: 6495407})

    // 忽略的字段为 0 或 空
   fmt.Println(Books{title: "Go 语言", author: "www.test.com"})
}

1.3 声明结构体

1.3.1 new声明

在 Go 中,结构体可以通过 new 函数直接创建。new 是 Go 内置的函数,用来分配内存并返回指向该类型的指针。

new 的使用,其中 T 是结构体类型。

p := new(T)

new 为类型 T 分配一块内存(零值初始化),返回指向 T 的指针(类型为 *T

1.3.2 直接声明

除了使用 new,还可以通过直接声明或者字面量初始化结构体:

var b Book
fmt.Println(b) // 输出:{  0}

字面量初始化

b := Book{Title: "Go Programming", Author: "John Doe", Pages: 300}
nick := Person{"nick", 28, "nickli@xxx.com"}
fmt.Println(b) // 输出:{Go Programming John Doe 300}

1.3.3 与 new 的对比

方法 分配内存 返回值 是否为指针
new 返回指针,类型为 *T
直接声明 返回值,类型为 T
字面量初始化 返回值,类型为 T 或 *T(取决于语法) 可选

1.3.4 new 和 & 操作符的区别

通过 & 也可以创建结构体指针,它们的行为与 new 类似,但更灵活。

b := &Book{Title: "Go Programming", Author: "John Doe", Pages: 300}
fmt.Println(b) // 输出:&{Go Programming John Doe 300}

区别:

  • new 只能分配内存并返回零值的结构体指针。
  • & 可以用于字面量初始化,并返回指针,语法更简洁

1.3.5 结构体指针

可以定义指向结构体的指针类似于其他指针变量,格式如下:var struct_pointer *Books
定义的指针变量可以存储结构体变量的地址。查看结构体变量地址,可以将 & 符号放置于结构体变量前:struct_pointer = &Book1
使用结构体指针访问结构体成员,使用 . 操作符:struct_pointer.title

1.4 结构体标签

在 Go 中,结构体字段后面的反引号(`)部分是 结构体标签(Tag),用于为字段添加额外的元信息。这些标签可以在运行时通过反射(reflect 包)读取,以实现特定功能。

1.4.1 标签的语法

标签是一个字符串,必须用反引号包裹,一般格式是:key:"value",多个标签可以用空格分隔:

type Person struct {
    Name string `json:"name" xml:"name"`
    Age  int    `json:"age" db:"age_column"`
}

1.4.2 标签的工作机制

读取标签: 使用 Go 的 reflect 包可以读取结构体的标签。

package main

import (
    "fmt"
    "reflect"
)

type Person struct {
    Name string `json:"name" xml:"name_xml"`
    Age  int    `json:"age"`
}

func main() {
    t := reflect.TypeOf(Person{})
    for i := 0; i < t.NumField(); i++ {
        field := t.Field(i)
        fmt.Printf("Field: %s, JSON Tag: %s, XML Tag: %s\n",
            field.Name,
            field.Tag.Get("json"),
            field.Tag.Get("xml"),
        )
    }
}
输出:
Field: Name, JSON Tag: name, XML Tag: name_xml
Field: Age, JSON Tag: age, XML Tag:

1.4.3 常见用途

1.4.3.1 JSON 序列化和反序列化

标签 json:"name" 用于指定字段在 JSON 格式中的键名。常见于 encoding/json 包:

type Person struct {
    Name string `json:"name"`
    Age  int    `json:"age"`
}
func main() {
    p := Person{Name: "Alice", Age: 30}
    // 序列化为 JSON
    jsonData, _ := json.Marshal(p)
    fmt.Println(string(jsonData)) // 输出:{"name":"Alice","age":30}

    // 反序列化 JSON
    var p2 Person
    json.Unmarshal([]byte(`{"name":"Bob","age":25}`), &p2)
    fmt.Println(p2) // 输出:{Bob 25}
}

1.4.3.2 数据库映射

标签用于指定字段与数据库表中列名的映射,常见于 ORM 框架(如 GORM):

type User struct {
    ID   int    `gorm:"primaryKey"`
    Name string `gorm:"column:user_name"`
}

1.4.3.3 表单处理

用于指定字段的表单名称,常见于 net/http 包的表单解析:

type LoginForm struct {
    Username string `form:"username"`
    Password string `form:"password"`
}

1.5 结构体作为函数参数

可以像其他数据类型一样将结构体类型作为参数传递给函数。
注意:结构体是作为参数的值传递

package main

import "fmt"

type Books struct {
    title string
    author string
    subject string
    book_id int
}

func changeBook(book Books) {
    book.title = "book1_change"
}

func main() {
    var book1 Books
    book1.title = "book1"
    book1.author = "zuozhe"
    book1.book_id = 1
    changeBook(book1)
    fmt.Println(book1)
}

如果想在函数里面改变结果体数据内容,需要传入指针

package main

import "fmt"

type Books struct {
    title string
    author string
    subject string
    book_id int
}

func changeBook(book *Books) {
    book.title = "book1_change"
}

func main() {
    var book1 Books
    book1.title = "book1"
    book1.author = "zuozhe"
    book1.book_id = 1
    changeBook(&book1)
    fmt.Println(book1)
}

1.6 嵌套结构体

在结构体中嵌套结构体时使用匿名字段可以更加简便获取内层结构体的字段,当内层结构体的字段和外层结构体的字段没有重复时可以直接获取,如果有重复时需要加上内层结构体名才能正常获取
这其实也是 Go 实现继承的方式

如果结构体中的字段也是一个结构体,那么这个结构体字段称为提升字段(Promoted fields),因为可以从外部结构体变量直接访问结构体类型中的字段,就像这些字段原本属于外部结构体一样。

package main

import "fmt"

type foo struct {        
	field1 int
	field2 string
}

type bar struct {        
	foo
	field1 string
	field3 int
}

func main() {        
	foobar := bar{   }
	foobar.foo.field1 = 1
	foobar.field2 = "hello"
	foobar.field1 = "world"
	foobar.field3 = 2
	fmt.Printf("%v", foobar)
}

1.7 匿名

1.7.1 匿名结构体

可以定义一个没有类型名称的结构体,这种结构体叫做匿名结构体(Anonymous Structures

var employee struct {          
        firstName, lastName string
        age int
}

给匿名struct类型定义方法

var	cache = struct	{        			
	sync.Mutex				
	mapping	map[string]string 
}{        			
	mapping : make(map[string]string), 
}
func Lookup(key	string)	string	{        		
	cache.Lock() // sync.Mxter的方法
	v	:=	cache.mapping[key] 
	cache.Unlock()			
	return	v
}

声明的同时进行赋值

   emp3 := struct {
   		firstName, lastName string
        age, salary         int
    }{
    firstName: "Andreah",
        lastName:  "Nikola",
        age:       31,
        salary:    5000,
    }

1.7.2 匿名字段

在创建结构体时,我们也可以只指定类型而不指定字段名。这些字段被称为匿名字段
下面的代码片就创建了一个 Person,该结构体有俩个匿名字段 string 和 int。
尽管匿名字段并没有名称,默认情况下,它的数据类型就是它的字段名称。因此,Person 结构体拥有俩个字段,其名称分别为:string 和 int
数据类型定义,仅能存在一次,如果有两个string 或 两个int 的话会冲突

type Person struct {     
    string
    int
}

func main() {  
    p := Person{"Naveen", 50}
    fmt.Println(p)
}

1.8 构造函数

Go没有提供构造函数。如果一个类型的零值是不合法的,应该将该类型设置为不导出,防止在其他包中导出该类型,并且需要提供一个函数NewT(parameters)去初始化带参数的T类型的的变量。Go中的一个约定是,应该把创建 T 类型变量的函数命名为 NewT(parameters)。这就类似于构造器了。如果一个包只含有一种类型,按照 Go 的约定,应该把函数命名为 New(parameters), 而不是 NewT(parameters)

package employee

import (  
    "fmt"
)

type employee struct {          
    firstName   string
    lastName    string
    totalLeaves int
    leavesTaken int
}

func New(firstName string, lastName string, totalLeave int, leavesTaken int) employee {          
    e := employee {   
     firstName, lastName, totalLeave, leavesTaken}
    return e
}

func (e employee) LeavesRemaining() {          
    fmt.Printf("%s %s has %d leaves remaining", e.firstName, e.lastName, (e.totalLeaves - e.leavesTaken))
}

2 接口

2.1 简介

接口(interface)是 Go 语言中的一种类型,用于定义行为的集合,它通过描述类型必须实现的方法,规定了类型的行为契约。
接口把所有的具有共性的方法定义在一起,任何其他类型只要实现了这些方法就是实现了这个接口。

2.1.1 接口的特点

隐式实现:

  • Go 中没有关键字显式声明某个类型实现了某个接口。
  • 只要一个类型实现了接口要求的所有方法,该类型就自动被认为实现了该接口。

接口类型变量:

  • 接口变量可以存储实现该接口的任意值。
  • 接口变量实际上包含了两个部分:
    • 动态类型:存储实际的值类型。
    • 动态值:存储具体的值。
  • 零值接口:接口的零值是 nil
    一个未初始化的接口变量其值为 nil,且不包含任何动态类型或值。
  • 空接口:定义为 interface{},可以表示任何类型

2.1.2 常见用法

  • 多态:不同类型实现同一接口,实现多态行为。
  • 解耦:通过接口定义依赖关系,降低模块之间的耦合。
  • 泛化:使用空接口 interface{} 表示任意类型。

2.2 接口操作

2.2.1 接口定义

接口定义使用关键字 interface,其中包含方法声明。

定义接口
type interface_name interface {
   method_name1 [return_type]
   method_name2 [return_type]
   method_name3 [return_type]
   ...
   method_namen [return_type]
}

定义结构体
type struct_name struct {
   /* variables */
}

实现接口方法
func (struct_name_variable struct_name) method_name1() [return_type] {
   /* 方法实现 */
}
...
func (struct_name_variable struct_name) method_namen() [return_type] {
   /* 方法实现*/
}

示例

package main

import (
        "fmt"
        "math"
)

// 定义接口
type Shape interface {
        Area() float64
        Perimeter() float64
}

// 定义一个结构体
type Circle struct {
        Radius float64
}

// Circle 实现 Shape 接口
func (c Circle) Area() float64 {
        return math.Pi * c.Radius * c.Radius
}

func (c Circle) Perimeter() float64 {
        return 2 * math.Pi * c.Radius
}

func main() {
        c := Circle{Radius: 5}
        var s Shape = c // 接口变量可以存储实现了接口的类型
        fmt.Println("Area:", s.Area())
        fmt.Println("Perimeter:", s.Perimeter())
}

2.2.2 空接口

空接口 interface{} 是 Go 的特殊接口,表示所有类型超集,任意类型都实现了空接口。

常用于需要存储任意类型数据的场景,如泛型容器、通用参数等。
当接口变量的动态类型和动态值都为 nil 时,接口变量为 nil

接口变量实际上包含了两部分:

  • 动态类型:接口变量存储的具体类型
  • 动态值:具体类型的值

2.2.3 类型断言

类型断言用于从接口类型中提取其底层值。
基本语法:value := iface.(Type)

  • iface:是接口变量。
  • Type:是要断言的具体类型。
    如果类型不匹配,会触发 panic。

为了避免 panic,可以使用带检查的类型断言:value, ok := iface.(Type)
ok 是一个布尔值,表示断言是否成功。如果断言失败,value 为零值,ok 为 false。

func main() {        
    var a interface{  }
    var b int
    a = b
    // 断言赋值
    c := a.(int)
    fmt.Printf("类型:%T\n",c)
}

2.2.4 类型选择(type switch)

type switch 是 Go 中的语法结构,用于根据接口变量的具体类型执行不同的逻辑。

package main

import "fmt"

func printType(val interface{}) {
        switch v := val.(type) {
        case int:
                fmt.Println("Integer:", v)
        case string:
                fmt.Println("String:", v)
        case float64:
                fmt.Println("Float:", v)
        default:
                fmt.Println("Unknown type")
        }
}

func main() {
        printType(42)
        printType("hello")
        printType(3.14)
        printType([]int{1, 2, 3})
}

2.3 接口与结构体

接口定义行为:

  • 接口定义了一组方法。
  • 一个结构体(或其他类型)只要实现了接口中定义的所有方法,就自动满足接口(称为接口的实现)。
  • 不需要显式地声明继承实现关系。

Go 的接口机制特性

  • 隐式实现:
    不需要显式声明结构体实现了某个接口,只要满足方法签名即可。
    这种设计避免了类和接口之间的紧耦合。
  • 多态:
    通过接口实现动态行为。
    结构体赋值给接口变量后,接口变量可以动态调用结构体实现的方法。
  • 接口变量:
    接口变量实际上由两部分组成:
    动态类型:接口实际存储的值的类型。
    动态值:实际存储的值。
var a Animal
a = Dog{}
fmt.Printf("动态类型:%T, 动态值:%v\n", a, a) // 动态类型:main.Dog, 动态值:{}

2.4 示例

2.4.1 接口传参

package main

import "fmt"

type Phone interface {
    call(param int) string
    takephoto()
}

type Huawei struct {
}

func (huawei Huawei) call(param int) string{
    fmt.Println("i am Huawei, i can call you!", param)
    return "damon"
}

func (huawei Huawei) takephoto() {
    fmt.Println("i can take a photo for you")
}

func main(){
    var phone Phone
    phone = new(Huawei)
    phone.takephoto()
    r := phone.call(50)
    fmt.Println(r)
}

2.4.2 修改属性

若想要通过接口方法修改属性,需要在传入指针的结构体才行

type fruit interface{
    getName() string   
    setName(name string)
}
type apple struct{   
    name string
}

func (a *apple) getName() string{   
    return a.name
}

func (a *apple) setName(name string) {
   a.name = name
}
func testInterface(){   
    a:=apple{"红富士"}   
    fmt.Print(a.getName())   
    a.setName("树顶红")   
    fmt.Print(a.getName())
}

2.4.3 多态示例

为不同数据类型的实体提供统一的接口

package main
import "fmt"
// 学生
type Student struct {     
	Name string
	Age int
	Score float32
}
// 教师
type Teacher struct {        
	Name string
	Age int
	Class int
}
// 接口定义
type Test interface {        
	Print()
	Sleep()
}
// 学生实现接口
func (p Student) Print() {        
	fmt.Println("name:",p.Name)
	fmt.Println("age:",p.Age)
	fmt.Println("score:",p.Score)
}

func (p Student) Sleep() {       
	fmt.Println("学生在睡觉")
}

// 教师实现接口
func (t Teacher) Print() {        
	fmt.Println("name:",t.Name)
	fmt.Println("age:",t.Age)
	fmt.Println("class:",t.Class)
}

func (t Teacher) Sleep() {        
	fmt.Println("教师在休息")
}

func main() {        
	var t Test
	var stu Student = Student {   
		Name: "zhangsan",
		Age: 18,
		Score: 90,
	}
	var tea Teacher = Teacher {        
		Name: "lisi",
		Age: 50,
		Class: 01,
	}

	// 多态表现
	t = stu
	t.Print()
	t.Sleep()
	fmt.Println("---------------------------")
	t = tea
	t.Print()
	t.Sleep()
}
 
name: zhangsan
age: 18
score: 90
学生在睡觉
---------------------------
name: lisi
age: 50
class: 1
教师在休息

标签:string,fmt,struct,接口,func,Go,type,结构
From: https://www.cnblogs.com/jingzh/p/18638384

相关文章

  • Go 并发之goroutine和Channel讲解
    目录1并发1.1简介1.2Goroutine1.2.1简介1.2.2特点1.2.3检测数据访问冲突1.2.4示例1.3通道(Channel)1.3.1普通通道1.3.1.1简介1.3.1.2声明通道1.3.1.3普通通道示例1.3.2带缓冲区通道1.3.2.1简介1.3.2.2带缓冲区通道示例1.3.3遍历1.3.3.1for遍历1.3.3.2range遍历......
  • Go基础之指针和反射讲解
    目录1指针1.1简介1.2使用指针1.3指针优化输出1.3.1优化输出复杂类型1.3.2去掉优化1.3.3基本类型1.4指针数组1.4.1指针数组优化1.5指向指针的指针1.6向函数传递指针参数2反射2.1reflect2.1.1示例2.2获取变量值ValueOf2.3修改变量值Value.Set2.3.1Elem方法2.3.2......
  • Go IO之文件处理,TCP&UDP讲解
    目录1文件处理1.1打开和关闭文件1.2读取文件1.2.1简单示例1.2.2中文乱码1.2.2.1bufio1.2.2.2ioutil1.3写入文件1.3.1Write和WriteString1.3.2fmt.Fprintln1.3.2.1写入文件1.3.2.2写入标准输出1.3.3bufio.NewWriter1.3.4ioutil.WriteFile2TCP&UDP2.1TCP2.1.1服......
  • Go 并发之WaitGroup,并发锁,Context
    目录1Go并发1.1WaitGroup1.2并发锁1.2.1互斥锁1.2.2读写互斥锁1.2.3sync.Once1.2.4sync.Map1.3Context1.3.1简介1.3.2主要功能1.3.3使用示例1.3.3.1取消信号1.3.3.2设置超时1.3.3.3传递值1Go并发1.1WaitGroupsync.WaitGroup是Go标准库提供的一种同步原语,常......
  • 数据结构之线性表之链表(附加一个考研题)
    链表的定义链表的结构:单链表-初始化代码实现:单链表-头插法代码实现:这里我给大家分析一下我们每创建一个新的节点都要插在头节点的后面,我们一定要注意顺序一定要先让新节点指向头节点指向的下一个节点,再让头节点指向新的节点单链表-遍历代码实现:代码分析:这里我......
  • 数据结构之栈和队列
    栈的定义:我们要记住这8个字,先进后出,后进先出我们对于栈的操作只有两个,进栈和出栈栈的顺序结构初始化:(和顺序表差不多)代码实现:栈的顺序结构进栈:代码实现:栈的顺序结构出栈:代码实现:这里解释一下,让下标减一,下次进行进栈的时候就直接覆盖了,和顺序表的原理差不多获取栈......
  • 链表 实现复杂的数据结构
    `#includeusingnamespacestd;structNode{intdata;Node*next;};Node*CreateNode(intdata){Node*newNode=newNode();newNode->data=data;newNode->next=NULL;returnnewNode;}voidtraverseLinkedList(Node*head){Node*current=head;while(curre......
  • client-go InClusterConfig方法
    InClusterConfig方法packagemainimport( "context" "test/signals" "time" "os" core_v1"k8s.io/api/core/v1" metav1"k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes......
  • 【设计模式与体系结构】创建型模式-建造者模式
    简介建造者模式指的是将一个复杂对象的创建与表示分离,使得同样的创建过程可以创建不同的表示,分离了部件的构造(由Builder负责)和装配(由Director负责)。从而可以构造出复杂的对象,这个模式适用于某个对象的构建过程复杂的情况。由于实现了构建和装配的解耦,不同的构建器,相同的装配,......
  • 全国青少年信息学奥林匹克竞赛(信奥赛)备考实战之循环结构(for循环语句)(三)
    在C++程序中,累乘的思想应用很广泛,很多情况下累加、累乘和累除相互结合使用可以解决很多问题。实战训练1—求阶乘问题描述:给定正整数n,求从1到n的每一个整数的阶乘。输入格式:输入一行,包含一个正整数(1<n≤12)。输出格式:输出n行,每行有两个数,分别是i和i的阶乘,两个......