首页 > 代码库 > C++ Primer Plus的若干收获--(十)

C++ Primer Plus的若干收获--(十)

明天就要回学校了,本来回家之前是有一片宏图伟志的,无奈只能抱着这可怜的十篇博客回学校了。自己马上就要大三,大学的下一半马上就要开始了,我的未来还有什么在等待着我呢,好期待!!!

 

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_Husing std::cout;#define STONEWT_Hclass 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 validmyCat=Stonewt(19.6);//ok,an explicit convertionmyCat=(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


 

类这部分比较多,而且不太好总结。估计进度会比较慢,要用比较多的笔墨来写了。不过没关系,我还是挺喜欢这块的。下篇仍然会接着这个转换函数来写,敬请期待!