类的基本思想是数据抽象和封装。数据抽象是一种依赖于接口和实现分离的编程技术。类的接口包括用户所能执行的操作;类的实现则包括类的数据成员,负责接口实现的函数体以及定义所需的各种私有函数。封装实现了类的接口和实现的分离。封装后的类能隐藏它的实现细节,类的用户只能使用接口而无法访问实现的具体部分。
类允许我们自己给应用定义新的类型,比如某个物体的属性。它的出现使得修改程序更容易,比如游戏角色的属性。
类有两个基本属性:一个是数据抽象(定义数据成员和函数成员);另一个是封装,保护类的成员不被随意访问的能力(private, protected)。
类还可以将其他类或函数设为友元,这样它们就能访问类的非公有成员。
类有默认构造函数和析构函数,构造函数是用来初始化对象,它是一种特殊的成员函数;析构函数是类结束后回收对象,也是一种特殊的成员函数。
比如我定义一个book类,书有名字,书号,编号等:
class Book {
public:
Book(string& book_name, string& book_no, int book_id, double revenue, double units_sold) :
bookName(book_name), bookNo(book_no), bookID(book_id), revenue(revenue), units_sold(units_sold) { }
string isbn() const { return bookNo; }
double book_price(string& book_name, int book_id);
double avg_price() const;
private:
string bookName;
string bookNo;
int bookID;
double revenue;
double units_sold;
};
C++ Primer中的改进Sales_data类:
struct Sales_data {
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;
};
Sales_data add(const Sales_data&, const Sales_data&);
std::ostream &print(std::ostream&, const Sales_data&);
std::istream &read(std::istream&, Sales_data&);
关于this
当我们调用成员函数时,实际上是在替某个对象调用它。成员函数通过一个名为this的额外的隐藏参数来访问调用它的那个对象。当我们调用一个成员函数时,用请求该函数的对象地址初始化this。this的目的是一直指向原来那个对象(我们自己调用的),所有this是一个常量指针,我们不能改变this中保存的地址。
引入const成员函数
默认情况下,this的类型是指向类类型非常量版本的常量指针。参数列表后面的const表示this是一个指向常量的指针。如:
std::string Sales_data::isbn(const Sales_data *const this)
{
return this->isbn;
}
在类的外部定义成员函数(对book类)
double Book::avg_price() const {
return (this->revenue) / (this->units_sold);
}
定义构造函数(针对Sales_data类)
struct Sales_data {
Sales_data() = default;
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&);
std::string bookNo;
unsigned units_sold = 0;
double revenue = 0.0;
};
访问控制与封装(我们重写Sales_data类,跟Book类相似)
class Sales_data {
public:
Sales_data() = default;
Sales_data(const std::string &s, unsigned n, double p) :
bookNo(s), units_sold(n), revenue(p * n) { }
Sales_data(const std::string &s) : bookNo(s) { }
Sales_data(std::istream&);
std::string isbn() const { return bookNo; }
Sales_data &combine(const Sales_data&);
private:
double avg_price() const {
return units_sold ? revenue / units_sold : 0;
}
std::string bookNo;
unsigned units_sold = 0;
double revenue = 0.0;
};
一个类可以包含0个或多个访问说明符,而且对于某个访问说明符能出现多少次没有严格限定。每个访问说明符指定了接下来的成员的访问级别,其有效范围直到出现下一个访问说明符或直达类的结尾处为止。
友元
类允许其他类或函数访问它的非公有成员,方法是令其他类或函数成为它的友元。
如果类想把一个函数作为它的友元,只需要增加一条以friend关键字开始的函数声明即可。
友元声明只能出现在类定义的内部,但是在类内出现的具体位置不限。
友元不是类的成员也不受它所在区域访问控制级别的约束。
但是,一般来说,最好在类定义开始或结束前的位置集中声明友元。
友元的声明仅仅指定了访问的权限,而非一个通常意义上的函数声明。如果我们希望类的用户能够调用某个友元函数,那么我们就必须在友元声明之外再专门对函数进行一次声明。
示例代码如下:
class Sales_data {
friend Sales_data add(const Sales_data&, const Sales_data&);
friend std::istream &read(std::istream&, Sales_data&);
friend std::ostream &print(std::ostream&, const Sales_data&);
public:
Sales_data() = default;
Sales_data(const std::string &s, unsigned n, double p) :
bookNo(s), units_sold(n), revenue(p * n) { }
Sales_data(const std::string &s) : bookNo(s) { }
Sales_data(std::istream&);
std::string isbn() const { return bookNo; }
Sales_data &combine(const Sales_data&);
private:
std::string bookNo;
unsigned units_sold = 0;
double revenue = 0.0;
};
Sales_data add(const Sales_data&, const Sales_data&);
std::istream &read(std::istream&, Sales_data&);
std::ostream &print(std::ostream&, const Sales_data&);
封装的益处:
- 确保用户代码不会无意间破坏封装对象的状态。
- 被封装的类的具体实现细节可以随时改变,而无须调整用户级的代码。