第十三章 虚函数

//1 指向对像的指针
/*
#include <iostream>
using namespace std;
class father
{
public:
	father():age(52){cout<<"调用父类的构造函数并初始化age的值为:"<<age<<endl;}
	~father(){cout<<"调用你类的析构函数"<<endl;}
	void jump(){cout<<"父亲可以跳五个台阶"<<endl;}
	void run(){cout<<"父亲可以跳万米"<<endl;}
private:
	int age;
};

class son:public father
{
public:
	son(){cout<<"调用子类构造函数"<<endl;}
	~son(){cout<<"调用子类析构函数"<<endl;}
	void math()const {cout<<"儿子会做数学"<<endl;}
	void jump()const {cout<<"儿子可以跳十个台阶"<<endl;}
	void run()const{ cout<<"儿子可以跑十万米"<<endl;}
};
int main()
{
	son *p = new son;
	p->jump();
	p->run();
	p->math();
	delete p;
    return 0;
}*/



//2 虚函数
/*
#include <iostream>
using namespace std;
class father
{
public:
	virtual void run()const{cout<<"父亲可以跑万米"<<endl;}
	void jump(){cout<<"父亲可以跳五个台阶"<<endl;}
};
class son : public father
{
public:
	void run()const{ cout<<"儿子可以跑十万米"<<endl;}
	void jump()const{cout<<"儿子可以跳十个台阶"<<endl;}
};
int main()
{
	father * p = new son;
	p->jump();

	//唉,刚才理解的大错特错了,只有是虚函数才有多态性
	p->run();
	//这里打印出"儿子可以跳十个台阶"??
	//为什么会这样呢,这是因为函数run()前面加关键字virtual,表示该函数是有多种形态的,即该函数可以被多个对像所拥有
	//而且功能不一,换句话说,多个对像在调用同一名字的函数时产生的效果也不一样,那么系统在执行到有关键字virutal的函数时
	//就会自动判断是哪里个对像调用了它,然后调用该对像的同名函数

	delete p;
    return 0;
}*/

// 3 拳击游戏
/*
#include <iostream>
using namespace std;
class poser
{
public:
	virtual void beat()const{ cout<<"一般选手的力量为260磅"<<endl;}
protected:
	int age;
};
class Ali:public poser
{
public:
	virtual void beat()const{cout<<"阿里一拳的力量是200磅"<<endl;}
};
class Lewis:public poser
{
public:
	virtual void beat()const{cout<<"刘易丝一拳的力量是250磅"<<endl;}
};
class Tyson:public poser
{
public:
	virtual void beat()const{cout<<"泰生一拳的力量是450磅"<<endl;}
};
int main()
{
	while(1)
	{
	     cout<<"阿里<1> 刘易丝<2> 泰生<3> 退出<0>";
		 int s;
		 bool isexit = false;
		 poser *p;
		 cin>>s;
		 switch(s)
		 {
			 case 1:
				 p = new Ali();
				 p->beat();
				 break;
			 case 2:
				 p = new Lewis();
				 p->beat();
				 break;
			 case 3:
				 p = new Tyson();
				 p->beat();
				 break;
			 case 0:
				 isexit = true;
				 break;
			 default:
				 cout<<"请输入0到3这的数字"<<endl;
				 break;
		 }
		 if(isexit == true){
		      cout<<"欢迎退出!"<<endl;
			  break;
		 }
	}
	return 0;
}*/


/*
#include <iostream>
using namespace std;
class poser
{
public:
	virtual void beat()const{ cout<<"一般选手的力量为260磅"<<endl;}
protected:
	int age;
};
class Ali:public poser
{
public:
	virtual void beat()const{cout<<"阿里一拳的力量是200磅"<<endl;}
};
class Lewis:public poser
{
public:
	virtual void beat()const{cout<<"刘易丝一拳的力量是250磅"<<endl;}
};
class Tyson:public poser
{
public:
	virtual void beat()const{cout<<"泰生一拳的力量是450磅"<<endl;}
};
int main()
{
	poser *m[5];
	poser *p;
	for(int i=0; i<5; i++)
	{
	    cout<<"阿里<1> 刘易丝<2> 泰生<3>";
		int s;
		cin>>s;
		switch(s)
		{
			 case 1:
				 p = new Ali();
				 break;
			 case 2:
				 p = new Lewis();
				 break;
			 case 3:
				 p = new Tyson();
				 break;
			 default:
				 p = new poser();
		 }
		 m[i] = p;
		 m[i]->beat();
	}
	return 0;
}*/



// 4 继承是否可以实现多态性
/*
#include <iostream>
using namespace std;
class A
{
public:
	 virtual void print(){cout<<"A"<<endl;}
};

class B : public A
{
public:
	void print(){cout<<"B"<<endl;}
};
class C : public A
{
public:
	void print(){cout<<"C"<<endl;}
};

int main()
{
	A a;
	B b;
	C c;
	//a.print();
	//b.print();
	//c.print();

	A *p = &a;
	A *p1 = &b;
    A *p2 = &c;
	cout<<"*p:"<<&p<<endl;
	cout<<"*p1:"<<&p1<<endl;
	cout<<"*p2:"<<&p2<<endl;
	
	//这里如果不把基类的print()函数设置成virutal的话,那么这里都将成了静态联编了
	p->print();
	p1->print();
	p2->print();



    return 0;
}*/
//两者之间为什么会有这么显示著的区别呢?这是因为在不使用virtual之关
//C++对重载的函数使用静态联编辑,而使用了virtual以后,C++则对该函数进行动态联编,
//那么什么是动态联编辑,什么又是静态联编辑呢,我们先来说明一下联编
//C++中的联编辑共分两种
//就是静态联编辑和动态联编辑
//因此在未加virtual说明时,该函数是静态联编,即被调函数和调用函数者的关系以及它闪的内存地址在编辑译时都已经确立好,运行时不再
//发生变化,这样的好处是速度快,因为运行的时候不用对各个对象的函数进行追踪,只需要传递参数,执行确定好的函数并在函数调用完毕后清理内存即可

//因此我们看到第一例中由于基类的print函数未说明为虚函数,则该函数在执行的时候将采用静态联编,即不对重载的各个对象的函数进行追踪
//这导致C++编译器在编辑时认定的指向基类的三个指针p p1 p2在运行时也不会根据对象的改变而发生改变
//因此就算是将三个对象摧存地址依次赋给了三个指针,三个指针还是默认指向基类

//与其相反,动态联编辑就要牺牲一些速度,因为每个函数调用在运行前是不可确立的
//要随着用户的操作来执行相应的函数,比如说在拳击游戏中用户按下一个出拳键,那么系统将根据用户选择的角色不同面彩用不同的出拳(函数)
//出拳这个动作名是相同的确(函数名相同),但是他们产生的效果是不一样的, 有的拳手力量大,有的拳手力量小
//(沙子数的功能不一,根据对象来定)。这个函数调用就是不可预测的,因为你无法事先预料到用户
//选择的是哪些个角色,这就是代码对每个角色进行追踪,并且在游戏运行时要时该地判断该调用哪些个角色的拳头(合适的函数),然后再调用它,虽然这样比较灵活,但是相应地也就大大地增加了系统的开销,
//不过在这个游戏中使用动态联编辑却是非常好的选择



//在编译时的静态联编
/*
#include <iostream>
using namespace std;
class A{
public:
	int get(){return 1;}
};
class B: public A
{
public:
	int get(){return 2;}
};
int main()
{
	A a;
	int one;
	one = a.get();
	cout<<"a的值是:"<<one<<endl;

	B b;
	one = b.get();
	cout<<"b的值是:"<<one<<endl;


    return 0;
}*/



//6 在运行时的静态联编
//接着我们再看一下运行时的效果,假设我们用指针在运行时再动态的指向某个对像
//然后再用该对像调用它的成员函数,由于静态联编的对像与指针的关系在编译时就已经确定,所以运行时再想对它改变也是无效的,我们看实例:
/*
#include <iostream>
using namespace std;
class A{
public:
	int get(){return 1;}
};
class B: public A
{
public:
	int get(){return 2;}
};
int main()
{
	while(1)
	{
	     cout<<"(1)父类 (2)子类 (3)退出";
		 int s;
		 cin>>s;
		 A *p;
		 bool quit = false;
		 switch(s)
		 {
			 case 1:
				 p = new A;
				 cout<<p->get()<<endl;
				 break;
			case 2:
				 p = new B;
				 cout<<p->get()<<endl;
				 break;
			case 3:
				quit = true;
				break;
			default:
				cout<<"请选择正确参数"<<endl;
				break;
		 }
		 if(quit == true)
		 {
		     break;
		 }
		 cout<<"get()值为:"<<p->get()<<endl;
	}



    return 0;
}*/




//在编译时的动态联编辑
//从对静态联编的上述实例中可以知道,编译程序在编译程序在编译阶段并不能确切知道将要调用的函数
//只有在程序执行时才能胡定将要调用的函数,为此要确切知道该调用的函数,要求联编工作要在程序运行时进行,
//这种在程序运行时进行联编工作被称为动态联编,或称动态束定,双叫晚期联编
/*
#include <iostream>
using namespace std;
class A{
public:
	virtual int get(){return 1;}
};
class B: public A
{
public:
	int get(){return 2;}
};
int main()
{
	while(1)
	{
	     cout<<"(1)父类 (2)子类 (3)退出";
		 int s;
		 cin>>s;
		 A *p;
		 bool quit = false;
		 switch(s)
		 {
			 case 1:
				 p = new A;
				 cout<<p->get()<<endl;
				 break;
			case 2:
				 p = new B;
				 cout<<p->get()<<endl;
				 break;
			case 3:
				quit = true;
				break;
			default:
				cout<<"请选择正确参数"<<endl;
				break;
		 }
		 if(quit == true)
		 {
		     break;
		 }
		 cout<<"get()值为:"<<p->get()<<endl;
	}



    return 0;
}*/


// 8  在统译时的动态联编
// 假如我们在虚函数中没有采用指针或者引用,那么就无法实现动态联编
/*
#include <iostream>
using namespace std;
class A{
public:
	virtual int get(){return 1;}
};
class B: public A
{
public:
	int get(){return 2;}
};
int main()
{
	while(1)
	{
	     cout<<"(1)父类 (2)子类 (3)退出";
		 int s;
		 cin>>s;
		 A p;
		 bool quit = false;
		 switch(s)
		 {
			 case 1:
				 p = A();
				 cout<<p.get()<<endl;
				 break;
			case 2:
				 p = B();
				 cout<<p.get()<<endl;
				 break;
			case 3:
				quit = true;
				break;
			default:
				cout<<"请选择正确参数"<<endl;
				break;
		 }
		 if(quit == true)
		 {
		     break;
		 }
		 cout<<"get()值为:"<<p.get()<<endl;
	}



    return 0;
}*/


//三种调用虚函数方式的比较
/*
#include <iostream>
using namespace std;

class father
{
public:
	virtual void run()const{cout<<"父亲可以跑万米\n";}
};
class son:public father
{
public:
	void run()const{cout<<"儿子可以跑十万米\n";}
};
class daughter:public father
{
public:
	void run()const{cout<<"女儿可以跑五万米\n";}
};
void One(father one);
void Tow(father *two);
void Three(father &three);



int main()
{
	father *p;
	int s;
	bool quit = false;
	while(1)
	{
	   cout<<"(1)父亲 (2)儿子 (3)女儿 (0)退出"<<endl;
	   cin>>s;
	   switch(s){
		   case 1:
			   p = new father;
			   Three(*p);
			   break;
		   case 2:
			   p = new son;
			   One(*p);
			   break;
		   case 3:
			   p = new daughter;
			   Tow(p);
			   break;
		   case 0:
			   quit = true;
			   break;
		   default:
			   cout<<"参数不正确!";
			   break;
	   }
	   if(quit == true){
	      break;
	   }
	}

   return 0;
}

void One(father one)
{
	one.run();
}

void Tow(father *two)
{
	two->run();
}

void Three(father &three)
{
	three.run();
}
*/


///////////////////////////////////////////////
///系统是如何调用虚函数的
/*每个对象创建虚函数时,对象都得记录这个虚函数,因此编译器建立了一个叫做T表的虚函数表
每个对象都有一个指向该表的指针,叫做虚表指针,该指针用来指向虚函数表,相反虚函数表也有一个指针指向该对象
当创建派生类对象的基类部分时,该对象的指针就自动初始化为指向虚函数表的正确部分,当调用派生类对象的构造函数时
这个对象就会添加到虚函数表中去,并且将指针指向该对象的重载函数
当使用指向基类的指针时,将根据对象的实际类型,将该对象的指针继续指向正确的函数*/

/*
#include <iostream>
using namespace std;
class A
{
public:
	virtual int get(){ return 0;}
};
class B : public A
{
public:
	int get(){ return 1;}
};
int main()
{
	B b;
	B *p = &b;   //这就是动态联编,两种实例化是一样的
	//A *p = &b;
	
	cout<<p->get()<<endl;
	cout<<p->A::get()<<endl; //成员名限定,可以直接访问到想访问的函数
    return 0;
}*/

//////////////////////////////////
//虚构造函数和析构函数
/*一个派生类对象在创建时会首先调用基类的构造函数,然后调用该类的构造函数,一般情况下
在使用虚函数的时候,我们都会将派生类对象传递给指向基类的指针,那么假如指向派生类对象的指针删除时会发生什么情况呢?
如果析构造函数是虚函数,那么就会进行正确的打操作,它会先调用派生类的析构函数,由于派生类的析构函数会自动调用基类的析构函数,因此构造的整个对象都会被销毁
一般情况下任何类的析构函数可以声明为虚析构函数,当指针被删除时,系统会获得对象运行时的类型并调用正确的析构函数,但是我们要注意三点:
一:由于析构函数不允许有参数,因此它不可能实现重载,那么一个类只能一个虚析构函数
二:只要基类的析构函数被说明为虚函数,那么派生类的析构函数无论声明与否,都自然成为虚函数
三:在C++中虚构函数是不存在的,因此也无法声明*/

#include <iostream>
using namespace std;
class A
{
public:
	A(){cout<<"创建A"<<endl;}
	virtual void func(){cout<<"类A"<<endl;}
	virtual ~A(){cout<<"析构A"<<endl;}
};

class B : public A
{
public:
	B(){cout<<"创建B"<<endl;}
	void func(){cout<<"类B"<<endl;}
	~B(){cout<<"析构B"<<endl;}
};

int main()
{
	A *p = new B;
	p->func();
	delete p;
    return 0;
}

  

posted @ 2012-07-09 00:32  简单--生活  阅读(450)  评论(0编辑  收藏  举报
简单--生活(CSDN)