【C++基础】第四十二课:[类]访问控制与封装

访问说明符,public,private,class关键字,友元,friend

Posted by x-jeff on June 3, 2022

【C++基础】系列博客为参考《C++ Primer中文版(第5版)》C++11标准)一书,自己所做的读书笔记。
本文为原创文章,未经本人允许,禁止转载。转载请注明出处。

1.访问控制与封装

在C++语言中,我们使用访问说明符(access specifiers)加强类的封装性。

  • 定义在public说明符之后的成员在整个程序内可被访问,public成员定义类的接口。
  • 定义在private说明符之后的成员可以被类的成员函数访问,但是不能被使用该类的代码访问,private部分封装了(即隐藏了)类的实现细节。

再一次定义Sales_data类,其新形式如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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个或多个访问说明符,而且对于某个访问说明符能出现多少次也没有严格限定。每个访问说明符指定了接下来的成员的访问级别,其有效范围直到出现下一个访问说明符或者到达类的结尾处为止。

1.1.使用class或struct关键字

我们可以使用class或struct定义类。‼️使用class和struct定义类唯一的区别就是默认的访问权限:如果我们使用struct关键字,则定义在第一个访问说明符之前的成员是public的;相反,如果我们使用class关键字,则这些成员是private的。

2.友元

既然Sales_data的数据成员是private的,我们的read、print和add函数也就无法正常编译了,这是因为尽管这几个函数是类的接口的一部分,但它们不是类的成员。

类可以允许其他类或者函数访问它的非公有成员,方法是令其他类或者函数成为它的友元(friend)。如果类想把一个函数作为它的友元,只需要增加一条以friend关键字开始的函数声明语句即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Sales_data{
//为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 : 
	double avg_price() const { return units_sold ? revenue/units_sold : 0; }
	std::string bookNo;
	unsigned units_sold = 0;
	double revenue = 0.0;
};
//Sales_data接口的非成员组成部分的声明
//解释见第2.1部分
Sales_data add(const Sales_data&, const Sales_data&);
std::istream &read(std::istream&, Sales_data&);
std::ostream &print(std:: ostream&, const Sales_data&);

友元声明只能出现在类定义的内部,但是在类内出现的具体位置不限。友元不是类的成员也不受它所在区域访问控制级别的约束。

一般来说,最好在类定义开始或结束前的位置集中声明友元。

封装有两个重要的优点:

  • 确保用户代码不会无意间破坏封装对象的状态。
  • 被封装的类的具体实现细节可以随时改变,而无须调整用户级别的代码。

2.1.友元的声明

友元的声明仅仅指定了访问的权限,而非一个通常意义上的函数声明。如果我们希望类的用户能够调用某个友元函数,那么我们就必须在友元声明之外再专门对函数进行一次声明(类的外部)。

许多编译器并未强制限制友元函数必须在使用之前在类的外部声明。