有些类与类之间有特殊的关系: (定义这些类时,下级别的成员除了拥有上一级的共性,还有自己的特性) |
C++继承(提高代码重用和开发效率): 子类拥有父类所有属性/行为; 子类是特殊父类; 子类对象可以作为父类对象使用; |
注意: 一个类可以有多个派生类; 一个类可有一个或多个基类; 派生类又可有派生类,称为多级继承; 继承关系不可循环; 基类友元关系、基类的构造函数、析构函数都不能被派生类所继承; |
一、继承语法
class 子类名:继承方式 父类名 (子类别名:派生类,父类别名:基类) |
派生类中的成员,包含两部分: 从基类(父类)继承过来的(共性); 自己增加的成员(个性); |
#include <iostream> using namespace std; // 基类 class Home { public: // 头部 void harder() { cout<<"百度内容"<<"消息"<<"设置"<<"用户名"<<endl; } // 下部 void footer() { cout<<"帮助"<<"举报"<<"用户反馈"<<endl; } }; // 主页子类 - 继承自基类(公有继承) class Homepage:public Home { public: // 内容 void visit() { cout<<"内容1"<<endl; } }; int main() { Homepage C1; C1.harder(); C1.visit(); C1.footer(); } |
二、继承方式
继承方式分类(默认私有继承): |
公有继承(class Son:public Base): 公有成员 - 在子类内外都可访问; 保护成员 - 在子类内可以访问,子类外不可访问; 私有成员 - 在子类内外都不可访问; |
保护继承(class Son: protected Base): 公有成员 - 在子类内可以访问,子类外不可访问; 保护成员 - 在子类内可以访问,子类外不可访问; 私有成员 - 在子类内外都不可访问; |
私有继承(class Son: private Base): 公有成员 - 在子类内可以访问(转为私有成员),子类外不可访问; 保护成员 - 在子类内可以访问(转为私有成员),子类外不可访问; 私有成员 - 在子类内外都不可访问; |
![]() |
三、继承中构造和析构顺序
构造:先构造父类,再构造子类; |
析构:先析构子类,再析构父类; |
四、继承到子类,子类大小
子类大小 = 父类大小 + 子类个性成员大小 |
五、继承中同名成员处理
访问子类中同名成员,直接访问; 访问父类中同名成员,需要加作用域(对象.基类名::同名成员); |
#include <iostream> using namespace std; // ----------------基类 class Base { public: Base() { m_A = 100; } void fun(); int m_A; }; // 基类成员函数 void Base::fun() { cout<<"基类fun"<<endl; } // ----------------子类 class Son:public Base { public: Son() { m_A = 200; } void fun(); int m_A; }; // 子类成员函数 void Son::fun() { cout<<"子类fun"<<endl; } // ----------------主函数 int main() { Son c1; // -----------------------------------访问同名成员变量 // 访问子类同名成员变量 - 直接访问 cout <<"子类:"<<c1.m_A <<endl; // 访问父类同名成员变量 - 使用作用域进行区分 cout <<"父类:"<<c1.Base::m_A <<endl; // -----------------------------------访问同名成员函数 // 访问子类同名成员函数 - 直接访问 c1.fun(); // 访问父类同名成员函数 - 使用作用域进行区分 c1.Base::fun(); } |
六、继承中静态成员变量/函数
静态成员函数和静态成员变量:在编译阶段被定义; 访问静态成员和访问非静态成员出现同名,处理方式一致(加作用域); |
#include <iostream> using namespace std; // ----------------------基类 class Base { public: // -----静态成员函数 static void fun() { cout<<"基类FUN"<<endl; } // -----静态成员变量 - 声明部分 static int m_B; }; // -----基类静态成员变量 - 定义部分 int Base::m_B = 1000; // ----------------------子类 class Son:public Base { public: // -----静态成员函数 static void fun() { cout<<"子类FUN"<<endl; } // -----静态成员变量 - 声明部分 static int m_B; }; // -----子类静态成员变量 - 定义部分 int Son::m_B = 1999; // ----------------------主函数 int main() { Son c1; // ----------------------访问静态成员变量 // 访问子类同名静态成员变量 - 直接访问 cout<<"子类m_B:"<<c1.m_B<<endl; // 访问父类同名静态成员变量 - 使用作用域加以区分 cout<<"父类m_B:"<<c1.Base::m_B<<endl; // ----------------------访问静态成员函数 cout<<"1.通过对象访问"<<endl; // 访问子类同名静态成员函数 - 直接访问 c1.fun(); // 访问父类同名静态成员函数 - 使用作用域加以区分 c1.Base::fun(); cout<< "2.通过类名访问"<<endl; // 访问子类同名静态成员函数 - 直接访问 Son::fun(); // 访问父类同名静态成员函数 - 使用作用域加以区分 Son::Base::fun(); } |
七、派生类成员权限恢复操作(访问声明)
对于private/protected继承方式,基类公有成员在派生类中变成私有/保护,可以在派生类中采用访问声明方式将其恢复成公有成员; |
格式:using 基类名::成员名;// 成员函数名后不加() |
注意:访问声明仅能将继承成员恢复到原来访问权限,即原来在基类中公有,被继承方式protected/private屏蔽后只能用访问声明恢复为公有,而不能改变为保护/私有,原来在基类中保护,也不能改变为公有/私有; |
#include <iostream> #include <cstring> using namespace std; // 基类 class Base { public: int A; void fun(void) { cout<<"Base_fun函数"<<endl; } protected: int B; private: int C; }; // 派生类 - 私有继承 class NUM:private Base { public: // 访问声明 - 恢复公有 using Base::A; using Base::fun; // 公有成员->私有成员 - 子类内可以访问,子类外不可访问 void lookbaseA() { cout<<A<<endl; } void lookbasefun() { fun(); } // 保护成员->私有成员 - 子类内可以访问,子类外不可访问 void lookbaseB() { cout<<B<<endl; } // 私有成员 - 子类内外都不可访问 void lookbaseC() { //cout<<C<<endl; } }; int main() { NUM a; a.A = 10; cout<<a.A<<endl; a.lookbaseA(); a.fun(); a.lookbasefun(); return 0; } |
八、派生类调用基类有参构造函数
#include <iostream> #include <cstring> using namespace std; // 基类 class Base { public: // 无参构造函数 Base() { cout<<"Base基类构造函数"<<endl; } // 有参构造函数 Base(int n):A(n) { cout<<"Base基类构造函数Base(int n)"<<endl; } // 析构函数 ~Base() { cout<<"Base基类析构函数"<<endl; } int A; }; // 子类 - 公有继承 class NUM:public Base { public: // 构造函数 - 调用基类构造函数 NUM():Base(66),A(2) { cout<<"NUM构造函数"<<endl; } // 析构函数 ~NUM() { cout<<"NUM析构函数"<<endl; } int A; }; int main() { NUM a; cout<<a.A<<endl; cout<<a.Base::A<<endl; return 0; } |
九、基类与派生类之间类型转换
类型转换:公有继承派生类对象可以被当作基类的对象使用,反之不可; 派生类对象可以隐含转为基类对象; 派生类对象可以初始化基类引用; 派生类指针可以隐含转换为基类指针; |
通过基类对象名、指针只能使用从基类继承的成员(扔掉派生类特性部分); |
(派生类存储结构:基类共性+派生类特性) |
#include <iostream> #include <cstring> using namespace std; // 基类 class Base { public: // 构造函数 Base(int n):A(n) { cout<<"Base构造函数"<<endl; } // 析构函数 ~Base() { cout<<"Base析构函数"<<endl; } int A; }; // 派生类 - 公有继承 class NUM:public Base { public: // 构造函数 - 调用基类有参构造 NUM():Base(66),A(2) { cout<<"NUM构造函数"<<endl; } // 析构函数 ~NUM() { cout<<"NUM析构函数"<<endl; } int A; }; int main() { // 1.派生类对象可以隐含转为基类对象 // 丢掉派生类共性部分 NUM a; Base &b = a; // 访问的为基类成员 cout<<b.A<<endl; // 2.派生类指针可以隐含转换为基类指针 Base *p = new NUM; // 访问的为基类成员 cout<<p->A<<endl; return 0; } |
十、多继承(一个类继承多个类)
语法:class 子类: 继承方式 父类1, 继承方式 父类2,继承方式 父类3……….. 多继承更大几率出现父类与子类中同名成员,需要加作用域去区分; |
#include <iostream> using namespace std; // 父类1 class Base { public: Base() { m_A = 100; } int m_A; }; // 父类2 class Base1 { public: Base1() { m_A = 1111; } int m_A; }; // 子类 class Son:public Base,public Base1 { public: // 个性 Son() { m_A = 2222; } int m_A; }; int main() { Son c1; // 子类大小为12字节 cout << "C1.m_A子类:"<<c1.m_A<<endl; // 访问父类同名成员 - 使用作用域加以区分 cout << "m_A - Base 类:"<<c1.Base::m_A<<endl; cout << "m_A - Base1类:"<<c1.Base1::m_A<<endl; } |
十一、菱形继承(钻石继承)
菱形继承概念: 两个派生类继承同一个基类; 同时又有某个类继承这两个类; 注意: 羊继承动物数据,骆驼同样继承动物数据,当羊驼使用数据时产生二义性; 羊驼继承自动物的数据继承了两份,这份数据只需要一份即可; |
#include <iostream> using namespace std; // 动物 class Animal { public: Animal() { number = 2; } int number; }; // 羊 class Sheep:public Animal { public: }; // 骆驼 class camel:public Animal { public: }; // 羊驼 class SheepCamel:public Sheep,public camel { public: }; int mian() { SheepCamel C1; cout<<"number"<<C1.number<<endl; return 0; } |
十二、虚继承(解决菱形继承缺陷)
关键字:virtual 不会出现二义性问题; 派生类会多一个虚基表指针[指向同一个地址]: 虚基类表指针是一个指向存储虚基类相关信息的表的指针,这个表存储了虚基类在派生类对象中的偏移量以及虚基类的地址等信息,通过这个指针,可以在派生类对象中准确地找到虚基类的成员。 |
#include <iostream> using namespace std; // 动物 class Animal { public: Animal() { number = 2; } int number; }; // 羊 class Sheep:virtual public Animal { public: }; // 骆驼 class camel:virtual public Animal { public: }; // 羊驼 class SheepCamel:public Sheep,public camel { public: }; int main() { // --------------------测试类大小 // 测试羊类大小 Sheep n1; cout<<"羊类大小:"<<sizeof(n1)<<endl; // 测试骆驼类大小 camel n2; cout<<"骆驼大小:"<<sizeof(n2)<<endl; // 测试羊驼类大小 SheepCamel n3; cout<<"羊驼类大小:"<<sizeof(n3)<<endl; // --------------------测试羊驼number n3.Sheep::number = 999; cout<<"骆驼number:"<<n3.camel::number<<endl; cout<<"羊驼number:"<<n3.number<<endl; // --------------------测羊、骆驼父类(动物类)number cout<<"羊 父类number:"<<n1.Animal::number<<endl; cout<<"骆驼父类number:"<<n2.Animal::number<<endl; // --------------------测羊、骆驼类number cout<<"羊 类number:"<<n1.number<<endl; cout<<"骆驼类number:"<<n2.number<<endl; return 0; } |
虚继承体系所占空间大小(虚继承还包含一个指向虚基类指针): 虚继承会产生虚基类表指针,而非虚函数表指针; 在派生类对象中,基类子对象都会保持其原始的完整性; 所以,动物是4,羊和骆驼都是8,羊驼是12个字节; |