运算符重载

1. 友元

一般来说,类的私有成员只能在类的内部访问,类之外是不能访问它们的。但如果将其他类或函数设置为类的友元(friend),就可以访问了。
友元的形式可以分为友元函数和友元类友元的声明是不受public/protected/private关键字限制的。具体操作是增加一条以friend关键字的声明语句:

class 类名A
{
//...
friend 函数原型;//可以是成员函数也可以是全局函数(自由函数/普通函数)
friend class 类名B;
//...
}

1.1 友元函数之全局函数

现在我们有一个全局函数distance,通过它计算两个点之间的距离,如果直接访问肯定是不行的,编译会报错。当我们在类Point中将其声明为友元之后,就可以了。

#include <math.h>
#include <iostream>

using std::cout;
using std::endl;

class Point
{
    //1.友元的第一种形式:友元之全局函数(自由函数、普通函数)
    friend double distance(const Point& lhs, const Point& rhs);
public:
    Point(int ix = 0, int iy = 0)
    : _ix(ix)
    , _iy(iy)
    {
        cout << "Point(int, int)" << endl;
    }

    void print() const
    {
        cout << "(" << _ix
            << ", " << _iy
            << ")" ;
    }

    ~Point()
    {
        cout << "~Point()" << endl;
    }
private:
    int _ix;
    int _iy;
};

double distance(const Point& lhs, const Point& rhs)
{
    return hypot(lhs._ix - rhs._ix, lhs._iy - rhs._iy);
}

void test()
{
    Point pt1(1, 2);
    Point pt2(4, 6);
    pt1.print();
    cout << "---->";
    pt2.print();
    cout << "之间的距离:" << distance(pt1, pt2) << endl;
}

int main(int argc, char* argv[])
{
    test();
    return 0;
}

1.2 友元函数之成员函数

假设类A有一个成员函数,该成员函数想去访问另一个类B类中的私有成员变量。这时候则可以在第二个类B中,声明第一个类A的那个成员函数为类B的友元函数,这样第一个类A的某个成员函数就可以访问第二个类B的私有成员变量了。

1.3 友元之友元类

如上面的描述,假设类A中不止有一个distance成员函数,还有其他成员函数,它们都需要访问类B的私有成员,如果还像上面的方式一个一个设置友元,就比较繁琐了,可以直接将A类设置为B的友元类。

#include <math.h>
#include <iostream>

using std::cout;
using std::endl;

class Point;//类的前向声明
class Line
{
public:
    //此时没有看到Point的定义,所以Point此时还是一个不完整类型
    double distance(const Point& lhs, const Point& rhs);
    void setPoint(Point& pt, int ix, int iy);
};
class Point
{ 
    //友元之成员函数
    /* friend double Line::distance(const Point& lhs, const Point& rhs); */
    /* friend void Line::setPoint(Point& pt, int ix, int iy); */
        
    //3.友元的第三种形式:友元之友元类
    friend class Line;
public:
    Point(int ix = 0, int iy = 0)
    : _ix(ix)
    , _iy(iy)
    {
        cout << "Point(int, int)" << endl;
    }

    void print() const
    {
        cout << "(" << _ix
            << ", " << _iy
            << ")" ;
    }

    ~Point()
    {
        cout << "~Point()" << endl;
    }
private:
    int _ix;
    int _iy;
};

//2.友元的第二种形式:友元之成员函数
double Line::distance(const Point& lhs, const Point& rhs)
{
    return hypot(lhs._ix - rhs._ix, lhs._iy - rhs._iy);
}

void Line::setPoint(Point& pt, int ix, int iy)
{
    pt._ix = ix;
    pt._iy = iy;
}

void test()
{
    /* Line line;//左值,对象 */
    Point pt1(1, 2);
    Point pt2(4, 6);
    pt1.print();
    cout << "---->";
    pt2.print();
    /* cout << "之间的距离:" << line.distance(pt1, pt2) << endl; */
    //创建临时对象
    cout << "之间的距离:" << Line().distance(pt1, pt2) << endl;
}

int main(int argc, char* argv[])
{
    test();
    return 0;
}

1.4 友元的性质

  1. 打破封装性,使得类的接口扩展更为灵活;
  2. 声明不受public/protected/private关键字限制的;
  3. 友元关系是单向的、不具备传递性、不能被继承。

2. 运算符重载

2.1 运算符重载的作用

C++预定义中的运算符的操作对象只局限于基本的内置数据类型,但是对于我们自定义的类型是没有办法操作的。但是大多时候我们需要对我们定义的类型进行类似的运算,这个时候就需要我们对运算符进行重新定义,赋予其新的功能,以满足自身的需求。

class Point{

	//...
	
public:
    Point(int ix = 0, int iy = 0)
    : _ix(ix)
    , _iy(iy)
    {
        cout << "Point(int, int)" << endl;
    }
    
    //...
    
private:
    int _ix;
    int _iy;
}

void test()
{
	/*int a = 10;*/
	/*int b = 20;*/
	/*int c = a + b; ok,内置类型*/
	Point pt1(1, 2);
	Point pt2(4, 6);
	Point pt3 = pt1 + pt2;//error,自定义类型,编译出错
}

为了使对用户自定义数据类型的数据的操作与内置数据类型的数据的操作形式一致,C++提供了运算符的重载,通过把C++中预定义的运算符重载为类的成员函数或者友元函数,使得对用户的自定义数据类型的数据(对象)的操作形式与C++内部定义的类型的数据一致。
**运算符重载的实质就是函数重载或函数多态。**运算符重载是一种形式的 C++ 多态。目的在于让人能够用同名的函数来完成不同的基本操作。要重载运算符,需要使用被称为运算符函数的特殊函数形式,运算符函数形式:

返回类型 operator 运算符(参数表)
{
	//...
}

2.2 运算符重载的规则

运算符是一种通俗、直观的函数,比如:int x = 2 + 3;语句中的“+”操作符,系统本身就提供了很多个重载版本:

int operator+(int, int);
double operator+(double, double);

但并不是所有的运算符都可以重载。可以重载的运算符有:

+-*/%^
&I~!=<
>+=-=*=/=%=
^=&=I=>><<>>=
<<===!=>=<=&&
II++- -->*->
[ ]( )newdeletenew[ ]delete[ ]

不可以重载的运算符:

..*?:::sizeof

关于sizeof运算符:

void test()
{
    int number = 10;
    printf("sizeof(number) = %lu\n", sizeof(number));
    printf("sizeof number = %lu\n", sizeof number);
}

结果如下:
sizeof(number) = 4
sizeof number = 4

印证了sizeof不是函数,如果它是函数的话,那么肯定需要使用参数列表

运算符重载还具有以下规则:

  1. 为了防止用户对标准类型进行运算符重载,C++规定重载的运算符的操作对象必须至少有一个是类类型或枚举类型(重要);
    在这里插入图片描述
  2. 重载运算符之后,其优先级和结合性还是固定不变的;
  3. 重载不会改变运算符的用法,原来有几个操作数、操作数在左边还是在右边,这些都不会改变;
  4. 重载运算符函数不能有默认参数,否则就改变了运算符操作数的个数(防止作为成员函数的隐含this)
  5. 重载逻辑运算符(&&,||)后,不再具备短路求值特性;
  6. 不能臆造一个并不存在的运算符,如@、$等。

2.3 运算符重载的形式

对于运算符重载而言,要确定函数的名字(这个比较简单),然后需要确定函数的参数列表,最后确定函数的返回类型。
运算符重载的形式有三种:

  1. 采用普通函数的重载形式;
  2. 采用成员函数的重载形式;
  3. 采用友元函数的重载形式。

2.3.1 运算符重载之普通函数

在上面的例子中,Complex对象无法执行加法操作,接下来我们重载+运算符。由于之前的定义中Complex的成员都设置成了private成员,所以不能访问,我们需要在类中添加2个get函数,获取其值。

class Complex
{
public:
	//...
	
	double getReal() const
	{
		return _real;
	}
	
	double getImage() const
	{
		return _image;
	}
	
	//...
};

Complex operator+(const Complex &lhs, const Complex &rhs)
{
	return Complex(lhs.getReal() + rhs.getReal(), lhs.getImage() + rhs.getImage());
}

void test()
{
	Complex c1(1, 2), c2(3, 4);
	Complex c3 = c1 + c2;//编译通过
}

2.3.2 运算符重载之成员函数

成员函数形式的运算符声明和实现与成员函数类似,首先应当在类定义中声明该运算符,声明的具体形式为:

返回类型 operator 运算符(参数列表);

既可以在类定义的同时,定义运算符函数使其成为inline型,也可以在类外定义运算符函数,但要使用作用域限定符::,类外定义的基本格式为:

返回类型 类名::operator 运算符(参数列表)
{
	//...
}

注意:用成员函数重载双目运算符时,左操作数无须用参数输入,而是通过隐含的this指针传入。回到Complex的例子,如果以成员函数形式进行重载,则不需要定义get函数,但形式上少一个参数

class Complex
{
public:
	//...
	Complex operator+(const Complex & rhs)
	{
		return Complex(_real + rhs._real, _image + rhs._image);
	}
	//...
};

2.3.3 运算符重载之友元函数

如果以友元函数形式进行重载,同样不需要定义get函数(这种方式比较推荐):

class Complex
{
	//...
	friend Complex operator+(const Complex &lhs, const Complex &rhs);
	//...
};

Complex operator+(const Complex &lhs, const Complex &rhs)
{
	return Complex(lhs._real + rhs._real, lhs._image + rhs._image);
}

运算符重载可以改变运算符内置的语义,如以友元函数形式定义的加操作符:

Complex operator+(const Complex &lhs,const Complex &rhs)
{
	return Complex(lhs._real - rhs._real, lhs._image - rhs._image);
}

明明是加操作符,但函数内却进行的是减法运算,这是合乎语法规则的,不过却有悖于人们的直觉思维,会引起不必要的混乱。因此,除非有特别的理由,尽量使重载的运算符与其内置的、广为接受的语义保持一致。

3. 特殊运算符的重载

3.1 复合赋值运算符

因为对象本身会发生变化,复合赋值运算符推荐以成员函数的形式进行重载,包括这些(+=,-=,*=,/=,%=,<<=,>>=,&=,^=,|=)。我们以+=为例讨论:

class Complex
{
	//...
public:
	//对于复合赋值运算符,对象本身发生了改变,推荐使用成员函数形式
	//& 实体生命周期大于函数的生命周期 
	Complex &operator+=(const Complex &rhs)
	{
		cout << "Complex &operator+=(const Complex &)" << endl;
		_dreal += rhs._dreal;
		_dimag += rhs._dimag;
		return *this;
	}
	//...
};

void test()
{
	Complex c1(1, 2), c2(3, 4);
	c2 += c1;//原型:c2.operator+=(c1);
}

3.2 自增自减运算符

自增运算符++和自减运算符–推荐以成员函数形式重载,分别包含两个版本,即运算符前置形式(如 ++x)和运算符后置形式(如 x++),这两者进行的操作是不一样的。因此,当我们在对这两个运算符进行重载时,就必须区分前置和后置形式。
C++根据参数的个数来区分前置和后置形式。如果按照通常的方法(成员函数不带参数)来重载++/- -运算符,那么重载的就是前置版本。要对后置形式进行重载,就必须为重载函数再增加一个int类型的参数,该参数仅仅用来告诉编译器这是一个运算符后置形式,在实际调用时不需要传递实参。

class Complex
{
public:
	//...
	
	//前置形式
	Complex& operator++()
	{
		++_real;
		++_image;
		return *this;
	}
	
	//后置形式
	Complex operator++(int) //int作为标记,并不传递参数
	{
		Complex tmp(*this);//tmp是一个局部变量
		_real++;
		_image++;
		return tmp;//返回了临时对象
	}
	
	//...
};

void test()
{
	int a = 3;
	int b = 4;
	(++a);//表达式的值与a的值,需要进行区分,对于重载前置++与后置++是有一定参考价值的
	(a++);
}

我们从这里也可以得出这样的结论:

  • 前置++比后置++的效率高,因为后置++函数在调用的时候,会有拷贝构造函数的调用,会有局部对象的销毁,这些都会产生开销;
  • 前置++的返回值是一个左值,可以进行取地址;后置++的返回值是一个右值,不能进行取地址。

3.3 赋值运算符

对于赋值运算符 =,只能以成员函数形式进行重载,我们已经在类和对象中探讨,就不再赘述。链接: link

3.4 输入输出流运算符

在之前的例子中,我们如果想打印一个对象时,常用的方法是通过定义一个 print 成员函数来完成,但使用起来不太方便。我们希望打印一个对象,与打印一个整型数据在形式上没有差别(如下例子),那就必须要重载<<运算符。

void test()
{
	int a = 1, b = 2;
	cout << a << b << endl;
	Point pt1(1, 2), pt2(3, 4);
	/* pt1.print();*/
	/* pt2.print();*/
	cout << pt1 << pt2 << endl;
}

从上面的形式能看出,cout是左操作数,a或者pt1是右操作数,那输入输出流能重载为成员函数形式吗?我们假设是可以的,由于非静态成员函数的第一个参数是隐含的this指针,代表当前对象本身,这与其要求是冲突的,违背了运算符重载的规则(不能改变操作数的位置),因此>>和<<不能重载为成员函数,只能是非成员函数,如果涉及到要对类中私有成员进行访问,还得将非成员函数设置为类的友元函数

class Point
{
public:
	//...
	friend ostream &operator<<(ostream &os, const Point &rhs);
	friend istream &operator>>(istream &is, Point &rhs);
private:
	int _ix;
	int _iy;
};

ostream & operator<<(ostream &os, const Point &rhs)
{
	os << "(" << rhs._ix
	   << "," << rhs._iy
	   << ")";
	return os;
}

istream &operator>>(istream &is, Point &rhs)
{
	/* is >> rhs._ix >> rhs._iy; */
	is >> rhs._ix;
	is >> rhs._iy;
	return is;
}

注意:函数的参数中的引用符号(流的引用符号)与函数返回类型中的引用符号此处是不能去掉的,因为对于流而言,拷贝构造函数早就已经被删除了(不提供了)。

cout << "c1 = " << c1 << endl;

原型:operator<<(operator<<(cout, "c1 = "), c1).operator<<(endl);

为什么?
<< 重载的形式不同。

cout << "c1 = " << c1 << endl << 1 << c1 << 3 << "hello" << endl;//简写,链式编程

3.5 函数调用运算符

我们知道,普通函数执行时,有一个特点就是无记忆性,一个普通函数执行完毕,它所在的函数栈空间就会被销毁,所以普通函数执行时的状态信息,是无法保存下来的,这就让它无法应用在那些需要对每次的执行状态信息进行维护的场景。大家知道,我们学习了成员函数以后,有了对象的存在,对象执行某些操作之后,只要对象没有销毁,其状态就是可以保留下来的,但在函数作为参数传递时,会有障碍。为了解决这个问题,C++引入了函数调用运算符。函数调用运算符的重载形式只能是成员函数形式,其形式为:

返回类型 类名::operator()(参数列表)
{
	//...
}

在定义()运算符的语句中,第一对小括号总是空的,因为它代表着我们定义的运算符名,第二对小括号就是函数参数列表了,它与普通函数的参数列表完全相同。对于其他能够重载的运算符而言,操作数个数都是固定的,但函数调用运算符不同,它的参数是根据需要来确定的, 并不固定。举例子说明如何使用:

#include <iostream>

using std::cout;
using std::endl;

//将重载了函数调用运算符的类创建的对象称为函数对象
class FunctionObject
{
public:
    FunctionObject()
    : _cnt(0)
    {

    }
    
    int operator()(int x, int y)
    {
        cout << "int operator()(int, int)" << endl;
        ++_cnt;
        return x + y;
    }

    int operator()(int x, int y, int z)
    {
        cout << "int operator()(int, int, int)" << endl;
        ++_cnt;
        return x * y * z;
    }

private:
    int _cnt;//函数对象的状态
};

int func(int x, int y)
{
    cout << "int func()(int, int)" << endl;
    static int cnt = 0;
    ++cnt;
    return x + y;
}

void test()
{
    int a = 3, b = 4, c = 5;
    FunctionObject fo;
    /* 原型:fo.operator()(a, b); */
    cout << "fo(a, b) = " << fo(a, b) << endl;

    /* 原型:fo.operator()(a, b, c); */
    cout << "fo(a, b, c) = " << fo(a, b, c) << endl;

    cout << "func(a, b) = " << func(a, b) << endl;


}

int main(int argc, char* argv[])
{
    test();
    return 0;
}

从例子可以看出,一个类如果重载了函数调用operator(),就可以将该类对象作为一个函数使用。对于这种重载了函数调用运算符的类创建的对象,我们称为函数对象(FunctionObject)。函数也是一种对象,这是泛型思考问题的方式。

3.6 下标访问运算符

下标访问运算符[]通常用于访问数组元素,它是一个二元运算符,如arr[idx]可以理解成arr是左操作数,idx是右操作数。对下标访问运算符进行重载时,只能以成员函数形式进行,如果从函数的观点来看,语句arr[idx];可以解释为arr.operator;,因此下标访问运算符的重载形式如下:

返回类型 &类名::operator[](参数类型);
返回类型 &类名::operator[](参数类型) const;

下标运算符的重载函数只能有一个参数,不过该参数并没有类型限制,任何类型都可以。如果类中未重载下标访问运算符,编译器将会给出其缺省定义,在其表达对象数组时使用。

#include <string.h>
#include <iostream>

using std::cout;
using std::endl;

/* int arr[10] = {1, 2, 3, 4, 7, 9}; */
/* arr.operator[](size_t idx) */

class CharArray
{
public:
    CharArray(size_t sz = 10)
    : _size(sz)
    , _data(new char[_size]())
    {

    }

    //下标访问运算符的重载可以让程序更加安全点
    //Q1:引用符号什么时候需要加上?
    //A1:有如下情况:
    //1、如果函数的返回值的生命周期比函数的生命周期大的时候,为了避免在执行return语句时多执行一次拷贝操作,应使用;
    //2、在流中,是可以无限传递参数执行下去,这个时候用引用可以减少多次拷贝,提高程序的执行效率;
    //3、在赋值运算符函数的返回类型的时候,也是返回的引用,因为连等的时候,可少执行拷贝操作,也提高程序执行效率。
    char& operator[](size_t idx)
    {
        /* { */
        /*     int a = 10; 缩短变量的生命周期*/
        /* } */
        /* const int &ref = 10;延长变量的生命周期 */
        cout << "char& operator[](size_t)" << endl;
        if(idx < _size)
        {
            return _data[idx];
        }
        else
        {
            static char nullchar = '\0';//static延长生命周期
            return nullchar;
        }
    }

    size_t size() const
    {
        return _size;
    }

    ~CharArray()
    {
        if(_data)
        {
            delete [] _data;
            _data = nullptr;
        }
    }

private:
    size_t _size;
    char *_data;
};

void test()
{
    
    const char* pstr = "helloworld";
    CharArray ca(strlen(pstr) + 1);


    for(size_t idx = 0; idx != ca.size(); ++idx)
    {
        /*原型: ca.operator[](idx) = pstr[idx]; */
        ca[idx] = pstr[idx];
    }

    for(size_t idx = 0; idx != ca.size(); ++idx)
    {
        /* cout << ca.operator[](idx) << " "; */
        cout << ca[idx] << " ";
    }
    cout << endl;
}

int main(int argc, char* argv[])
{
    test();
    return 0;
}

我们之前使用过的std::string同样也重载了下标访问运算符,这也是为什么它能像数组一样去访问元素的原因。

3.7 成员访问运算符

成员访问运算符包括箭头访问运算符->和解引用运算符*,我们先来看箭头运算符->,箭头运算符只能以成员函数的形式重载,其返回值必须是一个指针或者重载了箭头运算符的对象。来看下例子:

#include <iostream>

using std::cout;
using std::endl;

class Data
{
public:
    Data(int data = 0)
    : _data(data)
    {
        cout << "Data(int = 0)" << endl;
    }

    int getData() const
    {
        return _data;
    }

    ~Data()
    {
        cout << "~Data()" << endl;
    }
private:
    int _data;
};

class SecondLayer
{
public:
    SecondLayer(Data *pdata)
    : _data(pdata)
    {
        cout << "SecondLayer(Data*)" << endl;
    }
    
    //重载箭头访问运算符
    Data* operator->()
    {
        return _data;
    }

    //解引用运算符的重载
    Data& operator*()
    {
        return *_data;
    }

    ~SecondLayer()
    {
        cout << "~SecondLayer()" << endl;
        if(_data)
        {
            delete _data;
            _data = nullptr;
        }
    }
private:
    Data *_data;

};

class ThirdLayer
{
public:
    ThirdLayer(SecondLayer *psl)
    : _sl(psl)
    {
        cout << "ThirdLayer(SecondLayer*)" << endl;
    }
    
    SecondLayer& operator->()
    {
        return *_sl;
    }


    ~ThirdLayer()
    {
        cout << "ThirdLayer()" << endl;
        if(_sl)
        {
            delete _sl;
            _sl = nullptr;
        }
    }
private:
    SecondLayer *_sl;

};

void test()
{
    //此时sl可以看成智能指针:
    SecondLayer sl(new Data(10));//sl是栈对象
    /*原型: sl.operator->()->getData(); */
    cout << "sl->getData() = " << sl->getData() << endl;
    
    ThirdLayer tl(new SecondLayer(new Data(20)));
    /*原型: tl.operator->().operator->()->getData(); */
    cout << "tl->getData() = " << tl->getData() << endl;
}

void test1()
{
    
    SecondLayer sl(new Data(10));
    /*原型: sl.operator*().getData(); */
    cout << "(*sl).getData() = " << (*sl).getData() << endl;    
}

int main(int argc, char* argv[])
{
    test();
    cout << "=========" << endl;
    test1();
    return 0;
}

运行结果:
在这里插入图片描述

3.8 小结

对于运算符重载时采用的形式的建议:

  • 所有的一元运算符,建议以成员函数重载;
  • 运算符 = () [] -> ->* ,必须以成员函数重载;
  • 运算符 += -= /= *= %= ^= &= != >>= <<= 建议以成员函数形式重载;
  • 其它二元运算符,建议以非成员函数重载。

4. 类型转换函数

前面介绍过对普通变量的类型转换,比如说 int 型转换为long型,double型转换为int型,接下来我们要讨论下类对象与其他类型的转换。转换的方向有:

  • 由其他类型向自定义类型转换;
  • 由自定义类型向其他类型转换。

4.1 由其他类型向自定义类型转换

由其他类型向自定义类型转换是由构造函数来实现的,只有当类中定义了合适的构造函数时,转换才能通过。这种转换,一般称为隐式转换
这种隐式转换有时候用起来是挺好的,再比如,我们以前学过的std::string,当执行:

std::string s1 = "hello,world"; 

该语句时,这里其实是有隐式转换的,但该隐式转换的执行很自然,很和谐。而上面把一个int型数据直接赋值给一个Point对象,看起来就是比较诡异的,难以接受,所以这里我们是不希望发生这样的隐式转换的。那怎么禁止隐式转换呢,比较简单,只需要在相应构造函数前面加上explicit关键字就能解决。

4.2 由自定义类型向其他类型转换

由自定义类型向其他类型的转换是由类型转换函数完成的,这是一个特殊的成员函数。它的形式如下:

operator 目标类型()
{
	//...
}

类型转换函数具有以下的特征:

  1. 必须是成员函数;
  2. 参数列表中没有参数;
  3. 没有返回值;
  4. 函数体内必须以return语句返回一个目标类型的变量。
    我们来看一个例子说明:
#include <iostream>

using std::cout;
using std::endl;
using std::ostream;


class Complex
{
public:
    Complex(double dreal = 0, double dimag = 0)
    : _dreal(dreal)
    , _dimag(dimag)
    {
        cout << "Complex(double, double)" << endl;
    }

    Complex& operator+=(const Complex& rhs)
    {
        cout << "Complex& operator+=(const Complex&)" << endl;
        _dreal += rhs._dreal;
        _dimag += rhs._dimag;
        return *this;
    }
    
    friend ostream& operator<<(ostream& os1, const Complex& rhs);

    ~Complex()
    {
        cout << "~Complex()" << endl;
    }

private:
    double _dreal;
    double _dimag;
};

class Point
{
public:
    Point(int ix = 0, int iy = 0)
    : _ix(ix)
    , _iy(iy)
    {
        cout << "Point(int, int)" << endl;
    }

    friend ostream& operator<<(ostream& os, const Point& rhs);
    
    operator int()
    {
        cout << "operator int()" << endl;
        return _ix + _iy;
    }
    
    
    operator double()
    {
        cout << "operator double()" << endl;
        if(0 == _iy)
        {
            return 0.0;
        }
        else
        { 
            return (static_cast<double>(_ix)/_iy);//强制转换
        }
    }

    operator Complex()
    {
        cout << "operator Complex()" << endl;
        return Complex(_ix, _iy);
    }

    ~Point()
    {
        cout << "~Point()" << endl;
    }

private:
    int _ix;
    int _iy;
};

ostream& operator<<(ostream& os, const Complex& rhs)
{
    cout << "ostream& operator<<(ostream&, const Complex&)" << endl;
    os << rhs._dreal 
       << " + " << rhs._dimag
        << "i" << endl;
    return os;
}

ostream& operator<<(ostream& os1, const Point& rhs)
{
    cout << "ostream& operator<<(ostream&, const Point&)" << endl;
    os1 << "(" << rhs._ix 
        << ", " << rhs._iy
        << ")" << endl;
    return os1;
}

void test()
{
    Point pt(1, 2);
    cout << "pt = " << pt << endl;
 
    cout << "=========" << endl;
    //1、其他类型向自定义类型转换
    //类型不匹配,由合适的构造函数做支撑
    // Point   <-----   int    5 == Point(5, 0);
    Point pt1 = 5;
    cout << "pt1 = " << pt1 << endl;

    cout << "=========" << endl;
    
   //2、自定义类型向其他类型转换
   //将Point看成是自定义类型,将所有的int,double,Complex看成其他类型 
    int ix = pt;// Point ===>  int
    cout << "ix = " << ix << endl;

    double dy = pt;// Point ===> double
    cout << "dy = " << dy << endl;

    Complex c1 = pt;//Point ===> Complex
    cout << "c1 = " << c1 << endl;
}

int main(int argc, char* argv[])
{
    test();
    return 0;
}

运行结果:
在这里插入图片描述
再看一个比较特殊的点:

#include <iostream>

using std::cout;
using std::endl;
using std::ostream;



class Point
{
public:
    Point(int ix = 0, int iy = 0)
    : _ix(ix)
    , _iy(iy)
    {
        cout << "Point(int, int)" << endl;
    }

    operator int()
    {
        cout << "operator int()" << endl;
        return _ix + _iy;
    }
    
#if 0
    operator double()
    {
        cout << "operator double()" << endl;
        if(0 == _iy)
        {
            return 0.0;
        }
        else
        { 
            return (static_cast<double>(_ix)/_iy);//强制转换
        }
    }

    operator Complex()
    {
        cout << "operator Complex()" << endl;
        return Complex(_ix, _iy);
    }
#endif

    ~Point()
    {
        cout << "~Point()" << endl;
    }

private:
    int _ix;
    int _iy;
};


void test()
{
    Point pt(1, 2);
    //没有重载输出流运算符函数
    //实现了Point =====> int 
    cout << "pt = " << pt << endl; 
}

int main(int argc, char* argv[])
{
    test();
    return 0;
}

结果如下:
在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值