求职宝典 第九章 面向对象编程

来源:互联网 发布:egd网络黄金合法吗 编辑:程序博客网 时间:2024/05/16 15:31


C++中,一个派生类可以从一个基类中派生,也可以从多个基类中派生,前者称为单继承,后者称为多继承。

单继承的定义格式如下:

Class <派生类名><继承方式> <基类名>

{

    <派生类新定义成员>

}

多继承的定义格式如下:

Class <派生类名><继承方式1> <基类名1><继承方式2><基类名2>...

{

    <派生类新定义成员>

}

 

其中,<派生类名>是新定义的一个类的名字,它是从基类名中派生的,并且按指定的继承方式派生的。继承方式常用关键字表示如下:

Public:表示共有基类

Private:表示私有基类

Protected:表示保护基类

派生类对象由派生类本身定义的(非static)成员对象加上由基类(非static)成员对象组成。

 

1.基类成员在派生类中的访问属性

派生类可以继承基类中除了构造函数与析构函数(运算符重载函数也不能被继承)之外的成员,从基类继承来的成员在派生类中的访问属性由继承方式控制。

不论哪种继承方式,基类中的私有成员都不允许派生类继承,即在派生类中不可见。

派生类对基类的访问形式主要有两种:

1)内部访问:派生类中新增的成员函数对基类继承来的成员的访问。

2)对象访问:在派生类的外部,通过派生类的对象对从基类继承来的成员的访问。

 

共有继承:

    共有继承的特点是基类的共有成员和保护成员作为派生类的成员时,都保持原有的状态,而基类的私有成员仍然是私有的,不能被这个派生类的子类访问。

    在共有继承时,派生类的对象可以访问基类中的共有成员;派生类的成员函数可以访问基类中的共有成员和私有成员。这里,一定要区分清楚派生类的对象和派生类的成员函数对基类的访问是不同的。

私有继承:

私有继承的特点是基类的私有成员和保护成员都作为派生类的私有成员,并且不能被这个派生类的子类所访问。私有继承时,基类的成员只能由派生类直接访问,而无法再往下继承。

保护继承:

保护继承的特点是基类的所有共有成员和保护成员都成为派生类的保护成员,并且只能被派生类成员函数或者友元访问,基类的私有成员仍然是私有的。

三种继承方式总结:

内部访问中,三种继承方式子类都可以访问基类的共有成员和保护成员,不可以访问私有成员。

外部访问中,共有继承子类对象可访问基类共有成员,而私有继承和保护继承中子类对象对基类的任何成员都不能访问。

 

2.继承时导致的二义性

类间的转换:

C++自定义类型的转换规则是:

1)在共有继承方式(私有、保护继承时,不能隐式转换)下,派生类的对象/对象指针/对象引用可以赋值给基类的对象/对象指针/对象引用(发生隐式转换),反之不成立。因为派生类包含了基类的所有信息,而基类缺乏派生类的信息。

2)C++ 容许把基类的 对象指针/引用 强制转换(显式)成派生类的对象指针/引用。

3)一个指向基类的指针可以用来指向该基类的共有派生类的任何对象,这是C++实现程序运行时多态性的关键。

多基继承:

    当继承基类时,在派生类中就获得了基类所有数据成员的副本,称该副本为子对象。假若对类d1和类2进行多重继承而形成类mi,则类mi会包含d1的子对象和d2的子对象。

一般来说,在派生类中对积累成员的访问应当具有唯一性,但在多基继承时,如果多个基中存在同名成员的情况,造成编译器无从判断具体要访问哪个积累中的成员,则称为对基类成员访问的二义性问题。

若两个基类中具有同名的数据成员或者成员函数,应使用成员名限定来消除二义性,如:

Void disp()

{

   A::print(); //加成员名限定A::

}

但更好的办法是在类C中也定义一个同名print()函数,根据需要调用A::print()还是B::print(),从而实现对基类同名函数的隐藏。

菱形继承:

从继承图上看,有时该继承的结构为“菱形”,没有菱形的情况下,多重继承很简单,但菱形出现后,由于新类中存在重叠的子对象,增加了存储空间,同时又引入了二义性问题。

class A

{

public:

void print();

};

class B:public A{};

class C:public A{};

class D:public B, public C {}

void main()

{

D d;

A * pa = (A*)&d; //上行转换产生二义性

d.print();//二义性,系统不知调Bprint还是Cprint();

}//把子类的指针或引用转换成基类的指针或引用是上行转换,把基类的指针或引用转换成子类的指针或引用是下行转换。

1

D.print();编译错误,可改为d.B::print();d.C::print();若改为d.A::print();编译器会报“基类A不明确”。

2)A*pa = (A*)&d;产生二义性是由于d中含有两个基类对象A,隐式转换时不知道让pa指向哪个对象,从而出错,可改为:

 A* pa = (A*)(B*)&d;  //上行转换 或 A*pa = (A*)(C*)&d; //下行转换

 

2.转换构造函数

可以用单个实参来调用的构造函数 定义从形参类型到该类类型的一个隐式转换。

class Integral

{

public:

Integral (int = 0); //转换构造函数

private:

int real;

};

Integral integ = 1; //调用转换构造函数将1转换为Integral类的对象

转换构造函数需要满足以下条件之一:

1)Integral类的定义和实现中给出了仅包含只有一个int类型参数的构造函数。

2)Integral类的定义和实现中给出了包含一个int类型的参数,且其它参数都有缺省值的构造函数;

3)Integral类的定义和实现中虽不含int型参数,但包含一个非int类型如float类型,此外没有其它参数或者其它参数没有缺省值,且int类型参数可隐式转换为float类型参数。

 可以通过将构造函数声明为explicit来禁止隐式转换。

 

3.类型转换函数

通过类型转换构造函数可以将一个指定类型的数据转化为类的对象。但是不能反过来将一个类的对象转换为其它类型的数据。为此C++提供了一个称为类型转换函数的函数来解决这个转换问题。类型转换函数的作用是将一个类的对象转换成另一类型的数据。

class Integral

{

public:

Integral (int = 0); //转换构造函数

operator int(); //类型转换函数,函数名为operator int,希望转换成的目标类型为int

private:

int real;

}

 

Integral inte

g = 1;  //调用转化构造函数将1转换为Integral类的对象

int i = integ; //调用类型转换函数将integ转换成为int类型

 

定义类型转换函数,需要注意以下几点:

1)转换函数必须是成员函数,不能是友元形式;

2)转换函数不能指定返回值类型,但是函数体内必须用return语句以传值方式返回一个目标类型的变量;

3)转换函数不能有参数。

C++内建类别AB,在以下几种类型下B能隐式转化为A

1)B共有继承自A,可以是间接继承的。

2)中有类型转换函数

3)A实现了非explicit的 参数为B的构造函数

 

虚函数多态

多态是指同一操作作用于不同对象会产生不同的响应,分为静态多态和动态多态,其中函数重载和运算符重载属于静态多态性,虚函数属于动态多态性。C++是依靠虚函数来实现动态多态的。

1.静态联编与动态联编

C++编译器根据传递给函数的参数和函数名决定具体要使用哪一个函数,称为联编或绑定。

编译器可以在编译过程中完成这种联编,称为静态联编或早期联编。

编译器在某些场合下必须在程序运行时完成这种联编,称为称为动态联编或者晚期联编。

C++ 通过虚函数实现动态联编。只要在成员函数前加关键字virtual即可使其成为虚函数。

如果一个基类的成员函数被定义为虚函数,则他在所有派生类中也保持为虚函数。(即使在派生类中省略了virtual关键字)

派生类中可根据需要对虚函数进行重定义,其格式要求如下:

1)与基类的虚函数有相同的参数个数;

2)与基类的虚函数有相同的参数类型;

3)与基类的虚函数有相同的返回类型;

虚函数的访问:

虚函数可以通过函数名来调用,此时编译器采用静态联编。通过对象名访问虚函数时,调用哪个类的函数取决于定义对象名的类型。对象名是基类时,就调用基类的函数,对象名是子类时,就调用子类的函数。

使用指针访问非虚函数时,编译器根据指针本身的类型决定要调用哪个函数。

使用指针访问虚函数时,编译器根据指针所指的对象的类型决定要访问哪个函数(动态联编)。

总结,C++中的函数默认不使用动态绑定,要触发动态绑定,需要满足两个条件:

第一,只有指定为虚函数的成员才能进行动态绑定,成员函数默认为非虚函数,非虚函数不进行动态绑定。

第二,必须通过基类类型的引用或指针进行函数调用。

常见的不能声明为虚函数的有:普通函数(非成员函数)、静态成员函数、构造函数、友元函数,而内联成员函数、赋值操作符重载函数即使声明为虚函数也没有意义。

 

2.虚函数表指针(vptr)及虚基类表指针(bptr

C++在布局以及存取时间上主要的额外负担是由virtual引起的,包括:

Virtual funct机制:用以执行一个有效率的“执行期绑定”;

Virtual base class:用以实现多次出现在继承体系中的基类,有一个单一而被共享的实体。

 虚函数表指针

 含静态变量,虚函数类的空间计算

 虚函数表的实现

 虚基类表指针

3.虚拟继承时构造函数的书写

4.纯虚函数

 

动态运行时类型识别与显式转换

 

5.Typeid

    通过运行时的类型识别,程序能够使用基类的指针或引用来检索这些指针或引用所指对象的实际类型。

C++ 通过下面两个操作符提供类型识别:

1)typeid操作符,返回指针或引用所指对象的实际类型。

2)Dynamic_cast 操作符,将基类类型的指针或引用安全地转换为派生类型的指针或引用。

 

Typeid的用法为: typeid(e),这里e是任意表达式或类型名。Typeid用于比较测试两个类型是否相同,若相同,则测试成功,若不同,则测试失败。

 

只有当typeid的操作数是带虚函数的类类型的对象时,才返回动态类型信息。

测试指针(相对于指针指向的对象)返回的是静态的,编译时的类型。

 

6.显式转换

C++中,显式转换也称为强制类型转换(cast),包括以下列名字命名的强制类型转换操作符:

Static_cast, dynamic_cast,const_cast reinterpret_cast

命名的强制类型转换符号的一般形式如下:

Cast_name<type>(expression);

其中,cast_name为四种强制类型转换操作符之一,type为转换的目标类型,而expression则是被强制转换的表达式。

 

Reinterpret_cast

在引入命名的强制类型转换符之前,显式强制转换用圆括号将类型括起来实现:

Int * ip;

Char * pc = (char*) ip;

效果与使用reinterpret_cast效果相同。

Int *p;

Char *pc = reiterpret_cast<char *>(ip)

 

Const_cast

Const_cast,顾名思义,将转换掉表达式的const性质。

Const char *pc_str;

Char *pc = const_cast<char*>(pc_str);

只有使用const_cast 才能将const性质彻底转换掉。在这种情况下,试图使用其他三种强制类型转换都会导致编译时的错误。类似地,处理添加或者删除const特性,用const_cast符来执行其他任何类型的转换,都会引起编译错误。

编译器执行的任何类型转换都可以由static_cast显式完成:

Double d = 97.0;

Int i = static_cast<int>(d);

等价于

Double d = 97.0;

Int i = d;

仅当类型之间可以隐式转换时(除类层次间的下行转换外)static_cast转换才是合法的,否则将出错。

类层次间的下行转换是不能通过隐式转换完成的。由于没有动态类型检查,所以是不安全的。

Dynamic_cast 主要用于类层次间上行和下行转换。

在类层次间进行上行和下行转换时,dynamic_caststatic_cast效果是一样的;

在进行下行转换时,dynamic_cast具有类型检查的功能,比static_cast更安全。

0 0