继承、隐藏、覆盖和重载的区别

由于本人才疏学浅,本文难免存在遗漏之处,欢迎大家留言指正,本人将感激不尽。

1.父子类继承关系: 子类复制父类全部成员

首先,理解父子类的继承关系是怎样发生的。在此基础上就很容易理解它们之间的关系和区别。

每一个类有它自己的成员变量和成员函数,是一个独立的空间整体。当子类继承父类时,会将父类的全部成员全部复制一份,作为子类的成员,但是,同时也会标记这些成员是从父类中继承的,与子类本身的成员,还是有区别的。这里认为将子类本身的成员存在子类域,从父类复制过来的存在父类域。

如下图,Childer类中存在两个域,子类域和父类域,相互之间互不干扰。

在这里插入图片描述

如下面代码所示,父类大小8Bytes,子类的大小为16Bytes。其中子类会继承来自父类的8字节成员变量,即使子类和父类都拥有f_b成员变量,子类仍然会从父类继承父类的f_b变量。

#include <iostream>
using namespace std;
class Father{
public:
    int f_a;
    int f_b;
};
class Childer:public Father{
public:
    int c_a;
    int f_b;
};
int main(){
    cout<<"sizeof childer:"<<sizeof(Childer)<<endl;   //-> 16
    cout<<"sizeof father:"<<sizeof(Father)<<endl;     //-> 8
    return 0;
}

输出为

sizeof childer:16
sizeof father:8

此外,若是将父类中的成员变量改为private,仅仅父类可访问,子类同样会继承父类的成员变量,子类同样可访问继承自父类的变量f_a、f_b,但是无法访问父类的成员Father::f_a,Father::f_b。

#include <iostream>
using namespace std;
class Father{
private:
    int f_a;
    int f_b;
};
class Childer:public Father{
public:
    int c_a;
    int f_b; //隐藏父类的f_b,要想访问父类的f_b,需要通过this->Father::f_b(但是由于private,仍然无法访问)
	void set(){
		f_b = 1;  //访问子类的f_b
	//	this->Father::f_b = 2; //无法访问父类的f_b,private修饰
	}
};
int main(){
	Childer c;
	c.f_b = 1; //可访问子类的f_b,public修饰
//	c.f_a = 2; //不可访问父类的f_a,private修饰
//	c.Father::f_b = 2; //不可访问父类的f_b,尽管public继承,但是父类中通过private修饰
	c.set();
    cout<<"sizeof childer:"<<sizeof(Childer)<<endl;   //-> 16
    cout<<"sizeof father:"<<sizeof(Father)<<endl;     //-> 8
}

输出仍为:

sizeof childer:16
sizeof father:8

所以由此可以看到,子类会继承父类的成员,即使成员被private修饰。但是父类成员被private修饰之后,子类中将无法访问父类的私有成员。

2.覆盖:虚函数,成员函数类型一摸一样,父类指针调用子类对象成员

覆盖只发生在有虚函数的情况下,且父子类成员函数类型必须一摸一样,即参数和返回类型都必须一致。子类对象调用时,会直接调用子类域中的成员函数,父类域中的该同名成员就像不存在一样,(可以显示调用)即父类该成员被子类成员覆盖。这里很多人会感觉疑惑,认为是隐藏,因为父类的成员函数依然存在,依然可以调用,只是优先调用子类的,也就是“隐藏”了。而“覆盖”两个字的意思,应该是一个将另一个替代了,也就是另一个不存在了。

#include <iostream>
using namespace std;
class Father{
public:
	virtual void eat(){
		cout << "father eating" << endl;
	}
};
class Son : public Father{
public:
	void eat() {
		cout << "son eating" << endl;
	}
};
int main(){
	Father *f = new Son;
	f->eat();  //多态,运行时识别调用子类的eat函数
	Son s;
	s.eat(); //调用子类的eat函数,父类的eat函数被覆盖
	s.Father::eat(); //显示地调用父类的eat函数
	return 0;
}

输出为

son eating
son eating
father eating

首先需明白一点,虚函数的提出,是为了实现多态。也就是说,虚函数的目的是为了,在用父类指针指向不同的子类对象时,调用虚函数,调用的是对应子类对象的成员函数,即可以自动识别具体子类对象。所以,上述例子中,直接用子类对象调用虚函数是没有意义的,一般情况也不会这样使用。

3.隐藏:子类对象优先考虑子类域自身成员(成员变量和成员函数)

隐藏发生的主要原因,就是当子类有父类的同名成员时,子类对象访问该成员时,会发生冲突。所以编译器的处理方式是,优先考虑子类域中的自身成员。

1、若子类以及父类中含有相同的成员变量,则通过子类的对象获取该成员变量时,优先获取子类的成员变量,即将父类的同名成员变量隐藏。

2、若父类与子类含有同名函数,且该函数不满足覆盖的要求(覆盖:父类中该函数有virtual修饰,同名,参数列表一样,返类型一样),则通过子类对象调用该函数时,将调用子类的成员函数,将父类的成员函数隐藏

#include <iostream>
using namespace std;
class Father{
public:
	virtual void eat(){
		cout << "father eating" << endl;
	}
};
class Son : public Father{
public:
	void eat(int i) {
		cout << "son eating" << endl;
	}
};
int main(){
	Father *f = new Son;
	f->eat();

	Son s;
//	s.eat(); 父类中的eat()被子类的eat(int)隐藏了,该句编译报错error: no matching function for call to ‘Son::eat()’
	s.Father::eat();
	return 0;
}

输出为:

father eating
father eating

即,子类对象访问某成员时,编译器首先在子类域中检索,如果在子类域中找到该成员,则检索结束,返回该成员进行访问。如果在子类域中找不到该成员,则去父类域中检索。如果父类域中存在,则返回该成员进行访问,如果父类域中也不存在,则编译错误,该成员无效。

父类域中的 eat() 被子类域中的该同名成员 eat(int) 隐藏,即访问时完全以为该成员不存在,如果想访问父类域中的该成员,只能通过显示调用的方式,即:s.Father::eat();

4.重载:相同域的同名不同参函数

重载必须是发生在同一个域中的两个同名不同形参之间的。如果一个在父类域一个在子类域,是不会存在重载的,属于隐藏的情况,如上例中的eat函数。调用时,只会在子类域中搜索,如果形参不符合,会认为没有该函数,而不会去父类域中搜索。

5.总结

继承:
子类继承父类的成员变量和成员函数,拷贝一份父类成员变量。若子类中拥有与父类同名的成员变量,则子类拥有两份同名的成员变量,若父类中的同名成员变量是私有的,则子类中只能访问其本身的成员变量,不能访问父类中的成员变量。

覆盖:
父类中存在虚函数,子类中存在同名函数,且参数列表和返回类型一样,则通过子类对象访问该函数时,将负载父类的该虚函数。
可以将父类类型的指针指向子类对象来实现运行时识别对象类型(多态)。

隐藏:
子类与父类用用相同的成员变量或子类与父类拥有同名函数(不满足覆盖的要求),通过子对象访问同名成员时,将隐藏父类的同名成员。

重载:
在同一个类内部定义了同名的函数(返回值类型不一样或者参数列表不一致)


参考

https://www.cnblogs.com/Lalafengchui/p/3994340.html