【捡起C++】使用类

运算符重载

//mytime0.h -- Time class before operator overloading
#ifndef MYTIME0_H_
#define MYTIME0_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;
	void Show() const;
};
#endif // MYTIME0_H_
////mytime0.cpp -- implementing Time methods
#include 
#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::AddHr(int h)
{
	hours += h;
}

void Time::Reset(int h, int m)
{
	hours = h;
	minutes = m;
}

Time Time::Sum(const Time& t)const
//Time Time::operator + (const Time & t)const
{
	Time sum;
	sum.minutes = minutes + t.minutes;
	sum.hours = hours + t.hours + sum.minutes / 60;
	sum.minutes %= 60;
	return sum;
}

void Time::Show() const
{
	std::cout << hours << " hours, " << minutes << " minutes";
}
//usetime0.cpp -- using the first draft of the Time class
//compile usetime0.cpp and mytime0.cpp together
#include 
#include "mytime0.h"

int main()
{
	using std::cout;
	using std::endl;

	Time planning;
	Time coding(2, 40);
	Time fixing(5, 55);
	Time total;

	cout << "planning time = ";
	planning.Show();
	cout << endl;

	cout << "coding time = ";
	coding.Show();
	cout << endl;

	cout << "fixing time = ";
	fixing.Show();

	total = coding.Sum(fixing);
	cout << "\ncoding.Sum(fixing) = ";
	//操作符方法
	//total = coding + fixing;
	//cout << "coding + fixing = ";

	//函数方法
	//total = coning.operator + (fixing);
	//cout << "coding.operator + (fixing) = ";

	total.Show();
	cout << endl;

	return 0;
}

操作符重载版本

////mytime0.cpp -- implementing Time methods
#include 
#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::AddHr(int h)
{
	hours += h;
}

void Time::Reset(int h, int m)
{
	hours = h;
	minutes = m;
}

Time Time::Sum(const Time& t)const
//Time Time::operator + (const Time & t)const
{
	Time sum;
	sum.minutes = minutes + t.minutes;
	sum.hours = hours + t.hours + sum.minutes / 60;
	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 %= 60;
	return sum;
}

Time Time::operator-(const Time& t) const {
	Time diff;
	int tot1, tot2;
	tot1 = t.minutes + 60 * t.hours;
	tot2 = minutes + 60 * hours;
	diff.minutes = (tot2 - tot1) % 60;
	diff.hours = (tot2 - tot1) / 60;
	return diff;
}

Time Time::operator*(double mult) const {
	Time result;
	long totalminutes = hours * mult * 60 + minutes * mult;
	result.hours = totalminutes / 60;
	result.minutes = totalminutes % 60;
	return result;
}
void Time::Show() const
{
	std::cout << hours << " hours, " << minutes << " minutes";
}

友元

友元有三种:

  • 友元函数
  • 友元类
  • 友元成员函数

创建友元:

​ 创建友元函数的第一步是将其原型放在类声明中,并在原型前加上关键字friend:

friend Time operator*(double m, const Time& t); // goes in class declaration

​ 它不是成员函数,所以不要使用Time::限定符。另外,不要在定义中使用关键字friend,定义应该如下:

Time operator* (double m, const Time& t) // friend not used in definition
{
    Time result;
    long totalminutes = t.hours * m * 60 + t.minutes * m;
    result.hours = totalminutes / 60;
    result.minutes = totalminutes % 60;
    return result;
}

常用的友元:重载 << 运算符

​ 最初, << 运算符是C 和 C++的位运算符,将值中的位左移。ostream类对该运算符进行了重载,将其转换为一个输出工具。 对于每种基本类型,ostream类声明中都包含了相应的重载的operator << () 定义。

  1. <<的第一种重载版本

    要使Time类知道使用cout,必须使用友元函数。

    void operator << (ostream& os, const Time & t){
    	os << t.hours << " hours, " << t.minutes << " minutes";
    }
    
  2. <<的第二种重载版本

前面的实现存在一个问题,像下面这样的语句可以正常工作

```cpp
cout << trip;
```

但这种实现不允许

```
cout << "aaa" << trip;	
```

```cpp
int x = 5;
int y = 8;
cout << x << y;
```

 意味着它等同于

```cpp
(cout << x)<< y;
```

​       \<\<运算符要求左边是一个ostream对象。 因为cout 是ostream对象,所以 cout << x满足这种需求。 然而,表达式cout << x 位于 << y的左侧,所以输出语句也要求该表达式是一个ostream类型的对象。

​		 所以这里需要修改操作符重载函数,让他返回ostream对象的引用即可。

```cpp
ostream & operator << (ostream & os, const Time & t){
    os << t.hours << " hours, " << t.minutes << " minutes";
    return os;
}
```

提示:一般来说,要重载<<运算符来显示c_name的对象,可使用一个友元函数,其定义如下:

ostream & operator << (ostream& os, const c_name& obj){
    os << ...;  //display object contents
    return os;
}

//mytime3.h -- Time class before operator overloading
#ifndef MYTIME0_H_
#define MYTIME0_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*(double n) const;
    friend Time operator*(double m, const Time & t){
        return t * m;
    }
    friend std::ostream& operator << (std::ostream & os, const Time & t);
};
#endif // MYTIME0_H_
////mytime3.cpp -- implementing Time methods
#include 
#include "mytime3.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::AddHr(int h)
{
	hours += h;
}

void Time::Reset(int h, int m)
{
	hours = h;
	minutes = m;
}

Time Time::Sum(const Time& t)const
//Time Time::operator + (const Time & t)const
{
	Time sum;
	sum.minutes = minutes + t.minutes;
	sum.hours = hours + t.hours + sum.minutes / 60;
	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 %= 60;
	return sum;
}

Time Time::operator-(const Time& t) const {
	Time diff;
	int tot1, tot2;
	tot1 = t.minutes + 60 * t.hours;
	tot2 = minutes + 60 * hours;
	diff.minutes = (tot2 - tot1) % 60;
	diff.hours = (tot2 - tot1) / 60;
	return diff;
}

Time Time::operator*(double mult) const {
	Time result;
	long totalminutes = hours * mult * 60 + minutes * mult;
	result.hours = totalminutes / 60;
	result.minutes = totalminutes % 60;
	return result;
}
std::ostream & operator << (std::ostream & os, const Time& t){
    os << t.hours << " hours, " << t.minutes << " minutes";
    return os;
}
void Time::Show() const
{
	std::cout << hours << " hours, " << minutes << " minutes";
}
//usetime3.cpp
#include 
#include "mytime3.h"
int main(){
    using std::cout;
    using std::endl;
    Time aida(3, 35);
    Time tosca(2, 48);
    Time temp;
    cout << "Aida and Tosca:\n";
    cout << aida << "; " << tosca << endl;
    temp = aida + tosca;   //operator+()
    cout << "Aida + Tosca: " << temp << endl;
    temp = aida * 1.17;
    cout << "Aida * 1.17: " << temp << endl;
    cout << "10.0 * Tosca: " << 10.0 * tosca << endl'
    return 0;
}

重载运算符:作为成员函数还是非成员函数

​ Timer类的加法运算符在Time类声明中的原型如下:

Time operator+ (const Time& t) const;//member version	

​ 这个类也可以使用下面的原型:

friend Time operator+(const Time& t1, const Time& t2);//nonmember version

​ 什么是构造函数转换呢?通俗来讲,构造函数转换就是用户通过构造函数提供的定义类之间的自动类型转换。它的定义如下:
接收一个参数的构造函数可以实现这个参数类型和该类之间的自动转换。也就是说,只要是一个参数的构造函数,就可以作为转换函数,将类和其参数类型之间进行自动的转换。如:

MyClass(int i);//声明一个只有一个参数int的构造函数
Myclass mc;//通过默认参数获取mc对象
mc = 2;//这是可以的,通过构造函数转换完成了由int->MyClass的自动转换

​ 因此,可以将一个类定义成于基本类型或者另一个类相关,使得可以这这个相关类之间进行自动转换。然而,这样会导致意外的类型转换,怎么办呢?c++提供了一个关键字用于关闭这种特性:explicit关键字,该关键字用于关闭和用户通过构造转换函数定义的相关类之间的自动转换特性,但是可以显式的进行转换。下面通过示例来进一步理解这个特性。

//distance.h
#pragma once
const double M2K = 1.6;
//定义一个类,表示距离,并分别用km和miles表示
class Distance
{
private :
	int  m_km;
	double m_miles;
public:
	Distance();//无参构造
    Distance(int km);//只有一个int值的构造
	Distance(double mile);//只有一个double值的构造
	void show_by_km();//使用km输出
	void show_by_mile();//使用mile输出
	~Distance();//析构函数
};
//distance.cpp
#include "distance.h"
#include 
Distance::Distance()
{
	m_km = m_miles = 0;
}
Distance::Distance(int km)
{
	m_km = km;
	m_miles = km * M2K;
}
 
Distance::Distance(double mile)
{
	m_miles = mile;
	m_km = (int)m_miles / M2K;
}
 
void Distance::show_by_km()
{
	std::cout << "distance:" << m_km <<" km" << std::endl;
}
 
void Distance::show_by_mile()
{
	std::cout << "distance:" << m_miles << " mile(s)" << std::endl;
}
Distance::~Distance()
{
}
#include 
#include "distance.h"
using namespace std;
int main()
{	//使用无参构造初始化Distance对象
	Distance d;
	d = 23.4;//自动类型转换,通过Distance(double)构造函数
	d.show_by_mile();
	d.show_by_km();
	return 1;
}

​ 如果在声明构造方法时加上explicit关键字,则会关闭自动转换,但可以进行强制转换:

explicit Distance(int km);
//explicit关闭Distance和double自动转换特性
explicit Distance(double mile);

main.cpp中:

Distance d;
//由于使用了explicit关键字,只能进行强制转换
d = (Distance)23.4;

​ 那么编译器在什么时候调用构造转换函数进行自动转换呢?以上例为例,他会在什么时候调用Distance(int)或者Distance(double)呢?如果使用了关键字explicit,那么只会在强制类型转换时调用;如果没有使用explicit关键字,则有四种情况下会使用构造转换函数:

1.使用构造转换函数初始化时:Distance d(1.3);

2.将double值赋值给Distance时:Distance d; d = 1.3;

3.将double值传递给接收Distance对象或引用的函数时:

4.函数的返回值为Distance类型,但返回double值时:
Distance getDistance(double s)
{
	return s;
}

​ 对于第四点需要注意,如果使用getDistance(double)传入参数为long类型,那么如果只存在Distance(double),那么他会先将long自动转换为double,然后将double自动转换为Distance;如果还存在Distance(int),那么编译器将无法确定是先将long转换为double呢还是int呢,因此存在二义性。

转换函数

​ 转换函数采用如下的一般形式:

operator type() 

​ 这里的type可用内置类型、类类型或typedef名取代。但是不允许type表示数组或函数。
转换函数必须是成员函数,它的声明不能指定返回类型和参数列表。

​ 如果转换函数没有类成员进行修改,可以在函数声明中增加const关键字,增加代码的健壮性。

创建转换函数的规则

​ 1.转换函数必须是类的成员函数;

​ 2.转换函数不能指定返回类型,但有返回值;

​ 3.转换函数没有参数;

​ 例如,如果要将Distance d转换为double类型,则可以在Distance中定义如下的转换函数:

operator double();

​ 在Distance.cpp中编写转换函数:

Distance::operator double()
{
	return m_miles;
}

main.cpp中:

int main()
{	
	Distance distance;
	distance = 23.4;
	distance.show_by_mile();
	distance.show_by_km();
	//使用转换函数进行转换
	double temp = distance;
	cout << "temp:" <<temp<< endl;
	//这里没有重载<<操作符,为啥可以这样输出?
	cout << "distance:" << distance << endl;
	return 1;
}

​ 当调用double temp = d时编译器检测到类型不匹配,然后编译器会查看是否定义了与此匹配的转换函数,如果有定义,则进行转换,没有定义则编译失败。

    在上例中,并没有重载<<操作符,但是为什么在cout<

Distance.h中:

	operator int();

Distance.cpp中:

Distance::operator int()
{
	//四舍五入
	return (int)(m_miles + 0.5);
}

然后继续运行,结果发现编译失败了:

​ 系统提示“有多个运算符<<”, 原来是出现二义性了,因为当编译器查看是否有转换函数时,发现有两个相关类型的转换函数,不确定要转换为哪一个,因此出现二义性。

    要解决这个问题,可以进行显式的转换:
cout << "distance:" << (double)distance << endl; 
    和构造转换函数一样,转换函数也可以通过explicit关键字来取消其自动转换的特性,而是每次都通过显式的转换来进行转换,避免在用户不需要类型转换时就自动进行转换。
explicit operator double();
explicit operator int();

转换函数的优缺点
利用转换函数可以自动执行隐式转换,但是也正是这个原因,当用户不需要转换时,也会自动进行转换。因此,应该谨慎使用隐式自动转换,因尽量通过关键字explicit取消自动转换,使用显式类型转换。

你可能感兴趣的:(编程语言,C++)