通过继承可以很好的实现复用。每个类中相同的部分就可以定义为基类(父类),通过使用public、protected、private三种继承关系派生类(子类)在父类的基础上又有一些新的成员。
一、继承方式与访问限定符
三种继承方式与三种访问限定符就出现了九种组合。
继承方式/访问限定符 | 基类中public成员 | 基类中protected成员 | 基类中private成员 |
public | public成员 | protected成员 | 不可见 |
protected | protected成员 | protected成员 | 不可见 |
private | 不可见 | 不可见 | 不可见 |
可见没有继承关系前保护protected和私有private都是类外对象不能访问的,在有了继承后如果一些基类成员不想被派生类对象访问但类内可以访问就定义为protected。
子类继承了父类的所有成员(包括所有成员函数和成员变量)表中不可见只是因为限制了访问。
二、public继承的赋值兼容规则
#include <iostream>
#include <string>
using namespace std;
class Person//父类
{
public:
void ShowName()
{
cout<<_name<<endl;
}
Person()
{
_name = "jack_ma";
}
protected:
string _name;
};
class PRCperson:public Person//子类
{
public:
int _ID ;
PRCperson()
:_ID(86)
{}
void Print()
{
cout<<_ID<<endl;
}
void PrintPro()
{
cout<<_name<<endl;//在派生类中才可以通过派生类对象访问基类对象
}
};
int main()
{
Person p;
PRCperson r;
p = r;//切片
//r1 = p1;//PRCperson 比 person类多_ID,无法赋值
r.Print();
r.PrintPro();
//p._name = "ray";基类对象不能访问基类protected成员
Person *p1 = &r;//父类的指针可以指向子类
Person &p2 = r;//父类的引用可以指向子类
PRCperson *r1 = (PRCperson *)&p;//可以将父类的指针强转为子类的指针
PRCperson &r2 = (PRCperson &)p;//可以将父类的引用强转为子类的引用
system("pause");
}
三、继承中的子类函数对父类函数的隐藏
1.父类和子类各自有各自不同的作用域,这一点在区分重载时十分关键。(重载:在同一作用域中、函数名相同参数不同、virtual关键字可有可无)
2.子类和父类中有同名函数、参数不一定相同时构成隐藏的情况。隐藏是能找到的
举个例子:
class Base
{
public:
virtual void g(float x)//void g(float x)
{
cout << "Base::g(float) " << x << endl;
}
void h(float x)
{
cout << "Base::h(float) " << x << endl;
}
};
class Derived : public Base
{
public:
void g(int x)
{
cout << "Derived::g(int) " << x << endl;
}
void h(float x)
{
cout << "Derived::h(float) " << x << endl;
}
};
void main(void)
{
Derived d;
Base *pb = &d;
Derived *pd = &d;
pb->g(3.14f); // Base::g(float) 3.14
pd->g(3.14f); // Derived::g(int) float到int 3
pd->Base::g(3.14f)//指定作用域
pb->h(3.14f); // Base::h(float) 3.14
pd->h(3.14f); // Derived::h(float) 3.14
system("pause");
}
Devited中的函数g和h对Base中的函数g和h构成了隐藏。
其中g是函数名相同参数不同无论有无virtual关键字都是隐藏(作用域区别于重载),pb能找到Base::g(float),pd没有找到Derived::g(float)只能将float转化为int。h是函数名相同参数也相同但是没有关键字virtual(基类h中无virtual区别于覆盖)
四、子类构造函数、析构函数的调用顺序。
创建一个子类对象就会自动调用构造函数,子类中继承的父类看做一个整体(整体初始化用父类的构造函数)。析构函数则不同
不用显示调用父类的析构函数,出了作用域编译器会自动调用,因为对象在栈中先创建父类再创建子类,编译器为了防止你“做坏事”自己就按照后进先出的原则析构子类出了作用域再析构父类。
class Person
{
public:
Person(const char* name = "xxx")
:_name(name)
{
cout<<"Person(const char* name)"<<endl;
}
Person &operator = (const Person &p)
{
cout<<" Person &operator = "<<endl;
if(this!= &p)
{
_name = p._name;
}
return *this;
}
~Person()
{
cout<<"~Person"<<endl;
}
protected:
string _name;
};
class Student:public Person
{
public:
Student()
:_id(0)
{}
Student(const Student& s)
:Person(s)
,_id(s._id)
{}
Student &operator = (const Student &s)
{
if(this != &s)
{
Person::operator =(s);
_id = s._id;
}
return *this;
}
//~Student()
//{
// Person::~Person();//析构不需要显式调用,先定义父类后定义子类存在栈中
//为了保准先析构子类再析构父类编译器出了子类自动调用父类析构
//}
protected:
int _id;
};
int main()
{
Student s1;
Person p;
Student s2(s1);
s1.~Student();
p.~Person();
return 0;
}
五、多继承
如果子类继承了不止一个父类就是多继承。这时就会出现菱形继承的现象。
出现的问题有两个:数据冗余和二义性。二义性可以用指定作用域的方法解决,但是更好的是用虚拟继承同时解决数据冗余和二义性。
class A
{
public:
int _a;
};
class B: virtual public A
{
public:
int _b;
};
class C:virtual public A
{
public:
int _c;
};
class D:public B,public C
{
public:
int _d;
};
int main()
{
cout<<sizeof(D)<<endl;
D d;
d.B::_a = 1;
d.C::_a = 2;
d._b = 3;
d._c = 4;
d._d = 5;
system("pause");
}
用内存窗口查看D的储存方式
查看地址0x00f45814
这样在切片时就能很快找到公共元素a了。