记住一句话,在C++中,继承是一种使用子类对代码进行复用的手段,在写对实际设计的时候可以将数据抽象出来,使代码更具层次性和结构性。
我们以定义一个父类People,一个子类为Student,其中父类也叫做基类,子类又称派生类。这里先不要纠结继承方式,下面我会给大家详细的介绍继承方式,基本语法为:
有了基本语法的支持,我们看代码,如下:
class People
{
protected:
int _age;
string _name;
};
class Student :public People
{
protected:
int _id;
};
在我的C++类的内容讲解中我们可以了解到类的访问限定符有如下图所示的三个,这里我们不再仔细介绍,不熟悉的同学可以查看我以前的文章。
了解三个继承方式,如下图:
有了继承方式和访问限定符,那么我们在继承的过程中,当这两个关键字相撞时,就会产生一种特性,如下图:
我相信上面的图片大多数同学已经很熟悉了,但是不与其中的意义还不是深度的了解,下面让我来细细的刨析:
1、基类private成员在派生类中无论以什么方式继承都是不可见的。注意:这里的不可见是指基类的私有成员还是被继承到了派生类对象中,但是语法上限制派生类对象不管在类里面还是类外面都不能去访问它。可以理解为他在子类当中,但是子类对他没有使用权,因为子类看不见他。
2、如果基类成员不想在类外直接被访问,但需要在派生类中能访问,就定义为protected。可以看出保护成员限定符是因继承才出现的。
小tips:在父类中protected和private的特性几乎没有区别,这两个限定符的区别是体现在派生类中的。
3、 使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public,不过最好显示的写出继承方式。
4、. 在实际运用中一般使用都是public继承,几乎很少使用protetced/private继承,也不提倡使用protetced/private继承,因为protetced/private继承下来的成员都只能在派生类的类里面使用,实际中扩展维护性不强。
5、如何快速识别一个口诀:两两相遇选权限最小。
实例演示三种继承关系下基类成员的各类型成员访问关系的变化
class Person
{
public :
void Print ()
{
cout<<_name <<endl;
}
protected :
string _name ; // 姓名
private :
int _age ; // 年龄
};
class Student : protected Person{};
//Print仅内类可访问
//_name仅内类可访问
//_age不可见
class Student : private Person{};
//Print不可见
//_name不可见
//_age不可见
class Student : public Person{};
//Print函数类内外都可访问到
//_name仅内类可访问
//_age不可见
// Student的_num和People的_num构成隐藏关系,可以看出这样代码虽然能跑,但是非常容易混淆
class People
{
protected :
string _name = "小李子"; // 姓名
int _num = 111; // 身份证号
};
class Student : public People
{
public:
void Print()
{
cout<<" 姓名:"<<_name<< endl;
cout<<" 身份证号:"<<People::_num<< endl;
cout<<" 学号:"<<_num<<endl;
}
protected:
int _num = 999; // 学号
};
void Test()
{
Student s1;
s1.Print();
};
// B中的fun和A中的fun不是构成重载,因为不是在同一作用域
// B中的fun和A中的fun构成隐藏,成员函数满足函数名相同就构成隐藏。
class A
{
public:
void fun()
{
cout << "func()" << endl;
}
};
class B : public A
{
public:
void fun(int i)
{
A::fun();
cout << "func(int i)->" <<i<<endl;
}
};
void Test()
{
B b;
b.fun(10);
};
我们不写默认生成的派生的构造和析构?
a、父类继承下来得 (调用父类默认构造和析构处理) b、自己的(内置类型和自定义类型成员)(跟普通类一样)
我们不写默认生成的拷贝构造和operator=?
a、父类继承下来得 (调用父类拷贝构造和operator=) b、自己的(内置类型和自定义类型成员)(跟普通类一样)
总结:原则,继承下来调用父类处理,自己的按普通类基本规则,普通类的基本规则不懂查看我的C++类的基本知识文章了解。
1、父类没有默认构造,需要我们自己显示写构造
2、如果子类有资源需要释放,就需要自己显示写析构
3、如果子类存在浅拷贝问题,就需要自己实现拷贝构造和赋值解决浅拷贝问题。
父类成员调用父类的对应构造、拷贝构造、operator=和析构处理,自己成员按普通类处理。
class People
{
public:
People(const char* name)
:_name(name)
{}
People(const People& p)
:_name(p._name)
{}
People& operator=(const People& p)
{
if (this != &p)
_name = p._name;
return *this;
}
~People()
{
delete[] _ptr;
}
protected:
string _name;
int* _ptr = new int[10];
};
class Student :public People
{
public:
Student(const char* name,int num=1)
:People(name)
,_num(num)
{}
Student(const Student& s)
:People(s)
,_num(s._num)
{}
Student& operator=(const Student& s)
{
if (this != &s)
{
People::operator=(s);
_num = s._num;
}
return *this;
}
~Student()
{
delete[] _p;
}
protected:
int _num = 1;
string _s = "nice to meet you";
int* _p = new int[10];
};
注意:这里大家可能会有疑问,为什么没有对父类的资源进行主动的释放?
友元关系不能继承,也就是说基类友元不能访问子类私有和保护成员。一段代码加深理解:
lass Student;
class Person
{
public:
friend void Display(const Person& p, const Student& s);
protected:
string _name; // 姓名
};
class Student : public Person
{
protected:
int _stuNum; // 学号
};
void Display(const Person& p, const Student& s)
{
cout << p._name << endl;
cout << s._stuNum << endl;
}
void main()
{
Person p;
Student s;
Display(p, s);
}
虽然子类继承了父类,但是Display是友员函数,所以在子类中无法使用Display。
基类定义了static静态成员,则整个继承体系里面只有一个这样的成员。无论派生出多少个子类,都只有一个static成员实例 。
单继承:一个子类只有一个直接父类时称这个继承关系为单继承,如下图所示的关系
多继承:一个子类有两个或以上直接父类时称这个继承关系为多继承,如下图所示的关系
**菱形继承:**菱形继承是多继承的一种特殊情况。如下图所示的关系:
通过菱形继承关系图,可以看出菱形继承的问题会有些问题,从下面的对象成员模型构造,可以看出菱形继承有数据冗余和二义性的问题。在doctor的对象中Person成员会有两份。
class Person
{
public :
string _name ; // 姓名
};
class Student : public Person
{
protected :
int _num ; //学号
};
class Teacher : public Person
{
protected :
int _id ; // 职工编号
};
class doctor: public Student, public Teacher
{
protected :
string _majorCourse ; // 主修课程
};
void Test ()
{
// 这样会有二义性无法明确知道访问的是哪一个
doctor a ;
a._name = "peter";
// 需要显示指定访问哪个父类的成员可以解决二义性问题,但是数据冗余问题无法解决
a.Student::_name = "xxx";
a.Teacher::_name = "yyy";
}
虽然可以解决二义性问题,但是数据冗余问题无法解决,所以我们可以采用虚拟继承来解决菱形继承的二义性和数据冗余的问题。如上面的继承关系,在Student和Teacher的继承Person时使用虚拟继承,即可解决问题。需要注意的是,虚拟继承不要在其他地方去使用。以上述代码为例虚继承的实现:
class Person
{
public :
string _name ; // 姓名
};
class Student : virtual public Person
{
protected :
int _num ; //学号
};
class Teacher : virtual public Person
{
protected :
int _id ; // 职工编号
};
class doctor : public Student, public Teacher
{
protected :
string _majorCourse ; // 主修课程
};
void Test ()
{
doctor a ;
a._name = "peter";
}