前言
在很多程序中都存在着一些互相关联但是有细微差别的概念,比如,同一家服装店,不同衣服价格的定价策略不同,比如有的原价销售,有的打折销售。有时当总价格达到某个量级就会经行减免。
这时面向对象编程(OOP)适用于这类应用。
一、面向对象编程
这个概念太过宏大而且每个人都有自己对于其的了解,这里我们就简单讲讲什么是面向对象编程。
二、继承的概念及定义
2.1继承的概念
通过继承联系在一起的类构成一种层状结构,通常在层状结构的根部有一个基类,其他类直接或间接从基类继承而来,这些继承得到的类称为派生类。基类负责定义在层次关系中所有类共同拥有的成员,而每个派生类定义各自特有的成员。
2.2继承定义
定义格式
继承关系和访用限定符
继承基类成员访问方式的变化
总结:
三、派生类对象及派生类向基类的的类型转换
一个派生类对象包含多个组成部分:一个含有派生类自己定义的(非静态)成员的子对象,以及一个与该派生类继承的基类对应的子对象,如果有多个基类,那么这样的子类对象也有多个。
如上图所定义的student对象,它拥有一个自己定义的学号_No编号,还有继承自Person三个成员变量。(注:c++标准没有明确规范派生类对象在内存中如何分配,但是我们可以认为是如图分配的,在一个对象中,继承自基类的部分和派生类自定义的部分不一定是连续存储的)。
对于基类对象和派生类对象来说
class Person
{
protected :
string _name; // 姓名
string _sex; // 性别
int _age; // 年龄
};
class Student : public Person
{
public :
int _No ; // 学号
};
void Test ()
{
Student sobj ;
// 1.子类对象可以赋值给父类对象/指针/引用
Person pobj = sobj ;
Person* pp = &sobj;
Person& rp = sobj;
//2.基类对象不能赋值给派生类对象
sobj = pobj;
// 3.基类的指针可以通过强制类型转换赋值给派生类的指针
pp = &sobj
Student* ps1 = (Student*)pp; // 这种情况转换时可以的。
ps1->_No = 10;
pp = &pobj;
Student* ps2 = (Student*)pp; // 这种情况转换时虽然可以,但是会存在越界访问的问
题
ps2->_No = 10;
}
四、继承中的作用域
要注意的是第三条,如果基类和派生类有函数名相同但参数不同,不会构成函数重载,也派生类对象也无法调用基类中的函数,如下:
class A
{
public:
void fun(int c)
{
cout << "A";
}
int a;
};
class B :public A
{
public:
void fun(int c,int d)
{
cout << "B";
}
};
int main()
{
B b;
int a=0;
b.fun(a);
}
会显示b.fun()调用参数太少。
五、派生类的默认成员函数
六、继承与友元,静态成员
友元关系不能被继承,也就是说基类友元不能访问子类私有和保护成员。
基类定义了static静态成员,则整个继承体系里面只有一个这样的成员。无论派生出多少个子
类,都只有一个static成员实例 。
七、继承与组合
继承和组合
八、多继承,复杂的菱形继承和虚拟继承
单继承:一个子类只有一个直接父类时称这个继承关系为单继承
多继承:一个子类有两个或以上直接父类时称这个继承关系为多继承
菱形继承:菱形继承是多继承的一种特殊情况。
会顾之前第三章中的派生类对象拥有的内容,我们很容易发现问题,那就是最下方的Assistant类在实例化时会在内存中存两个Person的类成员。
那问题来了当我们从Assistant类中访问_name时,我们到底该访问哪个地址的数据,如果我们让该对象去直接转换成Student, Teacher的对象时,我们该在进行“切片”时,数据又该如何分配。
这就是数据二义性和数据冗余问题。
// 需要显示指定访问哪个父类的成员可以解决二义性问题,但是数据冗余问题无法解决
a.Student::_name = "xxx";
a.Teacher::_name = "yyy";
虚拟继承可以解决菱形继承的二义性和数据冗余的问题。如上面的继承关系,在Student和
Teacher的继承Person时使用虚拟继承,即可解决问题。需要注意的是,虚拟继承不要在其他地
方去使用。
8.1虚拟继承解决数据冗余和二义性的原理
这里我们为了研究虚拟继承原理,创建一个简单的菱形虚拟继承体系,再借助内存窗口观察对象成员的模型。
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()
{
D d;
d.B::_a = 1;
d.C::_a = 2;
d._b = 3;
d._c = 4;
d._d = 5;
return 0;
}
下图是菱形继承的内存对象成员模型:这里可以看到数据冗余(没有加virtual)
下图是菱形虚拟继承的内存对象成员模型:这里可以分析出D对象中将A放到的了对象组成的最下
面,这个A同时属于B和C,那么B和C如何去找到公共的A呢?这里是通过了B和C的两个指针,指
向的一张表。这两个指针叫虚基表指针,这两个表叫虚基表。虚基表中存的偏移量。通过偏移量
可以找到下面的A。
也就是说当我们在访问_a时会直接访问到一个偏移量,根据这个偏移量我们可以找到D中所存的独一份的A。