一、项目总体设计方案
1. 系统功能需求分析
本项目实现一个命令行复数计算器。程序以两个“当前操作数” 与 为核心对象:启动后先展示输入规则,再依次读取 、,随后进入循环菜单。为了让用户在多次运算中始终明确“本次计算基于哪两个复数”,菜单在每一轮操作前都会显示当前的 与 。
在输入能力上,系统需要覆盖常见的复数写法,例如:
- 、
- 、
- 纯实数(如 )
- 仅含虚部的形式(如 )
此外,输入不仅要能“读到”,还必须具备最基础的格式约束,以避免歧义或无效数据进入计算流程:除数字、小数点、正负号与字符 i 外,其它字符一律视为非法;若出现 i,要求其只能位于末尾,避免诸如 2i3 这类难以解释的表达。遇到非法输入时,程序应给出提示并要求重新输入,直到得到可解析的复数。
在运算能力上,系统提供针对 与 的四则运算:加、减、乘、除。每次运算以“表达式 + 结果”的形式输出,确保结果可追溯、易于核对。除法需要特别处理“分母为零复数”的情况:当 的模长为零时,除法被禁止,并输出明确提示,避免产生无意义结果或触发运行异常。
在交互健壮性上,系统必须满足以下要求:
- 提供循环菜单,用户可反复选择运算而无需重启程序;
- 能处理菜单输入异常(如误输入字符导致无法读取整型选项),应清理输入流状态并返回菜单重新选择;
- 退出时给出清晰的结束提示,并在需要的环境下避免窗口瞬间关闭,使最终输出对用户可见。
2. 系统整体结构设计
系统按“数据对象 + 运算能力 + 交互控制”的思路组织:核心数据由 Complex 类承载,运算通过运算符重载完成,交互与流程由主控循环驱动。整体数据流如下:
输入得到 → 在内存中保存为对象 → 根据菜单选择触发运算 → 输出结果并返回菜单。
2.1 数据对象层(Complex 类)
Complex 以两个双精度成员表示实部与虚部,并提供带默认参数的构造函数,用于统一表达“纯实数等价于虚部为 0 的复数”。类中提供模长计算接口,为除法的合法性判断提供基础能力。
2.2 运算能力层(运算符重载)
系统通过重载 + - * / 完成复数代数运算,通过重载 << 输出复数的规范文本形式,并在 >> 中实现从输入流读入整行字符串并解析为实部与虚部。为支撑解析过程,额外提供数值字符串合法性检查逻辑,用于识别可被解释为数值的片段并拒绝非法片段。
2.3 交互控制层(菜单与功能封装)
交互层通过多个独立函数分担职责:输入函数负责“提示—读取—失败重试”的闭环;菜单函数负责“状态展示—选项输出”;加减乘除分别由封装函数调用运算符重载并按统一格式输出结果;main 负责初始化、两次复数输入、循环菜单、分支调度与退出控制。模块之间通过参数传递共享当前 与 ,避免过度依赖全局状态,结构清晰且便于后续扩展。
二、项目详细设计方案
1. 复数类 Complex 与基本运算设计
Complex 用两个 double 成员 real 与 imag 表示复数的实部与虚部,对应数学形式 。构造函数 Complex(double r = 0.0, double i = 0.0) 通过默认参数直接支持创建 ,也可按给定实部、虚部初始化对象。
成员函数 getModulus() 返回复数模长
用于除法运算前判断分母是否为零。由于除法之外的运算不依赖模长,设计上避免在其他路径中频繁调用,减少重复计算。
四则运算分别由运算符重载实现:
- 加法:
- 减法:
- 乘法:
- 除法:
在实现中先计算分母 ,再分别求实部与虚部。对“分母为零”的防护放在调用侧(如 Div 函数)通过模长判断进行拦截,避免在运算符内部引入额外的交互逻辑。
上述运算符以引用方式接收参数,减少不必要的拷贝;返回值为新构造对象,避免修改参与运算的原始复数,从而使多次操作时状态更可控。
2. 复数输出格式设计(operator<<)
输出运算符 operator<< 负责将内部存储形式转换为更符合习惯的字符串形式,并统一处理边界显示,保证菜单展示与运算结果输出一致。
典型规则包括:
-
当实部与虚部均为 0 时输出
0; -
当仅有实部时输出实数(如
5); -
当仅有虚部时输出
i、-i或3.5i; -
当实部与虚部同时存在时:
- 虚部为正且已有实部时输出
a+bi; - 虚部为负时自然输出
a-bi;
- 虚部为正且已有实部时输出
-
对虚部系数为 的情况做简化: 与 不额外输出系数 1。
通过这些分支可以覆盖 、、、 等常见形式,并减少输出的冗余信息。
3. 数值字符串合法性校验(IsValidNumber)
函数 IsValidNumber(string s) 用于判断一个字符串是否可被解释为简单的十进制浮点数,为复数输入解析提供基础支撑。约束为:
- 允许首字符为
+或-作为符号位; - 允许出现小数点
.,但最多出现一次; - 其余字符必须为数字
0–9; - 空串直接判为非法。
覆盖 3、-2.5、+0.75、.5 等常用形式,不考虑科学计数法等更复杂格式,符合实验项目的输入需求与实现复杂度控制。
4. 复数字符串输入解析(operator>>)
输入运算符 operator>> 的目标是支持常见复数写法,并在遇到格式错误时通过 failbit 将错误状态反馈给上层交互逻辑。
实现思路是“整行读取 + 规则校验 + 分情况解析”:
-
整行读取:使用
getline读取一整行,避免空格或残留换行导致输入被截断。 -
字符集校验:逐字符检查,只允许数字、小数点、正负号与
i;一旦出现其它字符立即判错。 -
位置约束:若出现
i,必须位于字符串末尾,否则判错,避免歧义输入。 -
两大分支解析:
-
不含
i:视为纯实数,解析为实部,虚部置 0; -
含
i:去掉末尾i,再从末尾向前寻找用于分割实部与虚部的+或-(索引大于 0 才作为分隔符,避免把首字符符号误判为分隔)。- 找到分隔符:拆为实部片段与虚部系数片段;
- 未找到分隔符:视为纯虚数(实部为 0)。
-
对于虚部系数的特殊情形也进行兼容:空串或 + 视为 1,单独的 - 视为 -1,其余则必须通过 IsValidNumber 校验后才能解析为数值。
该解析逻辑能够支持 3+5i、-2.5-0.75i、i、-i、5 等输入形式,并在非法字符、i 位置异常或数值片段不合法时及时拒绝,保证系统不会在错误数据上继续运算。
5. 复数输入控制(Input)
Input(Complex& A) 将“提示—读取—失败重试”封装为闭环流程。每次尝试使用 cin >> A 调用输入运算符解析复数:
- 成功则退出循环;
- 失败则输出错误提示,调用
cin.clear()清除流错误状态,使下一次读入可继续进行。
由于底层解析使用整行读取,错误输入不会残留在缓冲区中,上层在清除状态后即可直接进入下一轮重试,交互体验更稳定。
6. 菜单与运算封装函数设计
菜单函数 menu(A,B) 在每轮交互时展示当前 与 ,并输出编号为 0–5 的操作选项,包括修改复数、加减乘除与退出。通过“状态先行”的展示方式,用户在连续运算时更容易核对对象来源。
运算功能分别由 Arr、Sub、Mul、Div 封装。前三者直接调用对应运算符得到结果 ,并按如下形式输出:
其中 分别为 、、。
Div 在执行除法前先检查 是否为 0;若 ,输出提示并终止本次操作,否则执行 并输出结果。这样可以在不中断程序整体运行的前提下,对非法运算进行局部拦截。
7. 主控制流程(main)设计
main 负责串联各模块并管理完整交互流程,整体为“启动说明—输入 A、B—循环菜单—分支调度—退出”。
-
启动阶段输出输入格式说明与示例,帮助用户快速建立输入预期;
-
依次调用两次
Input获取初始 与 ; -
在循环中先显示菜单,再读取整型选项
n:-
若读取失败(输入了非数字),则提示错误、清理流状态、丢弃本行剩余内容并回到菜单;
-
若读取成功,则通过
switch分派功能:- 1:重新输入 与 ;
- 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;}