继承
Day7-2 继承-基类和派生类
一、继承
1、继承的基础
面向对象的四大基本特征:抽象、封装、继承、多态。
- 继承:从既有类(基类)产生新类(派生类)的过程。
- 基类(父类):已有的类。
- 派生类(子类):从基类继承并扩展的新类。
2、继承的定义
class 子类(派生类)
: public/protected/private 父类(基类) //类派生列表
{
//数据成员
//成员函数
};
3. 派生类的构造过程
- 吸收 基类的成员。
- 改造 基类的成员。
- 新增 自己的成员。
示例代码:
#include <iostream>
using namespace std;
class Base {
public:
Base() : _base(0) { cout << "Base()" << endl; }
~Base() { cout << "~Base()" << endl; }
private:
long _base;
};
class Derived : public Base {
public:
//如果派生类显式定义了构造函数,而基类没有显式定义构造函数
//则创建派生类的对象时,派生类相对应的构造函数会被自动调用,
//此时都自动调用了基类缺省的构造函数
Derived(long derived = 0) : _derived(derived) { cout << "Derived()" << endl; }
~Derived() { cout << "~Derived()" << endl; }
private:
long _derived;
};
int main()
{
//在创建派生类对象的时候,会先调用基类的构造函数,然后调用派生类的构造函数 ??-->
//上面的说法是典型的错误,正确的如下:
//在创建派生类对象的时候,会调用派生类自己的构造函数,但是为了完成从基类吸收过来的数据成员的初始化,所以才会调用基类的构造函数
Derived d1(10);
cout << "When creating a derived class object, the derived class's own constructor is called, "
<<"but in order to complete the initialization of the data members absorbed from the base class,"
<<" the base class's constructor is called" << endl;
return 0;
}
派生类对象的构造顺序:
- 先调用基类的构造函数。
- 然后调用派生类的构造函数。
4、继承的局限
以下内容不会被继承:
- 基类的构造函数和析构函数
- 基类的友元关系
- 基类的
operator new/delete
操作符
#include <string>
#include <iostream>
/*
从 Circle 中提取一些公有的成员到基类 Shape 中
*/
class Shape
{
private: // 如须让子类可见,应使用 protected
double _x;
double _y;
std::string _name;
public:
Shape() : Shape(0.0, 0.0) {}
Shape(double x, double y, const std::string& name = "")
: _x(x), _y(y), _name(name) {}
Shape(const std::string& name) : Shape(0.0, 0.0, name) {}
~Shape() = default;
double x() const { return _x; }
double y() const { return _y; }
const std::string& name() const { return _name; }
void setX(double v) { _x = v; }
void setY(double v) { _y = v; }
};
const double M_PI = 3.141592653;
/*
一般使用公有继承,私有继承很少,保护继承从没见过
final 关键字可以作用在类名后面,令该类成为一个不可以被继承的类,它不允许有子类
*/
class Circle final: public Shape
{
private:
double _radius;
public:
// 子类的构造函数会先调用基类的构造函数,再调用自己的
// 不管有没有显式调用基类的构造函数,编译器都会安插代码去调用基类的构造函数,这是完整性构造保证的
Circle() : Circle(0.0, 0.0, 0.0) {}
Circle(double x, double y, double radius, const std::string& name = "")
: Shape(x, y, name), _radius(radius) {} // 显式调用基类的构造函数,然后继续初始化自己的成员
explicit Circle(double radius) : Circle(0.0, 0.0, radius) {}
// 析构和构造顺序相反,先析构自己的成员,再调用基类的构造函数
~Circle() = default;
// 由于子类的构造函数会覆盖(隐藏)基类的构造函数
// 比如由于没有定义 Circle(const std::string& name) 导致无法使用传入名字的构造函数
// 可以使用 using 显式引入基类的构造函数,让所有基类的构造函数在子类可见
// 普通函数也存在同名覆盖的问题,同样可以使用 using 引入
// 但一般只用来引入构造函数,因为普通函数理论上应该通过继承,子类可以使用基类的普通函数
// 如果子类中存在与基类的同名非虚函数,导致看不见基类的函数而失去对该函数的继承性,这是设计问题!应该修改名字而避免冲突
using Shape::Shape;
double radius() const { return _radius; }
void setRadius(double v) { _radius = v; }
double area() const { return M_PI * _radius * _radius; }
};
5. 访问权限
继承方式 | 基类 public |
基类 protected |
基类 private |
---|---|---|---|
public |
public |
protected |
不可访问 |
protected |
protected |
protected |
不可访问 |
private |
private |
private |
不可访问 |
6. protected
与 private
继承的区别
protected
继承可以被后续子类继续继承,而private
继承只能继承一代。- 默认继承方式:类的继承方式默认是
private
。
7. 完整示例代码:
#include <iostream>
using std::cout;
using std::endl;
class Point
{
public:
Point(int ix = 0 , int iy = 0)
: _ix(ix)
, _iy(iy)
{
/*_ix = ix;
_iy = iy;*/
cout << "Point(int,int)" << endl;
}
~Point() {
cout << "~Point()" << endl;
}
//拷贝构造函数
Point(const Point& rhs)
: _ix(rhs._ix)
, _iy(rhs._iy)
{
cout << "Point(const Point&)" << endl;
}
//问题1:拷贝构造函数中的引用符号能不能去掉?
//问题2:拷贝构造函数中的const关键字能不能去掉?
//解答1:去掉引用符号,会导致无穷递归调用,直到栈溢出(满足拷贝构造函数的调用时机1:形参与实参结合)
//解答2:去掉const关键字,会导致无法传递右值,即无法传递临时对象
//左值和右值
int number = 10;
int& ref = number;//左值引用
//int& ref1 = 10;//错误,不能将右值赋值给左值引用
int&& ref2 = 10;//右值引用
const int& ref3 = 10;//常量左值引用
//赋值运算符函数
Point& operator=(const Point& rhs)
{
cout << "Point& operator=(const Point&)" << endl;
if (this != &rhs)
{
_ix = rhs._ix;
_iy = rhs._iy;
}
}
int getY() const
{
return _iy;
}
void print() const
{
cout << "(" << _ix
<< "," << _iy
<< ")" << endl;
}
protected:
int _ix;
private:
int _iy;
};
//不管以什么继承方式,基类中的私有成员都不能在派生类中被访问,其他的都可以被访问
//不管以什么继承方式,派生类对象只能访问公有继承中的共有成员
class Point3D : protected Point
{
public:
Point3D(int ix = 0,int iy = 0 ,int iz = 0)
:Point(ix ,iy)
,_iz(iz)
{
cout << "Point3D(int = 0,int = 0,int= 0)" << endl;
}
~Point3D()
{
cout << "~Point3D()" << endl;
}
void print() const
{
cout << "ix = " << _ix << endl
<< "iy = " << getY() << endl
<< "iz = " << _iz << endl;
}
private:
int _iz;
};
class Point4D : private Point3D
{
public:
Point4D()
{
}
~Point4D()
{
}
void display() const
{
cout << "ix = " << _ix << endl
<< "iy = " << getY() << endl
/*<< "iz = " << _iz << endl*/
<< "im = " << _im << endl;
}
private:
int _im;
};
void test() {
Point pt;//栈对象
cout << "pt = ";
pt.print();
Point3D pt3d(1, 2, 3);
pt3d.print();
}
int main(int argc, char* argv[])
{
test();
return 0;
}
二、派生类对象的构造
1. 构造顺序
- 基类的构造函数先执行。
- 派生类的构造函数后执行。
- 如果基类没有显式构造函数,派生类默认调用基类的无参构造函数。
2. 析构顺序
- 派生类的析构函数先执行。
- 基类的析构函数后执行。
总结:当创建派生类对象的时候,会调用派生类的构造函数,为了完成从基类吸收过来的数据成员的初始化,会调用基类的构造函数,而如果基类没有显示定义构造函数,就会调用基类的无参构造函数,而如果基类有显示定义构造函数,那么也会默认调用基类的无参构造函数,完成初始化,如果不在派生类的构造函数的初始化列表中显示调用基类有参构造函数,就会报错。
总结:当创建派生类对象的时候,不管基类与派生类有没有显示写出构造函数,肯定默认会调用无参的,有时无参又没有,所以最好在派生类构造函数的初始化列表中,显示写出基类构造函数就可以。
派生类构造函数的功能:(执行顺序)
1、完成对象所占整块内存的开辟,由系统在调用构造函数时自动完成。
2、调用基类的构造函数完成基类成员的初始化。
3、若派生类中含对象成员、const成员或引用成员,则必须在初始化表中完成其初始化。
4、派生类构造函数体执行
三、派生类对象的销毁
派生类对象销毁的时候,会执行派生类的析构函数,然后基类析构函数会自动调用。
析构函数执行顺序:
1、先调用派生类的析构函数
2、再调用派生类中成员对象的析构函数
3、最后调用普通基类的析构函数
四、多继承
1. 多继承基础
多继承:一个类可以继承多个基类。
示例代码:
#include <iostream>
using namespace std;
class A {
public:
A() { cout << "A()" << endl; }
~A() { cout << "~A()" << endl; }
};
class B {
public:
B() { cout << "B()" << endl; }
~B() { cout << "~B()" << endl; }
};
//对于多继承而言,每个基类前面都要加上继承方式,否则就是默认继承方式,也就是私有继承
//多继承下,基类构造函数的执行顺序与其在派生类构造函数中的初始化顺序没有关系
//只与基类被继承的顺序有关
class C : public A, public B {
public:
C() { cout << "C()" << endl; }
~C() { cout << "~C()" << endl; }
};
int main() {
C obj;
return 0;
}
输出:
A()
B()
C()
~C()
~B()
~A()
多继承的顺序:
- 构造时:按照继承顺序(从左到右)调用基类构造函数。
- 析构时:按照继承顺序的相反方向调用基类析构函数。
2. 多继承的二义性问题
如果多个基类有相同的成员,派生类会出现二义性。
解决方案:
- 作用域限定符
- 虚拟继承(
virtual
关键字)
示例:
class A {
public:
void show() { cout << "A::show()" << endl; }
};
class B {
public:
void show() { cout << "B::show()" << endl; }
};
class C : public A, public B {
};
int main() {
C obj;
// obj.show(); // 错误,二义性
//多继承的问题1:成员函数访问冲突
//解决方式:使用类型+作用域限定符::
obj.A::show(); // 解决方案1
obj.B::show(); // 解决方案1
//多继承的问题2:数据成员的存储二义性
//解决办法:让派生类通过虚拟继承的方法去继承基类
return 0;
}
**1、成员函数访问冲突 **
解决方案:使用类名 + 作用域限定符
2、数据成员的存储二义性
解决方案:虚拟继承。
#include <iostream>
using namespace std;
class A
{
public:
virtual void a()
{
cout << "virtual void A::a()" << endl;
}
virtual void b()
{
cout << "virtual void A::b()" << endl;
}
virtual void c()
{
cout << "virtual void A::c()" << endl;
}
private:
};
class B
{
public:
virtual void a()
{
cout << "virtual void B::a()" << endl;
}
virtual void b()
{
cout << "virtual void B::b()" << endl;
}
void c()
{
cout << "virtual void B::c()" << endl;
}
void d()
{
cout << "virtual void B::d()" << endl;
}
private:
};
class C
:public A
,public B
{
public:
virtual void a()
{
cout << "virtual void C::a()" << endl;
}
void c()
{
cout << "virtual void C::c()" << endl;
}
void d()
{
cout << "virtual void C::d()" << endl;
}
private:
};
void test()
{
cout << "sizeof(A) = " << sizeof(A) << endl;
cout << "sizeof(B) = " << sizeof(B) << endl;
cout << "sizeof(C) = " << sizeof(C) << endl;
C c;
A* pa = &c;
pa->a();
pa->b();
pa->c();
cout << endl;
B* pb = &c;
pb->a();
pb->b();
pb->c();
pb->d();
cout << endl;
C* pc = &c;
pc->a();
//二义性
//pc->b();//C::b 对“b”的访问不明确
pc->A::b();
pc->B::b();
pc->c();
pc->d();
cout << endl;
}
int main()
{
test();
return 0;
}