继承的概念和意义 继承的概念 面向对象中的继承指类之间的父子关系
子类拥有父类的所有属性和行为
子类就是一种特殊的父类
子类对象可以当作父类对象使用
子类中可以添加父类没有的方法和属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 #include <iostream> #include <string> using namespace std;class Parent { int mv; public : Parent () { cout << "Parent()" << endl; mv = 100 ; } void method () { cout << "mv = " << mv << endl; } }; class Child : public Parent{ public : void hello () { cout << "I'm Child calss!" << endl; } }; int main () { Child c; c.hello (); c.method (); return 0 ; }
1 2 3 4 5 fengyun@ubuntu:~/share$ g++ test.cpp -o test fengyun@ubuntu:~/share$ ./test Parent() I'm Child calss! mv = 100
重 要 规 则 :
子类就是一个特殊的父类
子类对象可以直接初始化 父类对象
子类对象可以直接赋值 给父类对象
继承的意义 继承是 C++ 中代码复用 (不要动不动就复制粘贴)的重要手段。 通过继承,可以获得父类的所有功能 , 并且可以在子类中重写已有功能 , 或者添加新功能。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 #include <iostream> #include <string> using namespace std;class Memory { public : Memory () { cout << "Memory()" << endl; } ~Memory () { cout << "~Memory()" << endl; } }; class Disk { public : Disk () { cout << "Disk()" << endl; } ~Disk () { cout << "~Disk()" << endl; } }; class CPU { public : CPU () { cout << "CPU()" << endl; } ~CPU () { cout << "~CPU()" << endl; } }; class MainBoard { public : MainBoard () { cout << "MainBoard()" << endl; } ~MainBoard () { cout << "~MainBoard()" << endl; } }; class Computer { Memory mMem; Disk mDisk; CPU mCPU; MainBoard mMainBoard; public : Computer () { cout << "Computer()" << endl; } void power () { cout << "power()" << endl; } void reset () { cout << "reset()" << endl; } ~Computer () { cout << "~Computer()" << endl; } }; class HPBook : public Computer{ string mOS; public : HPBook () { mOS = "Windows 8" ; } void install (string os) { mOS = os; } void OS () { cout << mOS << endl; } }; class MacBook : public Computer{ public : void OS () { cout << "Mac OS" << endl; } }; int main () { HPBook hp; hp.power (); hp.install ("Ubuntu 16.04 LTS" ); hp.OS (); cout << endl; MacBook mac; mac.OS (); return 0 ; }
组合和继承的混合使用
小结
继承:是面向对象中类之间的一种关系
子类拥有父类的所有属性和行为
子类对象可以当作父类对象使用
子类中可以添加父类没有的方法和属性
继承是面向对象中代码复用的重要手段
继承的访问级别 子类是否可以直接访问父类的私有成员?
根据面向对象理论 :
根 据 语 法 :
面向对象中的访问级别不只是 public 和 private
可以定义 protected 访问级别
关 键 字 protected 的意义 修饰的成员不能被外界直接访问 修饰的成员可以被子类直接访问
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 #include <iostream> #include <string> using namespace std;class Parent { protected : int mv; public : Parent () { mv = 100 ; } int value () { return mv; } }; class Child : public Parent{ public : int addValue (int v) { mv = mv + v; } }; int main () { Parent p; cout << "p.mv = " << p.value () << endl; Child c; cout << "c.mv = " << c.value () << endl; c.addValue (50 ); cout << "c.mv = " << c.value () << endl; return 0 ; }
为什么面向对象需要protected?
不同的继承方式 C+ + 中支持三种不同的继承方式 1.public 继承 父类成员在子类中保持原有访问级别
2.private 继承 父类成员在子类中变为私有 成员
3.protected继承 父类中的公有成员变为保护成员 , 其它成员保持不变
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 #include <iostream> #include <string> using namespace std;class Parent { protected : int m_a; protected : int m_b; public : int m_c; void set (int a, int b, int c) { m_a = a; m_b = b; m_c = c; } }; class Child_A : public Parent{ public : void print () { cout << "m_a" << m_a << endl; cout << "m_b" << m_b << endl; cout << "m_c" << m_c << endl; } }; class Child_B : protected Parent{ public : void print () { cout << "m_a" << m_a << endl; cout << "m_b" << m_b << endl; cout << "m_c" << m_c << endl; } }; class Child_C : private Parent{ public : void print () { cout << "m_a" << m_a << endl; cout << "m_b" << m_b << endl; cout << "m_c" << m_c << endl; } }; int main () { Child_A a; Child_B b; Child_C c; a.m_c = 100 ; a.set (1 , 1 , 1 ); a.print (); b.print (); c.print (); return 0 ; }
遗憾的事实
一般而言 , C++工程项目中只使用 public 继承。
C + + 的派生语言只支持一种继承方式( public 继承 )
protected 和 private 继承带来的复杂性远大于实用性(太过鸡肋,鸡肋尚且食之有味,)
继承中的构造和析构
父类构造函数在子类中的调用方式
默认调用 适用于无参构造函数和使用默认参数的构造函数
显示调用 通过初始化列表进行调用 适用于所有父类构造函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 #include <iostream> #include <string> using namespace std;class Parent { public : Parent (string s) { cout << "Parent(string s) : " << s << endl; } }; class Child : public Parent{ public : Child () { cout << "Child()" << endl; } Child (string s) { cout << "Child(string s) : " << s << endl; } }; int main () { Child c; Child cc ("cc" ) ; return 0 ; }
先观察这个程序代码,子类Child中有两个构造函数,但是Child()和Child(string s)都没有显示调用父类的任何构造函数,因此这两个子类的构造函数将会隐式的调用父类无参构造函数和使用默认参数的构造函数 。 而观察父类并没有无参构造函数或使用默认参数的构造函数。因此编译器将会报错。 解决方法是1.Parent类中添加一个无参构造函数或者2.给Parent(string s)
提供一个默认参数,比如Parent(string s = "parent")
而如果子类中Child(string s) : Parent(s)构造函数显示的调用父类的这个 Parent(s)构造函数,那么父类这个 Parent(s)将会执行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 #include <iostream> #include <string> using namespace std;class Parent { public : Parent () { cout << "Parent()" << endl; } Parent (string s) { cout << "Parent(string s) : " << s << endl; } }; class Child : public Parent{ public : Child () { cout << "Child()" << endl; } Child (string s) : Parent (s) { cout << "Child(string s) : " << s << endl; } }; int main () { Child c; Child cc ("cc" ) ; return 0 ; }
1 2 3 4 5 fengyun@ubuntu:~/share$ ./test Parent() Child() Parent(string s) : cc Child(string s) : cc
构造规则
子类对象在创建时会首先调用父类的构造函数
先执行父类构造函数 再执行子类的构造函数
父类构造函数可以被隐式调用或者显示调用
对象创建时构造函数的调用顺序
调用父类的构造函数
调用成员变量 的构造函数
调用类自身的构造函数
口诀心法 :先父母 , 后客人 , 再自己。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 #include <iostream> #include <string> using namespace std;class Object { public : Object (string s) { cout << "Object(string s) : " << s << endl; } }; class Parent : public Object{ public : Parent () : Object ("Default" ) { cout << "Parent()" << endl; } Parent (string s) : Object (s) { cout << "Parent(string s) : " << s << endl; } }; class Child : public Parent{ Object mO1; Object mO2; public : Child () : mO1 ("Default 1" ), mO2 ("Default 2" ) { cout << "Child()" << endl; } Child (string s) : Parent (s), mO1 (s + " 1" ), mO2 (s + " 2" ) { cout << "Child(string s) : " << s << endl; } }; int main () { Child cc ("cc" ) ; return 0 ; }
在前述代码的基础上我们添加一个Object类作为Parent的父类,并且子类Child中有两个成员变量是Object类型,那么为了代码能够正常运行 1.我们必须首先在parent类中处理好显示或隐式的调用Object类的构造函数 2.处理完parent类后又必须在child类中处理好Obejct类成员变量的初始化 3.main函数中调用Child构造函数,查看各个类的构造顺序
1 2 3 4 5 6 7 fengyun@ubuntu:~/share$ g++ test.cpp -o test fengyun@ubuntu:~/share$ ./test Object(string s) : cc Parent(string s) : cc Object(string s) : cc 1 Object(string s) : cc 2 Child(string s) : cc
结果分析: Child cc(“cc”)首先处理父类的构造函数Parent(string s) : Object(s),而parent也先要处理parent的父类Object的构造函数Object(string s)。 处理完父母后,处理两个成员变量mO1,mO2的构造函数Object(string s)。 处理完客人后,处理自己,调用Child(string s)
析构顺序 析构函数的调用顺序与构造函数相反
执行自身的析构函数
执行成员变量的析构函数
执行父类的析构函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 #include <iostream> #include <string> using namespace std;class Object { string ms; public : Object (string s) { cout << "Object(string s) : " << s << endl; ms = s; } ~Object () { cout << "~Object() : " << ms << endl; } }; class Parent : public Object{ string ms; public : Parent () : Object ("Default" ) { cout << "Parent()" << endl; ms = "Default" ; } Parent (string s) : Object (s) { cout << "Parent(string s) : " << s << endl; ms = s; } ~Parent () { cout << "~Parent() : " << ms << endl; } }; class Child : public Parent{ Object mO1; Object mO2; string ms; public : Child () : mO1 ("Default 1" ), mO2 ("Default 2" ) { cout << "Child()" << endl; ms = "Default" ; } Child (string s) : Parent (s), mO1 (s + " 1" ), mO2 (s + " 2" ) { cout << "Child(string s) : " << s << endl; ms = s; } ~Child () { cout << "~Child() " << ms << endl; } }; int main () { Child cc ("cc" ) ; cout << endl; return 0 ; }
1 2 3 4 5 6 7 8 9 10 11 12 fengyun@ubuntu:~/share$ ./test Object(string s) : cc Parent(string s) : cc Object(string s) : cc 1 Object(string s) : cc 2 Child(string s) : cc ~Child() cc ~Object() : cc 2 ~Object() : cc 1 ~Parent() : cc ~Object() : cc
小结
子类对象在创建时需要调用父类构造函数进行初始化
先执行父类构造函数然后执行成员的构造函数
父类构造函数显示调用需要在初始化列表中进行
子类对象在销毁时需要调用父类析构函数进行清理
析构顺序与构造顺序对称相反
同名覆盖 子类中是否可以定义父类中的同名成员?如果可以,如何区分?如果不可以,为什么?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 #include <iostream> #include <string> using namespace std;class Parent { public : int mi; }; class Child : public Parent{ public : int mi; }; int main () { Child c; c.mi = 100 ; return 0 ; }
上述代码编译成功且运行成功,意味着编译器认可 子类中定义父类中的同名成员 这种行为。
成员变量命名冲突
子类可以定义父类中的同名成员
子类中的成员将隐藏父类中的同名成员
父类中的同名成员依然存在于子类中
通过作用域分辨符::
访问父类中的同名成员
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 #include <iostream> #include <string> using namespace std;class Parent { public : int mi; Parent () { cout << "Parent() : " << "&mi = " << &mi << endl; } }; class Child : public Parent{ public : int mi; Child () { cout << "Child() : " << "&mi = " << &mi << endl; } }; int main () { Child c; c.mi = 100 ; c.Parent::mi = 1000 ; cout << "&c.mi = " << &c.mi << endl; cout << "c.mi = " << c.mi << endl; cout << "&c.Parent::mi = " << &c.Parent::mi << endl; cout << "c.Parent::mi = " << c.Parent::mi << endl; return 0 ; }
C++允许同名变量的存在的原因是因为两个同名变量的作用域不同
1 2 3 4 5 6 7 8 fengyun@ubuntu:~/share$ g++ test.cpp -o test fengyun@ubuntu:~/share$ ./test Parent() : &mi = 0x7ffd055d0f10 Child() : &mi = 0x7ffd055d0f14 &c.mi = 0x7ffd055d0f14 c.mi = 100 &c.Parent::mi = 0x7ffd055d0f10 c.Parent::mi = 1000
观察结果得知,c.mi默认访问子类中的mi;只有通过作用域访问符::
才可访问父类的mi,c.parent::mi
。
函数命名冲突 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 #include <iostream> #include <string> using namespace std;class Parent { public : int mi; void add (int v) { mi += v; } void add (int a, int b) { mi += (a + b); } }; class Child : public Parent{ public : int mi; }; int main () { Child c; c.mi = 100 ; c.Parent::mi = 1000 ; cout << "c.mi = " << c.mi << endl; cout << "c.Parent::mi = " << c.Parent::mi << endl; c.add (1 ); c.add (2 , 3 ); cout << "c.mi = " << c.mi << endl; cout << "c.Parent::mi = " << c.Parent::mi << endl; return 0 ; }
1 2 3 4 5 fengyun@ubuntu:~/share$ ./test c.mi = 100 c.Parent::mi = 1000 c.mi = 100 c.Parent::mi = 1006
结果分析:子类中并没有定义add函数,但是c.add()调用了父类中的add函数,并且最后父类中的mi完成了累加add()函数,子类中的mi的值并没有发生变化。 这是因为父类的成员变量mi作用域两个大括号之间,父类add看到的mi只能是父类的mi,
接着我继续在子类中添加一个add(int a,int b,int c)试图实现重载,main函数中添加语句c.add(1,2,3)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 #include <iostream> #include <string> using namespace std;class Parent { public : int mi; void add (int v) { mi += v; } void add (int a, int b) { mi += (a + b); } }; class Child : public Parent{ public : int mi; void add (int a, int b,int c) { mi += (a + b + c); } }; int main () { Child c; c.mi = 100 ; c.Parent::mi = 1000 ; cout << "c.mi = " << c.mi << endl; cout << "c.Parent::mi = " << c.Parent::mi << endl; c.add (1 ); c.add (2 , 3 ); c.add (1 , 2 , 3 ); cout << "c.mi = " << c.mi << endl; cout << "c.Parent::mi = " << c.Parent::mi << endl; return 0 ; }
然而编译器报错
我的试图重载失败了,这是因为不符合重载的条件—–函数重载必须发生在同一作用域中
类中的成员函数可以进行重载
重载函数的本质为多个不同的函数
函数名和参数列表是唯一的标识
函数重载必须发生在同一作用域中
解决方案是用作用域访问符::
,main函数中进行修改
1 2 3 4 5 6 7 c.add (1 ); c.add (2 , 3 ); c.add (1 , 2 , 3 ); c.Parent::add (1 ); c.Parent::add (2 , 3 ); c.add (1 , 2 , 3 );
结论:
子类中的函数将隐藏父类的同名函数
子类无法重载 父类中的成员函数
使用作用域分辨符 访问父类中的同名函数
子类可以定义父类中完全相同的成员函数
同名覆盖引发的问题 子类对象可以当作父类对象使用( 兼容性 )
- 子类对象可以直接赋值给父类对象
- 子类对象可以直接初始化父类对象
- 父类指针可以直接指向子类对象
- 父类引用可以直接引用子类对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 #include <iostream> #include <string> using namespace std;class Parent { public : int mi; void add (int i) { mi += i; } void add (int a, int b) { mi += (a + b); } }; class Child : public Parent{ public : int mv; void add (int x, int y, int z) { mv += (x + y + z); } }; int main () { Parent p; Child c; p = c; Parent p1 (c) ; Parent& rp = c; Parent* pp = &c; rp.mi = 100 ; rp.add (5 ); rp.add (10 , 10 ); return 0 ; }
观察这一段程序,父类Parent中定义了两个add函数void add(int i)
和void add(int a, int b)
,子类中定义了一个add函数void add(int x, int y, int z)
和一个新变量int mv;
根据之前的知识,我们定义一个子类Child c;
c.add()是只能访问子类的中的add函数无法访问父类中的add函数。
而编译运行却发现47行48行同名覆盖似乎不见了? 51行,52行pp指针竟然找不到子类的mv成员和add函数?
当使用父类指针( 引用 ) 指向子类对象时
- 子类对象退化为父类对象
- 只能访问父类中定义的成员
- 可以直接访问被子类覆盖的同名成员
特殊的同名函数-重写
子类中可以重定义 父类中已经存在的成员函数
这种重定义发生在继承 中 , 叫做函数重写
函数重写 是同名覆盖的一种特殊情况
当函数重写遇上赋值兼容会发生什么? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 #include <iostream> #include <string> using namespace std;class Parent { public : void print () { cout << "I'm Parent." << endl; } }; class Child : public Parent{ public : void print () { cout << "I'm Child." << endl; } }; void how_to_print (Parent* p) { p->print (); } int main () { Parent p; Child c; p.print (); c.print (); cout << "----------------------------------" <<endl; how_to_print (&p); how_to_print (&c); return 0 ; }
1 2 3 4 5 6 fengyun@ubuntu:~/share$ ./test I'm Parent. I' m Child.---------------------------------- I'm Parent. I' m Parent.
问题分析
- 编译期间,编译器只能根据指针的类型判断所指向的对象
- 根据赋值兼容,编译器认为父类指针指向的是父类对象
- 因此,编译结果只可能是调用父类中定义的同名函数
在编译这个函数的时候,编译器不可能知道指针 P 究竟指向了什么。但是编译器没有理由报错。 于是,编译器认为最安全的做法是调用父类的 print 函数,因为父类和子类肯定都有相同的 print 函数。