首页 > 编程语言 >学习go语言编程之面向对象

学习go语言编程之面向对象

时间:2023-08-13 23:44:23浏览次数:47  
标签:int type 编程 接口 var 面向对象 类型 go interface

类型系统

类型系统是指一个语言的类型体系结构,一个典型的类型系统通常包含如下基本内容:

  • 基础类型,如:byte、int、bool、float等
  • 复合类型,如:数组、结构体、指针等
  • 可以指向任意对象的类型(Any类型)
  • 值语义和引用语义
  • 面向对象,即:所有具备面向对象特征(比如成员方法)的类型
  • 接口

为类型添加方法

在Golang中,可以给任意类型(包括内置类型,但不包括指针类型)添加相应的方法。

type Integer int

func (a Integer) Less(b Integer) bool {
	return a < b
}

如上示例,定义了一个新的类型Integer,它和int没有本质不同,只是它为内置的int类型增加了个新方法Less()

这样实现了Integer后,就可以让整型像一个普通的类一样使用:

func main() {
	var a Integer = 1
	if a.Less(2) {
		fmt.Println(a, "less 2")
	}
}

Golang中的面向对象最为直观,也无需支付额外的成本。如果要求对象必须以指针传递,这有时会是个额外成本,因为对象有时很小(比如4个字节),用指针传递并不划算。
只有在需要修改对象的时候才必须使用指针。

// 需要修改对象的时候才必须使用指针
func (a *Integer) Add(b Integer) {
	*a += b
}

值语义和引用语义

值语义和引用语义的差别在于赋值,如下示例:

b = a
b.Modify()

如果b的修改不会影响a的值,那么此类型就是值类型;如果会影响a的值,那么此类型就是引用类型。

Golang中的大多数类型都是值语义,包括:

  • 基本类型,如:byte、int、bool、float32、float64和string等
  • 复合类型,如:数组、结构体、指针等

Golang中的数组与基本类型没有区别,是很纯粹的值类型。

var a = [3]int{1, 2, 3}
var b = a
b[1]++
fmt.Println(a)
fmt.Println(b)

输出:

[1 2 3]
[1 3 3]

b[1]++的结果并没有影响到a[1]的值,这表明b=a赋值语句是数据内容的完整复制。

要想表达引用,需要使用指针:

var a = [3]int{1, 2, 3}
var b = &a
b[1]++
fmt.Println(a)
fmt.Println(*b)

输出:

[1 3 3]
[1 3 3]

b[1]++的结果影响到了a[1]的值,这表明b=&a赋值语句是数组内容的引用。变量b的类型不是[3]int,而是*[3]int

Golang中有4个类型比较特别,看起来像是引用类型,包括:

  • 数组切片:指向数组(array)的一个区间
  • map:极其常见的数据结构,提供键值查询能力
  • channel:执行体(goroutine)间的通信设施
  • 接口(interface):对一组满足某个契约的类型的抽象

结构体

Golang放弃了包括继承在内的大量面向对象特性,只保留了组合这个最基础的特性。
前面已经说过,所有的Golang类型(指针类型除外)都可以有自己的方法。在这个背景下,Golang的结构体只是很普通的复合类型。

// 定义一个矩形类型
type Rect struct {
	x, y          float64
	width, height float64
}

定义一个成员方法来计算矩形的面积:

// 定义一个成员方法来计算面积
func (r *Rect) Area() float64 {
	return r.width * r.height
}

初始化

创建并初始化类型的对象实例有多种方式:

// 创建初始化自定义类型对象实例
rect1 := new(Rect)
rect2 := &Rect{}
rect3 := &Rect{0, 0, 100, 200}
rect4 := &Rect{width: 100, height: 200}

在Golang中,未进行显示初始化的变量都会被初始化为该类型的零值(如bool类型的零值为false,int类型的零值为0,string类型的零值为空字符串)。
在Golang中,没有构造函数的概念,对象的创建通常交给一个全局的创建函数来完成,以NewXXX类命名,表示构造函数。

// 创建类型对象的全局函数
func NewRect(x, y, width, height float64) *Rect {
	return &Rect{x, y, width, height}
}

匿名组合

确切地说,Golang也提供了继承,但是采用了组合的文法,所以称其为匿名组合。

type Base struct {
	Name string
}

func (base *Base) Foo() {
    fmt.Println("This is foo in Base")
}
func (base *Base) Bar() {}

type Foo struct {
	Base
}

func (foo *Foo) Bar() {
	foo.Base.Bar()
}

如上示例代码定义了一个Base类(实现了Foo()和Bar()两个成员方法),然后定义了一个Foo类,该类从Base类“继承”并改写了Bar()方法(该方法实现时先调用了基类的Bar()方法)。
在“派生类”Foo中没有改写“基类”的Base的成员方法时,相应的方法就被“继承”,例如调用foo.Foo()和调用foo.Base.Foo()效果一致。

func main() {
    foo := new(Foo)
	foo.Foo()      // 输出:This is foo in Base
	foo.Base.Foo() // 输出:This is foo in Base
}

与其他语言不同,Golang很清晰地告诉了类的内存布局是怎样的。
此外,在Golang中还可以随心所欲地修改内存布局。

type Foo struct {
	// ...
	Base
}

这段代码从语义上来说,与其他例子并无不同,但是内存布局发生了变化:“基类”Base的数据放在了“派生类”Foo的最后。

另外,在Golang中还可以以指针的方式从一个类型“派生”:

type Foo struct {
	*Base
}

这段代码依然有“派生”的效果,只是在创建Foo实例的时候,需要外部提供一个Base类实例的指针。

可见性

Golang没有提供类似privateprotectedpublic这样表示可见性的关键字,要使某个符号对其他包可见,需要将该符号定义为大写字母开头。

type Rect struct {
	X, Y          float64
	Width, Height float64
}

这样Rect类型的全部成员就被导出了,可以被其他引用了Rect所在包的代码访问到。

成员方法的可见性也遵循同样的规则,如:

func (r *Rect) area() float64 {
	return r.Width * r.Height
}

这样Rect类型的成员方法area()就只能在该类型所在包内使用。

接口

非侵入式接口

在Golang中,一个类只需要实现了接口要求的所有函数,就可以说这个类实现了该接口。

如下定义一个File类,并实现了ReadWriteSeekClose方法。

// 定义一个File类
type File struct {
}

func (f *File) Read(buf []byte) (n int, err error) {
	return 0, nil
}
func (f *File) Write(buf []byte) (n int, err error) {
	return 0, nil
}
func (f *File) Seek(off int64, whence int) (pos int64, err error) {
	return 0, nil
}
func (f *File) Close() error {
	return nil
}

假设有如下接口:

type IFile interface {
	Read(buf []byte) (n int, err error)
	Write(buf []byte) (n int, err error)
	Seek(off int64, whence int) (pos int64, err error)
	Close() error
}

type IReader interface {
	Read(buf []byte) (n int, err error)
}

type IWriter interface {
	Write(buf []byte) (n int, err error)
}

type ICloser interface {
	Close() error
}

尽管File类并没有从这些接口继承,甚至可以不知道这些接口的存在,但是File类实现了这些接口(实现了接口中的所有函数),所以如下赋值是正确的:

var file1 IFile = new(File)
var file2 IReader = new(File)
var file3 IWriter = new(File)
var file4 ICloser = new(File)

接口赋值

接口赋值在Golang中分为两种情况:

  • 将对象实例赋值给接口
  • 将一个接口赋值给另一个接口

将某种类型的实例赋值给接口,这要求该对象实例实现了接口要求的所有方法。

// Integer是一个新的类型
type Integer int

func (a Integer) Less(b Integer) bool {
	return a < b
}

func (a *Integer) Add(b Integer) {
	*a += b
}

// LessAdder是一个接口类型
type LessAdder interface {
	Less(b Integer) bool
	Add(b Integer)
}

显然,Integer类实现了LessAdder接口的所有方法,所以可以将Integer类实例赋值给LessAdder接口类型。

var a Integer = 1
var b LessAdder = &a

将一个接口赋值给另一个接口,要求两个接口拥有相同的方法列表(方法次序可以不同)。

type ReadWriter interface {
	Read(buf []byte) (n int, err error)
	Write(buf []byte) (n int, err error)
}

type IStream interface {
	Write(buf []byte) (n int, err error)
	Read(buf []byte) (n int, err error)
}

如上,两个接口类型ReadWriterIStream拥有相同的方法列表,在Golang中这两个接口并无差别。
如下赋值是完全正确的:

var file1 ReadWriter = new(File) // 创建一个ReadWriter类型
var file2 IStream = file1        // 将ReadWriter类型赋值给IStream类型
var file3 ReadWriter = file2     // 将IStream类型赋值给ReadWriter类型

接口赋值并不要求两个接口必须等价:如果接口A的方法列表是接口B的方法列表的子集,那么接口B可以赋值给接口A,反过来并不成立。

type Writer interface {
	Write(buf []byte) (n int, err error)
}

var file1 ReadWriter = new(File)
var file2 Writer = file1     // 将“大”接口赋值给小接口可以
var file3 ReadWriter = file2 // 编译报错,不能将“小”接口赋值给大接口

接口查询

接口查询的语法如下:

// 判断file1对象(Writer类型)是否实现了IStream接口,如果实现了则执行特定的代码
var file1 Writer = new(File)
if file2, ok := file1.(IStream); ok {
    fmt.Println("file1 implement interface IStream, file2: ", file2)
}

还可以询问接口指向的对象是否是某个类型:

// 判断file1对象(Writer类型)是否属于File类型
var file1 Writer = new(File)
if file3, ok := file1.(*File); ok {
    fmt.Println("file1 is File type, file3: ", file3)
}

类型查询

在Golang中可以直接了当地询问接口只想的对象实例的类型:

var v1 interface{} = new(File)
switch v := v1.(type) {
case *File:
    fmt.Println(v, "is File type")
default:
    fmt.Println(v, "is Unknown type")
}

接口组合

// io包中的ReadWriter接口将基本的Read和Write方法组合起来
type ReadWriter interface { 
    Reader 
    Writer 
}

ReadWriter接口组合了ReaderWriter两个接口,它完全等同于如下写法:

type ReadWriter interface {
    Read(p []byte) (n int, err error)
    Write(p []byte) (n int, err error)
}

因为这两种写法的表意完全相同:ReadWriter接口既能做Reader接口的所有事情,又能做Writer接口的所有事情。

可以认为接口组合是类型匿名组合的一个特定场景,只不过接口只包含方法,而不包含任何成员变量。

Any类型

Golang中的任何对象都满足空接口interface{},所以interface{}看起来像是可以指向任何对象的Any类型,如下:

var v1 interface{} = 1                    // 将int类型赋值给interface{}
var v2 interface{} = "abc"                // 将string类型赋值给interface{}
var v3 interface{} = &v2                  // 将*interface{}类型赋值给interface{}
var v4 interface{} = struct{ x int }{1}   // 将结构体类型赋值给interface{}
var v5 interface{} = &struct{ x int }{1}

标签:int,type,编程,接口,var,面向对象,类型,go,interface
From: https://www.cnblogs.com/nuccch/p/17627540.html

相关文章

  • go语言环境要这样搭建才"省钱"
    go语言环境要这样搭建才省钱目录go语言环境要这样搭建才省钱本篇概要集成开发环境工具(ide)Goland或IntelliJIDEAVisualStudioCode其他集成开发环境VisualStudioCode的go语言开发环境搭建VisualStudioCode安装go编译环境安装配置go环境变量安装go语言插件使用golang编程本篇......
  • GUI编程
    GUI编程简介GUI的核心技术:SwingAWTAWT组件和容器Frameimportjava.awt.*;/***GUI的第一个界面*/publicclassDemo01{publicstaticvoidmain(String[]args){//FrameFrameframe=newFrame("我的第一个Java图像界面窗口");......
  • #region在多种编程语言及IDE中进行代码折叠,包括python msvc++ c#等
    vs/rider中折叠C#代码在写C#的时候,在visualstudio中可以使用#region和#endregion来进行代码折叠,那么在pycharm中是否可以呢?//这里有很多的代码......#region//这里的数百行的伪代码,可以直接折叠起来,方便阅读及归类[MenuItem("KEngine/KSFrameworkOptionsandHelp")]priva......
  • Shell编程规范与变量三
    目录1.正则表达式1.1元字符1.2表示次数1.3位置锚定1.4分组或其他1.5扩展正则表达式1.6grep2.AWK3.常用脚本1.正则表达式通配符功能是用来处理文件名,而正则表达式是处理文本内容中字符[localhost~]#man7regex#可以使用man手册帮助1.1元字符. 匹配任意单个......
  • java 8 函数式编程
    函数式编程1.Lambda表达式1.1概念对某些匿名内部类的写法优化,特点是可推导可省略。基本格式:(参数列表)->{代码}。例如:newThread(newRunnable(){@Overridepublicvoidrun(){System.out.println("运行进程");......
  • Django实现文件上传、文件列表查看、修改、限流和日志记录8
    Django实现文件上传、文件列表查看、修改、限流和日志记录8本章节,总结一些部署项目遇到的报错,希望会有所帮助NameError:name'datetime'isnotdefined报错“logging.info('用户{}在{}登录成功'.format(username,datetime.now()))NameError:name'datetime'isnotd......
  • Data structure and algorithm-One
    右边界左边界函数式编程传统的实现方式通常是将具体的功能代码直接写在方法内部。这样的实现方式对于固定的功能来说可能是足够的,但它的灵活性和复用性较低。每当需要不同的功能时,您需要编写新的方法或修改原有方法,这可能会导致代码的冗余和不易维护。而函数式编程的优点......
  • 并发编程 --- CAS原子操作
    介绍CAS(CompareAndSwap)是一种无锁算法的实现手段,中文名称为比较并交换。它由CPU的原子指令实现,可以在多线程环境下实现无锁的数据结构。原理CAS的原理是:它会先比较内存中的某个值是否和预期值相同,如果相同则更新这个值,否则不做任何操作。这整个过程是原子的,所以可以在......
  • 如何在C语言中进行图形界面编程
    在C语言中进行图形界面编程是一项非常有挑战性和有趣的任务。虽然C语言主要用于系统级编程和算法开发,但我们仍然可以使用一些库来实现简单的图形界面。在本文中,我将介绍一种在C语言中进行图形界面编程的方法。首先,让我们来了解一下几个常用的图形库,它们可以帮助我们在C语言中创建......
  • C语言编程教程:如何提取手机尾数
    C语言编程教程:如何提取手机尾数在C语言编程中,我们经常需要处理各种数据类型和操作。今天,我们将分享一个有趣且实用的主题:如何提取手机尾数。手机尾数是手机号码中的最后几位数字,提取这些数字可以为我们的程序带来更多可能性。在本文中,我们将介绍一种简洁而有效的方法,帮助你轻松提......