设计一个名为complex的类来表示复数_我个人所理解的Go的设计规范

aa66db4b09dfdecc32cbcde30c2ce151.png

先声明一下,我的文章每一个字都是自己写的!如果我想翻译什么文章过来,我会事先声明。。。

这篇文章主要是讲我个人所理解的Go语言所提倡的设计规范,但很遗憾,我发现很多人学习了Go,但从来不往这方面去想,Go语言为什么要这么设计?为什么要舍弃面向对象?为什么不加入泛型呢?我想用一些例子来加以作答。


Go提供了有史以来最强大的非面向对象的结构体类。

下面的例子以实现部分线性代数功能为目的,我们先构造两种结构体,一个是实数向量,一个是实数矩阵,另外我们还实现了赋值成员函数或者method。

请不要在意一些检查,鉴于篇幅,我们不可能把所有的都写上,所以请当做伪代码看待。

type 

他们的成员是64位浮点数slice用以包含数据,而RMatrix是由一个二维slice来包含数据的。相应的,我们还可以定义所谓的复数向量和复数矩阵

type 

这么设计是合理的,因为向量根据成员的类型不同,确实有好几种,而矩阵也一样。甚至于,你还可以根据不同精度的数据来定义32位向量和矩阵。

所以,在编程中,你会遇到形形色色的问题,往往你不得不写出好几个不同的类或者结构体来,他们都属于一个大类,比如,好几种向量,好几种矩阵。

对于向量和矩阵的赋值操作已经写完了,接下来我们可能会考虑打印问题,不过这并不重要,因为打印函数也只是每一个结构体自己的成员函数。真正的第一个难点是,向量和矩阵的操作,比如,两个向量的加法!


这一段我们只考虑向量。

我们可以考虑写一个加法函数,首字母大写,作为包的一个输出函数

func 

很简单,就是做两个实数向量的加法用的。但是这么设计不太方便,因为两个向量可能一个是复数向量,一个是实数向量,也有可能两个都是复数向量,那么是不是要写三个这样的函数呢?

这时候你是不是特别怀念泛型?但是,我们不需要泛型,因为Go语言设计接口就是为了这个目的!我们考虑以下接口

type 

对于RVector和CVector都实现这个接口,接下来就是改写VecAdd

func 

泛型就是这么实现的,连类型识别都不用的!所以Go语言其实是主张

任何函数的输入参数和返回值,除了基础类型(比如int等)以外,请尽量使用接口类型!并把函数内要使用的对象的成员函数在接口中定义!

但是,大家是不是觉得有点问题呢?为什么Vector里的ValAt函数一定要返回一个复数呢?害得VecAdd也默认新建一个复数向量。。。并没有必要这么做。

解决方法主要还是靠类型识别,可以在VecAdd里判断输入向量的类型到底是实数还是复数,然后决定新建那种向量对象,所以这不是什么大问题。

问题在于成员函数ValAt一直输出的是complex128,这么做的好处是排除了强类型运算要不停type assertion带来的麻烦,但不是很好的解决方案。我们满意于float32到float64的提升,但并不满意于float64到complex128的转变。。。

所以,我认为比较好的一个方案,就是把复数向量从Vector接口中赶出去,所有的实现Vector接口的结构都是各种不同精度的float的向量,唯独没有复数向量,所以他们最后都成为float64。而针对复数向量,写一个新的复数向量接口,接纳所有的向量,在里面全部变成复数向量并运算。


接下来,让我们约定一些很重要的东西,比如,你是怎么看待向量的?我的默认是

所有的向量都是列向量,所以也是一种特殊的只有一列的矩阵!

所以,向量也成了一种特殊的矩阵不是吗?按照这个逻辑,向量和矩阵其实也是可以想加减的。

  1. 矩阵也可以有Length,也就是列数乘以行数
  2. 向量也可以有两个坐标,行和列,只不过列向量比较特殊,只有一列。
  3. 如果矩阵和向量的Length一样,那么就直接按元素相加
  4. 如果矩阵和向量的Length不一样,向量的Length小,那么就还是把向量加到矩阵上去,向量加完了再从头开始加。

于是,我们可以写一个矩阵接口

type 

我们就不考虑复数矩阵了。其中,Length已经介绍过了,Dims返回两个整数,分别对应行数和列数,ValAt可以得到相应位置的元素,而Transpose则返回一个自身的转置矩阵。

于是我们可以有以下针对所有实现了Matrix接口结构体的“泛型“函数

func 

矩阵的花样比向量多,我们还可以定义一些特殊矩阵,比如对称矩阵、稀疏矩阵等,他们都应该是结构体struct,都应该实现上述的接口。

看到这里,大家也许觉得,标量其实也可以是矩阵和向量呀!没错,我们可以这么实现

type 

然后让这个新结构体实现Vector和Matrix接口定义的所有函数,这都不用细说了。但是这个简单的结构体说明了一个问题,任何Go的对象都可以实现泛型不是吗?设计一个接口和若干实现该接口的结构体不就行了?


请尽量不要使用 interface{} !

这是一个很不好的习惯!你如果习惯了这种做法,你将会看到type assertion满天飞。。。这不单单影响程序运行效率,而且还降低了可读性和可维护性。

所以,还是要记住上面的话

设计一个接口和若干实现该接口的结构体,而所有的函数,除了基础类型(比如int等)以外,请尽量只接纳接口并返回接口。


剩下的问题是什么?代码量!

按照Go语言这么写,你会发现代码量蹭蹭蹭往上升,这时候你会特别怀念Python。。。

但实际上这么想不太公平的。增加的代码量其实都是无脑代码!

你可以尝试这么做,写代码之前,找张纸,把你的设计思路写下来,结构体和接口之间的关系图画一画,然后按照这个来写,基本上是无脑的,这其实是Go强大所在!

另外,科班出身的人不要跟我吐槽这个很难很烦。。。你们当年学的就是这些,还不是手拿把攥手到擒来的事情?

Go的设计者们认为,其实绝大多数任务都不需要面向对象,我们只是需要面向对象思想里的一些特性,比如,多态,然后泛型也不是个问题了。。。

最后,Go语言其实是一种逼格很高的语言,它自带歧视,对科班出身的程序员很友好,但是对非科班出身的程序员很不友好。。。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
,包括实部和虚部。该需要包含以下功能: 1. 复数加法:重载+运算符,实现两个复数相加的功能。 2. 复数减法:重载-运算符,实现两个复数相减的功能。 3. 复数乘法:重载*运算符,实现两个复数相乘的功能。 4. 复数除法:重载/运算符,实现两个复数相除的功能。 5. 模的计算:实现计算复数的模的功能。 6. 流输出:实现以a+bi的形式输出复数的功能。 这是一个使用C++编写的Complex的例子: ```c++ #include <iostream> #include <cmath> using namespace std; class Complex { public: Complex(double r = 0, double i = 0) : real(r), imag(i) {} //重载+运算Complex operator+(const Complex &c) const { return Complex(real + c.real, imag + c.imag); } //重载-运算Complex operator-(const Complex &c) const { return Complex(real - c.real, imag - c.imag); } //重载*运算Complex operator*(const Complex &c) const { return Complex(real * c.real - imag * c.imag, real * c.imag + imag * c.real); } //重载/运算Complex operator/(const Complex &c) const { double r = c.real * c.real + c.imag * c.imag; return Complex((real * c.real + imag * c.imag) / r, (imag * c.real - real * c.imag) / r); } //计算模的功能 double modulus() const { return sqrt(real * real + imag * imag); } //以a+bi的形式输出复数的功能 friend ostream &operator<<(ostream &os, const Complex &c) { os << c.real << "+" << c.imag << "i"; return os; } private: double real, imag; }; ``` 如果要创建一个Complex对象,可以使用以下代码: ```c++ Complex c1(1, 2); //创建实部为1,虚部为2的复数 Complex c2(3, 4); //创建实部为3,虚部为4的复数 ``` 要进行复数的加、减、乘、除运算,可以像下面这样使用运算符: ```c++ Complex c3 = c1 + c2; //将两个复数相加 Complex c4 = c1 - c2; //将两个复数相减 Complex c5 = c1 * c2; //将两个复数相乘 Complex c6 = c1 / c2; //将两个复数相除 ``` 要计算复数的模,可以使用如下代码: ```c++ double mod = c1.modulus(); //计算复数c1的模 ``` 要输出一个复数,可以像下面这样使用流输出运算符: ```c++ cout << c1 << endl; //输出Complex对象c1的值 ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值