类的深入剖析(第二部分)
一、const对象和const成员函数
我们使用const对象和const成员函数来限制对对象的修改,以增强最小权限原则。
1、const对象
const Time noon(12,0,0);//const对象不能被修改
试图修改const对象的操作将导致编译错误
2、const成员函数
成员函数的声明也可以为const成员函数,其不能修改类对象的值。
1、 将修改对象的数据成员的成员函数定义为const会导致编译错误。
2、定义为const的成员函数它又调用同一类的同一实例的非const成员函数将导致编译错误。
3、const对象调用非const成员函数将导致编译错误。
4、不允许对构造函数和析构函数进行const声明,但是const对象可以调用构造函数和析构函数。
下面以一个简单地Time类来说明:
/*函数原型的定义,函数实现不需要改变的声明为const*/
int getHour() const;
int getMinute() const;
int getSecond() const;
void printUniversal() const;
/*函数实现时也需要在函数原型后面加上const限定词*/
int Time::getHour() const;
......
void Time::ntUniversal() const;
生成对象时,声明为const对象只能调用const函数;
而非const对象既可以调用const函数,也可以调用非const函数。
const与非const对比:
/*类的定义*/
class Time
{
public:
void setTime(int,int,int);//非const函数。
void setTime(int,int,int) const;//const函数。
//....
};
/*对象的生成*/
int main()
{
const Time noon;//const对象。
Time t1;//非const对象。
noon.setTime(12,0,0);//const对象调用const函数。
t1.setTime(9,0,10);//非const函数都可以调用。
}
3、成员初始化器
成员初始化器用来初始化类的数据成员,所有数据成员都可以用成员初始化器语法来进行初始化。
必须使用成员初始化器进行初始化的数据有:
- const数据成员;
- 引用的数据成员;
- 成员对象;
成员初始化器列表在构造函数体执行之前。
class ...//类的定义
{
private:
int count;
count int increment;//const数据成员
};
/*使用成员初始化器来初始化*/
Increment::Increment(int c,int i)//构造函数
:count(c),//可以使用成员初始化器初始化,也可以在构造函数体中初始化。
increment(i)//只可以在这初始化。
{
......
}
1、成员初始化器以冒号开始结尾没有任何符号。
2、如果有多个成员初始化器,那么他们之间用逗号隔开。
二、组成:对象作为类的成员
成员对象以在类的定义中声明的顺序构造(如果有多个),并不以他们在成员初始化列表的顺序,并且在包含他们的类对象之前建立。
下面举Date类和Employee类来说明:
//Date类
private:
int month;
int day;
int year;
//Employee类
private:
char firstName[25];
char lastName[25];
const Date bithDate;//Date类生成bithDate对象
const Date hireDate;//Date类生成hireDate对象
在Employee函数实现的构造函数中:
Employee::Employee(const char * const first,const char * const last,const Date & dateOfBirth,const Date & dateOfHire)
:bithDate(dateOfBirth),
hireDate(dateOfHire)//分别调用了两次拷贝构造函数初始化数据成员bithDate和hireDate。
{
......
}
如果成员对象不是用成员初始化器形式进行初始化并且成员对象的类没有提供默认的构造函数,将产生编译错误。
如果类的成员是另一个类的对象,即使制定这个成员对象为public的,也不会破坏该成员对象的封装性和private成员的隐蔽性,但是,它确实破坏了包含类实现的封装性和隐蔽性,因此类类型成员的对象必须是private的,就像所有其它数据成员一样。
三、友元函数和友元类
类的友元函数在类的定义域之外定义,却具有访问类的非public(以及public)成员的权限,单独的函数或者整个类都可以被声明为另一个类的友元;在类的定义中函数原型前加 friend关键字,将函数声明为类的友元。
要将类Class Two的所有成员函数声明为Class One 的友元,应在ClassOne定义中加入如下形式的声明:
friend Class Two;
下面第一个例子说明友元函数:
#include<iostream>
using namespace std;
class Count
{
friend void setX(Count &,int );//friend declaration.
public:
Count()
{
x = 0;
}
void print() const
{
cout << "x is :" << x << endl;
}
private:
int x;
};
void setX(Count &c,int number)
{
c.x = number;//allowed because setX is a friend of Count.
}
int main()
{
Count counter;
counter.print();
setX(counter,10);//set X using a friend function.
counter.print();
return 0;
}
第二个例子说明了友元类:
类A是类B的友元,类A可以通过类B的对象访问类B,特别是类B的私有数据成员。
四、使用this指针
每个对象都能使用一个this指针来指向自己的地址,对象的this指针不是对象本身的一部分。
每个类的成员函数都包含一个this指针,它指向这个成员函数被调用时所属的那个对象。例如:
object1.set(~);//set函数存在一个this指针指向object1;
object2.set(~);//set函数依然存在一个this指针指向object2.
1、隐式和显示的使用this指针来访问对象的数据成员
this指针作为一个隐式的参数传递给对象的每个非static成员函数;对象可以隐式或显式地使用this指针来引用他们的数据成员和成员函数。
隐式和显式地使用this指针:
(1)、隐式使用:
仅指明该数据成员名称。
(2)、显式使用:
this->//箭头运算符
(*this).//圆点运算符
例如要输出一程序中x的值,可以有三种方法:
cout " x = " << x ;//隐式使用
cout " this->x = " << this->x ;//使用箭头运算符
cout << " (*this).x = " << (*this).x ;//使用圆点运算符
2、使用this指针使串联的函数成为可能
使多个函数在同一条语句中调用:
//函数返回其对象的引用
t.setHour(10).setMinute(25).setSecond(50);
返回类型的比较:
Time setHour();//返回的是Time的一个副本
Time &setHour();//返回Time的引用
下面一个例子说明:
Time &Time::setHour(int h,int m,int s)
{
setHour(h);
setMinute(m);
setSecond(s);
return *this;
}//返回对象的引用,操纵者能够使用该对象。
void Time::printUniversal() const;//无返回类型
void Time::printStandard() const;//无返回类型
int main()
{
t.setHour(10).setMinute(25).setSecond(50);//正确
t.setTime(11,1,10).printUniversal();//正确
t.setTime(11,1,10).printUniversal().printStandard();//错误
t.printUniversal().setTime(11,1,10);//错误
}
五、static类成员
类的static数据成员看上去就像全局变量,但他们只在类的作用域起作用;每个类的static数据成员只有一份拷贝,由所有该类的对象共享。
如果该类中一个对象都没有被实例化过,这个static数据成员依然存在,至始至终都有这一份。有无对象时 都可以读它的数据。
1、静态数据成员的声明
变量声明前加static关键字,可以被声明为public,private,和protected
2、静态数据成员的初始化
静态数据成员在类的定义体 之外初始化。初始化时在静态数据成员名前加类名
double Account::interestRate = 0.0589//Account为类名
特殊情况下,整型const static数据成员可以在类声明时初始化。
静态数据成员的初始化不能放在头文件中,而是在类的成员函数定义的执行文件中。
3、静态数据成员的访问
类的静态数据成员可以在类的成员函数中直接使用。例如:
double Account::dailyReturn()
{
return (interestRate/365*amount);
}
非成员函数中(测试代码中的函数)(在类的非成员函数中使用数据成员,改数据成员应为公有的),静态数据成员通过下述两种方式访问:
成员访问运算符( -> , . )---------有对象时;
数据成员名前加类名和二元作用域分辨运算符( ::)------无对象时。
Time::静态数据成员;
当没有类的对象存在时,要访问类的private和protected的static数据成员,提供一个公有的静态成员函数,并通过在函数名前加类名和二元作用域分辨运算符的方式调用。(如果有类的对象存在时,可以通过公有的接口访问。)
静态成员函数的声明与非静态成员函数的声明是一样的,除了类中静态成员函数的声明前加关键字static.并且函数不能声明为const.
static数据成员和static成员函数只在声明时加static,在实现和初始化不需要加static,例如:
static int count;//声明静态数据成员
int Employee::count = 0;//初始化
static int getCount();//静态成员函数的声明,只在原型中。
int Employee::getCount()//静态成员函数的实现
{
}
const 成员函数在声明和实现时都需要加上const,例如:
int getHour() const;//声明
int Time::getHour() const//实现
{
}
六、代理类
代理类将客户代码与实现的变化相隔离
下面以一个简单地例子来说明:
class Implementation//定义Implementation类
{
public:
Implementation(int v)
:value(v)
{
}//空的函数体
void setValue(int v)
{
value = v;
}
int getValue() const
{
return value;
}
private:
int value;
};
/*代理类,此代理类提供给用户,私有数据成员以及成员函数的实验完全不能被看到*/
class Implementation;//声明一个需要被代理的类
class Interface
{
public:
Interface(int);//代理类自己的构造函数
void setValue(int);//与被代理的类完全一样
int getValue() const;//与被代理的类完全一样
~Interface();//代理类自己的析构函数
private:
Implementation *ptr;//一个私有数据成员指向被代理类的指针
};
#include "Interface.h"
#include "Implementation.h"
Interface::Interface(int v)
:ptr(new Implementation(v))//通过私有数据成员的指针来操纵被代理的类
{
}
void Interface::setValue(int v)//代理类中的setValue()
{
ptr->setValue(v);//被代理类中的setValue()
}
int Interface::getValue() const //代理类中的getValue()
{
return ptr->getValue();//被代理类中的getValue()
}
Interface::~Interface()
{
delete ptr;//删除指针
}
#include<iostream>
using namespace std;
#include "Interface.h"
int main(void)
{
Interface i(12);
cout << "Interface contains :" << i.getValue() << "before setValue" << endl;
i.setValue(10);
cout << "Interface contains :" << i.getValue() << "after setValue" << endl;
return 0;
}