继承
继承的概念和定义
继承的概念
继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段。
它允许程序员在保持原有类特性的基础上,进行扩展,增加功能,这样产生新的类,称派生类。继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。以前我们接触的复用都是函数复用,继承是类设计层次的复用。
示例:
#include<iostream>
using namespace std;
class Animal
{
public:
void Print()
{
cout << "name:" << _name << endl;
cout << "age:" << _age << endl;
}
private:
int _name;
int _age;
};
//继承后父类的Animal的成员(成员函数+成员变量)都会变成子类的一部分。
//这里体现出了Dog和Cat复用了Animal的成员。
//监视可以查看cat和dog对象,可以看到变量的复用。调用Print可以看到成员函数的复用。
class Dog : public Animal
{
private:
int _dogid;
};
class Cat : public Animal
{
private:
int _catid;
};
int main()
{
Cat c;
Dog d;
c.Print();
d.Print();
system("pause");
return 0;
}
继承的定义
定义格式
下面我们看到Animal是父类,也称作基类。Cat是子类,也称作派生类。
继承基类成员访问方式
类成员/继承方式 | public继承 | protected继承 | private继承 |
基类的public成员 | 派生类的public成员 | 派生类的protected成员 | 派生类的private成员 |
基类的protected成员 | 派生类的protected成员 | 派生类的protected成员 | 派生类的private成员 |
基类的private成员 | 在派生类中不可见 | 在派生类中不可见 | 在派生类中不可见 |
总结:
- 基类private成员在派生类中无论用哪种方式继承都是不可见的。(被继承,但是不可访问)
- 保护成员限定符protected是因为继承才出现的。如果基类成员不想在类外直接访问,但需要在派生类中能访问,就定义为protected。
- 访问方式都是取其中最小的权限。
- class默认的继承方式是private,struct的默认继承方式是public。
- 最好显示的写出继承方式
基类和派生类对象赋值转换
派生类对象可以赋值给基类的对象、指针、引用
这里有个形象的说法叫切片或者切割。寓意把派生类中父类那部分切来赋值过去。
class Person
{
private:
string _name;
string _gender;
int _age;
};
class Student:public Person
{
public:
int _num;
};
int main()
{
Student s;
//子类对象可以赋值给父类对象
Person p1 = s;
//子类对象可以赋值给父类指针
Person* p2 = &s;
//子类对象可以赋值给父类引用
Person& p3 = s;
return 0;
}
基类对象不能赋值给派生类对象
class Person
{
private:
string _name;
string _gender;
int _age;
};
class Student:public Person
{
public:
int _num;
};
int main()
{
Student s;
Person p;
s = p;
}
运行结果
基类的指针可以通过强制类型转换赋值给派生类的指针
注意:
必须是基类的指针指向派生类对象时才是安全的
class Person
{
private:
string _name;
string _gender;
int _age;
};
class Student:public Person
{
public:
int _num;
};
int main()
{
Person* p;
Student* s=(Student*)p;
}
继承中的作用域
- 在继承体系中基类和派生类都有独立的作用域。
- 派生类和基类中有同名成员,派生类成员将屏蔽基类对同名成员的直接访问,这种情况叫隐藏(在子类成员函数中,可以使用 基类::基类成员 显示访问 )
- 需要注意的是如果成员函数的隐藏,只需要函数名相同就构成了隐藏。
注意:
继承体系里最好不要定义同名成员。
代码示例1
//Student的_num和Person的_num构成隐藏关系,非常容易混淆
class Person
{
protected:
string _name = "张三"; //姓名
int _num = 3202; //身份证号
};
class Student :public Person
{
public:
void Print(){
cout << "姓名: " << _name << endl;
cout << "身份证: " << Person::_num << endl;
cout << "学号: " << _num << endl;
}
protected:
int _num = 05; //学号
};
int main()
{
Student s1;
s1.Print();
system("pause");
return 0;
}
运行结果
代码示例2
//B中的fun和A中的fun不构成重载,因为不在同一作用域
//B中的fun和A中的fun构成隐藏,成员函数满足函数名相同就构成隐藏
class A
{
public:
void fun(){
cout << "fun()" << endl;
}
};
class B :public A
{
public:
void fun(int i){
A::fun();
cout << "fun(int i)-> " << i << endl;
}
};
int main()
{
B b;
b.fun(2010);
return 0;
}
运行结果
继承与静态成员
基类定义了static静态成员,那么整个继承里面只有一个这样的成员。无论派生出多少个子类,都只有一个static成员实例。
继承和友元
友元关系不能继承,也就是说基类友元不能访问派生类的保护和私有成员。
派生类的默认成员函数
1.构造函数
子类的默认构造函数自动调用父类的构造函数,完成父类成员的初始化。
class A
{
public:
A(int a = 1)
:_a(a)
{
cout << "A(int)" << endl;
}
protected:
int _a;
};
class B :public A
{
};
int main()
{
B b;
system("pause");
return 0;
}
运行结果
如果基类没有默认的构造函数,则必须在派生类构造函数的初始化列表阶段显示调用。
class A
{
public:
A(int a)
:_a(a)
{
cout << "A(int)" << endl;
}
protected:
int _a;
};
class B :public A
{
public:
B(int a, int b)
:_b(b)
{
cout << "B(int,int)" << endl;
}
protected:
int _b;
};
int main()
{
B b(10,20);
system("pause");
return 0;
}
运行结果
子类的构造函数必须调用父类的构造函数初始化父类的那一部分成员,父类构造在初始化列表中调用。
class A
{
public:
A(int a = 1)
:_a(a)
{
cout << "A(int)" << endl;
}
protected:
int _a;
};
class B :public A
{
public:
B(int a, int b)
:A(a)
,_b(b)
{
cout << "B(int,int)" << endl;
}
protected:
int _b;
};
int main()
{
B b(10,20);
system("pause");
return 0;
}
运行结果
2.拷贝构造函数
子类默认的拷贝构造自动调用父类的拷贝构造。
class A
{
public:
A(int a=1)
:_a(a)
{
cout << "A(int)" << endl;
}
A(const A& a)
:_a(a._a)
{
cout << "A(const A& a)" << endl;
}
protected:
int _a;
};
class B :public A
{
public:
B(int a, int b)
:A(a)
,_b(b)
{
cout << "B(int,int)" << endl;
}
protected:
int _b;
};
int main()
{
B b(1, 2);
B copy(b);
system("pause");
return 0;
}
运行结果
子类显示定义拷贝构造,默认调用父类的默认构造。
class A
{
public:
A(int a=1)
:_a(a)
{
cout << "A(int)" << endl;
}
A(const A& a)
:_a(a._a)
{
cout << "A(const A& a)" << endl;
}
protected:
int _a;
};
class B :public A
{
public:
B(int a, int b)
:A(a)
,_b(b)
{
cout << "B(int,int)" << endl;
}
B(const B& b)
:_b(b._b)
{
cout << "B(const B& a)" << endl;
}
protected:
int _b;
};
int main()
{
B b(1, 2);
B copy(b);
system("pause");
return 0;
}
运行结果
派生类的拷贝构造函数必须调用基类的拷贝构造函数完成基类的拷贝初始化。
class A
{
public:
A(int a=1)
:_a(a)
{
cout << "A(int)" << endl;
}
A(const A& a)
:_a(a._a)
{
cout << "A(const A& a)" << endl;
}
protected:
int _a;
};
class B :public A
{
public:
B(int a, int b)
:A(a)
,_b(b)
{
cout << "B(int,int)" << endl;
}
B(const B& b)
:A(b)
,_b(b._b)
{
cout << "B(const B& a)" << endl;
}
protected:
int _b;
};
int main()
{
B b(1, 2);
B copy(b);
system("pause");
return 0;
}
运行结果
3.赋值运算符重载函数
子类默认的赋值运算符重载会自动调用父类的赋值运算符重载。
class A
{
public:
A(int a=1)
:_a(a)
{
cout << "A(int)" << endl;
}
A(const A& a)
:_a(a._a)
{
cout << "A(const A& a)" << endl;
}
A& operator=(const A& a)
{
if (this != &a)
{
_a = a._a;
}
cout << "A& operator=" << endl;
return *this;
}
protected:
int _a;
};
class B :public A
{
public:
B(int a, int b)
:A(a)
,_b(b)
{
cout << "B(int,int)" << endl;
}
B(const B& b)
:A(b)
,_b(b._b)
{
cout << "B(const B& a)" << endl;
}
protected:
int _b;
};
int main()
{
B b(1, 2);
B copy(b);
b = copy;
system("pause");
return 0;
}
运行结果
子类如果显示定义了赋值运算符重载函数,不会自动调用父类的赋值运算符。
class A
{
public:
A(int a=1)
:_a(a)
{
cout << "A(int)" << endl;
}
A(const A& a)
:_a(a._a)
{
cout << "A(const A& a)" << endl;
}
A& operator=(const A& a)
{
if (this != &a)
{
_a = a._a;
}
cout << "A& operator=" << endl;
return *this;
}
protected:
int _a;
};
class B :public A
{
public:
B(int a, int b)
:A(a)
,_b(b)
{
cout << "B(int,int)" << endl;
}
B(const B& b)
:A(b)
,_b(b._b)
{
cout << "B(const B& a)" << endl;
}
B& operator=(const B& b)
{
if (this != &b)
{
_b = b._b;
}
cout << "B& operator=" << endl;
return *this;
}
protected:
int _b;
};
int main()
{
B b(1, 2);
B copy(b);
b = copy;
system("pause");
return 0;
}
运行结果
派生类的operator=必须要调用基类的operator=完成基类的赋值。
class A
{
public:
A(int a=1)
:_a(a)
{
cout << "A(int)" << endl;
}
A(const A& a)
:_a(a._a)
{
cout << "A(const A& a)" << endl;
}
A& operator=(const A& a)
{
if (this != &a)
{
_a = a._a;
}
cout << "A& operator=" << endl;
return *this;
}
protected:
int _a;
};
class B :public A
{
public:
B(int a, int b)
:A(a)
,_b(b)
{
cout << "B(int,int)" << endl;
}
B(const B& b)
:A(b)
,_b(b._b)
{
cout << "B(const B& a)" << endl;
}
B& operator=(const B& b)
{
if (this != &b)
{
A::operator=(b);
_b = b._b;
}
cout << "B& operator=" << endl;
return *this;
}
protected:
int _b;
};
int main()
{
B b(1, 2);
B copy(b);
b = copy;
system("pause");
return 0;
}
运行结果
4.析构函数
子类的析构会默认调用父类的析构。
class A
{
public:
A(int a=1)
:_a(a)
{
cout << "A(int)" << endl;
}
A(const A& a)
:_a(a._a)
{
cout << "A(const A& a)" << endl;
}
A& operator=(const A& a)
{
if (this != &a)
{
_a = a._a;
}
cout << "A& operator=" << endl;
return *this;
}
~A()
{
cout << "~A()" << endl;
}
protected:
int _a;
};
class B :public A
{
public:
B(int a, int b)
:A(a)
,_b(b)
{
cout << "B(int,int)" << endl;
}
B(const B& b)
:A(b)
,_b(b._b)
{
cout << "B(const B& a)" << endl;
}
B& operator=(const B& b)
{
if (this != &b)
{
A::operator=(b);
_b = b._b;
}
cout << "B& operator=" << endl;
return *this;
}
protected:
int _b;
};
void test()
{
B b(1, 2);
}
int main()
{
test();
system("pause");
return 0;
}
运行结果
子类显示定义析构,也会自动调用父类的析构。
class A
{
public:
A(int a=1)
:_a(a)
{
cout << "A(int)" << endl;
}
A(const A& a)
:_a(a._a)
{
cout << "A(const A& a)" << endl;
}
A& operator=(const A& a)
{
if (this != &a)
{
_a = a._a;
}
cout << "A& operator=" << endl;
return *this;
}
~A()
{
cout << "~A()" << endl;
}
protected:
int _a;
};
class B :public A
{
public:
B(int a, int b)
:A(a)
,_b(b)
{
cout << "B(int,int)" << endl;
}
B(const B& b)
:A(b)
,_b(b._b)
{
cout << "B(const B& a)" << endl;
}
B& operator=(const B& b)
{
if (this != &b)
{
A::operator=(b);
_b = b._b;
}
cout << "B& operator=" << endl;
return *this;
}
~B()
{
cout << "~B()" << endl;
}
protected:
int _b;
};
void test()
{
B b(1, 2);
}
int main()
{
test();
system("pause");
return 0;
}
运行结果
注意:
综上,编译器会在任何时候调用父类析构,我们不需要在子类中显示的调用析构。
5.析构与构造的调用顺序
子类对象初始化先调用父类构造函数再调用子构造函数,子对象析构清理先调用子类的析构函数再调用父类的析构函数。
class A
{
public:
A(int a=1)
:_a(a)
{
cout << "A(int)" << endl;
}
A(const A& a)
:_a(a._a)
{
cout << "A(const A& a)" << endl;
}
A& operator=(const A& a)
{
if (this != &a)
{
_a = a._a;
}
cout << "A& operator=" << endl;
return *this;
}
~A()
{
cout << "~A()" << endl;
}
protected:
int _a;
};
class B :public A
{
public:
B(int a, int b)
:A(a)
,_b(b)
{
cout << "B(int,int)" << endl;
}
B(const B& b)
:A(b)
,_b(b._b)
{
cout << "B(const B& a)" << endl;
}
B& operator=(const B& b)
{
if (this != &b)
{
A::operator=(b);
_b = b._b;
}
cout << "B& operator=" << endl;
return *this;
}
~B()
{
cout << "~B()" << endl;
}
protected:
int _b;
};
void test()
{
B b(1, 2);
}
int main()
{
test();
system("pause");
return 0;
}
运行结果
菱形继承和菱形虚拟继承
多继承
一个子类有两个或以上直接父类时称这个继承关系为多继承。
菱形继承
菱形继承是多继承的一种特殊情况。
菱形继承的问题
从下面的对象成员模型构造,可以看出菱形继承有数据冗余和二义性的问题。
class person
{
public:
string _name;
};
class student :public person
{
protected:
int _num;
};
class teacher :public person
{
protected:
int _id;
};
class assist :public student, public teacher
{
protected:
string _course;
};
int main()
{
assist a;
//会有二义性
a._name = "krystal";
//需要显示指定访问哪个父类的成员可以解决二义性问题,但是数据冗余问题无法解决
a.student::_name = "kkk";
a.teacher::_name = "rrr";
}
菱形虚拟继承
虚拟继承可以解决菱形继承的二义性和数据冗余的问题。
在Student和Teacher的继承Person时使用虚拟继承,即可解决问题。
class person
{
public:
string _name;
};
class student :virtual public person
{
protected:
int _num;
};
class teacher :virtual public person
{
protected:
int _id;
};
class assist :public student, public teacher
{
protected:
string _course;
};
int main()
{
assist a;
a._name = "krystal";
}
菱形虚拟继承的原理
不使用虚继承观察内存
struct a
{
int _a;
};
struct b :public a
{
int _b;
};
struct c :public a
{
int _c;
};
struct d :public b, public c
{
int _d;
};
void test()
{
d d1;
d1.b::_a = 1;
d1._b = 2;
d1.c::_a = 3;
d1._c = 4;
d1._d = 5;
}
int main()
{
test();
return 0;
}
运行代码看内存窗口
使用虚继承观察内存
struct a
{
int _a;
};
struct b :virtual public a
{
int _b;
};
struct c :virtual public a
{
int _c;
};
struct d :public b, public c
{
int _d;
};
void test()
{
d d1;
d1.b::_a = 1;
d1._b = 2;
d1.c::_a = 3;
d1._c = 4;
d1._d = 5;
}
int main()
{
test();
return 0;
}
虚继承之所以能解决二义性和数据冗余的问题,他们在原本需要存放数据的地方存放了一个虚基表指针,而真正的“_a”数据放在对象的最底下,而我们只要通过这个指针找到虚基表中存放的偏移量,就能找到我们实际要访问的变量“_a”。