C++类成员变量是为什么选择private?

      首先说明下,在c++标准中,类成员变量可以有3个关键词,public,protected,private,分别对应公有成员,保护成员,私有成员
       换句话说,c++标准是完全允许使用公有成员的,所以我讨论的是我个人不用protected或public成员的原因,并不代表你(或其他人)不可以用
      事实上,我绝对不会反对你将类成员设为公有,因为那和我没关系。。
回到正题
~~~~~~~~~~第一步~~~~~~~~~~
对于一个类的定义
class Human_protected{
protected:
  double Age;
  std::string nm;
public:
  void GetName(std::string NewName){nm = NewName;}
};
他还有另一个“写法”
class Human_private
{
  private:
    double Age;
    std::string nm;
  public:
    std::string GetName()const{return nm;}
    double GetAge()const{return Age;}
    void GetName(std::string NewName){nm = NewName}; 
};
          这两个类将会作为基类,而他们类成员的值需要在继承类中使用,于是一个类将他的成员设为protected,以方便继承他的类的使用,而另一个类更侧重封装,将类成员设为私有,如需使用他们的值则需要调用类成员函数
        先看下两者的区别,以便让你有充足的理由选择其中的某一个
      目前为止双方的优势为
class Human_protected
    1 少打了不少字母,同时少定义两个成员函数
class Human_private
     暂无。。。
     目前看来,class Human_protected暂时优势,所以你选择了他,也就是选择将成员变量设为protected
Let's Continue..
~~~~~~~~~~~第二步~~~~~~~~~~
         在c++中,继承是个很常用的概念,对于第一步中的两个Human类,通过继承来建立一个Worker类是个不错的主意
class Worker_protected:public Human_protected
{
  private:
    double Pay;  //工资
    std::string TechType;  //技术工种名称
  public:
    void ShowInfo()const;
};
他的另一种“写法”其实完全一样
class Worker_private:public Human_private
{
  private:
    double Pay;
    std::string TechTypeSchool;
  public:
    void ShowInfo()const;
};

他们的不同在于成员函数ShowInfo()函数,他们的代码有所不同
void Worker_protected::ShowInfo()const
{
  std::cout<<"Name:"<<nm<<std::endl;
  std::cout<<"Age:"<<Age<<std::endl;
  std::cout<<"Technology Name :"<<TechType<<std::endl;
}
//----------------------
void Student_private::ShowInfo()const
{
  std::cout<<"Name:"<<GetName()<<std::endl;  //区别
  std::cout<<"Age:"<<GetAge()<<std::endl;  //区别
  std::cout<<"Technology Name :"<<TechType<<std::endl;
}
       这里的区别在于,对于protected成员nm和Age,继承他的类可以直接在成员函数中调用,而对于私有成员,继承他的类要使用他们的值必须使用基类的成员函数
     我们再次统计两个继承类的优劣势
Human_protected
     1成员函数中不需要调用基类的成员函数就可以直接使用基类的成员变量,节约的调用基类成员函数的开支,同时再次少打了一些字母。
       PS:不要否认少打字母不是优势,这有时候会让人感到厌烦甚至出错(这也是C++11引入关键字auto的原因之一,跑题了。。。)
Human_private
暂无
这里protected再次取胜
不过实际的软件设计往往会比较复杂,所以我们继续
~~~~~~~~~~第三步~~~~~~~~~~
         对于一个表示工人(worker)的类来说,他需要的功能远远不止显示信息(ShowInfo()函数)怎么多,我们需要加入更多的函数来完善这个类
void GetInfo(const std::string& NM , int ages , const std::string& techtype) //录入信息
void CountPay(int Hours);  //计算工资,小时数*工资数 +工种工资,不同的共种待遇不同
void ShowPay()const; //显示格式 (张三 :2000元)
void ShowHoliday(int Hours)const; //显示格式 (李四:已用年假5小时)
//。
//。
//。
//。
            以上成员函数实现Worker类的功能,包括录入信息,计算,显示工人的工资,显示他已经请的年假,等等,这些函数都除CountPay()外,都使用到了基类的成员变量nm(姓名).
          当然这些对于一个Worker类任然不够,我们假设还有其他10个成员函数(其实还是不够,但已经足够说明我们今天需要讨论的问题)来实现Worker类
          这些函数都有一个共同特点,他们需要使用到基类的成员变量nm,调用方式第二步已经说明了
          现在protected和private的优劣势越发明显,随着继承类(需要使用基类成员变量的)成员函数增多,private需要调用跟多次的函数,写更多的字母。。
         似乎胜负已分,那让我们再次继续
~~~~~~~~~~~第四步~~~~~~~~~~~~
          软件设计中途遇到设计上的变化是很稀松平常的事,一个软件在设计周期内经常需要改动其设计,而这次促使你修改软件的原因是你的老板跑过来找你,要求把姓名改用编号
         老板这么要求的原因有两个
1.很多员工抱怨显示名字有侵犯隐私的嫌疑
2.工人的姓名有重复的,财务根据姓名支付薪水的时候把钱打进了错误的人的账号里(啊,这真是最令人愉快的事,当然,前提是那是我的银行账号)
           据此,你的老板要求你用一个六位编号(数字)来代替姓名,因为每个工厂里的每个工人入职是都会分配一个唯一的六位编号,不用担心重复以及隐私的问题
          另外你的老板还有个很重要的要求,这个编号必须为int类型,因为这个编号将来可能会用于计算或者和其他系统的对接(的确,未雨绸缪通常是个不错的选择)
          对于Worker_private类来说,你可以把基类Human_private中的成员变量std::string nm直接注释掉,然后增加一个int Number,在GetName(std::string NewName)中把参数类型改为int,即GetName(int NewName)
        然后把函数std::string GetName()const{return nm;}改为
std::string GetName()const
{
  //将int转化未std::string的代码,这样的代码相信难不倒你吧
  return Number
}
就可以了,你根本不用考虑继承他的类,继承他的类都调用GetName()函数获得一个string,继承他的所有类(包含所有的子子孙孙)都不要为此做任何修改
PS:其实这样做有一个隐患,等会我在告诉你。
            然后我们来看Human_protected类,他同样需要修改,你第一个想到的办法可能是和上面Human_private类一样,注释掉std::string nm成员,然后增加一个int Number类成员,但你是一名严谨的程序员(是的,我相信你),未了程序的安全可靠性,你在做这样的修改后把这个程序其他的代码全部都检查了一遍,这样做的原因在于,子类是直接使用nm变量的,所以如果子类中有类似
std::cout<<nm;
std::string Info = nm + TechType;
这样的代码,直接删除类成员变量nm,就会导致程序崩溃,所以你检查整个程序也就理所当然了
        当然,你也可以有另一种该法,即把std::string nm 直接改成int nm;这样变量名不变对于类似std::cout<<nm的代码,他可以继续正常执行(有原来的输出string变成输出int),但对于
std::string Info = nm + TechType;
这样的代码,,直接修改变量类型依然会出错。
            所有的情况,最后的解决办法是一样的,那就是你必须检查所有继承类的代码,以确保修改基类后,这些继承类任然能正确的执行他们的功能
           结论:对于类成员未protected的基类的类成员的修改结果就是你必须检查整个程序以确保修改后的正确性,当然对于我现在写的例子来说这没什么,毕竟这只有几行代码,但如果你的项目比较大呢?
现在你有没有隐隐约约的意识到一些问题?(背后凉飕飕了。。)
~~~~~~~~~~第五步~~~~~~~~~~~~
            我们继续设计软件,实际工作中的软件设计规模往往比较大,而聪明的你满腹才华(是的,我相信你),你不再满足在一家工厂拿着3K的月薪,为一个老板编写工人管理系统,你觉得自立门户自主创业,经过几年的努力,你已经是一位软件公司的BOSS,拥有500个员工,现在你接到一笔价值一亿美元的软件生意(这其实也算不上大生意),软件是帮助客户定做一套企业管理软件,你和客户商量花费三年实际来完成,两年半时间完成,半年时间测试
            这个软件拥有三千万行的代码,大约包含了1500个类,其中有一个基类human类,他有一个成员变量std::string Name,整个程序有大约1000个类直接或间接继承自这个类,大约有12000个类成员函数都
需要使用到这个类的成员变量std::string Name,为了少打一些字母,你的程序员把std::string Name设为protected
            两年多过去了,你公司的软件即将完成并准备展开为期半年的测试,这时候你的客户打电话过来,要求把姓名改为用编号(一个七位数,类型必须是long long)直接原因是你客户的直接竞争对手在怂恿工人去控告公司侵犯隐私(啊,你的客户似乎摊上大事了),
          另外,软件的起始设计开始于两年前,硬件的变化也促使客户要求改用编号(long long),想想日新月异的硬件更新吧,两年多的时间足够硬件发生翻天覆地的变化了。
          再者,公司人员的增加使得员工重名的几率增大,工资发错人之类的现象不时出现
          由于std::string Name涉及了1000多个类和其中大约12000个成员函数,所以你要把这些全部逐一检查,修改(必须怎么做的原因就和第四步所说的一样)。当然,你还必须在几个月内完成软件剩余部分的同时完成这些工作(实时上需要修改的代码占了已完成部分的大多数)。。。。。你尝试在上一步中的方法,直接用替换,但是的程序员告诉你,无法保证修改后程序还能正确的执行,用为很多地方有std::string Name和另一个std::string相加的代码,但你的程序员很难准确的记住所有,毕竟人类的记忆很难记住如此庞大的代码。。
            现在你面临着到时间不能完成而支付巨额违约金的问题,或者你先发制人去控告客户提出了合同外的要求,但眼下最重要的问题在于你的一亿美元很有可能拿不到了,而这两年公司的开支都依赖银行贷款。。。。
回过来想想,相比于这些问题,最初将变量设为private,然后修改对应的成员函数(具体方法见第四步)是不是就不会遇到这些麻烦了,最后我们比较下多打几个字母是不是会更容易些。。。。
~~~~~~~~~第六步~~~~~~~~~~~
书接上回
           最终,你赢得了和你客户的官司,由于你客户提出了合同外的要求,所以你不需要支付违约金,但客户显然也不愿意支付你一亿美元的合同款,于是你和你的客户再次陷入漫长的官司中,当然我们先撇开法律问题不谈,眼下你最重要的是找到资金支付员工工资,维持公司的正常运营,这公司毕竟是你一手创建的心血。。。
          而你手里有一个现成的C++类库可以对外出售,毕竟你原来的客户已经不愿为这个程序支付一毛钱。。。
对于第三方的C++类库,市场上有很多,图像库Qt,微软的MFC图形库,甚至Boost库,你迫切的希望把你的类库出售,让尽可能多的人来购买你的拷贝
          为此,你为你的C++类库写了一份说明文档,这份文档和我们常见的文档有所不同,他比较厚。。。。
之所以比较厚,原因在于上面有一份关键词列表,上面也许有五万甚至十万个关键词,你告诉你(潜在的)客户,你们使用我的类库编程的时候,如果需要继承类库中的类(其实就是C++程序员很熟悉的代码重用)的时候,请背诵着数万个关键词,(数万 == 类库protected成员个数。。。)
           因为这些类成员是保护的,所以你在继承的时候必须格外小心。。。当然你也许会提出,这种问题namespace是个很不错的解决方案
         好吧,暂且是这样,那客户命名的问题我们先暂且放一下,稍后再谈
         在第四步里,我增说过把std::string nm注释掉,增加一个int Number这样做有个隐患,比较正确的做法就是把std::string nu直接改成int nu,然后有
std::string GetName()const
{
  //将int转化未std::string的代码
  return nm;
}
            事实上,在大型工程中往(protected)类中添加类成员是个很危险的行为,原因就在于你无法确定你添加的变量名是否已经在其他类中被使用。对于类成员未private的类来说,这个问题不大,仅仅是我上面说的一个隐患,因为你只要保证新加入的类成员名和该类的其他成员不一样,不需要考虑其他的类,但如果你要添加一个protected属性的类成员(而且其他相关的各种继承类也有大量的protected类成员),你就会发现问题了,你如何知道继承这个类,或者这个类继承的其他类中没有这样一个变量呢?
             好吧,让我们把时光往回推一些(爱因斯坦大大被打我,我纯粹是假设),你的公司设计的程序包含了1500个类,你当初开始设计第一个类的时候,你就要把类成员名称放入一个namespace,然后告诉你们公司所有的500个程序员,告诉他们,
           “这个变量名已经被使用了,因为他是protected的,所以不要在继承类中使用到这个变量名,你们记住,这样的变量名大概有5-10万个,以后将陆续加入这个namespace,你们一定要全记住了。。。千万啊。。。。”
            如果你不是Boss而是一名员工,当你需要开发一个需要在脑海中记住数万乃至跟多的变量名的程序的时候,有木有一种灾难的感觉?

~~~~~~~~~第七步~~~~~~~~~~
           假设你的公司是一家极具韧性的公司,你的员工在你的带领下客服了第六步我所述的困难,而且你也找到了一个优质客户,他不建议编程的时候使用额外的名称空间(namespace),现在你终于有了第一个客户,一切想着好的方向在发展
你的类库中的一个类,有类似代码
int dev;
这个表示某个硬件设备,0-9表示该设备的10种不同型号如果基类是protected,则有
class Pro
{
  protected:
    int dev;
}
当然,如果设为private的,则有必须使用一个专用的接口(成员函数)
class Pri
{
  public:
    int ShowDevictType(return dev;)const
}
         你的客户在购买了你的类库后使用(继承)了这个类,他需要在这个了类的基础上增加一些额外的功能
然而硬件的飞速发展使得人们很快不在使用这种设备了,所以删掉他是个不错的主意
         作为一家C++类库的开发公司,保证类库版本的跟新很重要,尤其是硬件飞速发展的今天。如果类成员是protected,你现在在删除这个变量的时候面临一个问题,你如何知道你的客户是如何使用他的呢?客户继承了你的这个类,然后添加了一些他们公司所需要的代码,他的代码里有很多直接使用int dev的代码,比如std::cout<<dev,或者其他相关dev的计算,如果你突然删除了这个变量,那当你的客户更新了类库,然后你的客户发现他以前的代码出现了BUG井喷。。。。
         也许你会提议,直接在类的构造函数里使用dev = 1;来固定他的值,然后再文档中说明下情况(做法类似下面的private的方法),但如我前面所说,你永远不可能确定你的客户会做什么?
          假如你的客户继承了你的类,然后再类里有一行,dev = 12;的代码。。。。不要怀疑,这是很容易犯的错误。
        没错,继承类可以直接使用基类的成员,dev的值被重置为12,但他的取值只能未0-9,超出了这个范围,你的类库中其他依赖这个值的代码就有可能出错,你的客户怎么做了以后,当他再次使用类库中其他的类,只要牵扯带这个dev变量的类都会出现莫名的错误,于是你的客户打电话过来,告诉你,你的类库存在着严重的bug,并且要求退款。。。你会不会感到很无辜
         而如果你的变量是private的,那问题就简单多了,你不知道你的客户将如何使用int dev的值,但你可以确定的是,你的客户是调用ShowDeviceType()函数来获得值的,所以当你的类库需要更新删除这个变量时
1 直接注释掉 int dev;
2 将ShowDeviceType()函数里的代码 return dev;改成return 1;  //数值只要是以前dev合法的值就可以
          没错啦,你和他一样,也会在类库其他地方的代码中使用到这个int dev的值,但都是调用ShowDeviceType()函数来获取的,所以不用担心删掉变量会对其他类有什么影响,这么改后你的类库中的代码以后可以慢慢的修改。        
           然后你在你更新类库的文档加上这么一句,“原先的接口(ShowDeviceType())已被声明废弃,但仍保留其接口(值固定为1)用作和以前的代码兼容,但会在将来的某个时间移除。”
         这句话是不是很熟悉?相信你以前看文档的时候,如果有版本升级,那肯定会见到类似的话,没错啦,其实这句话翻译过来就是,”变量我删了,你如果还用这个变量的话他是个固定的值,我代码里原来用到的地方我以后会逐一修改,但目前没时间,具体修改完成是在将来的某个时间。。。。“
         呼~~~~经过这么多步,我们回到最初的问题,你的类成员是选择private还是protected,或者干脆是public呢?


发布了92 篇原创文章 · 获赞 19 · 访问量 3万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 编程工作室 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览