The C++ programming language Part Two 10(译)

10.1  介绍
    C++的类概念的目的是为了给程序员提供一种创造像内嵌类型方便使用的工具。另外,继承类和模板提供一种组织相关类的方法,这种方法允许程序源利用他们之间的关系。
    一个类是一个概念的具体表示。例如,C++的内嵌类型:float 和他的操作符 +,-,*等提供了一种数学概念上的小数的具体近似。一个类是用户定义的类型。我们设计一个新类以提供在内嵌类型中没有直接相似的概念定义。例如,我们在一个程序可能提供一个叫Trunk_line的类型处理电话,一个叫Explosion的类型处理电视游戏,或者一个叫list<Paragraph>的类型处理一个文字处理程序。一个提供了应用概念类型的程序会比一个没有此功能的程序更容易理解和修改。一个精心选择过的用户定义类型集使一个程序更精简。另外,它使得许多代码分析更容易。特别的,它使编译器能检测到对象的非法使用,否则只有等到这个程序完全测试后才能检测到。
    在定义一个新的类型时,基础的概念是要把在实现中不重要的细节和对于使用的本质属性分开。这种区分最好通过疏导所有的数据结构的使用和内在过程表达。
    本章主要关注相对简单的具体用户定义类型,这种类型在逻辑不会和内嵌类型差别太大。理想情况下,这些类型在用法上和内嵌内型没有不同,只有在创建上有所不同。

10.2 类
    一个类是用户定义类型。本段介绍定义一个类和创建一个类的对象以及操作这些对象的基本方法。
10.2.1 成员函数
    考虑使用struct实现日期的概念,和实现操作日期类型数据的一组函数:
     struct Date{
               int d,m,y;
     };
     void init_date(Date& d,int ,int ,int);
     void add_year(Date& d,int n);
     void add_month(Date& d,int n);
     void add_day(Date& d,int n);
     在数据类型和它的函数之间没有明显的关系。这种关系可以通过声明成员函数实现。
     struct Date{
     int d,m,y;
     void init_date(Date& d,int ,int ,int);
     void add_year(Date& d,int n);
     void add_month(Date& d,int n);
     void add_day(Date& d,int n);
};
     在一个类定义中声明的函数称为成员函数,而且只能由使用标准语义的具有适当类型的特定变量调用。
例如:
     Date my_birthday;
     void f()
     {
          Date today;
          today.init(16,19,1996);
          my_birthday.init(30,12,1950);
          Date tomorrow = today;
          tomorrow.add_day(1);
     }
     因为不同的结构可以有相同名字的成员函数。当我们定义一个成员函数的时候,我们必须指定一个结构名称。
      void Date::init(int dd,int mm,int yy)
     {
         d = dd;
         m = mm;
         y = yy;
     }
     在一个成员函数中,成员名字不必显式引用一个对象。也就是说,名字指定函数调用所属的对象中的成员。例如,当Date::init()被today调用,m=mm赋值给today.m.一个类成员函数总明白哪个对象调用了它。
     构造函数:
          class X{ ... };
     当一个类被定义时被调用,因为它定义了一个新的类型。因为历史的原因,一个类定义总是被认为类声明。同时,就像声明不是定义,一个类的定义在不违反一个定义规则的情况下,可以通过使用#include在不同的源文件复制。
 10.2.2  访问控制
    在前面章节中Date 的声明提供了一组操作Date的函数。尽管如此,它并不指定那些函数是唯一直接依靠Date表示的,但是是唯一直接访问Date对象的。这样的限制可以通过使用Class代替struct表示。
    class Date{
          int d,m,y;
    public:
          void init(int dd,int mm,int yy); // initialize
          void add_year(int n);   //add n years
          void add_month(int n);    //add n months
          void add_day(int n); //add n days
     public标记把class体分为两个部分。在第一个私有部分中的名字只能被成员函数使用。第二个公有部分构成了对象的公有结构。一个struct只是简单的默认成员是公有的class。成员函数能被定义和使用。例如:
          inline void Date::add_year(int n)
          {
                  y += n;
          }
    尽管如此,非成员函数被禁止使用私有尘缘。例如:
         void timewarp(Date& d)
         {
                  d.y -= 200;
         }
     这样通过限制显式函数列表访问数据结构有几种好处。例如,任何导致Date取到非法值的错误肯定是由成员函数中的代码导致的。这意味着调试的第一阶段是完全在运行程序之前的。这里有一个大致观察到的例子,任何对于Date类型行为的改变能而且只能通过他的成员函数。特别的,如果我们改变了一个类的表示,我们治需要改变成员函数取利用新的表示。用户代码直接以来于共有接口,而且不需要被重写。另一个好处是一个潜在的用户只需要通过成员函数的定义取学习使用一个类。
     对于私有数据的保护依赖于限制使用类的成员名字。这可以通过操作和显示类型转换来规避。但是这是在欺骗。c++针对意外进行保护,而不是刻意的规避和欺骗。只有硬件才能针对恶意使用一种普遍用途语言进行保护,甚至那也在实际系统中也很困难。
     init()函数是被部分添加进去的,因为这有一个给对象的赋值的函数是很有用处的,另一部分原因是强制我们提供私有数据的值。
10.2.3 构造函数
    像init()函数给类或者对象提供初始化是不优雅和容易出错的。因为没有其他地方表明一个对象必须初始化,一个程序员可能会忘了这么做,或者做两次。一个更好的方法是允许程序员声明一个带有初始化对象功能的函数。因为这么一个函数构造了具有给定类型值的对象,这被称为构造函数。一个构造函数通过和类具有相同的名字来识别。例如:
   class Date{
        Date(int ,int ,int);
};
   当一个类有构造函数,所有的这个类的对象都会被初始化。如果这个构造函数需要参数,这些参数必须提供:
        class Date{
             int d,m,y;
        public:
             Date(int ,int,int);
             Date(int ,int);
             Date(int);
             Date();
             Date(const char*);
        };
       构造函数遵循和其他函数同样的重载规则,只要构造函数在参数上和其他构造函数足够不同,编译器就会选择一个正确的构造函数来使用:
       Date today(40);
       Date july4("July 4 ,1983");
       Date guy("5 Nov");
       Date now;
       Date的构造函数的增加是很典型的。当我们设计一个类的时候,一个程序员经常需要增加特性,只是因为有个人或许需要。仔细地决定那些特性是真的需要更加值得思考,然后只增加他们。尽管如此,这种额外的思考能获得更小和更容易理解的程序。一种减少相关函数的办法是使用默认的参数。在Date中,每个参数能被给予一个默认的参数。
10.2.4  静态成员
        Date的默认值的使用带来了一个巨大的隐藏的问题。我们的Date类变得依赖于全局变量today。这个Date只能在today被定义和正确使用的上下文中使用。这是一种导致一个类在被首先定义的上下文之外变得没有用处的限制。用户在使用这种上下文依赖的类时会有许多不适应,而且维护也变得混乱。也许一个小的全局变量还号处理,但是这样的风格导致了无用的代码,除了定义这些类的人。这个应该避免。
        幸运的是,我们在没有一个公有可访问的全局变量的负担的情况下,我们依然可以有这样的便利。一个属于类的变量,而且不是对象的一部分,称为静态变量。不同于普通的非静态成员,对于对象只有一个拷贝。类似的,一个需要访问成员而不需要被一个特殊对象调用的函数,称为静态成员函数。
        下面是一个保留了默认构造函数的一个重新设计,这个设计去除了Date依赖一个全局变量的问题。
        class Date{
             int d,m,y;
             static Date Default_date;
        public:
             Date(int dd=0;int mm=0;int yy=0);
             static void set_default(int ,int,int);
        };
        我们现在可以定义一个这样的构造函数:
        Date::Date(int dd,int mm,int yy)
        {
             d = dd ? dd:default_date.d;
             m = mm ? mm : default_date.m;
             y = yy? yy:default_date.y;
        }
        在适当的时候我们可以改变默认的date。一个静态成员可以像其他成员一样引用。另外,一个静态成员在不用对象的情况下引用,只能用起类名引用。例如:
        void f()
        {
            Date::set_default(4,5,1945);
            
        }
        静态成员(不管是函数还是数据)必须在某地方定义。例如:
        Date Date::default_date(16,12,1770);
        void Date::set_default(int d,int m,int y)
        {
             Date::default_date = Date(d,m,y);
        }
        现在默认的值是贝多芬的生日,知道某人改变它。
        注意Date()作为Date::default_date的标记。例如:
        Date copy_of_default_date = Date();
        最后,我们不需要一个分离的函数来读取默认的date;
10.2.5 类的对象的复制
        根据默认,类对象能被复制。特殊的,一个类对象能被另一个类对象初始化。在构造函数已经声明的情况下,这是可以做到的。例如:
        Date d =today;
        根据默认,一个类对象的拷贝是每一个成员的拷贝。如果这个默认不是你想要的,一个更合适的可以通过定义拷贝构造函数X::X(const X&)实现。这将会在10.4.4.1讨论。
        类似的,类对象能通过复制拷贝。例如:
        void f(Date& d)
        {
            d = today;
        }
        另外,默认的是成员拷贝。如果那不是对X类的正确选择,用户可以定义一个更合适的复制运算符(10.4.4.1)。
10.2.6 常成员函数
        Date至今提供了改变给定date的成员函数。不幸的是,我们没有提供一种检查Date值的方法。这个问题能通过增加读取日期、月份、年的函数来修正:
        class Date
        {
           int d,m,y;
        public:
           int day() const { return d };
           int month() const { return m;}
           int year() const;
        }
        注意在函数声明中跟在参数列表后面的const。它指出这些函数不能修改Date状态。
        自然地,编译器会捕捉违反这个规则的行为。例如:
        inline int Date::year() const
        {
           return y++;
        }
        当一个常函数成员在类外定义时,const需要被显式声明。
        一个常函数成员能被常类和非常类调用。一个非常函数成员只能呗非常类调用。
10.2.7 自我引用[class.this]
        状态更新函数add_year(),add_month(),和add_day()被定义为不返回值。对于这种更新函数,返回一个引用使得操作被连接是很有用的。例如,我们可以写:
        void f(Date& d)
        {
             d.add_day(1).add_month(1).add_year(1);
         }
        来增加一天、一月和一年。为了这么做,每个函数必须声明为返回一个Date引用。
        class Date
        {
            Date& add_year(int n);
            Date& add_month(int n);
            Date& add_day(int n); 
        };
        每个(非静态)函数成员知道那个对象调用了它,能显式地引用该类,例如:
        Date& Date::add_year(int n)
        {
            if(d==29 && m==2 && !leapyear(y+n)
            {
                 d = 1;
                 m = 3;
             }
             y += n;
             return *this;
         }
         表达式*this指向调用成员函数的类。它与Simula的THIS和Smalltalk的self相同。
         在一个非静态成员函数中,关键词this指向调用该函数的对象。在类X的非常成员函数中,this的类型是X * const.const明确地指出用户不能改变this的值。在一个....
10.2.7.1  物理和逻辑常量
         偶尔地,一个成员函数逻辑上是const,但是它需要改变成员的值。对于用户,这个函数不能改变对象的状态。然而,一些用户不能直接观察的细节被更新了。这被称为逻辑常量。例如,Date类有一个用户用来输出的返回string的函数。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值