• C++ 继承


    目录

    模版收尾

    继承

    赋值兼容转换

     继承的作用域

    同名变量

    同名成员函数

    派生类的成员构造函数

    不写构造函数

    写构造函数

    不写拷贝构造函数

    手动写拷贝构造

    运算符重载

    析构函数


    模版收尾

    模版的声明和定义不能分离,否则会报错.

    写下面三个文件:

    1. Stack.h
    2. #pragma once
    3. #include
    4. using namespace std;
    5. template <class T>
    6. T Add(const T& left, const T& right);
    1. Stack.cpp
    2. #include"Stack.h"
    3. template <class T>
    4. T Add(const T& left, const T& right)
    5. {
    6. cout << 1 << endl;
    7. return left + right;
    8. }
    1. Test.cpp
    2. #include"Stack.h"
    3. int main()
    4. {
    5. Add(1,2);
    6. fun();
    7. return 0;
    8. }

    运行结果:

     如上图所示,模版的声明和定义分离会出现链接错误。

    如果我们正常函数的定义和分离就不会出错,比如下图fun函数:

    这是刚开始我们把代码加载进内存,运行之后先执行预处理,预处理把"Stack.h"展开。

    展开之后就会在Stack.cpp里面找,但是Stack.cpp里面并没有对模版进行实例化,也就找不到地址:

    为什么Add预处理阶段找不到地址呢?这是因为在Stack.cpp也就是定义里面Add没有被实例化:

    Add是模版,没有实例化,在预编译阶段找不到地址,但是fun我们一开始就定义了是void类型,所以不管是预处理,编译,链接阶段都可以找到它的地址。

    解决方案:

    1.显示实例化:

    缺点:换类型之后还要重新显示实例化,假设我此时这样写:

    Add(1.0, 2.0);

     就会报错:

    1>Test.obj : error LNK2019: 无法解析的外部符号 "double __cdecl Add(double const &,double const &)" (??$Add@N@@YANABN0@Z),函数 _main 中引用了该符号

     需要再加个类型显示实例化:

    1. template
    2. double Add<double>(const double& left, const double& right);

    运行:

    1. 1
    2. 1

    但是这样太麻烦了,干脆不要声明定义分离了,把Stack.cpp删掉,定义全部放到Stack.h里面:

    继承

    继承就是一种复用:

     继承格式:

     

    举例:

    1. class Person
    2. {
    3. public:
    4. void Print()
    5. {
    6. cout << "name:" << _name << endl;
    7. cout << "age:" << _age << endl;
    8. }
    9. protected:
    10. string _name = "peter"; // 姓名
    11. int _age = 18;
    12. };
    13. // 继承后父类的Person的成员(成员函数+成员变量)都会变成子类的一部分。这里体现出了
    14. //Student和Teacher复用了Person的成员。下面我们使用监视窗口查看Student和Teacher对象,可
    15. //以看到变量的复用。调用Print可以看到成员函数的复用。
    16. class Student : public Person
    17. {
    18. protected:
    19. int _stuid; // 学号
    20. };
    21. class Teacher : public Person
    22. {
    23. protected:
    24. int _jobid; // 工号
    25. };
    26. int main()
    27. {
    28. Student s;
    29. Teacher t;
    30. s.Print();
    31. t.Print();
    32. return 0;
    33. }
    1. name:peter
    2. age:18
    3. name:peter
    4. age:18

    如上图,teach类和student类都继承了person类的成员函数和成员变量

    如果继承方式不写,class默认是私有,struct默认是公有:

    1. class Person
    2. {
    3. public:
    4. void Print()
    5. {
    6. cout << "name:" << _name << endl;
    7. cout << "age:" << _age << endl;
    8. }
    9. protected:
    10. string _name = "peter"; // 姓名
    11. int _age = 18;
    12. };
    13. // 继承后父类的Person的成员(成员函数+成员变量)都会变成子类的一部分。这里体现出了
    14. //Student和Teacher复用了Person的成员。下面我们使用监视窗口查看Student和Teacher对象,可
    15. //以看到变量的复用。调用Print可以看到成员函数的复用。
    16. class Student : Person
    17. {
    18. protected:
    19. int _stuid; // 学号
    20. };
    21. class Teacher : Person
    22. {
    23. protected:
    24. int _jobid; // 工号
    25. };
    26. int main()
    27. {
    28. Student s;
    29. Teacher t;
    30. s.Print();
    31. t.Print();
    32. return 0;
    33. }

    赋值兼容转换

    不同类型的相近对象之间赋值的时候会进行隐式类型转化,隐式类型转换会产生临时变量。

    如:

    1. double d = 2.2;
    2. int& r = d;

     报错原因:

    int 和double是相近类型,因为都表示大小,把double d赋值给int i,那么r对int d取地址实际上不是对d取地址,而是对临时变量取地址。

    解决方法:

    临时变量具有常性,加个const就行了:

    const int& r = d;

    那子类可不可以给父类呢?

    1. class Person{};
    2. class Student : public Person{};
    3. int main()
    4. {
    5. Student a;
    6. Person b = a;
    7. return 0;
    8. }

    没有报错可以的,因为父子之间是一种很亲近的关系。

    在public继承下,父子之间是一种 is a关系:

    student is a person ,teacher is a person:

    那既然是很亲近的关系,会进行隐式类型转换,那么肯定也会产生临时对象:

    1. Student a;
    2. Person& b = a;

     没加const没有报错。难道自定义类型不会隐式类型转换吗?

    下面代码可证明自定义类型也可以隐式类型转换:

    1. string c = "xxxx";
    2. string& d = "xxxx";

    1. string c = "xxxx";
    2. const string& d = "xxxx";

    原因:

     继承的作用域

    同名变量

    如下代码,父类和子类都有number变量,此时访问子类,打印出来的是子类的number:

    1. class Person
    2. {
    3. protected:
    4. string _name = "小李子"; // 姓名
    5. int _num = 111;//身份证号
    6. };
    7. class Student : public Person
    8. {
    9. public:
    10. void Print()
    11. {
    12. cout << " 姓名:" << _name << endl;
    13. cout << " 身份证号:" << _num << endl;
    14. }
    15. protected:
    16. int _num = 999; // 学号
    17. };
    18. void Test()
    19. {
    20. Student s1;
    21. s1.Print();
    22. };
    23. int main()
    24. {
    25. Test();
    26. return 0;
    27. }

    1.  姓名:小李子
    2.  身份证号:999

     如果想访问父类的number,需要突破作用域:

    1. class Person
    2. {
    3. protected:
    4. string _name = "小李子"; // 姓名
    5. int _num = 111;//身份证号
    6. };
    7. class Student : public Person
    8. {
    9. public:
    10. void Print()
    11. {
    12. cout << " 姓名:" << _name << endl;
    13. cout << " 身份证号:" << Person::_num << endl;
    14. }
    15. protected:
    16. int _num = 999; // 学号
    17. };
    18. void Test()
    19. {
    20. Student s1;
    21. s1.Print();
    22. };
    23. int main()
    24. {
    25. Test();
    26. return 0;
    27. }
    1. 姓名:小李子
    2. 身份证号:111

    同名成员函数

    如果父子类有两个同名成员函数,选哪个:

    1. // B中的fun和A中的fun不是构成重载,因为不是在同一作用域
    2. // B中的fun和A中的fun构成隐藏,成员函数满足函数名相同就构成隐藏。
    3. class A
    4. {
    5. public:
    6. void fun()
    7. {
    8. cout << "func()" << endl;
    9. }
    10. };
    11. class B : public A
    12. {
    13. public:
    14. void fun(int i)
    15. {
    16. cout << "func(int i)->" << i << endl;
    17. }
    18. };
    19. void Test()
    20. {
    21. B b;
    22. b.fun(10);
    23. };
    24. int main()
    25. {
    26. Test();
    27. return 0;
    28. }
    1. 答案:B
    2. //会调用子类同名成员函数,隐藏父类同名成员函数
    3. 运行结果:func(int i)->10

    如果我现在不带参:

    1. void Test()
    2. {
    3. B b;
    4. b.fun();
    5. };

     假设我就是不想带参,那就调父类嘛,突破一下作用域:

    1. void Test()
    2. {
    3. B b;
    4. b.A::fun();
    5. };
    运行结果:func()

    派生类的成员构造函数

    不写构造函数

    自定义类型,如果我们不写,编译器会自动生成构造函数

    派生类如果不写会调用父类的构造函数:

    1. class Person
    2. {
    3. public:
    4. Person()
    5. {
    6. cout << "Person()" << endl;
    7. }
    8. ~Person()
    9. {
    10. cout << "~Person()" << endl;
    11. }
    12. protected:
    13. string _name; // 姓名
    14. };
    15. class Student : public Person
    16. {
    17. public:
    18. protected:
    19. int _num; //学号
    20. };
    21. int main()
    22. {
    23. Student s1;
    24. return 0;
    25. }

    写构造函数

    1. class Person
    2. {
    3. public:
    4. Person(const char* name = "peter")
    5. : _name(name)
    6. {
    7. cout << "Person()" << endl;
    8. }
    9. Person(const Person& p)
    10. : _name(p._name)
    11. {
    12. cout << "Person(const Person& p)" << endl;
    13. }
    14. Person& operator=(const Person& p)
    15. {
    16. cout << "Person operator=(const Person& p)" << endl;
    17. if (this != &p)
    18. _name = p._name;
    19. return *this;
    20. }
    21. ~Person()
    22. {
    23. cout << "~Person()" << endl;
    24. }
    25. protected:
    26. string _name; // 姓名
    27. };
    28. class Student : public Person
    29. {
    30. public:
    31. Student(const char* name,int age)
    32. :_name()
    33. ,_age(age)
    34. {
    35. cout << "student const(student& s)" <
    36. }
    37. protected:
    38. int _age; //学号
    39. };
    40. void Test()
    41. {
    42. Student s1("张三",18);
    43. }
    44. int main()
    45. {
    46. Test();
    47. return 0;
    48. }

    这是因为派生类初始化父类成员变量必须把父类当成一个对象,而不是直接去初始化父类的成员变量,如果派生类不写构造函数会默认调用父类构造函数,如果派生类想写构造函数必须显示调用父类构造函数。

    改成如下:

    1. class Person
    2. {
    3. public:
    4. Person(const char* name = "peter")
    5. : _name(name)
    6. {
    7. cout << "Person()" << endl;
    8. }
    9. Person(const Person& p)
    10. : _name(p._name)
    11. {
    12. cout << "Person(const Person& p)" << endl;
    13. }
    14. Person& operator=(const Person& p)
    15. {
    16. cout << "Person operator=(const Person& p)" << endl;
    17. if (this != &p)
    18. _name = p._name;
    19. return *this;
    20. }
    21. ~Person()
    22. {
    23. cout << "~Person()" << endl;
    24. }
    25. protected:
    26. string _name; // 姓名
    27. };
    28. class Student : public Person
    29. {
    30. public:
    31. Student(const char* name,int age)
    32. :Person(name)
    33. ,_age(age)
    34. {
    35. cout << "student const(student& s)" <
    36. }
    37. protected:
    38. int _age; //学号
    39. };
    40. void Test()
    41. {
    42. Student s1("张三",18);
    43. }
    44. int main()
    45. {
    46. Test();
    47. return 0;
    48. }

    不写拷贝构造函数

    子类拷贝默认调用父类的拷贝构造

    1. Student s1("张三",18);
    2. Student s2(s1);

    手动写拷贝构造

    派生类写拷贝构造要先显示调用父类拷贝构造

    1. Student(const Student& s)
    2. :Person(s)//切片,把父类的成员变量切出来去给父类拷贝构造
    3. ,_age(s._age)
    4. {
    5. cout << "Student (const student&)" << endl;
    6. }

    运算符重载

    1. Student& operator=(const Student& s)
    2. {
    3. if (&s != this)
    4. {
    5. operator=(s);
    6. _age = s._age;
    7. }
    8. cout << "student operator =(const student& s)" << endl;
    9. return *this;
    10. }

    会栈溢出,一值调子类的operator:

     这是因为子类隐藏了父类,要想调父类的必须要显示调用:

    1. Student& operator=(const Student& s)
    2. {
    3. if (&s != this)
    4. {
    5. Person::operator=(s);
    6. _age = s._age;
    7. }
    8. cout << "student operator =(const student& s)" << endl;
    9. return *this;
    10. }

    析构函数

    子类调用析构不用显示调用父类析构,系统会自动按照先子后父调用析构

    1. ~Student()
    2. {
    3. cout << "~Student()" << endl;
    4. }

     演示:

    在快结束时按fn+f11会调用析构,如下图显示:程序结束时先调用子类析构,再调用父类析构:

    静态变量的继承

    如图,子类继承父类的静态变量,我们把地址打印出来看看,发现是同一个地址,也就是并没有继承,而是直接拿来用了。

    1. class Person
    2. {
    3. public:
    4. Person() { ++_count; }
    5. protected:
    6. string _name; // 姓名
    7. public:
    8. static int _count; // 统计人的个数。
    9. };
    10. int Person::_count = 0;
    11. class Student : public Person
    12. {
    13. protected:
    14. int _stuNum; // 学号
    15. };
    16. class Graduate : public Student
    17. {
    18. protected:
    19. string _seminarCourse; // 研究科目
    20. };
    21. int main()
    22. {
    23. cout << " 人数 :" << &Person::_count << endl;
    24. cout << " 人数 :" << &Person::_count << endl;
    25. return 0;
    26. }

    菱形继承

    如下图,子类和父类中都有_name成员,我们现在访问_name成员会有二义性,编译器不知道我们要访问父类还是子类的_name。

    1. class Person
    2. {
    3. public:
    4. string _name; // 姓名
    5. };
    6. class Student : public Person
    7. {
    8. protected:
    9. int _num; //学号
    10. };
    11. class Teacher : public Person
    12. {
    13. protected:
    14. int _id; // 职工编号
    15. };
    16. class Assistant : public Student, public Teacher
    17. {
    18. protected:
    19. string _majorCourse; // 主修课程
    20. };
    21. int main()
    22. {
    23. // 这样会有二义性无法明确知道访问的是哪一个
    24. Assistant a;
    25. a._name = "peter";
    26. return 0;
    27. }

     解决方法

    1:指定:

    1. class Person
    2. {
    3. public:
    4. string _name; // 姓名
    5. };
    6. class Student : public Person
    7. {
    8. protected:
    9. int _num; //学号
    10. };
    11. class Teacher : public Person
    12. /*
    13. 虚拟继承可以解决菱形继承的二义性和数据冗余的问题。如上面的继承关系,在Student和
    14. Teacher的继承Person时使用虚拟继承,即可解决问题。需要注意的是,虚拟继承不要在其他地
    15. 方去使用。
    16. 虚拟继承解决数据冗余和二义性的原理
    17. 为了研究虚拟继承原理,我们给出了一个简化的菱形继承继承体系,再借助内存窗口观察对象成
    18. 员的模型。*/
    19. {
    20. protected:
    21. int _id; // 职工编号
    22. };
    23. class Assistant : public Student, public Teacher
    24. {
    25. protected:
    26. string _majorCourse; // 主修课程
    27. };
    28. int main()
    29. {
    30. Assistant a;
    31. // 需要显示指定访问哪个父类的成员可以解决二义性问题,但是数据冗余问题无法解决
    32. a.Student::_name = "xxx";
    33. a.Teacher::_name = "yyy";
    34. return 0;
    35. }

    2.虚继承

    class Student :virtual  public Person
    class Teacher : virtual public Person
    1. class Assistant : virtual public Student, public Teacher

    题目

     解析:

    所以选C

  • 相关阅读:
    Java方法中不使用的对象应该手动赋值为NULL吗?
    [Spring Cloud] Ribbon介绍与定义负载均衡
    【Java】HashMap、HashTable和ConcurrentHashMap的区别
    Linux的进程管理
    解决k8s node节点报错: Failed to watch *v1.Secret: unknown
    数据结构-单链表
    Docker-compose教程(安装,使用, 快速入门)
    【23种设计模式】享元模式【⭐】
    [附源码]java毕业设计图书馆自习室管理系统
    MySQL数据库之备份与恢复
  • 原文地址:https://blog.csdn.net/m0_65143871/article/details/133960310