<C++基础学习二十五>类的其他特性(未完待续)

摘要: 本篇博客仅作为笔记,如有侵权,请联系,立即删除(网上找博客学习,然后手记笔记,因纸质笔记不便保存,所以保存到网络笔记)。

  本文主要介绍类的友元函数、虚函数、静态成员、const对象和volatile对象以及指向类成员的指针。

  一、友元函数

  当把类中的成员的访问权限定义为私有的或者保护的时,在类的外面,只能通过该类的成员函数来访问这些成员,这是由类的封装性所确定的。这种用法往往觉得不够方便,若把类的成员的访问定义为公有的访问权限时,又损坏了面向对象的封装性。为此,在C++中提供了友元函数,允许在类外访问类中的任何成员(私有的、保护的或公有的成员)。

  友元函数的说明及使用

  在定义一个类时,若在类中用friend修饰函数,则该函数就是该类的友元函数,它可以访问该类中的所有成员。说明一个友元函数的一般格式为:

friend 返回值类型 函数名称(形参列表);

  例如:

#include <iostream>

using namespace std;

class A
{
public:
    A(float a,float b){
        r = a;
        h = b;
    }
    float Getr(){return r;}
    float Geth(){return h;}
    friend float Volum(A &);  //原型说明
private:
    float r;
    float h;
};

float Volum(A &a){
    return PI * a.r * a.r * a.h;  //定义函数并非像成员函数使用作用域运算符“::”
}

int main(){
    A a1(25,40);
    cout << Volum(a1) << endl; //直接调用,而非a1.Volum(a1)
    cout << PI * a1.Getr() * a1.Getr() * a1.Geth() << endl;

    system("pause");
    return 0;
}

  本例程中,将Volum()函数定义为类A的友元函数,先是给出了原型说明。

  关于友元函数的使用,必须说明以下几点:

  1、友元函数不是类的成员函数;

  2、由于友元函数并不是类的成员函数,它不带有this指针,因此在友元函数体重不能直接使用类的成员,而是要将对象名或者对象的引用作为友元函数的参数,再使用运算符“.”来访问对象的成员。同时,在调用友元函数的时候,不需要类的对象来调用,而是可以和一般函数一样直接调用;

  3、友元函数与一般函数的不同点:友元函数必须在类的定义中说明,其函数体可在类内定义,也可以在类外定义;它可以访问该类中的所有成员(公有、私有、保护),而一般函数只能访问类中的公有成员。

  4、在类中对友元函数指定访问权限无效。正因为友元函数不是对应类的成员函数,所以它不受类中访问权限关键字的限定,可以把它放在类的任何一个位置;

  5、友元函数的作用域与一般函数作用域相同,一般具有文件作用域;

  6、由于友元函数破坏了类的封装性,所以谨慎使用友元函数。

总而言之,友元函数不是类的成员函数,它更类似于一般的函数,只不过它必须在类中进行说明,且必须用对象名或引用作为形参,且它能够访问类中的所有成员。

  -->成员函数用作友元

  一个类可以定义若干个友元函数,可以将一个类的任一个成员说明为另一个类的友元函数,以便通过该成员函数访问另一个类的成员,亦可以将一个类中的所有成员函数都说明为另一个类的友元函数。

  要将类C的一个成员函数(包括析构函数和构造函数)说明成类D的友元函数时,其一般格式如下:

class D;     //A reference to class D is given 
             //because class D is defined after class C, and class D is used in class C
class C{
    ...
public:
    void fun(D &); //Membership functions of class C
};

class D{
    ...
    friend void C::fun(D &);  //A member function of class C as a friend function of class D
}

void C::fun(D &d){     //Description of member functions of class C
    ...
}

  这段程序将类C的成员函数作为了类D的友元函数。在B行只能给出函数的原型说明,不能给出函数体,因为类D还没定义。能够用作友元函数的参数可以是类D的引用、类D的对象或者指向类D的指针。例如:

#include <iostream>
using namespace std;

class B;       //Referential Description of Class B

class A{
private:
    float x,y;
public:
    A(float a,float b){
        x = a;
        y = b;
    }
    float GetX() {return x;}
    float GetY() {return y;}
    void Setxy(B &);    //Class A member function, class B reference as a parameter
};

class B{
private:
    float c,d;
public:
    B(float a,float b){
        c = a;
        d = b;
    }
    float Getc(){return c;}
    float Getd(){return d;}
    friend void A::Setxy(B &);   //Friend function of class B
}

void A::Setxy(B &b){            //Definition of Friend Function
    x = b.c;
    y = b.d;
}

int main(){
    A a1(25,40);
    B b1(55,60);
    cout << a1.GetX() << ' ' << a1.GetY() << endl;
    a1.Setxy(b1);
    cout << a1.GetX() << ' ' << a1.GetY() << endl;

    system("pause");
    return 0;
}

  若要将一个类M中的所有成员函数都说明成另一个类N的友元时,则不必在类N中一一列出M类的成员函数为友元,可以简化为:

class N{
    ...
    friend class M;    //Class M is a friend of class N
};

class M{
    ...
};

  在类M中的所有成员函数可以使用类N中的全部成员,成类M为类N的友元。

注意:友元关系不是传递的。例如:类A是类B的友元,类B是类C的友元时,类A并不一定是类C的友元;这种友元关系也不具有交换性。例如:类A是类B的友元时,类B不一定是类A的友元。同样的,友元关系时不继承的。这是因为友元函数不是类的成员函数,当然不存在继承关系。

  二、虚函数

  多态性是实现OPP的关键技术之一。它常用虚函数或重载技术来实现。利用多态性实现技术,可以调用同一个函数名的函数,但实现完全不同的功能。

  在C++中,将多态性分为两种:编译时的多态性和运行时的多态性。编译时的多态性是通过函数的重载或运算符的重载来实现的;运行时的多态性是通过类的继承关系和虚函数来实现的。

  1、函数的重载:根据函数调用时,给出不同类型的实参或不同的实参个数,在程序执行前就可以确定应该调用哪一个函数;

  2、运算符的重载:根据不同的运算对象在编译时就可确定执行哪一种运算;

  3、运行时的多态性:在程序执行之前,根据函数名和参数无法确定应该调用哪一个函数,必须在程序的执行过程中,根据具体的执行情况来动态地确定。

  虚函数的定义和使用:

  为实现某一种功能而假设的虚拟函数称为虚函数,虚函数只能是一个类中的成员函数,并且不能是静态的成员函数。定义一个虚函数的一般格式为:

virtual 返回值 函数名(形参列表);

  一旦把某一个类的成员函数定义为虚函数,由该类所派生出来的所有派生类中,该函数均保持虚函数的特性。当在派生类中定义了一个与该虚函数同名的成员函数,并且改成原函数的参数个数、参数类型以及函数的返回值类型都与基类中的同名虚函数一样,则无论是否使用virtual修饰该成员函数,它都成为一个虚函数。

  也就是说,在派生类中重新定义基类的虚函数时,可以不使用关键字virtual来修饰。

  例如:

#include <iostream>

using namespace std;

class A{
private:
    int x;
public:
    A(){
        x = 100;
    }
    virtual void print(){
        cout << x << endl;
    }
};

class B:public A{
private:
    int y;
public:
    B(){
        y = 200;
    }
    void print(){
        cout << y << endl;
    }
};

class C:public A{
private:
    int z;
public:
    C(){
        z = 300;
    }
    void printf(){
        cout << z << endl;
    }
};

int main(){
    A a,*p;
    B b;
    C c;
    a.print();             //100
    b.print();             //200
    c.print();             //300
    p = &a;                
    p->print();            //100
    p = &b;                
    p->print();            //200
    p = &c;
    p->print();            //300

    system("pause");
    return 0;
}

   前三个输出都是很明显的,通过调用三个不同对象的成员函数,分别输出各自的值。因在编译时,根据对象名就可以确定要调用哪一个成员函数,这是编译时的多态性。

  而后三个的输出是将三个不同类型的对象起始地址赋给基类的指针变量,这在C++中是允许的,即可以将由基类所派生出来的派生类对象的地址给基类类型的指针变量。当基类指针指向不同的对象时,尽管调用的形式完全不同,但却是调用不同对象中的虚函数。因此输出了不同的结果,这就是运行时的多态。

  为了体会一下虚函数的用法,将上例中的virtual去掉,看一下程序:

 

#include <iostream>

using namespace std;

class A{
private:
    int x;
public:
    A(){
        x = 100;
    }
    void print(){
        cout << x << endl;
    }
};

class B:public A{
private:
    int y;
public:
    B(){
        y = 200;
    }
    void print(){
        cout << y << endl;
    }
};

class C:public A{
private:
    int z;
public:
    C(){
        z = 300;
    }
    void printf(){
        cout << z << endl;
    }
};

int main(){
    A a,*p;
    B b;
    C c;
    a.print();             //100
    b.print();             //200
    c.print();             //300
    p = &a;                
    p->print();            //100
    p = &b;                
    p->print();            //100
    p = &c;
    p->print();            //100

    system("pause");
    return 0;
}

   virtual删除前后比较一下,可以看出一些端倪:

  · 无虚函数时,遵循以下规则:C++规定,定义为基类的指针,也能作指向派生类的指针使用,并可以用这个指向派生类对象的指针访问继承来的基类成员;但不能用它访问派生类的成员。

  · 而使用虚函数实现运行时的多态性的关键在于:必须通过基类指针访问这些函数。也就是说,一旦定义为虚基类,只要定义一个基类的指针,就可以指向派生类的对象。

  关于虚函数,必须说明以下几点:

  · 当在基类中把成员函数定义为虚函数后,在其派生类中定义的虚函数必须与基类中的虚函数同名,参数的类型、顺序和参数的个数必须一一对应,函数的返回的类型也相同;

  · 实现这种动态的多态性时,必须使用基类类型的指针变量(引用对象也可以),使该指针指向不同派生类的对象,并通过调用指针所指向的虚函数才能实现动态的多态性;

  · 虚函数必须是类的一个成员函数,不能使友元函数,也不能是静态的成员函数;

  · 在派生类中没有重新定义虚函数时,与一般的成员函数一样,当调用这种派生类对象的虚函数时,则调用其基类中的虚函数;

  · 可把析构函数定义为虚函数,但是不能将构造函数定义为虚函数。通常在释放基类中和其派生类中的动态申请的存储空间时,也要把析构函数定义为虚函数,以便完成撤销对象时的多态性;

  · 虚函数与一般的成员函数相比较,调用时的执行速度要慢一些。因为为了实现多态性,在每一个派生类中均要保存相应虚函数的入口地址表,函数的调用机制也是间接实现的。

  总结起来虚函数的作用就是:

  派生类的指针可以赋给基类指针,而通过基类指针调用基类和派生类中的同名虚函数时:

  · 若该指针指向一个基类的对象,那么被调用是基类的虚函数;

  · 若该指针指向一个派生类的对象,那么被调用的是派生类的虚函数。

  这种机制叫做多态。

  类中的虚函数是动态生成的,由虚函数表的指向进行访问,不为类的对象分配内存,就没有虚函数表就无法访问。类中的普通函数静态生成,不为类的对象分配内存也可访问。

  下面看一个例子:

#include <iostream>

using namespace std;

class A{
public:
    virtual void fun1(){
        cout << "A::fun1" << ' ';
        fun2();
    }
    void fun2(){
        cout << "A::fun2" << ' ';
        fun3();
    }
    void fun3(){
        cout << "A::fun3" << ' ';
        fun4();
    }
    virtual void fun4(){
        cout << "A::fun4" << ' ';
        fun5();
    }
    void fun5(){
        cout << "A::fun5" << endl;
    }
};

class B:public A{
public:
    void fun3(){
        cout << "B::fun3" << ' ';
        fun4();
    }
    void fun4(){
        cout << "B::fun4" << ' ';
        fun5();
    }
    void fun5(){
        cout << "B::fun5" << endl;
    }
};

int main(){
    B b;
    b.fun1();  //A::fun1 A::fun2 A::fun3 B::fun4 B::fun5

    system("pause");
    return 0;
}

  这一题的主要知识是:C++的所有成员函数在被调用时都会得到this指针,然后通过this指针去调用供需函数就是常规的查虚函数表跳转。

  再看一个例子:

  

posted @ 2019-01-22 15:09  daisy_ai  阅读(137)  评论(0)    收藏  举报