Go语言实现运算符重载的方法详解

目录
  • 前言
  • 使用
  • 实现
  • 总结

前言

先带来日常的 GScript 更新:新增了可变参数的特性,语法如下:

int add(string s, int ...num){
	println(s);
	int sum = 0;
	for(int i=0;i<len(num);i++){
		int v = num[i];
		sum = sum+v;
	}
	return sum;
}
int x = add("abc", 1,2,3,4);
println(x);
assertEqual(x, 10);

得益于可变参数,所以新增了格式化字符串的内置函数:

//formats according to a format specifier and writes to standard output.
printf(string format, any ...a){}

//formats according to a format specifier and returns the resulting string.
string sprintf(string format, any ...a){}

下面重点看看 GScript 所支持的运算符重载是如何实现的。

使用

运算符重载其实也是多态的一种表现形式,我们可以重写运算符的重载函数,从而改变他们的计算规则。

println(100+2*2);

以这段代码的运算符为例,输出的结果自然是:104.

但如果我们是对两个对象进行计算呢,举个例子:

class Person{
	int age;
	Person(int a){
		age = a;
	}
}
Person p1 = Person(10);
Person p2 = Person(20);
Person p3 = p1+p2;

这样的写法在 Java/Go 中都会报编译错误,这是因为他们两者都不支持运算符重载;

但 Python/C# 是支持的,相比之下我觉得 C# 的实现方式更符合 GScript 语法,所以参考 C# 实现了以下的语法规则。

Person operator + (Person p1, Person p2){
	Person pp = Person(p1.age+p2.age);
	return pp;
}
Person p3 = p1+p2;
println("p3.age="+p3.age);
assertEqual(p3.age, 30);

有几个硬性条件:

operator

目前支持的运算符有:+-*/ == != < <= > >=

实现

以前在使用 Python 运算符重载时就有想过它是如何实现的?但没有深究,这次借着自己实现相关功能从而需要深入理解。

其中重点就为两步:

  • 编译期间:记录所有的重载函数和运算符的关系。
  • 运行期:根据当前的运算找到声明的函数,直接运行即可。

第一步的重点是扫描所有的重载函数,将重载函数与运算符存放起来,需要关注的是函数的返回值与运算符类型。

// OpOverload 重载符
type OpOverload struct {
	function  *Func
	tokenType int
}

// 运算符重载自定义函数
opOverloads []*symbol.OpOverload

在编译器中使用一个切片存放。

而在运行期中当两个入参类型相同时,则需要查找重载函数。

// GetOpFunction 获取运算符重载函数
// 通过返回值以及运算符号(+-*/) 匹配重载函数
func (a *AnnotatedTree) GetOpFunction(returnType symbol.Type, tokenType int) *symbol.Func {
	for _, overload := range a.opOverloads {
		isType := overload.GetFunc().GetReturnType().IsType(returnType)
		if isType && overload.GetTokenType() == tokenType {
			return overload.GetFunc()
		}
	}
	return nil
}

查找方式就是通过编译期存放的数据进行匹配,拿到重载函数后自动调用便实现了重载。

感兴趣的朋友可以查看相关代码:

编译期: https://github.com/crossoverJie/gscript/blob/ae729ce7d4cf39fe115121993fcd2222716755e5/resolver/type_scope_resolver.go#L127

运行期: https://github.com/crossoverJie/gscript/blob/499236af549be47ff827c6d55de1fc8e5600b9b3/visitor.go#L387

总结

运算符重载其实并不是一个常用的功能;因为会改变运算符的语义,比如明明是加法却在重载函数中写为减法。

这会使得代码阅读起来困难,但在某些情况下我们又非常希望语言本身能支持运算符重载。

比如在 Go 中常用的一个第三方精度库 decimal.Decimal ,进行运算时只能使用 d1.Add(d2) 这样的函数,当运算复杂时:

a5 = (a1.Add(a2).Add(a3)).Mul(a4);
a5 = (a1+a2+a3)*a4;

就不如下面这种直观,所以有利有弊吧,多一个选项总不是坏事。

GScript 源码: https://github.com/crossoverJie/gscript

到此这篇关于Go语言实现运算符重载的方法详解的文章就介绍到这了,更多相关Go语言运算符重载内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Go语言学习之运算符使用详解

    目录 1.算术运算符 2.关系运算符 3.逻辑运算符 4.位运算符 5.赋值运算符 6.特殊运算符 1.算术运算符 很常规,和java一样. 样例代码如下 // 算术运算符 func base() { a := 1 b := 20 c := 31 d := -1 fmt.Printf(" + -> %d\n", a+b) fmt.Printf(" - -> %d\n", b-a) fmt.Printf(" * -> %d\n",

  • 手把手带你走进Go语言之运算符解析

    目录 概述 Go 运算符 算术运算符 关系运算符 逻辑运算符 赋值运算符 概述 Golang 是一个跨平台的新生编程语言. 今天小白就带大家一起携手走进 Golang 的世界. Go 运算符 运算符 (operator) 可以帮助我们在程序中执行数学或逻辑运算. Go 语言内置的运算符有: 算术运算符 关系运算符 逻辑运算符 位运算符 赋值运算符 算术运算符 运算符 描述 + 相加 - 相减 * 相乘 / 相除 % 取余 ++ 自增 – 自减 例子: package main import "f

  • 一文带你掌握Go语言运算符的使用

    目录 算术运算符 关系运算符 逻辑运算符 位运算符 赋值运算符 其他运算符 运算符优先级 运算符用于在程序运行时执行数学或逻辑运算. Go 语言内置的运算符有: 算术运算符 关系运算符 逻辑运算符 位运算符 赋值运算符 其他运算符 接下来让我们来详细看看各个运算符的介绍. 算术运算符 算术运算符,所有的数据类型要相同 下表列出了所有Go语言的算术运算符.假定 A 值为 10,B 值为 20. 运算符 描述 实例 + 相加 A + B 输出结果 30 - 相减 A - B 输出结果 -10 * 相

  • golang中为什么不存在三元运算符详解

    三元运算符广泛存在于其他语言中,比如: python: val = trueValue if expr else falseValue javascript: const val = expr ? trueValue : falseValue c.c++: const char *val = expr ? "trueValue" : "falseValue"; 然而,被广泛支持的三目运算符在golang中却是不存在的!如果我们写出类似下面的代码: val := ex

  • Golang 运算符及位运算详解

    什么是运算符? 运算符用于执行程序代码运算,会针对一个以上操作数项目来进行运算.例如:2+3,其操作数是2和3,而运算符则是"+". 在vb2005中运算符大致可以分为5种类型:算术运算符.位运算符. 关系运算符.赋值运算符.逻辑运算符. 算数运算符 运算符 描述 + 相加 - 相减 * 相乘 / 相除 % 求余 注意: ++(自增)和--(自减)在Go语言中是单独的语句,并不是运算符. func main() { a, b := 3,4 fmt.Printf("a 加 b

  • Go语言运算符案例讲解

    算数运算符 算数运算符和C语言几乎一样 运算符 描述 实例 + 相加 A + B - 相减 A - B * 相乘 A * B / 相除 B / A % 求余 B % A ++ 自增 A++ – 自减 A– 注意点: 只有相同类型的数据才能进行运算 package main import "fmt" int main(){ var num1 int32 = 10 //var num2 int64 = num1 // 类型不同不能进行赋值运算 var num2 int64 = int64(

  • Go语言实现运算符重载的方法详解

    目录 前言 使用 实现 总结 前言 先带来日常的 GScript 更新:新增了可变参数的特性,语法如下: int add(string s, int ...num){ println(s); int sum = 0; for(int i=0;i<len(num);i++){ int v = num[i]; sum = sum+v; } return sum; } int x = add("abc", 1,2,3,4); println(x); assertEqual(x, 10)

  • Go语言实现JSON解析的方法详解

    目录 1.json序列化 2.Json反序列化为结构体对象 3.Json反序列化为map类型 4.Tag的使用 在日常项目中,使用Json格式进行数据封装是比较常见的操作,看一下golang怎么实现. 1.json序列化 将json字符串转为go语言结构体对象. package main import ( "encoding/json" "errors" "fmt" ) var parseJsonError = errors.New("

  • C++运算符重载实例代码详解(调试环境 Visual Studio 2019)

    最近看了菜鸟教程里的C++教程 遇到很多运算符重载,为了方便我的学习,我把这些总结了一下 如有错误(包括之前的博文)请评论留言,谢谢! 由于代码里注释的很清楚,我就不做过多的解释了. 下面是这次总结的头文件,用来放置一些类和方法. //C++运算符重载实例.h #pragma once #include <iostream> using namespace std; class chongzai { private: int i, j, k; public: chongzai() { i =

  • c++运算符重载基础知识详解

    实际上,很多C++运算符已经被重载.eg:将*运算符用于地址,将得到存储在这个地址中的值,将他用于2个数字时,得到的将是他们的乘积.C++根据操作数的数目和类型来决定采用哪种操作. C++允许将运算符重载扩展到用户定义的类型.例如,允许使用+将两个对象相加.编译器将根据操作数的数目和类型决定使用加法定义.运算符重载可以使代码看起来更自然.例如,将2个数组相加是一种常见的运算.通常,需要使用下面这样的for循环来实现: 复制代码 代码如下: for (int i = 0; i < 20; i++)

  • Python importlib模块重载使用方法详解

    模块介绍 Python提供了importlib包作为标准库的一部分.目的就是提供Python中import语句的实现(以及__import__函数).另外,importlib允许程序员创建他们自定义的对象,可用于引入过程(也称为importer). 什么是imp? 另外有一个叫做imp的模块,它提供给Python import语句机制的接口.这个模块在Python 3.4中被否决,目的就是为了只使用importlib. 了解:模块的重载 考虑到性能的原因,每个模块只被导入一次,放入字典sys.m

  • C语言长字符串的换行方法详解

    目录 1.长字符串示例 2.书写长字符串的换行方法 方法一:利用双引号对长字符串进行换行 方法二:利用反斜杠对长字符串进行换行 3.总结 在编写C程序时,如果想要打印某个字符串,而字符串的内容比较多,这就涉及到对这个长字符串进行书写换行,这里的换行并不会对最终的显示结果进行换行,只是为了阅读代码能够更加的清晰,不至于字符串的内容过长影响代码的阅读体验. 1.长字符串示例 /** * @file test.c * @author Ailson Jack (jackailson@foxmail.co

  • C语言qsort()函数的使用方法详解

    目录 前言 1.参数含义 1.首元素地址base 2.元素个数num 3.元素大小size 4.自定义比较函数compar 2.使用方式 1.头文件 2.compar的实现 3.整体代码 总结 前言 qsort()函数(quick sort)是八大排序算法中的快速排序,能够排序任意数据类型的数组其中包括整形,浮点型,字符串甚至还有自定义的结构体类型. 1.参数含义 void qsort (void* base, size_t num, size_t size,int (*compar)(cons

  • C 语言二叉树几种遍历方法详解及实例

    二叉树的一些概念 二叉树就是每个结点最多有两个子树的树形存储结构.先上图,方便后面分析. 1 满二叉树和完全二叉树 上图就是典型的二叉树,其中左边的图还叫做满二叉树,右边是完全二叉树.然后我们可以得出结论,满二叉树一定是完全二叉树,但是反过来就不一定.满二叉树的定义是除了叶子结点,其它结点左右孩子都有,深度为k的满二叉树,结点数就是2的k次方减1.完全二叉树是每个结点都与深度为k的满二叉树中编号从1到n一一对应. 2 树的深度 树的最大层次就是深度,比如上图,深度是4.很容易得出,深度为k的树,

  • C语言 操作符#与##使用方法详解

    目录 一.# 运算符 二.## 运算符 三.小结 一.# 运算符 # 运算符用于在预处理期将宏参数转换为字符串 # 的转换作用是在预处理期完成的,因此只在宏定义中有效 编译器不知道 # 的转换作用 用法: #define STRING(x) #x printf("%s\n",STRING(Hello World!)); 下面通过一个示例感受一下: test.c: #include <stdio.h> #define STRING(x) #x int main() { pri

  • C语言实现面向对象的方法详解

    目录 1.引言 2.封装 3.继承 4.多态 4.1 虚表和虚指针 4.2 在构造函数中设置vptr 4.3 继承 vtbl 和 重载 vptr 4.4 虚函数调用 4.5 main.c 5.总结 1.引言 面向对象编程(OOP)并不是一种特定的语言或者工具,它只是一种设计方法.设计思想.它表现出来的三个最基本的特性就是封装.继承与多态.很多面向对象的编程语言已经包含这三个特性了,例如 Smalltalk.C++.Java.但是你也可以用几乎所有的编程语言来实现面向对象编程,例如 ANSI-C.

随机推荐