继承 重载 封装 多态 静态成员变量

时间:2022-09-14 21:54:02

 独一无二----静态成员变量

我们学习C++的时候知道静态变量的特性,他不是临时变量,在编译期间就已经产成。用一个例子就能说明问题。
#include <iostream>
using namespace std;
class A{
public:
        A(){ cout << "Can you see me Now!" << endl; }
        ~A(){ cout << "I'm Go Away!" << endl; }
};
void TestStatic( void )
{
        static A a;
}
void main()
{
        cout << "Program Start!" << endl;
        TestStatic();
        TestStatic();
        TestStatic();
        cout << "Program End!" << endl;
}
结果是:
Program Start!
Can you see me Now!
Program End!
I'm Go Away!
A a只被定义了一次,而且析构是在主程序退出以后才进行的。这说明了什么呢,A a在void TestStatic( void )是同一个实例,他存在于整个程序但是只有在void TestStatic( void )中能访问他。
不相信?那我们来试试看。
#include <iostream>
using namespace std;
class A{
private:
        int count;
public:
        A():count(0){ cout << "Can you see me Now!" << endl; }
        ~A(){ cout << "I'm Go Away!" << endl; }
        void Inc(){ count++; }
        int Count(){ return count; }
};
void TestStatic( void )
{
        static A a;
        a.Inc();
        cout << "count's value is : " << a.Count() << endl;
}
void main()
{
        cout << "Program Start!" << endl;
        TestStatic();
        TestStatic();
        TestStatic();
        cout << "Program End!" << endl;
}
结果是:
Program Start!
Can you see me Now!
count's value is : 1   //初始化count为0,Inc导致count自加值应该为1
count's value is : 2   //没有初始化,Inc导致count自加值应该为2
count's value is : 3   //没有初始化,Inc导致count自加值应该为3
Program End!
I'm Go Away!
事实说明了一切,那么他是如何实现的呢,C++编译器里,他被创建在一个内存区域里,这块区域不是堆也不是栈,编译器在编译阶段就将他们记住,并为他们做好分配工作,如此一来就可以实现这个特性。
看起来他的作用有些象全局变量,但是我们知道,使用全局变量会整加模块的耦合性,降低代码的通用性,所以静态成员变量的出现为我们编程带来的灵活性。
如何将这个小东西用在我们的面向对象编程中呢。他扮演一个什么样的角色呢,这正是我要说的。
我们知道,类的成员变量表示了一个类的属性,对应着对象的物质特性,他们在类的某个实例创建的时候创建,消亡的时候消亡。但是上面说到,静态变量在编译期间就已经存在了,也就是并不随着实例创建的时候创建,消亡的时候消亡。是这样吗。看事实说话。
#include <iostream>
using namespace std;
class A{
public:
        A() { cout << "A is On!" << endl; }
        ~A() { cout << "A is OFF!" << endl; }
};
class B{
public:
        B() { cout << "B is On!" << endl; }
        ~B() { cout << "B is OFF!" << endl; }
private:
        static A a;
};
A B::a;
void main()
{
        cout << "Program Start!" << endl;
        B b1,b2,b3;
        cout << "Program End!" << endl;
}
结果是:
A is On! //瞧我又说中了,主程序还没有运行,构造函数就开始工作了,这时B的实例还没有登场
Program Start!
B is On! //b1创建了,但是b1.a并没有创建
B is On!
B is On!
Program End!
B is OFF! //B的实例销毁了,但是成员变量a没有销毁
B is OFF!
B is OFF!
A is OFF!  //看吧,这才是A的析构函数
注意一个约定:
A B::a;
静态成员变量的初始化一定要在主函数外面,而且静态成员变量一定要初始化。
事实上B::a并不属于B,将他作为B的成员只是为了确定访问的权限
private:
        static A a;
因为这样设定以后只有B才能访问B::a,当然设计的时候要考虑清楚,如果a与B并没有关系,那么这样的设计就没有什么实际的意义。
那么如何设计才能运用这个特性呢。
我们来举个例子。
我们有时候想知道类的实例有几个,也就是被实例化了几次。比如
class A;
A a1,a2,a3;
那么就应该被实例化了3次。能知道这个信息应该很不错。如果用一个全局变量,那么要考虑的问题很多,全局变量会被人任意修改,全局变量定义在那里以及全局变量初始化在那里也会带来困惑,全局变量会不会跟别的全局变量重名等等。
既然静态变量可以实现一些全局变量的功能,何不牛刀小试,看看效果如何。首先静态成员变量只有一个,而且不是类的真实属性,实际上只有一个变量,不会增加类的负担;第二可以给他加*问限制,只要不是public那么就不能随意修改。既然好处多多,那么就开工。
#include <iostream>
using namespace std;
class A{
public:
       A(){ count++; }; //当产生一个实例的时候计数器加一
    ~A(){ count--; } //当销毁一个实例的时候计数器减一
       int GetInstanceCount(){ return count; }
private:
       static int count;
};
int A::count = 0;
void main()
{
       cout << "Program Start! " << endl << endl;
       A a1,a2,a3;
       {
              A a4,a5,*pa;
              cout << "Now, Have a1 ,a2 ,a3 , a4 ,a5 Instances!" << endl;
              cout << "Number of class A's Instance is : " << a1.GetInstanceCount() << endl << endl;
              pa = new A;
              cout << "Now Creat a class A's Instance!" << endl;
              cout << "Number of class A's Instance is : " << a2.GetInstanceCount() << endl << endl;
              delete pa;
       }
       cout << "While class's Instances a4 , a5 , pa destroy!" << endl;
       cout << "Only a1 , a2 , a3 Left , is the Count of Instance is 3 ?" << endl;
       cout << "Number of class A's Instance is : " << a3.GetInstanceCount() << endl << endl ;
}
结果是:
Program Start!
Now, Have a1 ,a2 ,a3 , a4 ,a5 Instances! //有a1 ,a2 ,a3 ,a4 ,a5五个实例
Number of class A's Instance is : 5       //没错,正是五个
Now Creat a class A's Instance!        //在堆里创建了一个,使用pa得到他的引用
Number of class A's Instance is : 6      //跟想的一样,数目增加了
While class's Instances a4 , a5 , pa destroy! //在堆里释放一个,栈里释放2个
Only a1 , a2 , a3 Left , is the Count of Instance is 3 ? //6 – 1 –2 当然等于3啦
Number of class A's Instance is : 3 我说的没错吧。
因为在构造函数里操作的是同一个变量,所以才能得到正确的结果。这个技术在很多方面得到应用,比如,互斥信号,象动态连接库一样的引用计数器等等。
这里要记住的是,类的静态成员变量实际上只有一个,它不随着类的实例的创建/销毁而增加/减少。它不是类的真正成员,并不是类的一部分。

龙生九子----多态
一龙生九子,子子皆不同。这个道理说的是世界上没有两个或两个以上的事物是完全相同的,事物之间有相同相似的一面,当然也有不同相异的一面。只有将不同的和相同的都考虑进来,才能完整的表达事物。
事物的属性有着不同的一面。我们知道人感知事物的一个途径是观察,用对不同光线的反应来识别物体,所以我们给出事物的一个属性,颜色。有时候一些物体的颜色是固定的,白色的云,蔚蓝的天空,碧绿的草地,不禁令人神游太虚。
但有些方面是不确定的,有一句话说道:“月有阴晴圆缺,人有悲欢离合”,这句话还不足以说明这个道理。我们中文里有时候会省略一些部分,“今天你吃了没有?”吃的是什么,“早饭”,“下午茶”,“面包”,不知道。这里将吃东西抽象化了,这句话可以在早上说,中午说,晚上说,任何一个适合的时间段都可以说。感兴趣的地方出现了,我们不知道吃什么东西,什么时候吃,但是却可以使用它,简要的表达一下你的问候。这样简单的设下伏笔,在具体需要的环境才用具体表现出来具体的用法,被广泛运用在生活中。“我砍!”表达了一个人要做的动作,具体砍什么,只有感兴趣的人才会去关注。“我生病了。”表达了一个人的状态,具体生的什么病,不是人人都想知道的。
多态--就是指事物不同的方面
多态是一种复杂的应用,要全面的阐述它很难,所以理解它的思想就成了重点。在不同情况下,秉承它的思想,用不同的方法实现多态。
还记得C++中的虚函数吗,我在《后入为主----虚函数》中讲过,虚函数可以动态改变,运用这个特性就能实现我们的目的了。不同的语言使用不同的方法,实现的机制就是一个类型提供的接口可以改变。这样就能体现不同这个思想了。
为了更好理解,我们用C++实现一个例子。
我们知道,要生活,就要工作,工作是人的社会行为,几乎人人都在工作。世界上有形形色色的人,人的工作的区分在什么地方呢,那就是它的职业,不同职业的人做不同的事。
学生的工作就是学习,司机的工作是开车,小贩的工作是卖商品,程序员的工作是编写代码。
现在我们分析一下,世界上有很多人,人都要工作
人 { 工作 = 未确定 }
学生的工作是学习
学生 { 人 , 工作 = 学习 }
司机的工作是开车
司机 { 人 , 工作 = 开车 }
小贩的工作是贩卖
小贩 { 人 , 工作 = 贩卖 }
程序员的工作是编程
程序员 { 人 , 工作 = 编程 }
#include <iostream>
unsing namespace std;
class Man{
public:
    virtual void Work( void ) = 0;//纯虚函数,没有确定的职业就不知道他的工作。
};
class Student : public Man{
public:
    void Work( void ) { cout << "I'm Learning."<< endl; };
};
class Chauffeur : public Man{
public:
    void Work( void ) { cout << "I'm Driving."<< endl; };
};
class Vendor : public Man{
public:
    void Work( void ) { cout << "I'm Vending."<< endl; };
};
class Programer : public Man{
public:
    void Work( void ) { cout << "I'm Coding."<< endl; };
};
void main()
{
    Man * which[5];
    which[0] = new Student;
    which[1] = new Chauffeur;
    which[2] = new Vendor;
    which[3] = new Programer;
        which[4] = NULL;
    for( int i = 0 ; which[i] ; i++ )
       {
        which[i]->Work();
        delete which[i];
       }
}
运行结果
I'm Learning. //虚函数表指针指向的Student的虚函数表
I'm Driving. //虚函数表指针指向的Chauffeur的虚函数表
I'm Vending.  //虚函数表指针指向的Vendor的虚函数表
I'm Coding.  //虚函数表指针指向的Programer的虚函数表
我们看到了由于使用了虚函数,类增加了虚函数表和虚函数表指针
which[0] = new Student;
我们分析这个指针,他指向一个Student对象,而这个指针描述的是一个Man对象,在Student对象被创建的时候虚函数表指针指向了Student的虚函数表,所以which[0]->Work();调用的是在Student的虚函数表里查出来的Student::Work();其它的以此类推,不难得到运行的结果。
不同职业的类用同一个接口实现了不同功能,我们的目的达到了,代码很简洁,意图很明显,这里C++又一次很好的实现了面向对象的思想。

白马非马----继承
古人云:“白马非马”,为何如是说呢。
这里有个讲究,白马是指一种白色的马,而非马里的马指的是各种各样的马,有黑马,棕吗,五花马等等,这是在颜色上有所区别,马还有产地,品种等等区别。这里给了我们一个启示,各种各样的马可以找出很多共同点,称之为马,符合这些特点的动物就是马了,所以这些共同点不能多也不能少,多了不能同用于所有的马,少了表达不出马来。要区别一只马需要很多信息,比如一只黑色的蒙古马,在这里,我除了描述一只马外,还要指出它是黑色的,产地是蒙古,这里还没有加上马的品种。
我在《芥子须弥-----封装》里曾经提及,事物可以划分成属性和方法的集合。那么我们是不是可以对这个集合进行再加工呢,我们只要在里面加上一点别的什么,那他就可以变成别的事物了,想象一下在马的身上加上鹿的角,呵呵呵呵,可笑吗。那么我们说一些严肃的,知道我们中华民族的图腾吗,龙!龙是在蛇的头上加上鹿的角,身上加上虎的腿脚,尾巴是鱼的尾巴,等等。但是不是随随便便就能造就龙这样伟大的象征的,不好好设计,就会成为画蛇添足。
虽然是简单和相加,但也是有不同的方式的。一般来说有两种方式:关联和衍生。
关联:假设原来的类是用一个袋子将属性和方法包起来,那是用关联描述了多个类之间的关系,最简单的情况就是用一个更大的袋子将两个小袋子包起来,成为一个新的类。这种关联叫做组合。
衍生:衍生呢就是用一个大的袋子将一个小的袋子与一些属性和方法包起来,成为一个新的类
     
   A  
   C  
   B  
     


a:关联
     
   A  
   D  


b:衍生
我还是用C++来表示一下
class A{
//...属性+方法
};
class B{
//...属性+方法
};
//关联后的新类C
class C{
    A a;
    B b;
};
//衍生后的新类D
class D : public A{
//...属性+方法
};
看起来关联(组合)比较容易理解,即将类作为一个属性,然后形成一个新的类,这和原来类的使用没什么区别,自己定义的类就是一个类型,这正是封装要做的。
衍生—在原来的类的基础上加上一些属性和方法产生一个新类,看起来新颖诱人,在面包上洒一些芝麻,吃起来更有香味,不是吗。
如何好好利用这一方法,就是我要说的----继承,只要用心去做,面包也会非常美味。(这里提及的都是共有继承也就是class Y: public X;的形式)
上面的方法说起来简单,要是用好它,不是件容易的事。
首先D和A有相同的部分就是A
第二,D有A没有的部分
光光这两点还说明不了继承的用途
我们加上一个新类E,他也是与类A衍生得来的
class E : public A{
//...属性+方法
};
这样我们就清楚一点了,A是D和E的共同点
耶~~~~!我们终于发现这样做的好处了。
类D和类E有相同的部分A,而这里A只被处理了一次,也就是说我们可以少处理一个和A一模一样的工作,如果有许多个类都是从A衍生而来的,那么我们就发达了,问题是,如何让更多的类可以与类A衍生得到有用并好用的类。
这里老祖宗又显灵了,白马非马说的就是这个道理,马正是这个类A,加上颜色,产地,品种,就可以描述各种各样的马了,加上健康等信息还能说明马的状态,如一只活蹦乱跳的黑色蒙古马。
在我们的知识里有很多已知的共同点,碗,瓷器,风,人,花草,我们将他们成为统称。
我们可以下一个定义了:继承--就是将事物之间相同和相似的部分归纳出来。
我们来看一下基类A和子类D的关系,可以发现D中含有A,也就是说可以从一个D类的对象中提取出一个A类的对象来,这种现象就是继承的向下性,马可以是白马,而白马就不能说成是马这个种类的代表。表现在C++中如何处理呢。
D d;
A a = d;
这里将会产成一个A类型的临时变量,它是由d中类A那部分组成的
[
d的组成
[类A的部分]    --à 临时变量da;

]
A a = da;
这种现象叫做切片
再看另一种情况
D * d = new D;
A * a = d;
这时候A * a = d;只涉及到指针的赋值,没有对象的创建,所以不会引起切片,这里是将d在内存中的地址保存在a这个指针里,而描述指针指向的对象是一个类A的对象。由于,D中关联A的信息,C++编译器使得这样的操作是合法的,也是可以理解的。
我们可以把继承A的D称作为A的某个类型,是类A的一种(或者说,D是一个A),就象白马是一种马一样(白马是一匹马)。
继承非常强大,使用它能得到很多好处,但是世界上没有包治百病的,过分的运用继承也会带来麻烦。可以看出继承是一种静态的关系,在编译期间很多东西都定下来了,这也提高了效率,但是要改变很困难,所以灵活性还不够,要想清楚,你的目的是什么,再决定使用关联还是继承。

面面俱到----重载
面面俱到说的是一件事考虑到了很多方面。有的时候我们的一句话适用于很多方面,而这些方面有些类似。比方说加法,加法可以用于,整数和有理数。在C++里可以用两个函数来实现这个目的。
int intadd( int v1 , int v2 )
{
    return v1+v2;
}
float floatadd( float v1 , float v2 )
{
    return v1+v2;
}
使用的方式是这样的
int ia = 1 , ib = 2 , iab = 0;
float fa = 1.0 , fb = 2.0 , fab = 0.0;
iab = intadd( ia ,ib );
fab = floatadd( fa , fb );
这样看起来很不直观,可读性不强,我们的目的是让人一眼就可以看出是在做相加运算。
聪明的读者可能发现,两个函数的返回值和参数并不一样,这样就可以区别开两个不同的行为。这个特性可不可以利用呢,答案是可以,C++贴心的为我们做好了一切。原理很简单,将函数的返回值和参数用一种可逆的编码方法变成一个字符串,安插在函数名后面,编译器向用户屏蔽这一切,用户可以用同一个名字来使用不同的函数。
就是这样
int add( int v1 , int v2 )
{
    return v1+v2;
}
float add( float v1 , float v2 )
{
    return v1+v2;
}
int ia = 1 , ib = 2 , iab = 0;
float fa = 1.0 , fb = 2.0 , fab = 0.0;
iab = add( ia ,ib );
fab = add( fa , fb );
哦~~~~,编译通过了,结果也运行无误。
这样的代码看上去要比原来的好读多了,我们可以略过我们不感兴趣的那些东西。
重载的另一个方面是可以重载运算符,不同的是,它需要使用一个关键字operator。这样就可以用自己的方式来使用运算符了。那么立刻用运算符来改写上面的一切吧
int operator + ( int v1 , int v2 )
{
    return v1+v2;
}
float operator + ( float v1 , float v2 )
{
    return v1+v2;
}
int ia = 1 , ib = 2 , iab = 0;
float fa = 1.0 , fb = 2.0 , fab = 0.0;
iab = ia + ib ;
fab = fa + fb;
哦~~~,这段代码真是太漂亮了,可是不实用,为什么,用过C++的人都知道,象int,float这类的基本类型,本来就可以直接用运算符,那么,就来一个不是基本类型的吧。复数类在C++里可不是基本类型哦。
在这个例子里实现了complex类运算符+的重载,并重载了<<运算符,让ostream可以接受complex的输出。
改变运算符原来的意义并不是一个好主意,应该尽量保证运算符原来的意义,我们使用它的本意就在于让程序易读易懂,书写方便,iostream中的<<和>>是由于人们大量使用形成惯例后的产物,他们原来的意义是做移位运算。
#include <iostream>
unsing namespace std;
class complex{
private:
    int real;
    int image;
public:
    complex( int _real = 0 , int _image = 0 ) : real(_real),image(_image) {}
    int& Real(){ return real; }
    int& Image(){ return image; }
    complex operator + ( complex const& v )
        {
        complex w( real + v.real , image + v.image );
        return w;
        }
};
ostream& operator << ( ostream& out , complex& v )
{
    if( v.Real() )
        out << v.Real() ;
    if( v.Image() > 0 )
        cout << " + " ;
    else if( v.Image() == 0 )
        return cout;
    else
        cout << " - " ;
    cout << v.Image() <<"i";
    return out;
}
void main()
{
    complex w( 1 , 2 ) , v( 3 , 4);
    cout << (w + v) << endl;
}
代码看起来很简洁,可读性很强。
我们可以看出<<失去了他原来的意义,而变成了ostream类的接口,其实重载运算符在本质上和重载函数是一样的。
使用重载的目的就是提高代码的可读性,运用得好还可以提高可复用程度。

芥子须弥----封装
说起面向对象OOP首先就有人大喊着封装
究竟封装是什么呢,为什么要封装?
是的,本来并没有封装,封装是由于人们的需要才产生的,就如同计算机来到人间,编程语言进入你的大脑,自然而然。
在没有封装的时候,人们一样在编程,并没有因为封装的出现使得猿猴变成了人,所以封装没有那么神秘,可怕。
想象一下,我们生活里也有很多封装:食品被放在袋子里,用来防止混入灰尘和昆虫侵蚀;戴上太阳镜,防止紫外线伤害眼睛;钱和贵重物品放在保险柜里,防止心术不正的人。
好了,我们看出一点,封装的一个作用是保护我们的东西。
在生活中,有很多惯例,这里要提到一点,那就永真式,这是一个表示在无论什么情况下都为真的式子,当然这是一种理想的情况,但人们往往喜欢创造这样的东西,就象“水往低处流” 。牛顿说“由于地球引力,所以苹果是往地上掉的”幸亏有这样的道理,我们才有水喝,有苹果吃。当然了在地球上很难找出反例,所以我们无需考虑苹果飞上天的可能性。又如人的听觉范围是20HZ~~20kHZ,如果有人说地球上会有一个人的听觉超出这个范围,我一点异议都没有,但是我还是把人的听觉作为20HZ~~20kHZ来处理,应为概率论上有一句,小概率的事是不会发生的,我不打算证实他,我只是想利用他,我只处理20HZ~~20kHZ的听觉范围,在绝大多数领域里,这样做足够了,让在这方面有特别要求的人挣扎去吧,少处理这些个别的人会带来更多的好处。
我们又能发觉到,封装的好处能降低复杂度。
我想没有人会愚蠢到将一只羊和一只牛相加,得到两只羊或两只牛这样的结果,但是两只动物这样的结果却是可以接受的。
这样看来,封装他能够降低出错的可能性。
封装所做的远远不是这些。
等等,各位心中是不是充满了疑惑。
讲了这么久,为什么不说明封装到底是什么呢,文中并没有提到啊。
聪明的朋友,封装并不是什么东西,也不是什么方法,他是人们考虑和解决问题的思路,他就是你的思想,就是你的灵感一闪,就是你的锦囊妙计。封装就是人们为了更好的管理和使用事物的方案,它可以让你更方便,更安全的做你想做的事,长久以来,充满智慧的杰出者们摸索出一些使用封装来解决问题的办法。
在编程的应用上,我们看看封装给我们带来的好处吧。
由于计算机里,一些都数字化了,所以信息都是存放在很多很多的存储单元里的,由于硬件的限制,这些存储单元都有确实的大小。
首先我们来谈谈保护我们的数据不被随便访问,这正是我提到的。
比如说一个员工可以看到自己的工资,但如果他能够修改的话,只怕人人都想去这个公司工作了。修改工资的数据,万万不行,万万不行,但是如果人人都不能修改工资这一数据,那也不行啊,财会需要修改关于工资的数据。
这里我使用面向对象开发而设计的C++语言来实现他,因为他是为面向对象设计的,所以实现起来比较方便。没学过C++的人先要看一看基本概念,至少要理解private,public,protected,friend等关键字的概念。
我们来理解一下我们要做的事,公司里有很多人,人人都有工资,一些是员工,员工用工号来区别,一些是财会,而财会也是员工,员工能察看工资,而财会还能修改工资。
我们有三个角色,人,员工,财会,要做两件事,员工察看工资,财会修改工资,然后理清他们的关系
人有姓名,年龄,性别 这三样是我们需要处理的,我们要做的是处理工资,一些无关的信息就可以省略,习惯上每个部门都需要人的这三个信息
人 = { 姓名 , 年龄 , 性别 }
员工是人,财会是人,财会是员工,员工有工资,为了区别员工每个员工有工号
员工察看工资,财会修改工资
员工 = { 人 , 工资 ,工号 , 察看工资 }
财会 = { 员工 , 修改工资 }
//下面三句代码是使用标准函数库,可以节省我们很多功夫,方便我们理解我们要理解的,略过繁枝小节
#include <iostream>           
#include <string>
using namespace std;
class Man{
public:
       enum SexType { Mele , Female };              //枚举类型,性别只有男女两种,这里不考虑人妖,如果输入不是这样种类型,编译器会报警。类型转换中如果不是者两个值,也会报警,这样我们减少了误输入引起的错误。
private:   //变量不能乱操作,设定为私有,只有类的成员函数才能操作,起到保护作用
       string Name; //姓名,string是标准函数库里提供的类,可以方便的处理字符串
       unsigned Age; //年龄,由于年龄不可能是负数,所以用unsigned表示,这样可以防止逻辑错误
           SexType Sex;  //性别
public:     //对外的接口,当然要开放了
       string GetName( void ){ return Name; }  //得到人的名字
       void SetName( string const& name )        //改变人的名字,这里预先检查了名字是否合法
              {
              if( str == "" )
                     return;
              Name = name;
              }
       unsigned GetAge( void ){ return Age; }        //得到人的年龄
       void SetAge( unsigned age )                        //改变人的年龄,由于员工50岁就退休了,所以50以下的才合法
              {
              if( age >= 50 )
                     return;
              Age = age;
              }
       SexType GetSex( void ){ return Sex; }        //得到人的性别
       Void SetSex( SexType sex ){ Sex = sex }      //改变人的性别
};
class Accountant;
class Employee : public Man{
private:
       unsigned ID;     //工号
protected:              //虽然要加以保护,但是他的后继类财会要操作的
       unsigned Pay;    //工资
public:
       unsigned GetID( void ); //取得工号
       void SetID( unsigned ); //改变工号
       unsigned GetPay( void ){ return Pay; } //察看工资
friend class Accountant;    //由于财会能够修改所有员工的工资,所以要将访问权信托给财会
};
class Accountant : public Employee{
public:
       void SetPay( unsigned pay ){ Pay = pay; } //改变自己的工资
       void SetPay( Employee * man , unsigned pay ){ man->Pay = pay; }; //改变别人的工资
};
当然这是经过精心设计后的封装,简化了结构,正因为一开始细心的分析,才使得设计可以轻松自如,归根结底是由于思想正确,好了,封装是一种思想,我们现在将他体现了出来。
再看这个例子里,工资的类型是unsigned,非负整数,呵呵,大家都不愿意到工资为负的公司工作吧,这里简化问题是从人们的惯例的角度出发的,如果你的老板考虑工资为负的情况,那么…^_^
现在一个粗心的财会不小心改错了,他多敲了一个0,哇欧,请客请客,但这个财会可就惨了,这样的好事不会发生,不准发生,老板青着脸狂吼着。
设计不得不加上一个工资的上限,没办法啦,现在国家规定的吗。
class Accountant : public Employee{
       enum MaxPay{ MAXPAY = 8000 };
public:
       void SetPay( unsigned pay )
              {
              if( pay > MAXPAY )
                     return;
              Pay = pay;
              }
       void SetPay( Employee * man , unsigned pay )
              {
              if( pay > MAXPAY )
                     return;
              man->Pay = pay;
              }
};
可以看出封装的作用就是减少出错的可能,方便灵活的运用类型
在上面的例子里我们看到,类class是由一些变量和函数组成的,这些变量和函数是类的一部分,我们称之为成员,变量就是成员变量,函数当然就叫成员函数了。为什么要这样呢,我们考虑一下,事物是由物质和运动组成的,表现物质的一面我们通常描述他的一些属性,即他拥有什么,表现一个运动我们通常使用一个过程,要将一个事物的信息描述清楚就需要这两样东西。在长期的实践中程序员达成一个共识,将事物的特性(也就是它拥有的)称之为属性,他能够产生的行为称之为方法,数字化以后就是成员变量和成员函数,他们的组成的整体就是类(类型),这个类型将作为一个单独的节点考虑,就像例子中的Man,我们不会说这是一个姓名,年龄等等的组合体,而是将他作为一个类--class Man考虑,从而简化了问题。细小的事物组合成大的事物,大的事物组合成更大的事物,这样下去,再大难题也可以化作小模块来处理,这正是封装诱人的地方和他的使命。
需要补充的是,既然我们把数据保护起来,那么如何让用户访问这些数据就是一个问题了,在上面的例子中看出public:申明的方法,用户是可以使用的,而我们正是通过这些方法将数据的信息告诉使用者,这里我们将描述方法的部分就叫做接口(在C++里,就是类中成员函数的声明,用户一般只对public:部分的接口感兴趣,所以有人建议将public:部分的内容写在显眼的地方,比如靠类的顶部),也有人说是界面,也就是类和外界沟通和交流使用的渠道,所以接口是很重要的,他直接关系到你的类使用的方面。
而使用者使用类的某个接口的时候就象是通知这个类型使用某个行为,就象是传递一个消息给他一样,我们把使用接口称之为传递消息,而类被调用方法称之为接受消息。
现在我们可以出定义:封装就是将事物的内容和行为都隐藏在实现里,用户不需要知道其内部实现,这是大量程序员反复劳动后得出的一致结论。这样的好处就是使用方便,易于维护,任何一样都可以使程序员为之心动。当然我们不能保证高效,但是不意味着使用封装就没有高效的可能,如果在封装的基础上保证高效的话,我实在找不出理由来拒绝他。