写在前面
C++面向对象的思想,之前总结到,主要包括封装、抽象、继承和多态。
继承是完成多态的基础,也是C++中简化代码的神兵利器。
继承的基本概念
1、一个类A可以继承另一个类B,那么我们称类B为基类(父类),类A为派生类(子类)。
2、子类从父类继承了所有成员,除了构造函数、析构函数、赋值运算符重载函数。(还是要自己构造自己和“自杀”滴)。
3、子类的成员分为两部分: 继承,扩展
4、虽然父类的私有成员被子类继承,但子类不能直接访问父类的私有成员。(朋友可以用,儿子不可以哈哈哈,可以结合之前的友元类)可以通过继承自父类的公有成员函数来访问。
5、子类可以自己实现与父类成员函数原型相同(函数名,参数列表)的成员函数,重写或覆盖(overwrite)。
6、当通过父类对象调用被覆盖的函数时,父类版本的函数被调用,当通过子类对象调用覆盖父类的函数时,子类版本被调用。
7、在子类中调用父类中被覆盖的版本时,可以用子类对象名.父类名::函数名
。
注意区分复合类和继承,复合类是组合,类似结构体嵌套。
#include <iostream>
using namespace std;
class Base{
private:
int b_number;
public:
Base(int i);
void print() const;
int ger_number() const;
};
Base::Base(int i) : b_number (i) {}
int Base::ger_number() const {
return b_number;
}
void Base::print() const {
cout<<b_number<<endl;
}
class Derive : public Base{
private:
int d_number;
public:
Derive(int i,int j);
void print() const ;
int get_number() const;
};
Derive::Derive(int i,int j) :Base(i),d_number(j) {}
void Derive::print() const {
cout<<d_number<<endl;
}
int Derive::get_number() const {
return d_number;
}
int main(){
Base B(9);
B.print();
Derive D(1,2);
D.print();
D.Base::print();//打印的是父类的值
return 0;
}
打印如下
注意看,子类在实例化一个对象调用构造函数时,需要构造一个父类,在参数列表中调用父类的构造函数。和组合类相似。
保护成员protected
子类继承之后,依旧无法直接访问父类的私有成员,需要继承自父类的成员函数进行访问,如果父类有protected成员,子类就可以直接访问。
子类访问父类的私有成员的两种方式:
1、通过父类的公有成员函数
2、访问protected成员
继承的使用
使用情况:
1、两个类之间有自然的继承关系,即一个类是另一个类的特例
2、实现代码重用。一个类可能需要使用其他类中定义的成员,此时可以定义一个派生类继承自该类,这样就不必重复编写代码。
类之间的两种交互关系:
1、组合:一个类是另一个类的成员变量,一个类拥有另一个类
2、继承:一个类是另一个类的特例
虚函数
1、有了虚函数,无需向下转型,就可以正确的使用父类的指针和引用,调用子类的函数
2、虚函数的目的:期望父类指针、引用,不管指向父类还是子类,在调用override函数时,可以反映真实情况(派生类重写继承来的函数后,调用父类的成员函数的方法另一种,显式调用)
3、调用虚函数时,到底调用哪个版本,是根据指针或引用实际所指向的对象的类型来确定,而不是调用者本身类型来确定。指向父类就调用父类,子类就调用子类。
#include <iostream>
using namespace std;
class Thing{
public:
Thing();
~Thing();
virtual void print() const;
private:
};
Thing::Thing() {}
Thing::~Thing() {
cout<<"Thing desstructor"<<endl;
}
void Thing::print() const{
cout<<"I am a Thing"<<endl;
}
class Book : public Thing{
public:
Book();
~Book();
void print() const;
private:
};
Book::Book() {}
Book::~Book() {
cout<<"Book destructor"<<endl;
}
void Book::print() const {
cout<<"I am a Book"<<endl;
}
int main() {
Thing t;
Book b;
t.print();b.print();
//声明一个父类指针
Thing* array[2];
array[0]=&t;
array[1]=&b;
array[0]->print();
array[1]->print();
//析构时,子类先调用自己的析构函数,再调用父类的析构函数,目的是为了析构继承来的成员
return 0;
}
虚函数实现了利用父类指针操作子类的方法,和多态结合将是C++面向对象的利器。如上所示,利用父类指针或引用,结合虚函数,可以明确调用的是父类还是子类的方法。在子类进行析构时,会先调用子类的析构函数,再调用父类的析构函数,释放继承得到的成员。
虚析构函数
如果一个类有一个子类,则这个类(父类)的析构函数必然是虚函数,即虚析构。
如果父类的析构不是虚函数,则要删除一个指向子类对象的父类指针时,将调用父类版本的析构函数,子类只释放了来自于父类的那部分成员变量,自己扩展的成员没有被释放,造成内存泄露问题。
#include <iostream>
using namespace std;
class Base{
public:
Base();
virtual ~Base();
};
Base::Base() {}
Base::~Base() {
cout<<"Base destructor"<<endl;
}
class Derived : public Base{
public:
Derived();
~Derived();
};
Derived::Derived() {}
Derived::~Derived() {
cout<<"Derived destructor"<<endl;
}
int main() {
Base *p=new Derived();//声明一个子类对象在堆区,即自己开辟,父类指针指向
delete p;//操作父类指针,析构函数没有虚化,只调用父类析构函数,子类只释放了继承来的成员
return 0;
}
三大继承
公有继承,派生类可以访问基类的公有成员和保护成员。
私有继承,只有类能使用基类的成员,实例化对象不能使用(可以用组合类替代,更好,进行限定)
即编译器在确定派生类能否访问基类的公有或保护成员时,考虑的是继承层次结构中最严格的访问限定符
保护继承:
与私有继承类似
1、表示has-a的关系
2、允许派生类能访问基类的所有公有和保护成员
3、不能通过派生类的实例对象对基类的成员进行访问
不同在于,子类的子类可以访问基类,而私有继承不允许这样
多重继承
多重继承是指一个子类有两个或者两个以上的父类。如下图。
这种情况,存在一个问题,如果两个父类有同名的成员,子类对象在调用时调用谁的?
另一种情况,两个父类有一个共同的祖先类。菱形继承。
此时还会有另一个问题,两个父类都会继承祖先类的成员,又将继承下来的成员派生给子类,那么子类中将有两份祖先类的成员。虽然合法,但是多数情况下,是没有必要的。
#include <iostream>
using namespace std;
class R{//祖先类
public:
R();
R(int v);
void print() const;
private:
int r;
};
R::R() {};
R::R(int v) { r=v; }
void R::print() const {cout<<r<<endl;}
class A : public R{
public:
A(int v1,int v2);
private:
int a;
};
A::A(int v1, int v2) : R(v1),a(v2) {}
class B: public R{
public:
B(int v1,int v2);
private:
int b;
};
B::B(int v1, int v2) : R(v1),b(v2) {}
class C : public A,public B{
public:
C(int v1,int v2,int v3);
private:
int c;
};
C::C(int v1, int v2, int v3, int v) : A(v1,v3),B(v2,v3),c(v3) {}
int main() {
R rr(10);
A aa(1,2);
B bb(3,4);
C cc(5,6,7);
rr.print();
aa.print();
bb.print();
//cc.print();//Non-static member 'print' found in multiple base-class
// subobjects of type 'R': class C -> class A -> class R
//cc中有两个print()
return 0;
}
为了解决二义性,使用虚继承。
虚继承
1、直接继承祖先的两个基类,在继承时,加上virtua,即途中的父类一和父类二。
2、通过多重继承而来的那个子类,在构造函数时,要调用祖先类的构造函数。孙子辈的派生类,直接继承祖先的成员,再继承两个父类各自扩展的成员。
第一步更改:将继承自祖先类的两个类在继承是加virtual
第二步:孙子类在构造函数中直接调用祖先类构造函数来继承祖先的成员。
多重继承引发的重复调用问题
#include <iostream>
using namespace std;
class R{//祖先类
public:
R();
R(int v);
void print() const;
private:
int r;
};
R::R() {};
R::R(int v) { r=v; }
void R::print() const {cout<<"r="<<r<<endl;}
class A :virtual public R{
public:
A(int v1,int v2);
void f(){
cout<<"a="<<a<<endl;
R::print();
}
private:
int a;
};
A::A(int v1, int v2) : R(v1),a(v2) {}
class B:virtual public R{
public:
B(int v1,int v2);
void f(){
cout<<"b="<<b<<endl;
R::print();
}
private:
int b;
};
B::B(int v1, int v2) : R(v1),b(v2) {}
class C : public A,public B{
public:
C(int v1,int v2,int v3);
void f(){
cout<<"c="<<c<<endl;
A::f();
B::f();
}
private:
int c;
};
C::C(int v1, int v2, int v3) : R(v1),A(v1,v3),B(v2,v3),c(v3) {}
int main() {
R rr(10);
A aa(1,2);
B bb(3,4);
C cc(5,6,7);
rr.print();
aa.print();
bb.print();
cc.print();//Non-static member 'print' found in multiple base-class
// subobjects of type 'R': class C -> class A -> class R
cc.f();//r打印了两次,在A中调用打印一次,B中打印一次
return 0;
}
解决办法:将每个类自己的工作,单独封装。
class R{//祖先类
private:
int r;
public:
R(int x=0):r(x){};
void f(){
cout<<"r:"<<r<<endl;
}
};
class A :virtual public R{
public:
A(int x,int y) :R(x),a(y){};
void f(){
cout<<"a="<<a<<endl;
R::f();//重复调用了
}
protected:
//只打印自己
void fA(){
cout<<"a="<<a<<endl;
}
private:
int a;
};
class B:virtual public R{
public:
B(int x,int y):R(x),b(y){};
void f(){
cout<<"b="<<b<<endl;
R::f();
}
private:
int b;
protected:
void fB(){
cout<<"b="<<b<<endl;
}
};
class C: public A, public B{
private:
int c;
public:
C(int x,int y,int z,int t):R(x),A(x,y),B(y,z),c(t){};
void f(){
R::f();
fA();
fB();
cout<<"c="<<c<<endl;
}
protected:
void fC(){
cout<<"c"<<c<<endl;
}
};
将每个类单独封装一个自己的函数,在调用时避免重复调用。