C++运算符重载


  • C++运算符重载的实质:
    运算符重载的实质就是函数重载或函数多态。运算符重载是一种形式的C++多态。目的在于让人能够用同名的函数来完成不同的基本操作。

  • 运算符重载规则如下:
    ①、 C++中的运算符除了少数几个之外,全部可以重载,而且只能重载C++中已有的运算符。
    ②、 重载之后运算符的优先级和结合性都不会改变。
    ③、 运算符重载是针对新类型数据的实际需要,对原有运算符进行适当的改造。一般来说,重载的功能应当与原有功能相类似,不能改变原运算符的操作对象个数,同时至少要有一个操作对象是自定义类型。
    不能重载的运算符只有五个,它们是:成员运算符“.”、指针运算符“*”、作用域运算符“::”、“sizeof”、条件运算符“?:”。

  • 运算符重载形式有两种:

重载为类的成员函数&重载为类的友元函数。
  1. 函数类型 operator 运算符(形参表)
    {
    函数体;
    }

  2. friend 函数类型 operator 运算符(形参表)
    {
    函数体;
    }

  • 没有运算符重载:
Fred add(Fred, Fred);
Fred mul(Fred, Fred);

Fred f(Fred a, Fred b, Fred c)
{
return add(add(mul(a,b), mul(b,c)), mul(c,a));
}
  • 有运算符重载:
Fred operator+ (Fred, Fred);
Fred operator* (Fred, Fred);
Fred f(Fred a, Fred b, Fred c)
{
return a*b + b*c + c*a
}
  • 重载为类的成员函数

举个复数运算重载复数的四则运算符的栗子。
复数由实部和虚部构造,可以定义一个复数类,然后再在类中重载复数四则运算的运算符。

#include <iostream.h>
class complex
{
public:
complex() { real=imag=0; }
complex(double r, double i)
{
real = r, imag = i;
}
complex operator +(const complex &c);
complex operator -(const complex &c);
complex operator *(const complex &c);
complex operator /(const complex &c);
friend void print(const complex &c);

private:
double real, imag;
};
inline complex complex::operator +(const complex &c)
{
return complex(real + c.real, imag + c.imag);
}

inline complex complex::operator -(const complex &c)
{
return complex(real - c.real, imag - c.imag);
}
inline complex complex::operator *(const complex &c)

{
return complex(real * c.real - imag * c.imag, real * c.imag + imag * c.real);
}

inline complex complex::operator /(const complex &c)
{
return complex((real * c.real + imag + c.imag) / (c.real * c.real + c.imag * c.imag),
(imag * c.real - real * c.imag) / (c.real * c.real + c.imag * c.imag));
}

void print(const complex &c)
{
if(c.imag<0)
cout<<c.real<<c.imag<<'i';
else
cout<<c.real<<'+'<<c.imag<<'i';
}

 void main()
{
complex c1(2.0, 3.0), c2(4.0, -2.0), c3;
c3 = c1 + c2;
cout<<"/nc1+c2=";
print(c3);
c3 = c1 - c2;
cout<<"/nc1-c2=";
print(c3);
c3 = c1 * c2;
cout<<"/nc1*c2=";
print(c3);
c3 = c1 / c2;
cout<<"/nc1/c2=";
print(c3);
c3 = (c1+c2) * (c1-c2) * c2/c1;
cout<<"/n(c1+c2)*(c1-c2)*c2/c1=";
print(c3);
cout<<endl;
}

该程序的运行结果为:
c1+c2=6+1i
c1-c2=-2+5i
c1c2=14+8i
c1/c2=0.45+0.8i
(c1+c2)
(c1-c2)*c2/c1=9.61538+25.2308i

大家对代码里面的那个inline 是不是有点困惑,我给大家科普一下:
有inline的函数叫做内联函数。

  • 我们为什么要用inline关键字呢?
    在c/c++中,为了解决一些频繁调用的小函数大量消耗栈空间(栈内存)的问题,特别的引入了inline修饰符,表示为内联函数。

    栈空间就是指放置程序的局部数据(也就是函数内数据)的内存空间。

    在系统下,栈空间是有限的,假如频繁大量的使用就会造成因栈空间不足而导致程序出错的问题,如,函数的死循环递归调用的最终结果就是导致栈内存空间枯竭。

  • inline使用限制

inline的使用是有所限制的,inline只适合涵数体内代码简单的涵数使用,不能包含复杂的结构控制语句例如while、switch,并且不能内联函数本身不能是直接递归函数(即,自己内部还调用自己的函数)。

  • inline仅是一个对编译器的建议

inline函数仅仅是一个对编译器的建议,所以最后能否真正内联,看编译器的意思,它如果认为函数不复杂,能在调用点展开,就会真正内联,并不是说声明了内联就会内联,声明内联只是一个建议而已。

  • 关键字inline 必须与函数定义体放在一起才能使函数成为内联,仅将inline 放在函数声明前面不起任何作用。

  • 谨慎使用inline:

以下情况不宜使用内联:
(1)如果函数体内的代码比较长,使用内联将导致内存消耗代价较高。
(2)如果函数体内出现循环,那么执行函数体内代码的时间要比函数调用的开销大。类的构造函数和析构函数容易让人误解成使用内联更有效。要当心构造函数和析构函数可能会隐藏一些行为,如“偷偷地”执行了基类或成员对象的构造函数和析构函数。所以不要随便地将构造函数和析构函数的定义体放在类声明中。一个好的编译器将会根据函数的定义体,自动地取消不值得的内联(这进一步说明了 inline 不应该出现在函数的声明中)。

  • 只有当函数非常短小的时候它才能得到我们想要的效果;但是,如果函数并不是很短而且在很多地方都被调用的话,那么将会使得可执行体的体积增大。
  • 疑难问题:

all = total 1.5; 是可以运行的,all = 1.5total可以运行吗?

不可以,我们还原下此语句:all = total.operator*(1.5),换成1.5在乘号前面当然是不可以的。因为1.5不是对象。

这个时候有两种解决方式:

一:告诉每个人只能按照adjusted = total *1.5;

二:非成员函数,声明为 CMyTime operator *(double m,const CMyTime &t);

这样等价于:A = operator *(1.5,B)等价于:A = 1.5 * B;出于性能考虑,我们将其声明为friend 即友元函数(前面已经解释过为什么要声明为友元了):

friend CMyTime operator*(double m,const CMyTime &t);

接下来我们这样做:在.......h文件的public中加入:
friend CMyTime operator(double m, const CMyTime &t){ return tm; } (因为这个函数操作很简单,可以直接为内联函数);
然后就可以运行 A = 1.5 * B语句了。

  • 重载为类的友元函数

  • 为什么要用友元函数?
    因为大多数时候重载运算符要访问类的私有数据,(当然也可以设置为非友元非类的成员函数。但是非友元又不是类的成员函数是没有办法直接访问类的私有数据的),如果不声明为类的友元函数,而是通过在此函数中调用类的公有函数来访问私有数据会降低性能。所以一般都会设置为类的友元函数,这样我们就可以在此非成员函数中访问类中的数据了。

运算符重载函数还可以为友元函数。当重载友元函数时,将没有隐含的参数this指针。这样,对双目运算符,友元函数有2个参数,对单目运算符,友元函数有一个参数。但是,有些运行符不能重载为友元函数,它们是:=,(),[]和->。

#include <iostream.h>
class complex
{
public:
complex() { real=imag=0; }
complex(double r, double i)
{
real = r, imag = i;
}
friend complex operator +(const complex &c1, const complex &c2);
friend complex operator -(const complex &c1, const complex &c2);
friend complex operator *(const complex &c1, const complex &c2);
friend complex operator /(const complex &c1, const complex &c2);

void print(const complex &c);
private:
double real, imag;
};

complex operator +(const complex &c1, const complex &c2)
{
return complex(c1.real + c2.real, c1.imag + c2.imag);
}
complex operator -(const complex &c1, const complex &c2)
{
return complex(c1.real - c2.real, c1.imag - c2.imag);
}

complex operator *(const complex &c1, const complex &c2)
{
return complex(c1.real * c2.real - c1.imag * c2.imag, c1.real * c2.imag + c1.imag * c2.real);
}

complex operator /(const complex &c1, const complex &c2)
{
return complex((c1.real * c2.real + c1.imag * c2.imag) / (c2.real * c2.real + c2.imag * c2.imag),

(c1.imag * c2.real - c1.real * c2.imag) / (c2.real * c2.real + c2.imag * c2.imag));
}

void print(const complex &c)
{
if(c.imag<0)
cout<<c.real<<c.imag<<'i';
else
cout<<c.real<<'+'<<c.imag<<'i';
}

void main()
{
complex c1(2.0, 3.0), c2(4.0, -2.0), c3;
c3 = c1 + c2;
cout<<"/nc1+c2=";
print(c3);
c3 = c1 - c2;
cout<<"/nc1-c2=";
print(c3);
c3 = c1 * c2;
cout<<"/nc1*c2=";
print(c3);
c3 = c1 / c2;
cout<<"/nc1/c2=";
print(c3);
c3 = (c1+c2) * (c1-c2) * c2/c1;
cout<<"/n(c1+c2)*(c1-c2)*c2/c1=";
print(c3);
cout<<endl;
} 
  • 两种重载形式的比较

一般说来,单目运算符最好被重载为成员;对双目运算符最好被重载为友元函数,双目运算符重载为友元函数比重载为成员函数更方便此,但是,有的双目运算符还是重载为成员函数为好,例如,赋值运算符
因为,它如果被重载为友元函数,将会出现与赋值语义不一致的地方。
其他运算符的重载举例:
1).下标运算符重载
由于C语言的数组中并没有保存其大小,因此,不能对数组元素进行存取范围的检查,无法保证给数组动态赋值不会越界。利用C++的类可以定义一种更安全、功能强的数组类型。为此,为该类定义重载运算符[]。

下面一个例子:

#include <iostream.h>

class CharArray

{

public:

CharArray(int l)

{

Length = l;

Buff = new char[Length];

}

~CharArray() { delete Buff; }

int GetLength() { return Length; }

char & operator [](int i);

private:

int Length;

char * Buff;

};

 

char & CharArray::operator [](int i)

{

static char ch = 0;

if(i<Length&&i>=0)

return Buff[i];

else

{

cout<<"/nIndex out of range.";

return ch;

}

}

 

void main()

{

int cnt;

CharArray string1(6);

char * string2 = "string";

for(cnt=0; cnt<8; cnt++)

string1[cnt] = string2[cnt];

cout<<"/n";

for(cnt=0; cnt<8; cnt++)

cout<<string1[cnt];

cout<<"/n";

cout<<string1.GetLength()<<endl;

}
  • 该数组类的优点如下:

(1) 其大小不一定是一个常量。

(2) 运行时动态指定大小可以不用运算符new和delete。

(3) 当使用该类数组作函数参数时,不用分别传递数组变量本身及其大小,因为该对象中已经保存大小。

在重载下标运算符函数时应该注意:

(1) 该函数只能带一个参数,不可带多个参数。

(2) 不得重载为友元函数,必须是非static类的成员函数。

  • 重载增1减1运算符(++ --)

增1减1运算符是单目运算符。它们又有前缀和后缀运算两种。为了区分这两种运算,将后缀运算视为又目运算符。表达式 obj++或obj--
被看作为:
obj++0或obj--0

下面举一例子说明重载增1减1运算符的应用。

#include <iostream.h>

class counter

{

public:

counter() { v=0; }

counter operator ++();

counter operator ++(int );

void print() { cout<<v<<endl; }

private:

unsigned v;

};

 

counter counter::operator ++()

{

v++;

return *this;

}

 

counter counter::operator ++(int)

{

counter t;

t.v = v++;

return t;

}

 

void main()

{

counter c;

for(int i=0; i<8; i++)

c++;

c.print();

for(i=0; i<8; i++)

++c;

c.print();

}
  • 重载函数调用运算符

可以将函数调用运算符()看成是下标运算[]的扩展。函数调用运算符可以带0个至多个参数。下面通过一个实例来熟悉函数调用运算符的重载。

#include <iostream.h>

class F

{

public:

double operator ()(double x, double y) const;

};

 

double F::operator ()(double x, double y) const

{

return (x+5)*y;

}

 

void main()

{

F f;

cout<<f(1.5, 2.2)<<endl;

}
  • 重载输入输出运算符

class Student
{
    public:
        //构造函数,列表初始化
        Student():num(0),name(""){}
    //重载输出运算符    
    friend  ostream  &operator<<(ostream &os,const Student &stu);  //声明为友元
    //重载输入运算符
    friend  istream  &operator>>(istream &is,Student &stu);
    private:
        int      num;
        string   name;
};

ostream  &operator<<(ostream &os,const Student &stu)
{
    os<<stu.num<<" "<<stu.name;
    return os;
}

istream  &operator>>(istream &is,Student &stu)
{
    is>>stu.num>>stu.name;
    //输入判断
    if(!is)
        stu = Student(); //如果失败,默认初始化
    return is;
}

int main()
{
    Student  stu;
    cout<<"input:"<<endl;
    //调用重载的输入运算符函数
    cin>>stu;

    cout<<"output:";
    //调用者在使用的时候决定是否换行
    cout<<stu<<endl;

    return 0;
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,386评论 6 479
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,939评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,851评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,953评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,971评论 5 369
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,784评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,126评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,765评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,148评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,744评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,858评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,479评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,080评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,053评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,278评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,245评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,590评论 2 343

推荐阅读更多精彩内容

  • C++运算符重载-上篇 本章内容:1. 运算符重载的概述2. 重载算术运算符3. 重载按位运算符和二元逻辑运算符4...
    Haley_2013阅读 2,280评论 0 51
  • C++运算符重载-下篇 本章内容:1. 运算符重载的概述2. 重载算术运算符3. 重载按位运算符和二元逻辑运算符4...
    Haley_2013阅读 1,432评论 0 49
  • C++语言的一个很有意思的特性就是除了支持函数重载外还支持运算符重载,原因就是在C++看来运算符也算是一种函数。比...
    欧阳大哥2013阅读 2,685评论 0 8
  • 运算符重载规则 1.被重载的运算符必须是已经存在的C++运算符,不能重载自己创建的运算符; 2.运算符被重载之后,...
    随波逐流007阅读 721评论 0 1
  • 我是个孤儿。 那天我上山采药,突然下暴雨。不知所措的我躲在屋檐下等雨停,恍然看见一个与我年龄相仿的少年。 他身着...
    小弟不才阅读 691评论 2 8