第七章 类

一、定义抽象数据类型

  • 类背后的基本思想数据抽象(data abstraction)封装(encapsulation)
  • 数据抽象是一种依赖于接口(interface)实现(implementation)分离的编程技术。

1. 类成员

  • 必须在类的内部声明,不能在其他地方增加成员。
  • 成员可以是数据,函数,函数别名。

2. 类的成员函数

  • 成员函数的声明必须在类的内部。
  • 成员函数的定义既可以在类的内部也可以在外部。
  • 使用 点运算符 . 调用成员函数。
  • 必须对任何const或者引用类型成员以及默认构造函数的类类型的任何成员使用初始化式。ConstRef::ConstRef(int ii): i(ii), ci(ii), ri(ii) { };
  • 默认实参:Sales_item(const std::string &book): isbn(book), units_sold(0), revenue(0.0) { };
  • *this
    • 每个成员函数都有一个额外的,隐含的形参this
    • this总是指向当前对象,因此this是一个常量指针
    • 形参表后面的const,该表了隐含的形参this的类型,例如 bool same_isbn(const Sales_item &rhs) const; ,这种函数称为“常量成员函数”(this指向的当前对象是常量)。
    • return *this; 可以让成员函数连续调用。
    • const成员函数:this是指向const类类型的const指针(不可以改变this所指向的值,不可以改变this保存的地址)。
    • 非const成员函数:this是指向类类型的const指针(可以改变this所指向的值,不可以改变this保存的地址)。

3. 非成员函数

  • 和类相关的非成员函数,定义和声明都应该在类的外部。

4. 类的构造函数

  • 类通过一个或者几个特殊的成员函数来控制其对象的初始化过程,这些函数叫做构造函数
  • 构造函数是特殊的成员函数,且与类同名。它在类的public部分。
  • =default 要求编译器合成默认的构造函数。通常在定义自己的构造函数前调用它一次。(C++11)
  • 初始化列表:冒号和花括号之间的代码: Sales_item(): units_sold(0), revenue(0.0) { }

5. 拷贝、赋值和析构

  • 类中所有分配的资源都应该直接以类的数据成员形式存储。

二、访问控制与封装

  • 访问说明符(access specifiers)

    • public:定义在 public后面的成员在整个程序内可以被访问; public成员定义类的接口。
    • private:定义在 private后面的成员可以被类的成员函数访问,但不能被使用该类的代码访问; private隐藏了类的实现细节。
  • 使用class或者struct:都可以被用于定义一个类,唯一的区别就是默认的访问权限
    • 使用class:在第一个访问说明符之前的成员都是private的。
    • 使用struct:在第一个访问说明符之前的成员都是public的。

1. 友元

  • 允许特定的非成员函数访问一个类的私有成员
  • 友元的声明以关键字 friend 开始。通常将友元声明成组地放在类定义的开始或者结尾
class Sales_data {
friend Sales_data add(const Sales_data&, const Sales_data&); public:
Sales_data() = defalut;
Sales_data(const std::string &s, unsigned n, double p): bookNo(s), units_sold(n), revenue(p*n) {} private:
std::string bookNo;
unsigned units_sold = 0;
double revenue = 0.0;
};

2. 封装的益处

  • 确保用户的代码不会无意间破坏封装对象的状态。
  • 被封装的类的具体实现细节可以随时改变,而无需调整用户级别的代码

三、类的其他特性

  • 成员函数作为内联函数inline:

    • 在类的内部,常有一些规模较小的函数适合于被声明为内联函数
    • 定义在类内部的函数是自动内联的。
    • 在类外部定义的成员函数,也可以在声明时显式地加上inline。
  • 可变数据成员(mutable data member):
    • mutable size_t access_ctr;
    • 永远不会是const,即使它是const对象的成员。
  • 类类型
    • 每个类定义了唯一的类型。

四、类的作用域

  • 每个类都会定义它自己的作用域。在类的作用域之外,普通的数据和函数成员只能由引用、对象、指针使用成员访问运算符来访问。
  • 函数的返回类型通常在函数名前面,因此当成员函数定义在类的外部时,返回类型中使用的名字都位于类的作用域之外。
  • 如果成员使用了外层作用域中的某个名字,而该名字代表一种类型,则类不能在之后重新定义该名字。
  • 类的定义分两步处理
    • 首先,编译成员的声明。
    • 直到类全部可见后才编译函数体。
  • 类型名的定义通常出现在类的开始处,这样就能确保所有使用该类型的成员都出现在类名的定义之后。

五、构造函数再探

1. 构造函数初始化列表

  • 构造函数的初始值有时必不可少。如果成员是const、引用,或者属于某种未提供默认构造函数的类类型,我们必须通过构造函数初始值列表为这些成员提供初值。
  • 建议使用构造函数初始值
  • 最好令构造函数初始值的顺序与成员声明的顺序保持一致。建议尽量避免使用某些成员初始化其他成员。

2. 委托构造函数

  • 委托构造函数将自己的职责委托给了其他构造函数。
class Sales {
public:
// 非委托构造使用对应的实参初始化成员
Sales(std::string s, unsigned cnt, double price): bookNo(s), unit(cnt), revenue(cnt*price) {} // 其余构造函数全部委托给另一个构造函数
Sales(): Sales("", 0, 0) {}
Sales(std::string s): Sales(s, 0, 0) {}
Sales(std::istream &is): Sales() {read(is, *this);}
};

3. 默认构造函数的作用

  • 当对象被默认初始化或值初始化时自动执行默认构造函数。默认初始化的发生情况:

    • 当在块作用域内不使用任何初始值定义一个非静态变量或者数组时。
    • 当一个类本身含有类类型的成员且使用合成的默认构造函数时。
    • 当类类型的成员没有在构造函数初始值列表中显式地初始化时。
  • 值初始化的发生情况:
    • 在数组初始化的过程中如果提供的初始值数量少于数组的大小时。
    • 当不使用初始值定义一个局部静态变量时。

4. 隐式的类类型转换

  • 如果构造函数只接受一个实参,则它实际上定义了转换为此类类型的隐式转换机制。这种构造函数又叫转换构造函数(converting constructor)
  • 编译器只会自动地执行仅一步类型转换。
  • 抑制构造函数定义的隐式转换:
    • 将构造函数声明为explicit加以阻止。
    • explicit构造函数只能用于直接初始化不能用于拷贝形式的初始化

5. 聚合类

  • 聚合类使得用户可以直接访问其用户,它需要满足以下要求:

    • 所有成员都是public的。
    • 没有定义任何构造函数。
    • 没有类内初始值。
    • 没有基类,也没有virtual函数。
// 聚合类例子
struct Data {
int val;
string s;
};
  • 可以使用一个花括号括起来的成员初始值列表,初始值的顺序必须和声明的顺序一致。
// 聚合类使用
Data d = {0, "DD"};

6. 字面值常量类

  • constexpr函数的参数和返回值必须是字面值。
  • 字面值类型:除了算术类型、引用和指针外,某些类也是字面值类型。
  • 数据成员都是字面值类型的聚合类是字面值常量类
  • 如果不是聚合类,则必须满足下面所有条件:
    • 数据成员都必须是字面值类型。
    • 类必须至少含有一个constexpr构造函数。
    • 如果一个数据成员含有类内部初始值,则内置类型成员的初始值必须是一条常量表达式;或者如果成员属于某种类类型,则初始值必须使用成员自己的constexpr构造函数。
    • 类必须使用析构函数的默认定义,该成员负责销毁类的对象。

六、类的静态成员

  • 非static数据成员存在于类类型的每个对象中。但是static数据成员独立于该类的任意对象而存在。
  • 每个static数据成员是与类关联的对象,并不与该类的对象相关联。
  • 声明
    • 声明之前加上关键词static。
  • 使用
    • 使用作用域运算符::直接访问静态成员:r = Account::rate();
    • 也可以使用对象访问:r = ac.rate();
  • 定义
    • 在类外部定义时不用加static。
  • 初始化
    • 通常不在类的内部初始化,而是在定义时进行初始化,如 double Account::interestRate = initRate();
    • 如果一定要在类内部定义,则要求必须是字面值常量类型的constexpr。

【C++】《C++ Primer 》第七章的更多相关文章

  1. C++Primer 第七章

    //1.定义在类内部的函数是隐式内联的. //2.默认情况下,this指针的类型是指向类类型非常量版本的常量指针.对于类的常量成员函数的声明方法是:将const放置于成员函数的参数列表后,用于修饰th ...

  2. C primer plus 读书笔记第六章和第七章

    这两章的标题是C控制语句:循环以及C控制语句:分支和跳转.之所以一起讲,是因为这两章内容都是讲控制语句. 第六章的第一段示例代码 /* summing.c --对用户输入的整数求和 */ #inclu ...

  3. C++ Primer Plus学习:第七章

    C++入门第七章:函数-C++的编程模块 函数的基本知识 要使用C++函数,必须完成如下工作: 提供函数定义 提供函数原型 调用函数 库函数是已经定义和编译好的函数,可使用标准库头文件提供原型. 定义 ...

  4. 精通Web Analytics 2.0 (9) 第七章:失败更快:爆发测试与实验的能量

    精通Web Analytics 2.0 : 用户中心科学与在线统计艺术 第七章:失败更快:爆发测试与实验的能量 欢迎来到实验和测试这个棒极了的世界! 如果Web拥有一个超越所有其他渠道的巨大优势,它就 ...

  5. 《Entity Framework 6 Recipes》中文翻译系列 (38) ------ 第七章 使用对象服务之动态创建连接字符串和从数据库读取模型

    翻译的初衷以及为什么选择<Entity Framework 6 Recipes>来学习,请看本系列开篇 第七章 使用对象服务 本章篇幅适中,对真实应用中的常见问题提供了切实可行的解决方案. ...

  6. 《Entity Framework 6 Recipes》中文翻译系列 (41) ------ 第七章 使用对象服务之标识关系中使用依赖实体与异步查询保存

    翻译的初衷以及为什么选择<Entity Framework 6 Recipes>来学习,请看本系列开篇 7-7  标识关系中使用依赖实体 问题 你想在标识关系中插入,更新和删除一个依赖实体 ...

  7. Java语言程序设计(基础篇) 第七章 一维数组

    第七章 一维数组 7.2 数组的基础知识 1.一旦数组被创建,它的大小是固定的.使用一个数组引用变量,通过下标来访问数组中的元素. 2.数组是用来存储数据的集合,但是,通常我们会发现把数组看作一个存储 ...

  8. objective-c第七章课后练习2

    题:改变第七章例子中print方法,增加bool参数,判断如果是YES则对分数进行约简 @interface Fraction : NSObject { //int num,den; } @prope ...

  9. 读《编写可维护的JavaScript》第七章总结

      第七章 事件处理 7.1 典型用法 作者首先给了个我们一个处理事件的方法.看起来也没啥俩样,不过后来给出的优化方法很值得学习: // 不好的写法 function handleClick(even ...

随机推荐

  1. 什么时候使用transition?什么时候使用animation?

    不同点: 1. 触发条件不同.transition通常和hover等事件配合使用,由事件触发.animation则和gif动态图差不多,立即播放. 2. 循环. animation可以设定循环次数. ...

  2. hashmap为什么是二倍扩容?

    这个很简单,首先我们考虑一个问题,为什么hashmap的容量为2的幂次方,查看源码即可发现在计算存储位置时,计算式为: (n-1)&hash(key) 容量n为2的幂次方,n-1的二进制会全为 ...

  3. java试用静态图片制作gif

    参考博客:https://www.cnblogs.com/dreammyle/p/4843365.html 代码中需要的依赖: <!-- gif --> <dependency> ...

  4. CSS-backgroound和radial-giadient的常见用法

    前言 这里主要介绍下css中background和radial-giadient径向渐变的使用,工作中用到的地方可能也不太多,但是每次用到了都需要查阅官网,查资料就比较麻烦,这里记录一下我自己整理的常 ...

  5. 面试 03-DOM事件的总结

    03-DOM事件的总结 #前言 要学习事件的基础内容,请看先本人之前的基础文章: <04-JavaScript基础/27-事件对象Event> <04-JavaScript基础/28 ...

  6. js下 Day02、DOM文档对象模型

    一.DOM简介 Document Object Model 文档对象模型 DOM包含了所有HTML元素的属性和方法,以及访问他们的方式: #二.DOM节点 #1. 什么是节点? HTML中所有的元素都 ...

  7. rbd锁引起kvm虚拟机无法启动的故障

    前言 环境因为一些问题(网络,或者磁盘,或者其它各种异常),引起了集群的状态的一些变化,变化之后,集群的某些虚拟机正常某些虚拟机出现异常,异常现象就是无法启动 特别是win server2008 ,会 ...

  8. (菜鸟都能看懂的)网络最大流最小割,Ford-Fulkerson及Dinic详解

    关于网络流: 1.定义 个人理解网络流的意思便是由一条条水管以及一个源点S一个汇点T和一些节点组成的一张图,现在要从S点流水到T点,问怎么流才能让流到T的流量最大.边权表示的是这条水管的最大流量,假设 ...

  9. gcc编译阶段打印宏定义的内容

    背景 总所周知,代码量稍微大一点的C/C++项目的一些宏定义都会比较复杂,有时候会嵌套多个#if/#else判断分支和一堆#ifdef/#undef让你单看代码的话很难判断出宏定义的具体内容. 如果有 ...

  10. openstack高可用集群17-openstack集成Ceph准备

    Openstack集成Ceph准备 Openstack环境中,数据存储可分为临时性存储与永久性存储. 临时性存储:主要由本地文件系统提供,并主要用于nova虚拟机的本地系统与临时数据盘,以及存储gla ...