1. 运算符重载
运算符重载是一种形式的C++多态。要重载运算符需要使用被称为运算符函数的特殊函数形式,格式如下:
operatorop(argument-list)
如operator+(), operator*()
。其中op
必须是一个有效的C++运算符,不能虚构一个新符号。
比如,如果distrct2
, sid
和sara
都是Salesperson
类的对象,编写等式
district2 = sid + sara;
编译器发现,操作数是Salesperson
类对象,因此使用相应的运算符函数替换上述运算符
district2 = sid.operator+(sara);
// mytime.h -- Time class
#ifndef MYTIME_H_
#define MYTIME_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 Sum(const Time & t) const;
Time operator+(const Time & t) const; // operator overloading
void Show() const;
};
#endif
// mytime0.cpp -- implementing Time methods
#include <iostream>
#include "mytime0.h"
Time::Time(){
hours = minutes = 0;
}
Time::Time(int h, int m){
hours = h;
minutes = m;
}
void Time::AddMin(int m){
minutes += m;
hours += minutes / 60;
minutes % 60;
}
void Time::AddHour(int h){
hours += h;
}
void Time::Reset(int h, int m){
hours = h;
minutes = m;
}
Time Time::Sum(const Time & t) const{
Time sum;
sum.minutes = minutes + t.minutes
sum.hours = hours + t.hours + sum.minutes / 60;
sum.minutes = sum.minutes % 60;
return sum;
}
Time Time::operator+(const Time & t) const{
Time sum;
sum.minutes = minutes + t.minutes
sum.hours = hours + t.hours + sum.minutes / 60;
sum.minutes = sum.minutes % 60;
return sum;
}
void Time::Show() const{
std::cout << hours << " hours, " << minutes << " minutes";
}
C++对用户定义的运算符重载的限制:
- 重载后的运算符必须至少有一个操作数是用户定义的类型,防止用户为标准类型重载运算符。
- 使用运算符时不能违反与水暖福原来的句法规则。
- 不能创建新运算符
- 不能重载以下运算符:
sizeof
、.
、.*
、::
、?:
、typeid
、const_cast
、dynamic_cast
、reinterpret_cast
、static_cast
- 以下运算符只能通过成员函数进行重载:
=
、()
函数调用运算符、[]
下标运算符、->
2. 友元
友元函数、友元类和友元成员函数。通过让函数称为类的友元,可以赋予该函数与类的成员函数相同的访问权限。
为何需要友元
在为类重载二元运算符时,常常需要友元。比如重载的乘法运算符将一个对象值与一个基本类型值结合在一起,运算符左侧的操作数需要是调用对象A=B.operator*(2.75);
,即该类操作不满足交换律。
如果需要满足乘法交换律,则编译器不能使用成员函数调用来替换乘法表达式。
解决以上问题的一种方式时,采用非成员函数。非成员函数不是由对象调用的,它使用的所有值都是显式参数。使用非成员函数可以按所需的顺序获得操作数,但是非成员函数不能直接访问类的私有数据。
有一类特殊的非成员函数可以访问类的私有成员——友元函数。
2.1 创建友元
第一步:将其原型放在类声明中,并在原型声明前加上关键字friend
这意味着,operator*()
函数不是成员函数,因此不能使用成员运算符来调用;但是它与成员函数的访问权限相同。
第二步:编写函数定义。
friend Time operator*(double m, Time & t); // goes in class definition
Time operator*(double m, Time & t){ // friend not used in definition
Time result;
long totalminutes = t.hours * mult * 60 + t.minutes * mult;
result.hours = totalminutes / 60;
result.minutes = totalminutes % 60;
return result;
}
应当将友元函数看作类的扩展接口的组成部分。只有类声明可以决定哪一个函数是友元,因此类声明仍然控制了哪些函数可以访问私有数据。
类方法和友元只是表达类接口的两种不同机制。
2.2 常用的友元:重载<<
运算符
使得能够与cout
一起显示对象的内容。
ostream
类对<<
运算符进行了重载,将其转换为一个输出工具。
2.2.1 <<
的第一种重载版本
要使Time
类知道使用cout
,必须使用友元函数。(因为会使用到cout << time;
以及time << cout;
)
void operator<<(ostream & os, const Time & t){
os << t.hours << " hours, " << t.minutes << " minutes";
}
从以上定义可以看到,operator<<()
函数并不是ostram
类的友元,从始至终它都将ostream
对象当做一个整体使用。
2.2.2 <<
的第二种重载版本
2.2.1中的实现不允许如通常一样贱重新定义的<<
运算符与cout
一起使用:
cout << "Trip time: " << time << " (Tuesday)\n";
<<
运算符要求左边是一个ostream
对象,即需要将operator<<()
函数实现为返回一个指向ostream
对象的引用。
ostream & operator<<(ostream & os, const Time & t){
os << t.hours << " hours, " << t.minutes << " minutes";
return os;
}
3. 重载运算符:作为成员函数还是非成员函数
一般来说,非成员函数应当是友元函数,如此才能直接访问类的私有数据。
对于某些运算符而言,成员函数是唯一合法的选择;其他情况下,这两种格式没有太大区别。
4. 再谈重载:一个矢量类
显然应该为矢量重载运算符:
- 首先应当创建一个类表示矢量
- 其次矢量与普通数学运算有相似之处——重载运算符
// vect.h -- Vector class with <<, mode state
#ifndef VECTOR_H_
#define VECTOR_H_
#include <iostream>
namespace VECTOR{
class Vector{
public:
enum Mode {RECT, POL};
// RECT for rectangular, POL for Polar modes
private:
double x; // horizontal value
double y; // vertical value
double mag; // length of vector
double ang; // direction of vector in degrees
Mode mode; // RECT or POL
// private methods for setting values
void set_mag();
void set_ang();
void set_x();
void set_y();
public:
Vector();
Vector(double n1, double n2, Mode form = RECT);
void reset(double n1, double n2, Mode form = RECT);
~Vector();
double xval() const { return x;} // report x value
double yval() const { return y;} // report y value
double magval() const { return mag;} // report magnitude
double angval() const { return ang;} // report angle
void polar_mode(); // set mode to POL
void rect_mode(); // set mode to RECT
// operator overloading
Vector operator+(const Vector & b) const;
Vector operator-(const Vector & b) const;
Vector operator-() const;
Vector operator*(double n) const;
// friends
friend Vector operator*(double n, const Vector & a);
friend std::ostream & operator<<(std::ostream & os, const Vector & v);
};
} // end namespace VECTOR
#endif
// vect.cpp -- methods for the Vector class
#include <cmath>
#include "vect.h" // includes <iostream>
using std::sqrt
using std::sin;
using std::cos;
using std::atan;
using std::atan2;
using std::cout;
namespace VECTOR{
// compute degrees in one radian
const double Rad_to_deg = 45.0 / atan(1.0);
// private methods
// calculates magnitude from x and y
void Vector::set_mag(){
mag = sqrt(x*x + y * y);
}
void Vector::set_ang(){
if (x==0.0 && y==0.0)
ang = 0.0
else
ang = atan2(y, x);
}
// set x from polar coordinate
void Vector::set_x(){
x = mag * cos(ang);
}
void Vector::set_y(){
y = mag * sin(ang);
}
// public methods
Vector::Vector(){
x = y = ang = mag = 0.0;
mode = RECT;
}
Vector::Vector(double n1, double n2, Mode form){
mode = form;
if (form == RECT){
x = n1; y = n2;
set_mag(); set_ang();
}
else if (form == POL){
mag = n1; ang = n2;
set_x(); set_y();
}
else{
cout << "Incorrect 3rd argument to Vector() -- ";
cout << "vector set to 0\n";
x = y = mag = ang = 0.0;
mode = RECT
}
}
void Vector::reset(double n1, double n2, Mode form){
mode = form;
if (form == RECT){
x = n1; y = n2;
set_mag(); set_ang();
}
else if (form == POL){
mag = n1; ang = n2 / Rad_to_deg;
set_x(); set_y();
}
else{
cout << "Incorrect 3rd argument to Vector() -- ";
cout << "vector set to 0\n";
x = y = mag = ang = 0.0;
mode = RECT
}
}
Vector::~Vector(){}
void Vector::polar_mode(){
mode = POL;
}
void Vector::rect_mode(){
mode = RECT;
}
// operator overloading
Vector Vector::operaator+(const Vector & b) const{
return Vector(x + b.x, y + b.y);
}
Vector Vector::operator-(const Vector & b) const{
return Vector(x - b.x, y - b.y);
}
Vector Vector::operator-() const{
return Vector(-x, -y);
}
Vector Vector::operator*(double n) const{
return Vector(x * n, y * n);
}
// friend methods
Vector operator*(double n, const Vector & a){
return a * n;
}
std::ostream & operator<<(std::ostream & os, const Vector & v){
if (v.mode == Vector::RECT)
os << "(x, y) = (" << v.x << ", " << v.y << ")";
else if (v.mode == Vector::POL)
os << "(m, a) = (" << v.mag << ", " << v.ang << ")";
else
os << "Vector object mode is invalid";
return os;
}
} // end namespace VECTOR
// randwalk.cpp -- using the Vector class
// compile with the vect.cpp file
#include <iostream>
#include <cstdlib> // rand(), srand() prototypes
#include <ctime> // time() prototype
#include "vect.h"
int main(){
using namespace std;
using VECTOR::Vector;
srand(time(0)); // seed random-number generator
double direction;
Vector step;
Vector result(0.0, 0.0);
unsigned long steps = 0;
double target;
double dstep;
cout << "Enter target distance (q to quit): ";
while(cin >> target){
cout << "Enter step length: ";
if (!(cin >> dstep))
break;
while(result.magval() < target){
direction = rand(0) % 360;
step.reset(dstep, direction, Vector::POL);
result = result + step;
steps ++;
}
cout << "After " << steps << " steps, the subject has the following locations:\n";
cout << result << endl;
result.polar_mode();
cout << " or\n" << result << endl;
cout << "Average outward distance per step = " << result.magval()/steps << endl;
steps = 0;
results.reset(0.0, 0.0);
cout << "Enter target distance (q to quit): ";
}
cout << "Bye!\n";
cin.clear();
while(cin.get() != '\n')
continue;
return 0;
}
5. 类的自动转换和强制类型转换
C++语言不自动转换不兼容的类型。。
可以将类定义成与基本类型或另一个类相关,使得从一种类型转换为另一种类型是有意义的。
接收一个参数的构造函数为将类型与该参数相同的值转换。
- 将
Stonewt
对象初始化为double
值时 - 将
double
值赋给Stonewt
对象时 - 将
double
值传递给接收Stonewt
对象的函数时 - 返回值被声明为
Stonewt
的函数试图返回double
值时 - 上述任意一种情况下使用可转换为
double
类型的内置类型时
Stonewt(double lbs); // template for double-for-Stonewt conversion
Stonewt myCat; // create a Stonewt object
myCat = 19.6; // use Stonewt(double) to convert 19.6 to Stonewt
然而将构造函数用作自动类型转换函数可能会导致意外的类型转换。因此C++新增关键字explicit
用于关闭这种特性。
explicit Stonewt(double lbs); // no implicit conversions allowed
Stonewt myCat;
myCat = 19.6; // not valid
mycat = Stonewt(19.6); // ok, an explicit conversion
mycat = (Stonewt) 19.6; // ok, old form for explicit typecast
进行类转换为内置类型,需要使用特殊的C++运算符函数——转换函数。
Stonewt wolfe(285.7);
double host = double (wolfe); // syntax #1
double thinker = (double) wolfe; // syntax #2
double star = wolfe; // implicit use of conversion function
要转换typeName
类型,需要使用转换函数:operator typeName();
。转换函数必须是类方法,不能指定返回类型,且不能有参数。
operator int() const;
operator double() const;
Stonewt::operator int() const{
return int (pounds + 0.5); // pounds is a class attribute
}
Stonewt::operator double() const{
return pounds;
}
如果隐式转换存在二义性,则编译器拒绝语句。此时,需要采用显式强制类型转换来指出使用哪个转换函数。