明天就要回学校了,本来回家之前是有一片宏图伟志的,无奈只能抱着这可怜的十篇博客回学校了。自己马上就要大三,大学的下一半马上就要开始了,我的未来还有什么在等待着我呢,好期待!!!
10.1 友元
我们知道C++控制对类对象私有部分的访问。通常,公有类方法是唯一的途径,但是除此之外C++还提供了另外一种机制:友元。友元有三种友元函数,友元类与友元成员函数。在介绍如何成为友元前,先介绍一下为何需要友元。在为类重载二元运算符是常常需要友元。将之前的Time对象乘以实数就是这种情况。
我们之前重载了+ Time A,B; A=B*2.75;//由上篇我们知道这是可行的 B=2.75*A;
但是,下面这行的的代码是不对的。我们知道,遇到重载的操作符时,左侧的操作数是调用对象,但是2.75不是对象,因此编译器不能通过使用成员函数调用来替换该表达式。解决这个问题的方式就是使用非成员函数。非成员函数不是对象调用的,它使用的所有值都是显式参数。这样我们只要定义了如下的函数即可
Time operator*(double m,const Time& t);
但是这样使用又会引发新的问题:非成员函数不能直接访问类的私有数据,至少常规非成员函数不能访问。这时,我们就需要友元函数了。
10.2 创建友元
创建友元函数的第一步是将其原型放在类的声明中,并在之前加上friend关键字
friend Time operator*(double m,const Time& t);
该原型意味着两点:
- 虽然operator*()函数式在类声明中声明的,但它不是成员函数,不能用成员运算符来调用;
- 虽然operator*()函数不是成员函数,但是它的访问权限与成员函数相同。
完整的函数定义则不再需要friend。再有了定义和声明之后下面的语句就可以实现了
A=2.75*B;
10.3 重载<<运算符
(1)<<重载的第一个版本
要使Time类知道使用cout,必须使用友元函数。因为下面这样的语句使用两个对象,其中第一个是ostream类对象
cout<<trip;
但是如果使用Time成员函数的方法来重载,Time对象将是第一个操作数,而有这样的代码
trip<<cout;//显然是不合理的
因此我们需要友元来重载这样的运算符
void operator<<(ostream& os,const Time & t) { os<<t.hours<<"hours,"<<t,minutes <<"minutes"; }
(2)<<的第二个重载版本
之前的版本解决了cout<<trip这个问题,但是,它却不能解决这样的问题
cout<<"Trip time:"<<trip<<"Tuesday";
之所以不能这样做我们要先了解cout的一点知识。
cout<<x<<y;
正如iostream定义的那样,<<运算符要求左边第一个是ostream对象。显然表达式cout<<x满足这种要求,然而表达式cout<<x位于<<y的左侧,所以输出的语句也要求该表达式是一个ostream类型的对象。因此,ostream类将operator<<()函数实现为返回一个指向ostream对象的引用。所以,我们应该这样修改上述的重载函数
ostream& operator<<(ostream& os,const Time &t) { os<<t.hours<<"hours,"<<t.minutes<<"minutes"; return os; }
10.4 改进后的Time类
ifndef MYTH0_H_ #definr MYTH0_H_ class Time { private: int hours; int minutes; public: Time(); Time(int h,int m=0); void AddMin(int m); void AddHr(int h); void Reset(int h=0,int m=0); Time operator+(const Time& t) const; Time operator-(const Time& t) const; Time operator*(const Time &t) const; friend Time operator*(double m,const Time & t) {return t*m;} friend std::ostream& operator<<(std::ostream& os,const Time &t); void show() const; }; #endif // MYTH0_H_
10.5 类型转换
在讨论类的类型转换之前,我们先来复习C++时如何处理内置的类型转换的。
longble count=8; double time=11; int side=3.33;
上述的赋值代码均是可行的,因为在C++看来,各种数值类型都表示相同的东西——一个数字,同时c++包含用于转换的内置规则。但是C++不会自动转换不兼容的类型。
int *p=10;//invalid
为了更好地说明C++的有关类的转换机制,我们先看下如下的类
ifndef STONEWT_H using std::cout; #define STONEWT_H class Stonewt { public: Stonewt(double lbs,int stn); { stone=stn; pds_left=lbs; pounds=stn*Lbs_per_stn; } Stonewt(double lbs) { stone=int(lbs)/Lbs_per_stn;//integer divition pds_left=int(lbs)%Lbs_per_stn+lbs-int(lbs); pounds=lbs; } stonewt() {stone=pounds=pds_left=0;}; virtual ~Stonewt() {}; void show_lbs() const {cout<<pounds<<"pounds\n";}; void show_stn() const {cout<<stone<<"stone,"<<pds_left<<"pounds\n";}; private: enum{Lbs_per_stn=14}; int stone; double pds_left; double pounds; }; #endif // STONEWT_H
有了这个类,我们在看一下如下的代码
Stonewt myCat; myCat=19.6;//use Stonewt(double) to convert 19.6 to Stonewt
程序将使用构造函数Stone(double)来创建一个临时的Stonewt对象,并将19.6作为一个初始化值。随后,采用逐成员赋值方式来将该对象的内容复制到myCat对象中。这一过程称为隐式转换。只有接受一个参数的构造函数才能称为转换函数。下面的构造函数就不行
Stonewt(double lbs,int stn);
但是,如果第二个参数提供默认值就可以
Stonewt(double lbs=0,int stn);//int-to-Stonewt conversion
将构造函数用作自动类型转换似乎是一项不错的特性,但是随着C++的深入,我们也会发现其带来的问题。因为这会导致意外的类型转换。因此,C++提供了关键字explicit用于关闭这种自动的特性。
explicit Stonewt(double lbs); Stonewt=myCat; myCat=19.6;//not valid myCat=Stonewt(19.6);//ok,an explicit convertion myCat=(Stonewt)19.6;//ok,old form for type cast
那么编译器在什么时候将使用Stonewt(double)函数呢?如果在声明中使用了关键字explicit,则其将仅仅用于显示的类型转换,否则还可以使用如下的隐式转换:
-
将Stonewt对象初始化为double值时。
-
将double值赋给Stonewt参数的函数时。
-
将double值传递给接受Stonewt参数的函数时。
-
返回值被声明为stonewt的函数试图返回double值时。
-
在上述任一种情况下,使用可转换为double类型的内置类型。
最后一句的意思也就是说这种情况。函数原型化提供的参数匹配过程,允许使用Stonewt(double)构造函数来转换其他的数值类型。
Stonewt Jumbo(7000); Jumbo=7300;//uses Stonewt(double),converting int to double
类这部分比较多,而且不太好总结。估计进度会比较慢,要用比较多的笔墨来写了。不过没关系,我还是挺喜欢这块的。下篇仍然会接着这个转换函数来写,敬请期待!