转自:http://blog.csdn.net/itolfn/article/details/7412364
一:继承中的指针问题。
1. 指向基类的指针可以指向派生类对象,当基类指针指向派生类对象时,这种指针只能访问派生对象从基类继承
而来的那些成员,不能访问子类特有的元素 ,除非应用强类型转换,例如有基类B和从B派生的子类D,则B
*p;D dd; p=ⅆ是可以的,指针p只能访问从基类派生而来的成员,不能访问派生类D特有的成员.因为基类不
知道派生类中的这些成员。
2. 不能使派生类指针指向基类对象 .
3. 如果派生类中覆盖了基类中的成员变量或函数,则当声明一个基类指针指向派生类对象时,这个基类指针只能
访问基类中的成员变量或函数。例如:基类B和派生类D都定义了函数f,则B *p; D m; p=&m; m.f()将调用基类中
的函数f()而不会调用派生类中的函数f()。
4. 如果基类指针指向派生类对象,则当对其进行增减运算时,它将指向它所认为的基类的下一个对象,而不会指
向派生类的下一个对象,因此,应该认为对这种指针进行的增减操作是无效的.
二:虚函数
1. 为什么要使用虚函数:正如上面第1 和3 点所讲的,当声明一个基类指针指向派生类对象时,这个基类指针只
能访问基类中的成员函数,不能访问派生类中 特有的成员变量或函数 。如果使用虚函数就能使这个指向派生类对
象的基类指针访问派生类中的成员函数,而不是基类中的成员函数,基于这一点派生类中的这个成员函数就必须和
基类中的虚函数的形式完全相同,不然基类指针就找不到派生类中的这个成员函数。使用虚函数就实现了一个接口
多种方法。
2. 注意不能把成员变量声明为虚有的,也就是说virtual关见字不能用在成员变量前面。
3. 正如上面所介绍的,一般应使用基类指针来调用虚函数,如果用点运算符来调用虚函数就失去了它的意义.
4. 如果基类含有虚函数则当声明了一个基类的指针时 ,当基类指针指向不同的派生类时,它就会调用相应派生
类中定义的虚函数版本.这种调用方法是在运行时 决定的 ,例如在类B中声明了虚函数,C,D,E 都从B继承而
来且都实现了自已的虚函数版本,那么当定义了一个B类的指针P时,当P指向子类C时就会调用子类C中定义的
虚函数,当
P指向子类D时就会调用子类D中定义的虚函数 ,当P指向子类E时就会调用子类E中定义的虚函数 .
5. 虚函数须在基类中用virtual 关见字声明也可以在基类中定义虚函数,并在一个或多个 子类中重新定义 .重
定义虚函数时不需再使用virtual关见字,当然也可以继续标明virtual关见字,以便程序更好理解。
6. 包括虚函数的类被称为多态类.C++使用虚函数支持多态性.
7. 在子类中重定义 虚函数时 ,虚函数必须有与基类虚函数的声明完全相同的参数类型和数量,这和重载是不同
的.如果不相同,则是函数重载,就失去了虚函数的本质.
8. 虚函数不能是声明它的类的友元函数,必须是声明它的类的成员函数,不过虚函数可以是另一个类的友元.
9. 一旦将函数声明为虚函数,则不管它通过多少层继承,它都是虚函数,例如D和B继承,而E又从D继承,那
么在B中声明的虚函数,在类E中仍然是虚函数.
10.隐藏虚函数:如果基类定义了一个 虚函数 ,但派生类中却定义了一个虚函数的重载板本 ,则派生类的这个
版本就会把基类的虚函数隐藏掉,当使用基类指针调用该函数时只能调用基类的虚函数 ,而不能调用派生类的重
载版本,当用派生类的对象调用基类的 虚函数时就会出现错误了 ,因为基类的虚函数被派生类的重载版本隐藏了
。
11.带默认形参的虚函数:当基类的虚函数带有默认形参时,则派生类中对基类 虚函数的重定义也必须有相同数
量的形参,但形参可以有默认值也可以没有,如果派生类中的 形参数量和基类中的不一样多 ,则是对基类的虚函
数的重载 。
对虚函数的重定义也就意味着,当用指向派生类的基类指针调用该虚函数时就会调用基类中的虚函数版本。比如基
类定义virtual void f(int i=1, int j=2){}则派生类中必须定义带有两个形参的函数f才是对基类虚函数f的重定
义, 不然就是函数f的重载版本,比如派生类中定义的void f(),void f(int i),void f(int i=2)都是对函数f
的重载,不是对f的重定义。而void f(int i, int j),void f( int i, int j=3),void f(int i=4, int j=5)都
是对虚函数f的重定义。
12.如果虚函数形参有默认值,那么派生类中的虚数的形参不论有无默认值,当用指针调用派生类中的虚函数时就
会被基类的默认值覆盖,即派生类的默认值不起作用 。但用派生类的对象调用该函数时,就不会出现这种情况 。
13.当用指向派生类的基类指针调用虚函数时是以基类中的虚函数的形参为标准的,也就是只要调用的形式符合基
类中定义的虚函数的标准就行了。比如基类中定义virtual void f(int i=1,int j=2){}派生类中重定义为void f
(int i, int j=3){}这时如果用派生类的对象调用这个派生类中的 虚函数 f 时必须至少要有一个实参,但是用指
向派生类的基类指针调用该虚函数时就可以不用任何形参就能调用派生类中的这个函数f, 比如语句p->f()就会调
用派生类中的 虚函数版本 。当用指向派生类的基类指针调用虚函数时是以基类中的虚函数的形参为标准的,也就
是只要调用的 形式符合基类中定义的虚函数的标准就行了。
14.析构函数可以是虚函数,但构造函数不能.
15.纯虚函数声明形式为 virtual 类型 函数名(参数列表)=0;注意后面的等于0;
16.如果类至少有一个纯虚函数,则这个类就是抽象的。
17.如果基类只是声明虚函数而不定义虚函数则此虚函数是纯虚函数 . 任何派生类都必须实现纯虚函数的自已的
版本. 如果不实现纯虚函数那么该类也是抽象类。
18.抽象类不能有对象,抽象类只能用作其它类的基类,因为抽象类中的一个或多个函数没有定义,所以不能用抽
象类声明对象,
19.仍然可以用抽象类声明一个指针,这个指针指向派生类对象.
20.如果派生类中未定义虚函数 ,则会使用基类中定义的函数.
21.虚函数虚拟特性是以层次结构的方式来继承的,例如C从B派生而且C中重定义了B中的虚函数,而D又从C
派生且未重定义B中的虚函数,这时声明一个基类指针P,当P指向类D,并调用D中的虚函数时,由于D中未重
定义虚函数他会调用基类中的虚函数版本,这时他会调用类C中的虚函数而不是类B中的虚函数,因为类C比类B
更接近于类D.
#include <iostream>
using namespace std;
class A
{
public:
int b;
//virtual int b; //错误,不能把成员变量声明为虚有的
virtual void f()
{
cout<<"继续"<<endl;
}
virtual void h(int i=1,int j=2)
{
cout<<"继续H"<<endl;
}
~A(){
cout<<"析构A"<<endl;
}
};
class B:public A
{
public:
int b;
void f(int i)//重载虚函数f
{
cout<<"paif()"<<endl;
}
void f(){//在派生类中重定义虚函数f
cout<<"paixu"<<endl;
}
void h(){ //重载虚函数h的版本。注意这里不是对基类虚函数的重定义。
int b;
b=5;
cout<<"B的"<<b<<endl;
}
void h(int i,int j=3){
int b;
b=j;
cout<<"paixuH"<<b<<endl;
}//当基类中的虚函数有默认形参时,派生类中重定义基类中的虚函数的版本必须有相同数量的形参,
//形参可以有默认值,也可以没有。如果形参数量不一样多则是对虚函数的重载。
~B(){
cout<<"析构B"<<endl;
}
};
int main(){
B m;
A *p=&m;
//p->b=3/错误,指向派生类的基类指针不能调用派生类中的成员,只能调用基类中的成员,
//除非该成员是虚函数
p->f();W//调用派生类中的函数f
//p->f(4);错误,注意这里不是在调用派生类中带一个形参的f函数,因为带一个参数的f函数不是虚函数,
//用指向派生类的基类指针时不会调用派生类中的函数,除非这个函数是虚函数。这里基类中没有定义这种 //带一个形参的f函数,所以这时会出现错误。
p->A::f(); W//调用基类的虚函数f,可以用作用域运算符使用指向派生类的基类指针调用基类的虚函数
p->h();//调用派生类中的虚函数版本h,用指向派生类的基类指针调用虚函数时派生类中的虚函数的默认值在这里不起作用。虽然派生类中的虚函数需要一个参数,
//但这里不给参数仍是调用的派生类的带两个参数的虚函数h,而不是调用派生类中的不带参数的h函数
m.h(); //调用派生类中不带参数的h函数,如果要用对象调用派生类中带两个形参的h函数,在本例中必须使用一个实参值。
m.h(1);//调用派生类中带两个形参的h函数,用对象调用派生类中的虚函数时函数的默认值不受基类虚函数默认值的影响
m.A::h(1);// 调用基类中的虚函数h.
}