C++是一门面向对象的编程语言(OOP),它有三大特性:封装,继承,多态。
今天我来主要详解一下继承。
一,继承概念:
继承机制(inheritance)是面向对象程序设计中使代码可以复用的最重要手段。他允许程序员在保持原有类特性的基础上进行扩展,增加功能。继承是类之间的关系建模,共享公有的东西,实现各自本质不同的东西。这样产生的类叫做派生类,也叫子类。之前的类叫做基类,也叫父类。
二,继承关系以及继承下的访问限定符
三种类成员访问限定符:public(公有)、private(私有)、protected(保护)。
三种继承关系:public(公有继承)、private(私有继承)、protected(保护继承)。
注:如果继承关系省略,则默认为private继承。
3,继承的定义格式:
单继承:
class <派生类类名> :<继承方式> <基类类名>
{
<新定义的类成员>
}
多继承:
class <派生类类名> :<继承方式> <基类类名>,<继承方式> <基类类名> ...
{
<新定义的类成员>
}
class base //基类(父类)
{
public:
void fun()
{}
private :
int _Bname;
};
//派生类(子类)
class derive :public base //继承关系
{
public:
void done()
{}
private:
int _Dname;
};
四,派生类的构成
派生类的构成有两大部分:自身定义的成员,从基类继承过来的成员。
class base //基类(父类)
{
public:
int _Bname;//为了更方便显示结果,我将成员变量定义成共有的。
};
//派生类(子类)
class derive :public base //继承关系
{
public:
int _Dname;
};
class base //基类(父类)
{
public:
int _Bname;//为了更方便显示结果,我将成员变量定义成共有的。
};
//派生类(子类)
class derive :public base //继承关系
{
public:
int _Dname;
};
int main()
{
base b1;
b1._Bname = 1;
derive d1;
d1._Dname = 2;
d1._Bname = 3;
system("pause");
return 0;
}
从代码中可以看出,对象d1可以对_Bname进行赋值,图片中也很好地展现了出来。这说明成员 _Bname以是d1的构成一部分了。
五,三种继承关系
public继承:
在平时实践的例子中,大多数都属于公有继承。公有继承中,基类的公有成员和保护成员在派生类中维持自己的访问属性,其私有成员为不可见,也就是说派生类不能访问基类的私有成员。私有成员体现了封装性,如果基类的私有成员被派生类访问,则破坏了基类的封装性。
基类的私有成员不能被派生类访问的。如果你想看看基类的私有成员有哪些,你只需在基类中定义一个非私有的成员函数Show()即可。
class base //基类(父类)
{
public:
void Show()
{
cout << _Bname << endl;
}
private:
int _Bname;
};
公有继承是一个接口继承,保持 is-a 原则,每个父类可访问的成员对子类也可访问。因为每个子类对象都是一个父类对象。
private继承:
私有继承中,基类的公有成员和保护成员都会变成私有成员,而私有成员则是不可见的。私有继承和保护继承是一种实现继承,基类的部分成员并未完全成为子类接口的一部分,是 has-a 的关系原则。
protected继承:
保护继承是一种实现继承,基类的部分成员并未完全成为子类接口的一部分,是 has-a 的关系原则。所以如果你不想基类有的成员被派生类访问,但是需要在基类中访问,你就将该成员设为保护成员。这就看出保护成员限定符是因为继承才出现的。
注:使用关键字class时默认的继承方式是private,使用struct是默认的继承方式是public,不过最好标注出来。
六,继承与转换 (在public继承的基础上)
1,子类对象可以赋值给父类对象(切片/切割);
2,父类对象不能赋值给子类对象;
3,子类对象可以赋值给父类的指针/引用,即父类的指针/引用可以指向子类对象。
4,子类的指针/引用不能指向父类对象(可以通过强制类型转换完成)。
上面四点我们来通过图和代码的方式来详解:
class base //基类(父类)
{
public:
base()
:_Bname(1)
{}
void Show()
{
cout << _Bname << endl;
}
private:
int _Bname;
};
//派生类(子类)
class derive :public base //继承关系
{
public:
derive()
:_Dname(2)
{}
int _Dname;
};
void Test()
{
base b;
derive d;
//1,关于赋值
//b = d; //子类可以赋值给父类(切片/切割)
//d = b; //父类不可赋值给子类。没有找到接收“base”类型的右操作数的运算符
//2,关于引用/指针
base* p1 = &d; //父类指针/引用可以指向子类对象
base& r1 = d;
derive* p2 = (derive*)&b; //可以通过强转实现
derive& r2 = (derive&)b;
//如果这样,则会发生什么?
p2->_Dname = 10; //发生错误,原因是指针p2强转类型后指向父类,但是父类不存在后四个字节内容,即不存在_Dname;
r2._Dname = 20; //错误,原因同上
b.Show();
d.Show();
}
int main()
{
Test();
system("pause");
return 0;
}
七,继承体系中的作用域
1,基类和派生类都有自己独立的作用域。
2,子类和父类中有同名成员,子类成员将屏蔽父类对成员的直接访问(在子类成员函数中,可以使用 基类 ::基类成员 访问)。这叫隐藏,后面的文章我会详细讲解。
class base
{
public:
base(const char* name = "",int id = 0)
:_name(name),_num(id)
{}
protected:
string _name; //姓名
int _num; //身份证号 与子类_num成员同名
};
class derive :public base
{
public:
derive(const char* name,int id,int num)
:base(name,id),_num(num)
{}
void Display()
{
cout << "身份证号:" << base::_num << endl; //通过作用域解析符来指定
cout << "学号:" << _num << endl;
}
protected:
int _num; //学号 与父类_num成员同名
};
void Test()
{
derive d("paul",110,1);
d.Display();
}
int main()
{
Test();
system("pause");
return 0;
}