0 C++中多态的概念
多态是指通过基类的指针或者引用,利用虚函数机制,在运行时确定对象的类型,并且确定程序的编程策略,这是OOP思想的核心之一。多态使得一个对象具有多个对象的属性。class Core作为就算成绩的基类、class Grad为Core的子类,添加了论文(thesis)成绩,Grad最终成绩为论文成绩和基类计算方法得到的成绩中的较小值。这是一个知识点:继承的适用场合就是,子类和基类的功能是相同或者相似,但是子类多了一些扩展。
//filename:Core.h #ifndef GUARD_Core_h #define GUARD_Core_h #include <iostream> #include <stdexcept> #include <string> #include <vector> class Core { public: Core(): midterm(0), final(0) { } Core(std::istream& is) { read(is); } std::string name() const; virtual std::istream& read(std::istream&); virtual double grade() const; virtual ~Core() { } protected: std::istream& read_common(std::istream&); double midterm, final; std::vector<double> homework; virtual Core* clone() const { return new Core(*this); } private: std::string n; friend class Student_info; }; class Grad: public Core { public: Grad(): thesis(0) { } Grad(std::istream& is) { read(is); } double grade() const; std::istream& read(std::istream&); private: double thesis; Grad* clone() const { return new Grad(*this); } };
1 使用句柄类的必要性推导及句柄类实现及示例
接下来讨论如何使用这两个类,因为这两个类是不同的类型,各自的read,grade函数都有不同的定义,如何简明地使用两个类就成了一个要解决的问题。
方案1是对于每个学生,先判断下是什么类型,然后声明这种类型的对象,调用类中定义的grade函数完成成绩统计工作,使用Core的方法如下,Grad的使用将以下程序的Core替换为Grad。
//main_1.cpp use Core& Grad vector<Core> students; Core record; string::size_type maxlen = 0; // read and store the data while (record.read(cin)) { maxlen = max(maxlen, record.name().size()); students.push_back(record); } // alphabetize the student records sort(students.begin(), students.end(), compare); // write the names and grades for (vector<Core>::size_type i = 0; i != students.size(); ++i) { cout << students[i].name() << string(maxlen + 1 - students[i].name().size(), ' '); try { double final_grade = students[i].grade(); // `Core::grade' streamsize prec = cout.precision(); cout << setprecision(3) << final_grade << setprecision(prec) << endl; } catch (domain_error e) { cout << e.what() << endl; } }
方案2:对于方案1,为了使用Core、Grad的使用,代码极大的重复,不是好的编程实践。方案2的改进是声明一个Core*类型的对象,通过Core*类型调用虚函数,以多态的方式完成程序。
//main_2.cpp int main() { vector<Core*> students; Core* record; char ch; string::size_type maxlen = 0; while (cin >> ch) { if (ch == 'U') record = new Core; else record = new Grad; record->read(cin); maxlen = max(maxlen, record->name().size()); students.push_back(record); } sort(students.begin(), students.end(), compare_Core_ptrs); for (std::vector<Core*>::size_type i = 0; i != students.size(); ++i) { cout << students[i]->name() << string(maxlen + 1 - students[i]->name().size(), ' '); try { double final_grade = students[i]->grade(); streamsize prec = cout.precision(); cout << setprecision(3) << final_grade << setprecision(prec) << endl; } catch (domain_error e) { cout << e.what() << endl; } delete students[i]; } return 0; }
3 句柄类接口与实现
方案2精简了代码,并且保证了调用不同方案的灵活性,但这样的方案有个缺点:编程中需要随时记得为不同的对象分配空间,在对象使用后要记得销毁Core*分配的对象,回收空间,增加了程序出错的可能性。句柄类的优势就是将类似工作交给专门的句柄类完成,程序员使用句柄类完成统计成绩的工作。
//student_info.h #ifndef GUARD_Student_info_h #define GUARD_Student_info_h #include <iostream> #include <stdexcept> #include <string> #include <vector> #include "Core.h" class Student_info { public: // constructors and copy control Student_info(): cp(0) { } Student_info(std::istream& is): cp(0) { read(is); } Student_info(const Student_info&); Student_info& operator=(const Student_info&); ~Student_info() { delete cp; } // operations std::istream& read(std::istream&); std::string name() const { if (cp) return cp->name(); else throw std::runtime_error("uninitialized Student"); } double grade() const { if (cp) return cp->grade(); else throw std::runtime_error("uninitialized Student"); } static bool compare(const Student_info& s1, const Student_info& s2) { return s1.name() < s2.name(); } private: Core* cp; }; #endif
//student_info.cpp #include <iostream> #include "Core.h" #include "Student_info.h" using std::istream; istream& Student_info::read(istream& is) { delete cp; char ch; is >> ch; if (ch == 'U') { cp = new Core(is); } else { cp = new Grad(is); } return is; } Student_info::Student_info(const Student_info& s): cp(0) { if (s.cp) cp = s.cp->clone(); } Student_info& Student_info::operator=(const Student_info& s) { if (&s != this) { delete cp; if (s.cp) cp = s.cp->clone(); else cp = 0; } return *this; }
代码说明:student_info类中封装了基类指针,调用Core或者Grad中定义的方法,所以Student_info中要有和Core类相同的接口,该类的拷贝构造函数要得到指针指向的类的信息,得到原来值得副本,这个副本由clone函数得到,所以在Core和Grad中定义不同的clone函数获得当前值得副本:
virtual Core* clone() const { return new Core (*this); }
virtual Grad* clone() const { return new Grad (*this); }
因为Core和Grad都有各自的拷贝构造函数,所以将clone函数声明放在protected中,virtual是可以继承的,一般而言子类中继承virtual函数的参数和返回类型应该与基类相同,但是通过基类指针和基类引用调用的虚函数重新定义的时候,返回类型可以是子类指针或者子类引用类型,Grad* clone( )为protected或者private标签下都可以。
4 使用句柄类
//main_3.cpp #include "Student_info.h" using std::cin; using std::cout; using std::domain_error; using std::endl; using std::setprecision; using std::setw; using std::sort; using std::streamsize; using std::string; using std::vector; #ifdef _MSC_VER #include "../minmax.h" #else using std::max; #endif int main() { vector<Student_info> students; Student_info record; string::size_type maxlen = 0; while (record.read(cin)) { maxlen = max(maxlen, record.name().size()); students.push_back(record); } sort(students.begin(), students.end(), Student_info::compare); for (std::vector<Student_info>::size_type i = 0; i != students.size(); ++i) { cout << students[i].name() << string(maxlen + 1 - students[i].name().size(), ' '); try { double final_grade = students[i].grade(); streamsize prec = cout.precision(); cout << setprecision(3) << final_grade << setprecision(prec) << endl; } catch (domain_error e) { cout << e.what() << endl; } } return 0; }
自己实现句柄类机制
5 实现代码
A——基类,B——A的子类,Handle——句柄类,检测拷贝构造函数、operator=是否正确,代码如下:
#include <iostream> using namespace std; class A { protected: int len; virtual A* clone() {return new A(*this);} private: int HandleA; friend class Handle; public: A():len(0) { } A(int a) { len = a;} A(istream& is) {read(is);}; virtual istream& read(istream& is); virtual int sum() {return len + 100;} virtual ~A(){}; }; istream& A::read(istream& is) { is >> len; return is; } class B : public A { public: B():HandleB (9) { } B(int b) { HandleB = b; } B(istream& is) {read(is);} istream& read(istream& is); int sum() { return HandleB * 123;} protected: B* clone() {return new B(*this);} //private & protected both OK! private: int HandleB; }; istream& B::read(istream& is) { is >> HandleB; return is; } class Handle { public: Handle():pa(0){} istream& read(istream& is); Handle(Handle& f) { if(f.pa) pa = f.pa->clone(); else pa = 0;} Handle& operator= (Handle& f) { if( &f != this){ delete pa; if(f.pa) pa = f.pa->clone(); else pa = 0; } return *this; } int sum () { if(pa) return pa->sum(); else { pa = 0; return 233 ;} } ~Handle() {delete pa ;} private: A* pa; }; istream& Handle::read(istream& is) { delete pa; char ch; is >> ch; if (ch =='a' || ch == 'A'){ pa = new A (is); } else if(ch =='b' || ch == 'B') { pa = new B(is); } else { pa = 0; } return is; } void main () { Handle f; A a(12); f.read(cin); Handle g(f); Handle h = f; cout << f.sum() << endl; cout << g.sum() << endl; cout << h.sum() << endl; }
// 第一个计算结果由句柄对象f调用sum函数得到,第二个计算结果拷贝f初始化g,调用g的sum得到,第三个计算机结果先用f赋值给h,调用h的sum计算得到,3个计算结果应该一致。