继承是面向对象三大特性之一。
代码重用性:通过继承,子类可以继承父类的属性和方法,从而实现代码的复用。子类不需要从头开始编写相同的代码,而是可以直接使用父类已有的代码,减少了代码的冗余。
语法:class 子类:继承方式: 父类
子类 也称 派生类
父类 也称 基类
#include
using namespace std;
// 定义父类
class 形状
{
public:
void 显示()
{
cout << "这是一个形状。" << endl;
}
};
// 定义子类,并继承父类
class 矩形 : public 形状
{
public:
void 显示()
{
cout << "这是一个矩形。" << endl;
}
};
int main()
{
矩形 r;
r.显示(); // 调用子类的显示方法
r.形状::显示(); // 调用父类的显示方法
return 0;
}
在这个例子中,Shape
类被重命名为形状
类,其display
方法被重命名为显示
方法。矩形
类继承自形状
类,并重写了显示
方法。在main
函数中,我们创建了一个矩形
对象r
,并分别调用了子类和父类的显示
方法。运行程序将输出:
这是一个矩形。
这是一个形状。
父类
+----------------+
| |
公有 | 公有成员 |
| |
+----------------+
| |
保护 | 保护成员 |
| |
+----------------+
| |
私有 | 私有成员 |
| |
+----------------+
^
|
|
继承方式
在这个示例中,我们可以看到:
优先级:私有>保护>共有(子类共有改不了父;,子类保护将父类共有改为保护,其余不变;子类私有将父类所以的都改为私有)注意:父类私有不可访问。
#include
// 父类
class Parent
{
public:
void publicMethod()
{
std::cout << "This is a public method in the Parent class." << std::endl;
}
protected:
void protectedMethod()
{
std::cout << "This is a protected method in the Parent class." << std::endl;
}
private:
void privateMethod()
{
std::cout << "This is a private method in the Parent class." << std::endl;
}
};
// 公有继承
class PublicChild : public Parent
{
public:
void accessBaseClassMembers()
{
publicMethod(); // 可以访问父类的公有成员
protectedMethod(); // 可以访问父类的保护成员
//privateMethod(); // 无法访问父类的私有成员
}
};
// 私有继承
class PrivateChild : private Parent
{
public:
void accessBaseClassMembers()
{
publicMethod(); // 可以访问父类的公有成员
protectedMethod(); // 可以访问父类的保护成员
//privateMethod(); // 无法访问父类的私有成员
}
};
// 保护继承
class ProtectedChild : protected Parent
{
public:
void accessBaseClassMembers()
{
publicMethod(); // 可以访问父类的公有成员
protectedMethod(); // 可以访问父类的保护成员
//privateMethod(); // 无法访问父类的私有成员
}
};
int main()
{
PublicChild publicObj;
publicObj.accessBaseClassMembers();
PrivateChild privateObj;
privateObj.accessBaseClassMembers();
ProtectedChild protectedObj;
protectedObj.accessBaseClassMembers();
return 0;
}
在这个示例代码中,我们定义了一个父类 Parent
,其中包括了一个公有成员函数 publicMethod()
、保护成员函数 protectedMethod()
和私有成员函数 privateMethod()
。
然后,我们创建了三个不同的子类 PublicChild
、PrivateChild
和 ProtectedChild
分别进行公有继承、私有继承和保护继承。
在 accessBaseClassMembers()
方法中,我们可以看到:
PublicChild
类中,可以直接访问父类的公有成员和保护成员,但无法访问私有成员。PrivateChild
类中,可以通过子类的成员函数间接访问父类的公有成员和保护成员,但无法直接访问私有成员。在单继承(只有一个父类)的情况下,常见的对象模型是使用虚函数表(Virtual Function Table,简称VTable)来实现的。每个类都有一个对应的VTable,其中包含了该类的虚函数的地址。当对象调用虚函数时,实际上是通过VTable来确定要调用的函数。
在多重继承(多个父类)的情况下,对象模型的实现可以有所不同。常见的对象模型有水平继承和垂直继承两种:
水平继承(水平分割):在水平继承中,每个子类都包含其各个父类的成员变量和函数,形成一个扁平的对象。这种模型比较简单,但会导致数据冗余。
对象的构造顺序:
- 首先,先调用父类的构造函数,从顶层父类向子类逐级调用,按照继承链的顺序进行。
- 子类的构造函数在调用父类构造函数后才会执行,子类中先执行自己的构造逻辑。
对象的析构顺序:
- 先调用子类的析构函数,从最底层的子类向顶层父类逐级调用,按照继承链的逆序进行。
- 父类的析构函数在子类析构函数执行完毕后才会被调用。
#include
class Parent
{
public:
int value = 10;
};
class Child : public Parent
{
public:
void printValue()
{
std::cout << "Child value: " << value << std::endl; // 直接访问子类的同名成员
}
};
int main()
{
Child child;
child.printValue();
return 0;
}
输出结果为:Child value: 10
在上述代码中,子类 Child
直接访问了继承自父类 Parent
的同名变量 value
。
#include
class Parent
{
public:
int value = 10;
};
class Child : public Parent
{
public:
int value = 20;
void printValues()
{
std::cout << "Child value: " << value << std::endl; // 访问子类的同名成员
std::cout << "Parent value: " << Parent::value << std::endl; // 访问父类的同名成员
}
};
int main()
{
Child child;
child.printValues();
return 0;
}
输出结果为:
Child value: 20
Parent value: 10
在上述代码中,子类 Child
中存在同名成员变量 value
,通过 Parent::value
的方式可以访问父类 Parent
中的同名成员变量。
当在继承关系中存在同名的静态成员时,子类会隐藏父类的同名静态成员。访问同名静态成员时,需要使用作用域解析运算符(::)来指明所访问的是哪个类的静态成员。
#include
class Parent
{
public:
static int staticMember;
static void staticMethod()
{
std::cout << "Parent's staticMethod" << std::endl;
}
};
int Parent::staticMember = 10;
class Child : public Parent
{
public:
static int staticMember;
static void staticMethod()
{
std::cout << "Child's staticMethod" << std::endl;
}
};
int Child::staticMember = 20;
int main()
{
std::cout << "Parent staticMember: " << Parent::staticMember << std::endl; // 访问父类的静态成员
std::cout << "Child staticMember: " << Child::staticMember << std::endl; // 访问子类的静态成员
Parent::staticMethod(); // 调用父类的静态方法
Child::staticMethod(); // 调用子类的静态方法
return 0;
}
输出结果为:
Parent staticMember: 10
Child staticMember: 20
Parent's staticMethod
Child's staticMethod
在上述代码中,父类 Parent
和子类 Child
都定义了同名的静态成员和静态方法。通过使用作用域解析运算符(::),可以确定所访问的是父类还是子类的同名静态成员。
需要注意的是,子类的同名静态成员会隐藏父类的同名静态成员。这意味着,如果直接通过子类类型的对象访问同名静态成员,实际上会访问的是子类的同名静态成员,而不是父类的同名静态成员。例如:
cpp
Child child; std::cout << "Child staticMember: " << child.staticMember << std::endl; // 访问的是子类的同名静态成员
输出结果为:
Child staticMember: 20
通过子类对象访问同名静态成员将会访问子类的同名静态成员,而不是父类的同名静态成员。
c++允许一个子类继承多个类
语法:class 子类:继承方式 父类1 ,继承方式 父类2
#include
// 父类1
class Parent1
{
public:
void method1()
{
std::cout << "Parent1's method1" << std::endl;
}
};
// 父类2
class Parent2
{
public:
void method2()
{
std::cout << "Parent2's method2" << std::endl;
}
};
// 子类继承自父类1和父类2
class Child : public Parent1, public Parent2
{
public:
void childMethod()
{
std::cout << "Child's method" << std::endl;
}
};
int main()
{
Child child;
child.method1(); // 可以访问从父类1继承来的公有成员
child.method2(); // 可以访问从父类2继承来的公有成员
child.childMethod();
return 0;
}
输出结果为:
Parent1's method1
Parent2's method2
Child's method
在上述示例中,子类 Child
继承了 Parent1
和 Parent2
的公有成员,可以直接访问它们的方法。
菱形继承问题是指在面向对象编程中,存在一个类继承了两个不同的父类,而这两个父类又共同继承了同一个父类,从而形成了一个菱形的继承关系。这种继承关系可能导致一些问题。
一个常见的问题是多重继承会导致命名冲突。如果两个父类中有相同的方法或属性,在子类中调用这些方法或属性时就会产生冲突,编译器无法确定具体使用哪个父类的方法或属性。
另一个问题是菱形继承会增加代码的复杂性和混乱度。由于存在多个继承链,子类需要管理不同父类的状态和方法,导致代码难以理解和维护。
在C++中,可以使用虚拟继承来解决菱形继承问题。在定义继承关系时使用关键字"virtual",如下所示:
class A
{
public:
// A的成员变量和方法
};
class B : virtual public A
{
public:
// B的成员变量和方法
};
class C : virtual public A
{
public:
// C的成员变量和方法
};
class D : public B, public C
{
public:
// D的成员变量和方法
};
通过使用虚拟继承,B和C类都会继承自A,但A类中的成员只会在D类中保留一份,避免了重复继承和冲突问题。