类的基本思想是数据抽象和封装。数据抽象是一种依赖于借口和实现分离的编程技术。封装实现了类的借口和实现的分离。封装后的类隐藏了它的实现细节。也就是说,类的用户只能访问接口而无法访问实现。类想要实现数据抽象和封装,需要首先定义一个抽象数据类型。
7.1-7.4
定义在类内部的函数是隐式的inline函数。定义在类内的函数
std::string isbn const {return bookNo;}
const的作用是修改隐式指针的类型。默认情况下,this的类型是指向非常量版本的常量指针。
类的外部定义成员函数,成员函数的定义必须与它的声明匹配。类外部定义的成员的名字必须包含它所属的类名。定义一个返回this对象的函数。
Sales_data& Sales_data::combine(const Sales_data &rhs)
{
units_sold+=rhs.units_sold;
revenue+=rhs.revenue;
return *this;//返回调用该函数的对象
}
当我们定义的函数类似于某个内置运算符时,应该令函数的行为尽量模仿这个运算符。内置的赋值运算符把它的左侧运算对象当成左值返回,因此为了与它保持一致,combine函数必须返回引用类型。类的作者通常需要定义一些辅助函数,比如add, read, print等。尽管这些函数定义的操作从概念上来说属于类的接口组成部分,但他们实际上并不属于类本身。通常把函数的声明与类的声明(而非定义)放在同一个头文件中。
每个类都分别定义了它的对象被初始化的方式,类通过一个或几个特殊的成员函数来控制其对象的初始化过程,这些函数叫构造函数。构造函数的任务是初始化类对象的数据成员,无论何时只要类的对象被创建,就会执行构造函数。构造函数的名字与类名相同。与其他函数不同的是,构造函数没有返回类型。不同于其他成员函数,构造函数不能被声明称const的,当创建类的const对象时,直到构造函数完成初始化过程,对象才能真正取得其“常量”属性。因此,构造函数在const对象的构造过程中可以向其写值。
默认构造函数。默认构造函数无需任何实参。编译器创建的构造函数又被成为合成的默认构造函数。如果存在类内初始值,则用它来初始化成员。否则,默认初始化改该成员。
某些类不能依赖于合成的默认构造函数。对一个普通来来说,必须定义其默认构造函数。原因一,编译器只有发现类不包含任何构造函数的情况下才会替我们生成一个默认的构造函数。一旦我们定义了一下其他的构造函数,除非再定义一个默认的构造函数,否则类将没有默认构造函数。(如果类在某种情况下需要对象初始化,则该类很可能在所有情况下都需要控制)。原因二,某些类的合成的默认构造函数可能执行错误的操作。定义在块中的内置类型或者复合类型的对象被默认初始化,则他们的值将是未定义的。该准则同样适用于默认初始化的内置类型成员。原因三,编译器不能为某些类合成默认的构造函数。例如类中包含了一个其他类类型的成员且这个成员没有默认构造函数。
Struct Sales_data{
Sales_data=dafault;
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){}
Sales_data(std::istream &);
std::string isbn() const{return bookNo;}
Sales_data& combine(const Sales_data&);
double avg_price() const;
std::string bookNo;
unsigned units_sold=0;
double revenue=0.0;
};
在C++11新标准中,如果我们需要默认的行为,可以通过在参数列表后面写上=default要求编译器生成构造函数。如果=default在类的内部,则默认构造函数是内联的,如果它在类的外部,则该成员默认情况不内联。
构造函数初始值列表。在类的外部定义构造函数:
Sales_data::Sales_data(std::istream &is)
{
read(is,*this);
}
编译器生成的版本将对对象的每个成员执行拷贝、赋值和销毁操作。很多需要动态内存的类能(应该)使用vector对象或者string对象管理必要的存储空间。使用vector或者string的类能避免分配和释放内存带来的复杂性。
访问说明符加强类的封装性。定义在public说明符轴的成员在整个程序内可被访问。public成员定义类的接口。定义在private说明符之后的成员可以被类的成员函数访问,但是不能被使用该类的代码访问。private部分封装了类的实现细节。
class与struct定义类的唯一区别就是默认的访问权限。友元声明只能出现在类定义的内部,但是类内出现的具体位置不限。友元不是类的成员,也不受它所在区域访问控制级别的约束,一般来说,最好在类定义开始或结束前的位置集中声明友元。友元的声明仅仅指定了访问的权限。而非通常意义上的函数声明。
类成员再探。用来定义类型(typedef、using)的成员必须先定义后使用,与普通成员有差别。
可变数据成员, 在变量的声明中加入mutable关键字做到这一点。一可变数据成员永远不会是const。
当提供一个类内初始值时,必须用等号或者花括号表示。
每个类定义了唯一的类型,对于两个类来说,即使他们成员完全一样,这两个类也是两个不同的类型。
声明类类型的对象两种方法:
Sales_data item1;
class Sales_data item1;
第二种方式是从C语言继承过来的。两种方式等价。
就像可以把函数声明与定义分离一样,我们也能仅声明类而暂时不定义它。这种声明有时被称为前向声明。对于一个类类型来说,它在声明之后定义之前是个不完全类型。一旦遇到类名,定义的剩余部分就在类的作用域之内了。类型名要特殊处理,如果在类中使用过外层作用域的名字,且该名字表示一种类型,则类不能在之后重新定义该名字。
7.5
有的时候必须给构造函数成员提供初值(const、引用或者某种未提供默认构造函数的类类型),不能在函数体内赋值运算。
成员初始化的顺序与其在类定义中的出现顺序一致。C++11规定委托构造函数,它把它自己的一些或全部职责委托给了其他构造函数。
只允许一步类类型转换。如以下是错的
item.combine("9999-99");
explicit构造函数能抑制隐式转换。explicit构造函数只能用于直接初始化。
聚合类:所有成员都是public的,没有定义任何构造函数。没有类内初始值。没有基类,也没有virtual函数。
字面值常量类:数据成员都是字面值类型的聚合类。
7.6
类的静态成员, 有的时候,类需要它的一些成员与类本身直接相关,而不是与类的 各个对象保持关联。类的静态成员不与任何对象绑定在一起,它们不包含this指针。静态成员函数不能声明称const的,也不能在static函数体内使用this指针。虽然类的静态成员不属于某个对象,但是仍然可以用类的对象,引用,指针来访问静态成员。在类的外部定义static成员时,不能重复static关键字,该关键字只出现在类内部的声明语句中。
通常情况下,const成员不应该在类的内部初始化。但是可以为静态成员提供const整数类型的类内初始值。
静态成员可以是不完全类型(指针或引用也可以是不完全类型),普通成员不行。静态成员可以作为默认的实参,普通成员不可以。