C++学习之多态

多态是C++中的三大特性之一。这里所说的多态指的是动态多态。具体地说,通过一个指向基类的指针调用虚成员函数的时候,运行时系统将能够根据指针所指向的实际对象调用恰当的成员函数实现。

举个简单的例子:

class A
{
	public:
	virtual void print()
	{
		cout<<”this is A::print()!”<<endl;
}
};
class B:public A
{
	public:
	void print()
	{
		cout<<”this is B::print()!”<<endl;
}
};
int main()
{
	B b;
	A *pa = &b; // 可以用基类的指针指向派生类的对象,稍后会在类型转换时详细说明
	pa -> print(); // 输出this is A::print()!
	pa->A::print();// 输出this is B::print()!
	pa->B::print();// error 因为B::print不是类A的成员
}

需要注意的是:

实现多态的方式有两种:指针和引用。当基类的指针或者引用调用虚成员函数的时候,在运行期可以实现多态。但是该指针或引用都不能指向私有派生的对象(会有错误提示:“类型转换”从派生类到基类的转换存在,但不可以访问),可以指向公有派生的对象。

 

多态的实现是利用虚函数的原理,这点在http://blog.youkuaiyun.com/susershine/article/details/17020397中有详细的介绍。下面就介绍下使用虚函数应该注意的地方以及多态用途。

1)      不建议在构造函数和析构函数中使用虚函数。虽然在构造函数和析构函数中调用虚函数能通过编译器,但是在链接时有时会出现错误。并且在构造函数和析构函数中调用虚函数时:它们调用的函数是自己的类或基类中定义的函数,不会等到运行时才决定调用自己的还是派生类的函数。这可能会达到不到预计的目的。因为在构造类对象时,构造顺寻是先虚基类,非虚基类,派生类。在调用虚函数时,由于在构造基类时,派生类还没有构造成功,所以它不会下降到派生类的高度,因此实现不了动态的调用派生类中对基类虚函数重写的函数。因为析构函数的析构顺序和构造函数正好相反,因此在析构函数调用虚函数也可能会达不到预计的目的。如下例:

class myclass
{
    public:
    virtual void hello(){cout<<"hello from myclass"<<endl;}
    virtual void bye(){cout<<"bye from myclass"<<endl;}
    };

class son:public myclass
{
    public:
    virtual void hello() {cout<<"hello from son"<<endl;}
    son(){hello();}
    virtual ~son(){bye();}
    };
class grandson:public son
{
    public:
    void hello(){cout<<"hello from grandson"<<endl;}
    grandson(){cout<<"constructing grandson"<<endl;}
    virtual ~grandson(){cout<<"destructing grandson"<<endl;}
    };
int main()
{
    grandson gson;
    son *pson;
    pson = &gson;
    pson->hello();
    return 0;
}

其输出结果是:



从上面的结果可以看出,在构造grandson类时,要先构造myclass和son类,其中在构造son类时,调用了虚函数hello(),该函数的输出是hello from son,即该虚函数是son类中的成员函数,而不是按照多态的动态编译规则下降到grandson类中的成员函数hello()。

2)       虚析构函数。如果一个类中有虚函数,并且它会被其他派生类继承,那么它的析构函数最好声明为虚析构函数。因为删除一个派生类对象时,应该先调用派生类的析构函数,然后调用基类的析构函数。如果没有将基类的析构函数定义为虚函数,那么它只会调用基类的析构函数,而忽略了对派生类对象的析构。比如:

class base
{
		public:
		virtual ~base(){}
};
class derived :public base
{
		public:
		virtual ~derived(){}
};
base * pb = new derived;
delete pb;// 如果基类的析构函数不是虚析构函数,那么就会忽略了对派生类的析构

3)      虚函数的访问权限。

如果虚函数被声明为私有的,那么会出现什么情况呢?首先需要确定的是:访问权限(public、private、protected)和虚函数是两码事。无论函数的是什么样的访问权限,只要它是虚函数,那么它就服从动态编译的规则。只不过在有基类指针或引用调用虚函数时会受访问权限的限制。比如:

class base
{
		private:
		virtual void f();
};
class derived : public base
{
		public:
		void f();
};
derived d;
base *pb = &d;
pb -> f(); // error 访问权限受限制

如果将base类中的f()函数的访问权限设置为public;derived类中的f()函数的访问权限设置为private。此时就可以实现pb->f();并且调用的f()是派生类中的f()。

4)      使用虚函数的规则:

1.      如果一个函数作为类中不可变更的一部分,那么可以将此函数声明为基类的私有函数,并且在基类的公共函数中调用该函数,即该函数作为整个系统的一个不可以更改的实现细节。

2.      如果一个函数提供了算法骨架某环节的一个默认实现,那么可以考虑将该函数声明为基类的私有虚函数,表明可以在派生类中更改或者不更改该函数。

3.      如果作为算法骨架一部分某个函数要求在派生类中拥有不同的实现,那么可以考虑将该函数作为基类的私有纯虚函数,表示派生类必须改写它。

5)      实现类的接口与实现的分离

1.      非虚接口(Non-Virtual Interface NVI)。可以将虚函数私有化,通过一个公有函数调用该虚函数。可以实现接口和实现的分离。

2.      纯虚函数和纯抽象类,含有一个纯虚函数的类是纯虚类,纯虚类可以有成员变量,纯虚类可以有普通的成员函数。纯抽象类(接口类)仅仅只含有纯虚函数,不包含任何数据成员。具有纯虚函数的类是不能实例化对象的。但可以给纯虚函数定义一个默认的实现代码,派生类也可以继承该实现代码。如:

class base
{
		public:
		virtual void func() =0 ; // 纯虚函数
		virtual void func1() =0; // 纯虚函数
};//不可以实例化对象
void base::func(){cout<<”this is base::func()!”<<endl;}
void base::func1() {cout<<”this is base::func1()!”<<endl;}

class derived
{
		public:
		virtual void func() {base::func();}//实现对纯虚函数的实现代码的继承
		virtual void func1() {cout<<”this is derived::func1()!”<<endl;}//实现重写纯虚函数
};

6)      dynamic_cast 和 static_cast、reinterpret_cast

static_cast的用法如下:static_cast<type-id>(expression);

其作用:                                     

1.      用于类层次结构中基类和派生类之间指针或引用的转换。由于static_cast没有动态类型检查,所以是不安全的。但进行上行转换(把派生类的指针或引用转换为基类表示)是安全的。

2.      由于基本数据类型间的转换

3.      把任何类型的表达式转换成void类型

 

dynamic_cast的用法如下:dynamic_cast<type *>(expression);

dynamic_cast仅对多态类型有效,并且要求转型的目的类型必须是指针或引用。它会进行动态类型检查。当进行转换时的操作时不安全的,它会返回一个NULL;如果是引用它会抛出一个Bad_cast异常。并且使用dynamic_cast也能实现强制的“多态”。

 

reinterpret_cast是C++里的强制类型转换符。操作符修改了操作数类型,但仅仅是重新解释了给出的对象的比特模型而没有进行二进制转换。

例如:int *n=new int ;

double *d=reinterpret_cast<double*> (n);

在进行计算以后, d 包含无用值. 这是因为reinterpret_cast 仅仅是复制 n 的比特位到 d, 没有进行必要的分析。因此, 需要谨慎使用 reinterpret_cast.并且:reinterpret_cast 只能在指针之间转换。reinterpret_cast是为了映射到一个完全不同类型的意思,这个关键词在我们需要把类型映射回原有类型时用到它。我们映射到的类型仅仅是为了故弄玄虚和其他目的,这是所有映射中最危险的。



评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值