一、继承:
1.1
继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能。这样产生新的类,称派生类。继承呈现了面向对象程序设计的层次结构。体现了由简单到复杂的认识过程。
1.2三中继承方式图解:
(1)保护成员在继承时等同于公有成员,在类外访问等同于私有属性。
(2)基类私有成员在派生类中“不可见”
(3)使用继承, 我们的继承方式一般都是public继承, 因为其他继承方式, 没法复用代码,代码利用率低。
1.3具有继承关系的内存分布
隐藏父对象:就是在子类继承父类时,他是会在子类中构建一个隐藏的父对象,至于与它是什么方式继承无关,只要是继承就会生成该对象,此文中会大量使用隐藏父对象
1.3.1分布情况:
#include<iostream>
using namespace std;
class Base
{
private:
int a;
int b;
};
class Derived:public Base
{
};
int main()
{
cout << sizeof(Base) << endl;
cout << sizeof(Derived) << endl;
}
继承时,会继承基类的一切成员。
1.3.2不可见性:
派生类私有继承的基类私有成员“不可见”,无法访问。
#include<iostream>
using namespace std;
class A
{
private: int ax;
protected: int ay;
public: int az;
public:
A() {
ax = ay = az = 0;
}
};
class B:public A
{
private: int bx;
protected: int by;
public: int bz;
public: B() { bx = by = bz = 1; }
void fun()
{
ay = 20; az = 30;//ax在此不可访问,不可见。
}
};
int main()
{
B b;
b.fun();
}
子类对象在构建时会构建一个隐藏父对象。
**此时内存分布如下:**上低下高
1.3.3隐藏无名对象和类内对象
对于继承而来的无名隐藏对象和 成员对象来说,最大的区别在于保护成员的访问问题:
无名对象A只有私有成员不可访问,而成员有名对象aa保护成员也不可访问。
using namespace std;
class A
{
private: int ax;
protected: int ay;
public: int az;
public:
A() {
ax = ay = az = 0;
}
};
class B:public A
{
private: int bx;
protected: int by;
public: int bz;
A aa;
public: B() { bx = by = bz = 1; }
void fun()
{
ay = 20; az = 30;
}
};
int main()
{
B b;
b.fun();
}
1.3.4同名隐藏:
大家对c的同名隐藏并不陌生,继承过程也具有同名隐藏的特性
#include<iostream>
using namespace std;
class A
{
protected: int ax;
public:A() :ax(0) {}
};
class B
{
private: int ax;
public:
B():ax(10){}
void fun()
{
ax = 100;
}
};
int main()
{
B b;
b.fun();
}
2.1赋值兼容性规则:
2.1.1、切片问题:
可以通过子对象给父对象赋值,因为子对象会构建无名隐藏父对象,赋值时可以切片赋值;不能通过父对象给子对象赋值。
#include<iostream>
using namespace std;
class Object
{
private: int value;
public:
Object(int x = 0) :value(x)
{
cout << "Create Object:" << this << endl;
}
~Object()
{
cout << "Destroy Object:" << this << endl;
}
};
class Base:public Object
{
private: int num;
public:
Base(int x = 0) :num(x), Object(x + 10)
{
cout << "Create Base" << this << endl;
}
~Base()
{
cout << "Destory Base" << this << endl;
}
};
int main()
{
Object obja(100);
Base base(10);
obja = base;
}
如图:
2.1.2:引用和赋值:
不能用父类指针指向子对象
类型对指针的约束有两个方面:1.指针+1的能力
2.类型解析的能力
父类指针指向子类对象,受类型的约束,只能读取到隐藏父对象的信息。
引用也是同理,只有引用父对象的信息,但可以使用,在下文拷贝构造继承会有使用
3.1、继承关系中的构造函数,析构函数,拷贝函数,赋值函数。
3.1.1、
由上文切片问题处可知构造函数,析构函数可以继承。
构建子类对象时,会先构造隐藏父类对象,在此基础上构建出子类对象。
3.1.2、拷贝构造的继承
拷贝构造的继承方式大有不同,下边便来看看拷贝构造到底怎么继承。
#include<iostream>
using namespace std;
class Object
{
private: int value;
public:
Object(int x = 0) :value(x)
{
cout << "Create Object:" << this << endl;
}
~Object()
{
cout << "Destroy Object:" << this << endl;
}
Object(const Object& obj):value(obj.value)
{
cout << "Copy Create Object" << this << endl;
}
};
class Base:public Object
{
private: int num;
public:
Base(int x = 0) :num(x), Object(x + 10)
{
cout << "Create Base" << this << endl;
}
~Base()
{
cout << "Destory Base" << this << endl;
}
Base(const Base& bas):num(bas.num)
{
cout << "Copy Create Base" << this << endl;
}
};
int main()
{
Base base(10);
Base base1(base);
}
由此可知,在子类对象拷贝构造时,只会调用子类的拷贝构造函数,而由内存分布可知,子类对象含有隐藏父对象,那不调用父类对象的拷贝构造,如何来构建拷贝后对象的隐藏父对象,在图中调用的是父类对象的构造函数来构建该隐藏父对象。那这样的构造是否会出现问题呢?
由图可知,这样的拷贝方式会出现错误,它构建的拷贝对象的隐藏父对象和被拷贝对象的隐藏父对象不同。那么我们要怎么才能调用父类的拷贝构造来构建该隐藏父对象呢?
其实,也是很简单的,我们只需使用列表的方式加上对其的构建,使其在拷贝构造子对象时,必须先去拷贝构造隐藏父对象。
由赋值兼容性规则我们可以知道,可以通过父类对象引用子类对象,而正好可以匹配父类的拷贝构造函数。
这种方式就可以来调用父类的拷贝构造。
至于说要是不通过列表方式初始化,而是直接写入子类拷贝里边可不可以呢?
由运行结果就可以看出,写在子类内没有任何用处,它是有对父类对象的拷贝,但是他是重新生成的一个无名对象,和要拷贝的隐藏父对象毫无关系(由this指针的地址就可以看出),并且它在构建后立马析构。所以该种方式并不可取。
3.1.3、赋值语句的继承
#include<iostream>
using namespace std;
class Object
{
private: int value;
public:
Object(int x = 0) :value(x)
{
cout << "Create Object:" << this << endl;
}
~Object()
{
cout << "Destroy Object:" << this << endl;
}
Object(const Object& obj):value(obj.value)
{
cout << "Copy Create Object" << this << endl;
}
Object& operator =(const Object& obj)
{
if (this != &obj)
{
value = obj.value;
}
cout << "Object::operator=:" << this << endl;
return *this;
}
};
class Base:public Object
{
private: int num;
public:
Base(int x = 0) :num(x), Object(x + 10)
{
cout << "Create Base" << this << endl;
}
~Base()
{
cout << "Destory Base" << this << endl;
}
Base(const Base& bas):num(bas.num),Object(bas)
{
cout << "Copy Create Base" << this << endl;
}
Base& operator =(const Base& bas)
{
if (this != &bas)
{
num = bas.num;
}
cout << "Base::operator=:" << this << endl;
return *this;
}
};
int main(){
Base basea(10);
Base baseb(20);
basea = baseb;
}
由图可知,和拷贝函数相同,并没有调动父类的赋值语句,那结果会不会出现错误呢?
未调用父类的赋值语句结果出现错误,basea的value值不等于baseb的value,那如何可以调动父类的赋值语句呢?可能有的人会认为这里也调用拷贝构造那样的列表方式就可以了,其实不能这样做,我们要记得:只有构造和拷贝构造由列表初始化的方式,其他函数不能
我们的方式也很简单,加入强转即可。
我们把base类型的指针直接强转为Object类型,就可以扩大他的解析范围,从而可以完美赋值。
在这类问题中,我们一定要注意赋值兼容性的问题。