目录
一、引言
在面向对象编程中,继承是一个非常重要的概念,它允许我们创建一个新的类(子类),这个新类可以继承另一个已存在的类(父类)的属性和方法。C++ 作为一门强大的面向对象编程语言,提供了丰富的继承机制。本文将结合提供的代码,详细解析 C++ 继承的各种特性。
二、基本继承语法与示例
2.1 基本语法
在 C++ 中,使用 :
符号来实现继承,语法如下:
class 子类名 : 继承方式 父类名 {
// 子类的成员
};
其中,继承方式可以是 public
、protected
或 private
,默认情况下是 private
。
2.2 示例代码
class Person {
protected:
string _name = "张三";
string _address;
string _tel;
private:
int _age = 18;
};
class Student : public Person {
protected:
int _stuid;
};
在这个例子中,Student
类继承自 Person
类,使用了 public
继承方式。
三、访问控制与继承
3.1 不同访问控制符的影响
public
继承:父类的public
成员在子类中仍然是public
,protected
成员在子类中仍然是protected
,private
成员在子类中不可直接访问。protected
继承:父类的public
和protected
成员在子类中都变为protected
,private
成员在子类中不可直接访问。private
继承:父类的public
和protected
成员在子类中都变为private
,private
成员在子类中不可直接访问。
3.2 代码示例
class Person {
public:
void identity() {
cout << "void identity()" << _name << endl;
cout << _age << endl;
}
protected:
string _name = "张三";
private:
int _age = 18;
};
class Student : public Person {
void study() {
identity(); // 可以调用父类的 public 方法
// cout << _age << endl; // 错误,不能直接访问父类的 private 成员
}
};
四、子类的构造、拷贝构造、赋值重载和析构
4.1 构造函数
子类的构造函数必须调用父类的构造函数来初始化父类的那部分成员。如果父类没有默认构造函数,则必须在子类的初始化列表中显式调用父类的构造函数。
class Person {
public:
Person(const char* name = "xxx") : _name(name) {
cout << "Person()" << endl;
}
protected:
string _name;
};
class Student : public Person {
public:
Student(const char* name, int nums, const char* address)
: Person(name)
, _nums(nums)
, _address(address)
{}
protected:
int _nums;
string _address;
};
4.2 拷贝构造函数
子类的拷贝构造函数需要调用父类的拷贝构造函数来复制父类的成员。
class Student : public Person {
public:
Student(const Student& s)
: Person(s), _nums(s._nums), _address(s._address) {}
};
4.3 赋值重载函数
子类的赋值重载函数需要显式调用父类的赋值重载函数。
class Student : public Person {
public:
Student& operator=(const Student& s) {
if (this != &s) {
Person::operator=(s);
_nums = s._nums;
_address = s._address;
}
return *this;
}
};
4.4 析构函数
子类的析构函数会在执行完自身的析构代码后,自动调用父类的析构函数,不需要显式调用。
class Student : public Person {
public:
~Student() {
// 不需要显式调用父类的析构函数
//子类的析构与父类的虚构构成隐藏关系
//规定:不需要显示调用父类的析构,子类析构之后会自动调用父类的析构
//这样能保证析构顺序为先子后父
//如果显示调用则取决于实现者的实现顺序,不能保证先子后父
//Person::~Person();
}
};
五、隐藏(重定义)
当子类和父类有同名的成员函数或成员变量时,子类的成员会隐藏父类的成员。
4. 注意在实际中在继承体系⾥⾯最好不要定义同名的成员
5.1.示例代码
class A {
public:
void func() {
cout << "func()" << endl;
}
};
class B : public A {
public:
void func(int i) {
cout << "func(int i)" << endl;
}
};
int main() {
B b;
// b.func(); // 无法调用
b.func(1);
b.A::func(); // 要显示调用父类函数
return 0;
六、不能被继承的类
在C++98中,想要实现不能被继承的类,可以把类的构造函数定义在private中设为私有,而在C++11中加入了新的关键字final,直接写在该类的后面。
6.1示例代码
//不能被继承的类
//C++98
class Base final //C++11关键字final
{
private: //把构造函数私有
Base()
{}
};
class Derive : public Base
{
public:
Derive()
{}
protected:
int nums;
};
七、友元与继承
友元关系不能继承,也就是说基类友元不能访问派⽣类私有和保护成员。但是,如果在子类中再次声明同一个友元函数,那么该函数也会成为子类的友元。
class Student;
class Person {
friend void Display(const Person& p, const Student& s);
protected:
int _nums;
};
class Student : public Person {
friend void Display(const Person& p, const Student& s);
protected:
string _name;
};
void Display(const Person& p, const Student& s) {
cout << p._nums << endl;
cout << s._name << endl;
}
八、静态成员与继承
基类定义了static静态成员,则整个继承体系⾥⾯只有⼀个这样的成员。⽆论派⽣出多少个派⽣类,都只有⼀个static成员实例。
8.1代码示例
class Person
{
public:
string _name;
static int _count;
};
int Person::_count = 0;
class Student : public Person
{
protected:
int _stuNums;
};
int main()
{
Person p;
Student s;
// 这里的运行结果可以看到非静态成员_name的地址是不一样的
// 说明子类继承下来了,父子类对象各有一份
cout << &p._name << endl;
cout << &s._name << endl;
// 这里的运行结果可以看到静态成员_count的地址是一样的
// 说明子类和父类共用同一份静态成员
cout << &p._count << endl;
cout << &s._count << endl;
// 公有的情况下,父子类指定类域都可以访问静态成员
cout << Person::_count << endl;
cout << Student::_count << endl;
Person::_count++;
cout << p._count << endl;
cout << s._count << endl;
return 0;
}
九、多继承与菱形继承
9.1继承模型

9.2代码示例
多继承:
class 子类名 : 继承方式 父类名1, 继承方式 父类名2, ... {
// 子类的成员
};
菱形继承:
class Person
{
public:
Person(const char* name)
:_name(name)
{}
string _name;
};
//虚继承 关键词virtual
class Student : virtual public Person
{
public:
Student(const char* name, int nums = 0)
:Person(name)
,_nums(nums)
{}
protected:
int _nums; //学号
};
class Teacher : virtual public Person
{
public:
Teacher(const char* name, int id = 1)
:Person(name)
,_id(id)
{}
protected:
int _id; //职工号
};
//不要用菱形继承 用的时候加上virtual关键字 底层复杂
//多继承先继承的在前面,后继承的在后面
class Assistant : public Student , public Teacher
{
public:
Assistant(const char* name1, const char* name2, const char* name3)
:Student(name1)
,Teacher(name2)
,Person(name3)
{}
protected:
string _majorCourse; //主修课程
};
十、总结
C++ 的继承机制提供了强大的功能,但也带来了一些复杂的问题,如访问控制、构造析构顺序、隐藏、友元、静态成员、多继承和菱形继承等。在使用继承时,需要仔细考虑这些问题,以确保代码的正确性和可维护性。