VC杂记

 

多态性的类型

程序设计的多态性有两种形式:编译时的多态性和运行时的多态性。

多态性的实现与联编有关。将一个函数的调用与其相应的函数体代码相链接的过程,称为函数联编。C++中有两种类型的联编:静态联编和动态联编。

编译时的多态性是通过静态联编来实现的。静态联编是指在调用同名函数(即重载函数)时,编译器将根据调用时所使用的实际参数个数、类型的不同确定应该调用哪一个函数的实现,它是在程序编译阶段就确定下来的多态性。静态联编通过使用重载(overload)机制来获得,重载机制包括函数重载和运算符重载。

运行时的多态性是通过动态联编来实现的。动态联编是在程序的运行阶段根据当时的情况,确定应该调用哪个同名函数的实现。动态联编主要通过继承与虚函数两者结合来实现的。

静态联编与动态联编各有优缺点。静态联编的代码链接工作是在编译时完成的,所以运行时不需要额外时间来做这些工作,因此静态联编的代码效率较高;而动态联编将函数调用的链接时间后移到代码执行的时候,这必然使函数调用时间增加,效率降低,但大大增强了语言的灵活性。

多态的前提条件

         必须存在一个继承体系结构。

         继承体系结构中的一些类必须具有同名的virtual成员函数(virtual是关键字)。

         至少有一个基类类型的指针或基类类型的引用,这个指针或引用可用来对virtual成员函数进行调用。

注意:

基类类型的指针可以指向任何基类对象或派生类对象,反过来派生类类型的指针是不可以的基类的对象的。

函数重载

如果两个以上的函数取相同的函数名称,但是形参个数或者类型不同时,编译器会根据实参和形参的类型和个数选择最匹配的函数,自动确定调用哪个函数,这就是函数的重载。

注意 函数重栽时,函数声明必须能够互相区别,即函数的参数个数或参数类型必须有所不同,如果只有函数的返回值不同,则不能区分重栽函数;函数功能表面相类似,本质上有区别的函数,最好不要重载;不同参数传递方式无法区别重载函数。例如,int add(int a, int b)int add(int &a, int &b)这两个函数不能作为重载函数,否则编译时会出错。

操作符重载

下列操作符不能被重载:

         成员选择符(.)、成员对象选择符(.*)、域解析操作符(::)和条件操作符(?:

赋值操作符(=)不能被派生类所继承。

         下标操作符[]、赋值操作符=、函数调用操作符()和指针操作符->必须以类的成员函数的形式进行重载。

赋值操作符的重载:

         拷贝构造函数和赋值操作符都是用来拷贝一个类的对象给另一个同类型的对象。拷贝构造函数将一个对象拷贝到另一个新的对象;赋值操作符将一个对象拷贝到另一个已经存在的对象。如果类的设计者没有提供拷贝构造函数,也没有重载赋值操作符,编译器将会为这个类提供一个拷贝构造函数和一个赋值操作符。需要注意的问题就是和拷贝构造函数同样的问题,编译器提供的运作机制的特点以及引起的问题。

重载不能改变操作符的优先级和语法。

         如果一个内建操作符是一元的,那么所有对它的重载仍是一元的。如果一个内建操作符是二元的,那么所有对它的重载仍是二元的。

虚函数

基类的在派生类中仍然是虚函数,无论其前面是否有关键字virtual,编译器都将视它们为虚函数。虚函数和普通成员函数一样,在派生类中虚成员函数也可以从基类继承。构造函数不能是虚成员函数,但析构函数可以是虚成员函数,而且一般都把一个类的析构函数设计成虚成员函数。只有非静态成员函数才可以是虚函数;只有对象成员函数才可以是虚函数。

注意:

         基类中被声明了为虚函数的成员函数,在被继承以后的派生类当中即使不被显式的声明为虚函数,它也自动地被认为是虚函数。

         虚函数在类声明之外定义,关键字virtual仅在函数声明时需要,不需在函数定义中使用virtual关键字。

         C++仅允许将成员函数定义为虚函数,顶层函数不能为虚函数

在基类中只声明虚函数而不给出具体的定义,将它的具体定义放在各派生类中,这种虚函数称为纯虚函数(pure virtual function)。通过该基类指针或引用就可以调用所有派生类的虚函数,基类只是用于继承,仅作为一个接口,具体功能在派生类中实现。声明了纯虚函数的类,称为抽象类(abstract class)

纯虚函数的声明形式如下:

 

virtual  函数类型 函数原型(参数表)=0

将虚函数的原型设置为0,该虚函数即为纯虚函数。

抽象类的定义

只能用于别的类的基类,而本身不能直接创建对象的类称为抽象基类。带有纯虚函数的基类是抽象类。它的主要作用是通过抽象类为一个类族建立一个公共的接口,使其能够更有效地发挥多态性。抽象类声明了一族派生类共同操作的通用语义,而接口的完整实现——纯虚函数的实现体,要由派生类自己给出。

纯虚函数的例子:

#include <iostream.h> 

class shape

{

protected:

double x,y;

public:

void set (double I, double j) {x=I; y=j;}

virtual void area()=0;//声明纯虚函数

}; //抽象类shape定义结束

class triangle: public shape

{

public:

void area()

{

cout <<"Triangle S=1/2*"<<x<<"*"<<y<<"="<<0.5*x*y<<""n";

} // 派生类重新定义虚函数

};

class rectangle: public shape

{

public:

void area()

{

cout <<"Rectangle S=" <<x<<"*"<<y<<"="<<x*y<<""n";

}//派生类重新定义虚函数

}; //派生类rectangle定义结束

void main()

{

shape *p;

triangle t;

rectangle r;

p=&t;

p->set(5.1,10);

p->area();

p=&r;

p->set(5.1,10);

p->area();

}

采用虚函数的优点有:

可以使程序简单易懂;

使程序模块间的独立性加强,增加了程序的易维护性;

提高了程序中“信息隐藏”的等级。类的封装本身是私有成员的隐藏,而在基类和派生类之间虚函数的设置,实际上是以虚函数作为对外接口,它隐藏的是在各个派生类中虚函数内容各异的不同实现;

提高了“软件重用”的等级。虚函数是一类簇对外提供的接口,它实质上是一种接口重用。

通过基类指针来访问

一般情况下,指向一种类型对象的指针不允许指向另一种类型的对象。然而在具有层次关系的类结构中,指向基类对象的指针可以指向该基类的公有派生类对象(反向则不成立)。通过调用基类指针所指向的虚函数(指针指向哪一个派生类就调用哪一个派生类的虚函数),最终实现运行时的多态性。

纯虚函数的定义

在基类中只声明虚函数而不给出具体的定义,将它的具体定义放在各派生类中,这种虚函数称为纯虚函数(pure virtual function)。通过该基类指针或引用就可以调用所有派生类的虚函数,基类只是用于继承,仅作为一个接口,具体功能在派生类中实现。声明了纯虚函数的类,称为抽象类(abstract class)

纯虚函数的声明形式如下:

virtual  函数类型 函数原型(参数表)=0

将虚函数的原型设置为0,该虚函数即为纯虚函数。

继承

类型兼容规则:

一个公有派生类的对象在使用上可以被当作基类的对象,反之则禁止。具体表现在:

派生类的对象可以被赋值给基类对象。

派生类的对象可以初始化基类的引用。

指向基类的指针也可以指向派生类。

通过基类对象名、指针只能使用从基类继承的成员。

继承机制下的构造函数:

    当创建一个派生类对象时,基类的构造函数被自动调用,用来对派生类对象中的基类部分进行初始化,并完成其他一些相关事务。如果派生类定义了自己的构造函数,则由该构造函数负责对象中派生类添加部分的初始化工作。

继承机制下的析构函数:

在类的层次结构当中,构造函数按基类到派生类的次序执行,析构函数则按照派生类到基类的次序执行,因此,析构函数的执行次序和构造函数的执行次序是相反的。

基类成员的访问控制:

3.1 继承成员访问控制规则

继承访问控制

基类成员访问控制

在派生类中的访问控制

public

public

public

protected

protected

private

不可访问

protected

public

protected

protected

protected

private

不可访问

private

public

private

protected

private

private

不可访问

静态数据成员及成员函数

class Task

{

public:

       // …

private:

       static int sm_nCount;

       // …

};

int Task:: sm_nCount = 1000;

   特点:

    1. 属于一个类所共有;

2. static数据成员在类声明的内部声明,它必须在任何程序块之外被定义;

3. static数据成员不会影响该类及其对象的sizeof

class Task

{

public:

       static int getCount() const { return sm_nCount; }

       // …

private:

       static int sm_nCount;

       // …

};

         静态成员函数只能访问其他的static成员,包括数据成员和成员函数,不能访问非static数据成员和非static成员函数。

内联函数声明与使用

    声明时使用关键字 inline

    编译时在调用处用函数体进行替换,节省了参数传递、控制转移等开销。

    注意:

– 内联函数体内不能有循环语句和switch语句。

– 内联函数的声明必须出现在内联函数第一次被调用之前。

– 对内联函数不能进行异常接口声明

拷贝构造函数

拷贝构造函数是一种特殊的构造函数,其形参为本类的对象引用。

class 类名

public :

       类名(形参); //构造函数

       类名(类名 &对象名);//拷贝构造函数

           ...

}

类名:: 类名(类名 &对象名)//拷贝构造函数的实现

{   

函数体   

}

当用类的一个对象去初始化该类的另一个对象时系统自动调用拷贝构造函数实现拷贝赋值。

int main()

   Point A(1,2);

   Point B(A);      //拷贝构造函数被调用

   cout << B.GetX() << endl;

}

若函数的形参为类对象,调用函数时,实参赋值给形参,系统自动调用拷贝构造函数。例如:

void fun1(Point p)

{  

    cout<<p.GetX()<<endl;

}

int main()

{  

    Point A(1,2);

    fun1(A);        //调用拷贝构造函数

}     

当函数的返回值是类对象时,系统自动调用拷贝构造函数。例如:

Point fun2()

{   

     Point A(1,2);

     return A;       //调用拷贝构造函数

}

int main()

{

     Point B;

     B = fun2();

}

拷贝构造函数小结

         特点:

        1. 拷贝构造函数创建一个新的对象,它是一个已有对象的拷贝;

                   2. 拷贝构造函数可以有多个参数;

                   3. 如果类的设计者没有提供拷贝构造函数,编译器会自动生成一个。

何时需要定义自己的拷贝构造函数:通常,如果一个类包含指向动态存储空间指针类型的数据成员,则就应为这个类设计拷贝构造函数。
posted @ 2007-12-26 21:11  WolfのPL  阅读(393)  评论(0编辑  收藏  举报