C++Primer学习笔记(八)类
12.1. 类的定义和声明
1)扼要重述:
类成员可以是数据、函数或函数别名。
构造函数
成员函数:
在类内部定义的函数默认为inline。
将关键字const加在形参表之后,就可以将成员函数申明为常量。const成员不能改变其所操作的对象的数据成员。const必须同时出现在申明和定义中,若只出现在一处,就会出现编译错误。
2)数据抽象和封装:
类背后蕴涵的基本思想是数据抽象与封装。
数据抽象是一种依赖于接口和实现分离的编程技术。
封装是一项将低层次的元素组合起来形成新的、高层次实体的技术。
访问标号实施抽象和封装public/private;可多次出现;class默认以private开始
并非所有类型都是抽象的。具体类型如pair类型,仅将两个数据成员捆绑成单个对象
数据抽象和封装的好处:
1、避免类内部出现无意的、可能破坏对象状态的用户级错误。
2、随时间推移可以根据需要改变或缺陷(bug)报告来完善类实现,而无需改变用户级代码。
3)关于类定义的更多内容
1、同一类型的多个数据成员
2、使用类型别名来简化类
3、成员函数可以被重载
4、定义重载成员函数
5、显式指定inline成员函数
可以在类定义体内部指定一个成员为inline,作为申明的一部分。或者,也可以在类定义体外部的函数定义上指定inline。
在申明和定义处指定inline都是合法的。在类的外部定义inline的一个好处是使得类比较容易阅读。
4)类声明与类定义
类的定义放在头文件件中是一个很好的做法。
可以声明一个类而不定义它:
如:class Screen;
这个声明也称为前向声明,在声明之后定义之前,这个类是一个不完全类型,即已知类型却不知道包含哪些成员。
不完全类型只能用于定义指向该类型的指针及引用,或者用于声明(而不是定义)使用该类型作为形参类型或返回类型的函数。
5)类对象
定义一个类时,也就定义了一个类型。一旦定义了类,就可以定义该类型的对象。定义对象时,将为其分配存储空间,但(一般而言)定义类型时不进行存储分配。
类的定义以分号结束。分号是必须的,是因为在类定义之后可以接一个对象定义列表。
12.2. 隐含的 this 指针
成员函数具有一个附加的隐含形参,即指向该类对象的一个指针。这个隐含形参命名为 this。
如果对类成员的引用没有限编译器会将这种引用处理成通过 this 指针的引用。
何时使用 this 指针
当我们需要将一个对象作为整体引用而不是引用对象的一个成员时。
从 const 成员函数返回 *this
在普通的非 const 成员函数中,this 的类型是一个指向类类型的 const 指针
在 const 成员函数中,this 的类型是一个指向 const 类类型对象的 const 指针。
基于 const 的重载
const 对象只能使用 const 成员。非 const 对象可以使用任一成员,但非 const 版本是一个更好的匹配(重载时先调用这个版本)
可变数据成员(mutable data member)永远都不能为 const,甚至当它是const 对象的成员时也如此。
建议:用于公共代码的私有实用函数do_display:
避免在多个地方编写同样的代码。
增加调试信息。
随着类的演变而变得更复杂,修改方便。
使之成为内联函数,不需要开销。
12.3. 类作用域
即使两个类具有完全相同的成员列表,它们也是不同的类型。
形参表和函数体处于类作用域中
在定义于类外部的成员函数中,形参表和成员函数体都出现在成员名之后。
函数返回类型不一定在类作用域中
类作用域中的名字查找
- 首先,在使用该名字的块中查找名字的声明。只考虑在该项使用之前声明的名字。
- 如果找不到该名字,则在包围的作用域中查找。如下:
(1)函数作用域之后,在类作用域中查找;
(2)类作用域之后,在外围作用域中查找(全局函数,全局变量等)。
12.4. 构造函数
构造函数是特殊的成员函数,只要创建类类型的新对象,都要执行构造函数。
1.构造函数可以被重载
2.实参决定使用哪个构造函数
3.构造函数自动执行
构造函数不能声明为 const
构造函数初始化式
与任何其他函数一样,构造函数具有名字、形参表和函数体。与其他函数不同的是,构造函数也可以包含一个构造函数初始化列表
构造函数也可以包含一个构造函数初始化列表,构造函数初始化列表以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个数据成员后面跟一个放在圆括号中的初始化式。
Sales_item::Sales_item(const string &book)
: isbn(book), units_sold(0), revenue(0.0)
{ }
省略初始化列表在构造函数的函数体内对数据成员赋值是合法的
构造函数分两个阶段执行:(1)初始化阶段;(2)普通的计算阶段。计算阶段由构造函数函数体中的所有语句组成。
有时需要构造函数初始化列表
如果没有为类成员提供初始化式,则编译器会隐式地使用成员类型的默认构造函数。如果那个类没有默认构造函数,则编译器尝试使用默认构造函数将会失败。
可以初始化 const (对象或引用类型的对象),但不能对它们赋值 !
必须对任何 const 或引用类型成员以及没有默认构造函数的类类型的任何成员使用初始化式。
成员初始化的次序
每个成员在构造函数初始化列表中只能指定一次
如果一个成员是根据其他成员而初始化,则成员初始化的次序是至关重要的。
按照与成员声明一致的次序编写构造函数初始化列表是个好主意。此外,尽可能避免使用成员来初始化其他成员。
初始化式可以是任意表达式
类类型的数据成员的初始化式
初始化类类型的成员时,要指定实参并传递给成员类型的一个构造函数。
默认实参与构造函数
可以通过为 string 初始化式提供一个默认实参将这些构造函数组合起来
我们更喜欢使用默认实参,因为它减少代码重复。
默认构造函数
只有当一个类没有定义构造函数时,编译器才会自动生成一个默认构造函数。
如果定义了其他构造函数,则必须提供默认构造函数。
通常,在默认构造函数中给成员提供的初始值应该指出该对象是“空”的。
对于类类型成员,调用其默认构造函数进行初始化。
对于内置和复合类型,只对定义在全局作用域中的对象才初始化,当对象定义在局部作用域中,不进行初始化
最佳实践:如果类包含内置或复合类型的成员,应自定义构造函数
隐式类类型转换
可以用单个实参来调用的构造函数定义了从形参类型到该类类型的一个隐式转换。
抑制由构造函数定义的隐式转换:可以通过将构造函数声明为 explicit,来防止在需要隐式转换的上下文中使用构造函
通常,单形参构造函数应该为 explicit,当需要转换时显示地构造对象
类成员的显式初始化
三个重大的缺点:
1. 要求类的全体数据成员都是 public。
2. 将初始化每个对象的每个成员的负担放在程序员身上。这样的初始化是乏味且易于出错的,因为容易遗忘初始化式或提供不适当的初始化式。
3. 如果增加或删除一个成员,必须找到所有的初始化并正确更新。
结论:定义和使用构造函数几乎总是较好的。
12.5. 友元
友元机制允许一个类将对其非公有成员的访问权授予指定的函数或类。
友元的声明以关键字 friend 开始。它只能出现在类定义的内部。友元声明可以出现在类中的任何地方(通常开始或结尾)
友元可以是普通的非成员函数,或前面定义的其他类的成员函数,或整个类。
将一个类设为友元,友元类的所有成员函数都可以访问授予友元关系的那个类的非公有成员。
一般,先定义包含成员函数的类或非成员函数,再将其设为友元。这样就不必预先声明类和非成员函数来将它们设为友元。
重载函数与友元关系:类必须将重载函数集中每一个希望设为友元的函数都声明为友元
12.6. static 类成员
static 数据成员独立于该类的任意对象而存在;每个 static 数据成员是与类关联的对象,并不与该类的对象相关联。
static 成员函数没有 this 形参,它可以直接访问所属类的 static 成员,但不能直接使用非 static 成员。
使用 static 成员而不是全局对象有三个优点:
1. static 成员的名字是在类的作用域中,因此可以避免与其他类的成员或全局对象名字冲突。
2. 可以实施封装。static 成员可以是私有成员,而全局对象不可以。
3. 通过阅读程序容易看出 static 成员是与特定类关联的。这种可见性可清晰地显示程序员的意图。
定义 static 成员
在成员声明前加上关键字 static 将成员设为 static。static 成员遵循正常的公有/私有访问规则。
使用类的 static 成员
static成员属于该类类型,不属于某个对象,可以通过作用域操作符从类直接调用 static 成员,或者通过对象、引用或指向该类类型对象的指针间接调用。
static 成员函数
static 函数没有 this 指针,因为static 成员是类的组成部分但不是任何对象的组成部分
static 成员函数不能被声明为 const
static 成员函数也不能被声明为虚函数
static 数据成员
类内声明,类外定义(正好一次,不用加static关键字)
最好方法:将其定义放在包含类非内联成员函数定义的文件中
特殊的整型 const static 成员
类内 static const int period = 30;
类外 const int Account::period;
static 成员不是类对象的组成部分
static 数据成员的类型可以是该成员所属的类类型
非 static 成员被限定声明为其自身类对象的指针或引用
static 数据成员可用作默认实参,非 static 数据成员不能