浅谈C++基类中成员函数的三种状态

我们知道,C++中的派生类继承了基类的所有数据成员以及函数:

对于基类数据成员的继承,体现在了占用内存上面。

而对于基类成员函数的继承,则不应该从占用内存的角度区去理解。应是继承对基类成员函数的调用权(子类可以调用父类的公有函数,这种行为体现了经典的面向对象编程,此子类函数或这种做法被称为Template Method,此处Method 套用了Java中的函数method)

根据是否需要在派生类中重新定义(override,覆写)基类的成员函数,可以决定基类成员函数的使用形式:

1)如果不希望derived class中重新定义基类的成员函数,则使用non-virtual函数

2)如果希望在derived class中对它进行重新定义

      a)如果基类中已经有对它的默认定义,则使用虚函数。

      b)如果基类中的函数因其抽象性而无法定义,则使用纯虚函数。此时所在的类称为抽象类。

从上面可以看出,在使用虚函数的时候,利用了类型兼容规则

类型兼容规则,是指在需要基类对象的任何地方,都可以使用公有派生类的对象来替代基类对象。所指的替代包括三种情况:

class Base{...}
class Devried:public Base{...}
Base b, *pb;
Devried d;

1)派生类的对象转化为基类的对象。

扫描二维码关注公众号,回复: 2234242 查看本文章

      b = d;

2)派生类的对象初始化基类对象的引用。

      Base &rb = d;

3)派生类对象的地址转换为指向基类的指针。

       pb = &d;

类型兼容规则的引入,使得基类及其公有派生类的对象,可以使用相同的函数对它们统一进行处理。因为当函数的形参为基类的对象(或引用、指针)时,实参可以是派生类的对象(或指针),而没有必要为每一个类设计一个单独的模块,大大提高了程序的效率。可以说,类型兼容规则是多态性的重要基础之一。

下面是一般虚函数的使用:

#include<iostream>
using namespace std;

class Base1
{
public:
	virtual void display() const;
}; 
void Base1::display()const
{
	cout<<"Base1::display()"<<endl;
}
class Base2:public Base1
{
public:
	void display() const;	//覆盖基类的虚函数 
}; 
void Base2::display()const
{
	cout<<"Base2::display()"<<endl;
}
class Derived:public Base2
{
public:
	void display() const;	//覆盖基类的虚函数 
};
void Derived::display() const
{
	cout<<"Derived::display()"<<endl;
}

void f(Base1* ptr)	//参数为基类对象的指针 
{
	ptr->display();	//只有通过基类的指针或引用调用虚函数时,才会发生动态绑定 
} 
int main()
{
	Base1 base1;
	Base2 base2;
	Derived derived;
	f(&base1);
	f(&base2);
	f(&derived);
    return 0;
}

输出结果如下:


一般虚函数中还有虚析构函数:

#include<iostream>
using namespace std;
class A
{
public:
	~A();
}; 
A::~A()
{
	cout<<"A destructor"<<endl;
}

class B:public A
{
public:
	B();
	~B();
private:
	int* p;	
};
B::B()
{
	p=new int(0);	//体现构造函数的作用 
}
B::~B()
{
	cout<<"B destructor"<<endl;
	delete p;	//体现虚构函数的作用 
}

void f(A* a)	//参数为基类对象的指针 
{
	delete a;	//删除基类 
}
int main()
{
	A* a = new B();	 //通过派生类的构造函数创建基类对象的指针,在java中常用于体现多态性 
	f(a);	//程序输出结果是: A destructor,说明通过基类指针删除派生类对象时,调用的是基类的析构函数,并没有调用派生类的析构函数
			//因此,程序只是调用了派生类的构造函数动态分配了数据成员的内存空间,但是这内存空间没有得到释放,而且对象消失后对象成员又不能被本程序继续使用
			//这就造成了内存泄漏,对于内存需求量较大,长期连续运行的程序来说,如果持续发生着这样的错误是很危险的,最终将导致因内存不足而引起程序终止
			//此时,将基类的析构函数声明为虚析构函数可以解决问题,派生类的析构函数将会被执行 
	return 0;
}

纯虚函数:

#include<iostream>
using namespace std;

class Base1	//带有纯虚函数的类是抽象类 
{
public:
	virtual void display() const=0;	//纯虚函数,不需要给出定义,为整个类族提供了通用的外部接口语义,派生类中再对同名函数进行具体实现 
}; 

class Base2:public Base1
{
public:
	void display() const;	//覆盖基类的虚函数 
}; 
void Base2::display()const
{
	cout<<"Base2::display()"<<endl;
}
class Derived:public Base2
{
public:
	void display() const;	//覆盖基类的虚函数 
};
void Derived::display() const
{
	cout<<"Derived::display()"<<endl;
}

void f(Base1* ptr)	//参数为基类对象的指针 
{
	ptr->display();	//只有通过基类的指针或引用调用虚函数时,才会发生动态绑定,注意 "->" 
} 
int main()
{
	Base2 base2;
	Derived derived;
	f(&base2);
	f(&derived);
    return 0;
}

输出结果为:


以上均体现了C++中类的多态性,这也是C++语言常盛不衰的经典之处。

既然提到了“虚”,那就By the way一下虚基类:(不体现多态性)

#include<iostream>
using namespace std;
class Base0
{
public:
	int var0;
	void fun0()
	{
		cout<<"Member of Base0"<<endl;
	} 
};
class Base1:virtual public Base0
{
public:
	int var1; 
};
class Base2:virtual public Base0
{
public:
	int var2; 
};

class Derived:public Base1,public Base2
{
public:
	int var;
	void fun()
	{
		cout<<"Member of Derived"<<endl;
	}
};
int main()
{
	Derived d;
	d.var0;        //直接访问虚基类的数据成员
	d.fun0();		//直接访问虚基类的函数成员
	return 0;        
}

程序输出:

Member of Base0
所以,被声明为虚基类的Base0的成员var0和fun0只有一份副本,而通过两条派生路径继承,更加节省了内存空间。

以上类族的相关类成员的UML图形表示如下:



如果没有声明虚基类,则是:


此时,若通过Derived的对象去访问fun0和var0(要避免造成二义性),则是:

main()
{
	Derived d;
	d.Base1::var0=1;
	d.Base1::fun0();
	d.Base2::var0=2;
	d.Base2::fun0();	
}


猜你喜欢

转载自blog.csdn.net/dyd850804/article/details/80558667