继承的概念:
首先我们举一个例子:
如图, “公马”继承了“马”的全部特征,增加了“雄性”的新特征。“白公马”继承了“公马”的全部特征,再增加“白色”的特征。“公马”是“马”派生出来的一个分支,“白公马”是“公马”派生出来的一个分支。
继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能。这样产生新的类,称派生类。继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。
C++中的所谓的“继承”就是在一个已存在的类的基础上建立一个新的类。已存在的类(例如“马”)成为“基类”或“父类”。新建的类(例如“公马”)称为“派生类”或“子类”。
基类:负责定义各层次关系中所有类的共同成员。
派生类:负责定义各自特有的成员。
class Person //父类/基类
{
private:
int _age;
string _name;
};
class Student/*子类/派生类*/:public/*访问限定符*/ Person
{
private:
int _num;
};
继承方式:
1、public 公有继承
基类的公有成员和保护成员作为派生类的成员,保持原有状态和属性,私有成员不能被这个派生类的子类访问。
2、protected 保护继承
基类的所有公有成员和保护成员都成为派生类的保护成员,并且他们只能被派生类成员函数或友元访问,基类的私有成员依然是私有的。
如果一些基类成员不想被基类对象直接访问,但需要在派生类中访问,就应该将其声明为protected成员
3、private 私有继承
基类的公有成员和保护成员都称为派生类的私有成员,并且不能被这个派生类的子类所访问。
4、使用class时,默认继承方式为private,使用struct时,默认继承方式为pbulic。
不管是哪种继承方式,在派生类内部都可以访问基类的公有成员和保护成员,但是基类的私有成员存在但不可以访问。
下面对pbulic 继承方式进行演示:
class Date
{
public:
int _day;
protected:
int _month;
private:
int _year;
};
class Day:public Date
{
public:
void Fun()
{
_day = 9; //公有成员
_month = 9; //保护成员只能在父类和子类内部使用
_year = 2017; //在父类中是私有成员,在子类中无法访问。
}
};
int main()
{
Day d1;
d1._day = 10;
d1._month = 9; //无法在类外访问
d1._year = 2017; //无法访问保护成员
return 0;
}
继承与转换:
1.赋值兼容规则:public继承
在需要基类对象的时候,可以使用共有派生类的对象来替代。通过公有继承,派生类得到了基类中除构造函数,析构函数以外的所有成员,而且所有成员的访问控制属性与基类完全相同。则该公有派生类就具备了基类的所有功能。
(1).派生类的对象可以赋值给基类。
(2).派生类的对象可以初始化基类的引用。
(3).派生类对象的地址可以赋给指向基类的指针/引用。
(4).用公有派生类代替基类。
class Person
{
public:
void Display()
{
cout << _name << endl;
}
private:
string _name;
};
class Student:public Person
{
public:
int _num;
};
void test()
{
Person p;
Student s;
p = s; //子类可以初始化父类
//Person *p1 = &s;
//Person &r1 = s;//父类的指针和引用可以指向子类对象
}
继承中的构造与析构:
class Person
{
public:
Person(const char *name = "", int id = 0)
:_name(name)
,_number(id)
{}
protected:
string _name; //姓名
int _number; //身份证
};
class Student :public Person
{
public:
Student(const char*name, int id, int stuNum)
:Person(name,id)
,_num(stuNum)
{}
void Display()
{
cout << "学号" << _num << endl;
cout << "身份证" << Person::_number << endl;
cout << "姓名" << _name << endl;
}
protected:
int _num; //学号
};
该代码的对象模型为:
可以看出如果想要构造一个Student类型的对象,需要调用父类的构造函数对子类中的父类成员进行初始化。
同样,在析构一个子类对象时,需要调用父类的析构函数对子类中的父类成员进行析构。
注意:如果父类的构造函数中有参数时,需要在子类的初始化列表中显示调用。(因为编译器无法提供一个默认构造函数)
调用顺序:
派生类的构造函数:父类的构造函数—成员对象的构造函数—子类的构造函数。
析构函数则与构造函数相反。
测试函数:
class Date
{
public:
Date() {
cout << "Date()" << endl;
}
~Date() {
cout << "~Date()" << endl;
}
};
class Base
{
public:
Base(){
cout << "Base()" << endl;
}
~Base() {
cout << "Base()" << endl;
}
};
class Derived:public Base
{
public:
Derived(){
cout << "Derived()" << endl;
}
~Derived() {
cout << "Derived()" << endl;
}
private:
Date d1;
};
void test() {
Derived d2;
}
int main()
{
test();
system("pause");
return 0;
}
继承中的重名情况:
1.成员变量重名:
class A
{
public:
int a;
int b;
};
class B :public A
{
public:
int b;
int c;
};
void test() {
B b1;
b1.b = 1;
b1.A::b = 2;
}
对象模型为:
可以看出里面的有2分相同名字的成员,在进行直接访问b成员时,会直接访问派生类中的b,如果想要访问A中的b,则需要加上作用域。
2.成员函数重名:
成员函数重名与成员变量重名基本相同,在不声明作用域的情况下,默认调用子类中的成员。
class A
{
public:
void DisPlay()
{
cout << "A" << endl;
}
};
class B :public A
{
public:
void DisPlay()
{
cout << "B" << endl;
}
};
void test() {
B b1;
b1.DisPlay();
b1.A::DisPlay();
}
单继承与多继承:
单继承: 一个子类只有一个直接父类。
菱形继承:
class A
{
public:
int _a;
};
class B1 :public A
{
public:
int _b1;
};
class B2 :public A
{
public:
int _b2;
};
class D :public B1, public B2
{
public:
int _d;
};
void test()
{
D d1;
d1._a = 1;
}
但是这里会出现D::a,不明确的错误提示。
在创建出的d1对象中,_a有两份。所以当你直接访问时,编译器不知道该给哪一个赋值。
所以菱形继承存在二义性问题。 对于base class的调用时,要说明作用域。
void test()
{
D d1;
d1._b1 = 10;
d1._b2 = 11;
d1._d = 12;
d1.B1::_a = 13;
d1.B2::_a = 14;
}