C++ Primer Plus学习笔记11-使用类

本文介绍了C++中运算符重载的概念和限制,包括如何作为成员函数和非成员函数重载。同时,详细讨论了友元的作用,如创建友元函数,以及在重载运算符时的应用。文中还提到了类的自动转换和强制类型转换,以及转换函数的使用。
摘要由CSDN通过智能技术生成

1. 运算符重载

运算符重载是一种形式的C++多态。要重载运算符需要使用被称为运算符函数的特殊函数形式,格式如下:
operatorop(argument-list)
operator+(), operator*()。其中op必须是一个有效的C++运算符,不能虚构一个新符号。

比如,如果distrct2, sidsara都是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..*::?:typeidconst_castdynamic_castreinterpret_caststatic_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;
}

如果隐式转换存在二义性,则编译器拒绝语句。此时,需要采用显式强制类型转换来指出使用哪个转换函数。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值