c++ (10)继承
内容提要:
- 多态是学习的重点;
- 抽象、封装、继承、多态是面向程序设计的4大特点;
- 类的兼容性原则是重点也是难点;
1.类之间的关系
三种关系:包含关系、组合关系、继承关系;

2.继承的实例

继承有两个特性:
【特性1】传递性,高等植物、蕨类植物等都是植物,具有植物的共同特征;
【特性2】不是所有植物都属于蕨类;
3.继承的相关概念

4.派生类的基本定义

5.继承重要说明
1、子类拥有父类的所有成员变量和成员函数
4、子类可以拥有父类没有的方法和属性
2、子类就是一种特殊的父类
3、子类对象可以当作父类对象使用
1 // dm01.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。 2 //功能:继承的基本语法 3 4 #include <iostream> 5 using namespace std; 6 7 class Parent 8 { 9 public: 10 void print() { 11 int a = 100; 12 int b = 123; 13 cout << "a:" << a << endl; 14 cout << "b:" << b << endl; 15 } 16 17 int a; 18 int b; 19 protected: 20 21 private: 22 23 24 }; 25 26 /*一般继承使用public继承*/ 27 class Child :public Parent { 28 public : 29 protected: 30 private: 31 int c; 32 }; 33 34 35 36 int main() 37 { 38 Child c1; 39 //c1.c = 1; 40 c1.a = 2; 41 c1.b = 3; 42 c1.print(); 43 std::cout << "Hello World!\n"; 44 system("pause"); 45 }
6.派生类的访问控制
派生类继承了基类的全部成员变量和成员方法(除了构造和析构之外的成员方法) ,但是这些成员的访问属性,在派生过程中是可以调整的。
6.1 单个类的访问控制
1、类成员访问级别( public、 private 、 protected )
6.2 不同继承方式会改变继承成员的访问属性
1) C++中的继承方式会影响子类的对外访问属性
public 继承:父类成员在子类中保持原有访问级别
private 继承:父类成员在子类中变为 private 成员
protected 继承:父类中 public 成员会变成 protected
父类中 protected 成员仍然为 protected
父类中 private 成员仍然为 private
简单的理解:
public:类似于老爹的名字,说都可以有权限使用,包括儿子在内都可以使用;
protectd:类似于老爹的银行账号,只要本家的儿子可以获得并使用,在一帮情况下儿子也不允许使用;
private:类似老爹的情人,只有老爹用,其他人不能用。
2) private 成员在子类中依然存在,但是却无法访问到。不论种方式继承基类,派生类都不
能直接使用基类的私有成员。
3) C++中子类对外访问属性表



6.3 三看原则
C++中的继承方式( public 、 private、 protected )会影响子类的对外访问属性
判断某一句话,能否被访问
1)看调用语句,这句话写在子类的内部、外部
2)看子类如何从父类继承( public 、 private、 protected )
3)看父类中的访问级别( public 、 private 、 protected )
6.4 派生类类成员访问级别设置的原则
思考:如何恰当的使用 public, protected 和 private 为成员声明访问级别?
1、需要被外界访问的成员直接设置为 public
2、只能在当前类中访问的成员设置为 private
3、只能在当前类和子类中访问的成员设置为protected,protected 成员的访问权限介于public 和 private之间;
6.5 综合练习
1 // 类的继承方式对子类对外访问属性影响 2 #include <cstdlib> 3 #include <iostream> 4 using namespace std; 5 class A 6 { 7 private: 8 int a; 9 protected: 10 int b; 11 public: 12 int c; 13 A() 14 { 15 a = 0; 16 b = 0; 17 c = 0; 18 } 19 void set(int a, int b, int c) 20 { 21 this->a = a; 22 this->b = b; 23 this->c = c; 24 } 25 }; 26 class B : public A 27 { 28 public: 29 void print() 30 { 31 //cout<<"a = "<<a; //err 32 cout << "b = " << b; 33 cout << "c = " << endl; 34 } 35 }; 36 class C : protected A 37 { 38 public: 39 void print() 40 { 41 //cout<<"a = "<<a; //err 42 cout << "b = " << b; 43 cout << "c = " << endl; 44 } 45 }; 46 class D : private A 47 { 48 public: 49 void print() 50 { 51 //cout<<"a = "<<a; //err 52 cout << "b = " << b << endl; 53 cout << "c = " << c << endl; 54 } 55 }; 56 int main_01(int argc, char *argv[]) 57 { 58 A aa; 59 B bb; 60 C cc; 61 D dd; 62 aa.c = 100; //ok 63 bb.c = 100; //ok 64 //cc.c = 100; //err 类的外部是什么含义 65 //dd.c = 100; //err 66 aa.set(1, 2, 3); 67 bb.set(10, 20, 30); 68 //cc.set(40, 50, 60); //ee 69 //dd.set(70, 80, 90); //ee 70 bb.print(); 71 cc.print(); 72 dd.print(); 73 system("pause"); 74 return 0; 75 }
7 继承中的类构造和析构
7.1.类的兼容性原则
类型兼容规则是指在需要基类对象的任何地方,都可以使用公有派生类的对象来替代。
通过公有继承, 派生类得到了基类中除构造函数、 析构函数之外的所有成员。这样,公有派
生类实际就具备了基类的所有功能, 凡是基类能解决的问题, 公有派生类都可以解决。 类型
兼容规则中所指的替代包括以下情况:
1 子类对象可以当作父类对象使用 2 子类对象可以直接赋值给父类对象 3 子类对象可以直接初始化父类对象 4 父类指针可以直接指向子类对象 5 父类引用可以直接引用子类对象
在替代之后,派生类对象就可以作为基类的对象使用,但是只能使用从基类继承的成员。类型兼容规则是多态性的重要基础之一。
下面的程序关注点:
【关注点1】

第一层含义:【关注点1-3】父类的指针或者引用可以指向子类的对象;
在“63行”使用子类的引用赋值给父类的指针;
在“64行”调用了父类的成员函数没有问题的;
【关注点2】


在68 69行中可以兼容传递“父类”或者“子类”的指针;
【关注3】


第二层层含义:可以用子类的对象初始化父类的对象;

7.2 继承中的对象模型
类在 C++编译器的内部可以理解为结构体
子类是由父类成员叠加子类新成员得到的


问题:如何初始化父类成员?父类与子类的构造函数有什么关系
在子类对象构造时,需要调用父类构造函数对其继承得来的成员进行初始化
在子类对象析构时,需要调用父类析构函数对其继承得来的成员进行清理
1 #include <cstdlib> 2 #include <iostream> 3 using namespace std; 4 class Parent04 5 { 6 public: 7 Parent04(const char* s) 8 { 9 cout << "Parent04()" << " " << s << endl; 10 } 11 ~Parent04() 12 { 13 cout << "~Parent04()" << endl; 14 } 15 }; 16 class Child04 : public Parent04 17 { 18 public: 19 Child04() : Parent04("Parameter from Child!") 20 { 21 cout << "Child04()" << endl; 22 } 23 ~Child04() 24 { 25 cout << "~Child04()" << endl; 26 } 27 }; 28 void run04() 29 { 30 Child04 child; 31 } 32 int main_04(int argc, char *argv[]) 33 { 34 run04(); 35 system("pause"); 36 return 0; 37 }
7.3 继承中的构造析构调用原则
1、子类对象在创建时会首先调用父类的构造函数
2、父类构造函数执行结束后,执行子类的构造函数
3、当父类的构造函数有参数时,需要在子类的初始化列表中显示调用
4、析构函数调用的先后顺序与构造函数相反
7.4 继承与组合混搭情况下,构造和析构调用原则
1 原则:
先构造父类,再构造成员变量、最后构造自己 2 先析构自己,在析构成员变量、最后析构父类 3 // 先构造的对象,后释放
1 // 子类对象如何初始化父类成员 2 // 继承中的构造和析构 3 // 继承和组合混搭情况下,构造函数、析构函数调用顺序研究 4 #include <iostream> 5 using namespace std; 6 class Object 7 { 8 public: 9 Object(const char* s) 10 { 11 cout << "Object()" << " " << s << endl; 12 } 13 ~Object() 14 { 15 cout << "~Object()" << endl; 16 } 17 }; 18 class Parent : public Object 19 { 20 public: 21 Parent(const char* s) : Object(s) 22 { 23 cout << "Parent()" << " " << s << endl; 24 } 25 ~Parent() 26 { 27 cout << "~Parent()" << endl; 28 } 29 }; 30 class Child : public Parent 31 { 32 protected: 33 Object o1; 34 Object o2; 35 public: 36 Child() : o2("o2"), o1("o1"), Parent("Parameter from Child!") //多个继承使用逗号 37 { 38 cout << "Child()" << endl; 39 } 40 ~Child() 41 { 42 cout << "~Child()" << endl; 43 } 44 }; 45 void run05() 46 { 47 Child child; 48 } 49 int main(int argc, char *argv[]) 50 { 51 cout << "demo05_extend_construct_destory.cpp" << endl; 52 run05(); 53 system("pause"); 54 return 0; 55 }

【特别说明】多个继承使用逗号间隔;
7.5 继承中的同名成员变量处理方法
1、当子类成员变量与父类成员变量同名时 2、子类依然从父类继承同名成员 3、在子类中通过作用域分辨符 ::进行同名成员区分 ( 在派生类中使用基类的同名成员,显式地使用类名限定符 ) 4、同名成员存储在内存中的不同位置


总结:同名成员变量和成员函数通过作用域分辨符进行区分

原则:同名变量如果不指定作用域则默认使用子类的变量值;
7.6 派生类的中的static关键字



【特别说明】
【1】静态变量必须要“显示赋值”;不然在后期的使用过程中会报错;但是c++编译器即使在继承之后未使用,编译不会报错;但是在使用的时候就会报错;

【2】构造函数不写访问控制符时候默认是private权限,在继承的时候需要注意这个问题,否则可能发生子类访问不到的严重问题;
8.多继承
【说明】多继承在其他的面向对象的语言中被摒弃的概念;

1.多个基类的派生类构造函数可以用初始时调用基类构造函数初始化数据成员;
2.执行顺序与单继承构造函数情况类似。
多个直接基类构造函数执行顺序取决于定义派生类时指定的各个继承基类的顺序。
3.一个派生类对象拥有多个直接或间接基类的成员。不同名成员访问不会出现二义性。
4.如果不同的基类有同名成员,派生类对象访问时应该加以识别。





9.多继承的二义性-非重点
【问题描述】在base的老祖宗类中有个变量b,如果base1和base2同时都继承了base类,也就同时有b这个变量;
之后类c同时继承了base1和base2,则c中变量b究竟是base1还是base2的b值?


【解决方式】使用关键字“virtual”;

【原理】使用了virtual关键字之后c++编译器在处理时老祖宗类的构造函数只会调用一次,不会重复调用;
【特别说明】经过试验,virtual无法解决多继承带来的同名变量和同名函数的问题;


10.总结

浙公网安备 33010602011771号