继承是C++面向对象编程的核心特性之一,它允许程序员创建层次化的类结构,从而实现代码的重用和扩展。在这篇文章中,我们将深入探讨C++继承的基础概念,包括基类与派生类的关系、多重继承的处理、虚函数与多态的应用,以及如何在复杂系统中有效利用继承来构建可维护且扩展性强的代码架构。通过系统的学习,你将对C++继承有更深入的理解,并能够在实际开发中灵活应用这些知识。
在C++中,继承(Inheritance) 是面向对象编程(OOP)中的一个核心概念,它允许一个类(子类或派生类)从另一个类(基类或父类)继承属性和行为(成员变量和成员函数)。通过继承,子类不仅可以复用基类的已有功能,还可以扩展或修改其行为。这种机制大大提高了代码的复用性和扩展性。
继承使得一个类可以获取另一个类的特性。具体来说,子类会继承基类的成员变量和成员函数,并且可以添加新的成员或修改已有的成员。子类通过继承关系,可以拥有基类的公共和受保护(protected
)成员。
语法格式:
class 派生类名 : 继承方式 基类名 {
// 子类的成员和函数
};
public
、protected
、private
。继承方式 | 基类成员访问权限 | 基类的public成员 | 基类的protected成员 | 基类的private成员 |
---|---|---|---|---|
Public继承 | 子类的访问权限 | public | protected | 不可见 |
Protected继承 | 子类的访问权限 | protected | protected | 不可见 |
Private继承 | 子类的访问权限 | private | private | 不可见 |
说明:
public
成员在子类中保持为public
,可以从外部直接访问。protected
成员在子类中保持为protected
,只能在子类内部和其派生类中访问。private
成员对子类不可见,但可以通过基类的public
或protected
函数间接访问。public
成员在子类中变为protected
,只能在子类及其派生类中访问,外部不可访问。protected
成员保持为protected
。private
成员对子类不可见。public
和protected
成员在子类中都变为private
,只能在子类内部访问,外部和其派生类都不可访问。private
成员对子类不可见。#include <iostream>
using namespace std;
// 基类:Person
class Person {
public:
string name;
int age;
void displayInfo() {
cout << "Name: " << name << ", Age: " << age << endl;
}
};
// 派生类:Student,公有继承Person类
class Student : public Person {
public:
int studentID;
void displayStudentInfo() {
cout << "Student ID: " << studentID << endl;
}
};
int main() {
// 创建派生类对象
Student student;
student.name = "John"; // 可以访问基类的public成员
student.age = 20; // 可以访问基类的public成员
student.studentID = 12345;
// 调用基类的成员函数
student.displayInfo(); // 输出: Name: John, Age: 20
// 调用派生类的成员函数
student.displayStudentInfo(); // 输出: Student ID: 12345
return 0;
}
【注意事项】:
public
、protected
、private
)。virtual
)配合使用,可以实现运行时多态,这在子类重写基类函数时尤其有用。在C++中,基类和派生类之间的赋值和转换遵循一些规则和限制,主要涉及到指针和引用。当涉及到对象赋值时,我们需要注意对象的静态类型(编译时的类型)和动态类型(运行时的类型),这与继承、多态以及向上和向下转换密切相关。
向上转换是指把派生类对象的指针或引用赋值给基类的指针或引用。由于派生类继承了基类的所有公开和受保护成员,基类可以“容纳”派生类对象的一部分。向上转换是安全的,且自动进行。
示例:
#include <iostream>
using namespace std;
// 基类:Base
class Base {
public:
virtual void display() {
cout << "Base class" << endl;
}
};
// 派生类:Derived
class Derived : public Base {
public:
void display() override {
cout << "Derived class" << endl;
}
};
int main() {
Derived derivedObj;
// 向上转换:派生类对象的指针赋值给基类的指针
Base* basePtr = &derivedObj;
basePtr->display(); // 输出: Derived class (如果基类方法是虚函数)
// 向上转换:派生类对象的引用赋值给基类的引用
Base& baseRef = derivedObj;
baseRef.display(); // 输出: Derived class
return 0;
}
注意:
virtual
),则在运行时会调用派生类中的重写方法(即多态)。向下转换是指将基类的指针或引用转换为派生类的指针或引用。因为派生类通常比基类包含更多的信息,向下转换是有风险的,必须小心使用。C++提供了dynamic_cast
来进行安全的向下转换。
示例:
#include <iostream>
using namespace std;
// 基类:Base
class Base {
public:
virtual void display() {
cout << "Base class" << endl;
}
};
// 派生类:Derived
class Derived : public Base {
public:
void display() override {
cout << "Derived class" << endl;
}
void specialMethod() {
cout << "Derived class specific method" << endl;
}
};
int main() {
Base* basePtr = new Derived(); // 向上转换
basePtr->display(); // 输出: Derived class(因为基类的display是虚函数)
// 向下转换:将Base*转换为Derived*
Derived* derivedPtr = dynamic_cast<Derived*>(basePtr);
if (derivedPtr) {
derivedPtr->specialMethod(); // 输出: Derived class specific method
} else {
cout << "Conversion failed!" << endl;
}
delete basePtr;
return 0;
}
注意:
dynamic_cast
是安全的,如果转换失败,它会返回nullptr
,因此需要检查转换是否成功。nullptr
。向下转换通常用于启用多态行为,确保基类指针能安全地转换为实际的派生类。在C++中,不能直接将派生类对象赋值给基类对象(非指针或引用的对象)。如果这样做,基类只能接收到派生类的基类部分,派生类的其他成员会被“丢弃”。这可能导致意外的行为,因此建议避免使用直接赋值。
示例:
#include <iostream>
using namespace std;
// 基类:Base
class Base {
public:
int baseVar;
virtual void display() {
cout << "Base class" << endl;
}
};
// 派生类:Derived
class Derived : public Base {
public:
int derivedVar;
void display() override {
cout << "Derived class" << endl;
}
};
int main() {
Derived derivedObj;
Base baseObj = derivedObj; // 非指针/引用赋值,切割派生类对象
baseObj.display(); // 输出: Base class
return 0;
}
注意:
Base baseObj = derivedObj;
会将derivedObj
的基类部分赋值给baseObj
,这是**切割(slicing)**问题,派生类的特性会丢失。baseObj
仅仅是一个Base
类的对象。在C++中,继承中的作用域(Scope in Inheritance)涉及基类与派生类之间成员的可见性和访问权限。作用域决定了子类可以访问父类哪些成员,以及如何在子类中访问、覆盖或隐藏基类成员。作用域与访问控制(public
、protected
、private
)密切相关,并且需要考虑成员函数、变量、构造函数等在继承关系中的可见性。
基类的成员在派生类中的可见性主要受继承方式和成员的访问修饰符控制,下面是对每种修饰符的详细说明:
public
继承中,基类的public
成员在派生类中仍然保持为public
,可以在派生类的外部和内部访问。如果是protected
继承,则这些成员在派生类中变为protected
;在private
继承中,这些成员在派生类中变为private
。protected
成员在public
和protected
继承中,依旧保持为protected
。这意味着这些成员只能在派生类内部或其派生类中访问。在private
继承中,基类的protected
成员变为private
,只能在派生类内部访问。private
成员始终不能在派生类中直接访问,只有通过基类的public
或protected
方法间接访问。C++ 中的派生类可以定义与基类成员同名的成员,这种情况下,派生类的成员会隐藏基类的同名成员。这种行为叫做名称隐藏,它不仅适用于成员变量,还适用于成员函数。
示例代码:
#include <iostream>
using namespace std;
class Base {
public:
int x = 10; // 基类成员变量
void show() { // 基类成员函数
cout << "Base class show() called" << endl;
}
};
class Derived : public Base {
public:
int x = 20; // 派生类成员变量,隐藏了基类的x
void show() { // 派生类成员函数,隐藏了基类的show()
cout << "Derived class show() called" << endl;
}
void display() {
cout << "Derived class x: " << x << endl; // 输出派生类的x
cout << "Base class x: " << Base::x << endl; // 显式调用基类的x
}
};
int main() {
Derived d;
d.show(); // 输出: Derived class show() called
d.display(); // 输出:
// Derived class x: 20
// Base class x: 10
return 0;
}
在上面的例子中,派生类定义了一个名为x
的成员变量,它隐藏了基类的同名成员x
。在display()
函数中,我们通过Base::x
来显式访问基类的成员变量。同样,派生类的show()
方法隐藏了基类的show()
方法。
重要注意点:
::
),如Base::x
或Base::show()
。show()
是虚函数(virtual
),那么即使派生类定义了同名的show()
,也会根据实际对象类型进行动态调用,而不会发生隐藏。如果基类中的函数是虚函数(virtual
),那么当在派生类中覆盖该虚函数时,基类的函数不会被隐藏,而是会被动态绑定到派生类对象。在这种情况下,调用派生类对象时,即使是通过基类的指针或引用,也会调用派生类中覆盖的函数。
示例代码:
#include <iostream>
using namespace std;
class Base {
public:
virtual void show() { // 基类虚函数
cout << "Base class show() called" << endl;
}
};
class Derived : public Base {
public:
void show() override { // 覆盖基类的虚函数
cout << "Derived class show() called" << endl;
}
};
int main() {
Base* basePtr;
Derived derivedObj;
basePtr = &derivedObj;
basePtr->show(); // 输出: Derived class show() called
return 0;
}
注意:
basePtr
指向派生类对象,在调用show()
时,仍然会动态绑定到派生类的show()
方法。这是虚函数和多态的行为。示例:
#include <iostream>
using namespace std;
class Base {
public:
Base() {
cout << "Base constructor called" << endl;
}
virtual ~Base() { // 基类析构函数应该是虚函数
cout << "Base destructor called" << endl;
}
};
class Derived : public Base {
public:
Derived() {
cout << "Derived constructor called" << endl;
}
~Derived() {
cout << "Derived destructor called" << endl;
}
};
int main() {
Base* basePtr = new Derived(); // 基类指针指向派生类对象
delete basePtr; // 输出:
// Derived destructor called
// Base destructor called
return 0;
}
注意:
using
声明改变作用域C++允许使用using
声明将基类的某些成员引入到派生类中,以便修改其访问权限。例如,基类中的protected
成员可以通过using
声明在派生类中变为public
。
示例:
class Base {
protected:
int protectedVar;
public:
Base() : protectedVar(5) {}
};
class Derived : public Base {
public:
using Base::protectedVar; // 将基类的protected成员变为public
};
int main() {
Derived d;
d.protectedVar = 10; // 可以直接访问protectedVar
return 0;
}
在C++中,派生类的默认成员函数是指当你定义一个派生类时,编译器会自动为你生成的一些特殊成员函数。即使你没有显式定义这些函数,编译器也会根据特定规则生成这些默认函数。这些默认成员函数包括:默认构造函数、拷贝构造函数、移动构造函数、赋值运算符、移动赋值运算符、析构函数。
这些默认成员函数可以被显式定义或覆盖(尤其是在需要特殊操作时)。下面我们分别讨论每个默认成员函数在派生类中的行为。
如果你没有为派生类定义构造函数,编译器会自动生成一个默认的构造函数。这个默认构造函数会首先调用基类的默认构造函数,然后执行派生类中成员变量的默认初始化。
示例:
#include <iostream>
using namespace std;
class Base {
public:
Base() {
cout << "Base default constructor" << endl;
}
};
class Derived : public Base {
public:
// 自动生成的默认构造函数
// Derived() : Base() { }
};
int main() {
Derived d; // 输出: Base default constructor
return 0;
}
注意:
派生类的默认拷贝构造函数是编译器生成的,当派生类对象被拷贝时,它会首先调用基类的拷贝构造函数,然后依次拷贝派生类中的成员。拷贝构造函数实现的是浅拷贝,即成员逐个复制。
示例:
#include <iostream>
using namespace std;
class Base {
public:
Base(const Base& b) {
cout << "Base copy constructor" << endl;
}
};
class Derived : public Base {
public:
Derived(const Derived& d) : Base(d) {
cout << "Derived copy constructor" << endl;
}
};
int main() {
Derived d1;
Derived d2 = d1; // 输出:
// Base copy constructor
// Derived copy constructor
return 0;
}
注意:
如果你没有定义移动构造函数,编译器会为派生类自动生成一个默认的移动构造函数,它会调用基类的移动构造函数并移动派生类的成员。这个函数实现资源转移而非复制,适用于实现高效的资源管理(如动态分配的内存、文件句柄等)。
示例:
#include <iostream>
using namespace std;
class Base {
public:
Base(Base&& b) {
cout << "Base move constructor" << endl;
}
};
class Derived : public Base {
public:
Derived(Derived&& d) : Base(move(d)) {
cout << "Derived move constructor" << endl;
}
};
int main() {
Derived d1;
Derived d2 = move(d1); // 输出:
// Base move constructor
// Derived move constructor
return 0;
}
注意:
编译器会自动生成一个拷贝赋值运算符,当派生类对象被赋值给另一个对象时,拷贝赋值运算符会被调用。这个运算符会依次调用基类的拷贝赋值运算符和派生类中各成员的赋值运算符。
示例:
#include <iostream>
using namespace std;
class Base {
public:
Base& operator=(const Base& b) {
cout << "Base copy assignment operator" << endl;
return *this;
}
};
class Derived : public Base {
public:
Derived& operator=(const Derived& d) {
Base::operator=(d); // 显式调用基类的赋值运算符
cout << "Derived copy assignment operator" << endl;
return *this;
}
};
int main() {
Derived d1, d2;
d1 = d2; // 输出:
// Base copy assignment operator
// Derived copy assignment operator
return 0;
}
注意:
默认情况下,编译器会生成一个移动赋值运算符,用于对象之间的移动赋值。这会调用基类的移动赋值运算符并移动派生类中的成员。
示例:
#include <iostream>
using namespace std;
class Base {
public:
Base& operator=(Base&& b) {
cout << "Base move assignment operator" << endl;
return *this;
}
};
class Derived : public Base {
public:
Derived& operator=(Derived&& d) {
Base::operator=(move(d)); // 显式调用基类的移动赋值运算符
cout << "Derived move assignment operator" << endl;
return *this;
}
};
int main() {
Derived d1, d2;
d1 = move(d2); // 输出:
// Base move assignment operator
// Derived move assignment operator
return 0;
}
派生类的默认析构函数是编译器生成的,用于销毁对象。当派生类对象被销毁时,析构函数会首先销毁派生类的成员,然后调用基类的析构函数。如果基类的析构函数是虚函数,派生类的析构函数会自动变成虚函数。
示例:
#include <iostream>
using namespace std;
class Base {
public:
virtual ~Base() { // 基类析构函数为虚函数
cout << "Base destructor" << endl;
}
};
class Derived : public Base {
public:
~Derived() {
cout << "Derived destructor" << endl;
}
};
int main() {
Base* basePtr = new Derived();
delete basePtr; // 输出:
// Derived destructor
// Base destructor
return 0;
}
注意:
今天的分享到这里就结束啦!如果觉得文章还不错的话,可以三连支持一下,17的主页还有很多有趣的文章,欢迎小伙伴们前去点评,您的支持就是17前进的动力!