继承
什么是继承:
继承可以认为是类层面上的重载. 在保证原有类型的基础上进行扩展, 实现类的代码复用. 继承展现了面向对象程序设计的层次结构.
基类和派生类对象之间的赋值:
派生类可以赋值给基类的对象/指针/引用(切片), 但是基类不能赋值给派生类.
隐藏(重定义):
基类和派生类有独立的作用域
如果子类中有和父类同名的函数, 子类成员将屏蔽父类的同名成员的直接访问形成对父类同名成员的隐藏(重定义), 但是可以通过基类显示访问父类同名成员.
#include <iostream>
using namespace std;
class Person {
public:
void show() {
cout << "Person show()" << endl;
}
protected:
string _name;
};
class Student : public Person {
public:
void show() {
cout << "Student show()" << endl;
}
protected:
int _num;
};
int main() {
Student s;
s.show();
return 0;
}
如果将Student中的show()注释掉:
显然, 这种隐藏的概念还是比较让程序员头疼的, 因此在实际开发中, 不建议在派生类中定义与基类同名的函数, 这样就可以直接避免.
派生类的默认成员函数:
1.如果在派生类中不进行构造函数的重载, 继承部分会自动调用父类的构造函数完成部分初始化. 其他默认成员函数也是同样的道理.
2.如果是重载默认成员函数, 需注意的是, 继承部分要通过父类的默认成员来进行操作, 而扩展部分需要子类自己进行指定操作.
但是析构函数例外, 编译器为了不让析构顺序混乱, 在子类调用万析构函数后会自动调用父类析构函数.
#include <iostream>
#include <string>
using namespace std;
class Person {
public:
Person(const char* name = "person")
: _name(name){}
Person(const Person& p) {
_name = p._name;
}
Person& operator=(const Person& p) {
_name = p._name;
return *this;
}
~Person() {
cout << "~Person()" << endl;
}
void show() {
cout << _name << endl;
}
protected:
string _name;
};
class Student : public Person {
public:
Student(const char* name = "sock", int num = 0)
: Person(name), _num(num) {}
Student(const Student& s)
: Person(s) {
_num = s._num;
}
Student& operator=(const Student& s) {
Person::operator=(s);
_num = s._num;
return *this;
}
~Student() {
cout << "~Student()" << endl;
}
void show() {
cout << _name << "-" << _num << endl;
}
protected:
int _num;
};
int main() {
Student s;
s.show();
return 0;
}
实现一个不能被继承的类:
#include <iostream>
using namespace std;
// 构造函数私有化
class Person {
public:
static Person getInstancec() {
return Person();
}
private:
Person(){}
};
class Student : public Person {
};
int main() {
//Student s; // 出现错误
return 0;
}
// final关键字 c++11
class Person final {} // 直接不能被继承
继承和友元, 继承与静态
继承与友元
父类的友元函数不能访问子类的私有和保护成员
#include <iostream>
using namespace std;
class Student;
class Person {
public:
friend void show(const Person& p, const Student& s);
protected:
string _name = "sock";
};
class Student : public Person {
protected:
int _num = 0;
};
void show(const Person& p, const Student& s) {
cout << p._name << endl;
cout << s._num << endl; // 出错
}
int main() {
Person p;
Student s;
show(p, s);
return 0;
}
继承与静态
基类的静态成员, 在所有的派生类体系中都只有这一个static实例
#include <iostream>
using namespace std; //可通过这种demo计算继承体系中有多少个对象生成
class Person {
public:
Person() {
++_counts;
}
static int _counts;
protected:
string _name = "sock";
};
class Student : public Person {
protected:
int _num = 0;
};
int Person::_counts = 0;
int main() {
Person p;
Student s;
cout << Person::_counts << endl;
return 0;
}
菱形继承和菱形虚拟继承
菱形继承是复杂的多继承:
#include <iostream>
using namespace std;
class Person {
public:
string _name;
};
class Student : public Person {
protected:
int _num;
};
class Teacher : public Person {
protected:
int _id;
};
class Assistant : public Student, public Teacher {
protected:
string course;
};
int main() {
Assistant a;
//a._name = "sock"; // 产生二义性
a.Student::_name = "sock";
a.Teacher::_name = "sock";
return 0;
}
菱形继承中会产生数据冗余和二义性的问题, 一般名字只有一个, 但是菱形继承导致名字继承了多份并且赋值会产生二义性问题. 需通过显示指定直接父类才能挨个赋值.
虚拟继承可以解决这些问题:
#include <iostream>
using namespace std;
class Person {
public:
string _name;
};
class Student : virtual public Person {
protected:
int _num;
};
class Teacher : virtual public Person {
protected:
int _id;
};
class Assistant : public Student, public Teacher {
protected:
string course;
};
int main() {
Assistant a;
a._name = "sock";
cout << a._name << endl;
return 0;
}
菱形虚拟继承的原理:
继承和组合
1.public继承是一种is-a的关系。也就是说每个派生类对象都是一个基类对象。
2.组合是一种has-a的关系。假设B组合了A,每个B对象中都有一个A对象。
3.优先使用对象组合,而不是类继承 。
3.继承允许你根据基类的实现来定义派生类的实现。这种通过生成派生类的复用通常被称为白箱复用 (white-box reuse)。术语“白箱”是相对可视性而言:在继承方式中,基类的内部细节对子类可见 。 继承一定程度破坏了基类的封装,基类的改变,对派生类有很大的影响。派生类和基类间的依赖关 系很强,耦合度高。
4.对象组合是类继承之外的另一种复用选择。新的更复杂的功能可以通过组装或组合对象来获得。对 象组合要求被组合的对象具有良好定义的接口。这种复用风格被称为黑箱复用(black-box reuse), 因为对象的内部细节是不可见的。对象只以“黑箱”的形式出现。 组合类之间没有很强的依赖关系, 耦合度低。优先使用对象组合有助于你保持每个类被封装。
5.实际尽量多去用组合。组合的耦合度低,代码维护性好。不过继承也有用武之地的,有些关系就适 合继承那就用继承,另外要实现多态,也必须要继承。类之间的关系可以用继承,可以用组合,就 用组合。