第七章 类(class)7.1 笔记
最好不要把对象的定义和类的定义放在一起,这么做无异于把两种不同实体的定义混在了一条语句里,一会定义类,一会又定义变量,显然这是一种不被建议的行为。
类的定义最后要加上分号(:)
成员函数的声明必须放在类的内部,它的定义则既可以在类的内部也可以在类的外部,而作为接口组成部分的非成员函数,例如add、 read、和print等,他们的定义和声明都在外部
任何对类成员的直接访问都被看做this的隐式引用,也就是说,当isbn使用bookNo时,它隐式地使用this指向的成员,就像我们书写了this -> bookNo一样。this不能用作任何形参名和变量名。
std::string isbn() const { return this -> bookNo ;}
//因为this的目的总是指向这个对象,所以this是一个常量指针。紧跟在参数列表后面的const 表示this是一个指向常量的指针。像这样使用const的成员函数被称作常量成员函数(指明这个函数不会修改这个类的任何数据成员的值)。
可以把isbn()函数想象成如下形式
//伪代码,不能显示的定义this指针
std::string Sales_data::isbn(const Sales_data *const this) const { return this -> bookNo ;}
常量对象和常量对象的引用或指针都只能调用常量成员函数
编译器分两步处理类:首先编译成员的声明,然后才轮到成员函数体(如果有的话)。因此,成员函数体可以随意使用类中的其他成员而无需在意这些成员出现的次序。
像其它函数一样,当我们在类外部定义成员函数时,成员函数的定义必须与它的声明匹配。也就是说返回类型、参数列表和函数名都得与类内部的声明保持一致。如果成员被声明称常量成员函数,那么他的定义也必须在参数列表后表明指定const属性。同样,类外部定义的成员的名字必须包含它所属的类名:
double Sales_data::avg_price() const { //Sales_data::加在函数名之前而非返回类型之前
if (units_sold)
return revenue / units_sold;
else
return 0;
}
Sales_data& Sales_data::combine(const Sales_data &rhs)
{
units_sold += rhs.units_sold; //把rhs的成员加到this对象的成员上
revenue += rhs.revenue;
return *this; //返回调用该函数的对象
}
当我的交易处理程序调用如下的函数时,
total.combine(trans);
total的地址被绑定到隐式地this参数上,而rh绑定到了trans上。
这个函数一个值得关注的部分是它的返回类型和返回语句。一般来说,当我们定义的函数类似于某个内置运算符时,应该令该函数的行为尽量模仿这个运算符。内置的赋值运算符把他的左侧运算对象当成左值返回,因此为了与他保持一致性,combine函数必须返回引用类型。所以返回类型为Sales_data&。
int &refVal = ival; //refVal 是ival的引用,是ival的另一个名字
既然Sales_data的数据成员是private的,我们的read、print、和add函数也就无法正常编译了,这是因为尽管这几个函数是类的接口的一部分,但他们不是类的成员,类可以允许其他类或者函数访问它的非公有成员,方法是令其它类或函数成为它的友元(friend),只需要增加一条以friend关键字开始的函数声明语句即可
istream &read(istream &is, Person &item)//编译无法通过,要在类内部添加函数友元声明
{
is >> item.strName >> item.strAddress;
return is;
}
friend istream &read(istream &, Person &);//友元声明
7.14构造函数
构造函数的名字和类名相同。和其他函数不一样的是,构造函数没有返回类型;构造函数也有一个(可能为空的)参数列表和一个(可能为空的)函数体。类可以包含多个构造函数,和其他重载函数差不多, 不同的构造函数之间必须在参数数量或参数类型上有所区别。此外,构造函数不能被声明成const的。
默认构造函数又称之为合成的默认构造函数。
某些类不能依赖于合成的默认构造函数的情况:
1.只有当类没有声明任何构造函数时, 编译器才会自动地生成默认构造函数
2.如果类包含有内置类型或者复合类型的成员,则只有当这些成员全都被赋予了类内的初始值时,这个类才适用于合成的默认构造函数。(复合类型就是使用其他类型定义的类型,有三种复合类型,引用、指针、数组),内置类型就是(int、char、double、unsigned 等基本类型,若没有给这些成员初始值,默认值可能是未定义的)
3.如果类中包含一个其他类类型的成员且这个成员的类型没有默认构造函数,那么编译器就无法初始化该成员,也就无法生成默认的构造函数。对于这样的类来说,必须自定义默认构造函数。(classname() = default)
=default(默认构造函数)
构造函数初始值列表
Sales_data(const std::string &s) : bookNo(s) { }//未显示初始化的部分按默认初始化的方式初始化
等价于:
Sales_data(const std::string &s) : bookNo(s), units_sold(0), revenue(0) { }
Sales_data(const std::string &s, unsigned n, double p) : //初始值列表
bookNo(s), units_sold(n), revenue(p * n) { }
等价于
Sales_data(const std::string &s, unsigned n, double p)
{
bookNo = s;
units_sold = n;
revenue = p * n;
}
二者有一些小小的区别,那就是前者是通过初始化的方式,后者是通过赋值的方式
构造函数最好使用类内初始值或初始值列表
重载运算符:
friend ostream &operator << (ostream & os, const Sales_data & rhs);
friend istream& operator >> (istream &is, Sales_data & rhs);