概念及定义
继承机制是面向对象程序设计使代码可以复用的重要手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能,这样产生新的类,称派生类。
#include <iostream> using namespace std; #include <string> class Person { public: void Print() { cout << "name:" << _name << endl; cout << "age:" << _age << endl; } protected: string _name = "wzh"; int _age = 18; }; class Student : public Person { protected: int _stuid; }; class Teacher : public Person { protected: int _jobid; }; int main() { Student s; Teacher t; s.Print(); t.Print(); }
继承父类的成员(成员函数+成员变量)后,子类中也拥有了父类中的成员。
在
class Student : public Person {}
中,Student
是子类,也称派生类,public
是继承方式,Person
是父类,也称基类。继承后访问方式的变化
类成员/继承方式 public继承 protected继承 private继承 基类的public成员 派生类的public继承 派生类的protected继承 派生类的private继承 基类的protected成员 派生类的protected继承 派生类的protected继承 派生类的private继承 基类的private成员 在派生类中不可见 在派生类中不可见 在派生类中不可见 总结:
- 在派生类中不可见是指基类的私有成员还是被继承到了派生类中,但是在语法上限定了派生类对象无论是在类内还是类外都无法去访问它。
- 如果基类成员不想在类外直接被访问,但在派生类中可以访问,就可以定义为protected。该限定符是因继承才出现的。
- 关键字class默认继承方式是private,关键字struct默认继承方式是public。
基类和派生类对象的赋值转换
- 派生类对象可以赋值给基类的对象、基类的指针以及基类的引用。这种方式称为切片或切割。
- 基类对象不能赋值给派生类对象
- 基类的指针或者引用可以通过强制类型转换赋值给派生类的指针或者引用。但是必须是基类的指针是指向派生类对象时才是安全的。
class Person { protected: string _name; int _age ; }; class Student : public Person { public: int _stuid; }; void test() { Student s; // 子类对象可以赋值给父类对象/指针/引用 Person p = s; Person* p_ptr = &s; Person& p_ref = s; //基类对象不能赋值给派生类对象 s = p; //基类的指针可以通过强制类型转换赋值给派生类的指针,且基类的指针是指向派生类对象 p_ptr = &s; Student* s_ptr1 = (Student*)p_ptr; s_ptr1->_stuid = 10; //基类的指针不是指向派生类对象,存在越界访问 p_ptr = &p; Student* s_ptr2 = (Student*)p_ptr; s_ptr2->_stuid = 10; }
继承中的作用域
- 基类和派生类都有自己独立的作用域
- 当子类和父类有同名的成员时,子类将屏蔽父类的成员。这种情况叫隐藏也叫重定义。
- 与重载不同,只要函数名相同就构成重定义。
class Person { protected: string _name; int _age ; int _stuid = 11; void print() { cout << "pring()" << endl; } }; class Student : public Person { public: int _stuid = 22; void print(int i) { cout << "pring(int i)" << endl; } }; void test() { Student s; s.print(3); }
派生类的默认成员函数
- 派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员。如果基类没有默认的构造函数,则必须在派生类构造函数的初始化列表阶段显示调用。
/*派生类中基类的成员调用基类的构造函数来进行初始化*/ class Person { protected: string _name; Person(const string& name = "wzh") :_name(name) { cout << "Person()" << endl; } }; class Student : public Person { protected: int _stuid; }; int main() { Student s; } /*没有默认构造函数*/ class Person { protected: string _name; Person(const string name) :_name(name){ cout << "Person()" << endl; } }; class Student : public Person { public: Student(const string name = "wzh", const int stuid = 18) : _stuid(stuid) {//若存在默认构造函数,这种方式可行 cout << "Student()" << endl; } Student(const string name = "wzh", const int stuid = 18) : _name(name)//没有这种方式 , _stuid(stuid) { cout << "Student()" << endl; } Student(const string name = "wzh", const int stuid = 18) : Person(name)//没有默认构造函数,必须显示调用 , _stuid(stuid) { cout << "Student()" << endl; } protected: int _stuid; }; int main() { Student s("wzh", 1); }
- 派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化。
class Person { protected: string _name; Person(const string name = "wzh") :_name(name) { cout << "Person()" << endl; } Person(const Person& per) :_name(per._name) { cout << "Person(const Person& per)" << endl; } }; class Student : public Person { public: Student(const string name = "wzh", const int stuid = 18) : Person(name) , _stuid(stuid) { cout << "Student()" << endl; } Student(const Student& stu) : Person(stu) , _stuid(stu._stuid) { cout << "Student(const Student& stu)" << endl; } protected: int _stuid; }; int main() { Student s("wzh", 1); Student s2(s); }
- 派生类的
operator=
必须要调用基类的operator=
完成基类的赋值。class Person { protected: string _name; Person(const string name = "wzh") :_name(name) { cout << "Person()" << endl; } Person(const Person& per) :_name(per._name) { cout << "Person(const Person& per)" << endl; } Person& operator=(const Person& per) { if (this != &per) { _name = per._name; cout << "Person& operator=(const Person& per)" << endl; } return *this; } }; class Student : public Person { public: Student(const string name = "wzh", const int stuid = 18) : Person(name) , _stuid(stuid) { cout << "Student()" << endl; } Student& operator=(const Student& stu) { if (this != &stu) { Person::operator=(stu);//一定要显示调用,operator=(stu);会导致对父类的隐藏,从而出现递归调用 _stuid = stu._stuid; cout << "Student& operator=(const Student& stu)" << endl; } return *this; } protected: int _stuid; }; int main() { Student s("wzh", 1); Student s1; s1 = s; }
- 派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员。因为这样才能保证派生类对象先清理派生类成员再清理基类成员的顺序。
class Person { protected: string _name; Person(const string name = "wzh") :_name(name) { cout << "Person()" << endl; } ~Person() { cout << "~Person()" << endl; } }; class Student : public Person { public: Student(const string name = "wzh", const int stuid = 18) : Person(name) , _stuid(stuid) { cout << "Student()" << endl; } ~Student() {//子类的析构和父类的析构构成隐藏,因为它们的名字会被编译器同意处理为destructor(). //Person::~Person() 析构函数不需要显示调用,结束后会自动调用父类 cout << "~Student" << endl; } protected: int _stuid; }; int main() { Student s("wzh", 1); }
继承与友元
友元关系不能继承
class Student; class Person { protected: string _name; public: friend void print(const Person& p, const Student& s); }; class Student : public Person { public: protected: int _stuid; }; void print(const Person& p, const Student& s) { cout << p._name << endl; cout << s._stuid << endl;//无法访问 } int main() { Person p; Student s; }
继承与静态成员
若是在基类中定义了静态成员,则整个体系中只有一个这个成员,并不会因为派生类的增多而增多。
class Person { public: static int _count; Person() { _count++; } protected: string _name; }; int Person::_count = 0; class Student : public Person { protected: int _stuid; }; class Teacher : public Student { protected: int _jobid; }; int main() { Student s1; Student s2; Student s3; Teacher t1; cout << Person::_count << endl;//一共调用了4次,说明static成员在继承体系中是共有的 return 0; }
菱形继承
- 单继承:一个子类只有一个直接父类。
- 多继承:一个子类有两个或以上直接父类。
- 菱形继承:一个子类有多个父类,而多个父类又继承自同一个父类。
菱形继承具有数据冗余和二义性的问题。
class Person { public: string _name; }; class Student : public Person{ protected: int _num; }; class Teacher : public Person { protected: int _id; }; class Course : public Student , public Teacher { protected: string _course; }; int main() { Course c; //这里具有二义性,不知道访问哪一个父类的 _name //c._name = "wzh"; c.Student::_name = "www"; c.Teacher::_name = "zzz"; }
菱形虚拟继承
虚拟继承可以解决菱形继承的二义性和数据冗余的问题,但虚拟继承不可以在其他地方使用。
class Person { public: string _name; }; class Student : virtual public Person{ protected: int _num; }; class Teacher : virtual public Person { protected: int _id; }; class Course : public Student , public Teacher { protected: string _course; }; int main() { Course c; //使用虚拟继承后,此时访问不在具有二义性以及数据冗余 c._name = "wzh"; }
原理
在上面示例中,子类
Student
和Teacher
同时拥有父类的成员_name
,在使用虚拟继承后,子类中存放_name
的空间实际上存的并不是具体的值,而是指针,这个指针又称为虚基表指针。虚基表中存的是_name
的偏移量,通过偏移量就可以找到具体的值。
继承和组合
标签:name,继承,派生类,class,Person,基类,public From: https://www.cnblogs.com/zhiheng-/p/18204767组合:
- 组合是一种has-a的关系,即一个类包含另一个类的实例作为成员变量。
- 组合是一种黑箱复用,也就是说对象的内部细节是不可见的。
class Engine { public: void start() { /* 启动引擎 */ } void stop() { /* 停止引擎 */ } }; class Car { private: Engine engine; // 组合关系 public: void drive() { engine.start(); // 驾驶汽车的其他操作 engine.stop(); } };
继承:
- 继承是一种is-a的关系,即一个类派生自另一个类,从而获得父类的属性和方法。
- 继承是一种白箱复用,即基类的内部细节对子类可见,由此可见继承一定程度上破坏了基类的封装。
class Vehicle { public: void move() { /* 移动 */ } }; class Car : public Vehicle { // 继承关系 public: void drive() { /* 驾驶汽车 */ } };
总的来说:组合体现了代码的封装性和复用性,而继承体现了代码的多态性和扩展性。在使用上,若两者都可用那么优先使用组合。