c++之继承的理解

来源:互联网 发布:eclipse改端口号 编辑:程序博客网 时间:2024/06/10 11:47

面对对象编程

继承是为了解决代码的复用性
1.怎么解决代码的复用性?
组合和继承

组合:将一个类作为另一个类的对象成员。

class A{public:     void FunA()     {     }}class B{public:     void FunB()      {          a_.FunA();      }    private:     A a_;}

继承:
使用已有的类来创建新的类,新的类具有原有类的所有属性和操作,也可以增加新的属性和方法。

原有的类称为基类或者父类,新的类称为派生类或者子类

派生类是基类的具体化。
派生类的语法声明:
class 派生类名:继承方式 基类名
{
派生类新增成员的声明;
}
二、继承规则
派生类三种继承方式:公有继承、私有继承、保护继承
这里写图片描述

公有继承:父类成员在子类中保持原有的访问级别。
私有继承:父类成员在子类中变为private成员。
保护继承:父类成员在子类中变为protected成员。

2、默认继承的保护级别:
class Base{};
struct D1:Base{};//公有继承
class D2:Base{};//私有继承

3、不能自动继承的成员函数
(a)构造函数、析构函数、赋值运算符
(1)构造函数
派生类不能访问基类的私有成员,必须通过基类的方法来访问。
派生类构造函数必须使用基类构造函数。
派生类通过初始化列表来调用基类构造函数。

基类、派生类、子对象调用顺序。
子对象: 驻留在另一个对象中的对象,在一个类的定义中,声明另一个类的对象来作为成员变量。
构造函数:基类子对象–基类–派生类子对象–派生类
细狗函数:相反

(2)析构函数

(3)赋值运算符(operator=)

解释原因:
编译器总是根据类型来调用类成员函数。

但是一个派生类的指针可以安全地转化为一个基类的指针。这样删除一个基类的指针的时候,C++不管这个指针指向一个基类对象还是一个派生类的对象,调用的都是基类的析构函数而不是派生类的。如果你依赖于派生类的析构函数的代码来释放资源,而没有重载析构函数,那么会有资源泄漏。所以建议的方式是将析构函数声明为虚函数。
也就是delete a的时候,也会执行派生类的析构函数。

一个函数一旦声明为虚函数,那么不管你是否加上virtual 修饰符,它在所有派生类中都成为虚函数。但是由于理解明确起见,建议的方式还是加上virtual 修饰符。

构造方法用来初始化类的对象,与父类的其它成员不同,它不能被子类继承(子类可以继承父类所有的成员变量和成员方法,但不继承父类的构造方法)。因此,在创建子类对象时,为了初始化从父类继承来的数据成员,系统需要调用其父类的构造方法。

如果没有显式的构造函数,编译器会给一个默认的构造函数,并且该默认的构造函数仅仅在没有显式地声明构造函数情况下创建。

构造原则如下:
1. 如果子类没有定义构造方法,则调用父类的无参数的构造方法。
2. 如果子类定义了构造方法,不论是无参数还是带参数,在创建子类的对象的时候,首先执行父类无参数的构造方法,然后执行自己的构造方法。
3. 在创建子类对象时候,如果子类的构造函数没有显示调用父类的构造函数,则会调用父类的默认无参构造函数。
4. 在创建子类对象时候,如果子类的构造函数没有显示调用父类的构造函数且父类自己提供了无参构造函数,则会调用父类自己的无参构造函数。
5. 在创建子类对象时候,如果子类的构造函数没有显示调用父类的构造函数且父类只定义了自己的有参构造函数,则会出错(如果父类只有有参数的构造方法,则子类必须显示调用此带参构造方法)。
6. 如果子类调用父类带参数的构造方法,需要用初始化父类成员对象的方式,比如:

#include <iostream.h>classanimal{public:    animal(intheight, intweight)    {        cout<<"animal construct"<<endl;    }};classfish:publicanimal{public:    inta;    fish():animal(400,300), a(1)    {        cout<<"fish construct"<<endl;    }};voidmain(){    fish fh;}

(b)友元关系不能被继承(友元不属于类)
(c)静态成员无所谓继承。
基类中的static成员,在整个继承层次中只有一个实例
在派生类中访问static成员的方法
1、基类名::成员名
2、子类名::成员名
3、对象.成员名
4、指针->成员名
5、成员名

4、关于继承的补充
(1)
接口继承:公有继承,基类的公有成员函数在派生类中依然是公有,换句话说,
基类的接口成为了派生类的接口,因而是接口继承。、

实现继承:对于私有继承保护继承,我们想要在派生类中实现基类的接口,因此称为
实现继承。
++++++++++++++++++++
(2)
重载overload:是函数名相同,参数列表不同 重载只是在类的内部存在。但是不能靠返回类型来判断。
重写override:也叫做覆盖。子类重新定义父类中有相同名称和参数的虚函数。函数特征相同。但是具体实现不同,主要是在继承关系中出现的 。
重写需要注意:
1 被重写的函数不能是static的。必须是virtual的
2 重写函数必须有相同的类型,名称和参数列表
3 重写函数的访问修饰符可以不同。尽管virtual是private的,派生类中重写改写为public,protected也是可以的

重定义 (redefining)也叫做隐藏:
子类重新定义父类中有相同名称的非虚函数 ( 参数列表可以不同 ) 。
如果一个类,存在和父类相同的函数,那么,这个类将会覆盖其父类的方法,除非你在调用的时候,强制转换为父类类型,否则试图对子类和父类做类似重载的调用是不能成功的。

class Base{private:     virtual void display() { cout<<"Base display()"<<endl; }     void say(){ cout<<"Base say()"<<endl; }public:     void exec(){ display(); say(); }     void f1(string a) { cout<<"Base f1(string)"<<endl; }     void f1(int a) { cout<<"Base f1(int)"<<endl; } //overload,两个f1函数在Base类的内部被重载};class DeriveA:public Base{public:     void display() { cout<<"DeriveA display()"<<endl; } //override,基类中display为虚函数,故此处为重写     void f1(int a,int b) { cout<<"DeriveA f1(int,int)"<<endl; } //redefining,f1函数在Base类中不为虚函数,故此处为重定义     void say() { cout<<"DeriveA say()"<<endl; } //redefining,同上};class DeriveB:public Base{public:     void f1(int a) { cout<<"DeriveB f1(int)"<<endl; } //redefining,重定义};int main(){     DeriveA a;     Base *b=&a;     b->exec(); //display():version of DeriveA call(polymorphism) //say():version of Base called(allways )     b里边的函数display被A类覆盖,但是say还是自己的。     a.exec(); //same result as last statement        a.say();     DeriveB c;     c.f1(1); //version of DeriveB called     return 0;}

执行结果:

这里写图片描述

综上所述,总结如下:
1 成员函数重载特征:
a 相同的范围(在同一个类中)
b 函数名字相同
c 参数不同
d virtual关键字可有可无
2 重写(覆盖)是指派生类函数覆盖基类函数,特征是:
a 不同的范围,分别位于基类和派生类中
b 函数的名字相同
c 参数相同
d 基类函数必须有virtual关键字
3 重定义(隐藏)是指派生类的函数屏蔽了与其同名的基类函数,规则如下:
a 如果派生类的函数和基类的函数同名,但是参数不同,此时,不管有无virtual,基类的函数被隐藏。
b 如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有vitual关键字,此时,基类的函数被隐藏。

注意区分虚函数中的重载和重写:
class A
{
public:
virtual int fun(){}
};

class B:public A
{
int fun(int a){} //这是重载而不是重写:
}

int mian()
{

}

class B:public A
{
int fun() // 从A继承来的 fun, 编译器会自己偷偷帮你加上
int fun(int a){} // 新的fun, 和前面的只是名字一样的重载函数, 不是虚函数
}
(3)组合与继承
这里写图片描述

5、基类、派生类之间的转换

(1)派生类到基类的转换

1. 公有继承时,编译器自动执行转换,就是向上转型,

派生类对象指针自动转换为基类对象指针。
派生类引用自动转化为基类对象引用。
派生类对象自动转化为基类对象(特有的成员消失)
2.private/protected继承
派生类对象指针(引用)转化为基类对象指针(引用),必须强制类型转化。使用reinterpret_cast;
不能把派生类对象强制转化为基类对象。

(2)基类对象指针(引用)可强制类型转换为派生类对象指针(引用),而基类对象无法执行这类转换。

向下转型不安全,没有自动转换的机制。

6、多继承与多重继承
(1)多继承
class A;
class B;
class c: public A,public B;
解决二义性的问题(A和B中有相同的成员函数),采用限制域作用符::。

(2)多重继承(一个派生类可以有很多基类)
class A;
class B: public A;
class D:public A,public B;
派生类同时继承多个基类的成员,更好的软件重用性。
可能会有大量的二义性,多个基类中可能包含同名的变量或函数。

解决方法:
1.基类名::成员名。
2.采用虚基类来解决。

7、虚基类、虚继承
(1)虚基类的语法声明:class B1 : virtual public BB;
(2)虚基类的作用:解决多重继承的二义性问题
为最远的派生类提供唯一的基类成语,而不重复产生多次拷贝。
(3)构造函数的问题
1.虚基类的成员是由最远派生类的构造函数通过调用来实现初始化的。
2.在整个继承结构中,直接或间接继承虚基类的所有派生类,都必须在构造函数的成员初始化表中给出对虚基类的构造函数的调用
3.建立对象时,只有最派生类的构造函数调用虚基类的构造函数,该派生类的其他基类对虚基类构造函数的调用被忽略

8、虚继承对C++对象内存模型造成的影响。
这里写图片描述

这里写图片描述

0 0