C++--第五课-继承

    xiaoxiao2021-03-26  31

    继承中的几种关系

    1. is a的关系

    比如说基类狗,派生了肉狗和宠物狗,我们就可以说肉狗是狗,也可以说宠物狗是狗,这就是is a的关系。但是需要注意的是,并非所有的继承都是is a的关系。

    2. has a的关系

    继承中的权限继承

    class Base { public: int public_i_; // 外部可访问 protected: int protected_i_; // 内部可访问,外部无法访问, 可继承,子类可访问 private: int private_i_; // 内部可访问,外部不可访问, 不可继承,子类不可访问 }; class A : public Base // public继承所有的权限都没有发生改变 { void SetI() { protected_i_ = 100; } }; class B : protected Base // protected 继承会将基类中public的权限改为protected权限 { void SetI() { protected_i_ = 100; } }; class C : private Base // 将基类中的protectedpublic权限都改为private权限 { void SetI() { protected_i_ = 100; } };

    基类中有默认构造函数的继承中的构造和析构顺序

    #include <iostream> class Base { public: Base() { std::cout << "Base::Base()" << std::endl; } ~Base() { std::cout << "Base::~Base()" << std::endl; } int GetNum() { return num_; } private: int num_; }; class A : public Base { public: A() { std::cout << "A::A()" << std::endl; } ~A() { std::cout << "A::~A()" << std::endl; } }; int main() { A demo; demo.GetNum(); return 0; }

    基类中没有默认构造函数的继承中的构造和析构顺序

    #include <iostream> class Base { public: Base(int num) : num_(num) { std::cout << "Base::Base()" << std::endl; } ~Base() { std::cout << "Base::~Base()" << std::endl; } int GetNum() { return num_; } private: int num_; }; class A : public Base { public: A() : Base(10) // 此时会默认的调用基类的默认构造函数,如果基类中没有默认的构造函数时, // 那么必须在派生类中的初始化列表中显示的来构造 { std::cout << "A::A()" << std::endl; } ~A() { std::cout << "A::~A()" << std::endl; } }; int main() { A demo; demo.GetNum(); return 0; }

    继承中的函数,派生类中没有实现基类的函数方法

    #include <iostream> class Base { public: Base(int num) : num_(num) { std::cout << "Base::Base()" << std::endl; } ~Base() { std::cout << "Base::~Base()" << std::endl; } int GetNum() const { return num_; } private: int num_; }; class A : public Base { public: A(int num) : Base(0), num_(num) // 此时会默认的调用基类的默认构造函数,如果基类中没有默认的构造函数时, // 那么必须在派生类中的初始化列表中显示的来构造 { std::cout << "A::A()" << std::endl; } ~A() { std::cout << "A::~A()" << std::endl; } //int GetNum() const //{ // return num_; //} private: int num_; }; int main() { A demo(100); demo.GetNum(); std::cout << demo.GetNum() << std::endl; return 0; }

    结果是

    另一种情况是,派生类中实现了基类的函数方法

    #include <iostream> class Base { public: Base(int num) : num_(num) { std::cout << "Base::Base()" << std::endl; } ~Base() { std::cout << "Base::~Base()" << std::endl; } int GetNum() const { return num_; } private: int num_; }; class A : public Base { public: A(int num) : Base(0), num_(num) // 此时会默认的调用基类的默认构造函数,如果基类中没有默认的构造函数时, // 那么必须在派生类中的初始化列表中显示的来构造 { std::cout << "A::A()" << std::endl; } ~A() { std::cout << "A::~A()" << std::endl; } int GetNum() const { return num_; } private: int num_; }; int main() { A demo(100); demo.GetNum(); std::cout << demo.GetNum() << std::endl; return 0; }

    输出结果是

    基类指针指向派生类对象的函数调用情况

    #include <iostream> class Base { public: Base(int num) : num_(num) { std::cout << "Base::Base()" << std::endl; } ~Base() { std::cout << "Base::~Base()" << std::endl; } int GetNum() const { std::cout << "Base::GetNum()" << std::endl; return num_; } private: int num_; }; class A : public Base { public: A(int num) : Base(0), num_(num) // 此时会默认的调用基类的默认构造函数,如果基类中没有默认的构造函数时, // 那么必须在派生类中的初始化列表中显示的来构造 { std::cout << "A::A()" << std::endl; } ~A() { std::cout << "A::~A()" << std::endl; } int GetNum() const // 这个函数被重写了,也就是把基类中的方法给覆盖了,基类中的方法就不存在了 { std::cout << "A::GetNum()" << std::endl; return num_; } private: int num_; }; int main() { Base base(100); A a(200); Base *pBase = &a; // 因为 A is Base // 宠物狗 Is 狗 对 狗 泛指 宠物狗 // 狗 Is 宠物狗 错 std::cout << pBase->GetNum() << std::endl; // 调用的是 A 中的 Base中的GetNum方法,但是这样很显然不是我们想要的结果 // 这种用基类的指针指向派生类的对象的情况很多 return 0; }

    输出结果是

    使用虚函数在继承中函数调用的好处

    #include <iostream> class Base { public: Base(int num) : num_(num) { std::cout << "Base::Base()" << std::endl; } ~Base() { std::cout << "Base::~Base()" << std::endl; } virtual int GetNum() const { std::cout << "Base::GetNum()" << std::endl; return num_; } private: int num_; }; class A : public Base { public: A(int num) : Base(0), num_(num) // 此时会默认的调用基类的默认构造函数,如果基类中没有默认的构造函数时, // 那么必须在派生类中的初始化列表中显示的来构造 { std::cout << "A::A()" << std::endl; } ~A() { std::cout << "A::~A()" << std::endl; } int GetNum() const // 这个函数被重写了,也就是把基类中的方法给覆盖了,基类中的方法就不存在了 { std::cout << "A::GetNum()" << std::endl; return num_; } private: int num_; }; int main() { Base base(100); A a(200); Base *pBase = &a; // 因为 A is Base // 宠物狗 Is 狗 对 狗 泛指 宠物狗 // 狗 Is 宠物狗 错 std::cout << pBase->GetNum() << std::endl; // 调用的是 A 中的 Base中的GetNum方法,但是这样很显然不是我们想要的结果 // ,使用虚函数就可以满足我们的需求,我们在基类中的GetNum前面加上virtual关键字 // 这种用基类的指针指向派生类的对象的情况很多 return 0; }

    结果是 为什么会这样呢?因为使用了虚函数,虚函数用来指明派生类中需要调用的函数,也就是说会在派生类中优先寻找要调用的函数,而不是在基类中寻找这个函数。

    虚函数的其它例子

    派生类继承并操作了基类中的成员变量

    继承了基类中的str_变量,并对其进行了操作,导致内存泄漏,并且在析构的时候重复析构。

    #define _CRT_SECURE_NO_WARNINGS #include <iostream> #include <cstring> class String { public: String(const char *str = "") { unsigned int len = strlen(str); str_ = new char[len + sizeof(char)]; strcpy(str_, str); } ~String() { delete[] str_; } String &operator+=(const String &other) { unsigned int len = strlen(str_) + strlen(other.str_); char *temp = new char[len + sizeof(char)]; strcpy(temp, str_); strcat(temp, other.str_); delete[] str_; str_ = temp; return *this; } String operator+(const String &other) { String demo; demo += other; return demo; } protected: char *str_; }; class MyString : public String { public: MyString(const char *str = "PoEdu") { unsigned int len = strlen(str); str_ = new char[len + sizeof(char)]; strcpy(str_, str); } MyString operator+(const MyString &other) { } ~MyString() { delete[] str_; str_ = nullptr; } }; int main() { MyString str; //MyString other("Hello"); return 0; }
    调用基类的构造函数来操作基类的成员变量,而不是在派生类中直接操作基类只能怪的成员变量
    #define _CRT_SECURE_NO_WARNINGS #include <iostream> #include <cstring> class String { public: String(const char *str = "") { unsigned int len = strlen(str); str_ = new char[len + sizeof(char)]; strcpy(str_, str); } ~String() { delete[] str_; } String &operator+=(const String &other) { unsigned int len = strlen(str_) + strlen(other.str_); char *temp = new char[len + sizeof(char)]; strcpy(temp, str_); strcat(temp, other.str_); delete[] str_; str_ = temp; return *this; } String operator+(const String &other) { String demo; demo += other; return demo; } protected: char *str_; }; class MyString : public String { public: MyString(const char *str = "PoEdu") : String(str) { /*unsigned int len = strlen(str); str_ = new char[len + sizeof(char)]; strcpy(str_, str);*/ } MyString operator+(const MyString &other) { } ~MyString() { delete[] str_; str_ = nullptr; } }; int main() { MyString str; //MyString other("Hello"); return 0; }

    这样虽然保证了只对基类中的成员变量操作一次,但是在析构的时候还是会重复析构。

    虚析构函数的使用情形

    没有虚析构函数的析构情形

    #define _CRT_SECURE_NO_WARNINGS #include <iostream> #include <cstring> class A { public: ~A() { std::cout << "~A()" << std::endl; } }; class B : public A { public: ~B() { std::cout << "~B(()" << std::endl; } }; int main() { A *pA = new B(); delete pA; return 0; }

    运行结果如下图所示 这样只是调用了基类中的析构函数,并没有调用派生类中的析构函数

    有虚析构函数的析构情形

    #define _CRT_SECURE_NO_WARNINGS #include <iostream> #include <cstring> class A { public: virtual ~A() { std::cout << "~A()" << std::endl; } }; class B : public A { public: ~B() { std::cout << "~B(()" << std::endl; } }; int main() { A *pA = new B(); delete pA; return 0; }

    运行结果如下: 这样就会把派生类中的析构函数也调用了。

    正确的做法是只需要管理好本类中的成员变量即可,无需管理继承下来的成员变量(其实我觉得在实际的应用中,成员变量应该都是private的,是不需要被子类继承的,只有函数方法才需要被继承下来)
    #define _CRT_SECURE_NO_WARNINGS #include <iostream> #include <cstring> class String { public: String(const char *str = "") { unsigned int len = strlen(str); str_ = new char[len + sizeof(char)]; strcpy(str_, str); } ~String() { delete[] str_; } String &operator+=(const String &other) { unsigned int len = strlen(str_) + strlen(other.str_); char *temp = new char[len + sizeof(char)]; strcpy(temp, str_); strcat(temp, other.str_); delete[] str_; str_ = temp; return *this; } String operator+(const String &other) { String demo; demo += other; return demo; } protected: char *str_; }; class MyString : public String { public: MyString(const char *str = "PoEdu") : String(str) { length_ = new int(0); /*unsigned int len = strlen(str); str_ = new char[len + sizeof(char)]; strcpy(str_, str);*/ } MyString operator+(const MyString &other) { } ~MyString() { //delete[] str_; //str_ = nullptr; // 正确的方法是不要去管理基类中的成员变量,只需要管理本类中的成员变量即可 delete length_; // 只需要管理本类中的length变量就可以了 } private: int *length_; }; int main() { //MyString str; //MyString other("Hello"); return 0; }

    这样就保证了程序的正确运行。

    虚函数的调用

    #define _CRT_SECURE_NO_WARNINGS #include <iostream> #include <cstring> class String { public: String(const char *str = "") { unsigned int len = strlen(str); str_ = new char[len + sizeof(char)]; strcpy(str_, str); } String(const String &other) { unsigned int len = strlen(other.str_); str_ = new char[len + sizeof(char)]; strcpy(str_, other.str_); } ~String() { delete[] str_; } String &operator+=(const String &other) { unsigned int len = strlen(str_) + strlen(other.str_); char *temp = new char[len + sizeof(char)]; strcpy(temp, str_); strcat(temp, other.str_); delete[] str_; str_ = temp; return *this; } String operator+(const String &other) { String demo(*this); demo += other; return demo; } friend std::ostream &operator<<(std::ostream &os, const String &other) { os << other.str_; return os; } protected: char *str_; }; class MyString : public String { public: MyString(const char *str = "PoEdu") : String(str) { } MyString operator+(const MyString &other) { MyString temp(*this); temp += other; // 因为从基类中继承了一个 += 操作符 temp += "---------PoEdu"; return temp; // 此时在返回的时候会调用拷贝构造函数,如果没有实现拷贝构造函数, // 那么就会出现浅拷贝的情况,导致程序运行错误,所以我们必须手动实现拷贝构造函数 } }; int main() { MyString str("I Love "); String *pString = &str; std::cout << str + ("Mark") << std::endl; std::cout << *pString + ("Mark") << std::endl; return 0; }

    运行结果

    继承中的虚析构函数

    基类中的析构函数不是虚析构函数,而是普通函数的情况:

    #define _CRT_SECURE_NO_WARNINGS #include <iostream> #include <cstring> class A { public: ~A() { std::cout << "~A()" << std::endl; } }; class B : public A { public: B() : A() { demo_ = new int(0); } ~B() { std::cout << "~B()" << std::endl; delete demo_; } private: int *demo_; }; int main() { A * pA = new B(); delete pA; return 0; }

    运行结果是: 从结果上可以看出,在delete的时候,只是调用了基类中的析构函数,并没有调用派生类中的析构函数,那么,如何能让派生类中的析构函数也被调用呢?请看下面的情况!!! 基类中的析构函数是虚析构函数的情况:

    #define _CRT_SECURE_NO_WARNINGS #include <iostream> #include <cstring> class A { public: virtual ~A() { std::cout << "~A()" << std::endl; } }; class B : public A { public: B() : A() { demo_ = new int(0); } ~B() { std::cout << "~B()" << std::endl; delete demo_; } private: int *demo_; }; int main() { A * pA = new B(); delete pA; return 0; }

    运行结果: 关于继承的构造和析构顺序的总结: 一般的继承体系的类 基类构造 基类成员构造 子类构造 子类析构 基类成员析构 基类析构

    继承中的虚继承

    著名的菱形继承

    #define _CRT_SECURE_NO_WARNINGS #include <iostream> #include <cstring> class A { public: int a_; }; class B : public A { public: int b_; }; class C : public A { public: int c_; }; class D : public B, public C { // a_ b_ c_,并且a_是两个 // 我们称这种继承方式为菱形继承 }; int main() { D d; d.a_; return 0; }

    此时在编译的时候就会出错,如下所示: 但是,我们也是可以访问的

    #define _CRT_SECURE_NO_WARNINGS #include <iostream> #include <cstring> class A { public: int a_; }; class B : public A { public: int b_; }; class C : public A { public: int c_; }; class D : public B, public C { // a_ b_ c_,并且a_是两个 // 我们称这种继承方式为菱形继承 }; int main() { D d; d.B::a_; d.C::a_; return 0; }

    指明要访问哪个基类中的变量,就不会出现编译错误了。 这样虽然解决了编译错误的问题,但是我们不需要这样的情况,我们只需要一份变量就可以了,那么这样该怎么解决呢?我们可以通过虚继承来解决这个问题!!!

    #define _CRT_SECURE_NO_WARNINGS #include <iostream> #include <cstring> class A { public: int a_; }; class B : virtual public A { public: int b_; }; class C : virtual public A { public: int c_; }; class D : virtual public B, virtual public C { // a_ b_ c_,并且a_是两个 // 我们称这种继承方式为菱形继承 }; int main() { D d; d.a_; return 0; }

    此时编译也就可以通过了。

    继承中的纯虚函数

    #define _CRT_SECURE_NO_WARNINGS #include <iostream> #include <cstring> class Animal { public: virtual void Cry() = 0; // 纯虚函数,无需实现,需要子类来实现 }; class Dog : public Animal { public: }; int main() { Dog dog; // 此时编译不能通过,因为没有实现基类中的纯虚函数 return 0; } #define _CRT_SECURE_NO_WARNINGS #include <iostream> #include <cstring> class Animal { public: virtual void Cry() = 0; // 纯虚函数,无需实现,需要子类来实现 }; class Dog : public Animal { public: void Cry() { } }; int main() { Dog dog; // 当实现了基类中的纯虚函数后,就能实例化对象了 return 0; } #define _CRT_SECURE_NO_WARNINGS #include <iostream> #include <cstring> // 纯虚函数 不能实例化 class Animal { public: virtual void Cry() = 0; // 纯虚函数,无需实现,需要子类来实现(强制性的进行实现) }; class Dog : public Animal { public: void Cry() { } }; class A : public Dog { }; int main() { Dog dog; // 当实现了基类中的纯虚函数后,就能实例化对象了 // 只要有纯虚函数的类,我们称之为抽象类,抽象类是无法实例化的 A a; // 此时A类就可以实例化,因为Dog类中已经实现了纯虚函数 return 0; } #define _CRT_SECURE_NO_WARNINGS #include <iostream> #include <cstring> // 纯虚函数 不能实例化 是其它类的基类 经常用来作为接口使用 接口是用来进行解耦的 // 接口 // 没有任何的数据成员,只是把函数名列出来 class Animal { public: virtual void Cry() = 0; // 纯虚函数,无需实现,需要子类来实现(强制性的进行实现) virtual void Jump() = 0; // 跳的接口 }; class Dog : public Animal { public: void Cry() { } }; class A : public Dog { }; int main() { Dog *dog = new A(); // 用来指向子类 dog->Cry(); return 0; }
    转载请注明原文地址: https://ju.6miu.com/read-662731.html

    最新回复(0)