参数:第一个为输出流对象;第二个为要输出的对象(为了防止产生临时对象、提高程序的效率,将参数设置为引用类型,但引用类型又能改变实参的值,所以设置为const)。
2.声明派生类circle
在1的基础上,再声明派生类circle的部分。
#include
using namespace std;
class Point
{
protected:
float x, y;
public:
Point(float = 0, float = 0);
void setPoint(float, float);
float getX() const
{
return x;
}
float getY() const
{
return y;
}
friend ostream &operator<<(ostream &, const Point &);
};
// Point的构造函数
Point::Point(float a, float b)
{
x = a;
y = b;
}
// 设置x和y的坐标值
void Point::setPoint(float a, float b)
{
x = a;
y = b;
}
// 输出点的坐标
ostream &operator<<(ostream &output, const Point &p)
{
output << "[" << p.x << "," << p.y << "]" << endl;
return output;
}
class Circle: public Point
{
protected:
float radius;
public:
Circle(float x = 0, float y = 0, float r = 0);
void setRadius(float);
float getRadius() const;
float area () const;
friend ostream &operator<<(ostream &, const Circle &);
};
Circle::Circle(float a, float b, float r): Point(a, b), radius(r) {}
void Circle::setRadius(float r)
{
radius = r;
}
float Circle::getRadius() const
{
return radius;
}
float Circle::area() const
{
return 3.14159 * radius * radius;
}
ostream &operator<<(ostream &output, const Circle &c)
{
output << "Center=[" << c.x << "," << c.y << "],Radius = " << c.radius << ", area = " << c.area() << endl;
return output;
}
int main()
{
Circle c(3.5, 6.4, 5.2);
cout << "original circle:\nx=" << c.getX() << ",y = " << c.getY() << ", r = " << c.getRadius() << ",area = " <<
c.area() << endl;
c.setRadius(7.5);
c.setPoint(5, 5);
cout << "new circle:\n" << c;
Point &pRef = c;
cout << "pRef:" << pRef;
return 0;
}
3.声明circle的派生类cylinder
以circle为基础,从circle类派生出cylinder类。
#include
using namespace std;
class Point
{
protected:
float x, y;
public:
Point(float = 0, float = 0);
void setPoint(float, float);
float getX() const
{
return x;
}
float getY() const
{
return y;
}
friend ostream &operator<<(ostream &, const Point &);
};
// Point的构造函数
Point::Point(float a, float b)
{
x = a;
y = b;
}
// 设置x和y的坐标值
void Point::setPoint(float a, float b)
{
x = a;
y = b;
}
// 输出点的坐标
ostream &operator<<(ostream &output, const Point &p)
{
output << "[" << p.x << "," << p.y << "]" << endl;
return output;
}
class Circle: public Point
{
protected:
float radius;
public:
Circle(float x = 0, float y = 0, float r = 0);
void setRadius(float);
float getRadius() const;
float area () const;
friend ostream &operator<<(ostream &, const Circle &);
};
Circle::Circle(float a, float b, float r): Point(a, b), radius(r) {}
void Circle::setRadius(float r)
{
radius = r;
}
float Circle::getRadius() const
{
return radius;
}
float Circle::area() const
{
return 3.14159 * radius * radius;
}
ostream &operator<<(ostream &output, const Circle &c)
{
output << "Center=[" << c.x << "," << c.y << "],Radius = " << c.radius << ", area = " << c.area() << endl;
return output;
}
class Cylinder: public Circle
{
public:
Cylinder (float x = 0, float y = 0, float r = 0, float h = 0);
void setHeight(float);
float getHeight() const;
float area() const;
float volume() const;
friend ostream &operator<<(ostream &, const Cylinder &);
protected:
float height;
};
Cylinder::Cylinder(float a, float b, float r, float h)
: Circle(a, b, r), height(h) {}
void Cylinder::setHeight(float h)
{
height = h;
}
float Cylinder::getHeight() const
{
return height;
}
float Cylinder::area() const
return 2 * Circle::area() + 2 * 3.14159 * radius * height;
}
float Cylinder::volume() const
{
return Circle::area() * height;
}
ostream &operator<<(ostream &output, const Cylinder &cy)
{
output << "Center=[" << cy.x << "," << cy.y << "], r=" << cy.radius << ", h=" << cy.height << " \narea=" << cy.area() <<
", volume=" << cy.volume() << endl;
return output;
}
int main()
{
Cylinder cy1(3.5, 6.4, 5.2, 10);
cout << "\n original cylinder:\n x=" << cy1.getX() << ", y=" << cy1.getY() << ", r=" << cy1.getRadius() << ", h=" <<
cy1.getHeight() << "\narea=" << cy1.area()
<< ", volume=" << cy1.volume() << endl;
cy1.setHeight(15);
cy1.setRadius(7.5);
cy1.setPoint(5, 5);
cout << "\nnew cylinder:\n" << cy1;
Point &pRef = cy1;
cout << "\npRef as a point:" << pRef;
Circle &cRef = cy1;
cout << "\ncRef as a Circle:" << cRef;
return 0;
}
可以在一个工程里设计多个头文件“point.h”、“cylinder.h”、“circle.h”分别定义各个类。
再设计“point.cppp”、“circle.cpp”、“cylinder.h”多个源程序文件分别定义各个类的成员函数。
设计一个主函数,通过对象访问类的成员函数。
三、虚函数
1.虚函数的作用
在一个类中不能定义两个名字相同,参数个数和类型都相同的函数。在类家族中,不同层次的类可以出现名字相同,参数个数和类型都相同而功能不同的函数。如在【例6.1】中,circle类中定义了area函数,在circle的派生类cylinder类中也定义了一个area函数,这两个函数名字相同,参数个数也相同,但是功能不同,前者计算圆的面积,后者计算圆柱体的表面积。编译程序按照同名覆盖的原则决定调用哪个函数。
在【例6.1】中用cy1.area()调用的是派生类cylinder中的成员函数area,如果想调用cy1中直接基类circle的area函数,应当表示为cy1.circle::area()。虽然可以区分两个同名函数,但写起来非常不方便。
能否用同一个调用形式,即能调用派生类的函数也能调用基类的同名函数?C++中的虚函数就是解决这个问题的。虚函数的工作原理是在派生类中定义与基类函数同名的函数,通过基类指针或引用来访问基类或派生类中的同名函数。
派生类与基类的转换
赋值兼容性规则:
- 含义:在需要基类对象的任何地方都可以使用共有派生类的对象来替代
- 实质:公有派生类实际上具备了基类的所有功能,凡是基类能解决的问题,派生类都可以解决
(1)派生类的对象可以给基类的对象赋值
derived d; // derived类是base类的公有派生类
base b; b=d;
反之不然,即基类对象不能赋值给派生类的对象,即使使用强制类型转换也不行,如:
d=b; //error
d= (derived) b; //error
原因:基类对象不具有派生类的所有成员。
(2)派生类的对象可以初始化基类引用
derived d; // derived类是base类的公有派生类
base &rb=d;
反之不然,除非通过强制类型转换,用一个基类的对象初始化其派生类的对象。
base b;
derived &rd=(derived) b;
(3)派生类的对象的地址可以赋给指向基类的指针
derived d; // derived类是base类的公有派生类
base *pb=&d;
反之不然,除非通过强制类型转换,把一个指向基类的指针赋值给一个指向其派生类的指针。
base *pb=new base;
derived *pd=(derived *)pb;
赋值兼容性规则的作用。
- 赋值兼容性规则的引入,对于基类及其共有派生类的对象,就可以使用相同的函数统一处理,而没有必要为每个类设计单独的功能模块
- 当函数的形参为基类对象时,实参可以是派生类的对象
- 当函数的形参为指向基类对象的指针时,实参可以是派生类对象的地址
- 当函数的形参为基类对象的引用时,实参可以是派生类的对象
结论:
- 赋值兼容性规则是多态性的基础。同一个函数可以统一处理具有公有派生关系的基类的对象和派生类对象
【例6.1】开始时没有使用虚函数,然后再讨论使用虚函数的情况。
(4)【例6.2】基类与派生类中有同名函数display
#include
using namespace std;
class Student
{
public:
Student(int, string, float);
void display();
protected:
int num;
string name;
float score;
};
Student::Student(int n, string nam, float s)
{
num = n;
name = nam;
score = s;
}
void Student::display()
{
cout << "num:" << num << "\nname:" << name << "\nscore:" << score << "\n\n";
}
class Graduate: public Student {
public:
Graduate(int, string, float, float);
void display();
private:
float pay;
};
Graduate::Graduate(int n, string nam, float s, float p): Student(n, nam, s), pay(p) {}
void Graduate::display()
{
cout << "num:" << num << "\nname:" << name << "\nscore:" << score << "\npay=" << pay << endl;
}
int main()
{
Student stud1(1001, "Li", 87.5);
Graduate grad1(2001, "Wang", 98.5, 563.5);
Student *pt = &stud1;
pt->display();
pt = &grad1;
pt->display();
return 0;
}
原因是指针是指向基类的指针。用虚函数就能顺利地解决这个问题。
虚函数的使用方法:
- 在基类用virtual声明成员函数为虚函数。在派生类中重新定义同名函数,让它具有新的功能
- 在派生类中重新定义此函数时,要求函数名、函数类型、参数个数和类型与基类的虚函数相同,根据需要重新定义函数体。C++规定,当一个成员函数被声明为虚函数后,其派生类中的同名函数自动成为虚函数
- 定义一个指向基类对象的指针变量,并让他获得同一类族中某个对象的地址
- 用该指针变量调用虚函数,调用的就是该对象所属类的虚函数
根据虚函数的要求,程序作一点修改,在student类中声明display函数时,在最左边加一个关键字virtual,即
virtual void display();
把student类的display函数声明为虚函数。程序其他部分不变,再编译后运行程序,请看程序运行结果。
现在用同一个指针变量不仅输出了基类对象的数据,而且输出了派生类的所有数据,说明调用了grad1的display函数。
用同一种调用形式pt->display(),而且pt是一个基类的指针,可以调用同一类族中不同类的虚函数。这就是多态性,对同一消息,不同对象有不同的响应方式。
原来,基类指针是用来指向基类对象的,如用它指向派生类对象,几桶要进行指针类型转换,所以基类指针指向的是派生类对象中的基类部分。
虚函数突破了这个限制,在基类指针指向派生类对象后,就能调用派生类的虚函数。
以前介绍的函数重载处理的是同一层次中的同名函数问题,而虚函数处理的是不同派生层次上的同名函数问题,前者要求函数名相同,但函数的参数或类型可以不同;后者要求的是不仅函数名要相同,而且函数参数和类型也要相同。
2.静态关联与动态关联
从【例6.2】修改后的程序可以看到,同一个成员函数display在不同类中有不同的作用,呈现了多态。
以前学过的函数重载和用对象名调用虚成员函数,在编译时即可确定其调用的虚函数属于哪个类,其过程称为静态关联。
从【例6.2】里,在调用虚函数时没有指定对象名,系统怎样确定关联呢?编译系统把它放在运行阶段处理,在运行阶段确定关联关系。
在运行阶段,指针可以先后指向不同的类对象,从而调用同一类族中不同类的虚函数。
(1)研究虚函数
先看一个程序
#include
using namespace std;
class Novirtual
{
private:
int x;
public:
void Foo() {}
};
class Withvirtual
{
private:
int x;
public:
virtual void Foo() {}
};
int main()
{
cout << "无虚函数的对象长度=" << sizeof(Novirtual ) << endl;
cout << "有虚函数的对象长度=" << sizeof(Withvirtual ) << endl;
return 0;
}
【注】程序中两个类的差别是一个不带虚函数,另一个带虚函数。
当类中任何函数前带virtual时,编译系统将暗自在类中增加一个数据成员,在这称它为VPTR,它是一个指向函数指针表的指针,因此,Withvirtual类对象占用的内存空间比Novirtual类的对象大。C++需要用一个特别的间接引用调用虚函数。下面演示间接引用调用虚函数的工作原理。
struct dis
{
int i ;
virtual void f();
vitrual void g();
};
dis x;
dis *xp =&x;
xp->g();
这段代码中包括了调用虚函数(如果使用对象调用成员函数则是调用非虚函数,因为编译程序知道x的确切类型。)
(2)虚函数用法1
#include
using namespace std;
class base
{
public:
virtual void Fn()
{
cout << "在基类内" << endl;
}
};
class subclass : public base
{
public:
virtual void Fn()
{
cout << "在派生类内" << endl;
}
};
void test (base &b)
{
b.Fn();
}
int main()
{
base bc;
subclass sc;
cout << "调用test(bc)\n";
test(bc);
cout << "调用test(sc)\n";
test(sc);
return 0;
//当函数的形参为基类对象的引用时,实参可以是派生类//的对象。
}
(3)虚函数用法2
#include
using namespace std;
class base
{
public:
virtual void Fn(int x)
{
cout << "在基类内x=" << x << endl;
}
};
class subclass : public base
{
public:
virtual void Fn( float x)
{
cout << "在派生类内x=" << x << endl;
}
};
void test (base &b)
{
int i = 1;
b.Fn( i );
float f = 2.0;
b.Fn( f );
}
int main()
{
base bc;
subclass sc;
cout << "调用test(bc) \n";
test(bc);
cout << "调用test(sc) \n";
test(sc);
return 0;
}
上述两个程序唯一的区别是:base中Fn()被声明为Fn(int),而subclass中Fn()被声明为Fn(float)。因为在派生类里,虚函数虽与基类虚函数同名,但是参数的类型不相同,成员函数不能被重载。在第二次调用时,把float类型转换成int类型,仍然调用base的Fn(int)函数。
(4)虚函数用法2(改)
#include
using namespace std;
class base
{
public:
virtual void Fn(int x)
{
cout << "在基类内x=" << x << endl;
}
};
class subclass : public base
{
public:
virtual void Fn( int x)
{
cout << "在派生类内x=" << x << endl;
}
};
void test (base &b)
{
int i = 1;
b.Fn( i );
int f = 2.0;
b.Fn( f );
}
int main()
{
base bc;
subclass sc;
cout << "调用test(bc)\n";
test(bc);
cout << "用test(sc) \n";
test(sc);
return 0;
}
(5)虚函数用法3
#include
using namespace std;
class base
{
public:
virtual void Fn(int x )
{
cout << "在基类内x=" << x << endl;
}
};
class subclass : public base
{
public:
virtual void Fn(float x )
{
cout << "在派生类内x=" << x << endl;
}
};
int main(int argc, char *argv[])
{
base bc;
base *p;
subclass sc;
p = &bc;
cout << "调用基类p->Fn(2) \n";
p->Fn(2);
p = ≻
cout << "调用派生类p->Fn(2) \n";
p->Fn(2);
return 0;
}
(6)虚函数用法3(改)
#include
using namespace std;
class base
{
public:
virtual void Fn(int x )
{
cout << "在基类内x=" << x << endl;
}
};
class subclass : public base
{
public:
virtual void Fn(int x )
{
cout << "在派生类内x=" << x << endl;
}
};
int main(int argc, char *argv[])
{
base bc;
base *p;
subclass sc;
p = &bc;
cout << "调用基类p->Fn(2) \n";
p->Fn(2);
p = ≻
cout << "调用派生类p->Fn(2) \n";
p->Fn(2);
return 0;
}
3.在什么情况下应当声明虚函数
注意,只能将类的成员函数声明为虚函数。一个成员函数被声明为虚函数后,在同一类族不能再定义一个与该虚函数相同的非虚函数。
声明虚函数,主要考虑下面几点:
- 首先看成员函数的类是否会作为基类。然后看在派生类里它是否会被改变功能,如要改变,一般应该将它声明为虚函数。否则不要将它声明为虚函数
- 有时在定义虚函数时,函数体是空的。具体功能留给派生类去添加
4.虚析构函数
当派生类的对象撤销时一般先调用派生类的析构函数,然后调用基类的析构函数。如用new运算符建立一个动态对象,如基类中有析构函数,并且定义了一个指向基类的指针变量。在程序中用带指针参数的delete运算符撤销对象时,系统只会执行基类的析构函数,而不执行派生类的析构函数。
【例6.3】派生类对象析构函数的执行演示
#include
using namespace std;
class Point
{
public:
Point() {}
~Point()
{
cout << "executing Point destructor" << endl;
}
};
class Circle: public Point
{
public:
Circle() {}
~Circle()
{
cout << "executing Circle destructor" << endl;
}
private:
int radus;
};
int main()
{
Point *p = new Circle;
delete p;
return 0;
}
表示只执行了基类point的析构函数,未执行派生类的析构函数。这时可将基类的析构函数声明为虚函数,如:
virtual ~ Point(){cout<<"executing Point estructor" <
先调用派生类的析构函数,再调用基类的析构函数。当基类的析构函数为虚函数时无论指针指的是同一类族中的哪一类对象,撤销对象时,系统会采用动态关联,调用相应的析构函数。
构造函数不能声明为虚函数。
虚析构函数
何时需要虚析构函数?
- 当你可能通过基类指针删除派生类对象时
- 如果你打算允许其他人通过基类指针调用对象的析构函数(通过delete这样做是正常的),并且被析构的对象是有重要的析构函数的派生类的对象,就需要让基类的析构函数称为虚拟的
四、纯虚函数与抽象类
1.纯虚函数
只声明,不定义的虚函数称为纯虚函数。一般格式为:
virtual 函数类型 函数名(参数表)=0;
纯虚函数的作用:
在很多情况下,基类中不能为虚函数给出一个有意义的定义,而将它说明为纯虚函数,起作用是: 为派生类提供一个一致的接口(界面)。它的定义留给派生类来做,派生类根据需要来定义各自的实现。
注意:一个类可以说明一个或多个纯虚函数;纯虚函数与函数体为空的虚函数的区别:
- 纯虚函数:根本没有函数体;所在的抽象类,不能直接进行实例化
- 空的虚函数:函数体为空;所在的类可以实例化
- 共同的特点:可以派生出新的类
2.抽象类
抽象类:带有纯虚函数的类是抽象类
抽象类的作用:通过它为一个类族建立一个公共的接口,使他们能够更有效地发挥多态特性
抽象类刻画了一组子类的公共操作接口的通用语义,这些接口的语义也传给子类。一般而言,抽象类只描述这组子类共同操作接口,而完整的实现留给子类。
抽象类的说明:
- 抽象类是一个特殊的类,是为了抽象和设计的目的而建立的,它处于继承层次的结构的较上层,即只能用作其他类的基类,抽象类是不能定义对象的
- 从一个抽象类派生的类必须提供纯虚函数的实现代码或在该派生类中仍将它说明为纯虚函数,否则编译错
- 抽象类不能用作参数类型、函数返回类型或显式转换的类型,但可以说明指向抽象类的指针和引用,此指针可以指向它的派生类,实现多态性
- 构造函数不能是虚函数,析构函数可以是虚函数
3.应用实例
#include
using namespace std;
class B0
{ //抽象基类B0声明
public: //外部接口
virtual void display( ) = 0; //纯虚函数成员
};
class B1: public B0
{ //公有派生
public:
void display()
{
cout << "B1::display()" << endl; //虚成员函数
}
};
class D1: public B1
{ //公有派生
public:
void display()
{
cout << "D1::display()" << endl; //虚成员函数
}
};
void fun(B0 *ptr)
{ //普通函数
ptr->display();
}
int main()
{ //主函数
B0 *p; //声明抽象基类指针
B1 b1; //声明派生类对象
D1 d1; //声明派生类对象
p = &b1;
fun(p); //调用派生类B1函数成员
p = &d1;
fun(p); //调用派生类D1函数成员
return 0;
}
4.【例6.4】虚函数和抽象基类的应用
#include
using namespace std;
class Shape
{
public:
virtual float area() const
{
return 0.0; //虚函数
}
virtual float volume() const
{
return 0.0; //虚函数
}
virtual void shapeName() const = 0; //纯虚函数
};
class Point: public Shape
{ // Point是Shape的公用派生类
protected:
float x, y;
public:
Point(float = 0, float = 0);
void setPoint(float, float);
float getX() const
{
return x;
}
float getY() const
{
return y;
}
// 对纯虚函数进行定义
virtual void shapeName() const
{
cout << "Point:";
}
friend ostream &operator<<(ostream &, const Point &);
};
Point::Point(float a, float b)
{
x = a;
y = b;
}
void Point::setPoint(float a, float b)
{
x = a;
y = b;
}
ostream &operator<<(ostream &output, const Point &p)
{
output << "[" << p.x << "," << p.y << "]";
return output;
}
class Circle: public Point
{ // 声明Circle类
protected:
float radius;
public:
Circle(float x = 0, float y = 0, float r = 0);
void setRadius(float);
float getRadius() const;
virtual float area() const;
// 对纯虚函数进行再定义
virtual void shapeName() const
{
cout << "Circle:";
}
friend ostream &operator<<(ostream &, const Circle &);
};
Circle::Circle(float a, float b, float r): Point(a, b), radius(r) {}
void Circle::setRadius(float r)
{
radius = r;
}
float Circle::getRadius() const
{
return radius;
}
float Circle::area() const
{
return 3.14159 * radius * radius;
}
ostream &operator<<(ostream &output, const Circle &c)
{
output << "[" << c.x << "," << c.y << "], r=" << c.radius;
return output;
}
// 声明Cylinder类
class Cylinder: public Circle
{
public:
Cylinder (float x = 0, float y = 0, float r = 0, float h = 0);
void setHeight(float);
float getHeight() const;
virtual float area() const;
virtual float volume() const;
// 对纯虚函数进行再定义
virtual void shapeName() const
{
cout << "Cylinder:";
}
friend ostream &operator<<(ostream &, const Cylinder &);
protected:
float height;
};
Cylinder::Cylinder(float a, float b, float r, float h)
: Circle(a, b, r), height(h) {}
void Cylinder::setHeight(float h)
{
height = h;
}
float Cylinder::getHeight() const
{
return height;
}
float Cylinder::area() const
{
return 2 * Circle::area() + 2 * 3.14159 * radius * height;
}
float Cylinder::volume() const
{
return Circle::area() * height;
}
ostream &operator<<(ostream &output, const Cylinder &cy)
{
output << "[" << cy.x << "," << cy.y << "], r=" << cy.radius << ", h=" << cy.height;
return output;
}
int main()
{
Point point(3.2, 4.5); //建立Point类对象point
Circle circle(2.4, 12, 5.6); //建立Circle类对象circle
Cylinder cylinder(3.5, 6.4, 5.2, 10.5);
//建立Cylinder类对象cylinder
point.shapeName(); //静态关联
cout << point << endl;
circle.shapeName(); //静态关联
cout << circle << endl;
cylinder.shapeName(); //静态关联
cout << cylinder << endl << endl;
Shape *pt; //定义基类指针
pt = &point; //指针指向Point类对象
pt->shapeName(); //动态关联
cout << "x=" << point.getX() << ",y=" << point.getY() << "\narea=" << pt->area() << "\nvolume=" << pt->volume() <<
"\n\n";
pt = &circle; //指针指向Circle类对象
pt->shapeName(); //动态关联
cout << "x=" << circle.getX() << ",y=" << circle.getY() << "\narea=" << pt->area() << "\nvolume=" << pt->volume() <<
"\n\n";
pt = &cylinder; //指针指向Cylinder类对象
pt->shapeName(); //动态关联
cout << "x=" << cylinder.getX() << ",y=" << cylinder.getY() << "\narea=" << pt->area() << "\nvolume=" << pt->volume() <<
"\n\n";
return 0;
}