探索C++三大特性--C++ 继承详解:从概念到高级用法

🏷️ bte365 📅 2025-11-10 12:00:31 ✍️ admin 👀 9667 ❤️ 241
探索C++三大特性--C++ 继承详解:从概念到高级用法

一、继承的概念继承(Inheritance)机制是面向对象程序设计中最重要的手段之一。通过继承,我们可以在已有类的基础上进行扩展,增加新的方法和属性,从而创建新的类,这些新类被称为派生类(Derived Class)【14†source】。继承的主要目的是实现代码复用,使得相似功能代码的编写更加简洁高效,并使系统具有更好的可维护性。

1.1 没有继承时的问题在没有继承的情况下,我们可能会设计多个功能相似的类,这些类中含有相同的成员变量和成员函数。例如,我们设计了两个类:——Student和Teacher,它们都拥有姓名、地址、电话等成员变量,以及身份认证的成员函数。这导致了代码的冗余和维护困难。

代码语言:javascript复制class Student {

public:

// 身份认证

void identity() {

// ...

}

// 学习

void study() {

// ...

}

protected:

string _name = "peter"; // 姓名

string _address; // 地址

string _tel; // 电话

int _age = 18; // 年龄

int _stuid; // 学号

};

class Teacher {

public:

// 身份认证

void identity() {

// ...

}

// 授课

void teaching() {

// ...

}

protected:

string _name = "张三"; // 姓名

int _age = 18; // 年龄

string _address; // 地址

string _tel; // 电话

string _title; // 职称

};如上代码中,Student和Teacher类中存在大量重复成员变量和函数,这种冗余不仅增加了代码量,也使得代码维护变得复杂。

1.2 使用继承的改进通过继承,我们可以将公共的成员变量和函数提取到一个基类(Base Class)中,例如Person类,然后让Student和Teacher类继承Person类,从而减少重复代码。

代码语言:javascript复制class Person {

public:

// 身份认证

void identity() {

cout << "void identity()" << _name << endl;

}

protected:

string _name = "张三"; // 姓名

string _address; // 地址

string _tel; // 电话

int _age = 18; // 年龄

};

class Student : public Person {

public:

// 学习

void study() {

// ...

}

protected:

int _stuid; // 学号

};

class Teacher : public Person {

public:

// 授课

void teaching() {

// ...

}

protected:

string title; // 职称

};通过上述改进,Student和Teacher类继承了Person类,公共成员被提取到基类中,这样可以减少重复代码,提高代码的可维护性。

二、继承的定义与访问控制2.1 继承的定义方式在C++中,继承是通过指定继承方式来实现的,常见的继承方式有三种:

public 继承:基类的public和protected成员在派生类中保持相同的访问控制。

protected 继承:基类的public成员在派生类中变为protected,而protected成员保持不变。

private 继承:基类的所有非private成员在派生类中都变为private。

代码语言:javascript复制class Person {

public:

void Print() {

cout << _name << endl;

}

protected:

string _name; // 姓名

};

// public 继承

class Student : public Person {

protected:

int _stunum; // 学号

};在实际使用中,public继承是最常见的方式,因为它可以保持基类成员的可访问性。而protected和private继承较少使用,因为它们限制了派生类对基类成员的访问,从而降低了代码的扩展性和维护性。

2.2 基类成员的访问控制在继承体系中,基类的private成员在派生类中是不可见的,这意味着派生类对象无法访问基类中的私有成员。而基类的protected成员则可以在派生类中访问,这使得派生类能够继承和使用这些成员。

代码语言:javascript复制class Person {

protected:

string _name; // 姓名

private:

int _age; // 年龄

};

class Student : public Person {

public:

void Print() {

cout << _name << endl; // 可以访问基类的受保护成员

// cout << _age; // 无法访问基类的私有成员

}

protected:

int _stunum; // 学号

};三、继承中的作用域与隐藏规则3.1 成员隐藏在继承体系中,如果派生类和基类中存在同名的成员,那么基类的成员会被隐藏。此时,派生类只能访问自己的成员,而不能直接访问基类的同名成员。

代码语言:javascript复制class Person {

protected:

int _num = 111; // 身份证号

};

class Student : public Person {

public:

void Print() {

cout << "身份证号: " << Person::_num << endl;

cout << "学号: " << _num << endl;

}

protected:

int _num = 999; // 学号

};在上面的例子中,Student类中的_num隐藏了Person类中的同名成员,因此如果想访问基类中的_num,需要使用Person::_num来显式指定。

四、派生类的默认成员函数在派生类中,默认的成员函数(如构造函数、析构函数、拷贝构造函数、赋值运算符等)会自动生成,并且派生类会调用基类的相应成员函数来初始化基类部分。

代码语言:javascript复制class Person {

public:

Person(const char* name = "peter") : _name(name) {

cout << "Person()" << endl;

}

~Person() {

cout << "~Person()" << endl;

}

protected:

string _name; // 姓名

};

class Student : public Person {

public:

Student(const char* name, int num) : Person(name), _num(num) {

cout << "Student()" << endl;

}

~Student() {

cout << "~Student()" << endl;

}

protected:

int _num; // 学号

};

int main() {

Student s1("jack", 18);

return 0;

}在上面的代码中,Student类的构造函数和析构函数在执行时会首先调用基类Person的构造函数和析构函数,这样能够确保基类部分正确初始化和清理。

五、多继承与菱形继承问题5.1 多继承C++ 支持多继承,即一个类可以继承自多个基类。然而,多继承可能会导致菱形继承问题,即多个基类中有相同的成员,导致派生类中有两份相同的成员拷贝。

代码语言:javascript复制class Person {

public:

string _name; // 姓名

};

class Student : public Person {

protected:

int _num; // 学号

};

class Teacher : public Person {

protected:

int _id; // 职工编号

};

class Assistant : public Student, public Teacher {

protected:

string _majorCourse; // 主修课程

};在上述代码中,Assistant类同时继承自Student和Teacher,由于这两个基类都继承自Person,因此Assistant类中存在两份_name成员,这就产生了数据冗余和访问二义性的问题。

5.2 虚继承解决菱形继承问题为了避免菱形继承带来的问题,我们可以使用虚继承。通过虚继承,派生类只会保留一份基类的成员。

代码语言:javascript复制class Person {

public:

string _name; // 姓名

};

class Student : virtual public Person {

protected:

int _num; // 学号

};

class Teacher : virtual public Person {

protected:

int _id; // 职工编号

};

class Assistant : public Student, public Teacher {

protected:

string _majorCourse; // 主修课程

};

int main() {

Assistant a;

a._name = "peter"; // 没有二义性

return 0;

}通过虚继承,Assistant类中只会有一份Person类的成员,避免了数据冗余和访问的二义性。

六、继承与组合的选择在面向对象设计中,继承表示一种is-a的关系,而组合表示一种has-a的关系。优先使用组合而不是继承可以降低类之间的耦合度,提高代码的灵活性和可维护性。

代码语言:javascript复制class Tire {

protected:

string _brand = "Michelin"; // 品牌

size_t _size = 17; // 尺寸

};

class Car {

protected:

string _colour = "白色"; // 颜色

Tire _t1, _t2, _t3, _t4; // 四个轮胎

};

class BMW : public Car {

public:

void Drive() { cout << "驾驶宝马车" << endl; }

};在上述代码中,Car类通过组合方式包含了四个轮胎对象Tire,这种设计更符合逻辑上的has-a关系。在实际设计中,优先使用组合可以减少代码的耦合性,增强代码的复用性和灵活性。

总结继承是C++中实现代码复用和扩展的重要手段,它可以简化代码结构,减少冗余代码,增强代码的可维护性。然而,在使用继承时,我们需要遵循一些设计原则,避免滥用继承导致代码复杂化。同时,理解继承与组合之间的差异,合理选择使用继承或组合,对于编写高质量的面向对象代码非常重要。

希望这篇博客能够帮助大家深入理解C++继承的概念和使用方法。欢迎大家在实际项目中应用这些知识,编写出更加优雅和高效的代码。

🎯 相关推荐

乔冠华到底有多强?联合国大会仰天大笑,毛主席:一文顶两坦克师
手机上有耳机标志怎么去掉
365bet注册

手机上有耳机标志怎么去掉

📅 08-25 👀 1111
柴静雾霾纪录片引各方争议 这是目前最全面的汇总