小结:
类是C++语言中最基本的特征。类允许我们为自己的应用定义新类型,从而使得程序更加简洁且易于修改。
类有两项基本能力:一是数据抽象,即定义数据成员和函数成员的能力;而是封装,即保护类的成员不被随意访问的能力。通过将类的实现细节设为private,我们就能完成类的封装。类可以将其他类或者函数设为友元,这样它们就能访问类的非公有成员了。
类可以定义一种特殊的成员函数:构造函数,其作用是控制初始化对象的方式。构造函数可以重载,构造函数应该使用构造函数初始值列表来初始化所有数据成员。
类还能定义可变或者静态成员。一个可变成员永远都不会是const,即使在const成员函数内也能修改它的值;一个静态成员可以是函数也可以是数据,静态成员存在于所有对象之外。
笔记:
类的基本思想是数据抽象(data abstraction)和封装(encapsulation)。数据抽象是一种依赖于接口(interface)和实现(implementation)分离的编程(以及设计)技术。类的接口包括用户所能执行的操作;类的实现则包括类的数据成员、负责接口实现的函数体以及定义类所需的各种私有函数。
封装实现了类的接口和实现分离。封装后的类隐藏了它的实现细节,也就是说,类的用户只能使用接口而无法访问实现部分。
类要想实现数据抽象和封装,需要首先定义一个抽象数据类型(abstract data type)。在抽象数据类型中,由类的设计者负责考虑类的实现过程;使用该类的程序员则只需要抽象地思考类型做了什么,而无需了解类型的工作细节。
定义在类的内部的函数是隐式地inline函数。
成员函数通过一个名为this的额外的隐式参数来访问调用它的那个对象。当我们调用一个成员函数时,用请求该函数的对象地址初始化this。
把this设置为指向常量的指针有助于提高函数的灵活性。std::string isbn() const; 像这样使用const的成员函数被称作常量成员函数(const member function)。
常量对象,以及常量对象的引用或指针都只能调用常量成员函数。
在C++11新标准中,如果我们需要默认的行为,那么可以通过在参数列表后面写上 =default 来要求编译器生成构造函数。
尽管编译器能替我们合成拷贝、赋值和销毁的操作,但是必须要清楚的一点是,对于某些类来说合成的版本无法正常的工作。特别是,当类需要分配类对象之外的资源时,合成的版本常常会失效。例如分配和管理动态内存。
使用class和struct定义类的唯一区别就是默认的访问权限。
一般来说,最好在类定义开始或结束前的位置集中声明友元。
为了使友元对类的用户可见,我们通常把友元的声明与类本身放置在同一个头文件中(类的外部)。
一个const成员函数如果以引用的形式返回*this,那么它的返回类型将是常量引用。
对于公共代码使用私有功能的函数,在实践中,设计良好的C++代码常常包含大量类似于do_display的小函数。
即使两个类的成员列表完全一致,他们也是不同的类型。对于一个类来说,它的成员和其他任何类(或者任何其他作用域)的成员都不是一回事儿。
要想令某个成员函数作为友元,我们必须仔细组织程序结构以满足声明和定义的彼此依赖关系。
理解友元声明的作用是影响访问权限,它本身并非普通意义上的声明。
每个类都会定义它自己的作用域。在类的作用域之外,普通的数据和函数成员只能由对象、引用或者指针使用成员访问运算符来访问。
编译器处理完类中的全部声明后才会处理成员函数的定义。
类型名的定义通常出现在类的开始,这样就能确保所有使用该类型的成员都出现在类名的定义之后。
尽管类的成员被隐藏了,但我们仍然可以通过加上类的名字或显式地使用this指针来强制访问成员。
尽管外层的对象被隐藏掉了,但我们仍然可以用作用域运算符访问它。
使用构造函数初始值,建议读者养成使用构造函数初始值的习惯,这样能避免某些意想不到的编译错误,特别是遇到有的类含有需要构造函数初始值的成员时。
最好令构造函数初始值的顺序与成员声明的顺序保持一致。而且如果可能的话,尽量避免使用某些成员初始化其他成员。
在实际中,如果定义了其他构造函数,那么最好也提供一个默认构造函数。
能通过一个实参调用的构造函数定义了一条从构造函数的参数类型向类类型隐式转换的规则。
explicit构造函数只能用于直接初始化。
要想确保对象只定义一次,最好的办法是把静态数据成员的定义与其他非内联函数的定义放在同一文件中。
FAQ:
Q1:编译器创建的构造函数又被称为合成的默认构造函数(synthesized default constructor),初始化类的数据成员的规则。
A1:1、如果存在类内的初始值,用它来初始化成员。 2、否则,默认初始化该成员。
Q2:为什么一个普通的类,必须定义它自己的默认构造函数?
A2:(1)编译器只有在发现类不包含任何构造函数的情况下才会替我们生成一个默认的构造函数。
(2)对于某些类来说,合成的默认构造函数可能执行错误的操作。例如,定义在块中的内置类型或复合类型的对象被默认初始化,则它们的值将是未定义的。
(3)有的时候编译器不能为某些类合成默认的构造函数。例如,如果类中包含一个其他类类型的成员且这个成员的类型没有默认构造函数,那么编译器将无法初始化该成员。
Q3:静态成员与普通成员有何区别?
A3:静态成员与普通成员的区别主要体现在普通成员与类的对象关联,是某个具体对象的组成部分;而静态成员不从属于任何具体的对象,它由该类的所有对象共享。另外,还有一个细微的区别,静态成员可以作为默认实参,而普通数据成员不能作为默认实参。