概述
如果想通过指向派生类对象的基类指针,调用派生类中覆盖的成员函数,可以用虚函数的方法让派生类中覆盖的成员函数被调用
1.虚函数派生下去的仍然为虚函数,而且可以省去 virtual 关键字
2.如果你期望派生类从新定义一个成员函数,那么你应该在基类中把此函数设置为 virtual
3.如果定义的类作为基类,则应将那些要在派生类中重新定义的类的方式声明为虚拟的
4.如果使用指向对象的引用或指针调用虚方法,程序将使用做为对象类型的定义方法而不是使用做为了引用或指针定义的方法
用自己的话理解
1.虚拟函数用于继承(派生)
2.基类的函数被声明为虚拟函数,子类的函数也是虚函数,可以省去 virtual 关键字
3.虚拟函数用户实现多态性
4.A *a=new B; 只有类B继承了类A才能编译通过
5.调用虚函数中,子类有的会调用子类的,子类没有的会调用基类的
#include <iostream>
using namespace std;
class A
{
public:
virtual void test(void){cout<<"基类A的Test函数执行"<<endl;}
void print(void){cout<<"基类A的print函数"<<endl;}
};
class B:public A
{
public:
virtual void test(void){cout<<"派生类B的test函数执行"<<endl;}
void print(void){cout<<"派生类B的print函数"<<endl;}
};
int main (void)
{
A *a=new B;
a->test();
a->print();
return 0;
}
/*
2015年3月3日10:18:35
程序执行结果如下:
派生类B的test函数执行
基类A的print函数
请按任意键继续. . .
*/
#include <iostream>
using namespace std;
class A
{
public:
virtual void output1()=NULL;
virtual void output2(){cout<<"基类的output2"<<endl;}
};
class B:public A
{
private:
void output1(){cout<<"子类实现基类的纯虚函数"<<endl;}
protected:
void output2(){cout<<"子类的output2"<<endl;}
};
int main(void)
{
A *a=new B;
a->output1();
a->output2();
//A* a;
//B b;
//a = &b;
//a->output1();
//a->output2(); //通过 a 我们访问到了 B 变量 b 的私有成员函数和保护成员函数 output1,output2
//b.output1();
//b.output2();
//这里我们是无权限去访问类 B 变量 b 的私有成员函数和保护成员函数 output1,output2
//A a1; //error 基类的函数被声明为纯虚函数,不能被实力化
return 0;
}
/*
2015年3月3日10:19:22
程序执行结果如下:
子类实现基类的纯虚函数
子类的output2
请按任意键继续. . .
*/
#include <iostream>
using namespace std;
//基类的析构函数被声明为虚函数,是为了子类的析构函数被调用
class A
{
public:
A(){cout<<"基类A的构造函数执行中"<<endl;}
virtual ~A(){cout<<"基类A的析构函数执行中"<<endl;}
public:
virtual void print(void){cout<<"基类的print函数执行中"<<endl;}
};
class B:public A
{
public:
B(){cout<<"子类B的构造函数执行中"<<endl;}
~B(){cout<<"子类B的析构函数执行中"<<endl;}
public:
virtual void print(void){cout<<"子类的print函数执行中"<<endl;}
};
int main(void)
{
A* a=new B;
a->print();
delete a;
return 0;
}
/*
2015年3月3日10:20:07
程序执行结果如下:
基类A的构造函数执行中
子类B的构造函数执行中
子类的print函数执行中
子类B的析构函数执行中
基类A的析构函数执行中
请按任意键继续. . .
*/
#include <iostream>
using namespace std;
class A
{
public:
A(){cout<<"基类A的构造函数执行中"<<endl;}
//virtual A()=NULL; //error 构造函数不能为虚函数
virtual ~A()=NULL; //析构函数是虚函数必须提供纯虚析构函数的定义
public:
virtual void print(void){cout<<"基类的print函数执行中"<<endl;}
};
A::~A()
{
//析构函数是虚函数必须提供纯虚析构函数的定义
cout<<"测试下"<<endl;
return;
}
class B:public A
{
public:
B(){cout<<"子类B的构造函数执行中"<<endl;}
~B(){cout<<"子类B的析构函数执行中"<<endl;}
public:
virtual void print(void){cout<<"子类的print函数执行中"<<endl;}
};
int main(void)
{
A* a=new B;
a->print();
delete a;
return 0;
}
/*
2015年3月3日10:21:04
程序执行结果如下:
基类A的构造函数执行中
子类B的构造函数执行中
子类的print函数执行中
子类B的析构函数执行中
测试下
请按任意键继续. . .
*/
#include <iostream>
#include <windows.h>
using namespace std;
class A
{
public:
void foo(){printf("基类A的foo函数被调用n");} //1
virtual void fun(){printf("基类A的fun函数被调用n");} //2
};
class B :public A
{
public:
void foo(){printf("子类B的foo函数被调用n");} //3
virtual void fun(){printf("子类B的fun函数被调用n");} //4
};
int main(void)
{
A a;
B b;
A *p = &a;
p->foo(); //1
p->fun(); //2
p = &b;
p->foo(); //1
p->fun(); //4
B *ptr = (B *)&a;
ptr->foo();
ptr->fun();
return 0;
}
/*
2015年3月3日10:21:44
程序执行结果如下:
基类A的foo函数被调用
基类A的fun函数被调用
基类A的foo函数被调用
子类B的fun函数被调用
子类B的foo函数被调用
基类A的fun函数被调用
请按任意键继续. . .
*/
/*
第一个p->foo()和p->fuu()都很好理解,本身是基类指针,指向的又是基类对象,调用的都是基类本身的函数,因此输出结果就是1、2。
第二个p->foo(),p是类A的指针,调用基类的foo成员函数,fun是虚函数,调用子类的fun,因此输出结果就是1、4。
问这两调用的输出结果。这是一个用子类的指针去指向一个强制转换为子类地址的基类对象。结果,这两句调用的输出结果是3,2。
并不是很理解这种用法,从原理上来解释,由于B是子类指针,虽然被赋予了基类对象地址,但是ptr->foo()在调用的时候,由于地址偏移量固定,偏移量是子类对象的偏移量,于是即使在指向了一个基类对象的情况下,还是调用到了子类的函数,虽然可能从始到终都没有子类对象的实例化出现。
而ptr->fun()的调用,可能还是因为C++多态性的原因,由于指向的是一个基类对象,通过虚函数列表的引用,找到了基类中fun()函数的地址,因此调用了基类的函数。由此可见多态性的强大,可以适应各种变化,不论指针是基类的还是子类的,都能找到正确的实现方法。
*/
#include <iostream>
#include <windows.h>
using namespace std;
class Base
{
public:
virtual void f(float x){cout<<"Base::f(float)"<< x <<endl;}
void g(float x){cout<<"Base::g(float)"<< x <<endl;}
void h(float x){cout<<"Base::h(float)"<< x <<endl;}
};
class Derived : public Base
{
public:
virtual void f(float x){cout<<"Derived::f(float)"<< x <<endl;} //多态、覆盖
void g(int x){cout<<"Derived::g(int)"<< x <<endl;} //隐藏
void h(float x){cout<<"Derived::h(float)"<< x <<endl;} //隐藏
};
int main(void)
{
Derived d;
Base *pb = &d;
pb->f(3.14f);
pb->g(3.14f);
pb->h(3.14f);
//子类的默认调用子类的
Derived *pd = &d;
pd->f(3.14f);
pd->g(3.14f);
pd->h(3.14f);
return 0;
}
/*
2015年3月3日10:22:23
程序之间结果如下:
Derived::f(float)3.14
Base::g(float)3.14
Base::h(float)3.14
Derived::f(float)3.14
Derived::g(int)3
Derived::h(float)3.14
请按任意键继续. . .
*/
/*
令人迷惑的隐藏规则
本来仅仅区别重载与覆盖并不算困难,但是C++的隐藏规则使问题复杂性陡然增加。
这里“隐藏”是指派生类的函数屏蔽了与其同名的基类函数,规则如下:
(1)如果派生类的函数与基类的函数同名,但是参数不同。此时,不论有无virtual
关键字,基类的函数将被隐藏(注意别与重载混淆)。
(2)如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有virtual
关键字。此时,基类的函数被隐藏(注意别与覆盖混淆)。
上面的程序中:
(1)函数Derived::f(float)覆盖了Base::f(float)。
(2)函数Derived::g(int)隐藏了Base::g(float),而不是重载。
(3)函数Derived::h(float)隐藏了Base::h(float),而不是覆盖。
*/
#include <iostream>
#include <windows.h>
#include <tchar.h>
using namespace std;
//关键字virtual 的作用就是在当使用基类的指针的时候,使子类中与基类同名的成员在适当的时候被调用
class A
{
protected:
int x, y;
public:
void set_values (int a, int b) {x=a;y=b;}
virtual int area (void) { return (0); }
};
class B: public A
{
public:
int area (void) { return (x * y); }
};
class C: public A
{
public:
int area (void) {return (x * y / 2);}
};
int main(void)
{
B b;
C c;
A a;
A * p1 = &b;
A * p2 = &c;
A * p3 = &a;
p1->set_values (4,5);
p2->set_values (4,5);
p3->set_values (4,5);
cout << p1->area() << endl;
cout << p2->area() << endl;
cout << p3->area() << endl;
return 0;
}
/*
2015年3月3日10:23:04
程序执行结果如下:
20
10
0
请按任意键继续. . .
*/
#include <iostream>
#include <windows.h>
#include <tchar.h>
using namespace std;
class A
{
protected:
int x, y;
public:
void set_values (int a, int b) {x=a;y=b;}
virtual int area (void) =0;
};
class B: public A
{
public:
int area (void) { return (x * y);}
};
class C: public A
{
public:
int area (void) {return (x * y / 2);}
};
int main(void)
{
B b;
C c;
A * p1 = &b;
A * p2 = &c;
p1->set_values (4,5);
p2->set_values (4,5);
cout << p1->area() << endl;
cout << p2->area() << endl;
return 0;
}
/*
2015年3月3日10:23:37
程序执行结果如下:
20
10
请按任意键继续. . .
*/
#include <iostream>
#include <windows.h>
#include <tchar.h>
using namespace std;
class A
{
protected:
int x, y;
public:
void set_values (int a, int b) {x=a;y=b;}
virtual int area (void) =0;
void printarea (void) {cout << this->area() << endl;}
};
class B: public A
{
public:
int area (void) { return (x * y);}
};
class C: public A
{
public:
int area (void) {return (x * y / 2);}
};
int main(void)
{
B b;
C c;
A * p1 = &b;
A * p2 = &c;
p1->set_values (4,5);
p2->set_values (4,5);
p1->printarea();
p2->printarea();
return 0;
}
/*
2015年3月3日10:24:14
程序执行结果如下:
20
10
请按任意键继续. . .
*/
#include <iostream>
#include <windows.h>
#include <tchar.h>
using namespace std;
class A
{
public:
void set_values (int a, int b){x=a; y=b;}
protected:
int x, y;
};
class B: public A
{
public:
int area (void) {return (x * y);}
};
class C: public A
{
public:
int area (void){return (x * y / 2);}
};
int main(void)
{
B b;
C c;
A * p1 = &b;
A * p2 = &c;
p1->set_values (4,5);
p2->set_values (4,5);
//p1->area(); //error C2039: “area”: 不是“A”的成员
//b.set_values (4,5);
//c.set_values (4,5);
cout << b.area() << endl;
cout << c.area() << endl;
return 0;
}
/*
2015年3月3日10:24:51
程序执行结果如下:
20
10
请按任意键继续. . .
*/
C++纯虚函数
一、定义
纯虚函数是在基类中声明的虚函数,它在基类中没有定义,但要求任何派生类都要定义自己的实现方法。在基类中实现纯虚函数的方法是在函数原型后加“=0”
virtual void funtion()=0
二、引入原因
1、为了方便使用多态特性,我们常常需要在基类中定义虚拟函数。
2、在很多情况下,基类本身生成对象是不合情理的。例如,动物作为一个基类可以派生出老虎、孔雀等子类,但动物本身生成对象明显不合常理。
为了解决上述问题,引入了纯虚函数的概念,将函数定义为纯虚函数(方法:virtual ReturnType Function()= 0;),则编译器要求在派生类中必须予以重写以实现多态性。同时含有纯虚拟函数的类称为抽象类,它不能生成对象。这样就很好地解决了上述两个问题。
三、相似概念
1、多态性
指相同对象收到不同消息或不同对象收到相同消息时产生不同的实现动作。C++支持两种多态性:编译时多态性,运行时多态性。
a、编译时多态性:通过重载函数实现
b、运行时多态性:通过虚函数实现。
2、虚函数
虚函数是在基类中被声明为virtual,并在派生类中重新定义的成员函数,可实现成员函数的动态覆盖(Override)
3、抽象类
包含纯虚函数的类称为抽象类。由于抽象类包含了没有定义的纯虚函数,所以不能定义抽象类的对象。
最后
以上就是舒心麦片为你收集整理的virtual虚函数的全部内容,希望文章能够帮你解决virtual虚函数所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复