FRSS 发表于 2024-4-2 13:10

实现复数及一些基本的运算

在C/C++中,有一个头文件complex,里面实现了复数和一些运算,但是我们不用它,自己写一个复数类。
需求:实现四则运算、输入输出和一些复数版本的数学函数运算
如图为最终效果:

第一行为-5开平方,第二行为我们输入的复数,然后对其进行运算并输出。
main文件中的代码:
#include "Complex.h"
using std::cout, std::cin, std::endl;
int main() {
        cout << "已知负数-5,开平方为:" << csqrt(-5) << endl;
        //Complex z(a, b)为定义一个复数z=a+bi (b可以为负数)
        Complex z;
        cin >> z;
        cout << "输入的复数" << z << endl;
        cout << "复数" << z << "的模为" << z.abs() << endl;
        cout << "复数" << z << "的主辐角为" << z.argz() << endl;
        cout << "复数(" << z << ")+(2+3i) = " << z + Complex(2, 3) << endl;
        cout << "复数(" << z << ")-(2+3i) = " << z - Complex(2, 3) << endl;
        cout << "复数(" << z << ")*(2+3i) = " << z * Complex(2, 3) << endl;
        cout << "复数(" << z << ")/(2+3i) = " << z / Complex(2, 3) << endl;
        cout << "sqrt(" << z << ") = " << csqrt(z) << endl;
        cout << "exp(" << z << ") = " << exp(z) << endl;
        cout << "ln(" << z << ") = " << log(z) << endl;
        cout << "sin(" << z << ") = " << sin(z) << endl;
        cout << "cos(" << z << ") = " << cos(z) << endl;
        cout << "tan(" << z << ") = " << tan(z) << endl;
        return 0;
}
那么,开始吧!

第一步:创建Complex类
复数的标准形式为z=a+bi,所以我们需要两个字段:实部r和虚部img
因此代码为:
class Complex {
public:
        //我们需要实现的一些功能
private:
        double r;
        double img;
};
构造函数:我们允许通过调用构造函数Complex(a,b)来创建复数a+bi,因此在public中写一个构造函数,参数为a,b并给出定义:
Complex::Complex(double Real, double Imageine)
{
        r = Real;
        img = Imageine;
}
Rel()和Img()函数:两个成员函数,分别返回z的实部和虚部,这里就不做介绍了
模:成员函数abs()为返回复数的模,也很简单,直接return sqrt(r * r + img * img);就行
辐角:一个复数的辐角有无数个,这里只返回主值。根据百度对辐角的定义,完成代码:
double Complex::argz()
{
        const double pi = 3.141592654;
        if (this->r == 0 && this->img == 0) throw std::domain_error("Undefined");
        if (this->r > 0) return atan(this->img / this->r);
        else if (this->r == 0 && this->img > 0) return pi / 2;
        else if (this->r == 0 && this->img < 0) return -pi / 2;
        else if (this->r < 0 && this->img >= 0) return atan(this->img / this->r) + pi;
        else if (this->r < 0 && this->img < 0)return atan(this->img / this->r) - pi;
        else return NAN;
}
第二步:重载输入、输出运算符
重载输出运算符:在头文件中定义友元函数operator<<,然后在cpp文件中实现
注意:重载的输出运算符并不是简单的cout <<z.r<<"+"<<z.img<<"i";,而是会根据实部、虚部的符号进行输出,例如Complex(0,4)会输出4i而不是0+4i,Complex(1,-2)会输出1+-2i
分析思路:
如果z.r==0(纯虚数),那就判断img的符号,如果是正就直接cout <<z.img<<"i";,否则就cout <<"-"<<-a.img<<"i";
标准形式同理,根据虚部的符号进行输出,代码:
ostream& operator<<(ostream& os,const Complex& a) {
        if (a.img == 0) os << a.r;        //如果不是一个复数,就直接输出实部
        if (a.r == 0) {
                if (a.img > 0) os << a.img << "i";
                else if (a.img < 0) os << "-" << -a.img << "i";
        }
        else {
                if (a.img > 0) os << a.r << "+" << a.img << "i";
                else if (a.img < 0) os << a.r << "-" << -a.img << "i";
        }
        return os;
}
这样,我们就能直接cout << z来输出一个复数
重载输入运算符:这个应该是最复杂的了,我写了好久才勉强写出来一个石山代码,具体思路是用string存储输入,使用正则表达式判断是否为复数的输入格式,如果不是就直接抛出一个异常,是的话就分别将实部和虚部(包括符号)拷贝到一个char*中,然后使用std::stod转换为double类型存储到z.r和z.img中。这样输入a+bi时都会被识别为一个复数,缺点就是输入纯虚数bi时还是要输入0+bi、虚部为1时1不能省略。代码如下:
istream& operator>>(istream& is, Complex& a)
{
        std::string input;
        is >> input;
        std::regex complex("^(0|[-+]?\\d+(\\.\\d+)?)([-+]?\\d+(\\.\\d+)?)i?$");
        bool is_complex = std::regex_match(input, complex);
        if (!is_complex) throw std::exception("Invalid complex number!");
        int s = 1;
        char Real{ 0 };
        char Img{ 0 };
        //考虑到第一位如果是符号
        Real = input;
        for (int i = 1; (input < '9' && input > '0') || input == '.'; i++) {
                if (i > 48) break;
                Real = input;
                s++;
        }
        Img = input;
        for (int i = 1; (input <= '9' && input >= '0') || input=='.'; i++) {
                if (i > 48) break;
                Img = input;
                s++;
        }
        a.r = std::stod(Real);
        a.img = std::stod(Img);
        return is;
}
这样,我们就能够直接cin>>z;来按照a+bi的格式输入一个复数
第三步:重载一些基本的数学函数
首先想到的就是梦开始的地方:根号
由于sqrt函数已经被定义了,因此这里创建一个sqrt的扩展版函数csqrt,它接受一个参数x作为被开方数,x可以是任何实数,代码如下:
Complex __cdecl csqrt(double x)
{
        if (x >= 0) return Complex(sqrt(x), 0);
        else return Complex(0, sqrt(-x));
}
这样,如果被开方数非负,返回虚部为0的数,为负数就返回sqrt(-x)i
根据能得到s^z即pow函数的定义
但是呢,我们还是需要先给出log(z)的定义,想要log(z)的定义,我们还需要补充log(x)在x<0的定义(开始套娃)
log(x)在x=0处无定义,在x<0时多值,这里只考虑主值
log(-x)(x为正数)都可以化为log(x)+log(-1),所以只需根据欧拉公式的指数形式替换-1=i^2得到log(i)的值然后再次替换即可得到log(-1)的值,代码如下:
Complex __cdecl clog(double x)
{
        //ln(-1) = pi*i
        Complex logngo(0, 3.141592654);
        if (x > 0) return Complex(log(x), 0);
        else if (x < 0) return logngo + Complex(log(-x), 0);
        else throw std::domain_error("Undefined");
}
注意:在此之前要先重载+-*/运算符,因为比较简单,就不提了
然后根据log(z)的定义即可完成代码,这里使用的定义可能不一样,但返回的结果是一样的。代码如下:
Complex __cdecl log(const Complex& x)
{
        if (x.img == 0) return clog(x.r);//Complex(log(x.r), 0);
        const double pi = 3.141592654;
        if (x.r == 0) return clog(x.img) + Complex(0, pi / 2);
        double xita = atan(x.img / x.r);
        return clog(x.r / cos(xita)) + Complex(0, xita);
}
完美,这下可以写pow函数,也可以重载sqrt函数使它支持复数作为被开方数

如果你看懂了,你可以尝试:
根据复三角函数的定义重载sin、cos、tan、exp函数
完成pow函数的代码编写

woaipoie 发表于 2024-4-8 14:44

几乎忘完了{:1_896:}

yanger3999 发表于 2024-4-8 15:42

复习下基础知识,有个台湾的老师讲的不错
页: [1]
查看完整版本: 实现复数及一些基本的运算