继承机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有的特性基础上进行扩展,增加功能,这样产生新的类,称作是派生类。继承呈现了面向对象程序设计的层析结构,体现了由简单到复杂的认知过程。继承是类
目录
- 继承
- 继承中的作用域
- 子类的默认成员函数
- 继承与友元
- 复杂的菱形继承
- 继承的总结
继承
继承的概念
继承机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能。继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。
继承的定义

继承关系和访限定符

继承方式
class Person
{
public :
void Print ()
{
cout<<_name <<endl;
}
protected :
string _name = "张三" ; // 姓名
private :
int _age = 18 ; // 年龄
};
class Student : public Person
{
protected :
int _stunum = 22; // 学号
};
public继承
上面是给的缺省值来测试没写构造函数





父类和子类对象赋值转化
class Person
{
protected:
string _name; // 姓名
string _sex; // 性别
int _age; // 年龄
};
class Student : public Person
{
public:
int _No; // 学号
};
Student s;
Person p;
p = s;
Person *ptr = &s;//子类赋给父类指针
Person &ref = s;//子类赋给父类引用



继承中的作用域
class Person
{
protected:
string _name = "法外狂徒"; // 姓名
int _num = 11; // 身份证号
};
class Student : public Person
{
public:
void Print()
{
cout << " 姓名:" << _name << endl;
cout << " 身份证号:" << _num << endl;
cout << " 学号:" << _num << endl;
}
protected:
int _num = 2; // 学号
};
class A {
public:
void fun(double x)
{
cout << "fun()->x"<< x << endl;
}
};
class B : public A {
public:
void fun(int i)
{
cout << "fun()->" << i << endl;
}
};
int main()
{
B b;
b.fun(10);
b.A::fun(11.1);//加作用域
return 0;
}
父类和子类函数名相同不是重载而是隐藏,函数重载是在同一作用域,不同的作用域是隐藏
在子类成员函数中,可以使用 基类::基类成员 显示访问
在写代码中最好不要定义同名的成员
子类的默认成员函数
在类和对象的时候讲了6个默认的成员函数,现在子类中讲4个,构造,拷贝构造,赋值和析构
class Person //父类
{
public:
Person(const char* name = "李四")
: _name(name)
{
cout << "Person()" << endl;
}
Person(const Person& p)
: _name(p._name)
{
cout << "Person(const Person& p)" << endl;
}
Person& operator=(const Person& p)
{
cout << "Person operator=(const Person& p)" << endl;
if (this != &p)
_name = p._name;
return *this;
}
~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(const Student& s)
: Person(s)//这里就用到了切片,切父类的成员类拷贝
, _num(s._num)//拷贝子类的
{
cout << "Student(const Student& s)" << endl;
}
Student& operator = (const Student& s)
{
cout << "Student& operator= (const Student& s)" << endl;
if (this != &s)
{
Person::operator =(s);//调用父类的赋值
_num = s._num;//赋值子类自己的
}
return *this;
}
~Student()
{
//子类的析构函数完成清理后会自动调用父类的析构函数
cout << "~Student()" << endl;
}
protected:
int _num; //学号
};
总结:
- 派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员。如果基类没有默认的构造函数,则必须在派生类构造函数的初始化列表阶段显示调用。
- 派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化。
- 派生类的operator=必须要调用基类的operator=完成基类的复制。
- 派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员。因为这样才能保证派生类对象先清理派生类成员再清理基类成员的顺序。
- 派生类对象初始化先调用基类构造再调派生类构造。
- 派生类对象析构清理先调用派生类析构再调基类的析构。
继承与友元
友元关系不能继承,父类友元不能访问子类私有和保护成员
class Student;
class Person
{
public:
friend void Display(const Person& p, const Student& s);
protected:
string _name; // 姓名
};
class Student : public Person
{
public:
friend void Display(const Person& p, const Student& s);
protected:
int _stuNum; // 学号
};
void Display(const Person& p, const Student& s) {
cout << p._name << endl;//可以访问
cout << s._stuNum << endl;//要在子类中加上友元才能访问,不加会报错
}
int main()
{
Person p;
Student s;
Display(p, s);
return 0;
}
继承与静态成员
基类定义了static静态成员,则整个继承体系里面只有一个这样的成员。无论派生出多少个子类,都只有一个static成员实例 。
class Person
{
public:
Person() { ++_count; }
protected:
string _name; // 姓名
public:
static int _count; // 统计人的个数。
};
int Person::_count = 0;
class Student : public Person
{
protected:
int _id; // 学号
};
class Graduate : public Student
{
protected:
string _Course; // 科目
};
int main()
{
Student s1;
Student s2;
Student s3;
Graduate s4;
cout << " 人数 :" << Person::_count << endl;
cout << " 人数 :" << Student::_count << endl;
cout << " 人数 :" << &Person::_count << endl;
cout << " 人数 :" << &Student::_count << endl;
return 0;
}

复杂的菱形继承
单继承:一个子类只有一个直接父类时称这个继承关系为单继承


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 _Course; // 课程
};
int main()
{
// 这样会有二义性无法明确知道访问的是哪一个
Assistant a;
//a._name = "peter";
// 显示的调用解决了二义性,但数据冗余了
a.Student::_name = "盖伦";
a.Teacher::_name = "亚索";
return 0;
}

虚继承
虚拟继承可以解决菱形继承的二义性和数据冗余的问题。如上面的继承关系,在Student和Teacher的继承Person时使用虚拟继承,即可解决问题。需要注意的是,虚拟继承不要在其他地方去使用。
class A {
public:
int _a;
};
class B : public A {
public:
int _b;
};
class C : public A {
public:
int _c;
};
class D : public B, public C {
public:
int _d;
};
int main()
{
D d;
d.B::_a = 1;
d.C::_a = 2;
d._b = 3;
d._c = 4;
d._d = 5;
return 0;
}
我们可以通过内存窗口来观察对象成员的模型


D d;
d.B::_a = 1;
d.C::_a = 2;
d._b = 3;
d._c = 4;
d._d = 5;
B b = d;//把d赋给b,把d切过去那此时要怎么找到A呢?,所以就要用虚基表找
B类中各个成员在内存中的分布:

继承的总结
- C++语法复杂,其实多继承就是一个体现。有了多继承,就存在菱形继承,有了菱形继承就有菱形虚拟继承,底层实现就很复杂。所以一般不建议设计出多继承,一定不要设计出菱形继承。否则在复杂度及性能上都有问题。
- 多继承可以认为是C++的缺陷之一.
组合
继承是建立了父类与子类的关系,是一种“是”的关系,例如白猫是猫,组合是“有”的关系实际尽量多去用组合。组合的耦合度低,代码维护性好。不过继承也有用武之地的,有些关系就适合继承那就用继承,另外要实现多态,也必须要继承。类之间的关系可以用继承,可以用组合就用组合。
面试题
- 什么是菱形继承?菱形继承的问题是什么?
菱形继承是多继承的一种特殊继承,两个子类继承同一个父类,而又有子类同时继承这两个子类。可以看出菱形继承有数据冗余和二义性的问题。
- 什么是菱形虚拟继承?如何解决数据冗余和二义性的
在菱形继承的腰部加上virtual,通过虚基表指针和虚基表中的偏移量可以找到虚基类,只存1份
- 继承和组合的区别?什么时候用继承?什么时候用组合?
继承是一种"是",组合是"有"的关系,父类和子类是的关系用继承,是有的关系用组合。
以上就是C++继承,由于作者水平有限,如有问题还请指出!
到此这篇关于C++继承模式详解的文章就介绍到这了。希望对大家的学习有所帮助,也希望大家多多支持编程学习网。