继承是面向对象程序设计使代码可以复用的最重要的手段,允许程序员在保持原有类特性的基础上进行扩展,增加功能,通过继承产生的类,称为派生类。 继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。 在继承之前我们接触的复用只是函数复用,而继承是设计层面的复用。
其用法如下:
#include
using namespace std;
class person
{
public:
void Print()
{
cout << "name:" << _name << endl;
cout << "age:" << _age << endl;
}
protected:
string _name = "";
int _age = 0;
};
class student : public person
{
protected:
int _id;
};
class teacher : public person
{
int _jobid;
};
//其中,student和teacher就属于派生类(子类),而person就是这两个类的基类
//我们可以通过监视窗口查看student和teacher对象就可以观察到变量的复用,调用print可以喊道成员函数的复用。
int main()
{
student s;
teacher t;
s.Print();
t.Print();
return 0;
}
从下面就可以看到person是父类,也称为基类,而student是子类,也称为派生类。
在c++中,有三种继承方式,分别是:public继承, protected继承,private继承。
而访问限定符也有三种,public访问,protected访问,private访问。
这两个一组合就导致了继承的机制极为复杂!
类成员/继承方式 | public继承 | protected继承 | private 继承 |
---|---|---|---|
基类的public成员 | 派生类的public成员 | 派生类的protected成员 | 派生类的private成员 |
基类的protected成员 | 派生类的protected成员 | 派生类的protected成员 | 派生类的private成员 |
基类的private成员 | 在派生类中不可见 | 在派生类中不可见 | 在派生类中不可见 |
总结:
min(成员在基类的访问限定符,继承方式)
,public > protected > private需要注意的是在赋值的时候并没有调用拷贝构造,而是直接将里面的内容拷贝过去。
这也很容易理解,如果能赋值过去,那么子类对象多的成员不会知道应该赋值成什么
class person
{
public:
void Print()
{
cout << "name:" << _name << endl;
cout << "age:" << _age << endl;
}
protected:
string _name = "";
int _age = 0;
};
class student : public person
{
protected:
string _name;
int _id = 0;
public:
void print()
{
cout << _id << endl;
}
};
int main()
{
//在这种情况下想要访问基类的print()函数,应该使用访问限定符
student s;
s.person::print();
//所以在设计继承时最好不要定义同名成员,否则会出现二义性问题
}
在学习c++类和对象的时候,我们知道对于类来说有六个我们不写也会自动生成的默认成员函数,那么在派生类中,这些默认成员函数是如何生成的呢?
友元关系不能继承,也就是说基类友元不能访问子类私有和保护成员
通俗一点理解,父亲的朋友不是你的朋友。
class student;
class person
{
friend void display(const person& p, const student& s);
public:
void Print()
{
cout << "name:" << _name << endl;
cout << "age:" << _age << endl;
}
protected:
string _name = "";
int _age = 0;
};
class student : public person
{
protected:
int _id = 0;
};
class teacher : public person
{
int _jobid = 0;
};
void display(const person& p, const student& s)
{
cout << p._name << endl;
cout << s._id << endl;
}
基类定义了static静态成员,则整个继承体系里面只有一个成员。无论派生出多少个子类,都只有一个static成员实例。
#include
using namespace std;
class person
{
public:
static int _num;
person() { ++_num; }
void Print()
{
cout << "name:" << _name << endl;
cout << "age:" << _age << endl;
}
protected:
string _name = "";
int _age = 0;
};
int person::_num = 0;
class student : public person
{
protected:
int _id = 0;
};
class teacher : public person
{
int _jobid = 0;
};
int main()
{
student s;
teacher t;
person p;
cout << person::_num << endl;
return 0;
}
**单继承:**一个子类只有一个直接父类时称这个继承关系为单继承。
多继承:一个子类友两个或以上直接父类时称这个继承关系为多继承(只要保证父类中都没有同名成员就不会二义性问题)。
**菱形继承:**菱形继承时多继承的一种特殊情况
这种继承方式存在的问题是:可能会存在数据冗余和二义性的问题!看下图:
class person
{
public:
string _name;
int _age;
};
class student: public person
{
protected:
int _id;
};
class teacher : public person
{
protected:
int _num;
};
class assistant : public student, public teacher
{
protected:
string _course;
};
int main()
{
assistant s;
cout << s._name << endl;
}
如果想要解决二义性问题,可以使用指定访问,如s.student::_name
,s.teacher::_name
,但是,这样的方法仍然不能解决数据冗余的问题,想要解决这个问题,就需要使用虚拟继承。
虚拟继承可以解决虚拟继承的二义性和数据冗余的问题。如上面的继承关系,在student和teacher继承person时使用虚拟继承(在继承体系的腰部),即可解决问题。
class person
{
public:
int _age = 0;
};
class student: virtual public person
{
public:
int _id = 0;
};
class teacher : virtual public person
{
public:
int _num = 0;
};
class assistant : public student, public teacher
{
public:
int _t = 0;
};
int main()
{
assistant s;
cout << s._age << endl;
return 0;
}
通过使用虚继承,就可以使得assistant对象中只存在一个person成员,但是虚拟继承是如何做到的呢?我们可以通过借助内存窗口观察对象模型得知。
通过分析可以看到,assistant对象将person对象的数据放到了对象组成的最下面单独出来,而本来student成员和teacher成员里面的person成员则变成了两个指针,而这两个指针指向的是什么呢?同样我们通过监视窗口观察一下。
通过观察我们就可以发现,这两个指针指向的地方存的下一个位置储着一个数据指向从该地方到虚继承成员的数据存储地址,存的是偏移量。通过测试,我们就知道了虚继承是如何解决数据冗余的问题的了。
虚拟继承中student和teacher对象模型中的这两个指针叫做虚基表指针,这两个表叫做虚基表。虚基表中存的是偏移量,通过偏移量找到下面的person。
下面是对菱形虚拟继承的原理解释:
- 继承允许你根据基类的实现来定义派生类的实现。这种通过生成派生类的复用称为白箱复用。术语“白箱”是相对可视性而言:在继承关系中,基类的大部分细节对派生类可见。继承一定程度的破坏了基类的封装,基类的改变对派生类的影响有可能很大,派生类和基类之间的依赖关系较强,耦合度很高。
- 对象组合是继承之外的另一种复用选择。新的更复杂的功能可以通过组装或者组合对象来获得。对象组合要求被组合的对象具有良好的接口。这种复用风格被称为黑箱复用,因为对象的内部细节是不可见的。对象只以“黑箱”的方式呈现。组合类之间的依赖关系较弱,耦合度第。优先使用组合有助于保持每个类的独立性。
- 实际中应尽量多的取用组合。组合的耦合度低,代码维护性好。不过继承也有其用处,并且多态也只能通过继承实现。简单来说,只有组合无法完成的任务我们才使用继承。
以上就是关于c++继承的主要内容了,如果大家对本内容还有什么疑惑或者博主哪里说法有误的话,欢迎大家在评论区指出!