目录
一、概念
类的继承简单来说就是类的复用,类似函数复用,减少了代码量。一般是公共部分作为父类,继承该父类的子类都是作为父类的拓展。如下,一个老师和学生都是一个人,那么我们就可以先设置一个人类,然后老师类和学生类继承人类就行了。
class person
{
protected:
string _name;
int _age;
};
class student : public person
{
private:
string _major;
};
class teacher : public person
{
private:
string _course;
};
二、继承方式
继承方式有三种,public、protected、private。每种继承代表权限收缩,例如原父类成员是public,如果是protected继承,该成员就会收缩成protected。private继承就会收缩成private。具体转换关系可看下图。
注意:
1.父类的private成员继承后不可见,不可见表示的是继承还是会继承,但是在子类中无法访问。所以一般父类不设置private成员,没什么意义。
2.class时默认的继承方式是private继承,struct时默认的继承方式是public继承,不过最好显示的写出继承方式。
3.实际中都使用public继承,另外两个继承没什么用。
三、切片
子类可以切片给父类,所谓切片就是子类切出父类的部分给父类,可以是直接赋值,也可以是以指针或者引用的方式。
person p1;
student s1;
p1 = s1;
person* pp = &s1;
person& rp = s1;
四、继承中的作用域
无论是父类还是子类都是独立的作用域,c++允许子类可以有与父类的同名成员,但是在子类中访问该成员依据就近原则先访问子类成员,要访问父类的成员需要加上作用域。
如果成员是函数,就算继承来的父类成员函数和子类的成员函数名相同也不会构成函数重载。
class person
{
public:
void print()
{
cout << "person.print()" << endl;
}
protected:
string _name;
int _age;
};
class student : public person
{
public:
void print()
{
cout << "sudent.print()" << endl;
}
private:
string major;
};
int main()
{
person p1;
student s1;
s1.print();
s1.person::print();
return 0;
}
五、子类中的默认成员函数
1.子类构造时会调用父类的默认构造函数,如果父类没有默认构造函数,则子类要在初始化列表中显示调用父类构造函数。
2.子类的拷贝构造和operator=赋值都需要调用对应的父类函数。
3.子类的析构函数结束后会自动调用一次父类的析构函数,因此子类析构函数中不用调用父类析构函数,否则会引起多次析构。在不需要释放堆上空间时,类的析构函数都不用写。
4.创建一个子类,构造和析构的顺序是父类构造-->子类构造-->子类析构-->父类析构。
class person
{
public:
person(string name,int age)
:_name(name)
,_age(age)
{}
person(const person& p)
:_name(p._name)
, _age(p._age)
{}
person& operator=(const person& p)
{
if (this != &p)
{
_name = p._name;
_age = p._age;
}
return *this;
}
void print()
{
cout << "person.print()" << endl;
}
protected:
string _name;
int _age;
};
class student : public person
{
public:
student(string name = " ",int age = 0,string major = " ")
:person(name,age)
,_major(major)
{}
student(const student& s)
:person(s)
,_major(s._major)
{}
student& operator=(const student& s)
{
if (this != &s)
{
person::operator=(s);
_major = s._major;
}
return *this;
}
void print()
{
cout << "sudent.print()" << endl;
}
private:
string _major;
};
六、继承中的友元和静态成员
1.友元关系不能继承,父类的友元无法访问子类的成员。
2.无论有多少个子类继承了当前父类,该父类中的静态成员只能有一个。
class person
{
protected:
string _name;
int _age;
public:
static int _id;
};
int person::_id = 1;
class student : public person
{
private:
string _major;
};
int main()
{
person p1;
student s1;
student s2;
cout << s1._id++ << endl;
cout << s2._id << endl;
cout << p1._id << endl;
cout << person::_id << endl;
return 0;
}
七、菱形继承
可以有一种继承关系如下图,我们称为菱形继承。
class A
{
public:
int _a = 0;
};
class B : public A
{
public:
int _b = 1;
};
class C : public A
{
public:
int _c = 2;
};class D : public B , public C
{
public:
int _d = 3;
};
int main()
{
D d;
cout << d.B::_a << endl;//B中继承的_a
cout << d.C::_a << endl;//C中继承的_a
return 0;
}
菱形继承会导致代码冗余,访问成员产生二义性。
创建一个D,其内存中存了两份的_a,分别是B类中的_a和C类中的_a。如果父类A的数据量非常大,这时代码冗余就会显得很严重。
八、虚继承
为了解决菱形继承会产生两份父类数据的问题,可采用虚继承。
在中间腰部类中的继承方式中加上virtual关键字。
class B : virtual public A
class C : virtual public A
这时候就会只有一个_a,无论去改变那个类域中的值,_a都是同一个。
int main()
{
D d;
cout << d._a << endl;
d._a = 1;
cout << d.B::_a << endl;
cout << d.C::_a << endl;
return 0;
}
这时的内存空间中原先存放_a的位置只放了一个地址,这个地址指向一个虚基表(每一个虚拟继承父类的子类都有一个虚基表),其中存放的相对地址,找到_a的值存放的位置。
总结
public继承是最常用的继承方式。
子类的内容给父类叫做切片,父类无法给子类。
每一个类都是一个作用域空间,子类访问父类成员需要指定域空间。
友元关系无法继承。
子类继承父类的static成员都一直是同一个。
复杂菱形继承一般不建议使用,会产生代码冗余,一定要使用就要用虚继承。