目录
一、虚函数
1、什么是虚函数?
- 被 virtual 关键字修饰的成员函数称为虚函数。
- 在类中定义了虚函数就会有一个虚函数表(vftable),对象模型中就含有一个指向虚表的指针(__vfptr)。在定义对象时构造函数设置虚表指针指向虚函数表。
- 使用指针和引用调用虚函数,在编译只需要知道函数接口,运行时指向具体对象,才能关联具体对象的虚方法(通过虚函数指针查虚函数表得到具体对象中的虚方法)。
2、虚函数的作用
实现多态性,即通过基类访问派生类的函数。
3、虚函数的参数
虚函数的参数值是默认静态绑定的,在子类中 重新定义虚函数的时候,并没有重新定义继承而来的参数值;除非是在实际调用过程中传递了想用的参数。
4、虚函数代码示例
class Parent
{
public:
virtual void fn(int a = 10)//如果虚函数有值,虚函数的参数值是默认静态绑定
{
cout << "parent fn a=" << a<<endl;
}
};
class Child :public Parent
{
public:
virtual void fn(int b = 100)
{
cout << "child fn b=" << b << endl;
}
};
int main()
{
Child cc;
Parent* p = &cc;
//p->fn(200);//chlid fn b=200;
p->fn();//child fn b=10;产生了动态绑定,但是虚函数的参数是默认绑定
cc.fn();//child fn b=100;
}
二、虚析构函数
1、什么是虚析构函数?
- 派生类的析构函数会自动调用基类的析构函数。 只要基类的析构函数是虚函数,那么派生类的析构函数不论是否用virtual关键字声明,都自动成为虚析构函数。
- 一般来说,一个类如果定义了虚函数,则最好将析构函数也定义成虚函数。 析构函数可以是虚函数,但是构造函数不能是虚函数。(第4点有具体原因说明)
- 注意:类中没有虚函数,就不要把析构函数定义为虚。
在动态分配内存时所有C++的标准库函数都采用这种格式。
2、虚析构函数的作用
为了解决基类指针指向派生类对象,并用基类的指针删除派生类对象。
3、 构造函数
构造函数是类的一个特殊的成员函数 :
- 1) 当一个对象的生命周期结束时,系统会自动调用析构函数注销该对象并进行善后工作,对象自身也可以调用析构函数;
- 2)析构函数的善后工作是:释放对象在生命期内获得的资源(如动态分配的内存,内核资源);
- 3) 析构函数也用来执行对象即将被撤销之前的任何操作。
4、为什么构造函数不能是虚函数?
根据赋值兼容规则,可以用基类的指针指向派生类对象,如果使用基类型指针指向动态创建的派生类对象,由该基类指针撤销派生类对象,则必须将析构函数定义为虚函数,实现多态性,自动调用派生类析构函数,否则可能存在内存泄漏问题。
总结:在实现运行时的多态,无论其他程序员怎样调用析构函数都必须保证不出错,所以必须把析构函数定义为虚函数。
5、构造函数和虚析构函数的联系
- 如果构造函数可以定义为虚构造函数,使用指针调用虚构造函数,
- 如果编译器采用静态联编,构造函数就不能为虚函数。
- 如果采用动态联编,运行时指针指向具体对象,使用指针调用构造函数,相当于已经实例化的对象在调用构造函数,这是不容许的调用,对象的构造函数只执行一次。
6、为什么程序员不能调构造函数,但是可以调用析构函数?
原因:
- 虚函数调用只需要"部分的"信息,即只需要知道函数接口,而不需要对象的具体类型。但是构建一个对象,却必须知道具体的类型信息。
- 如果你调用一个虚构造函数,编译器无法知道你想构建是继承树上的哪种类型,所以构造函数不能为虚。
7、虚析构函数产生多态
多态特例——虚析构函数产生多态;
类有指针作为数据成员,必须要写析构函数,如果当前类被继承了,则析构函数写成virtual
为了实现多态,将子类的空间被合理的释放,防止内存泄露 。
class A {
public:
A()
{
cout << "A" << endl;
m_i = new int;
}
virtual ~A()
{
cout << "~A" << endl;
delete m_i;
}
private:
int* m_i;
};
class B :public A
{
public:
B()
{
cout << "B" << endl;
m_j = new int;
}
virtual ~B()
{
cout << "~B" << endl;
delete[]m_j;
}
private:
int* m_j;
};
void test(A* pb)
{
;
}
int main()
{
A* pb = new B;//new B类对象,先调用基类构造函数,再调用子类构造函数,
delete pb;//pb指向子类对象,但是pb是A类类型指针,所以只析构了A,没有调子类类型的析构函数,存在内存泄漏。
}
三、纯虚函数
1、定义:
纯虚函数(pure virtual function)是指没有具体实现的虚成员函数。它用于这样的情况:设计一个类型时,会遇到无法定义类型中虚函数的具体实现,其实现依赖于不同的派生类。
2、纯虚函数的一般格式
virtual返回类型函数名(参数表)=0;
“=0"表明程序员将不定义该虚函数实现,没有函数体,只有函数的声明;函数的声明是为了在虚函数表中保留一个位置。在虚函数()的基础上=0;“=0“体质上是将指向函数体的指针定义为nullptr。
3、例题理解
题目:
设计一个形状类——有矩形、圆形、三角形;分别计算三种图形的周长和面积;
class Shape
{
public:
virtual void Area() = 0;//纯虚函数就是在虚函数的基础上给个0
virtual void Girth() = 0;//相当于把函数指针指向空
};
class Rectangle :public Shape
{
public:
virtual void Area() { cout << "Rectangle area"<< m_length*m_width << endl; }
virtual void Girth(){ cout << "Rectangle girth " << 2*(m_length+m_width)<<endl; }
private:
int m_length;
int m_width;
};
class Circle :public Shape
{
public:
virtual void Area() { cout << "Circle area "<<3.14*m_ra*m_ra << endl; }
virtual void Girth() { cout << "Circle girth"<< 2*3.14*m_ra<< endl; }
private:
int m_ra;//圆的半径
};
class Triangle :public Shape
{
public:
virtual void Area() { cout << "Triangle area " << endl; }
virtual void Girth() { cout << "Triangle girth "<<3*m_length << endl; }
private:
int m_length;
};
void test(Shape* p)
{
p->Area();
p->Girth();
}
int main()
{
Shape* pf[3];
pf[0] = new Rectangle;
pf[1] = new Circle;
pf[2] = new Triangle;
for (int i = 0;i < 3;i++)
{
pf[i]-> Area();
pf[i]->Girth();
delete pf[i];
pf[i] = NULL;
}
/*Shape s;
s.Area();
s.Girth();*/
}
四、抽象类
1、抽象类的定义
- 含有纯虚函数的类是抽象类。
- 抽象类是一种特殊的类,它是为抽象的目而建立的,它处于继承层次结构的较上层。
- 抽象类不能实例化对象,因为纯虚函数没有实现部分,所以含有纯虚函数类型不能实例化对象;
注意:1)在定义纯虚函数时,不能定义虚函数的实现部分;
2)在没有重新定义这种纯虚函数之前,是不能调用这种函数的。
2、抽象类的主要作用
- 将相关的类型组织在一个继承层次结构中,抽象类为派生类型提供一个公共的根,相关的派生类型。
- 为了派生子类,作为当前类族最上面的基类出现,则在子类中必须要重写基类中的纯虚函数(具体类)。
- 如果在子类中没有实现纯虚函数,则子类也是抽象类。
如有错误,敬请指正。
您的收藏与点赞都是对我最大的鼓励和支持!