一个类:

class Sales_item {
public://程序的所有部分都可以访问带有 public 标号的成员。类型的数据抽象视图由其 public 成员定义。 // operations on Sales_item objects
    double avg_price() const;
    bool same_isbn(const Sales_item &rhs) const//const 成员不能改变其所操作的对象的数据成员。
        { return isbn == rhs.isbn; }
    //构造函数一般就使用一个构造函数初始化列表,来初始化对象的数据成员
    Sales_item(): units_sold(0), revenue(0.0) { }
private://使用类的代码不可以访问带有 private 标号的成员。private 封装了类型的实现细节。
    std::string isbn;
    unsigned units_sold;
    double revenue;
};

double Sales_item::avg_price() const
{
    if (units_sold)
        return revenue/units_sold;
    else
        return 0;
}

类背后蕴涵的基本思想是数据抽象封装。数据抽象是一种依赖于接口和实现分离的编程(和设计)技术。类设计者必须关心类是如何实现的,但使用该类的程序员不必了解这些细节。相反,使用一个类型的程序员仅需了解类型的接口,他们可以抽象地考虑该类型做什么,而不必具体地考虑该类型如何工作。

函数是封装的一种形式:函数所执行的细节行为被封装在函数本身这个更大的实体中。被封装的元素隐藏了它们的实现细节——可以调用一个函数但不能访问它所执行的语句。同样地,类也是一个封装的实体:它代表若干成员的聚焦,大多数(良好设计的)类类型隐藏了实现该类型的成员。

class和struct的区别:如果类是用 struct 关键字定义的,则在第一个访问标号之前的成员是公有的;如果类是用 class 关键字是定义的,则这些成员是私有的

  • 具体类型和抽象类型

并非所有类型都必须是抽象的。标准库中的 pair 类就是一个实用的、设计良好的具体类而不是抽象类。具体类会暴露而非隐藏其实现细节。一些类,例如 pair,确实没有抽象接口。pair 类型只是将两个数据成员捆绑成单个对象。在这种情况下,隐藏数据成员没有必要也没有明显的好处。在像 pair 这样的类中隐藏数据成员只会造成类型使用的复杂化。尽管如此,这样的类型通常还是有成员函数。特别地,如果类具有内置类型或复合类型数据成员,那么定义构造函数来初始化这些成员就是一个好主意。类的使用都也可以初始化或赋值数据成员,但由类来做更不易出错。

  • 成员函数可以被重载
class Screen {
public:
    typedef std::string::size_type index;
    // return character at the cursor or at a given position
    char get() const { return contents[cursor]; }
    char get(index ht, index wd) const;
    // remaining members
private:
    std::string contents;
    index cursor;
    index height, width;
};

与任意的重载函数一样,给指定的函数调用提供适当数目和/或类型的实参来选择运行哪个版本:

Screen myscreen;
     char ch = myscreen.get();// calls Screen::get()
     ch = myscreen.get(0,0);  // calls Screen::get(index, index)
  • 显示指定inline成员函数

在类内部定义的成员函数,例如不接受实参的 get 成员,将自动作为 inline 处理。也就是说,当它们被调用时,编译器将试图在同一行内扩展该函数。也可以显式地将成员函数声明为 inline:

 class Screen {
     public:
         typedef std::string::size_type index;
         // implicitly inline when defined inside the class declaration
         char get() const { return contents[cursor]; }
         // explicitly declared as inline; will be defined outside the class declaration
         inline char get(index ht, index wd) const;
         // inline not specified in class declaration, but can be defined inline later
         index get_cursor() const;
         // ...
      };
     // inline declared in the class declaration; no need to repeat on the definition
     char Screen::get(index r, index c) const
     {
         index row = r * width;    // compute the row location
         return contents[row + c]; // offset by c to fetch specified character
     }
     // not declared as inline in the class declaration, but ok to make inline in definition
     inline Screen::index Screen::get_cursor() const
     {
         return cursor;
     }

可以在类定义体内部指定一个成员为inline,作为其声明的一部分。或者,也可以在类定义外部的函数定义上指定 inline。在声明和定义处指定 inline 都是合法的。在类的外部定义 inline 的一个好处是可以使得类比较容易阅读。

像其他 inline 一样,inline 成员函数的定义必须在调用该函数的每个源文件中是可见的。不在类定义体内定义的 inline 成员函数,其定义通常应放在有类定义的同一头文件中。

  • 类的声明与定义

一旦遇到右花括号,类的定义就结束了。并且一旦定义了类,那以我们就知道了所有的类成员,以及存储该类的对象所需的存储空间。可以声明一个类而不定义它:

class Screen; // declaration of the Screen class

这个声明,有时称为前向声明(forward declaraton),在程序中引入了类类型的 Screen。在声明之后、定义之前,类 Screen 是一个不完全类型(incompete type),即已知 Screen 是一个类型,但不知道包含哪些成员。不完全类型(incomplete type)只能以有限方式使用。不能定义该类型的对象。不完全类型只能用于定义指向该类型的指针及引用,或者用于声明(而不是定义)使用该类型作为形参类型或返回类型的函数。在创建类的对象之前,必须完整地定义该类。必须定义类,而不只是声明类,这样,编译器就会给类的对象预定相应的存储空间。同样地,在使用引用或指针访问类的成员之前,必须已经定义类。

  • 为类的成员使用类声明

只有当类定义已经在前面出现过,数据成员才能被指定为该类类型。如果该类型是不完全类型,那么数据成员只能是指向该类类型的指针或引用

因为只有当类定义体完成后才能定义类,因此类不能具有自身类型的数据成员。然而,只要类名一出现就可以认为该类已声明。因此,类的数据成员可以是指向自身类型的指针或引用:

class LinkScreen {
         Screen window;
         LinkScreen *next;//类大小不知道,所以不能定义类的对象;但一个指针的大小是固定的,所以可以定义不完全类的指针
         LinkScreen *prev;
     };

类的前身声明一般用来编写相互依赖的类

  • 何时使用this指针

尽管在成员函数内部显式引用 this 通常是不必要的,但有一种情况下必须这样做:当我们需要将一个对象作为整体引用而不是引用对象的一个成员时。最常见的情况是在这样的函数中使用 this:该函数返回对调用该函数的对象的引用

某种类可能具有某些操作,这些操作应该返回引用(即调用这个成员函数的对象本身),比如Screen 类只有一对 get 操作。逻辑上,我们可以添加下面的操作。

一对 set 操作,将特定字符或光标指向的字符设置为给定值。一个 move 操作,给定两个 index 值,将光标移至新位置。

理想情况下,希望用户能够将这些操作的序列连接成一个单独的表达式:

// move cursor to given position, and set that character
     myScreen.move(4,0).set('#');

这个语句等价于:

myScreen.move(4,0);
     myScreen.set('#');

在单个表达式中调用 move 和 set 操作时,每个操作必须返回一个引用,该引用指向执行操作的那个对象:

class Screen {
     public:
          // interface member functions
          Screen& move(index r, index c);
          Screen& set(char);
          Screen& set(index, index, char);
          // other members as before
     };

注意,这些函数的返回类型是 Screen&,指明该成员函数返回对其自身类类型的对象的引用。每个函数都返回调用自己的那个对象。使用 this 指针来访问该对象。下面是对两个新成员的实现:

Screen& Screen::set(char c)
     {
         contents[cursor] = c;
         return *this;
     }
     Screen& Screen::move(index r, index c)
     {
         index row = r * width; // row location
         cursor = row + c;
         return *this;
     }

函数中唯一需要关注的部分是 return 语句。在这两个操作中,每个函数都返回 *this。在这些函数中,this 是一个指向非常量 Screen 的指针。如同任意的指针一样,可以通过对 this 指针解引用来访问 this 指向的对象

  •  从const成员函数返回this

在普通的非 const 成员函数中,this 的类型是一个指向类类型的 const 指针。可以改变 this 所指向的值,但不能改变 this 所保存的地址。在 const 成员函数中,this 的类型是一个指向 const 类类型对象的 const 指针。既不能改变 this 所指向的对象,也不能改变 this 所保存的地址

不能从 const 成员函数返回指向类对象的普通引用。const 成员函数只能返回 *this 作为一个 const 引用。

例如,我们可以给 Screen 类增加一个 display 操作。这个函数应该在给定的 ostream 上打印 contents。逻辑上,这个操作应该是一个 const 成员。打印 contents 不会改变对象。如果将 display 作为 Screen 的 const 成员,则 display 内部的 this 指针将是一个 const Screen* 型的 const。

如果 display 是一个 const 成员,且它的返回类型是 const Screen&。这个设计存在一个问题。如果将 display 定义为 const 成员,就可以在非 const 对象上调用 display,但不能将对 display 的调用嵌入到一个长表达式中。下面的代码将是非法的:

Screen myScreen;
     // this code fails if display is a const member function
     // display return a const reference; we cannot call set on a const
     myScreen.display().set('*');

问题在于这个表达式是在由 display 返回的对象上运行 set。该对象是 const,因为 display 将其对象作为 const 返回。我们不能在 const 对象上调用 set。

而这样则是可行的:

 // move cursor to given position, set that character and display the screen
     myScreen.move(4,0).set('#').display(cout);

为了解决这个问题,我们必须定义两个 display 操作:一个是 const,另一个不是 const。基于成员函数是否为 const,可以重载一个成员函数;同样地,基于一个指针形参是否指向 const(而非指针本身是否是const),可以重载一个函数。const 对象只能使用 const 成员因为,在使用非const成员函数时,隐含的传递了一个参数this,它是一个指向非const对象的const指针,而当你定义一个const对象,调用一个非const成员函数时,这个成员函数的this指针指向的就是一个const对象,const对象要求不可以被改变,而this指针又是指向非const对象的指针,可以改变它所指向的对象,矛盾出现了。所以,如果const对象使用的是const成员函数,那么隐含传递的是一个指向const对象的const指针,这个指向const对象的const指针接收的就是一个const对象,会正常执行,所以,const对象不能调用非const成员函数非 const 对象可以使用任一成员,但非 const 版本是一个更好的匹配

  • 可变的数据成员

有时(但不是很经常),我们希望类的数据成员(甚至在 const 成员函数内)可以修改。这可以通过将它们声明为 mutable 来实现。可变数据成员(mutable data member)永远都不能为 const,甚至当它是 const 对象的成员时也如此。因此,const 成员函数可以改变 mutable 成员。要将数据成员声明为可变的,必须将关键字 mutable 放在成员声明之前:

class Screen {
     public:
     // interface member functions
     private:
         mutable size_t access_ctr; // may change in a const members
         // other data members as before
      };

我们给 Screen 添加了一个新的可变数据成员 access_ctr。使用 access_ctr 来跟踪调用 Screen 成员函数的频繁程度:

void Screen::do_display(std::ostream& os) const
     {
         ++access_ctr; // keep count of calls to any member function
         os << contents;
     }

尽管 do_display 是 const,它也可以增加 access_ctr。该成员是可变成员,所以,任意成员函数,包括 const 函数,都可以改变 access_ctr 的值。因此,mutable主要是用来在const函数中使用的、可以改变的数据成员

 

 

你可能感兴趣的:(类)