上一节中定义的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;} // 直接定义在类内部的函数是隐式inline的函数 Sales_data& combine(const Sales_data&); double avg_price() const; std::string booNo; unsigned units_sold = 0; double revenue = 0.0; };
面向对象小叙
面向对象的根本思想是模拟真实的生活来进行编程的模块化和对象化的设计。
打比方说,当你的朋友让你帮他做一件事的时候,需要以下的过程。
1. 你收到朋友的请求。
2. 你用你的想法驱动必要的躯体(手、脚等)完成这个动作
在这个过程中就明显是一个面向对象的本质过程。
首先,你的朋友只能通过跟你对话来让你帮他完成这件事,而不是你的朋友通过控制你的手、脚来完成这件事情。
而你的耳朵,在这里充当了对外接收信息的责任,可以说耳朵就是一个接口。
控制自己躯体的事情只能自己来完成,别人是无法帮你完成的,这就是封装性。对外部来说如何控制你的手、脚是透明的,不可见也不可感知。
所以我们设计的类型,也要实现以上的特性,提供的对外部分是我们想对外提供,并且只能按照我们的预定方式去影响对象本身。
如何实现类型的封装
在C++中访问说明符用来实现对类型的封装性。
public/private
这两个是访问限制说明符具体作用来看以下代码:
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 &is); 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 };
Sales_data data, data2; data.isbn(); data.combine(data2); data.avg_price(); // 编译错误: 不可调用,私有只能在类内部使用 data.bookNo; // 编译错误: 不可调用,私有只能在类内部使用 data.units_sold; // 编译错误: 不可调用,私有只能在类内部使用 data.revenue; // 编译错误: 不可调用,私有只能在类内部使用
我们生命类型可以使用两个关键字class和struct
这两个关键字的唯一区别是,class默认为private访问权限,struct默认为public访问权限。
默认即不显示写出访问限定符时的权限。
一些特别情况
我们设想这样一种场景。你到医院了。
医生需要为你检查,这个时候医生就需要你比较隐私的东西(血液、唾液、毛发等)
但是这个时候是要你授权允许的,如果你不允许,医生也不可能拿走。
而在编程中也存在这种情况。
有时候你需要对某些特殊的类型打开你的访问权限,我们需要用到的东西是友元。
友元使用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 &is); 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 };
类封装的意义
类封装的主要优点:
1. 使用者不会无意间破坏对象
2. 在实现细节调整的时候不需要进行使用侧的修改。
封装性对于制造类的人和使用类的人是一个双赢的事情。
一方面对象被破坏的风险将降低很多。
另一方面,使用者也无需在意类制造者的内部逻辑更改。