ITEEDU

7.4 友元函数与友元类

类的友元函数(friendfunetlon)在类范围之外定义,但有权访问类的private(和第9章“继承”介绍的受保护)成员。函数或整个类都可以声明为另一个类的友元。

利用友元函数能提高性能,这里将介绍一个友元函数的例子。本书后面要用友元函数通过类对象和生成迭代类来重载运算符。迭代类对象用于连续选择项目或对容器类(见7.9节)对象中的项目进行操作。容器类对象能够存放项目。成员函数无法进行某些操作时可以使用友元函数(见第8章“运算符重载”)。

要将函数声明为类的友元,就要在类定义中的函数原型前面加上friend关键字。要将类ClassTwo声明为类ClassOne的友元,在类ClassOne的定义中声明如下:

friend class classTwo;

软件工程视点7. 9

尽管类定义中有友元函数的原型,但友元仍然不是成员函数。

软件工程视点7.10

private、protected和public的成员访问符号与友元关系的声明无关,因此友元关系声明可以放在类定义中的任何地方。

编程技巧7.2

将类中所有友元关系的声明放在类的首部之后,不要在其前面加上任何成员访问说明符。

友元关系是”给予”的,而不是”索取”的,即要让B成为A的友元,A要显式声明B为自己的友元。此外,友元关系既不对称也不能传递,例如.如果A是D的友元,B是C的友元,并不能说B就是A的友元(不对称)、c就是B的友元或A就是C的友元(不传递)。

软件工程视点7. 11

OOP组织中的有些人认为友元关系会破坏信息隐藏和降低面向对象设计方法的价值。

图7.5演示了声明与使用友元函数setX来设置Count类的private数据成员x。注意,类声明中(习惯上)首先是友元声明,放在public成员函数的声明之前。图7.6的程序演示了调用非友元函数cannotSetX修改private数据成员x时编译器产生的消息。图7.5和7. 6介绍了使用友元函数的“结构”,今后各章会介绍使用友元函数的实际例子。

  // Fig. 7.5:fig0705.cpp
 // Friends can access private members of a class.
 #include <  iostream.h>
 // Modified Count class
 class Count {
   friend void setX( Count &,int ); // friend declaration
 public:
   Count() { x = 0; }               // constructor
   void print()const {cout << x << endl; }  // output
 private:
   int x;  // data member
 };
 // Can modify private data of Count because
 // setX is declared as a friend function of Count
 void setX( Count &c, int val )
 {
   c.x = val;  // legal: setX is a friend of Count
 }
 int main()
 {
   Count counter;
   cout << "counter.x after instantiation: ";
   counter.print();
   cout << "counter.x after call to setx friend function: ";
   setX( counter, 8 );  // set x with a friend
   counter.print();
   return 0;
 }

输出结果:

counter.x after intantiation: 0

counter.x after call to setX friend function: 8

图7.5 友元可以访问类的private成员

注意第17行中函数setX是C语言式的独立函数,而不是Count类的成员函数,为此,对counter对象调用setX时,我们用第29行的语句:

setX(counter,8 ); // set x with a friend

来提取counter参数而不是用句柄(如对象名)调用如下函数:

counter.setX(s);

软件工程视点7.12

由于C++是个混合语言,经常在一个程序中并行采用两种函数调用,类C语言的调用将基本数据或对象传递给函数而C++调用将函数(或消息)传递给对象。

  // Fig. 7.6: fig07_O6.cpp
 // Non-friend/non-member functions cannot access
 // private data of a class.
 #include <  iostream.h>
 // Modified Count class
 class Count {
 public:
   Count() { x = 0; }              // constructor
  void print() const { cout << x << endl; }  // output
 private:
   int x;  // data member
 };
 // Function tries to modify private data of Count,
 // but cannot because it is not a friend of Count.
 void cannotSetX( Count &c, int val )
 {
   c.x = val;  // ERROR: 'Count::x' is not accessible
 }
 int main()
 {
   Count counter;
   cannotSetX( counter, 3 ); // cannotSetX is not a friend
   return 0;
 }

输出结果:

Compiling..

Fig07 06.cpp

Fig07_06.cpp(19) : error: 'x' :

Cannot access private member declared in class 'Count'

图7.6 非友元/非成员函数不能访问类的Private成员

可以指定重载函数为类的友元。每个重载函数如果要作为友元,就要在类定义中显式声明为类的友元。

7.5 使用this指针

每个对象都可以通过this指针访问自己的地址。对象的this指针不是对象本身的一部分,即this指针不在对该对象进行sizeof操作的结果中体现。但this指针在每次非static成员函数调用对象时(static成员见7.7节介绍)作为第一个隐式参数传递给对象(通过编译器)。

this指针隐式引用对象的数据成员和成员函数(当然也可以显式使用)。this指针的类型取决于对象类型和使用this的成员函数是否声明为const。在Employee类的非常量成虽函数中,this指针的类型为Employee *const(Employee对象的常量指针)。在Employee类的常量成员函数中,this指针的类型为const Employee *const(为常量Employee对象的常量指针)。

下面介绍一个显式使用this指针的简单例子,本章稍后和第8章将介绍一些使用this的复杂例子。每个非static成员函数都能访问所调用成员所在对象的this指针。

性能提示7.3

为了节约存储空间,每个类的每个成员函数只有一个副本,该类的每个对象都可调用这个成员函数。另一方面每个对象又有自己的类数据成员副本。

图7.7演示了显式使用this指针,从而使Test类的成员函数打印Test对象的private数据x。

  // Fig. 7.7: fig07_07.cpp
 // Using the this pointer to refer to object members,
 #include < iostream.h>
 class Test {
 public:
   Test( int = 0 };          // default constructor
   void print() const;
 private:
   int x;
 };
 Test::Test( int a ) { x = a; }  // constructor
 void Test::print() Const  // () around *this required
 {
   cout <<"      x =" << x
       << "\n this->x  "<< this->x
       << "\n(* this).x =" << ( *this ).x << endl;
 }
 int main()
 {
   Test testObject( 12 );
   testObject.print();
   return 0;
 }

输出结果:

x = 12

this-> = 12

(*this).x = 12

图 7. 7使用this指针

作为演示,图7.7中的print成员函数首先直接打印x。然后print用两个不同符号通过this指针访问x,一个是this指针和箭头运算符(->),一个是this指针和圆点运算符(.)。

注意*this和圆点(成员选择)运算符一起使用时要用括号括起来。这个括号是必需的,因为圆点运算符的优先级高于*运算符。如果没有括号,则下列表达式:

*this.x

求值为:

*( this.x)

这是个语法错误,因为圆点运算符不能和指针一起使用。

常见编程错误7.7

将对象指针和成员选择运算符(.)一起使用是个语法错误,因为成员选择运算符和对象或对该对象的引用一起使用。

this指针的一个有趣用法是防止对象赋值给自己。第8章“运算符重载”中将会介绍,自我赋值可能在对象包含动态分配内存的指针时导致严重的错误。

this指针的另一用法是允许连续使用成员函数调用。图7.8演示了返回Time对象的引用,使Time类的成员函数调用可以连续使用。成员函数setTime、setHour、setMinute和setSecond都可以返回Time&返回类型的*this。

 // Fig. 7.8: time6.h
 // Cascading member function calls.
 // Declaration of class Time.
 // Member functions defined in time6.cpp
 #ifndef TIME6_H
 #define TIME6_H
 class Time {
 public:
   Time( int = 0, int= 0, int = 0 );  // default constructor
   // set functions
   Time &setTime( int, int, int ); // set hour, minute, second
   Time &setHour( int );    // set hour
   Time &setMinute( int );  // set minute
   Time &setSecond( int );  // set second
   // get functions (normally declared const)
   int getHour() const;    // return hour
   int getMinute() const;  // return minute
   int getseeend() const;  // return second
   // print functions (normally declared const}
   void printMilitary() const;  // print military time
   void printstandard()  const;     // print standard time
 private:
   int hour;           // O - 23
   int minute;          // O - 59
   int second;          // O - 58
 };
 #endif
 // Fig. 7.8: time.cpp
 #include "time6.h"
 #include < iostream.h>
 // Constructor function to initialize private data.
 // Calls member function setTime to set variables.
 // Default values are 0(see class definiation).
 Time::Time( int hr, int min, int sec )
   { setTime( hr, min, sec ); }
 // Set the values of hour, minute, and second.
 Time &Time::setTime{ int h, int m, int s )
 {
   setHour( h );
   setMinute( m );
   setSecond( s );
   return *this;  // enables CaScading
 }
 // Set the hour value
 Time &Time::setHour( int h )
 {
   hour = ( h >= 0 && h < 24 ) ? h : 0;
   return *this;  // enables cascading
 }
 // Set the minute value
 Time &Time::setMinute( int m )
 {
   minute = ( m >= 0 && m < 60 ) ? m : 0;
   return *this;  // enables cascading
 }
 // Set the second value
 Time &Time::setSecond( int s )
 {
   second = ( s >= 0 && s < 60 ) ? s : 0;
   return *this;  // enables cascading
 }
 // Get the hour value
  int Time::getHour() const { return hour;}
 // Get the minute value
 int Time::getMinute() const { return minute; }
 // Get the second value
  int Time::getSecond() const {eturn second;}
 // Display military format time: HH:MM:
 void Time::printMilitary() const
 {
     cout << (hour < 10 ? "0": "") << hour <<":"
          << ( minute < 10 ? "0": "" ) << minute;
 }
 // Display standard format time: HH:MM:SS AM (or PM)
 void Time::printStandard() const
 {
     cout << ( ( hour == 0 || hour == 12 ) ? 12 : hour % 12 )
         << ":" << ( minute < 10 ? "0" : "" ) << minute
          << ":" << ( second < 10 ? "0" : "" ) << second
         << ( hour < 12 ? "AM" : "PM" );
 }
 // Fig. 7.8: fig07_08.cpp
 // cascading member function calls together
 // with the this pointer
 #include < iostream.h>
 #include "time6.h"
 int main()
 {
     Time t;
     t.setMour( 18 ).setMinute( 30 ).setSecond( 22 );
     cout << "Military time: ";
     t.printMilitaryO;
     cout << "\nStandard time: ";
     t.printStandard();
     cout << "\n\nNew standard time: ";
     t.setTime( 20, 20, 20 ).printStandard();
     cout << endl;
    return 0;
 }

输出结果:

Military time: 18:30

standard time: 6:30:22 PM

New standard time: 8:20:20 PM

图 7.8 连续使用成员函数调用

为什么可以将*this作为引用返回呢 圆点运算符(.)的结合律为从左向右,因此下列表达式:

t.setHour( 18 ).setMinute( 30 ).setSecond( 22 );

首先求值t.setHour(18),然后返回对象t的引用,作为这个函数调用的值。其余表达式解释如下:

t.setMinute(30).setSecond(22);

执行setMinute(30)调用并返回t的等价值,其余表达式解释为:t.setSecond(22);

注意:t.setTime(20,20,20).printStandard();

调用也体现了连续使用的特性。这些调用在这个表达式中必须按这个顺序出现,因为类中定义的printStandard并不返回t的引用,将上述语句中的printStandard调用放在setTime调用之前是个语法错误。