一、私有数据成员的使用
1.取值和赋值成员函数
面向对象的约定就是保证所有数据成员的私有性。一般我们都是通过公有成员函数来作为公共接口来读取私有数据成员的。某些时候,我们称这样的函数为取值和赋值函数。
取值函数的返回值和传递给赋值函数的参数不必一一匹配所有数据成员的类型。
#include iostream.h
class Date
{
int mo, da, yr;
public:
Date(int m,int d,int y) { mo=m; da=d; yr=y; }
int getyear() const { return yr; }
void setyear(int y) { yr = y; }
};
int main()
{
Date dt(4,1,89);
cout<<the year="" is:="" <<dt.getyear()<<endl;
dt.setyear(97);
cout<<the new="" year="" is:="" <<dt.getyear();
return 0;
}
上面的例子很简单,不分析了。要养成这样的习惯,通过成员函数来访问和改变类中的数据。这样有利于软件的设计和维护。比如,改变Date类内部数据的形式,但仍然用修改过的getyear()和setyear()来提供访问接口,那么使用该类就不必修改他们的代码,仅需要重新编译程序即可。
2.常量成员函数
注意上面的程序中getyear()被声明为常量型,这样可以保证该成员函数不会修改调用他的对象。通过加上const修饰符,可以使访问对象数据的成员函数仅仅完成不会引起数据变动的那些操作。
如果程序声明某个Date对象为常量的话,那么该对象不得调用任何非常量型成员函数,不论这些函数是否真的试图修改对象的数据。只有把那些不会引起数据改变的函数都声明为常量型,才可以让常量对象来调用。
3.改进的成员转换函数
下面的程序改进了从Date对象到CustomDate对象的成员转换函数,用取值和赋值函数取代了使用公有数据成员的做法。(以前的程序代码在上一帖中)
#include iostream.h
class CustomDate
{
int da,yr;
public:
CustomDate() {}
CustomDate(int d,int y) { da=d; yr=y; }
void display() const {cout<<yr<<'-'<<da<<endl;}
int getday() const { return da; }
void setday(int d) { da=d; }
};
class Date
{
int mo,da,yr;
public:
Date(int m,int d,int y) { mo=m; da=d; yr=y; }
operator CustomDate() const;
};
Date::operator CustomDate() const
{
static int dys[] = {31,28,31,30,31,30,31,31,30,31,30,31};
CustomDate cd(0,yr);
int day=da;
for(int i=0;i cd.setday(day);
return cd;
}
int main()
{
Date dt(11,17,89);
CustomDate cd;
cd=dt;
cd.display();
return 0;
}
注意上面的程序中Date::operator CustomDate()声明为常量型,因为这个函数没有改变调用它对象的数据,尽管它修改了一个临时CustomDate对象并将其作为函数返回值。
二、友元
前面已经说过了,私有数据成员不能被类外的其他函数读取,但是有时候类会允许一些特殊的函数直接读写其私有数据成员。
关键字friend可以让特定的函数或者别的类的所有成员函数对私有数据成员进行读写。这既可以维护数据的私有性,有可以保证让特定的类或函数能够直接访问私有数据。
1.友元类
一个类可以声明另一个类为其友元,这个友元的所有成员函数都可以读写它的私有数据。
#include iostream.h
class Date;
class CustomDate
{
int da,yr;
public:
CustomDate(int d=0,int y=0) { da=d; yr=y; }
void display() const {cout<<yr<<'-'<<da<<endl;}
friend Date; //这儿
};
class Date
{
int mo,da,yr;
public:
Date(int m,int d,int y) { mo=m; da=d; yr=y; }
operator CustomDate();
};
Date::operator CustomDate()
{
static int dys[] = {31,28,31,30,31,30,31,31,30,31,30,31};
CustomDate cd(0, yr);
for (int i=0;i cd.da+=da;
return cd;
}
int main()
{
Date dt(11,17,89);
CustomDate cd(dt);
cd.display();
return 0;
}
在上面的程序中,有这样一句 friend Date; 该语句告诉编译器,Date类的所有成员函数有权访问CustomDate类的私有成员。因为Date类的转换函数需要知道CustomDate类的每个数据成员,所以真个Date类都被声明为CustomDate类的友元。
2.隐式构造函数
上面程序对CustomDate的构造函数的调用私有显示该类需要如下的一个转换构造函数:
CustomDate(Date& dt);
但是唯一的一个构造函数是:CustomDate(int d=0;int y=0);
这就出现了问题,编译器要从Date对象构造一个CustomDate对象,但是CustomDate类中并没有定义这样的转换构造函数。不过Date类中定义了一个成员转换函数,它可以把Date对象转换成CustomDate对象。于是编译器开始搜索CustomDate类,看其是否有一个构造函数,能从一个已存在的CustomDate的对象创建新的CustomDate对象。这种构造函数叫拷贝构造函数。拷贝构造函数也只有一个参数,该参数是它所属的类的一个对象,由于CustomDate类中没有拷贝构造函数,于是编译器就会产生一个默认的拷贝构造函数,该函数简单地把已存在的对象的每个成员拷贝给新对象。现在我们已经知道,编译器可以把Date对象转换成CustomDate对象,也可以从已存在的CustomDate对象生成一个新的CustomDate对象。那么上面提出的问题,编译器就是这样做的:它首先调用转换函数,从Date对象创建一个隐藏的、临时的、匿名的CustomDate对象,然后用该临时对象作为参数调用默认拷贝构造函数,这就生成了一个新的CustomDate对象。
3.预引用
上面的例子中还有这样一句 class Date;
这个语句叫做预引用。它告诉编译器,类Date将在后面定义。编译器必须知道这个信号,因为CustomDate类中引用了Date类,而Date里也引用了CustomDate类,必须首先声明其中之一。
使用了预引用后,就可以声明未定义的类的友元、指针和引用。但是不可以使用那些需要知道预引用的类的定义细节的语句,如声明该类的一个实例或者任何对该类成员的引用。
4.显式友元预引用
也可以不使用预引用,这只要在声明友元的时候加上关键自class就行了。
#include iostream.h
class CustomDate
{
int da,yr;
public:
CustomDate(int d=0,int y=0) { da=d; yr=y; }
void display() const {cout<<yr<<'-'<<da<<endl;}
friend class Date; //这儿,去掉前面的预引用
};
class Date
{
... ...
};
Date::operator CustomDate()
{
... ...
}
int main()
{
... ...
}
5.友元函数
通常,除非真的需要,否则并不需要把整个类都设为另一个类的友元,只需挑出需要访问当前类私有数据成员的成员函数,将它们设置为该类的友元即可。这样的函数称为友元函数。
下面的程序限制了CustomDate类数据成员的访问,Date类中只有需要这些数据的成员函数才有权读写它们。
#include iostream.h
class CustomDate;
class Date
{
int mo,da,yr;
public:
Date(const CustomDate&);
void display() const {cout<<mo<<' '<<da<<'="" '<<yr<<endl;}
};
class CustomDate
{
int da,yr;
public:
CustomDate(int d=0,int y=0) { da=d; yr=y; }
friend Date::Date(const CustomDate&);
};
Date::Date(const CustomDate& cd)
{
static int dys[] = {31,28,31,30,31,30,31,31,30,31,30,31};
yr=cd.yr;
da=cd.da;
for(mo=0;mo<11;mo++)
if(da>dys[mo]) da-=dys[mo];
else break;
mo++;
}
int main()
{
Date dt(CustomDate(123, 89));
dt.display();
return 0;
}
6.匿名对象
上面main()函数中Date对象调用CustomDate类的构造函数创建了一个匿名CustomDate对象,然后用该对象创建了一个Date对象。这种用法在C++中是经常出现的。
7.非类成员的友元函数
有时候友元函数未必是某个类的成员。这样的函数拥有类对象私有数据成员的读写权,但它并不是任何类的成员函数。这个特性在重载运算符时特别有用。
非类成员的友元函数通常被用来做为类之间的纽带。一个函数如果被两个类同时声明为友元,它就可以访问这两个类的私有成员。下面的程序说明了一个可以访问两个类私有数据成员的友元函数是如何将在两个类之间架起桥梁的。
#include iostream.h
class Time;
class Date
{
int mo,da,yr;
public:
Date(int m,int d,int y) { mo=m; da=d; yr=y;}
friend void display(const Date&, const Time&);
};
class Time
{
int hr,min,sec;
public:
Time(int h,int m,int s) { hr=h; min=m; sec=s;}
friend void display(const Date&, const Time&);
};
void display(const Date& dt, const Time& tm)
{
cout << dt.mo << '/' << dt.da << '/' << dt.yr;
cout << ' ';
cout << tm.hr << ':' << tm.min << ':' << tm.sec;
}
int main()
{
Date dt(2,16,97);
Time tm(10,55,0);
display(dt, tm);
return 0;
}
1.取值和赋值成员函数
面向对象的约定就是保证所有数据成员的私有性。一般我们都是通过公有成员函数来作为公共接口来读取私有数据成员的。某些时候,我们称这样的函数为取值和赋值函数。
取值函数的返回值和传递给赋值函数的参数不必一一匹配所有数据成员的类型。
#include iostream.h
class Date
{
int mo, da, yr;
public:
Date(int m,int d,int y) { mo=m; da=d; yr=y; }
int getyear() const { return yr; }
void setyear(int y) { yr = y; }
};
int main()
{
Date dt(4,1,89);
cout<<the year="" is:="" <<dt.getyear()<<endl;
dt.setyear(97);
cout<<the new="" year="" is:="" <<dt.getyear();
return 0;
}
上面的例子很简单,不分析了。要养成这样的习惯,通过成员函数来访问和改变类中的数据。这样有利于软件的设计和维护。比如,改变Date类内部数据的形式,但仍然用修改过的getyear()和setyear()来提供访问接口,那么使用该类就不必修改他们的代码,仅需要重新编译程序即可。
2.常量成员函数
注意上面的程序中getyear()被声明为常量型,这样可以保证该成员函数不会修改调用他的对象。通过加上const修饰符,可以使访问对象数据的成员函数仅仅完成不会引起数据变动的那些操作。
如果程序声明某个Date对象为常量的话,那么该对象不得调用任何非常量型成员函数,不论这些函数是否真的试图修改对象的数据。只有把那些不会引起数据改变的函数都声明为常量型,才可以让常量对象来调用。
3.改进的成员转换函数
下面的程序改进了从Date对象到CustomDate对象的成员转换函数,用取值和赋值函数取代了使用公有数据成员的做法。(以前的程序代码在上一帖中)
#include iostream.h
class CustomDate
{
int da,yr;
public:
CustomDate() {}
CustomDate(int d,int y) { da=d; yr=y; }
void display() const {cout<<yr<<'-'<<da<<endl;}
int getday() const { return da; }
void setday(int d) { da=d; }
};
class Date
{
int mo,da,yr;
public:
Date(int m,int d,int y) { mo=m; da=d; yr=y; }
operator CustomDate() const;
};
Date::operator CustomDate() const
{
static int dys[] = {31,28,31,30,31,30,31,31,30,31,30,31};
CustomDate cd(0,yr);
int day=da;
for(int i=0;i cd.setday(day);
return cd;
}
int main()
{
Date dt(11,17,89);
CustomDate cd;
cd=dt;
cd.display();
return 0;
}
注意上面的程序中Date::operator CustomDate()声明为常量型,因为这个函数没有改变调用它对象的数据,尽管它修改了一个临时CustomDate对象并将其作为函数返回值。
二、友元
前面已经说过了,私有数据成员不能被类外的其他函数读取,但是有时候类会允许一些特殊的函数直接读写其私有数据成员。
关键字friend可以让特定的函数或者别的类的所有成员函数对私有数据成员进行读写。这既可以维护数据的私有性,有可以保证让特定的类或函数能够直接访问私有数据。
1.友元类
一个类可以声明另一个类为其友元,这个友元的所有成员函数都可以读写它的私有数据。
#include iostream.h
class Date;
class CustomDate
{
int da,yr;
public:
CustomDate(int d=0,int y=0) { da=d; yr=y; }
void display() const {cout<<yr<<'-'<<da<<endl;}
friend Date; //这儿
};
class Date
{
int mo,da,yr;
public:
Date(int m,int d,int y) { mo=m; da=d; yr=y; }
operator CustomDate();
};
Date::operator CustomDate()
{
static int dys[] = {31,28,31,30,31,30,31,31,30,31,30,31};
CustomDate cd(0, yr);
for (int i=0;i cd.da+=da;
return cd;
}
int main()
{
Date dt(11,17,89);
CustomDate cd(dt);
cd.display();
return 0;
}
在上面的程序中,有这样一句 friend Date; 该语句告诉编译器,Date类的所有成员函数有权访问CustomDate类的私有成员。因为Date类的转换函数需要知道CustomDate类的每个数据成员,所以真个Date类都被声明为CustomDate类的友元。
2.隐式构造函数
上面程序对CustomDate的构造函数的调用私有显示该类需要如下的一个转换构造函数:
CustomDate(Date& dt);
但是唯一的一个构造函数是:CustomDate(int d=0;int y=0);
这就出现了问题,编译器要从Date对象构造一个CustomDate对象,但是CustomDate类中并没有定义这样的转换构造函数。不过Date类中定义了一个成员转换函数,它可以把Date对象转换成CustomDate对象。于是编译器开始搜索CustomDate类,看其是否有一个构造函数,能从一个已存在的CustomDate的对象创建新的CustomDate对象。这种构造函数叫拷贝构造函数。拷贝构造函数也只有一个参数,该参数是它所属的类的一个对象,由于CustomDate类中没有拷贝构造函数,于是编译器就会产生一个默认的拷贝构造函数,该函数简单地把已存在的对象的每个成员拷贝给新对象。现在我们已经知道,编译器可以把Date对象转换成CustomDate对象,也可以从已存在的CustomDate对象生成一个新的CustomDate对象。那么上面提出的问题,编译器就是这样做的:它首先调用转换函数,从Date对象创建一个隐藏的、临时的、匿名的CustomDate对象,然后用该临时对象作为参数调用默认拷贝构造函数,这就生成了一个新的CustomDate对象。
3.预引用
上面的例子中还有这样一句 class Date;
这个语句叫做预引用。它告诉编译器,类Date将在后面定义。编译器必须知道这个信号,因为CustomDate类中引用了Date类,而Date里也引用了CustomDate类,必须首先声明其中之一。
使用了预引用后,就可以声明未定义的类的友元、指针和引用。但是不可以使用那些需要知道预引用的类的定义细节的语句,如声明该类的一个实例或者任何对该类成员的引用。
4.显式友元预引用
也可以不使用预引用,这只要在声明友元的时候加上关键自class就行了。
#include iostream.h
class CustomDate
{
int da,yr;
public:
CustomDate(int d=0,int y=0) { da=d; yr=y; }
void display() const {cout<<yr<<'-'<<da<<endl;}
friend class Date; //这儿,去掉前面的预引用
};
class Date
{
... ...
};
Date::operator CustomDate()
{
... ...
}
int main()
{
... ...
}
5.友元函数
通常,除非真的需要,否则并不需要把整个类都设为另一个类的友元,只需挑出需要访问当前类私有数据成员的成员函数,将它们设置为该类的友元即可。这样的函数称为友元函数。
下面的程序限制了CustomDate类数据成员的访问,Date类中只有需要这些数据的成员函数才有权读写它们。
#include iostream.h
class CustomDate;
class Date
{
int mo,da,yr;
public:
Date(const CustomDate&);
void display() const {cout<<mo<<' '<<da<<'="" '<<yr<<endl;}
};
class CustomDate
{
int da,yr;
public:
CustomDate(int d=0,int y=0) { da=d; yr=y; }
friend Date::Date(const CustomDate&);
};
Date::Date(const CustomDate& cd)
{
static int dys[] = {31,28,31,30,31,30,31,31,30,31,30,31};
yr=cd.yr;
da=cd.da;
for(mo=0;mo<11;mo++)
if(da>dys[mo]) da-=dys[mo];
else break;
mo++;
}
int main()
{
Date dt(CustomDate(123, 89));
dt.display();
return 0;
}
6.匿名对象
上面main()函数中Date对象调用CustomDate类的构造函数创建了一个匿名CustomDate对象,然后用该对象创建了一个Date对象。这种用法在C++中是经常出现的。
7.非类成员的友元函数
有时候友元函数未必是某个类的成员。这样的函数拥有类对象私有数据成员的读写权,但它并不是任何类的成员函数。这个特性在重载运算符时特别有用。
非类成员的友元函数通常被用来做为类之间的纽带。一个函数如果被两个类同时声明为友元,它就可以访问这两个类的私有成员。下面的程序说明了一个可以访问两个类私有数据成员的友元函数是如何将在两个类之间架起桥梁的。
#include iostream.h
class Time;
class Date
{
int mo,da,yr;
public:
Date(int m,int d,int y) { mo=m; da=d; yr=y;}
friend void display(const Date&, const Time&);
};
class Time
{
int hr,min,sec;
public:
Time(int h,int m,int s) { hr=h; min=m; sec=s;}
friend void display(const Date&, const Time&);
};
void display(const Date& dt, const Time& tm)
{
cout << dt.mo << '/' << dt.da << '/' << dt.yr;
cout << ' ';
cout << tm.hr << ':' << tm.min << ':' << tm.sec;
}
int main()
{
Date dt(2,16,97);
Time tm(10,55,0);
display(dt, tm);
return 0;
}