C++primer小结7-8章

第七章 类

类的基本思想是数据抽象封装
数据抽象是一种依赖于接口实现分离的编程技术。
封装实现了类的接口和实现的分离。
类想要实现数据抽象和封装,首先需要定义一个抽象数据类型。

7.1 定义抽象数据类型

当我们设计类的接口时,应该考虑如何才能使得类易于使用;而当我们使用类时,不应该顾及类的实现机理。
Note:定义在类内部的函数是隐式的inline函数。
引入this
成员函数通过一个名为this的额外的隐式参数来访问调用它的那个对象。当我们调用一个成员函数时,用请求该函数的对象地址初始化this。
因为this的目的总是指向“这个”对象,所以this是一个常量指针,我们不允许改变this中保存的地址。
引入const成员函数
默认情况下,this的类型是指向类类型非常量版本的常量指针。例如在Sales_data成员函数中,this的类型是Sales_this *const。尽管this是隐式的,但它仍然需要遵循初始化规则,意味着(在默认情况下)我们不能把this绑定到一个常量对象上。这一情况也就使得我们不能在一个常量对象上调用普通的成员函数。const的作用是修改隐式this指针的类型。
把this设置为指向常量的指针有助于提高函数的灵活性。然而,this是隐式的并且不会出现在参数列表中,C++允许把const关键字放在成员函数的参数列表之后,此时,紧跟在参数列表后面的const表示this是一个指向常量的指针。像这样使用const的成员函数被称作常量成员函数
因为this是指向常量的指针,所以常量成员函数不能改变调用它的对象的内容。常量对象,以及常量对象的引用或指针都只能调用常量成员函数。
在类的外部定义成员函数
类内声明,类外定义。在类的外部定义成员函数时,成员函数的定义必须与它的声明匹配。
定义一个返回this对象的函数
return *this
return语句解引用this指针以获得执行该函数的对象。

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

我们定义非成员函数的方式与定义其他函数一样,通常把函数的声明和定义分离开来。
Note:一般来说,如果非成员函数是类接口的组成部分,则这些函数的声明应该与类在同一个头文件内。
默认情况下,拷贝类的对象其实拷贝的是对象的数据成员。

7.1.4 构造函数

类通过一个或几个特殊的成员函数来控制其对象的初始化过程,这些函数叫做构造函数。
构造函数的任务是初始化类对象的数据成员。
构造函数的名字和类型相同。不一样的是,构造函数没有返回类型;构造函数也有一个(可能为空的)参数列表和一个(可能为空的)函数体。
构造函数不能被声明成const的。
类通过一个特殊的构造函数来控制默认初始化过程,这个函数叫默认构造函数。默认构造函数无须任何实参。
编译器创建的构造函数又被称为合成的默认构造函数,这个合成的默认构造函数按照如下规则初始化类的数据成员:

  • 如果存在类内的初始值,用它来初始化成员。
  • 否则,默认初始化该成员。

某些类不能依赖于合成的默认构造函数
合成的默认构造函数只适合非常简单的类。对于一个普通的类来说,必须定义它自己的默认构造函数。
只有当类没有声明任何构造函数时,编译器才会自动地生成默认构造函数。
如果类包含有内置类型或者复合类型的成员,则只有当这些成员全都被赋予了类内的初始值时,这个类才适合于使用合成的默认构造函数。
在C++11新标准中,如果我们需要默认的行为,那么可以通过在参数列表后面写上** = default**来要求编译器生成构造函数。
构造函数初始化列表
它负责为新创建的对象的一个或几个数据成员赋初值。构造函数初始值是成员名字的一个列表,每个名字后面紧跟括号括起来的(或者在花括号内的)成员初始值。不同成员的初始化通过逗号分割开来。

Sales_data(const std::string &s):bookNo(s){}
Sales_data(const std::string &s,unsigned n, double p): bookNo(s), units_sold(n), revenue(p*n) {}

在类的外部定义构造函数

7.1.5 拷贝、赋值和析构

一般来说,编译器生成的版本将对对象的每个成员执行拷贝、赋值和销毁操作。
很多需要动态内存的类能使用vector对象或者string对象管理必要的存储空间。使用vector或者string的类能避免分配和释放内存带来的复杂性。

7.2 访问控制和封装

我们使用访问说明符加强类的封装性:

  • 定义在public说明符之后的成员在整个程序内可被访问,public成员定义类的接口。
  • 定义在private说明符之后的成员可以被类的成员函数访问,但是不能被使用该类的代码访问,private部分封装了(即隐藏了)类的实现细节。

一个类可以包含0个或多个访问说明符。每个访问说明符指定了接下来的成员的访问级别,其有效范围直到出现下一个访问说明符或者到达类的结尾处为止。
使用class和struct定义类的唯一区别就是默认的访问权限。当我们希望定义的类的所有成员是public的时,使用struct;反之,如果希望成员是private的,使用class。

7.2.1 友元

类可以允许其他类或者函数访问它的非公有成员,方法是令其他类或者函数称为它的友元。如果类想把一个函数作为它的友元,只需要增加一条以friend关键字开始的函数声明语句即可。
友元声明只能出现在类定义的内部,但是在类内出现的具体位置不限。友元不是类的成员也不受它所在区域访问控制级别的约束。
Tip:一般来说,最好在类定义开始或结束前的位置集中声明友元。
封装的益处:

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

友元的声明仅仅指定了访问的权限,而非一个通常意义上的函数声明。如果我们希望类的用户能够调用某个友元函数,那么我们就必须在友元声明之外再专门对函数进行一次声明。
许多编译器并未强制限定友元函数必须在使用之前在类的外部声明。

7.3 类的其他特性

7.3.1 类成员再探

除了定义数据和函数成员之外,类还可以自定义某种类型在类中的别名。由类定义的类型名字和其他成员一样存在访问限制。可以是public或者private的一种。用来定义类型的成员必须先定义后使用。因此类型成员通常出现在类开始的地方。
令成员作为内联函数。
重载成员函数,匹配过程和非成员函数非常类似。
可变数据成员。通过在变量的声明中加入mutable关键字,用于修改某个类的数据成员,即使是在一个const成员函数内。
类数据成员的初始值。

7.3.2 返回*this的成员函数

返回*this意味着这些函数返回的是对象本身而非对象的副本(即返回执行它的对象的引用)。
一个const成员函数如果以引用的形式返回 *this,那么它的返回类型将是常量引用。

7.3.3 类类型

即使两个类的成员列表完全一致,他们也是不同的类型。对于一个类来说,它的成员和其他任何类(或者任何其他作用域)的成员都不是一回事儿。
类的声明
就像可以把函数的声明和定义分离开来一样,我们也可以仅仅声明类而暂时不定义它:class Screen;
这种声明有时被称作前向声明。在它声明之后定义之前是一个不完全类型
类允许包含指向它自身的引用或指针。

7.3.4 友元再探

类还可以把其他的类定义成友元,也可以把其他类的成员函数定义成友元。此外,友元函数能定义在类的内部,这样的函数是隐式内联的。
如果一个类指定了友元类,则友元类的成员函数可以访问此类包括非共有成员在内的所有成员。
友元关系不存在传递性。每个类负责控制自己的友元类或友元函数。
尽管重载函数的名字相同,但它们仍然是不同的函数。如果一个类想把一组重载函数声明成它的友元,它需要对这组函数中的每一个分别声明。
友元声明的作用是影响访问权限。

7.4 类的作用域

每个类都会定义自己的作用域。在类的作用域之外,普通的数据和函数成员只能由对象、引用或者指针使用成员访问运算符来访问。对于类类型成员则使用作用域运算符访问。
一个类就是一个作用域的事实能够很好的解释为什么当我们在类的外部定义成员函数时必须同时提供类名和函数名。
类的定义分两部处理:

  • 首先,编译成员的声明
  • 直到类全部可见后才编译函数体
  • Note: 编译器处理完类中的全部声明后才会处理成员函数的定义。

7.5 构造函数再探

如果没有在构造函数的初始值列表中显示地初始化成员,则该成员将在构造函数体之前执行默认初始化。
有时可以忽略数据成员初始化和赋值之间的差异,但并非总是这样。如果成员是const或者是引用的话,必须将其初始化。类似的,当成员属于某种类类型且该类没有定义默认构造函数时,也必须将这个成员初始化。
如果成员是const、引用或者属于某种未提供默认构造参数的类类型,我们必须通过构造函数初始值列表为这些成员提供初值。
成员的初始化顺序与它们在类定义中的出现顺序一致:第一个成员先被初始化,然后第二个,以此类推。
最好令构造函数初始值的顺序与成员声明的顺序保持一致。而且如果可能的话,尽量避免使用某些成员初始化其他成员。
如果可能的话,最好用构造函数的参数作为成员的初始值,而避免使用同一个对象的其他成员。这样的好处是我们可以不必考虑成员的初始化顺序。
委托构造函数
一个委托构造函数使用它所属类的其他构造函数执行它自己的初始化过程,或者说它把它自己的一些(或者全部)职责委托给了其他构造函数。
默认构造函数
当对象被默认初始化或值初始化时自动执行默认构造函数。
在实际中,如果定义了其他构造函数,那么最好也提供一个默认构造函数。

对于C++新手程序员来说有一种常犯的错误,试图以如下的形式声明一个用默认构造函数初始化的对象:

Sales_data obj(); //错误,声明了一个函数而非对象
Sales_data obj;  //正确,obj是一个对象而非函数

7.5.4 隐式的类类型转换

能通过一个实参调用的构造函数定义了一条从构造函数的参数类型向类类型隐式转换的规则。
只允许一步类类型转换。
类类型转换不是总有效。
在要求隐式转换的程序上下文中,我们可以通过将构造函数声明为explicit加以阻止。
explicit构造函数只能用于直接初始化。
当我们用explicit关键字声明构造函数时,它将只能以直接初始化的形式使用。而且,编译器将不会在自动转换过程中使用该构造函数。
为转换显示地使用构造函数。
标准库中含有显示构造函数的类(vector)。

7.5.5 聚合类

聚合类使得用户可以直接访问其成员,并且具有特殊的初始化语法形式。

7.5.6 字面值常量类

constexpr函数的参数和返回值必须是字面值类型。
一个字面值常量类必须至少提供一个constexpr构造函数。
constexpr构造函数体一般来说应该是空的。

7.6 类的静态成员

声明静态成员
我们通过在成员的声明之前加上关键字static使得其与类关联在一起。和其他成员一样,静态成员可以是public的或private的。静态数据成员的类型可以是常量、引用、指针、类类型等。
类的静态成员存在于任何对象之外,对象中不包含任何与静态数据成员有关的数据。类似的,静态成员函数也不与任何对象绑定在一起,它们不包含this指针。
使用类的静态成员
我们使用作用域运算符直接访问静态成员。
成员函数不用通过作用域运算符就能直接使用静态成员。
定义静态成员
和类的所有成员一样,当我们指向类外部的静态成员时,必须指明成员所属的类名。static关键字则只出现在类内部的声明语句中。
必须在类的外部定义和初始化每个静态成员。一个静态数据成员只能定义一次。
类似于全局变量,静态数据成员定义在任何函数之外。因此它一旦被定义,就将一直存在于程序的整个生命周期中。
即使一个常量静态数据成员在类内部被初始化了,通常情况下也应该在类的外部定义一下该成员。
静态成员能用于某些场景,而普通成员不能
静态成员独立于任何对象。静态数据成员的类型可以就是它所属的类类型,而非静态数据成员则受到限制,只能声明它所属类的指针或引用。静态成员和普通成员的另外一个区别是我们可以使用静态成员作为默认实参。非静态数据成员不能作为默认实参。

第八章 IO库

IO库设施:

  • istream, 提供输入操作
  • ostream, 提供输出操作
  • cin, 一个istream对象, 从标准输入读取数据
  • cout, 一个ostream对象, 向标准输出写入数据
  • cerr, 一个ostream对象, 通常用于输出程序错误信息,写入到标准错误
  • “>>”运算符,用来从一个istream对象读取输入数据
  • “<<”运算符,用来向一个ostream对象写入输出数据
  • getline函数,从一个给定的istream读取一行数据,存入一个给定的string对象中

8.1 IO类

  • iostream定义了读写流的基本类型
  • fstream定义了读写命名文件的类型
  • sstream定义了读写内存string对象的类型

标准库是我们忽略不同类型的流之间的差异,通过继承机制实现的。利用模板,可以使用具有继承关系的类,而不必了解继承机制如何工作的细节。
不能拷贝IO对象,因此我们不能将形参或返回类型设置为流类型。进行IO操作的函数通常以引用方式传递和返回流。读写一个IO对象会改变其状态,因此传递和返回的引用不能是const的。
确定一个流对象的状态的最简单的方法是 将它当作一个条件来使用:while(cin >> word)
while循环检查>>表达式返回的流的状态。如果输入操作成功,流状态保持有效,条件为真。
每个输出流都管理一个缓存区,用来保存程序读写的数据。
刷新输出缓冲区
endl:完成换行并刷新缓冲区的工作
flush:刷新缓冲区,但不输出任何额外的字符
ends:向缓冲区插入一个空字符,然后刷新缓冲区
如果程序崩溃,输出缓冲区不会被刷新。
当一个输入流被关联到一个输出流时,任何试图从输入流读取数据的操作都会先刷新关联的输出流。

8.2 文件输入输出

头文件fstream定义了三个类型来支持文件IO:ifstream从一个给定文件读取数据,ofstream向一个给定文件写入数据,以及fstream可以读写给定文件。
在每次打开文件时,都要设置文件模式,可能是显示的设置,也可能是隐式的设置。当程序未指定模式时,就使用默认值。

8.3 string流

istringstream从string读取数据,ostringstream从string写入数据,而头文件stringstream既可以从string读数据也可以向string写数据。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值