欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 科技 > 名人名企 > C++-第八章续:继承

C++-第八章续:继承

2025/4/24 12:49:06 来源:https://blog.csdn.net/2303_78095330/article/details/145410436  浏览:    关键词:C++-第八章续:继承

目录

第一节:析构函数

第二节:static

第三节:单继承与多继承

        3-1.单继承

        3-2.多继承

第四节:菱形继承

        4-1.数据冗余

        4-2.二义性

        4-3.解决办法

第五节:多继承的切片顺序

第六节:下期预告


第一节:析构函数

        上一章中的子类Student 和父类 Person:

// 基类
class Person { 
public:void WhoAmI() {std::cout << "Person" << std::endl;}std::string& Name() {return _name;}
private:std::string _name;
};// 子类
class Student:public Person {
public:std::string& StdId() {return _stdId;}std::string& Name() { // 重定义return _name; // 直接访问的是重定义后的_name}std::string _name; // 重定义
private:std::string _stdId; 
};

        那么子类的析构函数名为~Student,父类的析构函数名为~Person,看起来是不一样的,但是类的析构函数的名字实际上都是destructor。

        这就意味着子类有两个析构函数,一个是它自己的,一个是从父类继承的,因为都叫destructor,所以子类析构函数会对父类析构函数造成屏蔽。

        又因为父类是子类的切片,可以将子类的父类部分看作一个自定义类型,所以子类的析构函数会调用父类的析构函数,因为被屏蔽了,需要显式调用:

~子类()
{父类::~父类(); // 显式调用......
}

        上述伪代码只是一种容易理解才这样写,实际子类的析构函数不需要写出调用父类析构的代码,这是因为子类析构结束时会自动调用父类的析构函数:

// 基类
class Person { 
public:~Person() {std::cout << "父类析构" << std::endl;}
private:std::string _name;
};// 子类
class Student:public Person {
public:~Student() { // 没有写出调用父类析构的函数std::cout << "子类析构" << std::endl;}
private:std::string _stdId; 
};int main() {Student st1;return 0;
}

  

第二节:static

        在类中用 static 修饰的成员是静态成员,它的特点就是该类的所有实例共享这一个静态成员。

如果一个子类继承了一个父类,那么子类的所有实例也会与父类的所有实例共享这一个静态成员。

        即父类的静态成员由父类与子类共享。

        

        例如我想统计Person和Student一共实例化了多少个对象,就可以在Person中定义一个静态成员count,只要调用父类的构造函数就自增1:

// 基类
class Person { 
public:Person() {_count += 1;}static size_t _count;
private:std::string _name;
};
size_t Person::_count = 0;// 子类
class Student:public Person {
private:std::string _stdId; 
};int main() {std::vector<Person*> pv(100);for (int i = 0; i < 100; i++) // 创建100个Person对象pv[i]=new Person();std::vector<Student*> sv(1000);for (int i = 0; i < 1000; i++) // 创建1000个Student对象sv[i] = new Student;std::cout << "Person:" << Person::_count << std::endl;std::cout << "Student:" << Student::_count << std::endl;return 0;
}

  

        可以发现Person与Student一共创建了1100次,而且它们都有_count并且是一样的。

        可能会有人疑惑为什么上述代码中Student类的构造函数并没有将_count自增1,为什么结果不是100呢?这是因为上一章的默认构造说过的子类的默认构造会调用父类的默认构造,故子类实例化时会调用父类构造使_count自增1。

        

        值得注意的是子类新增的静态成员不与其父类共享。

第三节:单继承与多继承

        3-1.单继承

        单继承指只有一个直接父类的继承方式,例如:

        Student只有一个直接父类Person;

        Graduate只有一个直接父类Student,它们都是单继承。

        3-2.多继承

        与单继承相反,多继承指有两个或以上的直接父类,例如:

        这样类Assistant就会同时继承类Student和Teacher,它就有两个直接父类。

        

        如果类Student、Teacher有一个共同的父类Person,此时就会形成菱形继承。

第四节:菱形继承

        菱形继承指一个类的两个或者多个父类中,有两个或以上的父类又有一个共同的父类,例如:

        菱形继承有什么问题呢?菱形继承会造成数据冗余、二义性的问题。

        4-1.数据冗余

        Student和Teacher都继承了Person,那么它们各有一份Person切片,那么同时继承了Student和Teacher的Assistant就会有两份Person切片。

        然而实际上Assistant用不上两份Person切片,因为这两份切分的内容是一模一样的,这就造成了数据冗余。

        4-2.二义性

        假如Person中保存了名字和身份证号码等信息,那么对于Assistant来说,它有两份Person,当访问名字时就不知道访问继承自Student的名字还是Teacher的名字,就必须使用显式调用,此为二义性问题。

        4-3.解决办法

        可以用虚继承的方式解决数据冗余和二义性的问题。

        如果一个子类使用了虚继承,那么这个子类会用一个指针来代替父类切片,这个指针可以访问到父类的成员变量和成员函数,这个指针叫做虚基类指针

        如果这个父类又被其他子类虚继承,那么其他子类的虚基类指针也指向同一块数据,即多个子类共享一份父类。

        这就意味着如果又有一个新子类继承上述多个子类,新子类继承的多个虚基类指针就是同一个,指向同一个数据,这就解决了数据冗余和二义性的问题。

        虚继承的使用方式:

         

第五节:多继承的切片顺序

        多继承是一个类有多个直接父类,这些父类的切片在子类中的顺序是写在前面的在上面,例如:

        子类Assistant的结构示意图:

        请阅读以下代码,在上图中标注出指针ptr1,ptr2,ptr3分别指向哪个位置:

// 基类
class Person { 
public:Person() {_count += 1;}static size_t _count;std::string _name;
private:};
size_t Person::_count = 0;// 子类
class Student:public Person {};class Teacher:public Person{};class Assistant:public Student,public Teacher{};int main() {Assistant as;Assistant* ptr1 = &as;Student* ptr2 = &as;Teacher* ptr3 = &as;return 0;
}

  

        显然,它们的位置关系为:

        证明,打印Student的大小和ptr1与ptr3的差值和ptr2与ptr3的差值:

int main() {Assistant as;Assistant* ptr1 = &as;Student* ptr2 = &as;Teacher* ptr3 = &as;std::cout << "ptr3-ptr2:    " << (size_t)ptr3 - (size_t)ptr2 << std::endl;std::cout << "ptr3-ptr1:    " << (size_t)ptr3 - (size_t)ptr1 << std::endl;std::cout << "Student的大小:" << sizeof(Student) << std::endl;return 0;
}

  

第六节:下期预告

        至此类的继承已经讲完了,下一次将开始讲述类的多态。

        多态就是父类和子类去调用同一个函数,这个函数能识别调用者的身份并作出不同的响应。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词