继承机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能,这样产生新的类,称派生类。继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。以前我们接触的复用都是函数复用,继承是类设计层次的复用
定义格式
Person是父类,也称作基类。Student是子类,也称作派生类
不同类型的对象赋值时,相近类型可以隐式类型转换(产生临时变量)
int和int* 就是相近类型,int表示数据大小,int* 是地址,本质是一个编号
r引用的不是d,而是中间的临时变量,临时变量有常性,所以要加const
class Person
{
public:
Person(const char* name = "peter")
: _name(name)
{
cout << "Person()" << endl;
}
protected:
string _name; // 姓名
};
class Student : public Person
{
public:
Student(const char* name, int id)
:_id(id)
, Person(name)
{
cout << "Student(const char* name, int id)" << endl;
}
protected:
int _id;
}
要把父类当成一个完整的对象初始化,而不是单个成员初始化(否则会编译报错)。要初始化父类就要显示调用父类的构造函数,不显示调用也会自动调用父类的默认构造函数,自动调用时:父类如果没有默认构造就会报错。
class Person
{
public:
Person(const char* name = "peter")
: _name(name)
{
cout << "Person()" << endl;
}
Person(const Person& p)
: _name(p._name)
{
cout << "Person(const Person& p)" << endl;
}
Person& operator=(const Person& p)
{
cout << "Person operator=(const Person& p)" << endl;
if (this != &p)
_name = p._name;
return *this;
}
~Person()
{
cout << "~Person()" << endl;
}
protected:
string _name; // 姓名
};
class Student : public Person
{
public:
Student(const char* name, int id)
:_id(id)
//, Person(name)
{
cout << "Student(const char* name, int id)" << endl;
}
Student(const Student& s)
:Person(s)
, _id(s._id)
{
cout << "Student(const Student& s)" << endl;
}
Student& operator=(const Student& s)
{
if (&s != this)
{
Person::operator=(s);
_id = s._id;
}
cout << "Student& operator=(const Student& s)" << endl;
return *this;
}
~Student()
{
//Person::~Person();
cout << "~Student()" << endl;
}
protected:
int _id;
};
int main()
{
Student s1("张三", 18);
Student s2(s1);
Student s3("李四", 19);
s1 = s3;
return 0;
}
由于多态的原因,析构函数统一会被处理成destructor
父子类的析构函数构成隐藏
为了保证析构安全,先子后父
父类析构函数不需要显示调用,子类析构函数结束时会自动调用父类析构,不要显示调用,不然会多次调用。
保证先子后父
如何实现一个不能被继承的类?
C++98 私有化父类的构造函数
C++11 新增final,修饰父类,直接不能被继承,如:class A final
友元关系不能继承
基类定义了static静态成员,则整个继承体系里面只有一个这样的成员。无论派生出多少个子类,都只有一个static成员实例 。
静态成员可以说继承的是使用权,静态成员不属于某个对象,存在静态区,属于整个类,突破类域就能访问。
如果静态成员在父类是私有,在子类中这个静态成员不可见
多继承:一个子类有两个或以上直接父类时称这个继承关系为多继承
菱形继承:菱形继承是多继承的一种特殊情况
尽量不要使用菱形继承!
菱形继承有数据冗余和二义性的问题。
在Assistant的对象中Person成员会有两份。
class Person
{
public:
string _name; // 姓名
int _age;
int _tel;
// ...
};
class Student : public Person
{
protected:
int _num; //学号
};
class Teacher : public Person
{
protected:
int _id; // 职工编号
};
class Assistant : public Student, public Teacher
{
protected:
string _majorCourse; // 主修课程
};
void Test()
{
// 这样会有二义性无法明确知道访问的是哪一个
Assistant a;
//a._name = "peter";
// 需要显示指定访问哪个父类的成员可以解决二义性问题,
// 但是数据冗余问题无法解决
a.Student::_name = "小张";
a.Teacher::_name = "老张";
}
int main()
{
Test();
return 0;
}
虚拟继承可以解决菱形继承的二义性和数据冗余的问题。如上面的继承关系,在Student和Teacher的继承Person时使用虚拟继承,即可解决问题。需要注意的是,虚拟继承不要在其他地方去使用。
class Student : virtual public Person
class Teacher : virtual public Person
a.Student::_name = "小张";
a.Teacher::_name = "老张";
a._name = "张三";
都是同一份
B的指针可以访问b也要可以访问a,为了解决二义性付出了一些代价,用存的地址去表里找偏移量,偏移量在00 的下一个位置存是因为00的位置要存其他值,和多态有关,用自己的地址加上偏移量就取到a了
如果存的地址直接就是a的地址或者存的就是偏移量都是可以的,但引入多态之后情况会复杂,所以选择了这个方法.
为了解决数据冗余用了虚拟继承,但我们发现用了虚拟继承之后空间用的更多了,就像做生意,少存了一份a,当a越大,节省的空间就越多
如果D对象有很多,都只需要指向这个表会很方便
public继承是一种is-a的关系。也就是说每个派生类对象都是一个基类对象。
组合是一种has-a的关系。假设B组合了A,每个B对象中都有一个A对象。
//继承
class A
{
public:
void func()
{}
protected:
int _a;
};
class B : public A
{
public:
void f()
{
func();
_a++;
}
protected:
int _b;
};
// 组合
class C
{
public:
void func()
{}
protected:
int _c;
};
class D
{
public:
void f()
{
_c.func();
//_c._a++;
}
protected:
C _c;
int _d;
};
组合类的公有可以使用,组合类的保护就不可以使用了
对象的内部细节是不可见的。对象只以“黑箱”的形式出现。组合类之间没有很强的依赖关系,耦合度低。优先使用对象组合有助于你保持每个类被封装。
在继承方式中,基类的内部细节对子类可见。继承一定程度破坏了基类的封装,基类的改变,对派生类有很大的影响。派生类和基类间的依赖关系很强,耦合度高。
继承用起来比组合方便,但还是尽量多用组合,因为组合的耦合度低,代码维护性好。