[Note-sub] C++ primer Char.7 类

209 阅读4分钟

访问控制和封装

  • 访问说明符可加强类的封装性
    • 定义在public说明符后的成员在整个程序内可被访问,即public定义接口
    • 定义在private说明符后的成员只能在类内被访问,即private封装实现
  • 一个类可包含0个或多个访问说明符,对它们的出现次数无限定。
  • 每个访问说明符指定了接下来的成员的访问级别,其有效范围直到出现下一个访问说明符或到达类结尾
  • struct定义类,则定义在第一个访问说明符之前的成员默认是public。用class定义类,则定义在第一个访问说明符之前的成员默认是private。struct和class的唯一区别是默认访问权限

友元

  • 友元:类可允许其他类或函数访问它的非公有成员,方法是令其他类或函数成为它的友元
  • 要把一个函数声明为类的友元,只需在类中加一条friend关键字开头的函数声明。
  • 友元声明必须在类的内部,但最好在类的开始或结束处集中声明
  • 友元不是类的成员,也不受它所在的区域的访问控制级别的约束
  • 封装的好处:
  • 确保用户代码不会无意间破坏类的状态
  • 被封装的类的细节可随时改变。只要接口不变就不需要修改用户代码
  • 友元的声明仅指定访问权限,不是通常意义的声明。如果要使用,还需要在类外独立声明一次。
  • 为使友元对类的用户可见,通常把友元的独立声明和类的声明放在同一头文件 实例:
#include <iostream>
using namespace std;
class Box
{
   double width;
public:
   friend void printWidth( Box box );
   void setWidth( double wid );
};
// 成员函数定义
void Box::setWidth( double wid )
{
    width = wid;
}
// 请注意:printWidth() 不是任何类的成员函数
void printWidth( Box box )
{
   /* 因为 printWidth() 是 Box 的友元,它可以直接访问该类的任何成员 */
   cout << "Width of box : " << box.width <<endl;
}
// 程序的主函数
int main( )
{
   Box box;
   // 使用成员函数设置宽度
   box.setWidth(10.0);
   // 使用友元函数输出宽度
   printWidth( box );
   return 0;
}

类的其他特性

类成员再探

  • 类可以自定义某种类型在其中的别名,这些类型别名和其他名字一样受访问控制的约束
  • 用于定义类型的类型成员必须先定义后使用,这与普通成员不一样。因此类型成员通常出现在类开始处
  • 定义在类内的成员函数是隐式inline函数,在类外定义的成员函数也可使用inline关键字显式定义为inline函数
  • 可以但没必要在成员函数声明和定义处都说明inline,最好只在类外定义时才用
  • inline成员函数可重复定义,只需多个定义相同。因此应和类定义在同一头文件中
  • 成员函数可重载,其匹配过程类似于非成员函数的匹配
  • 可变数据成员:这种成员永远不会是const,即使是在const对象内。只需在成员声明前加mutable关键字
  • const成员函数可修改mutable数据成员
  • 类内初始值:如果构造函数初值列表中无此成员,但该成员在类内声明时提供了一个初始值,则该成员被初始化为该初始值。
  • 类内初始值只能用={}的初始化形式,不可用(),因为会和成员函数声明混淆。

返回*this的成员函数

  • 如果返回*this的成员函数其返回类型是引用,则返回的是对象本身而不是副本,是左值
  • 如果返回*this的成员函数其返回类型不是引用,则返回类型是*this的副本,是对象的拷贝
  • 若const成员函数以引用形式返回*this,则其返回的是常量引用。因为传入const成员函数的*this就是指向常量的指针
  • 通过区分成员函数是否是const的,可对其重载。原因是传入的隐式形参*this有底层const的差异
  • 将返回*this的const成员函数重载为非const成员函数是有必要的,因为非const对象调用它时希望返回一个非const引用
  • const对象上只能调用const成员函数,非const对象优先调用非const成员函数

友元再探

  • 类可将其他类、其他类的成员函数定义为友元。
  • 友元可被定义在类内部,这时是内联的
  • 如果一个类指定了友元类,则友元类的成员函数都可访问此类包括private成员在内的所有成员
  • 友元关系没有传递性,即A是B的友元,B是C的友元,这时A并不是C的友元
  • 可把另一个类的成员函数作为友元,只是要用类名指定作用域
  • 如果一个类要把一组重载函数声明为友元,则需对每一个函数分别声明才行
  • 类和非成员函数的声明并不需要在友元声明之前。当一个名字第一次出现在友元声明中时,隐式假定该名字在当前作用域中可见。但友元不一定真的要声明在当前作用域中。
  • 但类的成员函数的声明必须在友元声明之前(因为需要类提供作用域?)。即,如果类A的成员函数f是类B的友元,则声明顺序为:
    1. 定义A类并在其中声明成员函数f,但此时不能定义。因为定义需要用到B的成员。
    2. 定义B类并在其中声明A::f为友元
    3. 定义A::f,这时它可使用B的成员
  • 友元声明的作用是影响访问权限,它不是普通意义上的声明
  • 即使友元函数在类的内部被定义了,它也必须在类外独立声明,这样才能可见。

类的静态成员

  • 类的静态成员只与类本身相关,与其任何对象都无关。形式是在成员声明前加static关键字
  • 静态成员可以是public或private,类型可是常量、引用、指针、类类型等
  • 类的静态成员存在于任何对象之外,任何对象中都不包含与之相关的数据
  • 静态成员不与任何对象绑定,故不存在this指针。因此既不能在函数体内使用this指针,也不能被声明为const成员函数
  • 静态成员函数调用非静态成员时,并不知道是哪个对象的成员
  • 可用类的作用域运算符直接访问静态成员,也可用类的对象、引用、指针来访问静态成员
  • 成员函数不用通过作用域运算符就可访问静态成员
  • 静态成员函数可在类内或类外定义,在类外定义时不可重复static关键字,static只出现在声明中。
  • 静态数据成员并非在创建类时被定义,因此静态数据成员不由构造函数初始化
  • 不能在类内部初始化静态数据成员,静态数据成员必须在类外定义和初始化,一个静态数据成员只能被定义一次
  • 静态数据成员定义在任何函数之外,一旦被定义就存在于程序整个生命周期
  • 为确保静态数据成员只被定义一次,最好将其定义与其他非内联函数的定义放在同一头文件
  • 静态成员函数可在类内和类外定义,静态数据成员只能在类外定义和初始化
//声明静态成员
class Account{
public:
    void calculate() {amount+=amount*interestRate;}
    static double rate() {return interestRate;} //静态成员函数,它可在类内也可在类外定义
    static void rate(double);                   //静态成员函数
private:
    string owner;
    double amount;
    static double interestRate;                 //静态成员变量
    static double initRate();                   //静态成员函数
};
//定义静态成员
void Account::rate(double newRate){         //定义静态成员函数,它可在类内也可在类外定义
    interestRate=newRate;
}
double Account::interestRate=initRate();    //定义静态成员变量,它只能在类外定义和初始化
//访问静态成员
double r;
r=Account::rate();  //通过作用域访问
Account ac1;
Account *ac2=&ac1;
r=ac1.rate();       //通过类引用访问
r=ac2->rate();      //通过类指针访问
  • 通常,类的静态数据成员不应在类内初始化。特例是,可为静态数据成员提供const整型的类内初始值,且该静态数据成员必须是constexpr类型,初值必须是常量表达式。它们可用到任何需要常量表达式的地方
  • 例子:类内初始化的静态数据成员必须是字面值常量类型的constexpr
class Account{
public:
    static double rate(){return interestRate;}
    static void rate(double);
private:
    static constexpr int period=30; //常量表达式
    double daily_tbl[period];       //可用于需要常量表达式的地方
};
  • 通常,类的静态数据成员不应在类内初始化特例是,可为静态数据成员提供const整型的类内初始值,且该静态数据成员必须是constexpr类型,初值必须是常量表达式。它们可用到任何需要常量表达式的地方
class Account{
public:
    static double rate(){return interestRate;}
    static void rate(double);
private:
    static constexpr int period=30; //常量表达式
    double daily_tbl[period];       //可用于需要常量表达式的地方
};
  • 如果类内部提供了一个初值,则成员定义不可再提供初值。
  • 即使一个常量静态数据成员在类内被初始化了,通常也应在类外部定义一下(不提供初值)
  • 在某些非静态成员非法的场合,静态成员可正常使用,如静态成员可以是不完全类型,特别的,静态数据成员的类型可以是他所属的类类型。而非静态数据成员只能被声明为它所属的类的指针或引用
  • 例子:静态数据成员的类型可以是他所属的类类型
class Bar{
    static Bar mem1;    //对,静态成员可以是不完全类型
    Bar *mem2;          //对,指针可以是不完全类型
    Bar mem3;           //错,数据成员必须是完全类型
};
  • 可用静态成员做默认实参,非静态不可,因为它的值属于对象的一部分,读到声明时类未完全定义(编译顺序的原因:静态先于非静态)