C++(7)类

第七章 类

  • 数据抽象(data abstraction)
    • 接口(interface)
    • 实现(implementation)
  • 封装(encapsulation)

定义抽象数据类型

数据成员+成员函数

成员函数
  • this是一个常量指针,总是指向“这个”对象。(成员函数中,隐式地使用this)
  • const成员函数:把const关键字放在成员函数的参数列表之后,表示this是一个指向常量的指针。

    //即this的默认类型为
    Class *const
    //加上const之后,变为:
    const Class *const
类作用域和成员函数
  • 定义在类内部的函数是隐式的inline函数。
  • 类本设备就是一个作用域。
  • 编译器分两步处理类:首先编译成员声明,然后才轮到成员函数体。
    • 所以即使数据成员定义在成员函数之后,成员函数仍然能使用数据成员。
类的外部定义成员函数
  • 类外部定义的成员名字必须包含它所属的类名。
返回this对象
return *this;

类相关的非成员函数

一般与类声明(而非定义)在同一个头文件内。

  • IO类型属于不能被拷贝的类型,作为参数时只能通过引用来传递他们。
  • print函数不负责换行:一般来说,执行输出任务的函数应该尽量减少对格式的控制。

构造函数(constructor)

类通过一个或几个特殊的成员函数来控制其对象的初始化过程。

  • 构造函数名字和类名相同
  • 构造函数没有返回值
  • 不能被声明为const
合成默认构造函数

当类没有声明任何构造函数时,编译器才会自动生成默认构造函数。

  • 如果类内包含有内置成员或者复合类成员(如指针和数组),只有当这些成员全都被赋予类内初始值时,这个类才适合于食用合成的默认构造函数。
  • 类中包含其他类的类型成员,且这个成员的类型没有默认构造函数,那么编译器将无法初始化该成员。
定义默认构造函数
ClassName() = default;  //C++11
构造函数初始值列表(constructor initialize list)

如下,冒号及冒号和花括号之间的代码:

Sales_data(const std::string &s, unsigned n, double p) :
           bookNo(s), units_sold(n), revenue(p*n) {}
  • 负责为新创建的对象的一个或几个数据成员赋初值。
  • 每个名字后面紧跟括号括起来的(或花括号内的)成员初始值。
类的外部定义构造函数

必须指明该构造函数时哪个类的成员。

拷贝、赋值和析构

  • 拷贝:初始化变量以及以值的方式传递或返回一个对象等。
  • 赋值:使用赋值运算符时会发生对象的赋值操作。
  • 析构:销毁对象。

访问控制与封装

使用访问说明符(access specifiers)加强类的封装性:

  • public:该说明符后的成员在整个程序内可被访问,public成员定义了类的接口。
  • private:该说明符后的成员可被类的成员函数访问,但不能被使用该类的代码访问。private部分封装了类的实现细节。
class或struct关键字

二者的默认访问权限不一样。

  • 若使用struct,定义在第一个访问说明符之前的成员是public的。
  • 若使用class,则定义在第一个访问说明符之前的成员是private的。
  • 出于统一编程风格考虑:当希望定义的类的所有成员是public的时,使用struct;否则使用class。

友元(friend)

当函数时类的接口部分,但它不是类的成员时,无法访问类的私有数据成员。

  • 类可以允许其他类或者函数访问它的非公有成员,方法是令其他类或者函数成为它的友元
  • 在类中增加一条以friend关键字开头的函数声明即可。
    • 只能出现在类定义内部
    • 在类中的具体位置不限(一般最好在类的开始或结束前的位置集中声明友元)
    • 友元不是类的成员,不受它所在的区域访问控制级别约束。
  • 友元的声明仅仅制定了访问权限,而非一个通常意义上的函数声明。如果希望类的用户能够调用某个友元函数,那么我们就必须在友元声明之外再专门对函数进行一次声明。

类的其他特性

  • 类型成员
  • 类的成员的类内初始值
  • 可变数据成员
  • 内联成员函数
  • 从成员函数返回*this
  • 如何定义并使用类类型及友元类

类成员

1.类型成员
class Screen {
public:
    typedef std::string::size_type pos;
    //等价于:
    using pos = std::string::size_type; //二选一即可
private:
    pos cursor = 0;
    pos height = 0, width = 0;
    std::string contents;
};

pos是一个类型成员

  • 用来定义类型的成员必须先定义后使用,这一点与普通成员有所区别。因此,通常出现在类开始的地方。
2.令成员作为内联函数
1. 可在类的内部把inline作为声明的一部分显式地声明成员函数,
2. 也能在类的外部用inline关键字修饰类的定义。
  • inline成员函数应该与相应的类定义在同一个头文件中。
3.重载成员函数

与非成员函数一样,成员函数也可以被重载。

4.可变数据成员

在变量的声明中加入mutable关键字
- 一个可变数据成员(mutable data member)永远不会是const,即使它是const对象成员。
- 因此,一个const成员函数可以改变一个可变成员的值。

5.类数据成员的初始值
  • C++11规定,可以为数据成员提供一个类内初始值(in-class initializer)。创建对象时,类内初始值将用于初始化数据成员。
  • 对类内初始值:或者放在花括号里,或者放在等号右边。不能使用圆括号。

返回*this的成员函数

  • 返回值使用引用(&),返回对象本身而非对象的副本。
  • 从const成员函数返回*this
    • 一个const成员函数如果以引用的形式返回*this,那么它的返回类型将是常量引用。
  • 基于const的重载
    • 通过区分成员函数是否是const的,可以对其进行重载,类似根据指针参数是否指向const(6.4节)而重载函数。

      class Screen {
      public:
          //根据对象是否是const重载display函数
          Screen &display(std::ostream &os)
                      { do_display(os); return *this; }
          const Screen &display(std::ostream &os) const
                      { do_display(os); return *this; }
      
      private:
          //该函数负责显示Screen的内容
          void do_display(std::ostream &os) const {os << contents;}
          //其他成员与之前版本一致
      }
    • 当一个成员调用另一个成员时,this指针在其中隐式地传递。

    • 对于公共代码使用私有功能函数
      • 避免在多处使用相同的代码。
      • 预期随着类的规模发展,display函数有可能变得更复杂。
      • 可能在开发过程中给do_display函数添加某些调试信息,最终产品发布时再去掉。
      • 这个额外的函数调用不会增加任何开销(它隐式地被声明为内联函数)。

类类型

Sales_data item1;       //默认初始化Sales_data类型的对象
//可以把类名跟在关键字class或struct后面
class Sales_data item1; //等价声明
  • 可以只声明类而暂时不定义它:

    class Screen;   //Screen类的声明
  • 类允许包含指向它自身类型的引用或指针。

友元再探

  • 可以把其他类定义成友元
    • 友元关系不能传递。
  • 也可以把其他类的成员函数定义成友元
    1. 必须先定义该函数所在的类,其中声明了该函数,但不能定义它。
    2. 接下来定义本类。
    3. 最后定义该函数,此时它才能使用本类的成员。
      • 把该函数声明成友元时必须明确指出该成员函数属于哪个类。
      • 友元函数能定义在类的内部,这样的函数是隐式内联的。
      • 友元的声明作用仅仅影响访问权
      • 所以即使定义在类内部,也必须在类的外部提供相应的声明从而使得函数可见。

类的作用域

在类的作用域之外:

  • 普通的数据和函数成员:由对象、引用或者指针使用成员访问运算符来访问。

    Screen scr(ht, wd, ' ');    //创建Screen对象scr并初始化
    Screen *p = &scr;
    char c = sca.get(); //访问scr对象的get成员
    c = p->get();       //访问p所指对象的get成员
  • 类类型成员:使用作用域运算符访问。

    Screen::pos ht = 24, wd = 80;   //使用Screen定义的pos类型
  • 定义在类外部的成员:必须同时提供类名和函数名。

    • 原因是:一旦遇到类名,定义的剩余部分(包括参数列表和函数体)就在类的作用域之内了,就可以直接使用类的其他成员而无需再次授权。
    • 返回类型在类名之前,所以位于作用域之外。若要使用类定义的类型作为返回类型,必须明确指定类。
    class Window_mgr {
    public:
        //向窗口添加一个Screen,返回它的编号
        ScreenIndex addScreen(const Screen&);
        //其他成员与之前一致
    };
    //首先处理返回类型,之后才进入Window_mgr的作用域
    Window_mgr::ScreenIndex
    Window_mgr::addScreen(const Screen &s)
    {
        screens.push_back(s);
        return screens.size() - 1;
    }
名字查找与类的作用域
  • 类中首先编译成员声明,直到类全部可见后才编译函数体。
  • 一般来说,内层作用于可以重新定义外层作用域的名字,但类型名不允许

构造函数再探

构造函数初始值列表

  • 初始化(初始值列表,冒号和花括号之间)
  • 赋值:函数体内利用=赋值(不建议)
    1. 如果成员是const或者引用,必须将其初始化
    2. 如果成员属于某种类型且该类型没有定义默认构造函数是,也必须将其初始化。
  • 成员初始化顺序与它们在类定义中出现的顺序一致
  • 构造函数提供默认实参,相当于默认构造函数。

委托构造函数(delegating constructor)

C++11新标准:一个委托构造函数使用它所属的类的其他构造函数执行它自己的初始化过程,或者说把它自己的一些(或全部)职责委托给了其他构造函数。

class Sale_data {
public:
    Sales_Data(srd::string s, unsigned cnt, double price):
            bookNo(s), units_sold(cnt), revenue(cnt*price) { }
    //以下为委托构造函数
    Salea_data() : Sales_data(" ", 0, 0) { }
    Salea_data(srd::string s) : Sales_data(s, 0, 0) { }
    Salea_data(srd::istream &is) : Sales_data() { read(is, *this); }
}
  • 如果受委托构造函数体有代码,将先执行这些代码,然后控制权才会交还给委托者的函数体。

隐式的类类型转换

string null_book = "9-999-99999-9";
item.combine(null_book);
//构造一个临时的Sales_data对象
//只允许一部累类型转换
item.combine("9-999-99999-9");  //错误!
item.combine(string("9-999-99999-9"));  //正确
item.combine(Sales_data("9-999-99999-9"));  //正确
  • 可将构造函数声明为explicit阻止隐式转换。
    • 只对一个实参的构造函数有效。
    • 只能在类内声明构造函数时使用。
  • 为转换显式地使用构造函数

    item.combine(Sales_data(null_book));
    item.combine(static_cast<Sales_data>(cin)); //使用static_cast执行强制转换
聚合类(aggregate class)
  • 所有成员都是public的。
  • 没有定义任何构造函数。
  • 没有类内初始值。
  • 没有基类,也没有virtual函数。
字面值常量类
  1. 数据成员都是字面值类型的聚合类。
  2. 如果不是聚合类:
    • 数据成员都是字面值类型。
    • 类至少含有一个constexpr构造函数。
    • 若数据成员含有类内初始值:内置类型成员,初始值为常量表达式;类类型,初始值使用成员自己的constexpr构造函数。
    • 必须使用析构函数的默认定义。

类的静态成员

类的一些成员与类本身直接相关,而不是与类的各个对象保持关联。

  • 成员声明之前加上关键字static使得其与类关联在一起。
  • 静态成员函数也不与任何对象绑定在一起,它们不包含this指针。
使用类的静态成员
  • 使用作用域运算符直接访问静态成员
  • 仍然可以使用类的对象、引用或指针来访问。
  • 成员函数不应通过作用域运算符就能直接使用静态成员。
定义静态成员
  • 既可以在类的内部也可以在外部定义静态成员函数。外部定义时,不能重复static关键字。
  • 对于静态数据成员:必须在类的外部定义和初始化。
  • 若类内初始化静态成员:该成员必须是字面值常量类型constexpr,初始值必须为常量表达式。
使用静态成员
  • 静态成员可以是类类型,而普通成员只能是类类型的指针或引用。
  • 静态成员可用作默认实参。

参考:C++Primer第五版

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值