一、虚函数
多态性(polymorphism)是面向对象程序设计的基石之一,而虚函数(virtual function)则是多态性的必要成分。
相同的函数调用可以执行完全不同的函数,这种能力被称为多态性。要实现多态性必须具备几个条件:
首先,所有的不同的模型类必须是从同一个基类派生出来的;
其次,基类中的要调用的函数必须声明为virtual。
1.指针访问哪个成员函数???
#include<iostream>
using namespace std;
class Base
{
public:
virtual void show()
//void show()
{
cout<<"Base\n";
}
};
class Derv1:public Base
{
public:
void show()
{
cout<<"Derv1\n";
}
};
class Derv2:public Base
{
public:
void show()
{
cout<<"Derv2\n";
}
};
int main()
{
Derv1 d1;
Derv2 d2;
Base* ptr;
ptr=&d1;
ptr->show();
ptr=&d2;
ptr->show();
return 0;
}
当基类不是虚函数的时候:编译器会忽略指针ptr的内容,并且只选择匹配指针类型的成员函数。输出:Base
当基类中是虚函数的时候:编译器根据指针ptr的内容选择函数,而不是根据指针类型。此时输出为:Derv1 Derv2
注:这里采用了后联编(late binding):刚开始编译器不知道那个要被调用,在运行的时候才知道。
2.抽象类和纯虚函数
按照上面的例子把virtual void show() 变为
virtual void show() = 0;
虽然它只是一个声明,但是没有必要继续编写基类成员函数show()的定义,即使确实需要它。 因为纯虚函数是不能建立对象实例的。如:
//Base bad; //can't make object from abstract class
注意:一旦在基类中加入一个纯虚函数,就必须在该基类所有可建立对象实例的派生类中重载该函数。(如果某个类没有重载该虚函数,那么它就将变成一个抽象类,这样就不能建立他的对象实例了)
下面列举一个图形实例中的虚函数:
#include<iostream>
using namespace std;
#include "msoftcon.h"
class shape
{
protected:
int xCo,yCo;
color fillcolor;
fstyle fillstyle;
public:
shape():xCo(0),yCo(0),fillcolor(cWHITE),fillstyle(SOLID_FILL) {}
shape(int x,int y,color fc,fstyle fs):xCo(x),yCo(y),fillcolor(fc),fillstyle(fs) {}
virtual void draw() =0 //pure virtual draw function
{
set_color(fillcolor);
set_fill_style(fillstyle);
}
};
class ball:public shape
{
public:
ball():shape(),radius(0) {}
ball(int x,int y,color fc,fstyle fs,int r):shape(x,y,fc,fs),radius(r) {}
void draw()
{
shape::draw();
draw_circle(xCo,yCo,radius);
}
private:
int radius;
};
class rect:public shape
{
public:
rect():shape(),width(0),height(0) {}
rect(int x,int y,color fc,fstyle fs,int w,int h):shape(x,y,fc,fs),
width(w),height(h) {}
void draw()
{
shape::draw();
draw_rectangle(xCo,yCo,xCo+width,yCo+height);
set_color(cWHITE);
draw_line(xCo,yCo,xCo+width,yCo+height);
}
private:
int width,height;
};
int main()
{
init_graphics();
shape* pShapes[2];
pShapes[0]=new ball(40,12,cBLUE,X_FILL,5);
pShapes[1]=new rect(12,7,cRED,SOLID_FILL,10,15);
for (int j=0;j<2;j++)
{
pShapes[j]->draw();
}
for (int j=0;j<2;j++)
{
delete pShapes[j];
}
set_cursor_pos(1,25);
return 0;
}
这是一个组合图形元素较好的方法,特别市在大量的对象需要聚集成一个单元的时候适用。
2.虚析构函数---基类虚构函数应该总是虚的
#include<iostream>
using namespace std;
class Base
{
public:
//~Base() //结果为值销毁了基类的部分 Base destroyed
virtual ~Base() //结果为销毁两部分:基类,派生类 Derv destroy Base destroyed
{ cout<<"Base destroyed\n"; }
};
class Derv:public Base
{
public:
~Derv()
{cout<<"Derv destroy\n";}
};
int main()
{
Base* pbase=new Derv;
delete pbase;
return 0;
}
一般的为了能彻底地销毁派生类的对象,有必要将基类的析构函数定义为虚的
3.虚基类
class Parent
{
protected:
int basedata;
};
class Child1:public Parent
{};
class Child2:public Parent
{};
class Grandchild:public Child1,public Child2
{
public:
int getdata()
{
return basedata;
}
};
这里要个问题,因为Grandchild根本没有办法知道basedata是从哪一个child哪里继承过来的,所以编译器会提示错误。
为了解决这种模糊,有必要将类Child1和类Child2都变为虚基类
class Child1:virtual public Parent
{};
class Child2:virtual public Parent
{};
两个关键字virtual的使用使得他们公用了基类的一个子对象。因为只有一个basedata副本,因此模糊性就没有了。
二、友元函数
封装和数据隐藏的概念要求非成员函数不能访问对象的私有和保护数据,也就是说,如果不是对象的成员就对对象中的私有和保护数据没有办法。但是友元函数就可以做到上面不可能的事。
1.看一个基础的例子
#include<iostream>
using namespace std;
class beta; //need for frifunc declaration
class alpha
{
private:
int data;
public:
alpha():data(3) {}
friend int frifunc(alpha,beta); //friend function
};
class beta
{
private:
int data;
public:
beta():data(7) {}
friend int frifunc(alpha,beta); //friend function
};
//
int frifunc(alpha a,beta b) //frifunc definition
{
return (a.data+b.data);
}
//------------------------------------------------------------------------
int main()
{
alpha aa;
beta bb;
cout<<frifunc(aa,bb)<<endl;
return 0;
}
注意:1) friend int frifunc(alpha,beta); 这个声明可以放在类中的任何地方,与在公共部分还是私有部分没有关系;
2) 类在声明之前不能被引用。所以class beta; 必须放在程序的开始处。
2.突破访问限制
这里最常用的功能就是用友元函数来增加重载运算符的功能。
举例:
#include<iostream>
using namespace std;
class Distance
{
private:
int feet;
float inches;
public:
Distance():feet(0),inches(0.0) {}
Distance(float fltfeet)
{
feet=static_cast<int>(fltfeet);
inches=12*(fltfeet-feet);
}
Distance(int f,float i):feet(f),inches(i) {}
void showdist()
{
cout<<feet<<"\'-"<<inches<<"\"";
}
Distance operator + (Distance);
//friend Distance operator + (Distance,Distance);
};
Distance Distance::operator+(Distance dd)
{
int f=feet+dd.feet;
float i=inches+dd.inches;
if (i>=12.0)
{
i-=12.0;
f++;
}
return Distance(f,i);
}
//Distance operator +(Distance d1,Distance d2) //friend function
//{
// int f=d1.feet+d2.feet;
// int i=d1.inches+d2.inches;
// if (i>=12.0)
// {
// i-=12.0;
// f++;
// }
// return Distance(f,i);
//}
//
int main()
{
Distance d1=2.5;
Distance d2;
d2=d1+10.0;
cout<<"d2= ";d2.showdist();
d2=10.0+d1; //错误
cout<<"\nd2= ";d2.showdist();
cout<<endl;
return 0;
}
在这里d2=10.0+d1;是错误的,因为重载后的运算符+是某个对象的成员,而该对象必须是运算符左边的一个变量,而在这个表达式中运算符的左边是个常量,所以会出现错误。
这种情况有两种解决方案:
1)我们可以创建一个新的Distance类来解决:d2=Distance(10.0)+d1; --当然这种不是很直观而且粗糙。
2)我们可以重新用友函数来重载运算符(如程序中注释掉得部分),这样d2=10.0+d1;就成立了。
注意:这里比直接重载的时候多了一个参数,因为此时运算符不属于任何对象,所以像普通重载运算符所属的对象也会当做参数来呈现。在本例中10.0作为第一个参数传给Distance类的参数d1,通过一个参数的构造函数的转换,就可以计算了。
3.友元类
当整个类被声明为友元时,该类的所有成员函数也都同时成为友元的。
举例:
#include<iostream>
using namespace std;
class alpha
{
private:
int data;
public:
alpha():data(99) {}
friend class beta;
};
class beta
{
public:
void func1(alpha a) { cout<<"data1="<<a.data; }
void func2(alpha a) { cout<<"\ndata2="<<a.data; }
};
//
int main()
{
alpha a;
beta b;
b.func1(a);
b.func2(a);
cout<<endl;
return 0;
}
注意:友元函数的声明中用关键字class只是beta是一个类:friend class beta; 如若不然必须和上一个例子一样事先声明beta是一个类:class beta;然后再引用就不需要关键字class了:friend beta;
三、静态函数
你可能还记得:静态数据成员不会为每一个对象都准备一个副本,类的所有对象都共享一个数据项。
这里我们要把成员函数也静态下,实例:
#include<iostream>
using namespace std;
class gamma
{
private:
static int total;
int ID;
public:
gamma()
{
total++;
ID=total;
}
~gamma()
{
total--;
cout<<"Destroyint ID number "<<ID<<endl;
}
void showid()
{
cout<<"ID number is "<<ID<<endl;
}
static void showtotal()
{
cout<<"the total is "<<total<<endl;
}
};
int gamma::total=0;
int main()
{
gamma a;
gamma::showtotal();
gamma b,c;
gamma::showtotal();
a.showid();
b.showid();
c.showid();
cout<<"-------------------end of program------------------\n";
return 0;
}
1.访问static函数:gamma::showtotal();
2.注意析构函数销毁对象的顺序。如输出所示,最后创建的对象c被首先销毁。(由此我们可以推断出:本地对象是存储在堆栈中的)。