什么是封装?
封装(Encapsulation)是将数据和处理数据的程序(procedure)组合起来,仅对外公开接口(interface),达到信息隐藏(information hiding)的功能。封装的优点是能减少耦合(Coupling)。C++、Java、C# 等语言定义对象都是在语法中明确地使用类型(Class)来做到封装。
封装可以隐藏实现细节,使得代码模块化;封装是把过程和数据包围起来,对数据的访问只能通过已定义的界面。面向对象计算始于这个基本概念,即现实世界可以被描绘成一系列完全自治、封装的对象,这些对象通过一个受保护的接口访问其他对象。在面向对象编程上可理解为:把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。
封装的三个条件:
1.有一个清楚的边界。
2.有确定的接口,用来接收用户发送的信息。
3.受保护的内部实现。
数据封装:
c++仰仗强有力的类型检查能力,正确无误的识别类型、对象、成员、变量与操作,匹配参数传递中的实体类型、过滤公有、私有的访问权限,并用可见性规则挑剔不合格常规默认名字,使得编程的数据外在安全性得到保障。
c++通过类机制对类的数据访问权限的规定,拒绝或屏蔽来自类外部直接访问类内操作的数据。这种屏蔽是类对象内部数据的有选择屏蔽,从而使数据仅在类对象内部的专门操作(成员函数)来操控,以保证数据的内在安全性。这种内在的安全性正是语言设计者在编程层面对数据经受各种目的的访问所造成的内容不确定性的应对技术,即类机制的数据封装技术。
利用类的数据封装,提高了对象的访问的安全性。用户不能直接操作对象中的数据分量,只能以对象的身份去操作(调用成员函数),去影响对象的值,从而职责分明:如果对象执行了某一功能的操作,却没有达到该功能的所声称的效果,则找类算账;如果对象执行了操作,达到了该操作所声称的功能,还不理想,那一定是应用错误,由编程者自己负责。职责明确使类定义和其实现比数据的过程化处理更容易重用和维护了。
批注:数据封装对对象来说,带来了统一的分布格局和严格的访问限制。由于对象数据往往不是独立的--同类对象之间需要在类内共享数据,异类对象之间需要数据往来,于是数据的操作经常是以一个局外人的身份往来于诸多不同的对象之间,使得屏蔽技术所主导的数据封装在编程之中又带来了访问不便。于是,在数据屏蔽的基础上,类的静态成员和友元便作为完善数据封装技术的语言成分粉墨登场了。
下面具体阐述一下类和对象
1.对象
class 类名
{
private:
成员数据;
成员函数;
public:
成员数据;
成员函数;
protected:
成员数据;
成员函数;
};
用private限定的成员为私有成员,私有成员限定在该类的内部使用。(只允许该类中的成员函数使用私有的成员数据,对于私有的成员函数,只能被该类内的成员函数调用;类就相当于私有成员的作用域。 )
用public限定的成员为公有成员,公有成员的数据或函数,不受类的控制,可以在类内类外自由使用。对类而言是透明的。
用protected限定的成员为保护成员,只允许在类内以及该类的派生类中使用保护的数据或函数。该类及该类的派生类是保护成员的作用域。
私有成员 | 公有成员 | 保护成员 | |
类内函数 | 可以调用 | 可以调用 | 可以调用 |
类外函数 | 不可调用 | 可以调用 | 不可调用 |
在定义类时,只是定义了一种导出的数据类型,并不为类分配存储空间,所以,在定义类中的数据成员时,不能对其初始化。
//在c++中,函数调用需要建立栈环境,进行参数复制,保护调用现场,返回时,还要进行返回值复制,恢复调用现场。这些工作都是与完成特定任务的操作无关的额外
//开销。程序效率由于该项工作而受到影响,所以,流行的CPU都已经将函数调用的额外工作硬件化了,以次来减少运行开销。尽管如此,调用工作还是有一些微小的
//开销,如果频繁调用很少语句的小函数,则这些开销对性能的影响还不好说。
#include "stdafx.h"
#include "iostream"
using namespace std;
bool isnumber (char ch)
{
return ch>='0' && ch<='9' ? 1:0 ;
}
int _tmain(int argc, _TCHAR* argv[])
{
char c;
while(cin>>c && c!='\n')
if(isnumber(c))
cout<<"hello world.\n";
else
cout<<"good morning.\n";
return 0;
}
//程序不断到输入设备中读取数据,频繁调用isnumber函数,isnumber是个小函数,所以函数调用的开销相对来说占的比重就大了。下面对程序进行更改:
#include "stdafx.h"
#include "iostream"
using namespace std;
int _tmain(int argc, _TCHAR* argv[])
{
char c;
while(cin>>c && c!='\n')
if(c>='0' && c<='9' ? 1 : 0)
cout<<"hello world.\n";
else
cout<<"good morning.\n";
return 0;
}
//该程序在if语句中用表达式替换了函数调用。在程序运行上,因为失去了大量的函数调用开销,提高了执行效率。
#include "stdafx.h"
#include "iostream"
using namespace std;
inline bool isnumber(char); //内联声明
int _tmain(int argc, _TCHAR* argv[])
{
char c;
while(cin>>c && c!='\n')
if(isnumber(c))
cout<<"hello world.\n";
else
cout<<"good morning.\n";
return 0;
}
bool isnumber(char ch)
{
return ch>='0' && ch<='9' ? 1 : 0;
}
//由于isnumber函数比相应的表达式可读性好,所以若程序中多处出现isnumber,而又讲其替换为复杂的实现语句的话,就会降低程序的可读性。
//我们既要用函数用来体现其结构化和可读性,又要使效率尽可能地高。解决办法就是将这种小函数声明为内联。
内联函数体应该尽可能小,且要结构简单,这样嵌入的代码才不会影响调用函数的主体结构。所以在内联函数中,不能含有复杂的结构控制语句,如switch和while。如果内联函数有这些语句,则编译器将无视内联声明,只是视同普通函数那样产生调用代码。当然递归函数属于结构复杂的函数,也不能用来做内联函数。(PS:正因为函数调用有开销,而内联函数调用几乎没有调用开销,所以编程时就应尽可能内联函数调用。)
3.类类型
2.利用指针访问私有数据成员。
3.利用函数访问私有数据成员。
4.利用引用访问私有数据成员。
//1.通过公有函数为私有函数赋值
#include "stdafx.h"
#include "iostream"
using namespace std;
class Test
{
private:
int x,y;
public:
void Setxy (int a, int b)
{
x=a;
y=b;
}
void Printxy ()
{
cout<<"x="<
//2.利用指针访问私有数据成员
#include "stdafx.h"
#include "iostream"
using namespace std;
class Test
{
private:
int x,y;
public:
void Setxy (int a, int b)
{
x=a;
y=b;
}
void Getxy (int *px, int *py)
{
*px=x;
*py=y;
}
void Printxy ()
{
cout<<"x="<
//3.利用函数访问私有数据成员
#include "stdafx.h"
#include "iostream"
using namespace std;
class Test
{
private:
int x,y;
public:
void Setxy (int a, int b)
{
x=a;
y=b;
}
int Getx ()
{
return x;
}
int Gety ()
{
return y;
}
void Printxy ()
{
cout<<"x="<
//4.利用引用访问私有数据成员
#include "stdafx.h"
#include "iostream"
using namespace std;
class Test
{
private:
int x,y;
public:
void Setxy (int a, int b)
{
x=a;
y=b;
}
void Getxy (int &px, int &py)
{
px=x;
py=y;
}
void Printxy ()
{
cout<<"x="<