一、什么是继承
1.继承定义
继承机制是面向对象程序设计使代码可以复用的最重要的手段,该机制自动地为一个类提供来自另一个类的操作和数据结构。只需要在新类中定义已有的类中没有的成分来建立一个新类。继承是类设计层次的复用。
继承包括成员变量和成员函数。
如下所示,Animal类的成员变量有age,成员函数有eat( )和sleep( )。Dog类的成员变量有legs,继承了Animal的age成员变量、成员函数eat( )和sleep( ),并且还定义了新的成员变量legs和新的成员函数bark( ),因此Dog类有2个成员变量:age和legs,3个成员函数:eat( )、sleep( )、bark( )。
Animal叫做父类,也叫做基类。Dog叫做子类,也叫作派生类。
转化成代码:
1. #define _CRT_SECURE_NO_WARNINGS 1 2. #include<iostream> 3. using namespace std; 4. class Animal 5. { 6. public: 7. void eat() 8. {} 9. 10. void sleep() 11. {} 12. 13. private: 14. int _age = 1; 15. }; 16. 17. 18. class Dog :public Animal 19. { 20. public: 21. void bark() 22. {} 23. 24. public: 25. int _legs = 4; 26. }; 27. 28. int main() 29. { 30. Animal a; 31. Dog d; 32. 33. return 0; 34. }
F10监视,可以看到对象a的成员变量只有一个,age。对象d的成员变量有两个,一个是继承自Animal的age,一个是Dog类自己定义的成员变量legs:
2.继承基类成员访问方式
父类有3种访问方式:public、protected、private,子类继承父类也有3种继承方式:public、protected、private。组合起来一共有9种:
总结:
① 基类private成员在派生类中无论以什么方式继承都是不可见的。这里的不可见是指基类的私有成员还是被继承到了派生类对象中,但是语法上限制派生类对象不管在类里面还是类外面都不能去访问它。
② 基类private成员在派生类中是不能被访问,如果基类成员不想在类外直接被访问,但需要在派生类中能访问,就定义为protected。可以看出保护成员限定符是因继承才出现的。
③ 基类的私有成员在子类都是不可见。基类的其他成员在子类的访问方式 == Min(成员在基类的访问限定符,继承方式),public > protected > private。
④ 使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public,最好显式写出继承方式。
⑤ 在实际运用中一般使用都是public继承,几乎很少使用protetced/private继承,也不提倡使用
protetced/private继承,因为protetced/private继承下来的成员都只能在派生类的类里面使用,实际中扩展维护性不强。
关于什么叫做不可见:
假如将父类成员改为private,那么子类继承了父类的私有成员,内存上,子类对象有这个成员,但是语法上不允许访问:
1. #define _CRT_SECURE_NO_WARNINGS 1 2. #include<iostream> 3. using namespace std; 4. class Animal 5. { 6. public: 7. void eat() 8. {} 9. 10. void sleep() 11. {} 12. 13. private://访问方式改为private 14. int _age = 1; 15. }; 16. 17. 18. class Dog :public Animal 19. { 20. public: 21. void bark() 22. {} 23. 24. void func() 25. { 26. //cout << _age << endl;//语法上不可以访问,但是内存上,子类对象有父类的私有成员 27. } 28. public: 29. int _legs = 4; 30. }; 31. 32. int main() 33. { 34. Animal a; 35. Dog d; 36. 37. return 0; 38. }
F10监视:
二、基类和派生类互相赋值转换
1.赋值规则
(1)派生类对象可以赋值给基类的对象 / 基类的指针 / 基类的引用。这里有个形象的说法叫切片或者切割。即把派生类中父类那部分切来赋值过去。(子可以给父)
(2)基类对象不能赋值给派生类对象。(父不能给子)
(3)基类的指针可以通过强制类型转换赋值给派生类的指针。但是必须是基类的指针是指向派生类对象时才是安全的。
2.切片
(1)子类对象赋值给父类对象
子可以赋值给父,但父不能赋值给子:
1. #define _CRT_SECURE_NO_WARNINGS 1 2. #include<iostream> 3. #include<string> 4. using namespace std; 5. 6. class Person 7. { 8. public: 9. string _name; 10. string _sex; 11. int _age; 12. }; 13. 14. class Student:public Person 15. { 16. public: 17. int _No; 18. }; 19. 20. int main() 21. { 22. //1.子类对象赋值给父类对象 23. //定义对象时,内置类型不处理,自定义类型会调用自己默认的构造函数 24. Person p; 25. Student s; 26. 27. s._name = "Delia"; 28. s._sex = "女"; 29. s._No = 21356; 30. 31. p = s;//子可以给父 32. //s = p;编译报错,父不能给子 33. return 0; 34. }
F10监视: 像拷贝构造一样,把子类中父类那部分切片赋值给父类对象,也就是把子类成员依次赋值给父类对象:
从string的buffer的地址可以看出,string调用了深拷贝,因为s和p的_name虽然内容都是"Delia",但是两个Delia的地址不同:
假如父类和子类有同名成员变量,那么切片赋值时,只会切割从父类继承的成员,即把父类的成员变量切过去,因为父类并不拥有子类的成员变量,因此切片后,父类的成员变量的值为父类的成员变量的值。如,父类和子类拥有同名成员变量_No:
1. #define _CRT_SECURE_NO_WARNINGS 1 2. #include<iostream> 3. #include<string> 4. using namespace std; 5. 6. class Person 7. { 8. public: 9. string _name; 10. string _sex; 11. int _age; 12. int _No = 999; 13. }; 14. 15. class Student :public Person 16. { 17. public: 18. int _No; 19. }; 20. 21. int main() 22. { 23. //1.子类对象赋值给父类对象 24. //定义对象时,内置类型不处理,自定义类型会调用自己默认的构造函数 25. Person p; 26. Student s; 27. 28. s._name = "Delia"; 29. s._sex = "女"; 30. s._No = 21356; 31. 32. p = s;//子可以给父 33. cout << "p._No : " << p._No << endl; 34. 35. return 0; 36. }
(2)子类对象赋值给父类指针
① 子类对象可以赋值给父类指针
1. int main() 2. { 3. //2.子类对象赋值给父类指针 4. Person *p; 5. Student s; 6. 7. s._name = "Delia"; 8. s._sex = "女"; 9. s._No = 21356; 10. 11. p = &s; 12. 13. return 0; 14. }
F10-监视:发现子类对象s给父类指针p赋值成功,子类对象初始化了父类对象的一部分,_name和_sex,因此子类对象赋值给父类对象后,父类对象只能初始化子类对象赋值的成员,对于其他成员,如果是内置类型不处理,_age为随机值,自定义类型会调用它的默认构造函数:
② 若父类指针要赋值给子类指针,必须将父类指针强转为子类指针:
1. int main() 2. { 3. 4. //父类指针赋值给子类指针 5. Person p;//父类对象 6. Student *s;//子类指针 7. 8. p._name = "Delia"; 9. p._sex = "女"; 10. p._age = 6; 11. 12. Person* pp = &p;//父类指针 13. 14. s = (Student*)pp;//将父类指针强转为子类型指针后,再赋值给子类型指针 15. 16. return 0; 17. }
F10监视:父类指针成功赋值给子类指针:
③ 指向父类对象的父类指针,强转为子类指针后赋值给子类指针可能会造成访问越界
1. int main() 2. { 3. //指向父类对象的父类指针,强转为子类指针后赋值给子类指针可能会造成访问越界 4. Person p; 5. 6. p._name = "Delia"; 7. p._sex = "女"; 8. p._age = 7; 9. //s._No = 21530; 10. 11. Person* pp = &p; 12. 13. Student* ss = (Student*)pp; 14. //ss->_No = 6;访问越界造成程序崩掉 15. 16. return 0; 17. }
F10监视:发现赋值成功了
(3)子类对象赋值给父类引用
1. int main() 2. { 3. 4. //子类对象赋值给父类引用 5. Student s; 6. 7. s._name = "Delia"; 8. s._sex = "女"; 9. s._age = 7; 10. s._No = 21369; 11. 12. Person& rp = s;//子类对象赋值给父类引用 13. 14. return 0; 15. }
F10监视:父类引用被成功赋值了: