c++primer 第7章 类

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

第7章 类

  • 使用类定义自己的数据类型
  • 数据抽象
    • 将对象的具体实现与对象所能执行的操作分离开来
    • 依赖于接口和实现分离的编程技术
      • 接口用户所能执行的操作
      • 实现类的数据成员、负责接口实现的函数体以及定义类所需的各种私有函数
  • 封装
    • 实现类的接口和实现的分离
    • 影藏实现细节用户只能访问接口无法访问实现
  • 抽象数据类型
    • 类的设计者考虑类的实现过程
    • 使用类的程序员抽象思考类型做了什么无须了解类型工作细节

7.1 定义抽象数据类型

  • Sales_item类 抽象数据类型 通过接口使用对象 不能访问类数据成员
  • Sales_data类 不是抽象数据类型 允许用户直接访问数据成员->封装隐藏数据成员 变成抽象数据类型

7.1.1 设计Sales_data类

  • 定义成员函数先将其定义为普通函数执行复合赋值运算
  • 用户 不同语境决定了不同的含义
    • 用户代码或类的使用者 程序员
    • 应用程序使用者 如书店经理
    • 设计类接口时应考虑使类易于使用
    • 使用类时不应顾及类的实现机理
    • 开发应用程序须充分了解用户需求
    • 类设计者应关注使用类的程序员的需求
    • 良好设计的类直观易于使用的接口高效的实现过程
  • 使用改进的Sales_data类

7.1.2 定义改进的Sales_data类

  • 定义在类内部的函数隐式内联inline
  • 定义成员函数
    • 所有成员须在类内部声明成员函数体可定义在类内或类外
  • 引入this
    • 调用成员函数实际是在替某个对象调用它
    • 成员函数通过this隐式参数访问调用它的对象
    • 调用成员函数用请求该函数的对象地址初始化this
    • 成员函数内部可直接使用该对象成员无须成员访问运算符对类成员的直接访问看作this的隐式调用
    • 任何自定义名为this的参数或变量行为都是非法的
    • this总是指向调用该成员函数的对象是一个常量指针不允许改变this保存的地址
  • 引入const成员函数
    • const 修改隐式this指针的类型
    • 默认情况下this指向类类型非常量版本的常量指针意味着不能将this绑定到常量对象上故不能在常量对象上调用普通成员函数
    • 无须修改数据成员时将this设置为指向常量的指针有助于提高函数灵活性
    • 常量成员函数
      • 将const关键字放在成员函数的参数列表后表示this是一个指向常量的指针
      • 不能改变调用它的对象的内容可以读取数据成员不能写入新值
      • 常量对象/常量对象的引用或指针都只能调用常量成员函数
  • 类作用域和成员函数
    • 类本身是一个作用域类成员函数的定义嵌套在类的作用域内
    • 编译器分两步处理类
        1. 先编译成员函数声明
        1. 才轮到成员函数体
      • 故成员函数体可随意使用类成员其他函数无须在意成员出现次序
    • 在类的外部定义成员函数
      • 定义必须与声明匹配
        • 返回类型/形参列表/函数名都需与类内部声明一致
      • 被声明为常量成员函数定义也须在参数列表后指定const属性
      • 类外部定义的成员名字必须包含它所属的类名
      • 使用作用域运算符::说明该函数被声明在类内部剩余代码是位于内的作用域内的
    • 定义一个返回this对象的指针
      • 当定义函数类似于某个内置类型运算符时应令该函数行为尽量模仿该运算符
      • 返回对象引用类型需将调用函数对象当成一个整体访问
        • return *this; 解引用this指针以获得执行该函数的对象

7.1.3 定义类相关的非成员函数

  • 定义辅助函数概念上属于类接口组成部分实际上不属于类本身
  • 非成员函数。通常将函数声明和定义分离开来
  • 如果非成员函数是类的接口组成部分函数声明应与类在同一个头文件内
  • 定义read和print函数
    • io类属于不能被拷贝的类型只能通过引用来传递他们
    • 读取和写入操作会改变流的内容故两函数都接受普通引用
  • 定义add函数
    • 默认情况下拷贝类的对象拷贝的是对象的数据成员

7.1.4 构造函数

  • 构造函数定义类对象被初始化的方式控制对象初始化过程初始化类对象数据成员
  • 只要类的对象被创建就会执行构造函数
  • 构造函数名字与类名相同没有返回类型也有一个参数列表和函数体
  • 类可包含多个构造函数与其重载函数差不多形参数量或类型须有区别
  • 构造函数不能被声明为const。当创建类const对象时直到构造函数完成初始化过程对象才真正取得"常量"属性。构造函数在const对象构造过程中可以向其写值。
  • 合成的默认构造函数
    • 如果类没有显式定义构造函数编译器会隐式定义一个默认构造函数
    • 合成的默认构造函数初始化规则
      • 若存在类内初始值用其来初始化成员
      • 否则默认初始化该成员
        • 默认初始化定义时没指定初值变量被默认初始化内置类型定义于函数体外初始化为0定义于函数体内则不被初始化。建议初始化每一个内置类型变量
  • 某些类不能依赖于合成的默认构造函数
    • 普通类必须定义自己的默认构造函数原因有三
        1. 编译器只会在我们没有声明任何构造函数时会隐式定义一个默认构造函数一旦定义了其他构造函数除非再定义一个默认构造函数否则类没有默认构造函数
        1. 合成的默认构造函数可能执行错误的操作。定义在块中的内置类型算术类型字符 整型 浮点型 布尔型空类型/复合类型数组和指针的对象被默认初始化则他们的值是未定义的同样适用于默认初始化内置类型成员。只有当类 内置/复合类型的成员全部都被赋予类内初始值时该类才适用于合成的默认构造函数
        1. 有时编译器不能为某些类合成默认构造函数。如类中包含一个其他类类型成员且该成员类型没有默认构造函数编译器将无法初始化该成员。必须自定义默认构造函数否则该类将没有可用的默认构造函数。
  • 定义Sales_data的构造函数
  • =default的含义
    • ales_data()=default;一个不接受任何实参的默认构造函数
    • 定义目的既需要其他形式的构造函数也需要默认构造函数希望该默认构造函数作用完全等同于合成默认构造函数
    • =default既可以和声明一起出现在类的内部内联也可以作为定义出现在类的外部
    • 若编译器不支持类内初始值默认构造函数应该使用构造函数初始值列表来初始化类的每个成员
  • 构造函数初始值列表
    • 负责为新创建的对象的一个或几个数据成员赋值如
Sales_data(const std::string &s):bookNo(s) {}
Sales_data(const std::string &s,unsigned n,double p):
           bookNo(s),uits_sold(n),revenue(p*n) {}
  • 当某个数据成员被构造函数初始值列表忽略时它将以合成默认构造函数相同的方式隐式初始化如使用类内初始值
  • 若不能使用类内初始值则所有构造函数都应该显式地初始化每个内置类型的成员
  • 在类的外部定义构造函数
    • 构造函数无返回类型定义从指定函数名开始类外部定义构造函数须指明构造函数是哪个类的成员

7.1.5 拷贝、赋值和析构

  • 拷贝初始化变量以及以值的方式传递或返回一个对象
  • 赋值使用赋值运算符
  • 销毁对象不存在时执行销毁操作局部对象会在创建它的块结束时被销毁vector对象被销毁时存储在其中的对象也会被销毁
  • 若不主动定义拷贝、赋值和析构操作编译器会替我们合成它们
  • 某些类不能依赖于合成的版本
    • 当类需要分配类对象之外的资源时管理动态内存的类
    • 使用vector或string类能避免分配和释放内存带来的复杂性拷贝、赋值和析构的合成版本能正常工作。对含vector成员的对象执行拷贝、赋值和析构时会设法拷贝或赋值成员中的元素对象被销毁时将销毁vector对象依次销毁vector中的每一个元素
    • 类中所分配的资源都应该直接以类的数据成员的形式存储

7.2 访问控制与封装

  • 访问说明符加强类的封装性
    • public说明符后的成员在整个程序内都可以被访问public成员定义类的接口
    • private说明符之后的成员可以被类的成员函数访问但不能被使用该类的代码访问private部分封装隐藏类的实现过程
  • 再次定义Sales_data类
    • public接口部分构造函数和部分成员函数
    • private实现部分数据成员
  • 一个类可定义0/多个访问说明符每个说明符出现次数也没有严格限定每个说明符指定接下来的成员的访问级别有效范围直到下一个访问说明符或者到达类的结尾为止
  • 使用class或struct关键字
    • 唯一区别class和struct的默认访问权限不太一样
    • struct定义在第一个说明符之前的成员是public的
    • class定义在第一个说明符之前的成员是private的
    • 出于统一编程风格当定义的类的所有成员是public时使用struct;当定义的类的所有成员是private时使用class

7.2.1 友元

  • 类允许友元其他类或者函数访问它的非公有成员只需增加一条friend关键字开始的函数声明
  • 友元声明只能出现在类定义的内部但出现位置不限友元不是类的成员也不受所在区域访问控制级别约束
  • 但一般最好在类定义开始或结束前的位置集中声明友元
  • 封装的益处
    • 确保用户代码不会无意间破环对象状态
    • 数据成员定义为private的类作者可以较为自由地修改数据只要类的接口不变用户代码就无须改变
    • 将查错限制在有限范围内极大降低维护代码以及修成程序错误地难度
    • 当类的定义发生改变无须更改用户代码但使用该类的源文件须重新编译
  • 友元地声明
    • 仅指定访问权限而非通常意义的函数声明类成员调用友元函数前须在友元声明外再次声明
    • 为使友元对类成员可见通常将友元声明与类本身放置在同一个头文件中
    • 编译器未强制限定友元函数须在使用之前在类的外部声明一些编译器允许在尚无友元函数的初始声明的情况下就调用它

7.3 类的其他特性

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

7.3.1 类成员再探

  • 定义一个类型成员
    • 自定义某种类型在类中的别名与其他成员一样存在访问限制public/private
    • 用来定义类型的成员必须先定义后使用因此类型成员通常出现在类开始的地方
  • Screen类的成员函数
  • 令成员作为内联函数
    • 定义在类内部的成员函数自动inline
    • 可以在类内部作为声明的一部分显示声明成员函数也能在类的外部用inline关键字修饰函数的定义
    • 虽无须在声明和定义同时说明inline但合法最好只在类外部定义的地方说明inline使类更容易理解
    • 和在头文件中定义inline函数原因一样编译器想展开函数不仅需要函数声明还需要函数定义且多个定义须完全一致inline成员函数也应该与相应的类定义在同一个头文件中
  • 重载成员函数
    • 参数数量/类型上有所区别
  • 可变数据成员
    • 即使在const成员函数内也能修改某个数据成员通过在变量声明前加mutable关键字
    • 可变数据成员永远也不会是const即使它是const对象的成员可用于记录const成员函数被调用的次数
  • 类数据成员的初始值
    • 类内初始值必须用=的初始化形式
    • 或花括号括起来的直接初始化形式

7.3.2 返回*this的成员函数

  • 定义返回为引用时返回对象本身而非对象副本返回类型为非引用时返回*this的副本
  • 从const成员函数返回*this
    • 一个const成员函数如果以引用的形式返回*this那么它的返回类型将是常量引用
  • 基于const的重载
    • 通过成员函数是否是const的可对其进行重载原因与根据指针参数是否指向const而重载差不多非常量版本函数对常量对象不可用只能在常量对象上调用const成员函数非常量对象可调用常量/非常量版本但非常量版本是更好的匹配根据对象是否是const决定了应该调用的函数版本
    • 当一个成员调用另一个成员是this指针在其中隐式传递
  • 对于公共代码使用私有功能函数
    • 避免在多处使用同样的代码
    • 类规模发展函数变得更复杂将相同操作写在一处较好
    • 可能在函数中添加某些调试信息在一处添/删更容易
    • 额外调用不增加开销因为在类内部定义隐式地被声明为内联函数

7.3.3 类类型

  • 每个类定义唯一类型两个类即使成员(列表)完全一样两个类也是不同的类型
  • 一个类的成员和其他任何类或任何其他作用域地成员都不是一回事
  • 可直接使用类名作为类型名也可把类名跟在关键字class或struct后面
  • 类的声明
    • 前向声明仅仅声明类而暂时不定义它
    • 不完全类型声明之后定义之前
    • 啊我打字真的好慢呀这样做笔记到底有没有必要呢再说吧剩下的下次接着写
阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: c++