c++ primer 学习笔记21类的定义和声明、隐含的this指针、类作用域

来源:互联网 发布:知乎 欧美系护肤品 编辑:程序博客网 时间:2024/05/16 07:08

 类的定义和声明 

在 public 部分定义的成员可被使用该类型的所有代码访问;在 private 部分定义的成员可被其他类成员访问。

构造函数 
创建一个类类型的对象时,编译器会自动使用一个构造函数来初始化该对象。构造函数是一个特殊的、与类同名的成员函数,用于给每个数据成员设置适当的初始值。

Sales_item(): units_sold(0), revenue(0.0) { } 

成员函数 

在类内部,声明成员函数是必需的,而定义成员函数则是可选的。在类内部定义的函数默认为 inline(第 7.6 节)。 
在类外部定义的成员函数必须指明它们是在类的作用域中。 

将关键字 const 加在形参表之后,就可以将成员函数声明为常量: 
double avg_price() const; 
 
const 成员不能改变其所操作的对象的数据成员。const 必须同时出现在声明和定义中,若只出现在其中一处,就会出现一个编译时错误。

访问标号实施抽象和封装 
在 C++ 中,使用访问标号(第 2.8 节)来定义类的抽象接口和实施封装。
一个类可以没有访问标号,也可以包含多个访问标号: 
•  程序的所有部分都可以访问带有 public 标号的成员。类型的数据抽象视图由其 public 成员定义。 
•  使用类的代码不可以访问带有 private 标号的成员。private 封装了类型的实现细节。 
一个访问标号可以出现的次数通常是没有限制的。每个访问标号指定了随后的成员定义的访问级别。这个指定的访问级别持续有效,直到遇到下一个访问标号或看到类定义体的右花括号为止。

   可以在任意的访问标号出现之前定义类成员。在类的左花括号之后、第一个访问标号之前定义成员的访问级别,其值依赖于类是如何定义的。如果类是用 struct 关键字定义的,则在第一个访问标号之前的成员是公有的;如果类是用class 关键字是定义的,则这些成员是私有的。 

显式指定 inline 成员函数 
在类内部定义的成员函数,将自动作为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;      } 
 类声明与类定义 

将类定义在头文件中,可以保证在每个使用类的文件中以同样的方式定义类。使用头文件保护符(header guard)(第 2.9.2 节),来保证即使头文件在同一文件中被包含多次,类定义也只出现一次。

 类对象 
定义一个类时,也就是定义了一个类型。一旦定义了类,就可以定义该类型的对象。定义对象时,将为其分配存储空间,但(一般而言)定义类型时不进行存储分配: 
     class Sales_item { 
     public: 
         // operations on Sales_item objects 
     private: 
         std::string isbn; 
         unsigned units_sold; 
         double revenue; 
     }; 
 
定义了一个新的类型,但没有进行存储分配。当我们定义一个对象 
     Sales_item item; 
 
时,编译器分配了足以容纳一个 Sales_item 对象的存储空间。

为什么类的定义以分号结束 
我们在第 2.8 节中指出,类的定义分号结束。分号是必需的,因为在类定义之后可以接一个对象定义列表。定义必须以分号结束: 
class Sales_item { /* ... */ }; 
class Sales_item { /* ... */ } accum, trans; 
 
 通常,将对象定义成类定义的一部分是个坏主意。这样做,会使所发生的操作难以理解。对读者而言,将两个不同的实体(类和变量)组合在一个语句中,也会令人迷惑不解。 

 隐含的 this 指针 
成员函数具有一个附加的隐含形参,即指向该类对象的一个指针。这个隐含形参命名为 this,与调用成员函数的对象绑定在一起。成员函数不能定义 this 形参,而是由编译器隐含地定义。成员函数的函数体可以显式使用 this 指针,但不是必须这么做。如果对类成员的引用没有限定,编译器会将这种引用处理成通过 this 指针的引用。

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

基于 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; 
     } 

使用类的成员 
在类作用域之外,成员只能通过对象或指针分别使用成员访问操作符 . 或 -> 来访问。这些操作符左边的操作数分别是一个类对象或指向类对象的指针。
跟在操作符后面的成员名字必须在相关联的类的作用域中声明: 
     Class obj;     // Class is some class type 
     Class *ptr = &obj; 
     // member is a data member of that class 
     ptr->member;   // fetches member from the object to which ptr points 
     obj.member;    // fetches member from the object named obj 
     // memfcn is a function member of that class 
     ptr->memfcn(); // runs memfcn on the object to which ptr points 
     obj.memfcn();  // runs memfcn on the object named obj 
 
一些成员使用成员访问操作符来访问,另一些直接通过类使用作用域操作符(::)来访问。一般的数据或函数成员必须通过对象来访问。定义类型的成员,如 Screen::index,使用作用域操作符来访问。 

作用域与成员定义 
尽管成员是在类的定义体之外定义的,但成员定义就好像它们是在类的作用域中一样。回忆一下,出现在类的定义体之外的成员定义必须指明成员出现在哪个类中: 
     double Sales_item::avg_price() const 
     { 
         if (units_sold) 
             return revenue/units_sold; 
         else 
             return 0; 
     } 

形参表和函数体处于类作用域中 
在定义于类外部的成员函数中,形参表和成员函数体都出现在成员名之后。这些都是在类作用域中定义,所以可以不用限定而引用其他成员。例如,类 Screen 中 get 的二形参版本的定义: 
     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 
     } 

函数返回类型不一定在类作用域中 
与形参类型相比,返回类型出现在成员名字前面。如果函数在类定义体之外定义,则用于返回类型的名字在类作用域之外。如果返回类型使用由类定义的类型,则必须使用完全限定名。例如,考虑 get_cursor 函数: 
     class Screen { 
     public: 
         typedef std::string::size_type index; 
         index get_cursor() const; 
     }; 
     inline Screen::index Screen::get_cursor() const 
     { 
         return cursor; 
     } 

通常,名字必须在使用之前进行定义。而且,一旦一个名字被用作类型名,该名字就不能被重复定义: 
     typedef double Money; 
     class Account { 

 public: 

 Money balance() { return bal; } // uses global definition of 
Money 
     private: 
         // error: cannot change meaning of Money 
         typedef long double Money; 
         Money bal; 
         // ... 
     }; 
 
类成员定义中的名字查找 
按以下方式确定在成员函数的函数体中用到的名字。 
1.  首先检查成员函数局部作用域中的声明。 
2.  如果在成员函数中找不到该名字的声明,则检查对所有类成员的声明。 
3.  如果在类中找不到该名字的声明,则检查在此成员函数定义之前的作用域中出现的声明。 

下面的函数使用了相同的名字来表示形参和成员,这是通常应该避免的。

 // Note: This code is for illustration purposes only and reflects 
bad practice 
     // It is a bad idea to use the same name for a parameter and a member 
     int height; 
     class Screen { 
     public: 
         void dummy_fcn(index height) { 
             cursor = width * height; // which height? The parameter 
         } 
     private: 
         index cursor; 
         index height, width; 

 }; 
 
查找 dummy_fcn 的定义中使用的名字 height 的声明时,编译器首先在该函数的局部作用域中查找。函数的局部作用域中声明了一个函数形参。dummy_fcn 的函数体中使用的名字 height 指的就是这个形参声明。 

在本例中,height 形参屏蔽名为 height 的成员。 
 
尽管类的成员被屏蔽了,但仍然可以通过用类名来限定成员名或显式使用 this 指针来使用它。

函数作用域之后,在类作用域中查找 
如果想要使用 height 成员,更好的方式也许是为形参取一个不同的名字: 
     // good practice: Don't use member name for a parameter or other local 
variable 
     void dummy_fcn(index ht) { 
         cursor = width * height; // member height 
     } 

类作用域之后,在外围作用域中查找 

如果编译器不能在函数或类作用域中找到,就在外围作用域中查找。在本例子中,出现在 Screen 定义之前的全局作用域中声明了一个名为 height 的全局声明。然而,该对象被屏蔽了。 
 
尽管全局对象被屏蔽了,但通过用全局作用域确定操作符来限定名字,仍然可以使用它。 
 
 
     // bad practice: Don't hide names that are needed from surrounding scopes 
     void dummy_fcn(index height)

    { 
         cursor = width * ::height;// which height? The global one 
     } 
 
 
 

0 0
原创粉丝点击