使用类
11.1 操作符重载
C++ 允许将操作符重载扩展到用户定义的类型,例如,允许用 + 将两个对象相加;
操作符函数:
operator op (argument - list) // op 是将要重载的操作符
eg: operator *() 重载 * 操作符 // op 必须是有效的C++操作符
定义 operator + () 成员函数:
district2 = sid + sara; 等效于 district2 = sid.operator + (sara); // 隐式调用sid,显示调用sara对象
11.2 计算时间:一个操作符重载的范例
mytime.h:
1 #ifndef MYTIME0_H_ 2 #define MYTIME0_H_ 3 class Time 4 { 5 private: 6 int hours; 7 int minutes; 8 public: 9 Time(); 10 Time(int h, int m = 0); 11 void AddMin(int m); 12 void AddHr(int h); 13 void Reset(int h = 0, int m = 0); 14 Time operator +(const Time & t) const; 15 void show() const; 16 }; 17 18 #endif
mytime.cpp:
1 #include<iostream> 2 #include"mytime0.h" 3 4 Time::Time() 5 { 6 hours = minutes = 0; 7 } 8 9 Time::Time(int h, int m) 10 { 11 hours = h; 12 minutes = m; 13 } 14 15 void Time::AddMin(int m) 16 { 17 minutes += m; 18 hours += minutes / 60; 19 minutes %= 60; 20 } 21 22 void Time::AddHr(int h) 23 { 24 hours += h; 25 } 26 27 void Time::Reset(int h, int m) 28 { 29 hours = h; 30 minutes = m; 31 } 32 33 Time Time::operator +(const Time & t) const 34 { 35 Time sum; 36 sum.minutes = minutes + t.minutes; 37 sum.hours = hours + t.hours + sum.minutes / 60; 38 sum.minutes %= 60; 39 return sum; 40 } 41 42 void Time::show() const 43 { 44 std::cout << hours << " hours, " << minutes << " minutes"; 45 }
usetime.cpp:
1 #include<iostream> 2 #include"mytime0.h" 3 using namespace std; 4 5 int main() 6 { 7 Time planning; 8 Time coding(2, 40); 9 Time fixing(5, 55); 10 Time total; 11 12 cout << "Planning time = "; 13 planning.show(); 14 cout << endl; 15 16 cout << "coding time = "; 17 coding.show(); 18 cout << endl; 19 20 cout << "fixing time = "; 21 fixing.show(); 22 cout << endl; 23 24 total = coding + fixing; 25 cout << "coding + fixing = "; 26 total.show(); 27 cout << endl; 28 29 return 0; 30 }
C++ 对用户定义的操作符重载的限制:
重载后的操作符必须至少有一个操作数是用户定义的类型,防止为标准类型重载操作符;
使用操作符不能违反操作符原来的句法规则;
不能定义新的操作符;
一些操作符不能重载
11.3 友元简介
C++ 提供可以通过友元获得类私有部分的访问权限,友元有3种:
1. 友元函数
2. 友元类
3. 友元成员函数
友元函数的用处:
对于乘法操作符重载,通过成员函数调用:
A = B * 2.75 被转换为成员函数调用 A = B.operator(2.75);
但是不能通过成员函数来完成 A = 2.75 * B;
通过非成员函数——所有参数显示调用:
A = operator * (2.75,B);
函数原型: Time operator * (double m,Time & t):
操作符表达式左边的操作数对应于操作符函数的第一个参数,操作符表达式右边的操作数对应于操作符函数的第二个参数
创建友元函数第一步是将其原型放在类声明中,并在前面加上关键字 friend:
friend Time operator * (double m, const Time & t);
operator *() 虽然在类中声明,但不是成员函数,不能使用成员操作符调用;
operator *() 虽然不是成员函数,但是与成员函数的访问权限相同;
提示:如果为类重载操作符,并将非类的项作为其第一个操作数,则可以用友元函数来反转操作数的顺序
113.2 常用的友元:重载 << 操作符
1. << 的第一个重载版本
要使 Time 类知道使用 cout,必须使用友元函数:
语句 cout<<trip; cout 是一个 ostream 类对象;
要使用一个Time成员函数来重载 << ,Time对象为第一个参数: trip << cout;
使用友元函数(而不是成员函数):
void operator << (ostream & os,const Time & t);
可以实现 cout << trip; ( operator << (cout,trip) )
2. << 的第二种重载版本
<< 重载要实现输出的拼接,cout<<"Trip time: "<< trip;
则必须返回一个 ostream 对象,继续使用操作符 << 进行输出;
ostream & operator << (ostream & os,const Time & t);
提示:一般来说,要重载 << 操作符来显示 c_name 的对象,可以使用一个友元函数,定义如下:
ostream & operator << ( ostream & os,const c_name & obj );
例程:
mytime2.h:
1 #ifndef MYTIME_H_ 2 #define MYTIME_H_ 3 #include<iostream> 4 5 class Time 6 { 7 private: 8 int hours; 9 int minutes; 10 public: 11 Time(); 12 Time(int h, int m = 0); 13 void AddMin(int m); 14 void AddHr(int h); 15 void Reset(int h = 0, int m = 0); 16 Time operator + (const Time & t) const; 17 Time operator - (const Time & t) const; 18 Time operator * (double n) const; 19 20 friend Time operator *(double m, const Time & t) 21 { 22 return t * m; 23 } 24 25 friend std::ostream & operator << (std::ostream & os, const Time & t); 26 }; 27 28 29 #endif
mytime2.cpp
1 #include<iostream> 2 #include"mytime.h" 3 4 using namespace std; 5 6 Time::Time() 7 { 8 hours = minutes = 0; 9 } 10 11 Time::Time(int h, int m) 12 { 13 hours = h; 14 minutes = m; 15 } 16 17 void Time::AddMin(int m) 18 { 19 minutes += m; 20 hours += minutes / 60; 21 minutes %= 60; 22 } 23 24 void Time::AddHr(int h) 25 { 26 hours += h; 27 } 28 29 void Time::Reset(int h, int m) 30 { 31 hours = h; 32 minutes = m; 33 } 34 35 Time Time::operator+(const Time & t) const 36 { 37 Time sum; 38 sum.minutes = minutes + t.minutes; 39 sum.hours = hours + t.hours + sum.minutes / 60; 40 sum.minutes %= 60; 41 return sum; 42 } 43 44 Time Time::operator - (const Time & t) const 45 { 46 Time diff; 47 int total1, total2; 48 total1 = hours * 60 + minutes; 49 total2 = t.hours + t.minutes; 50 51 diff.minutes = (total1 - total2) % 60; 52 diff.hours = (total1 - total2) / 60; 53 return diff; 54 } 55 56 Time Time::operator * (double n) const 57 { 58 Time result; 59 long totalminutes = hours * n * 60 + minutes * n; 60 result.hours = totalminutes / 60; 61 result.minutes = totalminutes % 60; 62 return result; 63 } 64 65 std::ostream & operator << (std::ostream & os, const Time & t) 66 { 67 os << t.hours << " hours," << t.minutes << " minutes"; 68 return os; 69 }
main.cpp
1 #include<iostream> 2 #include"mytime.h" 3 4 using namespace std; 5 6 int main() 7 { 8 Time aida(3, 35); 9 Time tosca(2, 48); 10 Time temp; 11 12 cout << "Aida and Tosca: "; 13 cout << aida << "; " << tosca << endl; 14 15 temp = aida + tosca; 16 17 cout << "Aida + Tosca: " << temp << endl; 18 temp = aida * 1.17; 19 cout << "Aida * 1.17: " << temp << endl; 20 cout << "10 * Tosca: " << 10 * tosca << endl; 21 22 return 0; 23 24 }
11.4 重载操作符: 作为成员函数还是非成员函数
对于很多操作符,可以使用成员函数或非成员函数来实现操作符重载,非成员函数应为友元函数才能直接访问类的私有成员:
Time operator + (const Time & t) const; // 成员函数,隐式和显示调用参数
friend Time operator + (const Time & t1, const Time & t2); // 非成员函数,友元函数,显示调用两个参数
选择哪个更好:
对于某些操作符,成员函数是唯一的选择;
有时,根据类设计,使用非成员函数更好
11.5 再谈重载: 矢量类
11.6 类的自动转换和强制类型转换
程序清单 11.16 stonewt.h
1 #ifndef STONEWT_H_ 2 #define STONEWT_H_ 3 4 class Stonewt 5 { 6 private: 7 enum { Lbs_per_stn = 14 }; // or static const int Lbs_per_stn = 14 8 int stone; 9 double pds_left; 10 double pounds; 11 public: 12 Stonewt(double lbs); 13 Stonewt(int stn, double lbs); 14 Stonewt(); 15 ~Stonewt(); 16 void show_lbs() const; // show weight in pounds format 17 void show_stn() const; // show weight in stone format 18 }; 19 #endif
程序清单 11.17 stonewt.cpp
1 #include<iostream> 2 #include"stonewt.h" 3 using namespace std; 4 5 // conststruct Stonewt object from double value 6 Stonewt::Stonewt(double lbs) 7 { 8 stone = int(lbs) / Lbs_per_stn; 9 pds_left = int(lbs) % Lbs_per_stn + lbs - int(lbs); 10 pounds = lbs; 11 } 12 13 // construct Stonewt object from stone, double values 14 Stonewt::Stonewt(int stn, double lbs) 15 { 16 stone = stn; 17 pds_left = lbs; 18 pounds = stn*Lbs_per_stn + lbs; 19 } 20 21 // default constructor, wt = 0 22 Stonewt::Stonewt() 23 { 24 stone = pounds = pds_left = 0; 25 } 26 27 // destructor 28 Stonewt::~Stonewt() 29 { 30 } 31 32 // show weight in stones 33 void Stonewt::show_stn() const 34 { 35 cout << stone << " stone, " << pds_left << " pounds "; 36 } 37 38 // show weight in pounds 39 void Stonewt::show_lbs() const 40 { 41 cout << pounds << " pounds "; 42 }
程序清单 11.18 stone.cpp
1 #include<iostream> 2 #include"stonewt.h" 3 using std::cout; 4 void display(const Stonewt st, int n); 5 6 int main() 7 { 8 Stonewt pavarotti = 260; // 使用构造函数初始化 9 Stonewt wolf(285.7); // same as Stonewt wolf = 285.7 10 Stonewt taft(21, 8); 11 12 cout << "The pavarotti weighted "; 13 pavarotti.show_stn(); 14 cout << "The wolf weighted "; 15 wolf.show_stn(); 16 cout << "The taft weighted "; 17 taft.show_lbs(); 18 19 pavarotti = 265.8; // 使用构造函数转换 20 taft = 325; // same as taft = Stonwt(325) 21 22 cout << "After dinner, the taft weighted "; 23 taft.show_lbs(); 24 display(taft, 2); 25 cout << "The wrestler weighted even more. "; 26 display(422, 2); 27 cout << "No stone left unerned "; 28 return 0; 29 } 30 31 void display(const Stonewt st, int n) 32 { 33 for (int i = 0; i < n; i++) 34 { 35 cout << "Wow! "; 36 st.show_stn(); 37 } 38 }
11.6.1 程序说明
在C++中接收一个参数的构造函数为将类型与该参数相同的值转换为类提供了蓝图:
Stonewt ( double lbs ); // template for double-to-Stonewt conversion
用于将double类型的值转换为 Stonewt 类型:
Stonewt myCat;
myCat = 19.6;
程序将使用构造函数 Stonewt(double) 创建一个临时的对象,并将19.6作为初始化值;
随后采用逐成员赋值方式将该临时对象的内容复制到myCat中——隐式转换
只有接受一个参数的构造函数才能作为转换函数;
最新C++ 新增一个关键字(explicit),关闭隐式转换,仍允许显示转换,即显示强制类型转换:
explicit Stonewt ( double lbs ); // no implicit conversions allowed
Stonewt myCat; // create a Stonewt object
myCat = 19.6; // not valid
myCat = Stonewt ( 19.6 ); // ok, an explicit conversion
myCat = (Stonewt) 19.6; // ok
记住: 只接受一个参数的构造函数定义了从参数类型到类类型的转换,如果使用了关键字 explicit 限定,则只能用于显示转换
编译器什么时候将使用 Stone ( double ) ,声明中未使用关键字 exiplicit:
将 Stonewt 对象初始化为 double 值时;
将 double 值赋给 Stonewt 对象时;
将 double 值传递给接受 Stonewt 参数的函数时;
返回值被声明为 Stonewt 的函数试图返回一个 double 值时;
在上述任一情况下,使用可以转换为 double 类型的内置类型时;
程序中 display ( 422, 2 ); // convert 422 to double, then to Stonewt;
11.6.2 转换函数
将类类型转换为某种类型,必须使用特殊的C++操作符——转换函数;
转换函数是用户定义的强制类型转换,可以像使用强制类型转换那样使用它们;
如果定义了 Stonewt 到 double 的转换函数:
Stonewt wolfe ( 285.7 );
double host = double (wolfe);
double thinker = (double) wolfe;
创建转换函数:
operator tyoeName();
转换函数必须是类方法;
转换函数不能指定返回类型;
转换函数不能有参数
eg: operator double
警告: 应谨慎地使用隐式转换函数。通常,最好选择仅在被显示地调用时才会执行的函数;
C++ 为类提供了下面的类型转换:
1. 只有一个参数的类构造函数用于将类型与参数相同的值转换为类类型:
例如:将 int 值赋给 Stonewt 对象时,接收int参数的Stonewt类构造函数将被自动调用;
在构造函数使用 explicit 可以防止隐式转换,而只允许显示转换;
2. 被称为转换函数的特殊类成员操作符函数,用于将类对象转换为其他类型:
转换函数是类成员,没有返回类型,没有参数,名为 operator typeName();
11.7 总结
本章介绍了使用类的许多重要方面:
一般来说,访问私有类成员的唯一方法是使用类方法;
C++ 可以使用友元函数来访问类的私有成员,要成为友元,需要在类声明中声明该函数,并加关键字 friend;
C++ 扩展了对操作符的重载,允许自定义特殊的操作符函数,描述了特定操作符与类之间的关系:
操作符函数可以是类成员,也可以是友元函数(一些操作符只能是类成员函数);
要调用操作符函数,可以直接调用该函数,也可以以通常的句法使用被重载的操作符;
操作符函数: operator op ( argument - list );
如果操作符函数是类成员函数,则第一个操作数是调用对象;
要使第一个操作数不是类对象,则必须使用友元函数,这样就可以将操作数按所需的顺序传递给函数;
最常见的操作符重载任务之一是定义 << 操作符,使之与 cout 一起使用,来显示对象内容:
要使重新定义的操作符能与其自身进行拼接,需要将返回类型声明为 ostream &:
ostream & operator << ( ostream & os, const c_name & obj )
{
os<< . . .; // display object contents
return os;
}
C++ 允许指定在类和基本类型之间进行转换的方式:
任何接受唯一一个参数的构造函数都可被用作转换函数,将类型与该参数相同的值转换为类;
如果将类型与该参数相同的值赋给对象,C++ 会自动调用该构造函数;
假设有一个 String 类,包含一个将 char * 值作为唯一参数的构造函数,bean 是 String 对象:
bean = "pinto"; // converts type char* to type String
如果构造函数的声明前面加上了关键字 explicit,则构造函数只能显示转换:
bean = String("pinto"); // converts type char * to type String explicitly
要将类对象转换为其他类型,必须定义转换函数,指出如何进行转换,转换函数必须是成员函数:
operator typeName();
转换函数没有返回类型,没有参数,但必须返回转换后的值