2.1继承的概念
C++通过类派生的机制来支持继承。被继承的类称为基类或超类 ,新产生的类为派生类或子类。基类和派生类的集合称作类继承层次结构 。由基类派生出,派生类的设计形式为:
class 派生类名: 访问限定符 基类名
{
private:
成员表1;//派生类增加或替代的私有成员
public:
成员表2;//派生类增加或替代的公有成员
protected:
成员表3; //派生类增加或替代的保护成员了
}; //分号不可少
总结:派生反映了事物之间的联系,事物的共性与个性之间的关系。派生与独立设计若干相关的类,前者工作量少,重复的部分可以从基类继承来,不需要单独编程。继承是类型设计层面上的复用。
2.2编制派生类时可分为四步
注意:构造函数和析构函数不能被继承。 我们知道构造函数和析构函数是跟对象的创建与消亡的善后工作相关。我们所创建派生类的对象,虽然和基类的对象有相同之处,但是仍然是不同的对象。所以,适用于基类的构造函数和析构函数不可能完全满足派生类对象的创建和消亡的善后工作。因此,构造函数和析构函数不被继承。
第二:其他函数都可以通过类对象来调用,创建对象时由系统调用该对象所属类的构造函数,在该对象生存期中也只调用这一次。由继承而来的派生类对象,是能够调用父类的函数,但不能调用构造函数也不能调用析构函数。
继承方式,亦称为访问控制,是对基类成员进一步的限制。继承方式也是三种: 公有 (public) 方式,亦称公有继承 保护 (protected) 方式,亦称保护继承 私有 (private) 方式,亦称私有继承。
2.3两层继承关系中的数据成员和方法(可见性与访问控制)
class object
{
private:int oa;
protected: int ob;
public:int oc;
};
class Base : public object//此处继承方式无论是公有、私有还是保护,以下结果均相同
{
private:int bx;
protected: int by;
public:int bz;
void fun()
{
bx=by=bz=0;//ok
oa=1;//error
ob=2;//ok
oc=3;//ok
}
};
int main()
{
object obj;
Base base;
cout<<"obj size: "<<sizeof(obj)<<endl;//12
cout<<"base size: "<<sizeof(base)<<endl;//24
//在外部直接访问基类成员数据
base.object::oc=2;//只有当继承方式为公有时,才可以这样访问,oc是object类中的公友成员数据,所有外部可以访问
return 0;
}
base对象中有四个成员:使用基类名标识这个无名对象
在派生类方法中可以访问派生类自身的所有属性,所有属性就包括这个基类无名对象,无论以保护还是私有属性进行继承,派生类均可访问,而该对象的成员数据能否访问就取决于成员数据的属性。基类无名对象中的成员有三个,派生类方法只能访问其公有和保护属性,私有不能访问。
在继承关系中,对于内部函数,保护属性相当于公有属性;而对于外部函数,保护属性相当于私有属性。
总结: 1.不论采取何种继承方式,基类中所有数据成员都将继承到派生类。
2.在类型的继承层次结构中,保护属性当作公有属性来使用。
3.继承性具有传递性。
4.无论采取何种继承方式,派生类对象的成员方法都可以去访问基类对象中的保护和公有属性。基类对象与成员对象的区别。
2.4继承的基对象成员和成员对象的差别
主要区别在于保护属性以及构造顺序
class object
{
private:int oa;
protected: int ob;
public:int oc;
};
class Base : public object
{
private:int bx;
protected: int by;
public:int bz;
object obja;
void fun()
{
oc=1;//ok
ob=2;//ok
obja.oc=1;//ok
obja.ob=2;//error
}
};
int main()
{
Base base;
//访问继承的基类无名对象的成员
base.object::oc=1;//ok
//访问作为成员属性的基类对象的成员
base.obja.oc=2;//ok
return 0;
}
主函数中构建base对象时先调用基类的默认构造函数,构建基类无名对象,然后再调用自己的默认构造函数,依次构建bx、by、bz,最后再调用object类的构造函数,构建obja对象。
总结:继承的基对象一定是第一个进行构建的,然后再按照设计的顺序对其他成员属性进行构建,包括成员对象。
继承于基类的派生类方法可以访问基类的保护属性,但当基类对象作为派生类的成员时,就不可以访问该成员对象的保护属性。
2.5多层继承关系中的数据成员和方法(可见性和访问控制)
不是多重继承,多重继承是一个对象同时继承多个基类。
class object
{
private:int oa;
protected: int ob;
public:int oc;
};
class Base : public object
{
private: int bx;
protected: int by;
public: int bz;
};
class Test: public Base
{
private: int ta;
protected: int tb;
public: int tc;
void fun_ts()
{
ta=tb=tc=0;//ok
by=bz=1;//ok
bx=1;//error
ob=oc=2;//ok
oa=2;//error
}
};
int main()
{
object obj;
Base base;
Test test;
return 0;
}
test对象中有四个成员,base对象中有4个成员,obj对象中有三个成员
对于Test类,无论继承方式是公有私有还是保护,除了私有的oa和bx不能访问,其他均可以访问。但当Base类的继承方式变为私有时,Test类的成员方法将无法访问object类中所有成员。继承关系中,对于内部方法,保护属性当作公有属性。
2.6同名隐藏(成员属性和成员方法)
成员属性
class Object
{
public:
int val;
int num;
};
class Base : public Object
{
public:
void fun()
{
val=1;
num=2;//是Base类中的num
sum=3;
//当需要访问基类中同名的成员数据时,可以加上作用域限定符
Object::num=4;//ok
}
public:
int num;
int sum;
};
派生类中同名的成员属性名把基类中同名的成员属性名给隐藏掉了。成员数据名相同时,首先在自己的类中寻找该名,没有找到再去基类中寻找,与成员的访问属性无关。这种方式叫做就近原则。
无论在类内还是在类外,当需要访问基类中同名的成员属性时,必须加上作用域限定符。不加作用限定符的均进行就近原则,与其访问属性无关。
成员方法
class Object
{
public:
void fun()
{
cout<<"Object::fun"<<endl;
}
void func()
{
cout<<"Object::func"<<endl;
}
};
class Base : public Object
{
public:
void fun()
{
cout<<"Base::fun"<<endl;
}
void func(int x)
{
cout<<"Base::func"<<endl;
}
};
int main()
{
Base base;
base.fun();//会调用Base类成员方法
base.Object::fun();//调用Object类成员方法
base.func(12);//error
base.func();//ok
base.Object::func(12);//ok
return 0;
}
无法通过参数的不同来进行区分,必须使用作用域限定符,否则为派生类的函数。
当然也可以在Base派生类中进行声明基类中的该方法,如:using Object::func; 必须为以公有属性进行声明,这样就可以在调用时不加作用域解析符,但这并不是函数重载。但是当两个函数参数相同时这样做依然会使用就近原则先调用派生类中的方法。