继承和派生
在C++中,继承和派生是面向对象编程的两个重要概念,用于实现类与类之间的关系。
继承是指一个类可以从另一个类中继承属性和方法,并且可以在此基础上扩展出自己的属性和方法。被继承的类称为基类(父类),继承的类称为派生类(子类)。在C++中,可以通过以下方式定义一个派生类:
class DerivedClass : public BaseClass { // 派生类的成员变量和成员函数 };
在上面的示例中,DerivedClass是派生类,BaseClass是基类。关键字public表示使用公有继承,表示DerivedClass继承了BaseClass的所有public和protected成员,但不继承BaseClass的private成员。
派生类可以访问基类的public和protected成员,但不能访问基类的private成员。当派生类的成员变量或成员函数与基类的成员变量或成员函数同名时,可以使用作用域解析运算符::来指定调用哪个类的成员。
在派生类中,可以通过以下方式调用基类的构造函数:
class DerivedClass : public BaseClass { public: DerivedClass(int x, int y, int z) : BaseClass(x, y), m_z(z) {} private: int m_z; };
在上面的示例中,调用了BaseClass的构造函数,并将参数x和y传递给它。
派生类中还可以重写(override)基类的成员函数,即在派生类中重新定义一个和基类相同名称、参数列表和返回类型的成员函数。在调用派生类的成员函数时,会优先调用派生类中的函数,如果派生类中没有定义相应的函数,则会调用基类的函数。
继承和派生是面向对象编程的重要概念,可以实现代码的复用和扩展。在使用继承和派生时,需要注意类之间的关系,避免出现循环继承等问题。同时,需要注意访问权限的控制,避免对private成员的直接访问。
需要继承机制的例子
继承机制可以实现代码的复用和扩展,下面是一些需要继承机制的例子:
- 图形类的继承
在图形类中,可以定义一个基类Shape,包括图形的公共属性和方法,如颜色、位置、面积、周长等。然后可以通过继承机制定义各种具体的图形类,如矩形类、圆形类、三角形类等。这样,可以避免在每个具体的图形类中都定义公共的属性和方法,提高了代码的复用性。 - 汽车类的继承
在汽车类中,可以定义一个基类Vehicle,包括汽车的公共属性和方法,如品牌、型号、颜色、速度、加速度等。然后可以通过继承机制定义各种具体的汽车类,如轿车类、越野车类、卡车类等。这样,可以避免在每个具体的汽车类中都定义公共的属性和方法,提高了代码的复用性。 - 员工类的继承
在员工类中,可以定义一个基类Employee,包括员工的公共属性和方法,如姓名、性别、年龄、职位、工资等。然后可以通过继承机制定义各种具体的员工类,如经理类、销售员类、工人类等。这样,可以避免在每个具体的员工类中都定义公共的属性和方法,提高了代码的复用性。
继承机制可以极大地提高代码的复用性和可维护性,同时也可以实现代码的扩展。在使用继承机制时,需要注意类之间的关系,选择适当的继承方式,避免出现循环继承等问题。同时,需要注意访问权限的控制,避免对private成员的直接访问。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AtqB4lzN-1687179456360)(2023-06-19-20-36-13.png)]
派生类的写法
在C++中,可以通过继承来创建派生类。派生类继承了基类的所有成员函数和成员变量,并且可以在此基础上扩展出自己的成员函数和成员变量。
派生类的定义方式如下:
// 基类 class BaseClass { public: int m_varBase; void funcBase(); }; // 派生类 class DerivedClass : public BaseClass { public: int m_varDerived; void funcDerived(); };
在上面的代码中,DerivedClass是派生类,BaseClass是基类。关键字public表示使用公有继承,表示DerivedClass继承了BaseClass的所有public和protected成员,但不继承BaseClass的private成员。
当派生类的成员变量或成员函数与基类的成员变量或成员函数同名时,可以使用作用域解析运算符::来指定调用哪个类的成员。例如,可以通过DerivedClass::m_varBase来访问基类中的成员变量m_varBase。
派生类中可以重写(override)基类的成员函数,即在派生类中重新定义一个和基类相同名称、参数列表和返回类型的成员函数。在调用派生类的成员函数时,会优先调用派生类中的函数,如果派生类中没有定义相应的函数,则会调用基类的函数。
在派生类中,可以通过以下方式调用基类的构造函数:
class DerivedClass : public BaseClass { public: DerivedClass(int x, int y, int z) : BaseClass(x, y), m_varDerived(z) {} private: int m_varDerived; };
在上面的代码中,调用了BaseClass的构造函数,并将参数x和y传递给它。
继承和派生是面向对象编程的重要概念,可以实现代码的复用和扩展。在使用继承和派生时,需要注意类之间的关系,避免出现循环继承等问题。同时,需要注意访问权限的控制,避免对private成员的直接访问。
派生类对象的内存空间
派生类对象在内存中的空间由两部分组成:基类部分和派生类部分。
基类部分是从基类中继承而来的,派生类对象中包含了基类对象的完整副本。在内存中,基类对象的成员变量和成员函数的布局与基类定义的布局相同。
派生类部分是派生类自己定义的成员变量和成员函数,它们被添加到了基类对象的末尾。派生类的成员变量和成员函数的布局与派生类定义的布局相同。
由于派生类对象包含了基类对象的完整副本,因此可以通过派生类对象访问基类对象中定义的成员变量和成员函数。同时,由于基类部分和派生类部分在内存中是连续的,因此派生类对象可以强制转换为基类对象的指针或引用,并且可以在基类对象的范围内使用。
例如,假设有如下的基类和派生类:
class Base { public: int m_varBase; void funcBase() {} }; class Derived : public Base { public: int m_varDerived; void funcDerived() {} };
那么,派生类对象在内存中的布局如下图所示:
|<-- 基类部分 -->|<-- 派生类部分 -->| | m_varBase | m_varDerived | | funcBase() | funcDerived() |
可以看到,派生类对象中包含了基类对象的完整副本,基类部分和派生类部分在内存中是连续的。
需要注意的是,在派生类中访问基类成员时,需要使用作用域解析运算符::来指定基类成员的名称和访问权限,否则可能会产生二义性。例如,可以使用Base::m_varBase来访问基类中的成员变m_varBase。
继承和派生是面向对象编程的重要概念,可以实现代码的复用和扩展。在使用继承和派生时,需要注意类之间的关系,避免出现循环继承等问题。同时,需要注意访问权限的控制,避免对private成员的直接访问。
示例程序:学籍管理
下面是一个简单的学籍管理的实例程序,展示了如何使用继承来实现不同类型的学生对象。
#include <iostream> #include <string> using namespace std; // 基类:学生 class Student { public: Student(string name, int age, string gender) : m_name(name), m_age(age), m_gender(gender) {} void display() { cout << "姓名:" << m_name << endl; cout << "年龄:" << m_age << endl; cout << "性别:" << m_gender << endl; } private: string m_name; int m_age; string m_gender; }; // 派生类1:本科生 class Undergraduate : public Student { public: Undergraduate(string name, int age, string gender, string major) : Student(name, age, gender), m_major(major) {} void display() { Student::display(); cout << "专业:" << m_major << endl; } private: string m_major; }; // 派生类2:研究生 class Postgraduate : public Student { public: Postgraduate(string name, int age, string gender, string research) : Student(name, age, gender), m_research(research) {} void display() { Student::display(); cout << "研究方向:" << m_research << endl; } private: string m_research; }; int main() { Student s1("张三", 20, "男"); s1.display(); Undergraduate s2("李四", 22, "女", "计算机科学与技术"); s2.display(); Postgraduate s3("王五", 25, "男", "计算机视觉"); s3.display(); return 0;
在上面的代码中,Student是基类,包含了学生的姓名、年龄和性别信息。Undergraduate是派生类1,继承了Student的属性,并添加了专业信息。Postgraduate是派生类2,继承了Student的属性,并添加了研究方向信息。在每个派生类中,都重写了基类的display()函数,以便输出自己的属性。
在主函数中,创建了一个基类对象s1,一个Undergraduate对象s2和一个Postgraduate对象s3,分别输出了它们的属性。
继承可以实现代码的复用和扩展,使得程序更加灵活和可维护。在使用继承时,需要注意类之间的关系,选择适当的继承方式,避免出现循环继承等问题。同时,需要注意访问权限的控制,避免对private成员的直接访问。
继承关系&复合关系
继承关系和复合关系是面向对象编程中两种不同的关系。它们分别用于描述不同的对象之间的关系和组合方式。
继承关系是一种"is-a"的关系,用于描述一个类是另一个类的一种特殊形式。在继承关系中,子类继承了父类的属性和方法,并且可以在此基础上添加自己的属性和方法,从而实现代码的复用和扩展。例如,可以定义一个Animal类作为基类,然后定义Dog类和Cat类作为Animal类的子类,从而实现复用和扩展。
复合关系是一种"has-a"的关系,用于描述一个类包含另一个类的对象。在复合关系中,一个类实例化了另一个类的对象,并将其作为自己的成员变量使用。例如,可以定义一个Car类,包含了多个Wheel类对象,从而实现复杂的组合关系。
虽然继承关系和复合关系都可以用于实现代码的复用和扩展,但它们的应用场景不同。继承关系适用于描述"is-a"的关系,即一个类是另一个类的一种特殊形式;而复合关系适用于描述"has-a"的关系,即一个类包含另一个类的对象。
需要注意的是,在使用继承和复合时,需要考虑类之间的耦合性问题。继承关系会使得子类与父类之间产生紧密的耦合关系,一旦父类发生改变,子类也需要相应地进行修改。而复合关系则可以降低类之间的耦合度,使得类之间更加独立和灵活。
类之间的两种关系是继承关系和组合关系。
下面分别给出继承关系和组合关系的代码实例。
继承关系:
#include <iostream> #include <string> using namespace std; // 基类:人 class Person { public: Person(string name, int age) : m_name(name), m_age(age) {} void display() { cout << "姓名:" << m_name << endl; cout << "年龄:" << m_age << endl; } private: string m_name; int m_age; }; // 派生类:学生 class Student : public Person { public: Student(string name, int age, string school) : Person(name, age), m_school(school) {} void display() { Person::display(); cout << "学校:" << m_school << endl; } private: string m_school; }; int main() { Person p1("李四", 20); p1.display(); Student s1("张三", 18, "清华大学"); s1.display(); return 0; }
在上面的代码中,Person是基类,Student是派生类。Student继承了Person的属性和方法,并添加了自己的属性m_school。在Student中,重写了基类的display()函数,以便输出自己的属性。
组合关系:
#include <iostream> #include <string> using namespace std; // 基类:轮胎 class Tyre { public: Tyre(int size) : m_size(size) {} void display() { cout << "轮胎尺寸:" << m_size << endl; } private: int m_size; }; // 派生类:汽车 class Car { public: Car(string brand, int size) : m_brand(brand), m_tyre(size) {} void display() { cout << "品牌:" << m_brand << endl; m_tyre.display(); } private: string m_brand; Tyre m_tyre; }; int main() { Car c1("宝马", 18); c1.display(); return 0; }
在上面的代码中,Tyre是基类,Car是派生类。Car包含了一个Tyre对象m_tyre,从而实现了复杂的组合关系。在Car中,重写了自己的display()函数,以便输出自己的属性和包含的Tyre对象的属性。
需要注意的是,在使用继承和组合时,需要考虑类之间的耦合性问题。继承关系会使得子类与父类之间产生紧密的耦合关系,一旦父类发生改变,子类也需要相应地进行修改。而组合关系则可以降低类之间的耦合度,使得类之间更加独立和灵活。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bMjIzwXI-1687179456362)(2023-06-19-20-43-47.png)]
复合关系的使用
复合关系是面向对象编程中的一种关系,指一个类对象包含了另一个类对象,用于描述对象之间的组合关系。
下面以一个简单的图形类作为例子,说明复合关系的使用。
#include <iostream> #include <string> using namespace std; // 点类 class Point { public: Point(int x, int y) : m_x(x), m_y(y) {} private: int m_x; int m_y; }; // 图形类 class Shape { public: Shape(string type, int width, int height, int x, int y) : m_type(type), m_width(width), m_height(height), m_point(x, y) {} void display() { cout << "图形类型:" << m_type << endl; cout << "宽度:" << m_width << endl; cout << "高度:" << m_height << endl; cout << "位置:" << m_point.m_x << ", " << m_point.m_y << endl; } private: string m_type; int m_width; int m_height; Point m_point; // 复合关系 }; int main() { Shape s("矩形", 100, 50, 10, 20); s.display(); return 0; }
在上述代码中,Point类表示坐标点,Shape类表示图形类,包含了图形类型、宽度、高度和位置信息,其中位置信息通过复合关系包含了一个Point对象。在Shape类中,定义了display()函数以便输出各个属性。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3e52S4Z1-1687179456362)(2023-06-19-20-46-31.png)]
复合关系的使用
正确的写法: 为“狗”类设一个“业主”类的对象指针; 为“业主”类设一个“狗”类的对象指针数组。 class CMaster; //CMaster必须提前声明,不能先 //写CMaster类后写Cdog类 class CDog { CMaster * pm; }; class CMaster { CDog * dogs[10]; };
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1uNQjjlq-1687179456363)(2023-06-19-20-47-14.png)]