设计模式:考试总结
发布日期:2021-07-01 04:10:33
浏览次数:2
分类:技术文章
本文共 21545 字,大约阅读时间需要 71 分钟。
绪论
设计模式:每一个模式描述了一个在我们周围不断重复发生的问题,以及该问题的解决方案的核心。这样,你就能一次又一次地使用该方案而不必做重复劳动。
一个模式有四个基本要素:
- 模式名称(pattern name):一个助记名,它用一两个词来描述模式的问题、解决方案和效果。
- 问题(problem):描述了应该在何时使用模式。
- 不是基本的数据结构,也不是整个系统。
- 不特定于域。
- 解决方案(solution):描述了设计的组成成分,它们之间的相互关系及各自的职责和协作方式。
- 效果(consequences):描述了模式应用的效果及使用模式应权衡的问题。
- 对时间和空间的衡量。
- 语言和实现问题。
- 对系统灵活性、扩充性或可移植性的影响。
设计模式扮演的角色:
- 应用程序:减少类依赖性、平台依赖性。
- 工具箱:代码复用。
- 框架:强调设计重用,在框架中使用模式使学习框架的使用更加容易。
设计模式与框架最主要的不同在于如下三个方面:
- 设计模式比框架更抽象。
- 设计模式是比框架更小的体系结构元素。
- 框架比设计模式更加特例化。
- 使用设计模式的缺点:
- 使设计复杂化。
- 根据空间和时间要求,降低系统性能。
- 仅在需要时使用设计模式:
- 灵活性。
- 可扩展性。
- 便携性。
实例研究:设计一个文档编辑器
设计问题:
- 文档结构:组合模式
- 格式化:策略模式
- 修饰用户界面:装饰模式
- 支持多种视感标准:抽象工厂模式
- 支持多种窗口系统:桥接模式
- 用户操作:命令模式
- 拼写检查和断字处理:迭代器模式和访问者模式
文档结构:组合模式
class Glyph { public: virtual void Draw(Window *) = 0; virtual void Bounds(Rect &) = 0; virtual bool Intersects(const Point &) = 0; virtual void Insert(Glyph *, int) = 0; virtual void Remove(Glyph *) = 0; virtual Glyph *Child(int) = 0; virtual Glyph *Parent() = 0;}
程序格式化示例
格式化:策略模式
void Composition::Compose() { switch (_breakingStrategy) { case SimpleStrategy: ComposeWithSimpleStrategy(); break; case TexStrategy: ComposeWithTexStrategy(); break; // ... }}// With strategy patternvoid Composition::Compose() { _compositor->Compose(); // ...}
修饰用户界面:装饰模式
- 使用类继承或mixin class的缺点:
- 运行时不能扩展或删除功能。
- 如果有许多装饰功能,子类的数量就会激增。
- 使用类组合的缺点:
- 两个类的接口不同。
- 难以递归装饰。
- 基本类必须注意接口元素。
装饰模式与复合模式的比较:
- 装饰模式是退化的复合模式。
- 意图不同:
- 装饰:用于添加其他功能。
- 复合:用于对象聚合。
I/O 流示例
Stream *fStream = new FileStream("test.cpp");Stream *mStream = new MemoryStream();Stream *fcStream = new CompressingStream( new FileStream("test.cpp"));Stream *f7Stream = new ASCII7Stream(new FileStream("test.cpp"));Stream *fc7Stream = new CompressingStream( new ASCII7Stream(new FileStream("test.cpp")));fc7Stream->PutInt(12);fc7Stream->PutString("hello");
支持多种视感标准:抽象工厂模式
switch (Style) { case Motif: ScrollBar * sb = new MotifScrollBar; Button * bn = new MotifButton; Menu * mn = new MotifMenu; break;case Mac: ScrollBar * sb = new MacScrollBar; Button * bn = new MacButton; Menu * mn = new MacMenu; break;case PM: ScrollBar * sb = new PMScrollBar; Button * bn = new PMButton; Menu * mn = new PMMenu; break;}// With abstract factory patternswitch (Style) { case Motif: guiFactory = new MotifFactory( ); break; case Mac: guiFactory = new MacFactory( ); break; case PM: guiFactory = new PMFactory( ); break;}ScrollBar * sb = guiFactory->CreateScrollBar( );Button * bn = guiFactory->CreateButton( );Menu * mn = guiFactory->CreateMenu( );// new ImplementationWidget * MotifFactory::Make(string name){ if ( name == "ScrollBar" ) return new MotifScrollBar(); // other widgets ... // to append a new widget if ( name == "ListBox" ) return new MotifListBox(); }//...ScrollBar * sb = dynamic_cast(guiFactory->Make("ScrollBar") );ListBox * lb = dynamic_cast (guiFactory->Make(“ListBox”);
支持多种窗口系统:桥接模式
桥接模式和策略模式之间的区别:
- 策略模式:
- 对于一个操作,可以运行时开关,具有上下文。
- 桥接模式:
- 类层次结构。
用户操作:命令模式
命令模式的应用:
- 支持撤消。
- 支持上下文相关菜单。
- 支持命令宏。
- 支持记录更改以恢复崩溃的系统。
- 支持信息系统中的事务概念。
拼写检查和断字处理:迭代器模式和访问者模式
迭代器模式
templateclass List { List(long size = DEFAULT_LIST_CAPACITY); long count() const; Item &Get(long index) const; void print();}template class Iterator { public: virtual void First() = 0; virtual void Nex() = 0; virtual bool IsDone() const = 0; virtual Item CurrentItem() const = 0;protected: Iterator();}template class ListIterator : public Iterator - { public: ListIterator(const List
- *aList) : _list(aList), _current(0) { }; virtual void First() { _current = 0; } virtual void Next() { _current++; } virtual bool IsDone() const { return _current >= _list->Count(); } virtual Item CurrentItem() const { if (IsDone()) throw IteratorOutOfBounds; return _list->Get(_current); }private: const List
- *_list; long _current;}void PrintEmployees(ListIterator
&i) { for (i.First(); !i.IsDone(); i.Next()) { i.CurrentItem()->Print(); }}List *employees;ListIterator forward(employees);ReverseListIterator backward(employees);PrintEmployees( forward );PrintEmployees( backward );
保证迭代器被删除:动态分配的迭代器对象在使用完毕后,必须删除这个迭代器,否则会造成内存泄露。IteratorPtr 提供作为一个迭代器的代理,通过定义私有的 new 和 delete 操作符来保证 IteratorPtr 总在栈上分配,可以保证在 Iterator 对象离开作用域时清除它。并且重载了“->”和“*”操作符为指向迭代器的指针,使得和调用迭代器的语法相同。
templateclass IteratorPtr { public: IteratorPtr(Iterator - *i) : _i(i) { } ~IteratorPtr() { delete _i; } Iterator
- *operator->() { return _i; } Iterator
- &operator*() { return *_i; }private: // to avoid multiple deletions of _i IteratorPtr(const IteratorPtr &); IteratorPtr &operator=(const IteratorPtr &);private: Iterator
- *_i;}// pseudo-codevoid ConditionalInsert(AbstractList *list, Item I) { IteratorPtr it(list->CreateIterator()); // syntax of using an IteratorPtr object is as the // same as the Iterator* for (it->First(); !it->IsDone; it->Next()) if (it->CurrentItem == I) break; if (!it->IsDone) return; // the item already exist. list->Append(I); // the "Iterator*" will be deleted automatically}
迭代器模式:先序遍历
void PreorderIterator::Next() { Iterator < Glyph * > *i = _iterators.Top()->CurrentItem()->CreateIterator(); i->First(); _iterators.Push(i); while (_iterators.Size() > 0 && _iterators.Top()->IsDone()) { delete _iterators.Pop(); _iterators.Top()->Next(); }}
访问者模式
void SpellingChecker::Check( Glyph * glyph ) { Character * c; Row * r; Image * i; if (c=dynamic_cast(glyph)) { // analyze the character }else if (r=dynamic_cast (glyph ) { // prepare to analyze r's children }else if (i=dynamic_cast
(glyph ) { // do nothing }}SpellingChecker spellingChecker;Composition *c;Glyph * g;PreorderIterator i(c);for ( i.First(); ! i.IsDone(); i.Next() ) { g = i.CurrentItem( ); SpellingChecker.Check( g );}// With visitor patternvoid Subclass::CheckMe(SpellingChecker& checker) { checker.Check(this);}class SpellingChecker { public: SpellingChecker(); void Check(Character*); void Check(Row*); void Check(Image*);private: char _currentWord[ MAX_WORD_SIZE ]; }SpellingChecker spellingChecker;Composition *c;Glyph * g;PreorderIterator i(c);for ( i.First(); ! i.IsDone(); i.Next() ) { g = i.CurrentItem( ); g.CheckMe( spellingChecker );}
创建型模式
生成器模式
生成器隐藏了产品的内部结构和组装细节。
生成器模式和抽象工厂模式的区别:
- 生成器模式:
- builder类负责组装产品,可能通过调用其他类来实现。
- builder类逐步制造和组装产品。
- 抽象工厂模式:
- 抽象工厂模式的客户负责组装产品。
- 工厂一次生产所有零件。
工厂方法模式
图表元素操作示例
层次结构部分平行。
实现代码
// Parameterized factory methodclass Creator { virtual Product * Create(ProductID id) { if (id==MINE) return new MyProduct; if (id==YOURS) return new YourProduct; ... }};class MyCreator: public Creator { virtual Product * Create(ProductID id) { if (id==YOURS) return new MyProduct; if (id==MINE) return new YourProduct; if (id==THEIRS) return new TheirProduct; ... return Creator::Create(id); }}// Template could be used to avoid sub-classing of the creator classclass Creator { public: virtual Product * CreateProduct() = 0;};templateclass StandardCreator: public Creator{ public: virtual Product* CreateProduct() { return new TheProduct; }}StandardCreator aCreator;StandardCreator bCreator;
原型模式
实现代码
class CPrototypeMonster { protected: CString _name;public: CPrototypeMonster(); CPrototypeMonster(const CPrototypeMonster ©); ~CPrototypeMonster(); virtual CPrototypeMonster *Clone() const = 0; void Name(CString name); CString Name() const;};class CGreenWigglyMonster : public CPrototypeMonster { protected: int _numberOfArms; double _slimeAvailable;public: CGreenWigglyMonster(); CGreenWigglyMonster(const CGreenWigglyMonster ©); ~CGreenWigglyMonster(); virtual CPrototypeMonster *Clone() const; void NumberOfArms(int numberOfArms); void SlimeAvailable(double slimeAvailable); int NumberOfArms() const; double SlimeAvailable() const;};class CPurpleLongNoseMonster : public CPrototypeMonster { protected: int _intensityOfBadBreath; double _lengthOfWhiplikeAntenna;public: CPurpleLongNoseMonster(); CPurpleLongNoseMonster(const CPurpleLongNoseMonster ©); ~CPurpleLongNoseMonster(); virtual CPrototypeMonster *Clone() const; void IntensityOfBadBreath(int intensityOfBadBreath); void LengthOfWhiplikeAntenna(double lengthOfWhiplikeAntenna); int IntensityOfBadBreath() const; double LengthOfWhiplikeAntenna() const;};class CGreenPotBellyMonster : public CPrototypeMonster { protected: double _roomAvailableInBelly;public: CGreenPotBellyMonster(); CGreenPotBellyMonster(const CGreenPotBellyMonster ©); ~CGreenPotBellyMonster(); virtual CPrototypeMonster *Clone() const; void RoomAvailableInBelly(double roomAvailableInBelly); double RoomAvailableInBelly() const;};CPrototypeMonster::CPrototypeMonster(const CGreenWigglyMonster ©) { _name = copy._name;}CGreenWigglyMonster::CGreenWigglyMonster(const CGreenWigglyMonster ©) : CPrototypeMonster(copy) { _numberOfArms = copy._numberOfArms; _slimeAvailable = copy.slimeAvailable;}CPurpleLongNoseMonster::CPurpleLongNoseMonster(const CPurpleLongNoseMonster ©) : CPrototypeMonster(copy) { _intensityOfBadBreath = copy._intensityOfBadBreath; _lengthOfWhiplikeAntenna = copy._lengthOfWhiplikeAntenna;}CGreenPotBellyMonster::CGreenPotBellyMonster(const CGreenPotBellyMonster ©) : CPrototypeMonster(copy) { _roomAvailableInBelly = copy._roomAvailableInBelly;}CPrototypeMonster *CGreenWigglyMonster::Clone() const { return new CGreenWigglyMonster(*this);}CPrototypeMonster *CPurpleLongNoseMonster::Clone() const { return new CPurpleLongNoseMonster(*this);}CPrototypeMonster *CGreenPotBellyMonster::Clone() const { return new CGreenPotBellyMonster(*this);}void DoCoolStuffWithAMonster(const CPrototypeMonster *originalMonster) { CPrototypeMonster *newMonster = originalMonster->Clone(); ASSERT(newMonster); newMonster->Name("A name"); // Add code doing all sorts of cool stuff with the monster. delete newMonster;}
使用原型管理器
class CBasePrototype { protected: CString _name;public: CBasePrototype(); CBasePrototype(const CBasePrototype ©); ~CBasePrototype(); virtual CBasePrototype *Clone(CString _name) const = 0; void Name(CString name); CString Name() const;};class CPrototypeManager { protected: std::list_prototypeList;public: CPrototypeManager(); CPrototypeManager(const CPrototypeManager ©); ~CPrototypeManager(); unsigned intNumberOfPrototypes() const; void AddPrototype(CBasePrototype *prototype); void DeletePrototype(const CString *label); void DeleteAllPrototypes(); CBasePrototype *FindPrototype(const CString *label); const CBasePrototype *FindPrototype(const CString *label); std::list AvailablePrototypes() const;};
结构型模式
适配器模式
class Shape { public: Shape(); virtual void BoundingBox(Point &bottomLeft, Point &topRight) const; virtual Manipulator *CreateManipulator() const;};class TextView { public: TextView(); void GetOrigin(Coord &x, Coord &y) const; void GetExtent(Corrd &width, Coord &height) const; virtual bool IsEmpty() const;}class TextShape : public Shape, private TextView { public: TextShape(); virtual void BoundingBox(Point &bottomLeft, Point &topRight) const { Corrd bottom, left, width, height; GetOrigin(bottom, left); GetExtent(width, height); bottomLeft = Point(bottom, left); topRight = Point(bottom + height, left + width); } virtual bool IsEmpty() const { return TextView::IsEmpty(); } virtual Manipulator *CreateManipulator() const { return new TextManipulator(this); }};
Lambda 示例
#include#include //Adaptee 1struct CoffeMaker { void Brew(double quantity, double temp) const { std::cout << "I am brewing " << quantity << "ml coffee @" << temp << " degree C" << std::endl; }};//Adaptee 2 (having difference interface from Adaptee 2)struct JuiceMaker { void Squeeze(double quantity) const { std::cout << "I am making " << quantity << "ml Juice" << std::endl; }};// Targetstruct Beverage { virtual void getBeverage(int quantity) = 0;};// Adapterclass Adapter : public Beverage { std::function Request;public: Adapter(CoffeMaker *cm1) { Request = [cm1](int quantity) { cm1->Brew(quantity, 80); }; } Adapter(JuiceMaker *jm1) { Request = [jm1](int quantity) { jm1->Squeeze(quantity); }; } void getBeverage(int quantity) { Request(quantity); }};//Clientint main() { CoffeMaker coffeMaker; Adapter adp1(&coffeMaker()); adp1.getBeverage(30); JuiceMaker juiceMaker Adapter adp2(&juiceMaker); adp2.getBeverage(40);}
外观模式
行为模式
职责链模式
void Handler::handleRequest(Request *theRequest) { switch (theRequest->getKind()) { case Help: // cast argument to appropriate type handleHelp((HelpRequest *) theRequest); break; case Keyboard_Message: handlePrint((Keyboard_Message *) theRequest); break; default: // ... break; }}typedef int Topic;const Topic NO_HELP_TOPIC = -1class HelpHandler { public: HelpHandler(HelpHandler *successor = 0, Topic= NO_HELP_TOPIC); virtual bool hasHelp(); virtual void setHandler(HelpHandler *successor, Topic); virtual void handleHelp();private: HelpHandler *_successor; Topic _topic;};HelpHandler::HelpHandler(HelpHandler *successor, Topict) : _successor(successor), _topic(t) { }bool HelpHandler::hasHelp() { return _topic != NO_HELP_TOPIC;}void HelpHandler::handleHelp() { if (_successor != 0) { _successor->handleHelp(); }}class Widget : public HelpHandler { protected: Widget(Widget *parent_, Topic t = NO_HELP_TOPIC);private: Widget *parent;}Widget::Widget(Widget *parent_, Topic t) :HelpHandler(parent_, t) { parent = parent_;}class Button : public Widget { public: Button(Widget *parent_, Topic t = NO_HELP_TOPIC); virtual void handleHelp();}Button::Button(Widget *parent_, Topic t) :Widget(parent_, t) { }void Button::handleHelp() { if (hasHelp()) { // Offer help on the button } else { HelpHandler::handleHelp(); }}class Dialog : public Widget { public: Dialog(HelpHandler *parent_, Topic t = NO_HELP_TOPIC); virtual void handleHelp();}Dialog::Dialog(HelpHandler *parent_, Topic t) :Widget(0) { setHandler(parent_, t);}void Dialog::handleHelp() { if (hasHelp()) { // offer help on the dialog } else { HelpHandler::HandleHelp(); }}class Application : public HelpHandler { public: Application(Topic t) : HelpHandler(0, t) { } virtual void handleHelp();}void Application::handleHelp() { // application-specific operations, eg. // show a list of help topics}const Topic APPLICATION_TOPIC = 1;const Topic PRINT_TOPIC = 2;const Topic PAPER_ORIENTATION_TOPIC = 3;Application *application = new Application(APPLICATION_TOPIC);Dialog *dialog = new Dialog(application, PRINT_TOPIC);Button *button = new Button(dialog, PAPER_ORIENTATION_TOPIC);
解释器模式
class BooleanExp { public: BooleanExp(); virtual ~BooleanExp(); virtual bool Evaluate(Context &) = 0; virtual BooleanExp *Replace(const char *, BooleanExp &) = 0; virtual BooleanExp *Copy() const = 0;}class Context { public: bool Lookup(const char *) const; void Assign(VariableExp *, bool);}class VariableExp : public BooleanExp { public: VariableExp(const char *name) { _name = strdup(name); } virtual ~VariableExp(); virtual bool Evaluate(Context &aContext) { return aContext.Lookup(_name); } virtual BooleanExp *Replace(const char *name, BooleanExp &exp) { if (strcmp(name, _name) == 0) return exp.Copy(); else return new VariableExp(_name); } virtual BooleanExp *Copy() const { return new VariableExp(_name); }private: char *_name;}class AndExp : public BooleanExp { public: AndExp(BooleanExp *op1, Boolean *op2) { _operand1 = op1; _operand2 = op2; } virtual ~AndExp(); virtual bool Evaluate(Context &aContext) { return _operand1->Evaluate(aContext) && _operand2->Evaluate(aContext); } virtual BooleanExp *Replace(const char *name, BooleanExp &exp) { return new AndExp( _operand1->Replace(name, exp), _operand2->Replace(name, exp)) } virtual BooleanExp *Copy() const { return new AndExp( _operand1->Copy(), _operand2->Copy()) }}void main() { // The expression is: (true and x ) or (y and (not x) ) BooleanExp *expression; Context context; VariableExp *x = new VariableExp("X"); VariableExp *y = new VariableExp("Y"); Expression = new OrExp( new AndExp(new Constant(true), x), new AndExp(y, new NotExp(x)) ); context.Assign(x, false); context.Assign(y, true); Bool result = expression->Evaluate(context); // Now we replace the variable y with a new expression and // re-evaluate the expression VariableExp *z = new VariableExp("Z"); NotExp not_z(z); BooleanExp *replacement = expression->Replace("Y", not_z); context.Assign(z, true); result = replacement->Evaluate(context);}
观察者模式
Sort 案例
概述
对一个文件的所有行进行排序。支持的运行参数
-i : 忽略大小写 -n: 将关键字看作数字,按照数字大小进行排序 -f k: 关键字从第k个field开始。默认情况为整个一行 -c k:关键字从第k列开始。默认情况为整个一行 -p [ first | random | median3 ]:指定 pivot 值 -r : 降序输出,默认情况为升序输出- Singleton
- Options, System_Sort, Compare_Func_Factory
- Strategies
- Pivot_Strategies以及其三个子类
- Adaptor
- Sort_AT_Adaptor
- Facade
- System_Sort
class Options { public: enum Pivot_Strategy { FIRST, RANDOM, MEDIAN3 }; static Options *instance(); void parse_args(int argc, char **argv); bool ignore_case(); bool key_is_numeric(); bool reverse_output(); int field_offset(); int column_offset(); Pivot_Strategy pivot_strat(); char *file_name();private: Options(); bool _ignore_case; bool _key_is_numeric; bool _reverse_output; int _field_offset, _column_offset; Pivot_Strategy _pivot_strat; char *_file_name; static Options _instance;};class Input { public: Input(); char *read(char *file_name); char *buffer() const; size_t num_lines() const;private: size_t _num_lines; char *_buffer;};templateclass Array { public: void Resize(size_t size = 0) { _size = size; _array = new T[_size]; } ~Array() { delete[] _array; }; T &operator[](size_t index) { return _array[index]; } size_t size() const { return _size; }private: T *_array; size_t _size;};template class Access_Table { public: virtual int make_table(char *buffer, size_t num_lines) = 0; T &element(size_t index) { return _access_array[index]; }; size_t length() const { return _access_array.size(); } virtual ~Access_Table() { delete _access_buffer; };protected: Array _access_array; char *_access_buffer;};class Pivot_Strategies { public: virtual int get_pivot(Sort_AT_Adaptor &, int lo, int hi) = 0;};class First_Pivot : public Pivot_Strategies { public: virtual int get_pivot(Sort_AT_Adaptor &, int lo, int hi);};class Random_Pivot : public Pivot_Strategies { public: Random_Pivot(); virtual int get_pivot(Sort_AT_Adaptor &, int lo, int hi);};class Median3_Pivot : public Pivot_Strategies { public: virtual int get_pivot(Sort_AT_Adaptor &, int lo, int hi);};class Line_Ptrs { public: // Comparison operator used by sort(). int operator<(const Line_Ptrs &) const; // Beginning of line and key (field or column). char *_bol, *_bok;};int Line_Ptrs::operator<(const Line_Ptrs &p) const { Compare_Func_Factory::Func_Pointer fp; fp = Compare_Func_Factory::instance()->get_func_pointer(); return ((*fp)(this->_bok, r._bok) <= 0);}class Sort_AT_Adaptor : private Access_Table { public: virtual int make_table(char *buffer, size_t num_lines); Line_Ptrs &operator[](size_t index) { return element(index); } size_t size() const { return length(); } friend std::ostream &operator<<(std::ostream &os, Sort_AT_Adaptor &sort_at_adaptor);};class Compare_Func_Factory { public: typedef int (*Func_Pointer)(const char *, const char *); static Compare_Func_Factory *instance(); void set_func_pointer(); Func_Pointer get_func_pointer();private: Compare_Func_Factory(); static Compare_Func_Factory _instance; Func_Pointer _func_pointer;};
转载地址:https://mortal.blog.csdn.net/article/details/92421481 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!
发表评论
最新留言
表示我来过!
[***.240.166.169]2024年04月10日 12时14分55秒
关于作者
喝酒易醉,品茶养心,人生如梦,品茶悟道,何以解忧?唯有杜康!
-- 愿君每日到此一游!
推荐文章
HDU-2838 Cow Sorting(树状数组)
2019-04-30
基于SSM的兼职论坛系统的设计与实现
2019-04-30
基于java的ssm框架就业信息管理系统的设计
2019-04-30
如何用同期群分析模型提升留存?(Tableau实战)
2019-04-30
Oracle字符串分隔符替换(替换奇数个或偶数个)
2019-04-30
Oracle 利用 UTL_SMTP 包发送邮件
2019-04-30
Oracle的pfile和spfile的一点理解和笔记
2019-04-30
java实现稀疏数组及将稀疏数组存入硬盘中
2019-04-30
2021-05-18
2019-04-30
基础架构系列篇-NGINX部署VUE
2019-04-30
基础架构系列篇-系统centos7安装kafka
2019-04-30
软件质量的8个特性
2019-04-30
2021年不可错过的17种JS优化技巧(一)
2019-04-30
在 Vue 中用 Axios 异步请求API
2019-04-30
MySQL进阶查询(SELECT 语句高级用法)
2019-04-30
Mysql 之主从复制
2019-04-30
【NLP学习笔记】中文分词(Word Segmentation,WS)
2019-04-30
对于时间复杂度的通俗理解
2019-04-30
如何输入多组数据并输出每组数据的和?
2019-04-30