文章目录
1 继承
在一个类与另外一个类比较,除了一些扩充外,其余的部分相同。我们可以考虑使用继承。
基类集成一些核心的公共操作,另一个类进行扩充。
例如:
基类
class Core{
public:
Core();
Core(std::istream&);
std::string name() const;
double garde() const;
std::istream& read(std::istream&);
protected:
std::istream& read_commom(std::istream&);
double mid, final;
std::vector<double> homework;
private:
std::string n;
};
派生类:
class Grad::public Core{
public:
Grad();
Grad(std::istream&);
double garde() const;
std::istream& read(std::istream&);
private:
double thesis;
};
Grad从Core类派生而来,但是由于使用了public关键字,因此Grad仅将Core的公有接口继承过来作为Grad公有接口的一部分,Core的公有成员同时也是Grad的公有成员。另外保护成员,Grad也可以访问。
派生类可以重定义基类的成员函数,但是在派生类中不能删除任何基类函数。
例如重定义read函数:
std::istream& Grad::read(std::istream& in){
read_common(in);
in>>thesis;
read_hw(in,homework);
//等同于 read_hw(in,Core::homework);
return in;
}
在派生类中无需声明就可以调用基类中的成员与成员数据。
1.1 继承与构造函数
对于自定义类型来说,编译器现为对象分配内存空间,然后调用一个合适的构造函数初始化对象。
但对于派生类来说:
-
1 为整个对象分配内存空间(包括基类和派生类定义的数据);
-
2 调用基类构造函数以便初始化对象中的基类部分数据;
-
3 使用构造出初始化器对对象的派生类部分数据进行初始化;
-
4 如果有的话,执行派生类构造函数的函数体。
-
自己选择基类构造函数
- 派生类中,可以使用构造初始化器来指定要想调用的基类的构造函数。
- 在构造初始化器中使用它的基类名,并在基类名后面附上一个参数列表(可以为空),这些参数用于构造函数对象中基类的部分的初始值。
- 编译器根据参数的个数与类型选择调用基类中的相应构造函数。
//派生类构造函数
<派生类名>::<派生类名>(<形参声明>) : <基类名>(<参数表>){
<派生类构造函数的函数体>
}
- 不选择
- 如果初始化时,没有指定基类中的哪个构造函数,那编译器将调用基类默认的构造函数以构造对象。
- 也就是除非基类有默认构造函数,不然派生类就必须显示调用基类的构造函数。
示例(不选择):
基类:
class Core{
public:
Core():mid(0),final(0){}
Core(std::istream& is){read(is);}
};
派生类:
class Grad::public Core{
//两个构造函数均隐式调用Core::Core函数,初始化基类部分
Grad():theis(0){}
Grad(std::istream& is){read(is);}
};
C++没要求派生类构造函数一定要带有基类构造函数一样的参数。
对于下面的程序:
首先系统为Grad类型对象分配合适的内存,然后运行Core类的默认构造函数对g对象中的Core类部分的数据成员进行初始化,最后调用Grad的默认构造函数。
Grad g;
对于下面的程序:
首先系统为Grad类型对象分配合适的内存,然后运行Core类的默认构造函数对g对象中的Core类部分的数据成员进行初始化,最后调用Grad::Grad(istream&)构造函数为name、mid、final、thesis、homework成员初始化。
Grad g(cin);
示例(选择):
基类:
class Core{
public:
Core(double mid, double final){//被派生类构造函数选择
mid = 0;
final = 0;
}
Str_c name() const { return n;}
protected:
double mid, final;
Vec<double> homework;
private:
Str_c n;
};
派生类:
class Grad:public Core{
public:
Grad(double mid, double final, double thesis):Core(mid,final){thesis = 0;}
private:
double thesis;
};
1.2 多态与虚拟函数
1.2.1虚拟函数
我们希望希望系统根据实际传递给函数的参数类型来运行基类和派生类中都公有的,但是派生类重定义的函数(函数名和参数列表类型都相同)。
示例:(未重定义基类的成员函数)
比较函数:
bool compare(const Core& c1, const Core &c2){
return c1.name() < c2.name();
}
调用这个比较函数
Core c(cin);
Core c2(cin);
Grad g(cin);
Grad g2(cin);
compare(g,g2);//两个Grad比较
compare(c,c2);//两个Core比较
compare(c,g);//Grad记录和一个Core记录比较
在比较Grad类时,实际上是在调用从Core类继承过来的name函数。原理是,在以指针作为compare函数的参数,编译器可以讲指向Grad类型的对象指针,转化为Core*,并将指针绑定到Grad对象中的Core部分。
示例:(重定义基类的成员函数)头文件
bool compare_grades(const Core& c1, const Core& c2){
return c1.grade() < c2.grade();
}
由于Grad中也重定义了grade函数,但是没有做任何的区分,如果比较Grad类型对象就会出错,因为Grad和Core中类的grad函数是不同的,对于Grad类型对象,由于多了thesis,只能调用Grad::grade函数。
我们希望的是根据传递给函数的实际参数来调用对应的Grad类型中的grade函数或Core中的grad函数,而参数只有运行时才是已知的。
为了支持这样的选择,C++提供了虚拟(virtual)函数
class Core{
public:
virtual double grade() const;
};
这样在调用comprade_grade函数,程序在执行时将由参数c1和c2的实际类型决定调用哪个grade函数。
virtual只能类的定义里被使用。如果virtual在函数体之外单独定义,那么我们无法在定义时重复virtual关键字。virtual函数一定要通过对象来调用,因为有隐藏的this指针。声明和实现分离时,在实现文件中不能定义实现纯虚函数
如果类中一个函数是虚拟的,那么派生类中它的虚拟特性也会被继承。那么在派生类中需要在重复声明virtual关键字。
如果基类和派生类具有相同的函数名,但是两个函数的参数个数与参数类型都不相同,那么它们就像完全不相同的两个函数。
示例:
定义:
void Core::regrade(double d){final = d;}
void Grad::regrade(double d1, double d2){final = d1;thesis = d2;}
调用:
Core& r;//r是指向Core类型的引用
r.regrade(100);
r.regrade(100,100);//编译错误,Core::regrade函数只带一个参数
Grad& g;//g是指向Grad类型的引用
g.regrade(100);//编译错误,因为Grade::regrade带有两个参数
g.regrade(100,100);//能正常运行
对于Grad类来说,虽然有一个只带一个参数的基类Core的regrade函数,但是在派生类中也有regrade这个成员函数,因此,基类中的regrade函数被隐藏了起来。
如果我们想在派生类中显示调用基类中的regrade函数,那么就要对它进行显示调用。
g.Core:regrade(100);//调用Core::regrade
如果要将regrade声明为虚拟函数,那么基类和派生类中regrade函数的接口声明要相同,可以想下面那样,另外增加一个具有默认值的不被使用的函数。
virtual void Core::regrade(double d, double = 0){final = d;}
1.2.1.1 纯虚拟函数
纯虚拟函数:不用为虚拟函数写出具体的实现,但是这个类不能有相应的对象。可能会有派生类的对象(实现了该纯虚拟函数)。
定义了纯虚拟函数的类为抽象基类,该类只在继承树中用于提供对象的接口。编译器会禁止为这个类生成相应的对象。
示例:
calss{
virtual wd_sz width() const = 0;//纯虚拟函数
};
一个函数的纯虚拟特性也会被继承。如果在派生类中定义了全部继承而来的虚拟函数,那么它就是一个具体的类。
1.2.2 动态绑定or静态绑定
只有引用或者指针为参数调用虚拟函数时,它的运行选择才会有意义。
如果我们以对象(相对于引用或指针而言)的名义调用一个虚拟函数,那么我们可以在编译时了解对象的类型。对象的类型一旦确定,即使在运行时也不会改变。
相反的,一个指向基类的对象的引用或者指针可能确实是指向一个基类对象,也可能是指向该基类派生出来类的对象,也就是说,引用或者指针指向的对象的实际类型在运行时是可以变换的。
动态绑定或静态绑定的区别:
动态绑定(dynamic binding)就是运行是才决定调用什么函数。
静态邦迪:编译的时候就决定下来调用什么函数。
如果一个指针或一个引用调用虚拟函数,那么函数将是动态绑定的。
示例:
Core c;
Grad g;
Core* p;
Core& r = g;
c.grade();//Core::grade()静态绑定
g.grad();//Grade::grade()静态绑定
p->grade();//根据所对象的类型进行动态绑定
r.grade();//根据r所引用对象进行动态绑定
在要求一个指向基类对象的指针或引用的地方,可以用一个指向派生类的指针或引用来代替,即多态性(polymorphism)。
多态性:起源于希腊词语(polymorphos),意思是“具有多种形态“。在程序设计中,指用一个类型表示几种类型的能力。C++通过虚拟函数的动态 绑定性以支持多态性。
在通过一个指针或一个引用调用一个虚拟函数时,实际上就在进行一个多态调用。引用(或者指针)参数的类型是固定的,但是参数所引用(或者所指)的对象的类型可以是所引(或所指)对象的类型,或者由该类派生出来的任何一个子类。因此可以通过一种类型来调用许多函数中的一个。
虚拟函数无论是否调用它,都要在程序中对它们进行定义。对于非虚拟函数来说,如果程序中没有调用它,可以在类中仅对它进行声明而不定义。
但是如果在类中只对虚拟函数进行了声明而不定义,许多编译器就会生成一些奇怪的错误信息。如果一个程序在编译时产生一个莫名其妙的信息,而且信息说明存在一些没有被定义的内容,那么最好检查一下是不是对全部的虚拟函数进行了定义。
更新前面示例设计的类:
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;
protected:
std::istream& read_common(std::istream&);
double midterm, final;
std::vector<double> homework;
private:
std::string n;
};
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;
};
1.3 示例
1.3.1 对类型依赖
头文件:
//
// Created by MacBook Pro on 2020-03-19.
//
#ifndef ACM_TEST_H
#define ACM_TEST_H
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;
protected:
std::istream& read_common(std::istream&);
double midterm, final;
std::vector<double> homework;
private:
std::string n;
};
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;
};
std::istream& read_hw(std::istream&, std::vector<double>&);
double grade(double, double, double);
double grade(double, double, const std::vector<double>&);
double median(std::vector<double> );
bool compare(const Core& , const Core& );
bool compare_core_ptrs(const Core*, const Core*);
bool compare_grades(const Core& , const Core&);
#endif //ACM_TEST_H
源文件:
#include <iostream>
#include <string>
#include <vector>
#include <stdexcept>
#include <algorithm>
#include <iomanip>
#include <string>
#include "test.h"
using std::cin; using std::cout;
using std::endl;
using std::istream;
using std::domain_error;
using std::vector;
using std::min;
using std::string;
using std::max;
using std::streamsize;
using std::setprecision;
using std::sort;
using std::string;
std::istream& read_hw(std::istream&, std::vector<double>&);
std::string Core::name() const { return n;}
istream& Core::read_common(istream& in){
in >> n >> midterm >> final;
return in;
}
istream& Core::read(istream& in){
read_common(in);
read_hw(in, homework);
return in;
}
istream& Grad::read(istream& in){
read_common(in);
in >> thesis;
read_hw(in, homework);
return in;
}
//不能将重载函数命名为模版参数,不然编译器无法决定调用按一个版本
bool compare(const Core& c1, const Core& c2){
return c1.name() < c2.name();
}
bool compare_grades(const Core& c1, const Core& c2){
return c1.grade() < c2.grade();
}
//改版一
bool compare_core_ptrs(const Core* cp1, const Core* cp2){
return compare(*cp1, *cp2);
}
double Core::grade() const{
return ::grade(midterm, final, homework);
}
double Grad::grade() const {
return min(Core::grade(), thesis);//显示表明
}
double median(vector<double> vec){
typedef vector<double>::size_type vec_sz;
vec_sz size = vec.size();
if(size == 0){
throw domain_error("median of an empty vector");
}
sort(vec.begin(), vec.end());
vec_sz mid = size/2;
return size % 2 == 0 ? (vec[mid] + vec[mid -1]) /2 : vec[mid];
}
double grade(double midterm, double final, const vector<double>& hw){
if(hw.size() == 0)
throw domain_error("student has done no homework");
return grade(midterm, final, median(hw));//如果家庭作业非空,则调用grade3
}//grade2
double grade(double midterm, double final, double hw){
return midterm * 0.2 + final * 0.4 + hw * 0.4;
}
istream& read_hw(istream& in, vector<double>& hw){
if(in){
hw.clear();
double x;
while(in >> x){
hw.push_back(x);
}
in.clear();
}
return in;
}
int main(int argc, char** argv){
//类型依赖(向量的定义)
vector<Core> students;
// vector<Grad> students;
Core record;
// Grad 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(), compare);
for (vector<Core>::size_type i = 0; i != students.size(); ++i) {
cout << students[i].name()
<< string(maxlen + 1 - students[i].name().size(),' ');
try{
//grade函数(类型依赖)
double final_grade = students[i].grade();
streamsize pre = cout.precision();
cout << setprecision(3) << final_grade//Core函数
<< setprecision(pre) << endl;
}catch(domain_error e){
cout << e.what() << endl;
}
}
// for (vector<Grad>::size_type i = 0; i != students.size(); ++i) {
// cout << students[i].name()
// << string(maxlen + 1 - students[i].name().size(),' ');
// try{
// //grade函数(类型依赖)
// double final_grade = students[i].grade();
// streamsize pre = cout.precision();
// cout << setprecision(3) << final_grade//Grad::grade
// << setprecision(pre) << endl;
// }catch(domain_error e){
// cout << e.what() << endl;
// }
// }
return 0;
}
1.3.2 使用指针或引用
由于上一节的程序会导致对类型的依赖:
- 1 对向量的定义;
- 2 对局部的临时变量的定义;
- 3 read函数;
- 4 grade函数。
- 5 compare函数。
解决方法:
read和grade两个函数使用虚拟函数,重写compare(指向Core类型对象的指针)。对向量和变量的定义都定义为指针类型。
改写后的示例:
int main(){
vector<Core*> students;
Core* record;
while(record->read(cin)){//出错
}
}
出错是因为:record指向了一个空指针。
解决办法是:用户亲自管理好从文件中读出的数据所占的内存。
继续完善后:
bool compare_core_ptrs(const Core* cp1, const Core* cp2){
return compare(*cp1, *cp2);
}
int main(int argc, char** argv){
vector<Core*> students;
Core* record;
string::size_type maxlen = 0;
char ch;
while(cin >> ch){
if(ch == 'U'){
record = new Core;//为一个Core类型对象分配内存
}else{
record = new Grad;//为一个Grad类型对象分配内存
}
record->read(cin);//虚拟调用
maxlen = max(maxlen, record->name().size());//间接引用
students.push_back(record);
}
sort(students.begin(), students.end(), compare_core_ptrs);
for (vector<Core*>::size_type i = 0; i != students.size(); ++i) {
cout << students[i]->name()
<< string(maxlen + 1 - students[i]->name().size(),' ');
try{
//grade函数(类型依赖)
double final_grade = students[i]->grade();
streamsize pre = cout.precision();
cout << setprecision(3) << final_grade
<< setprecision(pre) << endl;
}catch(domain_error e){
cout << e.what() << endl;
}
delete students[i];//释放在读内存时生成的临时变量
}
return 0;
}
需要注意的是:
由于向量是指针类型向量,因此students[i]生成一个指针,一旦获得了指针,也就得到了一个必须本身被间接引用以得到其指向的对象的指针。
1.3.3.虚拟析构函数
由于全部指向对象的指针都保存为基类指针(Core*类型的指针),不是指向派生类的指针(Gradz *类型的指针)。删除这个指针时,我们是在删除一个指向基类(Core)的指针,而不是指向派生类(Grad)的指针(即使指向的对象类确实是Grad)。
解决办法是:把Core的析构函数显示定义为虚拟析构函数,工作是删除对对象的数据成员。
class Core{
public:
virtual ~Core(){}
}
完整头文件:
//
// Student_info.h
//
#ifndef ACM_Student_info
#define ACM_Student_info
#include <iostream>
#include <string>
#include <vector>
class Core{
public:
//虚拟析构函数
//Grad在内的全部派生类会继承这个析构函数
virtual ~Core(){}
//构造函数
//默认构造函数(构造初始化器)
Core():midterm(0), final(0){}
//使用一个istream类型变量构造一个Core对象
Core(std::istream& is) {read(is);}
//存取器函数(允许对一部分数据结构访问)
std::string name() const;
virtual std::istream& read(std::istream&);
//虚拟函数(指针或引用指向的对象的实际类型在运行时是可以变化的)
//只能在类内定义
//只有以引用或指针为参数调用虚拟函数时,它们的选择才会有意义(动态绑定)
//以对象(区别于指针或引用)调用虚拟函数,在编译时候对象的类型就确定了,运行时也不会改变(静态绑定)
//通过虚拟函数+动态绑定的特性以支持多态性(多态的调用)
//多态性:一种类型可以表示几种类型(由该类派生出来的任何一个子类)
//可以用一个Core类型的指针指向一个Core类型对象或它的派生类如(Grad类型对象)
virtual double grade() const;
protected://赋予派生类访问基类中保护成员的权利
std::istream& read_common(std::istream&);
double midterm, final;
std::vector<double> homework;
private://只有Core类成员以及它的友元函数可以访问
std::string n;
};
//继承:Grad类是从Core类中派生出来,
//Core类中的每个成员(除构造函数、赋值运算符和西沟函数除外)也是Grad的成员
class Grad:public Core{
public:
//构造函数
//两个构造函数均隐式地调用Core::Core()函数,以初始化对象中的基类部分
Grad():thesis(0){}
Grad(std::istream& is){ read(is); }
double grade() const;
std::istream& read(std::istream&);
private:
double thesis;
};
std::istream& read_hw(std::istream&, std::vector<double>&);
double grade(double, double, double);
double grade(double, double, const std::vector<double>&);
double median(std::vector<double> );
bool compare(const Core& , const Core& );
bool compare_core_ptrs(const Core*, const Core*);
bool compare_grades(const Core& , const Core&);
#endif
主函数:
#include <iostream>
#include <string>
#include <vector>
#include <stdexcept>
#include <algorithm>
#include <iomanip>
#include <string>
#include "Student_info.h"
using std::cin; using std::cout;
using std::endl;
using std::istream;
using std::domain_error;
using std::vector;
using std::min;
using std::string;
using std::max;
using std::streamsize;
using std::setprecision;
using std::sort;
using std::string;
std::string Core::name() const { return n;}
istream& Core::read_common(istream& in){
in >> n >> midterm >> final;
return in;
}
istream& Core::read(istream& in){
read_common(in);
read_hw(in, homework);
return in;
}
istream& Grad::read(istream& in){
read_common(in);
in >> thesis;
read_hw(in, homework);
return in;
}
//不能将重载函数命名为模版参数,不然编译器无法决定调用按一个版本
//指向Core类型的指针,也可以将指向Grad的指针传给它,编译器会转换为Core*
bool compare(const Core& c1, const Core& c2){
return c1.name() < c2.name();
}
bool compare_core_ptrs(const Core* cp1, const Core* cp2){
return compare(*cp1, *cp2);
}
bool compare_grades(const Core& c1, const Core& c2){
return c1.grade() < c2.grade();
}
double Core::grade() const{
return ::grade(midterm, final, homework);
}
double Grad::grade() const {
return min(Core::grade(), thesis);//显示表明
}
double median(vector<double> vec){
typedef vector<double>::size_type vec_sz;
vec_sz size = vec.size();
if(size == 0){
throw domain_error("median of an empty vector");
}
sort(vec.begin(), vec.end());
vec_sz mid = size/2;
return size % 2 == 0 ? (vec[mid] + vec[mid -1]) /2 : vec[mid];
}
double grade(double midterm, double final, const vector<double>& hw){
if(hw.size() == 0)
throw domain_error("student has done no homework");
return grade(midterm, final, median(hw));//如果家庭作业非空,则调用grade3
}//grade2
double grade(double midterm, double final, double hw){
return midterm * 0.2 + final * 0.4 + hw * 0.4;
}
istream& read_hw(istream& in, vector<double>& hw){
if(in){
hw.clear();
double x;
while(in >> x){
hw.push_back(x);
}
in.clear();
}
return in;
}
int main(int argc, char** argv){
//改版一(需用户自己管理好从文件中读出数据所占的内存,检测程序正在读的记录类型)
vector<Core*> students;
Core* record;
string::size_type maxlen = 0;
//改版一(使用指针或引用)
char ch;
while(cin >> ch){
if(ch == 'U'){
record = new Core;//为一个Core类型对象分配内存
}else{
record = new Grad;//为一个Grad类型对象分配内存
}
record->read(cin);//虚拟调用
maxlen = max(maxlen, record->name().size());//间接引用
students.push_back(record);
}
//改版一(使用指针或引用)
sort(students.begin(), students.end(), compare_core_ptrs);
//改版一(使用指针或引用)
for (vector<Core*>::size_type i = 0; i != students.size(); ++i) {
cout << students[i]->name()
<< string(maxlen + 1 - students[i]->name().size(),' ');
try{
//grade函数(类型依赖)
double final_grade = students[i]->grade();
streamsize pre = cout.precision();
cout << setprecision(3) << final_grade
<< setprecision(pre) << endl;
}catch(domain_error e){
cout << e.what() << endl;
}
delete students[i];//释放在读内存时生成的临时变量
}
return 0;
}
2 句柄类(handle class)
在前面的程序中,为了管理指针,程序引入了许多复杂性,同时也引入了一些可能导致错误的缺陷。
用户必须记得在读记录是为这些记录分配内存,还需要在再次不用时释放占用的的缺陷。还需要经常对一个指针间接引用以得到指针指向的对象。
为了解决这样的问题,我们可以利用句柄类来达到目的。用这个新类去封装这个指向Core类型对象的指针,这样就将隐患在用户面前隐藏起来了。
示例:
由于用户可能会在程序中使用到Student_info类的对象,而不是用Core或者Grad类行对象,因此Student_info必须提供和Core类相同的接口。
由于cp可能会是0(当用户调用默认构造函数生成一个Student_info类型对象时),此时无法将这些函数调用传递给底层的对象去处理。因此显示一个运行时错误,表示程序出错。
把比较函数定义为静态数据成员是因为:它不能访问类型对象中的非静态数据成员,不会重载Core的compare函数。
class Student_info{
public:
//构造函数
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;}
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;
}
2.1 读取句柄类
任务:
- 1首先释放该句柄指向对象(如果句柄不为0)占用的空间;
- 2 判断读入的对象所具有的类型;
- 3 为正确类型的对象分配合适的大小的内存空间。
示例:
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;
}
2.2 复制句柄类
由于复制时,不知道正在复制的对象为何种类型。
解决办法就是在Core类型中定义一个虚拟函数来生成一个新的对象哪个,用来存储原来哪个对象的一个副本。同于赋予句柄类访问Core的权限。
示例:
class Core{
friend class Stundet_info;
protected:
virtual Core* clone() const {return new Core(*this);}
}
通过虚拟函数,可以通过Core::clone来间接调用Grad::clone。
class Grad{
protected:
virtual Grad* clone(){ return new Grad(*this);}
}
复制构造函数和赋值运算符函数:
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;
}
2.3 完整示例
头文件:
//
// Student_info.h
//
#ifndef ACM_Student_info
#define ACM_Student_info
#include <iostream>
#include <string>
#include <vector>
//#include "Handle.h"
class Core{
friend class Student_info;
public:
//虚拟析构函数
//Grad在内的全部派生类会继承这个析构函数
virtual ~Core(){}
//构造函数
//默认构造函数(构造初始化器)
Core():midterm(0), final(0){}
//使用一个istream类型变量构造一个Core对象
Core(std::istream& is) {read(is);}
//存取器函数(允许对一部分数据结构访问)
std::string name() const;
virtual std::istream& read(std::istream&);
//虚拟函数(指针或引用指向的对象的实际类型在运行时是可以变化的)
//只能在类内定义
//只有以引用或指针为参数调用虚拟函数时,它们的选择才会有意义(动态绑定)
//以对象(区别于指针或引用)调用虚拟函数,在编译时候对象的类型就确定了,运行时也不会改变(静态绑定)
//通过虚拟函数+动态绑定的特性以支持多态性(多态的调用)
//多态性:一种类型可以表示几种类型(由该类派生出来的任何一个子类)
//可以用一个Core类型的指针指向一个Core类型对象或它的派生类如(Grad类型对象)
virtual double grade() const;
protected://赋予派生类访问基类中保护成员的权利
virtual Core* clone() const{return new Core(*this);}
std::istream& read_common(std::istream&);
double midterm, final;
std::vector<double> homework;
private://只有Core类成员以及它的友元函数可以访问
std::string n;
};
//继承:Grad类是从Core类中派生出来,
//Core类中的每个成员(除构造函数、赋值运算符和西沟函数除外)也是Grad的成员
class Grad:public Core{
public:
//构造函数
//两个构造函数均隐式地调用Core::Core()函数,以初始化对象中的基类部分
Grad():thesis(0){}
Grad(std::istream& is){ read(is); }
double grade() const;
std::istream& read(std::istream&);
protected:
virtual Grad* clone(){ return new Grad(*this);}
private:
double thesis;
};
std::istream& read_hw(std::istream&, std::vector<double>&);
double grade(double, double, double);
double grade(double, double, const std::vector<double>&);
double median(std::vector<double> );
bool compare(const Core& , const Core& );
bool compare_core_ptrs(const Core*, const Core*);
bool compare_grades(const Core& , const Core&);
#endif
//
// Created by MacBook Pro on 2020-03-18.
//
#ifndef ACM_HANDLE_H
#define ACM_HANDLE_H
#include <iostream>
#include "Student_info.h"
class Student_info{
public:
//构造函数
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;}
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;
};
std::istream& Student_info::read(std::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;
}
#endif //ACM_HANDLE_H
源文件:
#include <iostream>
#include <string>
#include <vector>
#include <stdexcept>
#include <algorithm>
#include <iomanip>
#include <string>
#include "Student_info.h"
#include "Handle.h"
using std::cin; using std::cout;
using std::endl;
using std::istream;
using std::domain_error;
using std::vector;
using std::min;
using std::string;
using std::max;
using std::streamsize;
using std::setprecision;
using std::sort;
using std::string;
std::string Core::name() const { return n;}
istream& Core::read_common(istream& in){
in >> n >> midterm >> final;
return in;
}
istream& Core::read(istream& in){
read_common(in);
read_hw(in, homework);
return in;
}
istream& Grad::read(istream& in){
read_common(in);
in >> thesis;
read_hw(in, homework);
return in;
}
//不能将重载函数命名为模版参数,不然编译器无法决定调用按一个版本
//指向Core类型的指针,也可以将指向Grad的指针传给它,编译器会转换为Core*
bool compare(const Core& c1, const Core& c2){
return c1.name() < c2.name();
}
bool compare_core_ptrs(const Core* cp1, const Core* cp2){
return compare(*cp1, *cp2);
}
bool compare_grades(const Core& c1, const Core& c2){
return c1.grade() < c2.grade();
}
double Core::grade() const{
return ::grade(midterm, final, homework);
}
double Grad::grade() const {
return min(Core::grade(), thesis);//显示表明
}
double median(vector<double> vec){
typedef vector<double>::size_type vec_sz;
vec_sz size = vec.size();
if(size == 0){
throw domain_error("median of an empty vector");
}
sort(vec.begin(), vec.end());
vec_sz mid = size/2;
return size % 2 == 0 ? (vec[mid] + vec[mid -1]) /2 : vec[mid];
}
double grade(double midterm, double final, const vector<double>& hw){
if(hw.size() == 0)
throw domain_error("student has done no homework");
return grade(midterm, final, median(hw));//如果家庭作业非空,则调用grade3
}//grade2
double grade(double midterm, double final, double hw){
return midterm * 0.2 + final * 0.4 + hw * 0.4;
}
istream& read_hw(istream& in, vector<double>& hw){
if(in){
hw.clear();
double x;
while(in >> x){
hw.push_back(x);
}
in.clear();
}
return in;
}
int main(int argc, char** argv){
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 (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;
}
2.4 句柄类改进
2.4.1 把指针类和接口类分离设计
这一章对上一章的句柄类进行改进,Student_info类提供接口,通过另一个类来控制“句柄”,这个类用于管理指向实现对象的指针。
把这个类称为Handle,具有的特点:
- Handle类型是一个在指向某对象的值;
- 可以对Handle类型对象进行复制;
- 可以通过检测一个Handle类型对象来判断其是否指向另一个对象。
- 如果Handle类型对象指向继承关系树中某个类的对象,可以使得Handle类对象触发多态行为。也就是说,如果通过Handle类调用一个虚拟函数,在程序运行时能够动态选择调用哪一哪个函数,正如通过一个真实的指针调用这个虚拟函数时所做的那样。
要求:
- Handle对象负责对指向的对象进行内存管理。
- 对于一个对象来说,只能为其匹配一个Handle类型对象,此后就不再直接通过指针访问该对象。全部的访问都要通过这个Handle类型对象来进行。这一限制避免了使用C++自带的指针时所固有有的问题。
- 复制Handle类型对象时,为对象新生成一个副本,这个每个Handle类型对象都指向各自的副本。
- 删除Handle类型对象时,会删除相应的对象,同时也是删除该对象的唯一途径。
- 允许用户生成一个没有指向任何对象的Handle类型对象,但是用户在试图通过这个对象去访问它所指向的对象时,程序会抛出一个异常。用户可以通过检测Handle是否有效来避免这个问题。
设计的类:
template<class T> class Handle{
public:
//构造函数
Handle():p(0){}
//复制构造
Handle(const Handle& s):p(0){if(s.p) p = s.p->clone();}
//赋值运算符
Handle& operator=(const Handle&);
//使指针与实际对象关联
//例如Handle<Core> student(new Grad);
//封装了一个Cote*指针,对该指针初始化,指向生成的一个Grad类型对象
Handle(T* t):p(t){}
//析构函数
~Handle(){delete p;}
//运算符函数
//允许条件语句中检测一个Handle类型对象的值
operator bool() const { return p;}
//访问与Handle相关联的对象
//得到该指针指向的对象
//*student 等同于*(student.p) student类型对象
T& operator*() const;
//返回一个可以看作是指针的值
//student->y 等同于 (student.operator->)->y 等同于 student.p->y
//自我保护机制通常不允许我们直接访问student.p
//因此,->运算符会把对Hanle类型对象里的函数调用交给Handle类型对象的指针成员
T* operator->() const;
private:
T* p;
};
成员函数实现:
赋值运算符函数:
template <class T>
Handle<T>& Handle<T>::operator=(const Handle &rhs) {
if(&rhs != this){
delete(p);
p = rhs.p ? rhs.p->clone():0;
}
return *this;
}
运算符函数:
template <class T>
T& Handle<T>::operator*() const {
if(p) return *p;
throw std::runtime_error("unbound Handle ");
}
template <class T>
T* Handle<T>::operator->() const {
if(p) return p;
throw std::runtime_error("unbound Handle");
}
把Handle类声明为Core类的友元:
//前置声明
template <class T> class Handle;
class Core{
friend class Handle<Core>;//实例化声明
//....
}
main函数重写:
//重写比较函数bool compare_Core_handles(const Handle<Core> cp1, const Handle<Core> cp2){
return compare(*cp1, *cp2);
}
int main(int argc, char** argv){
vector<Handle<Core>> students;
Handle<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_handles);
for (vector<Handle<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;
}
}
return 0;
}
现在可以重写编写Students_info类使得这个类成为一个纯接口类,让它从管理指针的工作中解脱出来。
重写后的代码为:
//重写Student_info的代码
class Student_info{
public:
Student_info(){}
Student_info(std::istream& is){ read(is);}
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:
Handle<Core> cp;
};
重写函数:
std::istream& Student_info::read(std::istream& is){
与之前的不同,删除了这句话,因为对cp进行赋值会释放对象暂用的内存
//Hanle类的赋值运算符函数会在赋值给cp的时候,自动删除Handle此前所指的对象
// delete cp;//如果有的话,删除所指对象,删除一个零指针也是无害的
char ch;
is >> ch;
if(ch == 'U') cp = new Core(is);
else cp = new Grad(is);
return is;
}
2.4.2 设计引用计数句柄
准备知识:
- 一个对象为另一个对象的副本,但是它们的值公用一块内存,此时,不需要它们以值的方式工作。
- 一旦一个对象生成以后,其他类就没办法改变对象的状态,没有理由对基层的对象进行复制,复制这些对象只会浪费CPU时间与内存空间。
- 因此,可以允许几个Handle类型对象指向同一个底层对象,当最后一个指向Handle类型对象被删除时,释放对象的内存。
为了亲自决定是否对对象进行复制,
- 使用引用计数(referenece cout),用于记录有多少个对象指向某个底层对象。
- 每次生成一个新的指向目标对象的句柄时,让引用计数加1。
- 在一个句柄对象被删除时,可以安全删除目标对象并释放其内存。
- 优点:去掉大量不必要的内存管理和数据复制行为。
设计难点:计数器存放在什么地方?
因为无法获得任何一种类的源代码,因此不能通过向类中加入计数器来实现计数。
我们在新设计的类中加入另外一个指针以跟踪计数。
设计后的代码:
template <class T> class Ref_handle{
public:
//构造函数
Ref_handle():p(0),refptr(new std::size_t(1)){}
//使指针与实际对象关联
Ref_handle(T* t):p(t), refptr(new std::size_t(1)){}
//进行复制时加1
Ref_handle(const Ref_handle& h):p(h.p), refptr(h.refptr){
++*refptr;
}
//赋值运算符函数
Ref_handle& operator=(const Ref_handle&);
//析构函数
~Ref_handle();
operator bool()const { return p;}
T& operator*() const{
if(p) return *p;
throw std::runtime_error("unbound Ref_handle");
}
T* operator->() const{
if(p) return p;
throw std::runtime_error("unbound Ref_handle");
};
private:
T* p;
std::size_t* refptr;
};
template <class T>
Ref_handle<T>& Ref_handle<T>::operator=(const Ref_handle &rhs) {
//取保引用计数不会被无意义归零
++*rhs.refptr;
//释放做操作数对象,如有必要释放指针
if(--*refptr == 0){//防止自我赋值
delete(refptr);
delete(p);
}
//赋值右操作数对象的值
refptr = rhs.refptr;
p = rhs.p;
return *this;
}
template <class T>
Ref_handle<T>::~Ref_handle() {
if(--*refptr == 0){
delete(refptr);
delete(p);
}
}
这个类可以在一个对象的不同副本之间共享一块内存数据的类来说,可以正常工作。但是对于Student_info这样的类,将不能正常工作,无论是否需要,它都不会对数据进行复制。
因此写继续完善 ,再写一个类定义程序决定何时复制目标对象。它保留了Ref_handle类的功能,又允许用类的作者在使用Hadle类型对象时,像值一样使用。这样的句柄保留了C++自带指针的有用的特性,同时又可以避免许多安全隐患。
2.4.3 可控句柄
实现代码:
多增加了一个函数make_unique(),这个函数用于在改变对象值时,复制该对象,同时把原对象的引用计数减一(因为复制了对象,便不再共享原对象)。
template <class T>
class Ptr{
public:
//在需要时有条件复制对象
void make_unique(){
if(*refptr != 1){//没有被引用直接该,引用了则复制
--*refptr;
refptr = new size_t(1);
p = p?p->clone():0;
}
}
Ptr():p(0),refptr(new size_t(1)){}
Ptr(T* t):p(t),refptr(new size_t(1)){}
Ptr(const Ptr& h):p(h.p),refptr(h.refptr){++*refptr;}
Ptr& operator=(const Ptr&);
~Ptr();
operator bool() const {return p;}
T& operator*() const{
if(p) return *p;
throw std::runtime_error("unbound Ptr");
}
T* operator->() const{
if(p) return p;
throw std::runtime_error("unbound Ptr");
}
private:
T* p;
std::size_t * refptr;
};
template <class T>
Ptr<T>& Ptr<T>::operator=(const Ptr &rhs) {
++*rhs.refptr;
if(--*refptr == this){
delete(refptr);
delete(p);
}
refptr = rhs.refptr;
p = rhs.p;
return *this;
}
template <class T>
Ptr<T>::~Ptr() {
if(--*refptr==0){
delete(refptr);
delete(p);
}
}
在Core类中加入友元:
//前置声明
template <class T> class Ptr;
class Core{
friend class Ptr<Core>;
//....
}
对前面的Student_info类中使用最新版本的Ptr类,不需要对Student_info类的程序做任何改变。
由于Student_info类的任何一个操作都要通过覆盖来改变对象的值。唯一会变对象的值的操作是read函数,但是这个函数总会讲一个新生成的值赋给对象的Ptr的成员数据。在进行这种操作时,Ptr类删除或保留旧值,取决于是否还有其他对象指向旧值。
注意的是任何时候将要读取的对象都会有一个新的Ptr类型对象,这也是对该对象唯一的使用者。
Student_info s1;//新生成Ptr对象
s1(cin);
Student_info s2 = s1;//将s1复制到s2中,新生成Ptr对象
s2(cin);//改变的是s2而不是s1
2.4.3.1 可控句柄示例
Str类的原始版本:
#ifndef ACM_STR_H
#define ACM_STR_H
#include "Vec.h"
#include <iostream>
class Str{
friend std::istream& operator>>(std::istream&, Str&);
public:
typedef Vec<char>::size_type size_type;
//实现+=运算符
Str& operator += (const Str& s){
std::copy (s.data.begin(), s.data.end(), std::back_inserter(data));
return *this;
}
//默认构造
Str(){}
//生成一个Str对象,包含c的n个副本
Str(size_type n, char c):data(n,c){}//使用Vec类中的构造函数构造data数据
//生成一个Str对象并使用一个空字符结尾的字符数组来初始化
Str(const char* cp){
std::copy(cp, cp + std::strlen(cp), std::back_inserter(data));
}
//生成一个Str对象并使用迭代器b和e之间的内容对他进行初始化
template<class In>Str (In b, In e){
std::copy(b, e, std::back_inserter(data));
}
//大小
size_type size() const { return data.size();}
//索引
char& operator[](size_type i) { return data[i];}
const char &operator[](size_type i) const { return data[i];}
private:
Vec<char> data;
};
//输入运算符
std::istream& operator>>(std::istream&, Str&);
//输出运算符
std::ostream& operator<<(std::ostream&, const Str&);
//加号运算符
Str operator+(const Str&, const Str&);
//输出运算符定义
std::ostream& operator<<(std::ostream& os, const Str& s){
for (Str::size_type i = 0; i != s.size(); ++i) {
os << s[i];
}
return os;
}
//输入运算符的定义
std::istream& operator>>(std::istream& is, Str& s){
//抹去存在的值(s)
s.data.clear();
//按序读字符并忽略前面的空格字符
char c;
//值进行循环条件,不进行其他工作
while(is.get(c) && isspace(c));
//读入非空白字符
if(is){
do{
s.data.push_back(c);
}while(is.get(c) && !isspace(c));
//如果遇到一空格字符,将它放在输入流的后面
if(is){
is.unget();
}
}
return is;
}
//加号运算符的定义
Str operator+ (const Str& s, const Str& t){
Str r = s;
r += t;
return r;
}
#endif //ACM_STR_H
使用Ptr类进行改进:
不是在每个Str类型对象中直接存储一个向量类型对象,而是使用了一个指向向量的指针。这样之后允许许多的STr类型对象公用同一底层字符数据。
class Str{
friend std::istream& operator>>(std::istream&, Str&);
public:
typedef Vec<char>::size_type size_type;
typedef Vec<char>::iterator iterator;
typedef Vec<char>::const_iterator const_iterator;
实现+=运算符(重写)
Str& operator += (const Str& s){
data.make_unique();//新增
std::copy (s.data->begin(), s.data->end(), std::back_inserter(*data));
return *this;
}
默认构造(//全部重写)
Str():data(new Vec<char>){}
//生成一个Str对象,包含c的n个副本
Str(size_type n, char c):data(new Vec<char>(n,c)){}//使用Vec类中的构造函数构造data数据
//生成一个Str对象并使用一个空字符结尾的字符数组来初始化
Str(const char* cp):data(new Vec<char>){
std::copy(cp, cp + std::strlen(cp), std::back_inserter(*data));
}
//生成一个Str对象并使用迭代器b和e之间的内容对他进行初始化
template<class In>Str (In b, In e):data(new Vec<char>){
std::copy(b, e, std::back_inserter(*data));
}
//大小
size_type size() const { return data->size();}
索引(重写)
char& operator[](size_type i) {
data.make_unique();
return (*data)[i];
}
const char &operator[](size_type i) const { return (*data)[i];}
iterator begin() { return data->begin(); };
const_iterator begin() const { return data->begin(); };
iterator end() { return data->end(); };
const_iterator end() const { return data->end(); };
private:
重写
Ptr<Vec<char> > data;//存储指向向量的指针
};
//输入运算符
std::istream& operator>>(std::istream&, Str&);
//输出运算符
std::ostream& operator<<(std::ostream&, const Str&);
//加号运算符
Str operator+(const Str&, const Str&);
//输出运算符定义
std::ostream& operator<<(std::ostream& os, const Str& s){
for (Str::size_type i = 0; i != s.size(); ++i) {
os << s[i];
}
return os;
}
//输入运算符的定义
std::istream& operator>>(std::istream& is, Str& s){
//抹去存在的值(s)
s.data->clear();
//按序读字符并忽略前面的空格字符
char c;
//值进行循环条件,不进行其他工作
while(is.get(c) && isspace(c));
//读入非空白字符
if(is){
do{
s.data->push_back(c);
}while(is.get(c) && !isspace(c));
//如果遇到一空格字符,将它放在输入流的后面
if(is){
is.unget();
}
}
return is;
}
//加号运算符的定义
Str operator+ (const Str& s, const Str& t){
Str r = s;
r += t;
return r;
}
上面的代码存在一个严重的问题,由于make_unique函数里调用clone函数,而vec类中没有clone函数。但是我们不能直接向Vec类中添加一个clone成员函数。也就是试图调用一个实际不存在的函数,但是有办法令这个函数存在。
解决办法是定义一个既可以直接调用又可以创建的中间全局函数。
定义全局函数
template <class T>T* clone(const T* tp){
return tp->clone();
}
//模版特化
template <>
Vec<char>* clone(const Vec<char>* vp){
return new Vec<char>(*vp);
}
修改Ptr类中的函数:
void make_unique(){
if(*refptr != 1){
--*refptr;
refptr = new size_t(1);
p = p?clone(p):0;
}
}
总的来说:
- 如果使用了Ptr<T>但是没有调用Ptr<T>::make_unique函数,那么是否定义Ptr<T>::make_unique都没关系;
- 如果调用了Ptr<T>::make_unique函数,而且定义了T::clone函数,那么Ptr<T>::make_unique,将调用T::clone;
- 如果调用了Ptr<T>::make_unique函数,而且不想调用T::clone函数(或者它根本不存在),可以通过一个clone<T>模版来完成工作。
2.4.4 把计数句柄的管理内存和计数分离
计数:
//
// Created by MacBook Pro on 2020-03-20.
//
#ifndef ACM_REFPTR_H
#define ACM_REFPTR_H
#include <cstddef>
class Refptr_counter{
public:
//构造
Refptr_counter():counter(new std::size_t(1)){}
Refptr_counter(std::size_t s):counter(new std::size_t(s)){}
//复制构造
Refptr_counter(const Refptr_counter& h):counter(h.counter){}
//赋值运算符
Refptr_counter& operator=(const Refptr_counter& rhs){
if(*counter <= 0){
delete counter;
}
counter = rhs.counter;
return *this;
};
//析构函数
~Refptr_counter(){
if(*counter <= 0) delete counter;
}
//运算符函数
//前缀
Refptr_counter& operator++(){
(*counter)++;
return *this;
}
Refptr_counter&operator--(){
(*counter)--;
return *this;
}
//后缀(返回临时变量)
Refptr_counter operator++(int){
Refptr_counter res(*(this->counter));//复制构造函数
(*counter)++;
return res;
}
Refptr_counter operator--(int){
Refptr_counter res = *this;//赋值构造函数
(*counter)--;
return res;
}
//返回计数值
std::size_t operator*() const{
return *counter;
}
private:
std::size_t* counter;
};
#endif //ACM_REFPTR_H
管理内存
#ifndef ACM_PTR_H
#define ACM_PTR_H
#include "Refptr.h"
template <class T>T* clone(const T* tp){
return tp->clone();
}
//分离计数与管理内存
template <class T>
class Ptr_r{
public:
//在需要时有条件复制对象
void make_unique(){
if(*refptr_c != 1){
--refptr_c;
refptr_c = Refptr_counter();
p = p?clone(p):0;
}
}
//构造函数
Ptr_r():p(0){}//refptr有自己的构造函数
//与实际对象绑定
Ptr_r(T* t):p(t){}
//复制构造函数
Ptr_r(const Ptr_r& h):p(h.p),refptr_c(h.refptr_c){++refptr_c;}
//赋值运算符
Ptr_r& operator=(const Ptr_r&);
//析构函数
~Ptr_r();
//条件判断
operator bool() const {return p;}
//隐藏指针
T& operator*() const{
if(p) return *p;
throw std::runtime_error("unbound Ptr_r");
}
//返回指针
T* operator->() const{
if(p) return p;
throw std::runtime_error("unbound Ptr_r");
}
private:
T* p;
Refptr_counter refptr_c;
};
template <class T>
Ptr_r<T>& Ptr_r<T>::operator=(const Ptr_r &rhs) {
++(*rhs.refptr_c);
if(*(--refptr_c) == 0){
// delete(refptr);//refptr析构函数会执行
delete(p);
}
refptr_c = rhs.refptr_c;
p = rhs.p;
return *this;
}
template <class T>
Ptr_r<T>::~Ptr_r() {
if(*(--refptr_c) ==0){
// delete(refptr);//refptr析构函数会执行
delete(p);
}
}
class Student_info{
public:
Student_info(){}
Student_info(std::istream& is){ read(is);}
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();
}
void regrade(double final, double thesis){
//改变时,先得到副本
cp.make_unique();
if(cp) cp->regrade(final, thesis);
else throw std::runtime_error("regrade of unknown students");
}
private:
// Handle<Core> cp;
Ptr<Core> cp;
};
std::istream& Student_info::read(std::istream& is){
与之前的不同,删除了这句话,因为对cp进行赋值会释放对象暂用的内存
//Hanle类的赋值运算符函数会在赋值给cp的时候,自动删除Handle此前所指的对象
// delete cp;//如果有的话,删除所指对象,删除一个零指针也是无害的
char ch;
is >> ch;
if(ch == 'U') cp = new Core(is);
else cp = new Grad(is);
return is;
}
#endif //ACM_PTR_H
测试源文件:
#include <iostream>
#include <string>
#include <vector>
#include <stdexcept>
#include <algorithm>
#include <iomanip>
#include <string>
#include "Student_info.h"
#include "Ptr.h"
#include <fstream>
#include <sstream>
using std::stringstream;
using std::cin; using std::cout;
using std::endl;
using std::istream;
using std::domain_error;
using std::vector;
using std::min;
using std::string;
using std::max;
using std::streamsize;
using std::setprecision;
using std::sort;
using std::string;
std::string Core::name() const {
#if ANNOTATE == 1
std::cerr<<" Core::name() "<<std::endl;
#endif
return n;}
istream& Core::read_common(istream& in){
in >> n >> midterm >> final;
return in;
}
istream& Core::read(istream& in){
read_common(in);
read_hw(in, homework);
return in;
}
istream& Grad::read(istream& in){
read_common(in);
in >> thesis;
read_hw(in, homework);
return in;
}
//不能将重载函数命名为模版参数,不然编译器无法决定调用按一个版本
//指向Core类型的指针,也可以将指向Grad的指针传给它,编译器会转换为Core*
bool compare(const Core& c1, const Core& c2){
return c1.name() < c2.name();
}
//Handle类调用
//bool compare_Core_handles(const Handle<Core> cp1, const Handle<Core> cp2){
// return compare(*cp1, *cp2);
//}
//bool compare_Core_Ptr(const Ptr<Core> cp1, const Ptr<Core> cp2){
// return compare(*cp1, *cp2);
//}
bool compare_grades(const Core& c1, const Core& c2){
return c1.grade() < c2.grade();
}
double Core::grade() const{
#if ANNOTATE == 1
std::cerr<<" Core::grade()"<<std::endl;
#endif
return ::grade(midterm, final, homework);
}
double Grad::grade() const {
#if ANNOTATE == 1
std::cerr<<" Grad::grade()"<<std::endl;
#endif
return min(Core::grade(), thesis);//显示表明
}
double median(vector<double> vec){
typedef vector<double>::size_type vec_sz;
vec_sz size = vec.size();
if(size == 0){
throw domain_error("median of an empty vector");
}
sort(vec.begin(), vec.end());
vec_sz mid = size/2;
return size % 2 == 0 ? (vec[mid] + vec[mid -1]) /2 : vec[mid];
}
double grade(double midterm, double final, const vector<double>& hw){
if(hw.size() == 0)
throw domain_error("student has done no homework");
return grade(midterm, final, median(hw));//如果家庭作业非空,则调用grade3
}//grade2
double grade(double midterm, double final, double hw){
return midterm * 0.2 + final * 0.4 + hw * 0.4;
}
istream& read_hw(istream& in, vector<double>& hw){
if(in){
hw.clear();
double x;
while(in >> x){
hw.push_back(x);
}
in.clear();
}
return in;
}
string Core::letter_grade() const {
static const double numbers[]={
97, 94, 90, 87, 84, 80, 77, 74, 70, 60, 0
};
static const char* const letters[]={
"A+","A","A-","B+","B","B-","C+","C","C-","D","F"
};
//计算成绩的个数
static const size_t ngrades = sizeof(numbers)/sizeof(*numbers);
double result = grade();
for (size_t i = 0; i < ngrades; ++i) {
if(result >= numbers[i]){
return letters[i];
}
}
return "? \? \?";
}
double Credit::grade() const {
if(homework.empty()){
return (midterm + final)/2;
}else{
return Core::grade();
}
}
istream& Audit::read(std::istream &is) {
Core* record;
char ch;
maxlen = 0;
while(is >> ch) {
if (ch == 'U') {
record = new Core;//为一个Core类型对象分配内存
} else{
record = new Grad;//为一个Grad类型对象分配内存
}
record->read(is);//虚拟调用
maxlen = max(maxlen, record->name().size());
students_garde[record->name()] = record->grade();
}
return is;
}
double Audit::checked(const std::string & name) const {
const_iterator iter = students_garde.find(name);
if(iter != students_garde.end()){
return iter->second;
}else{
return -1;
}
}
int main(int argc, char** argv){
vector<Student_info> students;
Student_info record;
char ch;
string::size_type maxlen = 0;
std::ifstream infile("/Users/macbookpro/CLionProjects/ACM/students10.txt");
while(record.read(infile)){
maxlen = max(maxlen, record.name().size());
students.push_back(record);
}
infile.close();
infile.clear();
sort(students.begin(), students.end(), Student_info::compare);
for (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;
}