五、实现隐藏(Hiding the Implementation)
五、实现隐藏(Hiding the Implementation)
5.1 设定限制(Setting limits)
- 为什么需要限制访问?
- 防止用户程序员误操作内部成员。
- 支持类库设计者随之修改内部实现,不影响外部代码。
- 为什么用class而不是struct?
class
默认成员是private
,而struct
默认是public
。class
更有利于隐藏实现细节 和限制接口
5.2 访问控制(Access Control)
实现隐藏
- 访问控制常常被称为实现隐藏(implementation hiding)。
- 访问控制在抽象数据类型的内部设置了边界,出于两个原因:
- 为了将接口(interface)与实现(implementation)分离。
- 为了明确客户端程序员能用和不能用的地方
关键字说明
public
:所有人都可以访问,private
:只有本类成员函数和类自己能访问。protected
:类似private ,唯一的区别是:继承类(“Inherited” class) 可以访问protected
成员(但不能访问private
成员),也就是说类自己和子类可以访问。
//C05:Private.cpp
//Setting the boundaryclass B
{
private:char j;float f;
public:int i;void func();
};void B::func()
{i = 0;j = '0';f = 0.0;
};void main()
{B b;b.i = 1;//OK.public//! b.j = '1'; // Illegal,private//! b.f = 1.0; //Illegal,private
}
类的格式
class X
{
public:void interface_fun();
private:void private_fun();int internal;int mx;
};
class X
{void private_fun();int internal;int mX;
public:void interface_fun();
};
class
默认是private
5.3 友元(Friends)
什么是friend
-
友元函数是一种有特权的函数。
-
允许外部函数(友元函数)或类(友元类)访问本类的私有成员。
-
一个普通的
public
成员函数具备三个逻辑上不同的特性:- 该函数可以访问类对象的私有部分。
- 该函数属于类的作用域中。(这个函数是在类的”命名空间里面“定义的,它是类的一部分)
- 该函数必须通过对象调用(拥有一个
this
指针)。
-
一个
public static
成员函数仅具有前两个特性。 -
一个
friend
函数仅具有第一个特性。
friend的作用
- 友元(friend)可以提高效率
- friend不能被继承(无论私有继承还是公有继承)
- 我们可以将一个全局函数(global function) 声明为友元;也可以将另一个类的成员函数 ,甚至整个类声明为友元。
Example1:一个全局函数作为一个类友元
//Example1
#include <iostream>
using namespace std;
class Time
{int hours,minutes;
public:void set(int nhours,int nminutes){hours = nhours;minutes = nminutes;}friend void show(Time& time);//friend functions
};void show(Time& time)//no prefix "friend"
{//access private numberscout << time.hours << ":" << time.minutes << endl;
}void main()
{Time time;time.set(20,30);show(time); // not a member of "time"
}
输出:
20:30
Example2:一个全局函数作为多个类的友元
//Example
#include <iostream>
using namespace std;
calss Boat;//to be a defined later
class Car
{
public:void set(int i){weight = i;}friend int totalWeight(Car& c,Boat& b);
private:int weight;
};class Boat
{
public:void set(int i){weight = i;}friend int totalWeight(Car& c,Boat& b);
private:int weight;
};int totalWeight(Car& c,Boat& b){return c.weight+b.weight;
}void main()
{Car c;c.set(10);Boat b;b.set(8);cout << "The total weight is " << totalWeight(c,b) << endl;
}
这里之所以要先声明
Boat
,因为C++的原则是使用前必须声明,如果不提前声明,那么在Car
里面的totalWeight
将Boat& b
作为形参就会报错 。
输出:
The total weight is 18
Example3:一个成员函数作为一个友元
//Example3
#include <iostream>
#include <string>
using namespace std;class Girl;//to be defined laterclass Boy
{
public:void init(string n){name = n;}void Disp(Girl& x);//member function
private:string name;
};class Girl
{
public:void init(string n){name = n;}friend void Boy::Disp(Girl& x);//friend
private:string name;
};void Boy::Disp(Girl& x)
{cout << "Boy's name is " << name << endl << "Girl's name is " << x.name << endl;
}void main()
{Boy b;b.init("Bob");Girl g;g.init("Kitty");b.Disp(g);
}
输出:
Boy's name is Bob
Girl's name is Kitty
这个样例正式因为
Boy::Disp(Girl& x)
被Girl
声明为friend
,所以它才可以执行x.name
。
Example4:一个类作为一个友元
//Example4
#include <iostream>
using namespace std;
class X
{
public:void Set(int i){x = i;}void Display(){cout << "x=" << x << "," << "y=" << y << endl;}
private:int x;static int y;friend class Y;//friend class 没有事先声明也可以
};int X::y = 1;//类外初始化静态成员变量class Y
{
public:void Set(int i,int j);void Display();
private:X a;//member object
};void Y::Set(int i,int j)
{a.x = i; //using private members of aX::y = j;//a::y? a.y?
}void Y::Display()//using private members of a
{cout << "x=" << a.x << ",";cout << "y=" << X::y << endl;
}void main()
{X b;b.Set(5);b.Display();Y c;c.Set(6,9);c.Display();b.Display();
}
输出:
x=5,y=1
x=6,y=9
x=5,y=9
在 C++ 中,
friend class
语句 不需要提前声明该类,编译器会自动隐式地把Y
当作类名并接受,即便此时它还没定义。C++ 编译器在处理friend
时是宽容的,它允许你声明一个类是朋友,即使它还没有被声明或定义,因为编译器知道之后可能会出现Y
的定义,它会延迟检查这个类是否存在。
这里对statci进行一下介绍
static
在C++中,static
成员变量或成员函数表示“属于类本身而不是对象 ”。这意味着:
-
所有对象共享一份
static
变量statci
成员变量在类中声明,在类外定义- 它的声明周期贯穿程序整个运行周期
- 不管有多少个对象,
static
成员变量只有一份内存拷贝。
-
static
成员变量的定义在类外进行有且仅有一次初始化:int x::y = 1;
这行代码就是对静态成员变量
y
进行初始化,只能写一次,而且必须在类外 -
访问方式:
-
类名访问:
X::y
。 -
或者对象访问:
obj.y
(虽然能访问,但推荐用类名访问以强调这是类共享的)X::y = j;//a::y? a.y?
所以这里
a.y
是可以的,但a::y
不可以,因为::
要求左边是类型名 或命名空间 ,而不是对象。
-
5.4 类(The class)
类的结构基本样式:
class Date
{
public:void SetDate(int y,int m,int d);int IsLeapYear();void Print();
private:int year,month,day;
};
//member function's definition
成员函数定义:
- 使用作用域解析符
::
。
class Date
{
public:void SetDate(int y,int m,int d);int IsLeapYear();void Print();
private:int year,month,day;
};void Data::SetDate(int y,int m,int d)
{year = y;month = m;day = d;
}
实例
- Handle类隐藏实现细节(Pimpl手法)
Handle.h
//C05:Handle.h
//Handle calsses
#ifndef HANDLE_H
#define HANDLE_H
class Handle
{struct Cheshire;//declaration onlyCheshire* smile;
public:void initialize();void cleanup();int read();void change(int);
};
#endif
Handle.cpp
//C05:Handle.cpp
//Handle implementation
#include "Handle.h"
#incldue <cassert>struct Handle::Cheshire
{int i;
};
…………
UseHandle.cpp
//C05:Use.Handle.cpp
#include "Handle.h"int main()
{Handle u;u.initialize();u.read();u.change(1);u.cleanup();
}
5.5 总结(Summary)
- 访问控制: 控制接口和实现分离
- friend: 特殊访问权限
- 静态数据成员: 类级别共享数据
- Handle类设计: 实现与接口分离的典范