标签:-- 纯虚函数 重载 rcp 返回值 为什么 作用域 派生 实现
多态分为静态多态和动态多态
虚函数实现原理:当在类中定义一个虚函数,则该类所实例化的对象的存储位置将会多一个vfptr指针,该指针指向一个虚函数表,虚函数表中存放的就是虚函数的入口地址
构造函数不能设置为虚函数,虚函数就是为实现C++的OOP编程中的多态性而设定的,而动态多态性的激活条件是:
==由上面动态多态性的激活条件可以知道,要使用虚合理函数,则必须先创建派生类的对象,而在创建派生类对象的过程中必将调用其基类的构造函数,而如果基类中的构造函数是虚函数,而此时派生类对象又没有创建,陷入了矛盾的境地,所以构造函数不能设置为虚函数,同时也是没有任何意义的一件事==
当基类中的数据成员申请了堆空间,同时该基类中也定义了析构函数用于销毁该空间和虚函数。如果该基类有一个派生类,该派生类中也申请了一块堆空间。这时我们如果new一个派生类中空间,把new表达式返回的派生类指针赋给一个基类指针。如果我们使用delete表达式来释放刚才申请的派生类空间,这时只会调用基类的析构函数,不会调用派生类的析构函数,因为已经把派生类的指针赋给了基类指针。这时我们应该把析构函数设置为虚函数,在调用派生类的析构函数的同时,也将会调用基类的析构函数,这时所有申请的资源都得到了释放。代码如下:
#include <string.h>
#include <iostream>
using std::cout;
using std::endl;
class Base
{
public:
Base(const char * base)
: _base(new char[strlen(base) + 1]())
{
strcpy(_base, base);
cout << "Base(const char * base)" << endl;
}
virtual void print(void) const
{
cout << "base = " << _base << endl;
}
~Base()
{
if (_base)
delete [] _base;
cout << "~Base()" << endl;
}
private:
char * _base;
};
class Derived
: public Base
{
public:
Derived(const char * base, const char * derived)
: Base(base)
, _derived(new char[strlen(base) + 1]())
{
strcpy(_derived, derived);
cout << "Derived(const char * base, const char * derived)" << endl;
}
void print(void) const
{
cout << "derived = " << _derived << endl;
}
~Derived()
{
if (_derived)
delete [] _derived;
cout << "~Derived()" << endl;
}
private:
char * _derived;
};
int main(void)
{
Base * base = new Derived("hello", "world");
base->print();
delete base;
return 0;
}
运行结果
Base(const char * base)
Derived(const char * base, const char * derived)
derived = world
~Base()
分析:对于虚函数的调用这里就不再分析,就是派生类将基类的虚函数给覆盖了,这时从基类对象调用基类中的虚函数,将会调用派生类的虚函数。这里分析析构函数,通过运行结果可知,在delete base时,基类的析构函数被调用了,但是派生类的析构函数没有被调用,也就是说,基类中申请的堆资源得到了释放,而派生类中的资源没有得到释放,这不是我们想要的结果,原因上面已经分析了。要解决这个问题,应该想到,在调用派生了析构函数时,基类的析构函数也会被自动调用,我们可以尝试调用派生类的析构函数,那么怎么调用派生类的析构函数呢?这里可以利用虚函数的覆盖特性,我们把基类的析构函数设置为virtual,这时delete base时将调用派生类的析构函数,达到了我们的目的,代码修改如下:
virtual
~Base()
{
if (_base)
delete [] _base;
cout << "~Base()" << endl;
}
运行结果:
Base(const char * base)
Derived(const char * base, const char * derived)
derived = world
~Derived()
~Base()
当基类析构函数设置为虚函数后,其派生类析构函数会自动成为虚析构函数,即使没有加上virtual关键字,如果接下来要调用析构函数,就会走虚函数的调用机制
#include <iostream>
using std::cout;
using std::endl;
class Base
{
public:
Base() = default;
Base(int base_val)
: _base_val(base_val)
{
cout << "Base(int base_val)" << endl;
}
void func1(void)
{
//this->display();
display();
}
void func2(void)
{
Base::display();
}
virtual void display(void) const
{
cout << "base_val = " << _base_val << endl;
}
private:
int _base_val;
};
class Derived
: public Base
{
public:
Derived(int base_val, int derived_val)
: Base(base_val)
, _derived_val(derived_val)
{
cout << "Derived(int base_val, int derived_val)" << endl;
}
void display(void) const
{
cout << "derived_val = " << _derived_val << endl;
}
private:
int _derived_val;
};
int main(void)
{
Base base(12);
base.func1();
Derived derived(34, 56);
derived.func1();
cout << "-----------" << endl;
base.func2();
derived.func2();
return 0;
}
运行结果:
Base(int base_val)
base_val = 12
Base(int base_val)
Derived(int base_val, int derived_val)
derived_val = 56
-----------
base_val = 12 base_val = 34
分析:由上面的代码可知,display()函数定义为了虚函数,同时在基类中的func1 和 func2 函数中通过两种方式来访问该虚函数; Base base(12); base.func1();对于这段代码很好理解,就是基本的创建对象,然后访问对象中的成员,只不过在该成员中访问了该对象的虚函数,但由于没有继承关系,所以和普通的访问没有区别;
Derived derived(34, 56);
derived.func1();
对于这段代码,由于 Derived类继承自Base类,在继承了基类中的 func1、func2和虚函数后,由于Derived类中重新定义了该虚函数,这时子类的同名函数会覆盖基类中的虚函数,所以这时func1调用的就是子类中重定义的虚函数;
base.func2();
derived.func2();
上面的代码通过运行结果可知,虽然是derived对象调用的func2函数,但是该函数内部通过作用域运算符调用了虚函数,这时的调用将会直接调用基类中的虚函数,而不会调用派生类中的虚函数
#include <iostream>
using std::cout;
using std::endl;
class Grandpa
{
public:
Grandpa(int grandpa_val)
: _grandpa_val(grandpa_val)
{
cout << "Grandpa(int grandpa_val)" << endl;
}
virtual
void func1(void)
{
cout << "Grandpa: func1()" << endl;
}
virtual
void func2(void)
{
cout << "Grandpa: func2()" << endl;
}
~Grandpa()
{
cout << "~Grandpa()" << endl;
}
private:
int _grandpa_val;
};
class Son
: public Grandpa
{
public:
Son(int grandpa_val, int son_val)
: Grandpa(grandpa_val)
, _son_val(son_val)
{
cout << "Son(int grandpa_val, int son_val)" << endl;
func1();
}
#if 1
void func1(void)
{
cout << "Son: func1()" << endl;
}
void func2(void)
{
cout << "Son: func2()" << endl;
}
#endif
~Son()
{
cout << "~Son()" << endl;
func2();
}
private:
int _son_val;
};
class Grandson
: public Son
{
public:
Grandson(int grandpa_val, int son_val, int grandson_val)
: Son(grandpa_val, son_val)
, _grandson_val(grandpa_val)
{
cout << "Grandson(int grandpa_val, int son_val, int grandson_val)" << endl;
}
void func1(void)
{
cout << "Grandson: func1()" << endl;
}
void func2(void)
{
cout << "Grandson: func2()" << endl;
}
~Grandson()
{
cout << "~Grandson()" << endl;
}
private:
int _grandson_val;
};
int main(void)
{
Grandson test(1, 2, 3);
//cout << "-------------/\n";
Grandpa & tt = test;
cout << "-------------/\n";
tt.func1();
return 0;
}
运行结果
Grandpa(int grandpa_val)
Son(int grandpa_val, int son_val)
Son: func1()
Grandson(int grandpa_val, int son_val, int grandson_val)
-------------/
Grandson: func1()
~Grandson()
~Son()
Son: func2()
~Grandpa()
由代码和运行结果可知,当我们在构造函数或者析构函数内部调用虚函数,虽然表面上看和动态联编没说区别,但是,在这两个函数体内调用虚函数,其实本质上调用的是自身类中的函数(不是虚函数),或者说没有从虚函数的机制进行调用,而是按照普通函数的机制进行调用(静态联编)
我们知道,如果一个基类中定义了虚函数,同时派生类中覆盖了该虚函数,这时实例化派生类时创建的派生类对象的存储空间当中将会多一个指向虚函数表的指针,使得能够表现为动态多态的性质,这里就对虚函数表的存在进行验证,代码如下:
#include <iostream>
using std::cout;
using std::endl;
class Base
{
public:
Base(long base_val)
: _base_val(base_val)
{
cout << "Base(int base_val)" << endl;
}
virtual
void func1(void) { cout << "Base::func1()" << endl; }
virtual
void func2(void) { cout << "Base::func2()" << endl; }
virtual
void func3(void) { cout << "Base::func3()" << endl; }
private:
long _base_val;
};
class Derived
: public Base
{
public:
Derived(long base_val, long derived_val)
: Base(base_val)
, _derived_val(derived_val)
{
cout << "Derived(long base_val, long derived_val)" << endl;
}
virtual
void func1(void) { cout << "Derived::func1()" << endl; }
virtual
void func2(void) { cout << "Derived::func2()" << endl; }
virtual
void func3(void) { cout << "Derived::func3()" << endl; }
private:
long _derived_val;
};
int main(void)
{
Base base(10);
cout << "sizeof(Base) = " << sizeof(Base) << endl;
long * p = (long *)&base;
cout << p[0] << endl;
cout << p[1] << endl;
typedef void (*Function)(void);
long * p2 = (long *)p[0];
Function f = (Function)p2[0];
//Function f = (Function)p[0][0];
f();
f = (Function)p2[1];
f();
f = (Function)p2[2];
f();
return 0;
}
运行结果
Base(int base_val)
sizeof(Base) = 16
94272637283664
10
Base::func1()
Base::func2()
Base::func3()
编译和运行环境是gcc version 7.4.0 (Ubuntu 7.4.0-1ubuntu1~18.04.1) ,所以系统的是64位的,所以在指针的大小是8个字节(64位),所以为了方便验证,这里用long类型的来存储数据。我们知道,对象base的在栈中的大小是16个字节,其中8个字节的long类型数据,8个字节的指针,指针也就是vfptr指针,指向虚函数表的,这里就是验证虚函数表是否真的存在。对对象base取地址,强制转换为long类型,看 p[1]数据,就是我们一开始初始化base对象时赋的值, p[0] 则是一个指针,指向虚函数表,因为指针的大小是8个字节,所以我们再一次把 p[0]强制转换为long* 类型,从程序中我们可以看出来,p[0] 中共有3个指针,也就是 p2中的数据,因为 p2中的数据是8个字节的指针,但编译器无法识别这个地址数据,所以我们把该数据强制转换为指针函数,这样就可以调用虚函数了。从而验证了虚函数表哦的存在性
#include <math.h>
#include <iostream>
using std::cout;
using std::endl;
class Figure //定义一个抽象基类
{
public:
virtual void display(void) const = 0; //定义纯虚函数
virtual double area(void) const = 0;
};
void display(Figure & figure)
{
figure.display();
cout << ", the area is " << figure.area() << endl;
}
class Circle
: public Figure
{
public:
Circle(double radius)
: _radius(radius)
{}
void display(void) const
{
cout << "I am Circle";
}
double area(void) const
{
return 3.14 * _radius * _radius;
}
private:
double _radius;
};
class Rectangle
: public Figure
{
public:
Rectangle(double length, double width)
: _length(length)
, _width(width)
{}
void display(void) const
{
cout << "I am Rectangle";
}
double area(void) const
{
return _length * _width;
}
private:
double _length;
double _width;
};
class Triangle
: public Figure
{
public:
Triangle(double a, double b, double c)
: _a(a)
, _b(b)
, _c(c)
{}
void display(void) const
{
cout << "I am Triangle";
}
double area(void) const
{
double p = (_a + _b + _c)/2;
return sqrt(p * (p - _a) * (p - _b) * (p - _c));
}
private:
double _a;
double _b;
double _c;
};
int main(void)
{
Circle circle(10);
Rectangle rec(10, 12);
Triangle triangle(3, 4, 5);
display(circle);
display(rec);
display(triangle);
return 0;
}
运行结果:
I am Circle, the area is 314
I am Rectangle, the area is 120
I am Triangle, the area is 6
分析:从上面的代码可以知道,Figure为抽象基类,只定义向外提供的各种接口,而实现则由该基类的派生类来完成,在main函数中也可以看出来,调用同一个函数,但结果却是不一样的。这种抽象基类遵循的原则是:对修改关闭(不需要修改原来的代码),对扩展开放。
另:对于这种抽象基类应该注意的是,只要基类中有定义的纯虚函数,则其派生类中必须对基类中的所有纯虚函数给予实现,只要有一个纯虚函数没有实现,则该派生类也不能创建对象。
上面是按照定义纯虚函数来实现抽象类,这里还有另外一种抽象类,就是将基类的构造函数设置为protected,这时的基类是不能实例化对象的,要调用基类的构造函数,通过该基类派生出子类,实例化该子类,通过该对象来调用基类中可以访问的成员,代码如下:
#include <iostream>
using std::cout;
using std::endl;
class Base
{
public:
virtual void print(void)
{
cout << "I am Base_class" << endl;
cout << "base_val = " << _base_val << endl;
}
protected: //把基类的构造函数定义为protected性质,这时外部非派生类将不能对该类进行实例化,只能通过该类的派生来调用该基类的构造函数
Base(int base_val)
: _base_val(base_val)
{
cout << "Base(int base_val)" << endl;
}
private:
int _base_val;
};
class Inherit_cls
: public Base
{
public:
Inherit_cls(int base, int inherit_val) //通过派生类中的构造函数来调用基类中的构造函数
: Base(base)
, _inherit_val(inherit_val)
{
cout << "Inherit_cls(int base, int inherit_val) " << endl;
}
void print(void)
{
cout << "I am Inherit_cls" << endl;
cout << "inherit_val = " << _inherit_val << endl;
}
private:
int _inherit_val;
};
int main(void)
{
Inherit_cls test(12, 34);
Base & base = test;
base.print();
return 0;
}
分析:上面的代码中是C++语言的动态多态性,但是把基类中的构造函数设置为了protected,这时是无法通过该基类来创建基类对象,只能通过基类的派生类创建对象,这种抽象类的功能相比较前一种而言弱了很多,对于抽象类,应该使用纯虚函数来实现。
#include <iostream>
using std::cout;
using std::endl;
class A
{
public:
virtual
void f(void) { cout << "A::f()" << endl; }
virtual
void g(void) { cout << "A::g()" << endl; }
virtual
void h(void) { cout << "A::h()" << endl; }
};
class B
{
public:
virtual
void f(void) { cout << "B::f()" << endl; }
virtual
void g(void) { cout << "B::g()" << endl; }
void h(void) { cout << "B::h()" << endl; }
void j(void) { cout << "B::j()" << endl; }
};
class C
: public A
, public B
{
public:
virtual
void f(void) { cout << "C::f()" << endl; }
void h(void) { cout << "C::g()" << endl; }
void j(void) { cout << "C::j()" << endl; }
};
int main(void)
{
C c;
cout << "sizeof(A) = " << sizeof(A) << endl;
A & a = c;
a.f();
a.g();
a.h();
cout << "-----------------" << endl;
B & b = c;
b.f();
b.g();
b.h();
b.j();
c.f();
//c.g();
c.h();
c.j();
return 0;
}
运行结果
sizeof(A) = 8
C::f()
A::g()
C::g()
/-----------------
C::f()
B::g()
B::h()
B::j()
/---------------
C::f()
C::g()
C::j()
由上面的代码可知,这里的多重继承虚函数指的就是C多重继承自A,B两个类;同时C也继承了A,B的虚函数表,如果C类中有定义覆盖A,B中的虚函数,则虚函数表中的虚函数将发生变化,将原来基类中的虚函数覆盖为派生类中的虚函数,这就表现了动态多态的性质。对于此类情况,把各种类的虚函数继承关系画出来,就一目了然。
标签:-- 纯虚函数 重载 rcp 返回值 为什么 作用域 派生 实现
原文地址:https://www.cnblogs.com/dengxinlong/p/11299115.html