类
在c++中,可以使用类定义自己的数据类型。类的基本思想是数据抽象和封装。数据抽象是一种依赖于接口和实现分离的编程技术。
类的接口包括用户所能执行的操作;类的实现规则包括类的数据成员、负责接口实现的函数体以及定义类所需的各种私有函数。
封装实现了类的接口和实现的分离。封装后隐藏了类的实现细节。
7.1 定义抽象数据类型
抽象数据类型不允许用户直接访问它的数据成员。
7.1.1 设计Sales_data类
Sales_data类的数据成员包括:
- string类型的bookNo,表示ISBN编号;
- unsigned类型的units_sold,表示某本书的销量;
- double类型的revenue,表示这本书的总销售输入;
Sales_data类的接口应该包含以下操作:
- 一个isbn成员函数,用于返回对象的ISBN编号;
- 一个combine成员函数,用于将一个Sales_data对象加到另一个对象上;
- 一个名为add的函数,执行两个Sales_data对象的加法;
- 一个read函数,将数据从istream读入到Sales_data对象中;
- 一个print函数,将Sales_data对象的值输出到ostream中;
7.1.2 定义改进的Sales_data类
定义类的时候,需要在类函数体内定义数据成员、成员函数和非成员接口函数。其中成员函数的声明必须在类的内部,定义在类的内部和外部都可。而作为接口组成部分的非成员函数,声明和定义都在类的外部。
struct Sales_data {
//数据成员
string bookNo;
unsigned units_sold = 0;
double revenue = 0.0;
//成员函数
string isbn() const { return bookNo; };
Sales_data& combine(const Sales_data&);
double avg_price() const;
};
//Sales_data的非成员函数
Sales_data add(const Sales_data&, const Sales_data&);
ostream& print(ostream&, const Sales_data&);
istream& read(istream&, Sales_data&);
this:对于成员函数isbn(),它只返回了类的bookNo数据成员,实际上返回的是当前对象的数据成员。
const成员函数:isbn函数的参数列表之后的const关键字,其作用是修改隐式this指针的类型。在参数列表后的const表示this是一个指向常量的指针,这样使用const的成员函数被称为常量成员函数。
在类的外部定义成员函数,其中Sales_data::avg_price是作用域运算符,表示avg_price函数是属于类的作用域内。
double Sales_data::avg_price() const {
if (units_sold)
return revenue / units_sold;
else
return 0;
}
定义一个返回this对象的函数:
Sales_data& Sales_data::combine(const Sales_data& rhs) {
units_sold += rhs.units_sold;
revenue += rhs.revenue;
return *this;
}
7.1.3 定义类相关的非成员函数
类的作者常常需要一些辅助函数,尽管这些函数定义的操作从概念上来说属于类的接口的组成部分,但是实际上不属于类本身。
定义read和print函数:
istream& read(istream& is, Sales_data& item)
{
double price = 0;
is >> item.bookNo >> item.units_sold >> price;
item.revenue = price * item.units_sold;
return is;
}
ostream& print(ostream& os, const Sales_data& item)
{
os << item.isbn() << " " << item.units_sold << " "
<< item.revenue << " " << item.avg_price();
return os;
}
定义add函数:
Sales_data add(const Sales_data& lhs, const Sales_data& rhs)
{
Sales_data sum = lhs;
sum.combine(rhs);
return sum;
}
7.1.4 构造函数
每个类都通过构造函数分别定义了它的对象被初始化的方法,构造函数的任务是初始化类对象的数据成员,无论何时类被创建,都会执行构造函数。
构造函数没有返回类型,其形参列表可以为空也可由形参,函数体也可以为空也可有内容。类可以包含多个构造函数,和其他重载函数类似。
合成的默认构造函数:如果一个类并没定义任何构造函数,则类通过一个特殊的构造函数来控制默认初始化过程,叫做默认构造函数,由编译器创建的构造函数叫做合成的默认构造函数,如果存在类内初始值就用它来初始化成员,否则默认初始化该成员。
合成的默认构造函数只适合非常简单的类,一般都得定义自己的默认构造函数。默认构造函数即以自身的类名作为函数名。
//构造函数
Sales_data() = default;
Sales_data(const string &s):bookNo(s){}
Sales_data(const string &s ,unsigned n, double p):
bookNo(s),units_sold(n),revenue(p*n){}
Sales_data(istream&);
Sales_data() = default:该构造函数不接受任何实参,所以它是个默认构造函数,定义该构造函数的目的仅仅是因为需要其它形式的构造函数。
第二和第三个构造函数中出现了新的部分:冒号和花括号之间的代码,该部分被称为构造函数初始值列表,其中花括号定义了空的函数体,负责为新创建的对象的一个或几个数据成员赋初值。
第三个构造函数的定义在类的外部。定义构造函数时必须指明该构造函数时哪个类的成员。
调用各构造函数的代码如下:
Sales_data total;
Sales_data t1("10001-100");
Sales_data t2("10001-100",5,30.5);
Sales_data t3(cin);
7.1.5 拷贝、赋值和析构
除了类的对象初始化之外,还需要控制拷贝、赋值和销毁对象时发生的行为。如果没有主动定义这些操作,编译器将合成它们。比如没有定义赋值语句,就会将各个数据成员进行赋值。
7.2 访问控制与封装
在C++中,可以使用访问说明符来加强类的封装性。
- 定义在public说明符之后的成员在整个程序内可被访问,public成员定义类的接口
- 定义在private说明符之后的成员可以被类的成员函数访问,但是不能被使用该类的代码发昂我,private封装了类的实现细节
class关键字和struct关键字都可以用来定义类,但是class关键字在第一个访问说明符之前的成员都是private的,而struct关键字在第一个访问说明符之前的成员都是public的。
7.2.1 友元
如果类的数据成员是private的,则在函数外定义的类的接口无法正常编译。如果想让其他类或者函数访问它的非公有成员,就应该令其他类和函数称为他的友元,如下:
class Sales_data{
//为类的非成员函数做的友元声明
friend Sales_data add(const Sales_data&, const Sales_data&);
friend istream &read(istream&, Sales_data&);
friend ostream &print(ostream&, const Sales_data&);
public:
Sales_data()=defult;
...
private:
string bookNo;
unsigned units_sold=0;
double revenue=0.0;
}