C++核心编程—面向对象的三大特性—继承
文章目录
- C++核心编程---面向对象的三大特性---继承
- 1. 基本继承语法
- 2. 继承方式
- 3. 多重继承
- 4. 构造和析构顺序
- 4.1 构造函数的调用顺序:
- 4.2 析构函数的调用顺序:
- 5. 虚继承
- 6. 访问基类成员
- 7. 同名成员和同名静态成员的处理
- 1. 成员变量的处理
- 2. 成员函数的处理
- 3. 静态成员的处理
- 4. 示例代码
C++中的继承是面向对象编程的一个核心特性,它允许我们创建一个类(派生类),该类可以从已有的类(基类)中继承属性和行为。
1. 基本继承语法
在C++中,使用冒号(:)和访问修饰符来表示继承关系。访问修饰符可以是public
、protected
或private
,它们定义了基类成员在派生类中的可访问性。
class 基类名 {// 基类的成员
};class 派生类名 : 访问修饰符 基类名 {// 派生类的成员
};//例如:
//
// Created by 86189 on 2024/6/21.
//
#include <iostream>
#include <utility>using namespace std;class Base {
public:int age{};int sex{};string name = " ";string phone = " ";
};class Student : public Base {
public:string job;Student(string name, int age, int sex, string phone, const string& job) {this->name = std::move(name);this->age = age;this->sex = sex;this->phone = std::move(phone);this->job = job;}
};ostream &operator<<(ostream &out, Student &s) {out << "姓名:" << s.name << " 年龄:" << s.age << " 性别:" << s.sex << " 电话:" << s.phone << " 职业:" << s.job;return out;
}class Teacher : public Base {
public:string job;Teacher(string name, int age, int sex, string phone, const string& job) {this->name = std::move(name);this->age = age;this->sex = sex;this->phone = std::move(phone);this->job = job;}
};ostream &operator<<(ostream &out, Teacher &t) {out << "姓名:" << t.name << " 年龄:" << t.age << " 性别:" << t.sex << " 电话:" << t.phone << " 职业:" << t.job;return out;
}int main() {Student s("张三", 18, 1, "123456789", "学生");Teacher t("李四", 18, 1, "123456789", "教师");cout << s << endl;cout << t << endl;return 0;
}
2. 继承方式
public
继承:基类的公有成员在派生类中仍为公有;保护成员变为派生类的保护成员;私有成员对派生类不可见,但影响派生类的布局。protected
继承:基类的公有和保护成员都变为派生类的保护成员;私有成员对派生类不可见。private
继承:基类的所有公有和保护成员都变为派生类的私有成员;私有成员对派生类不可见。
//
// Created by 86189 on 2024/6/21.
//
#include <iostream>using namespace std;class Base{
public:int age;
protected:int height;
private:int weight;
};class Son:public Base{
public:Son(int a,int b) : Base(){age = a;height = b;}
};
class Son2:private Base{
public:Son2(int a,int b) : Base(){age = a;height = b;}
};class Son3:protected Base{
public:Son3(int a) : Base(){age = a;//weight = b; 无法访问}
};int main(){Son s(10,20);Son2 s2(10,20);Son3 s3(10);cout<<s.age<<endl;
// cout<<s2.age<<endl; 保护成员 无法访问
// cout<<s3.age<<endl; 私有成员 无法访问return 0;
}
注意:从父类继承的属性同样位于子类上。
3. 多重继承
C++还支持一个派生类从多个基类继承,这称为多重继承。在多重继承的情况下,访问修饰符需要分别指定。
class 派生类名 : 访问修饰符 基类名1, 访问修饰符 基类名2, ... {// 派生类的成员
};//
// Created by 86189 on 2024/6/21.
//#include <iostream>using namespace std;class Base1 {
public:int a;static void func1() {cout << "Base1::func1" << endl;}
};class Base2 {
public:int a;static void func2() {cout << "Base2::func2" << endl;}
};class Derived : public Base1, public Base2 {
public:int b;Derived(int a, int b,int c) : Base1(), Base2() {Base1::a = a;this->b = b;Base2::a = c;}static void func3() {cout << "Derived::func3" << endl;}
};int main() {Derived d(1, 2,3);cout << d.Base1::a << endl;cout << d.Base2::a << endl;Derived::func1();Derived::func2();Derived::func3();cout << d.b << endl;return 0;
}
4. 构造和析构顺序
在C++中,继承涉及的构造函数和析构函数调用顺序遵循以下规则:
4.1 构造函数的调用顺序:
-
基类构造函数:当创建派生类的对象时,首先会调用基类的构造函数。如果有多个基类(多继承情况),则按照派生类定义时基类出现的顺序调用。每个基类构造函数按其自身的参数列表进行初始化。
-
成员对象构造函数:接下来,按照它们在派生类中声明的顺序,调用派生类中任何非静态成员对象的构造函数。
-
派生类构造函数:最后,调用派生类自身的构造函数体。这里可以进一步初始化派生类新增的成员变量。
4.2 析构函数的调用顺序:
析构函数的调用顺序与构造函数的调用顺序相反:
-
派生类析构函数:当派生类对象生命周期结束时,首先执行派生类的析构函数体,释放派生类特有的资源。
-
成员对象析构函数:接着,按照它们在派生类中声明的逆序,调用派生类中非静态成员对象的析构函数。
-
基类析构函数:最后,按照派生类继承列表中基类出现的逆序调用基类的析构函数,释放基类的资源。
总结来说,构造函数是从基类到派生类、从父到子的方向进行初始化,而析构函数则是从派生类到基类、从子到父的方向进行资源的清理。这样的设计保证了资源的正确分配和释放。
5. 虚继承
虚继承是用来解决多重继承中可能出现的二义性和数据冗余问题的。当一个类作为多个派生类的基类时,如果每个派生类都包含基类的一个完整副本,这将导致存储空间的浪费。通过在派生类声明时使用virtual
关键字,可以实现虚继承。
class 基类名 {// ...
};class 派生类1 : virtual public 基类名 {// ...
};class 派生类2 : virtual public 基类名 {// ...
};//
// Created by 86189 on 2024/6/21.
//
#include <iostream>using namespace std;class Base{
public:int age;
};class Son1 : virtual public Base{
public:int sex{};
};class Son2 : virtual public Base{
public:int high{};
};class GrandSon : public Son1 , public Son2{
public:int weight{};
};int main(){Son1 s1;Son2 s2;s1.age = 18;s2.age = 20;cout << s1.age << endl;cout << s2.age << endl;GrandSon g;g.age = 20;cout << g.age << endl;return 0;
}
6. 访问基类成员
在派生类中,可以直接访问基类的公有和受保护成员(根据继承的访问权限)。如果需要明确地指明成员来自哪个基类,可以使用基类名::
操作符。
class Base {
public:void baseFunction() { /*...*/ }
};class Derived : public Base {
public:void derivedFunction() {baseFunction(); // 直接调用Base::baseFunction(); // 明确指定基类}
};
7. 同名成员和同名静态成员的处理
在C++继承中,如果派生类(子类)和基类(父类)拥有同名的成员(包括数据成员和成员函数),可以通过以下几种方式来解决同名成员的访问问题:
1. 成员变量的处理
- 优先级:如果派生类中有一个与基类同名的数据成员,那么通过派生类对象访问该同名成员时,将优先访问派生类的成员变量。
- 访问基类成员:如果需要访问被派生类同名成员隐藏的基类成员,可以使用作用域解析运算符
::
明确指定基类的作用域。例如,baseClass::memberName
。
2. 成员函数的处理
- 覆盖(Overriding)与隐藏:如果派生类定义了一个与基类同名的成员函数,这可能构成重写(如果是虚函数)或隐藏(如果是非虚函数或参数列表不同)。对于虚函数,派生类会覆盖基类的同名函数,而对于非虚函数或签名不同的函数,则是隐藏基类的同名函数。
- 访问基类函数:如果要访问被派生类同名函数隐藏的基类函数,同样需要使用作用域解析运算符,如
baseClass::functionName()
。 - 注意重写的规则:重写要求函数签名(除了返回类型外的参数列表)必须与基类中的虚函数完全匹配,并且访问权限不能比基类中的更严格。
3. 静态成员的处理
- 静态成员的处理方式与非静态成员类似,即派生类可以重新定义同名的静态成员,但访问时需明确指定是基类还是派生类的静态成员。
4. 示例代码
class Base {
public:int var = 10;void display() { cout << "Base's var: " << var << endl; }
};class Derived : public Base {
public:int var = 20;void display() { cout << "Derived's var: " << var << endl; }void accessBaseVar() {cout << "Accessing Base's var: " << Base::var << endl; // 明确访问基类的var}void callBaseDisplay() {Base::display(); // 明确调用基类的display函数}
};int main() {Derived d;d.display(); // 输出 Derived's var: 20d.accessBaseVar(); // 输出 Accessing Base's var: 10d.callBaseDisplay(); // 输出 Base's var: 10return 0;
}
这段代码展示了如何在派生类中处理同名的成员变量和成员函数,以及如何通过作用域解析运算符访问基类的同名成员。
同时:需要注意的是,当基类的成员函数有重载时,需要指定基类的作用域才可以访问到发生了重载的函数。