【Go语言】go语言简单的变量声明和结构体使用

目录

数字类型

布尔类型

字符串类型

[Rune 类型](#Rune 类型)

[Byte 类型](#Byte 类型)

类型转换

结构体

结构体定义

结构体作为函数参数

结构体方法

[1. 接收者类型](#1. 接收者类型)

[2. 方法定义](#2. 方法定义)

[3. 值接收者 vs 指针接收者](#3. 值接收者 vs 指针接收者)

[4. 调用方式的灵活性](#4. 调用方式的灵活性)

[5. 方法集](#5. 方法集)

[6. 方法与继承](#6. 方法与继承)

[7. 方法声明的位置](#7. 方法声明的位置)


在Go语言中,基础类型(也称为原始类型)是构建更复杂数据结构的基石。Go语言的基础类型可以分为几个主要类别,包括数字类型、布尔类型、和字符串类型。下面,我将为您详细介绍这些类型及其声明方法,帮助您更好地理解和使用它们。

数字类型

在Go语言中,整数类型分为有符号和无符号,各自的范围也不同。这里给出一些整数类型的声明示例:

Go 复制代码
	var i int = 1
	var i8 int8 = 127
	var i16 int16 = 32767
	var i32 int32 = -2147483648
	var i64 int64 = 9223372036854775807
	
	var u uint = 1
	var u8 uint8 = 255
	var u16 uint16 = 65535
	var u32 uint32 = 4294967295
	var u64 uint64 = 18446744073709551615
	
	

	var f32 float32 = 3.1415926
	var f64 float64 = 3.1414926564565

布尔类型

布尔类型表示逻辑值,只有truefalse两个值。

Go 复制代码
	var b bool = true
	if b {
		fmt.Println("b 是真")
	} else {
		fmt.Println("b 是假")
	}

字符串类型

Go 复制代码
var s string = "Hello, Go!" // 字符串类型

Rune 类型

rune类型代表一个Unicode码点。它是int32的别名,用于处理Unicode字符。

Go 复制代码
var r rune = '中' // 使用单引号声明rune类型,代表一个Unicode字符
fmt.Printf("%T, %d\n", r, r) // 输出: int32, 20013('中'的Unicode码点)

Byte 类型

byte类型是uint8的别名,常用于处理ASCII字符或字节数据。

Go 复制代码
var b byte = 'A' // 使用单引号声明byte类型,代表一个ASCII字符
fmt.Printf("%T, %d\n", b, b) // 输出: uint8, 65('A'的ASCII码)

类型转换

Go语言中的类型转换需要显式进行。这是因为Go语言在设计时强调类型安全,避免了隐式类型转换可能带来的问题。

Go 复制代码
var i int = 42
var f float64 = float64(i) // 将int类型显式转换为float64类型
var u uint = uint(f) // 将float64类型显式转换为uint类型
fmt.Println(i, f, u) // 输出: 42 42 42

结构体

结构体定义

Go 复制代码
type Person struct {
	name string
	Age  int
}

结构体作为函数参数

Go 复制代码
type Person struct {
	name string
	Age  int
}

func testPersion(p1 Person) Person {
	var p Person
	p.name = "Alice"
	p.Age = 18
	return p
}

结构体方法

Go 复制代码
package main

import "fmt"

type Circle struct {
    Radius float64
}

// Circle类型的方法,计算圆的面积
func (c Circle) Area() float64 {
    return 3.14 * c.Radius * c.Radius
}

func main() {
    circle := Circle{Radius: 5}
    fmt.Println("Circle area:", circle.Area()) // 输出:Circle area: 78.5
}

1. 接收者类型

  • 值接收者:方法通过值接收其所属类型的一个副本。这意味着方法内部对接收者的任何修改都不会影响原始实例。
  • 指针接收者:方法通过指针接收其所属类型,允许直接修改原始实例的字段。使用指针接收者还可以避免在调用方法时复制大型结构体,从而提高效率。

2. 方法定义

方法定义与函数类似,但在函数名之前添加了一个额外的参数,称为接收者,表示该方法附加到的类型。

3. 值接收者 vs 指针接收者

  • 使用值接收者定义的方法在调用时,接收者会被复制一份。这适用于小型结构体或你不希望方法能修改其接收者的场景。
  • 使用指针接收者可以让方法修改其接收者指向的值。这对于需要修改接收者,或避免在每次方法调用时复制大型结构体时非常有用。

4. 调用方式的灵活性

  • 即使方法的接收者是值类型,你也可以使用指针来调用该方法,Go语言会自动解引用。
  • 同样,如果方法的接收者是指针类型,你也可以直接使用值类型调用该方法,Go语言会自动取地址。

这个特性使得Go语言在处理方法调用时非常灵活。下面是一个示例,展示了如何定义和使用值接收者和指针接收者的方法,以及如何通过值类型和指针类型来调用这些方法。

Go 复制代码
package main

import "fmt"

type MyStruct struct {
    Value int
}

// 使用值接收者定义的方法
func (s MyStruct) ValueReceiverMethod() {
    fmt.Printf("ValueReceiverMethod 被调用, Value: %d\n", s.Value)
}

// 使用指针接收者定义的方法
func (s *MyStruct) PointerReceiverMethod() {
    fmt.Printf("PointerReceiverMethod 被调用, Value: %d\n", s.Value)
}

func main() {
    value := MyStruct{Value: 5}

    // 通过值类型调用值接收者的方法
    value.ValueReceiverMethod()

    // 通过值类型调用指针接收者的方法
    // Go语言自动取value的地址,使其符合方法的接收者类型
    value.PointerReceiverMethod()

    pointer := &MyStruct{Value: 10}

    // 通过指针类型调用值接收者的方法
    // Go语言自动解引用pointer,使其符合方法的接收者类型
    pointer.ValueReceiverMethod()

    // 通过指针类型调用指针接收者的方法
    pointer.PointerReceiverMethod()
}

在这个示例中,MyStruct定义了两个方法:ValueReceiverMethod使用值接收者,而PointerReceiverMethod使用指针接收者。

  • ValueReceiverMethod通过结构体实例的值调用时,Go语言直接使用这个值。
  • PointerReceiverMethod通过结构体实例的值调用时,Go自动取这个值的地址,以适应方法的指针接收者。
  • 同样,当ValueReceiverMethod通过结构体的指针调用时,Go自动解引用这个指针,以适应方法的值接收者。
  • PointerReceiverMethod通过结构体的指针调用时,直接使用这个指针,因为它与方法的接收者类型匹配。

5. 方法集

  • 值类型的变量可以调用值接收者和指针接收者的方法。
  • 指针类型的变量也可以调用值接收者和指针接收者的方法。
  • 但是,接口类型的变量只能调用它的方法集内的方法。值接收者方法属于类型的值和指针的方法集,而指针接收者方法只属于类型指针的方法集

代码定义:

Go 复制代码
package main

import "fmt"

// Shaper 接口,包含一个方法 ShapeName
type Shaper interface {
    ShapeName() string
}

// Square 结构体
type Square struct {
    Name string
}

// Circle 结构体
type Circle struct {
    Name string
}

// 使用值接收者实现 Shaper 接口的 ShapeName 方法
func (s Square) ShapeName() string {
    return "Square: " + s.Name
}

// 使用指针接收者实现 Shaper 接口的 ShapeName 方法
func (c *Circle) ShapeName() string {
    return "Circle: " + c.Name
}
Go 复制代码
func main() {
    square := Square{Name: "MySquare"}
    circle := Circle{Name: "MyCircle"}

    // 值类型的Square可以直接赋值给Shaper接口
    var s Shaper = square
    fmt.Println(s.ShapeName())

    // 指针类型的Circle也可以赋值给Shaper接口
    var c Shaper = &circle
    fmt.Println(c.ShapeName())

    // 注意: 直接使用值类型的Circle赋值给Shaper接口会引发编译错误
    // var cError Shaper = circle
    // fmt.Println(cError.ShapeName())

    // 直接通过值类型调用指针接收者的方法是可以的,Go会自动取地址
    fmt.Println(circle.ShapeName())

    // 通过指针类型调用值接收者的方法也是可以的,Go会自动解引用
    circlePtr := &square
    fmt.Println(circlePtr.ShapeName())
}
  • Square使用值接收者实现了Shaper接口的ShapeName方法,所以Square的值和指针类型都可以赋值给Shaper接口变量。
  • Circle使用指针接收者实现了Shaper接口的ShapeName方法,所以只有Circle的指针类型可以赋值给Shaper接口变量。如果尝试将Circle的值类型直接赋值给接口变量,会引发编译错误,因为Circle值类型的方法集不包括ShapeName方法。
  • 这个例子展示了如何根据方法的接收者类型(值接收者或指针接收者)以及它们如何匹配接口的方法集,来决定是否可以将一个类型的实例赋值给接口。

通过这个例子,你应该能更清楚地理解方法集和接口之间的关系,以及它们是如何影响Go语言中类型的行为的。掌握这些概念对于高效使用Go语言是非常重要的。继续探索和实践,你将能更深入地理解Go语言的面向对象特性。

6. 方法与继承

Go语言没有提供传统的继承机制,但可以通过嵌入结构体来实现类似继承的效果。方法也可以通过这种方式被嵌入的结构体所继承。

在Go语言中,虽然没有类似于传统面向对象语言的继承机制,但通过结构体的嵌入(有时也称为组合)和接口,我们可以实现类似继承的功能,包括方法的重用。这种方式提供了更灵活和更安全的代码组织形式。下面是一个示例,演示如何通过嵌入结构体来实现方法的"继承"。

假设我们有一个Car结构体,它有一个Drive方法,然后我们希望ElectricCar结构体"继承"Car的方法。

Go 复制代码
package main

import "fmt"

// Car 基础结构体
type Car struct {
    Name string
}

// Car的Drive方法
func (c Car) Drive() {
    fmt.Println(c.Name, "is driving.")
}

// ElectricCar 嵌入了Car
type ElectricCar struct {
    Car   // 嵌入Car结构体
    BatteryLife int
}

func main() {
    eCar := ElectricCar{
        Car:        Car{Name: "Tesla Model S"},
        BatteryLife: 300,
    }

    // 通过ElectricCar直接调用Car的Drive方法
    eCar.Drive()

    // 也可以通过嵌入的Car结构体字段调用
    eCar.Car.Drive()

    // 访问ElectricCar的字段
    fmt.Println("Battery life:", eCar.BatteryLife)

    // 访问嵌入的Car结构体的字段
    fmt.Println("Car name:", eCar.Name) // 直接访问,Go自动解析到eCar.Car.Name
    fmt.Println("Car name:", eCar.Car.Name) // 显式访问
}
  • ElectricCar通过嵌入Car结构体,"继承"了Car的方法和属性。这意味着,即使没有显式定义Drive方法,ElectricCar的实例也能调用Drive方法。
  • 通过这种嵌入机制,我们可以很容易地重用Car的功能,同时为ElectricCar添加特有的属性和方法,例如BatteryLife属性。
  • 你可以通过嵌入结构体的实例直接访问其公开的字段和方法,就像它们是外层结构体的一部分一样。在上面的示例中,eCar.Name实际上是对eCar.Car.Name的访问简写。
  • 如果需要,嵌入的结构体也可以被显式地引用,如eCar.Car.Drive()

这种方式允许Go语言开发者在没有传统类继承的情况下实现非常灵活的类型设计。通过组合和接口,你可以构建出清晰、易于理解和维护的代码结构。这种方法强调的是,"有什么"(组合)而不是"是什么"(继承),更加注重对象的能力而非其身份。继续探索Go的类型系统,你会发现更多有趣且实用的设计模式。

7. 方法声明的位置

方法可以在类型定义内部或外部声明,但是接收者的类型必须在同一个包内。这意味着你不能为内置类型或来自其他包的类型定义方法。

为自定义类型声明方法

Go 复制代码
package main

import "fmt"

// 在main包中定义了一个简单的结构体类型Person
type Person struct {
    Name string
}

// 为Person类型声明一个方法SayHello,这是允许的,因为Person在同一个包内定义
func (p Person) SayHello() {
    fmt.Println("Hello,", p.Name)
}

func main() {
    person := Person{Name: "张三"}
    person.SayHello() // 调用SayHello方法
}

在这个例子中,Person类型和它的方法SayHello都是在main包中定义的。这完全符合Go语言的规则。

尝试为内置类型或其他包的类型添加方法

假设我们想为内置的int类型添加一个新方法。尝试这样做会遇到编译错误,因为int是一个内置类型,不在用户自定义的包中。

Go 复制代码
func (i int) IsPositive() bool {
    return i > 0
}

同样地,如果你试图为来自其他包的类型定义方法,也会遇到问题。例如,尝试为time.Time类型(定义在time包中)添加一个方法:

Go 复制代码
package main

import (
    "fmt"
    "time"
)

// 尝试为time包中的Time类型添加一个新方法Display
func (t time.Time) Display() {
    fmt.Println("The time is:", t)
}

func main() {
    now := time.Now()
    now.Display() // 这里会引发编译错误
}

上述代码尝试为time.Time类型添加一个Display方法。因为Time类型定义在time包中,而不是当前的包(即使是main包),所以这将导致编译错误。

这些限制确保了Go语言包的封装性和独立性,防止了对已有类型的随意扩展,促进了代码的清晰性和维护性。要为其他包中的类型添加新的行为,通常的做法是在你自己的包中定义一个新的类型,这个新类型可以嵌入原有类型或者作为原有类型的一个字段存在。

相关推荐
IT_陈寒3 分钟前
Java线程池用错参数,我的服务居然悄悄崩溃了
前端·人工智能·后端
光影少年15 分钟前
Express 中间件原理
后端·node.js·express
aramae1 小时前
MySQL内置函数(7)
开发语言·笔记·后端·mysql·其他
徐小夕2 小时前
存量.doc 文档怎么办?JitWord 开源转换库打通旧文档到在线协同全链路
后端·架构·github
ee又momo2 小时前
一次 JWT Token 过期续期的踩坑记录
后端
五彩小白2 小时前
GO语言装饰器语法
golang
二月龙2 小时前
App 瘦身实战:图片、so 库、资源压缩,安装包体积减小 40%
后端
大勇前进2 小时前
Kotlin 协程实战避坑:90% 的人都会踩的生命周期泄漏问题
后端
苏三说技术3 小时前
Spring AI、LangChain4j、AgentScope、Embabel,哪个AI框架更好?
后端
烽学长3 小时前
(附源码)基于Springboot+vue的图书阅读分享系统的设计与实现
java·spring boot·后端