Primer 第七章 类

基本思想

数据抽象(data abstraction)

依赖于接口(interface)和实现(implementation)分离的编程技术

  • interface
    包括用户所能执行的操作
  • implementation
    包括类的数据成员、负责接口实现的函数体以及定义类所需的各种私有函数

封装(encapsulation)

实现类的接口和实现的分离
首先定义一个 抽象数据类型(abstract data type)
定义在类内部的函数是隐式的inline函数

成员函数通过一个名为this的额外的隐式参数来访问调用它的那个对象
当调用一个成员函数时,用请求该函数的对象地址初始化 this
this是类的常量指针,具体类型为函数类型
可以在参数列表后使用const关键字来修改隐式this指针的类型(即常量成员函数const member function)
伪代码 对this指针的理解std::string Sales_data::isbn(const Sales_data *const this) {return this->isbn;}
编译器首先编译成员的声明,才编译成员函数体

类的外部定义成员函数

  • 定义与声明匹配
  • 类外部定义的成员的名字必须包含所属的类名
    • eg
      • double Sales_data::avg_price() const{ }
  • 如果函数在概念上属于类但是不定义在类中,则一般应与类声明在同一个头文件中 (类接口的非成员组成部分)

constructor(构造函数)

  • :类通过的一个或几个来控制其对象的初始化过程的特殊成员函数   
    
    • 构造函数的名字和类名相同
    • 构造函数没有返回类型
    • 有一个(可能为空的)参数列表和一个(可能为空的)函数体
    • 类中可以有多个构造函数(类似于其他函数重载)
    • 构造函数不能被声明为const
      • 类中const对象在获得“常量”属性前,先被构造函数初始化
      • 构造函数在const对象的构造过程中可以向其写值
        类通过一个特殊的构造函数来控制默认初始化过程
  • default constructor(默认构造函数)
    • 若无显式定义,编译器将会隐式定义
      • synthesized default constructor(合成的默认构造函数)
        • 规则
          • 如果存在类内初始值,则使用
          • 否则,默认初始化
  • 必须定义默认构造函数
    1. 编译器只有在发现类中不包含任何构造函数的情况下才会生成一个默认的构造函数
    2. synthesized default constructor可能执行错误的操作
    3. 有的编译器不能为某些类合成默认的构造函数(类成员交错的时候,难以识别)

= default

需要默认的行为,通过在参数列表后面写上 = default 来要求编译器生成构造函数

  • = default 既可以和声明一起出现在类的内部(默认构造函数内联)
  • = default 也可以作为定义出现在类的外部(该成员默认情况下不是内联的)

copy constructor(拷贝构造函数)

构造函数初始值列表contructor initialize list

  • eg Sales_data(const std::string &s):bookNo(s) {}
  • template className(parameters):object(parameters) { } contructor initialize list
  • 初始值前后位置关系不会影响实际的初始化顺序

成员初始化顺序
在构造函数初始值中每个成员只能出现一次
构造函数初始值列表只说明用于初始化成员的值,不限定初始化的具体执行顺序
成员初始化顺序与在类定义中的出现顺序一致
当涉及一个成员使用另一个成员初始化的时候,初始化顺序就有意义了

  • 如果一个构造函数为所有参数都提供了默认实参,则它实际上也定义了默认构造函数

  • 构造函数的唯一目的就是为数据成员赋初值

  • istream为参数的构造函数需要执行一些实际的操作

    • Sales_data::Sales_data(std::istream &is)
      {
      read(is,*this);
      }

拷贝、赋值和析构

不主动定义将系统合成(浅拷贝)

析构函数

  • ~(T)

  • 析构函数与构造函数成对,析构函数没有参数,只能有一个

    只要写了析构函数,就必须写 赋值构造函数赋值运算符

  • 析构函数会在程序结束时销毁对象

赋值函数

  • T & operator=(const T &obj)
    {
    if(this!=&obj0)
    {

      }return *this;
    

    }

访问控制与封装

access specifiers

  • public 成员在整个程序内可被访问,public成员定义类的接口
  • private 成员可以被类的成员函数访问

struct class
使用访问说明符前的成员

  • struct -> public
  • class -> private

友元friend

加友元属性允许其他类或者函数访问它的非公有成员

  • 加 friend 修饰词
  • 友元声明只能出现在类定义的内部
    • 最好在类定义开始或结束前的位置集中声明友元
  • 友元的声明仅仅指定了访问的权限
    • 在友元声明之外还必须再对函数进行声明

mutable

mutable data member

  • 可变数据成员永远不会是const
  • mutable 为修饰词
  • 可以用于在 const 成员函数中执行 mutable 成员值修改的操作

类类型

  1. 类不同 成员的路径不同
  2. 可以将类名跟在关键字 class 或 struct 后面
    • Sales_data i; (== class Sales_data i;)

类的声明

前向声明(forward declaration)

只声明暂时不定义

  • 在定义前是不完全类型(incomplete type)
    • 在定义前不可使用该类
    • 该类使用必须在类定义完成之后
    • 不可重复定义
  • 完成类的定义后,编译器才能知道存储该数据类型需要多少空间
    • 类的成员类型不能是该类自己
    • 允许包含指向它自身类型的引用或指针
      每个类负责控制自己的友元类或友元函数
      友元重载函数需要友元每一个重载
  • 类通常在不同头文件中,还需要 extern 声明
    (意味着能在一个类中能使用不同类中的重载函数?)

类和非成员函数的声明不是必须在它们的友元声明前
类外定义的成员函数需要在返回类型中指明它所属的类
编译器处理完类中的全部声明后才会处理成员函数的定义

名字查找

  1. 在名字所在的块中寻找其声明语句,只考虑在名字的使用之前出现的声明
  2. 如果没有,继续查找外层作用域
  3. 如果最终没有找到匹配的声明,报错

普通块作用域的名字查找

  1. 成员函数内查找,只考虑在名字的使用之前出现的声明
  2. 类内查找,考虑类中所有成员
  3. 成员函数定义之前的作用域内继续查找
  4. 外作用域
    可见不可见问题
    =

委托构造函数

(delegating constructor)
使用所属类的其他构造函数执行它自己的初始化过程,或者说它把它自己的一些(或者全部)职责委托给了其他构造函数
调用其他构造函数,用临时值方式进行构造

  • 提供实参或者接受部分接受实参的任务,并把实参交与构造函数进行初始化
  • 有一个成员初始值的列表和一个函数体
  • 初始值列表只有一个唯一的入口(类名)
  • 参数列表必须与类中另外一个构造函数匹配
  • 接受参数并调用构造函数进行初始化

默认初始化发生情况

  • 在块作用域内不使用任何初始值定义一个非静态变量或数组
  • 当一个类本身含有类类型的成员且使用合成的默认构造函数
  • 当类类型的成员没有在构造函数初始值列表中显式地初始化
    值初始化发生情况
  • 数组初始化的过程中提供的初始值数目少于数组的大小
  • 不适用初始值定义一个局部静态变量
  • “通过书写形如T()的表达式显式地请求值初始化时,其中 T 是类型名(vector的一个构造函数只接受一个实参用于说明vector大小)它就是使用一个这种形式的实参来对它的元素初始化器进行值初始化”

类类型转换
只允许一步类类型转换
转换结果为一个临时量
explicit 抑制构造函数定义的隐式转换
explicit 构造函数只能用于直接初始化
可以对explicit类型函数进行强转static_cast<>()

STL中含有单参数的构造函数
接受单参数的const char* 的string构造函数 非explicit
接受一个容量参数的vector构造函数 explicit 只能直接初始化

聚合类

可以直接访问成员
相当于直接struct

  • 所有成员为public
  • 没有定义任何构造函数
  • 没有类内初始值
  • 没有基类,没有virtual
  • 明显缺点(P267)

字面值常量类

  • 数据成员都是字面值类型的聚合类
  • 非聚合类
    • 数据成员都是字面值类型
    • 类必须至少有一个 constexpr 构造函数
    • 如果一个数据成员含有类内初始值,则内置类型成员的初始值必须是一条常量表达式;或者如果成员属于某种类类型,则初始值必须使用成员自己的 constexpr 构造函数
    • 类必须使用析构函数的默认定义,该成员负责销毁类的对象
      constexpr 构造函数体一般来说应该是空的
      constexpr 构造函数用于生成 constexpr 对象以及 constexpr 函数的参数或返回类型

类的静态成员

当在类的外部定义静态成员时,不能重复 static,该关键词只出现在类内部的声明语句

  1. 静态成员不属于类的任何一个对象,不由类的构造函数初始化
  2. 必须在类的外部定义和初始化每个静态成员
  3. 把静态数据成员的定义与其他非内联函数的定义放在同一个头文件
  • 普通成员属于对象,没有对象无法单独存在
  • 静态成员属于类
  • 静态数据成员不依赖于类内对象的存在
  • 类的成员能访问静态成员 不能通过静态成员直接访问普通成员

静态成员的类内初始化

可以为静态成员提供 const 整数类型的类内初始值(要求静态成员必须是字面值常量类型的 constexpr)

静态数据成员可以是不完全类型
静态数据成员的类型可以就是它所属的类类型

©️2020 CSDN 皮肤主题: 游动-白 设计师:上身试试 返回首页