基类与派生类
继承是指当创建一个类时,您不需要重新编写新的数据成员和成员函数,只需指定新建的类继承了一个已有的类的成员即可。这个已有的类称为基类,新建的类称为派生类。
#include <iostream>
using namespace std;
class Shape
{
public:
void setWidth(int w)
{
width = w;
}
void setHeight(int h)
{
height = h;
}
protected:
int width;
int height;
};
class Rectangle: public Shape
{
public:
int getArea()
{
return (width * height);
}
};
int main(void)
{
Rectangle Rect;
Rect.setWidth(5);
Rect.setHeight(7);
cout << "Total area: " << Rect.getArea() << endl;
return 0;
}
其中Shape为基类,Rectangle为派生类,Rectangle继承了Shape的成员变量和成员函数。并且一个派生类可以继承多个基类,继承形式如下:
class<派生类名>:<继承方式><基类名>,<继承方式><基类名>
{
<派生类新定义成员>
};
其中继承方式有三中:
- public 表示公有继承;
- protected 表示保护继承;
- private 表示私有继承;
继承与访问控制权限
类的控制访问权限
在讲到继承的控制访问权限之前,我们需要先复习一下类的控制访问权限。C++的类有三种控制访问权限。
private能够被访问的情况:
- 该类中的函数
- 其友元函数访问。
不能访问情况:
不能被任何其他访问,该类的对象也不能访问。
protected可以被访问情况:
- 该类中的函数
- 子类的函数
- 其友元函数访问。
不能被该类的对象访问。
public可以被访问情况
- 该类中的函数
- 子类的函数
- 其友元函数访问。
- 类的对象
如表格所示
控制权限 | 成员函数 | 友元函数 | 子类成员函数 | 类的对象 |
---|---|---|---|---|
public | √ | √ | √ | √ |
protected | √ | √ | √ | × |
private | √ | √ | × | × |
继承类型
当一个类派生自基类,该基类可以被继承为 public、protected 或 private 几种类型。继承类型是通过上面讲解的访问修饰符来指定的。但是在继承过程中权限会发生相关改变。
- 公有继承(public):当一个类派生自公有基类时,基类的公有成员也是派生类的公有成员,基类的保护成员也是派生类的保护成员,基类的私有成员不能直接被派生类访问,但是可以通过调用基类的公有和保护成员来访问。
- 保护继承(protected): 当一个类派生自保护基类时,基类的公有和保护成员将成为派生类的保护成员。
- 私有继承(private):当一个类派生自私有基类时,基类的公有和保护成员将成为派生类的私有成员。
因此在实际使用中很少使用private或protected继承。
这里有一个盲区需要我们注意:class和struct的区别并不在于class既有成员函数又有成员变量,struct只有成员变量,而是访问权限的不同,class的默认访问权限为private,struct的默认访问权限为public
派生类的构造与析构函数
派生类会继承所有的数据,尽管有的数据无法访问,但是不会继承基类的构造函数和析构函数以及基类的友元函数。需要注意的一点是:基类必须定义一个虚析构函数,目的是为了内存泄露,当使用虚函数的时候,会告诉编译器具体执行哪一个析构函数。具体原因可以参开基类为什么要定义虚析构函数
当一个派生类对象被创建时:
- 首先在栈或者堆上面为整个对象分配空间
- 调用基类构造函数初始化基类数据
- 调用派生类构造函数
当一个子类对象被显示delete或者生命周期j结束
- 调用派生类析构函数
- 调用基类析构函数
- 回收该对象的内存资源
如下面一段代码
#include <iostream>
using namespace std;
class Shape
{
public:
Shape();
virtual ~Shape();
};
Shape::Shape()
{
cout<<"基类构造函数被调用"<<endl;
}
Shape::~Shape()
{
cout<<"基类析构函数被调用"<<endl;
}
class Rectangle:public Shape
{
public:
Rectangle();
~Rectangle();
int t=0;
private:
int *a;
};
Rectangle::Rectangle()
{
cout<<"派生类构造函数被调用"<<endl;
}
Rectangle::~Rectangle()
{
cout<<"派生类析构函数被调用"<<endl;
}
int main(void)
{
Shape *s=new Rectangle();
cout<<s.t<<endl;
delete s;
return 0;
}
如果Shape的析构函数不为虚函数,那么在delete的时候只会调用基类析构函数而不会调用派生类构造函数。
多继承初始化和析构顺序
当派生类为多继承时,构造函数初始化顺序还是先初始化基类,然后初始化继承类。初始化的顺序,基类构造函数执行顺序与继承声明顺序有关,构造函数刚好与析构函数相反。
#include <iostream>
using namespace std;
class A
{
public:
A();
virtual ~A();
};
A::A()
{
cout<<"Constructor A is called"<<endl;
}
A::~A()
{
cout<<"Destructor A is called"<<endl;
}
class B
{
public:
B();
virtual ~B();
};
B::B()
{
cout<<"Constructor B is called"<<endl;
}
B::~B()
{
cout<<"Destructor B is called"<<endl;
}
class C
{
public:
C();
virtual ~C();
};
C::C()
{
cout<<"Constructor C is called"<<endl;
}
C::~C()
{
cout<<"Destructor C is called"<<endl;
}
class D:public B,public C,public A
{
public:
D();
~D();
};
D::D()
{
cout<<"Constructor D is called"<<endl;
}
D::~D()
{
cout<<"Destructor D is called"<<endl;
}
int main(void)
{
A *a = new D();
delete a;
return 0;
}
继承的类型转换
我们可以将基类的指针或者引用绑定到派生类对象中基类的部分,这种转换叫做派生类到基类的类型转换。
例如
Shape *s=new Rectangle();
但是这个过程是不可逆的,所以不存在从基类到派生类的类型转换。原因在于基类对象只有积累本身的成员,而派生类有自身成员或者从其他基类继承了成员,如果用基类给派生类复制,那么派生类就能够访问不存在的数据。例如:
class A
{
public:
int x;
};
class B:public A
{
public:
int y;
};
B *b=new A();
cout<<b.y<<endl;
此时通过基类A生成的对象时没有成员变量y,因此b.y是在访问一个不存在的变量,编译器为了消除这种错误,所以不允许从基类向派生类的转换。
继承与静态成员 :如果基类中定义了一个静态成员,那么整个继承体系中只有一个静态成员。
防止继承:对于某一些类,我们希望它被继承,这是需要利用final防止其被继承
形如:
class Shape final {/* code */};//此时shape不能够被继承