Java开发已经是红海一片,面临着35岁危机的压力,需要适时的调整策略,以应对可能会出现的不确定性。毕竟,命运掌握在自己手里,比掌握在公司手里 安全感会强很多。
尝试的其中一条路即为:Java转Go,海外开发web3相关,也有其他的尝试,会开辟相应的专栏收录。
针对每一个部分,都会在最后准备练习题
,可以多做几遍,用于巩固知识,多动手!
后续golang语言学习过程中产生的全部内容,会发布在 web3这个专栏中。
Go语言基础快速突破[面向对象编程](1-2周)
1. 类型系统
1.1 为类型添加方法
在Go语言中,方法是作用在特定类型上的函数。通过func
关键字和接收者(receiver)定义,接收者可以是任何用户定义的类型。
示例代码
go
type MyInt int
// 方法定义
func (myInt MyInt) Double() int {
return int(myInt * 2)
}
func main() {
a := MyInt(5)
fmt.Println(a.Double()) // 输出: 10
}
注意事项
- 方法必须定义在与类型相同的包中。
- 方法可以定义在结构体上,也可以定义在其他用户自定义类型上。
1.2 值语义和引用语义
Go语言中,类型的方法可以是指针接收者或值接收者。这决定了方法调用时的参数传递方式。
值语义
值接收者在方法调用时会复制整个对象。
go
type Point struct{ X, Y int }
func (p Point) Move(dx, dy int) {
p.X += dx
p.Y += dy
}
func main() {
p := Point{1, 2}
p.Move(2, 3)
fmt.Println(p) // 输出: {1 2}
}
引用语义
指针接收者在方法调用时只复制指针(地址)。
go
type Point struct{ X, Y int }
func (p *Point) Move(dx, dy int) {
p.X += dx
p.Y += dy
}
func main() {
p := &Point{1, 2}
p.Move(2, 3)
fmt.Println(p) // 输出: &{3 5}
}
何时使用
- 如果方法需要修改接收者的值,或者接收者是一个大对象,推荐使用指针接收者。
- 如果方法不需要修改接收者,且接收者是小对象,使用值接收者即可。
1.3 结构体
Go语言中的结构体(struct
)是复合数据类型,用于组合不同类型的字段。结构体可以拥有方法,支持嵌入(embedding),从而实现类似继承的效果。
定义结构体
go
type Person struct {
FirstName string
LastName string
Age int
}
初始化结构体
go
p := Person{
FirstName: "John",
LastName: "Doe",
Age: 30,
}
// 或者
p := new(Person)
p.FirstName = "John"
p.LastName = "Doe"
p.Age = 30
结构体的方法
go
func (p Person) FullName() string {
return p.FirstName + " " + p.LastName
}
func (p *Person) SetAge(age int) {
p.Age = age
}
嵌入(Embedding)
Go语言通过嵌入实现类似继承的功能,但更灵活。
go
type Animal struct {
Name string
}
func (a Animal) Speak() {
fmt.Println("Hello, I am", a.Name)
}
type Dog struct {
Animal // 嵌入Animal
Breed string
}
func main() {
dog := Dog{
Animal: Animal{Name: "Buddy"},
Breed: "Golden Retriever",
}
dog.Speak() // 输出: Hello, I am Buddy
}
结构体的可见性
- 首字母大写表示该字段或方法是导出的(public)。
- 首字母小写表示该字段或方法是非导出的(private)。
go
type Person struct {
PublicField string // 导出
privateField string // 非导出
}
匿名结构体
匿名结构体没有名字,通常用于临时场景。
go
p := struct {
FirstName string
LastName string
}{"John", "Doe"}
fmt.Println(p.FirstName, p.LastName) // 输出: John Doe
2. 初始化
Go语言提供了多种初始化结构体的方式,每种方式都有其适用场景。
字段名初始化
使用字段名进行初始化,这是最清晰的方式。
go
type Person struct {
FirstName string
LastName string
Age int
}
// 字段名初始化
p1 := Person{
FirstName: "John",
LastName: "Doe",
Age: 30,
}
顺序初始化
按照结构体字段定义的顺序进行初始化。
go
// 顺序初始化(必须按字段顺序)
p2 := Person{"Jane", "Smith", 25}
零值初始化
结构体的零值是所有字段的零值。
go
// 零值初始化
var p3 Person
fmt.Println(p3) // 输出: { 0}
new函数初始化
使用new
函数创建结构体指针。
go
// new函数初始化
p4 := new(Person)
p4.FirstName = "Bob"
p4.LastName = "Johnson"
p4.Age = 35
3. 匿名组合
Go语言通过嵌入实现代码复用,这是一种组合而非继承的方式。
基本嵌入
将一个结构体嵌入到另一个结构体中。
go
type Animal struct {
Name string
Age int
}
func (a Animal) Describe() string {
return fmt.Sprintf("I am %s, %d years old", a.Name, a.Age)
}
type Dog struct {
Animal // 嵌入Animal结构体
Breed string
}
func main() {
dog := Dog{
Animal: Animal{Name: "Buddy", Age: 3},
Breed: "Golden Retriever",
}
fmt.Println(dog.Describe()) // 输出: I am Buddy, 3 years old
fmt.Println(dog.Name) // 直接访问嵌入字段
}
方法提升
嵌入结构体的方法会被提升到外层结构体。
go
type Cat struct {
Animal
Color string
}
func (c Cat) Meow() {
fmt.Println("Meow!")
}
func main() {
cat := Cat{
Animal: Animal{Name: "Whiskers", Age: 2},
Color: "Orange",
}
cat.Describe() // 调用Animal的方法
cat.Meow() // 调用Cat自己的方法
}
字段冲突处理
当嵌入的结构体有相同字段名时,需要明确指定。
go
type Base struct {
Name string
}
type Derived1 struct {
Base
Name string // 与Base.Name冲突
}
type Derived2 struct {
Base
Name string
}
func main() {
d1 := Derived1{
Base: Base{Name: "BaseName"},
Name: "DerivedName",
}
fmt.Println(d1.Name) // 输出: DerivedName
fmt.Println(d1.Base.Name) // 输出: BaseName
}
4. 可见性
Go语言通过大小写来控制字段和方法的可见性。
导出字段(Public)
首字母大写的字段和方法可以被其他包访问。
go
// person.go
package main
type Person struct {
FirstName string // 导出字段
LastName string // 导出字段
age int // 非导出字段
}
func (p Person) GetAge() int { // 导出方法
return p.age
}
func (p *Person) setAge(age int) { // 非导出方法
p.age = age
}
非导出字段(Private)
首字母小写的字段和方法只能在当前包内访问。
go
// 在同一个包内可以访问非导出字段
func (p *Person) SetAge(age int) {
p.age = age // 可以访问非导出字段
}
跨包访问示例
go
// main.go
package main
import "otherpackage"
func main() {
p := otherpackage.Person{
FirstName: "John", // 可以访问
LastName: "Doe", // 可以访问
// age: 30, // 编译错误:无法访问非导出字段
}
age := p.GetAge() // 可以访问导出方法
// p.setAge(30) // 编译错误:无法访问非导出方法
}
5. 接口
Go语言的接口是一种抽象类型,定义了对象的行为规范。
5.1 其他语言的接口
Java接口
Java中的接口是显式声明的,类必须明确实现接口。
java
interface Animal {
void speak();
}
class Dog implements Animal {
public void speak() {
System.out.println("Woof!");
}
}
Go接口
Go语言的接口是隐式实现的,只要类型实现了接口的所有方法,就自动实现了该接口。
go
type Animal interface {
Speak()
}
type Dog struct{}
func (d Dog) Speak() {
fmt.Println("Woof!")
}
// Dog自动实现了Animal接口,无需显式声明
5.2 非侵入式接口
Go语言的接口设计是非侵入式的,这意味着:
接口定义
接口只定义方法签名,不包含实现。
go
type Reader interface {
Read(p []byte) (n int, err error)
}
type Writer interface {
Write(p []byte) (n int, err error)
}
隐式实现
类型不需要显式声明实现了某个接口。
go
type File struct {
name string
}
func (f *File) Read(p []byte) (n int, err error) {
// 实现读取逻辑
return len(p), nil
}
func (f *File) Write(p []byte) (n int, err error) {
// 实现写入逻辑
return len(p), nil
}
// File自动实现了Reader和Writer接口
优势
- 解耦:接口定义者和实现者可以独立演化
- 灵活性:可以为任何类型定义接口
- 可测试性:易于进行单元测试
5.3 接口赋值
接口变量可以存储任何实现了该接口的类型值。
基本赋值
go
type Animal interface {
Speak()
}
type Dog struct{}
func (d Dog) Speak() {
fmt.Println("Woof!")
}
type Cat struct{}
func (c Cat) Speak() {
fmt.Println("Meow!")
}
func main() {
var animal Animal
animal = Dog{}
animal.Speak() // 输出: Woof!
animal = Cat{}
animal.Speak() // 输出: Meow!
}
接口作为参数
go
func MakeSpeak(a Animal) {
a.Speak()
}
func main() {
MakeSpeak(Dog{}) // 输出: Woof!
MakeSpeak(Cat{}) // 输出: Meow!
}
接口切片
go
func main() {
animals := []Animal{Dog{}, Cat{}, Dog{}}
for _, animal := range animals {
animal.Speak()
}
// 输出:
// Woof!
// Meow!
// Woof!
}
5.4 接口查询
接口查询用于检查接口变量中存储的具体类型。
类型断言
go
type Animal interface {
Speak()
}
type Dog struct{}
func (d Dog) Speak() {
fmt.Println("Woof!")
}
func (d Dog) Fetch() {
fmt.Println("Fetching ball...")
}
func main() {
var animal Animal = Dog{}
// 类型断言
if dog, ok := animal.(Dog); ok {
dog.Fetch() // 输出: Fetching ball...
}
// 类型断言失败
if cat, ok := animal.(Cat); !ok {
fmt.Println("animal is not a Cat")
}
}
类型开关
go
func describeAnimal(a Animal) {
switch v := a.(type) {
case Dog:
fmt.Println("This is a Dog")
v.Fetch()
case Cat:
fmt.Println("This is a Cat")
default:
fmt.Printf("Unknown animal type: %T\n", v)
}
}
5.5 类型查询
使用反射进行类型查询。
反射类型查询
go
import "reflect"
func main() {
var animal Animal = Dog{}
// 使用反射获取类型
t := reflect.TypeOf(animal)
fmt.Println("Type:", t)
// 检查是否实现了某个接口
if t.Implements(reflect.TypeOf((*Animal)(nil)).Elem()) {
fmt.Println("animal implements Animal interface")
}
}
5.6 接口组合
接口可以通过组合形成更复杂的接口。
基本组合
go
type Reader interface {
Read(p []byte) (n int, err error)
}
type Writer interface {
Write(p []byte) (n int, err error)
}
type Closer interface {
Close() error
}
// 组合接口
type ReadWriter interface {
Reader
Writer
}
type ReadWriteCloser interface {
Reader
Writer
Closer
}
实际应用
go
type File struct {
name string
}
func (f *File) Read(p []byte) (n int, err error) {
return len(p), nil
}
func (f *File) Write(p []byte) (n int, err error) {
return len(p), nil
}
func (f *File) Close() error {
return nil
}
func main() {
var file ReadWriteCloser = &File{"test.txt"}
// file可以用于任何需要Reader、Writer或Closer的地方
data := make([]byte, 100)
file.Read(data)
file.Write(data)
file.Close()
}
5.7 Any类型
Go 1.18引入了泛型,any
类型是interface{}
的别名。
基本用法
go
func PrintAny(value any) {
fmt.Printf("Value: %v, Type: %T\n", value, value)
}
func main() {
PrintAny(42) // Value: 42, Type: int
PrintAny("hello") // Value: hello, Type: string
PrintAny(Dog{}) // Value: {}, Type: main.Dog
}
泛型函数
go
func Min[T any](a, b T) T {
// 这里需要类型约束,any类型无法比较
return a
}
// 使用类型约束
func Min[T comparable](a, b T) T {
if a < b {
return a
}
return b
}
func main() {
fmt.Println(Min(3, 5)) // 3
fmt.Println(Min("a", "b")) // a
}
类型约束
go
type Number interface {
~int | ~float64
}
func Sum[T Number](numbers []T) T {
var sum T
for _, n := range numbers {
sum += n
}
return sum
}
func main() {
ints := []int{1, 2, 3, 4, 5}
fmt.Println(Sum(ints)) // 15
floats := []float64{1.1, 2.2, 3.3}
fmt.Println(Sum(floats)) // 6.6
}
面向对象编程部分练习题
go
package main
import (
"errors"
"fmt"
"reflect"
)
// 3.1 类型系统
// 3.1.1 为类型添加方法
// Exercise: 定义一个名为 Counter 的整数类型,并为其添加 Increment 和 Decrement 方法。
type Counter int
func (c *Counter) Increment() {
*c++
}
func (c *Counter) Decrement() {
*c--
}
func exercise3_1_1() {
var counter Counter = 5
counter.Increment()
fmt.Printf("After increment: %d\n", counter) // 应输出 6
counter.Decrement()
fmt.Printf("After decrement: %d\n", counter) // 应输出 5
}
// 3.1.2 值语义和引用语义
// Exercise: 定义一个结构体 Point,并实现两个版本的 Move 方法:一个使用值接收者,另一个使用指针接收者。
type Point struct {
X, Y int
}
func (p Point) MoveByValue(dx, dy int) Point {
p.X += dx
p.Y += dy
return p
}
func (p *Point) MoveByPointer(dx, dy int) {
p.X += dx
p.Y += dy
}
func exercise3_1_2() {
p1 := Point{1, 2}
p2 := p1.MoveByValue(2, 3)
fmt.Printf("After MoveByValue: p1=%v, p2=%v\n", p1, p2) // p1不变,p2变化
p3 := &Point{1, 2}
p3.MoveByPointer(2, 3)
fmt.Printf("After MoveByPointer: p3=%v\n", *p3) // p3变化
}
// 3.1.3 结构体
// Exercise: 定义一个结构体 Person,包含 FirstName, LastName 和 Age 字段,并添加 FullName 和 SetAge 方法。
type Person struct {
FirstName string
LastName string
Age int
}
func (p Person) FullName() string {
return p.FirstName + " " + p.LastName
}
func (p *Person) SetAge(age int) {
if age < 0 {
panic("age cannot be negative")
}
p.Age = age
}
func exercise3_1_3() {
p := Person{"John", "Doe", 30}
fmt.Println("Full Name:", p.FullName()) // John Doe
p.SetAge(35)
fmt.Println("Age after SetAge:", p.Age) // 35
}
// 3.2 初始化
// Exercise: 使用不同的方式初始化结构体 Student,包括字面量、new、& 和构造函数。
type Student struct {
Name string
Grade int
}
func NewStudent(name string, grade int) *Student {
return &Student{Name: name, Grade: grade}
}
func exercise3_2() {
// 字面量
s1 := Student{"Alice", 90}
fmt.Println("s1:", s1)
// new
s2 := new(Student)
s2.Name = "Bob"
s2.Grade = 85
fmt.Println("s2:", *s2)
// &
s3 := &Student{"Charlie", 95}
fmt.Println("s3:", *s3)
// 构造函数
s4 := NewStudent("David", 88)
fmt.Println("s4:", *s4)
}
// 3.3 匿名组合
// Exercise: 定义一个结构体 Employee,嵌入 Person,并添加 Department 字段。
type Employee struct {
Person
Department string
}
func exercise3_3() {
e := Employee{
Person: Person{"Jane", "Smith", 28},
Department: "Engineering",
}
fmt.Println("Employee Name:", e.FullName()) // 继承 Person 的方法
fmt.Println("Department:", e.Department)
}
// 3.4 可见性
// Exercise: 定义一个结构体 BankAccount,包含私有字段 balance 和公有字段 AccountNumber。
type BankAccount struct {
AccountNumber string
balance float64 // 私有字段
}
func NewBankAccount(accountNumber string, initialBalance float64) (*BankAccount, error) {
if initialBalance < 0 {
return nil, errors.New("initial balance cannot be negative")
}
return &BankAccount{AccountNumber: accountNumber, balance: initialBalance}, nil
}
func (ba *BankAccount) Deposit(amount float64) error {
if amount <= 0 {
return errors.New("deposit amount must be positive")
}
ba.balance += amount
return nil
}
func (ba *BankAccount) Withdraw(amount float64) error {
if amount <= 0 {
return errors.New("withdrawal amount must be positive")
}
if amount > ba.balance {
return errors.New("insufficient funds")
}
ba.balance -= amount
return nil
}
func (ba *BankAccount) Balance() float64 {
return ba.balance
}
func exercise3_4() {
ba, err := NewBankAccount("123456789", 1000)
if err != nil {
panic(err)
}
fmt.Printf("Account: %s, Balance: $%.2f\n", ba.AccountNumber, ba.Balance())
err = ba.Deposit(500)
if err != nil {
panic(err)
}
fmt.Printf("After deposit: $%.2f\n", ba.Balance())
err = ba.Withdraw(200)
if err != nil {
panic(err)
}
fmt.Printf("After withdrawal: $%.2f\n", ba.Balance())
}
// 3.5 接口
// 3.5.1 其他语言的接口
// Go 接口是隐式实现的,与其他语言不同。
// 3.5.2 非侵入式接口
// Exercise: 定义一个接口 Speaker,包含 Speak 方法。让 Dog 和 Cat 类型实现该接口。
type Speaker interface {
Speak() string
}
type Dog struct{}
func (d Dog) Speak() string {
return "Woof!"
}
type Cat struct{}
func (c Cat) Speak() string {
return "Meow!"
}
func exercise3_5_2() {
var speaker Speaker
speaker = Dog{}
fmt.Println("Dog says:", speaker.Speak())
speaker = Cat{}
fmt.Println("Cat says:", speaker.Speak())
}
// 3.5.3 接口赋值
// Exercise: 将具体类型的变量赋值给接口变量。
func exercise3_5_3() {
var speaker Speaker
dog := Dog{}
cat := Cat{}
speaker = dog
fmt.Println("Speaker (dog):", speaker.Speak())
speaker = cat
fmt.Println("Speaker (cat):", speaker.Speak())
}
// 3.5.4 接口查询
// Exercise: 查询接口变量是否实现了某个接口。
func exercise3_5_4() {
var speaker Speaker = Dog{}
if _, ok := speaker.(Dog); ok {
fmt.Println("speaker is a Dog")
}
if _, ok := speaker.(Cat); ok {
fmt.Println("speaker is a Cat")
} else {
fmt.Println("speaker is not a Cat")
}
}
// 3.5.5 类型查询
// Exercise: 使用类型断言查询接口变量的具体类型。
func exercise3_5_5() {
var speaker Speaker = Dog{}
switch v := speaker.(type) {
case Dog:
fmt.Printf("speaker is a Dog: %T\n", v)
case Cat:
fmt.Printf("speaker is a Cat: %T\n", v)
default:
fmt.Println("speaker is unknown")
}
}
// 3.5.6 接口组合
// Exercise: 定义两个接口 Walker 和 Talker,并组合成一个新的接口 Actor。
type Walker interface {
Walk() string
}
type Talker interface {
Talk() string
}
type Actor interface {
Walker
Talker
}
type Human struct{}
func (h Human) Walk() string {
return "Walking..."
}
func (h Human) Talk() string {
return "Talking..."
}
func exercise3_5_6() {
var actor Actor = Human{}
fmt.Println("Actor walks:", actor.Walk())
fmt.Println("Actor talks:", actor.Talk())
}
// 3.5.7 Any 类型
// Exercise: 使用 interface{} 存储不同类型的值,并演示类型断言。
func exercise3_5_7() {
var any interface{}
any = 42
fmt.Printf("any is int: %d\n", any.(int))
any = "Hello"
fmt.Printf("any is string: %s\n", any.(string))
any = 3.14
fmt.Printf("any is float64: %.2f\n", any.(float64))
// 使用类型查询
switch v := any.(type) {
case int:
fmt.Printf("any is int: %d\n", v)
case string:
fmt.Printf("any is string: %s\n", v)
case float64:
fmt.Printf("any is float64: %.2f\n", v)
default:
fmt.Println("any is unknown")
}
}
// 主函数:运行所有练习
func main() {
fmt.Println("=== 3.1.1 为类型添加方法 ===")
exercise3_1_1()
fmt.Println("\n=== 3.1.2 值语义和引用语义 ===")
exercise3_1_2()
fmt.Println("\n=== 3.1.3 结构体 ===")
exercise3_1_3()
fmt.Println("\n=== 3.2 初始化 ===")
exercise3_2()
fmt.Println("\n=== 3.3 匿名组合 ===")
exercise3_3()
fmt.Println("\n=== 3.4 可见性 ===")
exercise3_4()
fmt.Println("\n=== 3.5.2 非侵入式接口 ===")
exercise3_5_2()
fmt.Println("\n=== 3.5.3 接口赋值 ===")
exercise3_5_3()
fmt.Println("\n=== 3.5.4 接口查询 ===")
exercise3_5_4()
fmt.Println("\n=== 3.5.5 类型查询 ===")
exercise3_5_5()
fmt.Println("\n=== 3.5.6 接口组合 ===")
exercise3_5_6()
fmt.Println("\n=== 3.5.7 Any 类型 ===")
exercise3_5_7()
}