虚函数与动态绑定

在定义基类时,我们希望基类中的有些函数可以在派生类中重新定义。比如,我们定义了基类记录的书,可以求出买了多少书花了多少钱;而在派生类中,我们定义的是打折的书,还是要计算买了多少书花了多少钱。这时,就需要重新定义计算钱数的函数了。注意,这里的重新定义,与之前讲过的函数重载或者操作符重载不同:后面两类,是通过不同的形参,返回值类型来让编译器判断到底使用的是哪个函数,在程序执行前就能判断;而这里的重新定义,只是函数体的内容不同,参数、返回值都与积累完全相同。只有程序运行时才能判断使用的到底是哪个层次中的函数(这个过程成为动态绑定),这样做是为了让程序的接口统一。
通过把一个函数声明为虚函数,(这个函数不能使这个类的构造函数,也不能是static函数)我们就可以在派生类中重新定义它。虚函数在声明时,需要加上关键字virtual,不能在类的外部出现这个关键字。这一点和友元的声明不同:友元可以既可以在类内不声明,也可以在类外部声明。当一个函数被声明为虚函数后,这个函数在对于基类的后代而言,就都是虚函数了,不用加上virtual关键字。(当然,你也可以加上以强调它是虚函数)。

只有通过基类的引用或者指针调用虚函数时,才能发生动态绑定。因为引用或者指针既可以指向基类对象,也可以指向派生类对象的基类部分,用引用或者指针调用的虚函数。
举一个例子:

class Item_base
{
public:
	//构造函数
	Item_base(const std::string &book = "",double sales_price = 0.0):
		isbn(book),price(sales_price){ }
	//返回isbn号
	std::string book(){return isbn;}
	//基类不需要折扣策略
	virtual double net_price(std::size_t n)const{return n * price;}
	//析构函数
	virtual ~Item_base(){};

private:
	std::string isbn;
protected:
	double price;
};

class Bulk_item:public Item_base
{
public:
	//构造函数
	Bulk_item(const std::string& book = "",double sales_price = 0.0,std::size_t qty = 0,double disc_rate = 0.0):
		Item_base(book,sales_price),quantity(qty),discount(disc_rate){ }
	~Bulk_item(){}
	double net_price(std::size_t)const;


private:
	//买多少书以后才有折扣
	std::size_t quantity;
	//折扣幅度
	double discount;
};


其中:

double Bulk_item::net_price(std::size_t cnt)const
{
	if(cnt > quantity)
		return cnt * (1 - discount) * price;
	else
		return cnt * price;

}

我们再定义一个打印结果的函数:
我们再定义一个打印结果的函数:我们再定义一个打印结果的函数:我们再定义一个打印结果的函数:我们再定义一个打印结果的函数:

void print_total(std::ostream& os,const Item_base& item,std::size_t n)
{
	os<<"ISBN: "<<item.book()<<"\tnumber sold: "<<n<<"\ttotal price: "<<item.net_price(n)<<std::endl;
}

那么在主函数中:

int main()
{

	Item_base b1("aaa",10);
	Bulk_item d1("bbb",10,5,0.5);
	print_total(cout,b1,10);
	print_total(cout,d1,10);
	return 0;
}

可以发现,虽然print_total函数的输入参数是基类的引用,我们能够也能够通过派生类来访问它。当使用基类访问它时,调用的是基类的net_price函数,派生类访问它时,调用的是派生类的net_price函数。这个调用规则不是在编译时可以确定的,必须在程序运行时才能确定。所以称之为“动态关联”。与之相对的,是静态关联,比如函数的重载,操作符的重载等等。程序执行之前,就能确定调用的是哪个函数。

特别的,有时我们会遇到这种情况:我们定义了一个类,这个类也有自己的数据和函数,但是我们并不希望用户建立这个类的对象,而只能建立从这个基类派生出的类的对象。有人觉得,既然我们不希望建立一个一个类的对象,那么我们干嘛要定义这个类呢?其实很多时候,基类,是对一些问题的抽象,对它建立对象是没有意义的。我们只是用它派生其他的,具体的类。比如,我们建立了“动物”类,定义了动物的“身高”、“体重”,定义了动物的动作:“吃饭”、“睡觉”。从“动物”类派生出“老虎”等类。此时,大家也应该觉得,定义一个动物类对象没有多大的意义。
言归正传,如何实现上面的构想呢?就是使用纯虚函数。纯虚函数的定义很简单,就是在某个函数的形参列表后面加上=0。但这一举动却意义非凡:首先我们定义了这个函数(而不是在派生类中定义),是得我们为这类函数提供了统一的、可覆盖的接口,以便于后面的管理;其次,当一个类中含有(或者)纯虚函数时,这个类就被成为“抽象基类”,我们不能创建这个类的对象,只能用它来派生别的类,创建派生类的对象,这样规定,也防止我们误用了这个类(因为他是被用来继承的,很多成员都不完善。)举一个例:

class annimal
{
public:
	virtual void eat()=0;
	void sleep(){};

private:
	double height;
	double weight;
};

class tiger:public annimal
{
public:
	void eat(){std::cout<<"eat meat"<<std::endl;}
	void sell(){std::cout<<"sleep at night"<<std::endl;};
};

class bat:public annimal
{
public:
	void eat(){std::cout<<"eat blood"<<std::endl;};
	void sell(){std::cout<<"sleep at day"<<std::endl;};

};
int main()
{
	//不能创建抽象类的对象
	//annimal a1;
	tiger t1;
	t1.eat();
	bat b1;
	b1.eat();
	return 0;
}



  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值