3211 字
16 分钟
复数计算器实验报告
goatpretty
/
cpp-labs
Waiting for api.github.com...
00K
0K
0K
Waiting...

一、项目总体设计方案#

1. 系统功能需求分析#

  本项目实现一个命令行复数计算器。程序以两个“当前操作数” AABB 为核心对象:启动后先展示输入规则,再依次读取 AABB,随后进入循环菜单。为了让用户在多次运算中始终明确“本次计算基于哪两个复数”,菜单在每一轮操作前都会显示当前的 AABB

  在输入能力上,系统需要覆盖常见的复数写法,例如:

  • a+bia+b\mathrm iabia-b\mathrm i
  • i\mathrm ii-\mathrm i
  • 纯实数(如 55
  • 仅含虚部的形式(如 3i3\mathrm i

  此外,输入不仅要能“读到”,还必须具备最基础的格式约束,以避免歧义或无效数据进入计算流程:除数字、小数点、正负号与字符 i 外,其它字符一律视为非法;若出现 i,要求其只能位于末尾,避免诸如 2i3 这类难以解释的表达。遇到非法输入时,程序应给出提示并要求重新输入,直到得到可解析的复数。

  在运算能力上,系统提供针对 AABB 的四则运算:加、减、乘、除。每次运算以“表达式 + 结果”的形式输出,确保结果可追溯、易于核对。除法需要特别处理“分母为零复数”的情况:当 BB 的模长为零时,除法被禁止,并输出明确提示,避免产生无意义结果或触发运行异常。

  在交互健壮性上,系统必须满足以下要求:

  • 提供循环菜单,用户可反复选择运算而无需重启程序;
  • 能处理菜单输入异常(如误输入字符导致无法读取整型选项),应清理输入流状态并返回菜单重新选择;
  • 退出时给出清晰的结束提示,并在需要的环境下避免窗口瞬间关闭,使最终输出对用户可见。

2. 系统整体结构设计#

  系统按“数据对象 + 运算能力 + 交互控制”的思路组织:核心数据由 Complex 类承载,运算通过运算符重载完成,交互与流程由主控循环驱动。整体数据流如下:

  输入得到 A,BA,B → 在内存中保存为对象 → 根据菜单选择触发运算 → 输出结果并返回菜单。

2.1 数据对象层(Complex 类)#

  Complex 以两个双精度成员表示实部与虚部,并提供带默认参数的构造函数,用于统一表达“纯实数等价于虚部为 0 的复数”。类中提供模长计算接口,为除法的合法性判断提供基础能力。

2.2 运算能力层(运算符重载)#

  系统通过重载 + - * / 完成复数代数运算,通过重载 << 输出复数的规范文本形式,并在 >> 中实现从输入流读入整行字符串并解析为实部与虚部。为支撑解析过程,额外提供数值字符串合法性检查逻辑,用于识别可被解释为数值的片段并拒绝非法片段。

2.3 交互控制层(菜单与功能封装)#

  交互层通过多个独立函数分担职责:输入函数负责“提示—读取—失败重试”的闭环;菜单函数负责“状态展示—选项输出”;加减乘除分别由封装函数调用运算符重载并按统一格式输出结果;main 负责初始化、两次复数输入、循环菜单、分支调度与退出控制。模块之间通过参数传递共享当前 AABB,避免过度依赖全局状态,结构清晰且便于后续扩展。


二、项目详细设计方案#

1. 复数类 Complex 与基本运算设计#

  Complex 用两个 double 成员 realimag 表示复数的实部与虚部,对应数学形式 z=real+imag,iz = \text{real} + \text{imag},\mathrm i。构造函数 Complex(double r = 0.0, double i = 0.0) 通过默认参数直接支持创建 0+0i0+0i,也可按给定实部、虚部初始化对象。

  成员函数 getModulus() 返回复数模长

z=real2+imag2,|z|=\sqrt{\text{real}^2+\text{imag}^2},

用于除法运算前判断分母是否为零。由于除法之外的运算不依赖模长,设计上避免在其他路径中频繁调用,减少重复计算。

  四则运算分别由运算符重载实现:

  • 加法: (a+bi)+(c+di)=(a+c)+(b+d)i.(a+b\mathrm i)+(c+d\mathrm i)=(a+c)+(b+d)\mathrm i.
  • 减法: (a+bi)(c+di)=(ac)+(bd)i.(a+b\mathrm i)-(c+d\mathrm i)=(a-c)+(b-d)\mathrm i.
  • 乘法: (a+bi)(c+di)=(acbd)+(ad+bc)i.(a+b\mathrm i)(c+d\mathrm i)=(ac-bd)+(ad+bc)\mathrm i.
  • 除法: a+bic+di=(a+bi)(cdi)c2+d2=ac+bdc2+d2+bcadc2+d2i.\frac{a+b\mathrm i}{c+d\mathrm i} =\frac{(a+b\mathrm i)(c-d\mathrm i)}{c^2+d^2} =\frac{ac+bd}{c^2+d^2}+\frac{bc-ad}{c^2+d^2}\mathrm i.

  在实现中先计算分母 c2+d2c^2+d^2,再分别求实部与虚部。对“分母为零”的防护放在调用侧(如 Div 函数)通过模长判断进行拦截,避免在运算符内部引入额外的交互逻辑。

  上述运算符以引用方式接收参数,减少不必要的拷贝;返回值为新构造对象,避免修改参与运算的原始复数,从而使多次操作时状态更可控。

2. 复数输出格式设计(operator<<)#

  输出运算符 operator<< 负责将内部存储形式转换为更符合习惯的字符串形式,并统一处理边界显示,保证菜单展示与运算结果输出一致。

  典型规则包括:

  • 当实部与虚部均为 0 时输出 0

  • 当仅有实部时输出实数(如 5);

  • 当仅有虚部时输出 i-i3.5i

  • 当实部与虚部同时存在时:

    • 虚部为正且已有实部时输出 a+bi
    • 虚部为负时自然输出 a-bi
  • 对虚部系数为 ±1\pm 1 的情况做简化:1i1\mathrm i1i-1\mathrm i 不额外输出系数 1。

  通过这些分支可以覆盖 3+5i3+5\mathrm i2i-2\mathrm i55i\mathrm i 等常见形式,并减少输出的冗余信息。

3. 数值字符串合法性校验(IsValidNumber)#

  函数 IsValidNumber(string s) 用于判断一个字符串是否可被解释为简单的十进制浮点数,为复数输入解析提供基础支撑。约束为:

  • 允许首字符为 +- 作为符号位;
  • 允许出现小数点 .,但最多出现一次;
  • 其余字符必须为数字 09
  • 空串直接判为非法。

  覆盖 3-2.5+0.75.5 等常用形式,不考虑科学计数法等更复杂格式,符合实验项目的输入需求与实现复杂度控制。

4. 复数字符串输入解析(operator>>)#

  输入运算符 operator>> 的目标是支持常见复数写法,并在遇到格式错误时通过 failbit 将错误状态反馈给上层交互逻辑。

  实现思路是“整行读取 + 规则校验 + 分情况解析”:

  1. 整行读取:使用 getline 读取一整行,避免空格或残留换行导致输入被截断。

  2. 字符集校验:逐字符检查,只允许数字、小数点、正负号与 i;一旦出现其它字符立即判错。

  3. 位置约束:若出现 i,必须位于字符串末尾,否则判错,避免歧义输入。

  4. 两大分支解析

    • 不含 i:视为纯实数,解析为实部,虚部置 0;

    • i:去掉末尾 i,再从末尾向前寻找用于分割实部与虚部的 +-(索引大于 0 才作为分隔符,避免把首字符符号误判为分隔)。

      • 找到分隔符:拆为实部片段与虚部系数片段;
      • 未找到分隔符:视为纯虚数(实部为 0)。

  对于虚部系数的特殊情形也进行兼容:空串或 + 视为 1,单独的 - 视为 -1,其余则必须通过 IsValidNumber 校验后才能解析为数值。

  该解析逻辑能够支持 3+5i-2.5-0.75ii-i5 等输入形式,并在非法字符、i 位置异常或数值片段不合法时及时拒绝,保证系统不会在错误数据上继续运算。

5. 复数输入控制(Input)#

  Input(Complex& A) 将“提示—读取—失败重试”封装为闭环流程。每次尝试使用 cin >> A 调用输入运算符解析复数:

  • 成功则退出循环;
  • 失败则输出错误提示,调用 cin.clear() 清除流错误状态,使下一次读入可继续进行。

  由于底层解析使用整行读取,错误输入不会残留在缓冲区中,上层在清除状态后即可直接进入下一轮重试,交互体验更稳定。

6. 菜单与运算封装函数设计#

  菜单函数 menu(A,B) 在每轮交互时展示当前 AABB,并输出编号为 0–5 的操作选项,包括修改复数、加减乘除与退出。通过“状态先行”的展示方式,用户在连续运算时更容易核对对象来源。

  运算功能分别由 ArrSubMulDiv 封装。前三者直接调用对应运算符得到结果 CC,并按如下形式输出:

(A) op (B)=C(A)\ \text{op}\ (B) = C

  其中 op\text{op} 分别为 ++-×\timesDiv 在执行除法前先检查 B|B| 是否为 0;若 B=0|B|=0,输出提示并终止本次操作,否则执行 A/BA/B 并输出结果。这样可以在不中断程序整体运行的前提下,对非法运算进行局部拦截。

7. 主控制流程(main)设计#

main 负责串联各模块并管理完整交互流程,整体为“启动说明—输入 A、B—循环菜单—分支调度—退出”。

  • 启动阶段输出输入格式说明与示例,帮助用户快速建立输入预期;

  • 依次调用两次 Input 获取初始 AABB

  • 在循环中先显示菜单,再读取整型选项 n

    • 若读取失败(输入了非数字),则提示错误、清理流状态、丢弃本行剩余内容并回到菜单;

    • 若读取成功,则通过 switch 分派功能:

      • 1:重新输入 AABB
      • 2–5:执行加减乘除;
      • 0:输出退出提示并结束程序;
      • 其它值:提示无效选项并回到菜单。

  通过“循环 + 分支 + 输入流恢复”组合,系统能够在一次运行中反复修改复数、执行不同运算,并在发生菜单输入错误时保持可恢复、可继续的交互体验。


三、程序清单#

#include<iostream>
#include<string>
#include<cmath>
#include<cstdlib>
using namespace std;
class Complex {
private:
double real;
double imag;
public:
Complex(double r=0.0,double i=0.0) {
real=r;
imag=i;
}
double getModulus();
Complex operator+(Complex &B);
Complex operator-(Complex &B);
Complex operator*(Complex &B);
Complex operator/(Complex &B);
friend ostream& operator<<(ostream& os, const Complex& A);
friend istream& operator>>(istream& is, Complex& A);
};
double Complex::getModulus() {
return (sqrt(real*real+imag*imag));
}
Complex Complex::operator+(Complex &B) {
Complex d;
d.real=real+B.real;
d.imag=imag+B.imag;
return d;
}
Complex Complex::operator-(Complex &B) {
Complex d;
d.real=real-B.real;
d.imag=imag-B.imag;
return d;
}
Complex Complex::operator*(Complex &B) {
Complex d;
d.real=real*B.real-imag*B.imag;
d.imag=imag*B.real+real*B.imag;
return d;
}
Complex Complex::operator/(Complex &B) {
Complex d;
double temp=B.real*B.real+B.imag*B.imag;
d.real=(real*B.real+imag*B.imag)/temp;
d.imag=(imag*B.real-real*B.imag)/temp;
return d;
}
ostream& operator<<(ostream& os, const Complex& A) {
if (A.real==0 && A.imag==0) {
os<<"0";
return os;
}
if (A.real!=0) {
os<<A.real;
}
if (A.imag!=0) {
if(A.real!=0 && A.imag>0)
os<<"+";
if(A.imag==1) os<<"i";
else if(A.imag==-1)
os<<"-i";
else
os<<A.imag<<"i";
}
return os;
}
bool IsValidNumber(string s) {
if (s.empty()) return false;
int dotCount = 0;
for (int k=0; k < s.length(); k++) {
if (k==0 && (s[k]=='-' || s[k]=='+'))
continue;
if (s[k]=='.') {
dotCount++;
if(dotCount>1)
return false;
} else if(s[k]<'0' || s[k]>'9') {
return false;
}
}
return true;
}
istream& operator>>(istream& is, Complex& A) {
string strPtr;
if(is.peek()=='\n')
is.ignore();
getline(is,strPtr);
int n=strPtr.length();
if (n==0) {
is.setstate(ios::failbit);
return is;
}
int i_pos=-1;
for (int j=0; j<n; j++) {
if(strPtr[j]=='i') {
i_pos =j;
break;
}
char c = strPtr[j];
if (!((c>='0' && c<='9') || c=='.' || c=='+' || c=='-' || c=='i')) {
is.setstate(ios::failbit);
return is;
}
}
if(i_pos!=-1 && i_pos!=n-1) {
is.setstate(ios::failbit);
return is;
}
if(i_pos==-1) {
if(IsValidNumber(strPtr)) {
A.real=atof(strPtr.c_str());
A.imag=0;
} else {
is.setstate(ios::failbit);
}
}
else {
string temp=strPtr.substr(0, i_pos);
int splitPos=-1;
for(int k=temp.length()-1;k>0;k--) {
if (temp[k]=='+' || temp[k]=='-') {
splitPos=k;
break;
}
}
string s_real,s_imag;
if(splitPos==-1) {
s_real="0";
s_imag=temp;
} else {
s_real=temp.substr(0, splitPos);
s_imag=temp.substr(splitPos);
}
if(s_real=="" || s_real=="+")
A.real=0;
else if(IsValidNumber(s_real))
A.real=atof(s_real.c_str());
else
A.real=0;
if(s_imag=="" || s_imag=="+")
A.imag=1;
else if(s_imag=="-")
A.imag=-1;
else if(IsValidNumber(s_imag))
A.imag=atof(s_imag.c_str());
else {
is.setstate(ios::failbit);
}
}
return is;
}
void Input(Complex &A) {
while(1) {
cout<<"请输入一个虚数:";
if(cin>>A) {
break;
} else {
cout<<"\n输入格式错误,请重新输入。\n";
cin.clear();
}
}
}
void menu(Complex &A,Complex &B) {
cout<<"\n当前A="<<A<<" B="<<B<<endl;
cout<<"\n\n1. 修改复数\n";
cout<<"2. 加法运算\n";
cout<<"3. 减法运算\n";
cout<<"4. 乘法运算\n";
cout<<"5. 除法运算\n";
cout<<"0. 退出系统\n\n";
}
void Arr(Complex &A,Complex &B) {
Complex C;
C=A+B;
cout<<"("<<A<<")+("<<B<<")="<<C<<endl;
cout<<"\n加法运算完成。\n";
}
void Sub(Complex &A,Complex &B) {
Complex C;
C=A-B;
cout<<"("<<A<<")-("<<B<<")="<<C<<endl;
cout<<"\n减法运算完成。\n";
}
void Mul(Complex &A,Complex &B) {
Complex C;
C=A*B;
cout<<"("<<A<<")*("<<B<<")="<<C<<endl;
cout<<"\n乘法运算完成。\n";
}
void Div(Complex &A,Complex &B) {
Complex C;
if(B.getModulus()==0)
cout<<"该复数不能进行除法运算。\n" ;
else {
C=A/B;
cout<<"("<<A<<")/("<<B<<")="<<C<<endl;
cout<<"\n除法运算完成。\n";
}
}
int main() {
Complex A,B;
cout<<"所有虚数必须按照a+bi的格式输入,";
cout<<"如3+5i;-i;5。"<<endl;
Input(A);
Input(B);
while(1) {
int n;
menu(A,B);
if (!(cin>>n)) {
cout <<"\n输入字符无效,请输入数字。\n";
cin.clear();
cin.ignore(1024,'\n');
system("pause");
continue;
}
switch(n) {
case 1:
Input(A);
Input(B);
cout<<"虚数修改完成。\n";
break;
case 2:
Arr(A,B);
break;
case 3:
Sub(A,B);
break;
case 4:
Mul(A,B);
break;
case 5:
Div(A,B);
break;
case 0:
cout<<"\n正在退出中...\n";
system("pause") ;
return 0;
default:
cout<<"\n输入数字无效,请重新输入。\n";
system("pause");
break;
}
}
system("pause") ;
return 0;
}
复数计算器实验报告
https://blog.goatpretty.com/posts/006/
作者
GoatPretty
发布于
2025-12-24
许可协议
CC BY-NC-SA 4.0