运算符重载相关 笔记

来源:互联网 发布:tv263网络电视 编辑:程序博客网 时间:2024/06/08 00:33

第一遍学习C++的时候,总感觉有哪里不通畅,每天都会遇到各种各样的问题,譬如临时对象,譬如运算符重载。所以特意阅读了一些大牛的博文,记录总结感悟下,希望能打通任督二脉。

1. 赋值操作符(=):

  1)二元操作符;行为:右值赋给左值。因为左值涉及到写入,所以必然不能是const的。但是右值可能只是涉及到读的操作,所以是可以为const的。const 意思是只读的,字面理解下应该能有点收获。

  2)通常返回左操作数的引用,目的是避免创建和撤销临时对象。关于临时对象以后会总结一下。一般如下几种情况会产生临时对象:1)函数的值传递,2)返回值,3)类型转换.

  我是这样理解赋值操作的。可能存在一些问题,大家参考下:

int a;int b=10; a=b;a收到的可能只是b产生的临时对象,也就是说b的拷贝。并不是b自身。广义的讲,int a 中的a 也可以当作对象理解。狭义的理解一般就是指类产生的对象。

  举个例子:

#include <iostream>using namespace  std;int main(){char str1[] = "Hello";char str2[] = "World";str2 = str1;   //“=”: 左操作数必须为左值return 0;}
可以看到,这样操作不错误的。因为数组名是一个常数,常数是不能当左值用的。

2. 运算符重载的目的:

  因为我们从小就接触各种运算操作,整数加减乘除到浮点数,我们可能太习以为常,没有深究过背后的一些东西。比如a+b; 如果a and b 是整数,那么+是一种使用,但是a b 是浮点数,为什么还可以用+进行运算。其实这个地方就涉及到了运算符重载,只是我们习以为常的接受而已。同样的,运算符重载就一个目的:是运算符运作尽可能符合我们的心意。既然整数可以加减,那么类的对象是否可以加减?但是现阶段的+号只是支持内置的基本数据类型,对于自定义数据类型却不支持,为了要支持,那就只能自己重新实现一下+号的功能,也许丰富更好的描述重载。其实如果一开始程序中的+等操作都不支持,那么想要实现1+2等简单的操作,自己也是需要写一个函数进行实现此功能。

3. 类型转换函数:

  必须是成员函数,并且不能有返回类型,类似构造函数,但不是完全一致。举个例子:

#include <iostream>using namespace  std;class Test{Test(){...}operator int(){return int_value;}};
构造函数是没有返回值的(但是可以有形参的)。类型转换函数是不能有形参表,不能有返回值类型,而且必须返回一个目标类型的值,例子中的目标类型就是int 。

4. 一些解释:

  1)什么是运算符重载:运算符与类结合,产生新的含义。强调一点,不要简单的理解成这样:重载只是实现了 对象#对象(#代表操作符,这个地方的对象是只一个类产生的对象)其实这个样子也可以:obj1#obj2 不同的类也可以运算。更进一步理解,obj+int 所以不要狭隘理解。其实如果你能把一切都当成对象理解的话,那么就更能想通了。

  2)为什么要引入:多态,对就是多态。一个函数多种形态,这个也也是多态啊。多态也不要理解的太片面哦。反正当初我是如此理解。

  3)如何实现? 两种方式:类的成员函数或者友元函数。

  4) 重载一些规则:有几个不能重载的:(.);(.*);(?:),(sizeof)目前我知道的。可能还有别的,感兴趣可以自己查一下。

5. 重载为成员函数还是友元函数的:

  1)一般情况:一元运算符是重载为成员,二元重载为友元。

  2) 运算符重载需要修改类的对象的状态,重载为成员比较好。

  3)同基本数据类型运算,一定要重载为友元。其实很好理解,如果你把int也当作类来理解的话,那么你要同int 类交互的话,除非你是类的友元,不然你是无法进行访问。这个是我的片面理解,可能每个人看法不同。

  4)同其他类进行运算,一定要是友元。最常见的是<< 和>>运算符。待会底下会有解释,这个地方先不讲。

  5)=,[],(),->必须重载为成员函数。+=,-=,*=等带等号的建议重载为成员。<< >>必须友元。

6. 形参表和返回值类型的确定:

 1)形参表的确定:如果参数不会被修改,推荐使用const 引用,避免临时对象的产生。

 2)返回值类型的确定:

     1.如果返回值可能出现在=的左边,推荐使用non const 引用。

     2. 返回值只能出现在等号的右边,作为右值处理。那么可以使用const 引用或const值

    3.如果两边都能出现的话,那么只能当左值处理了。推荐使用non const 引用。为什么?因为能左能右,一旦使用了const,那么就变成只读的,那么就没法当作左值使用,所以一定要是non const 的。

测试案例:

1. + -运算符测试。加减运算符一定是二元操作符了,而且返回值是临时对象,所以只能是右值,那么就可以是const的了。

#include <iostream>using namespace  std;class Point{private:int x;public://构造函数,拷贝构造函数,重载+ - 操作符。减号重载为友元。Point(int x_x = 0) :x(x_x){ cout << "Using the Constructor" << endl; }Point(Point& temp);const Point operator+(const Point&);friend const Point operator-(const Point&, const Point&);};Point::Point(Point& temp){x = temp.x;}const Point Point::operator+(const Point& temp){return Point(x+temp.x);}const Point operator-(const Point& temp1, const Point& temp2){return Point(temp1.x - temp2.x);}int main(){Point p1(2);Point p2(3);p1 + p2;p1 + 1;  //这个地方就要进行转换了。会以1为参数调用构造函数,产生了类型转换,有临时对象产生。//1 + p1;//这个地方是无法运行的。因为涉及到函数调用的问题,等等会讲。a - 1;1 - a;system("pause");return 0;}
函数的调用方式:分为显式调用和隐式调用。其中显式调用可以进一步划分为:成员函数的显式调用和友元的显式调用。待会下面举例子。

2. ++ -- 测试案例:

#include <iostream>using namespace  std;class Point{private:int x;public:Point(int x_x = 0) :x(x_x){cout << "Using the Constructor" << endl;}Point operator++();   //++obj 前置自增.const Point operator++(int x);//obj++ 后置自增.friend Point operator--(Point& p);//友元函数定义--obj;friend const Point operator--(Point& p, int x);//后缀可以返回一个const类型的值 友元定义obj--;  //观察前置不需要参数,后置需要一个int .成员函数//友元因为缺少this ,所以补齐参数。};Point Point::operator++(){x++;return (*this);}const Point Point::operator++(int x){Point temp = *this;this->x++;return temp;}Point operator--(Point& p){p.x--;return p;}const Point operator--(Point& p, int x){Point temp = p;p.x--;return temp;}int main(){}
++ -- 涉及到prefix和postfix 也就是前置自增和后置自增的问题。和普通的重载函数一样,区分只能从形参个数和类型来区分。后置的是带个虚参int 的,这个地方的int 啥都不表示,只是告诉编译器我是后置。

函数调用方式:

  隐式: obj++;++obj; 同名字很搭配嘛。不看函数都不知道是对象的自增。所以叫隐式很贴切。

  显式:obj.operator++();obj.operator++(int)  重载为成员函数省略了this

  友元的显式调用: obj.operator++(obx) ;obj.operator++(obx,int)

 讲一下a++ 和++a 的区别:

  1) a++ ;返回值是一个临时对象,只能当右值使用。所以可以使用const 修饰下。

  2)++a; 返回值:*this 可以看到返回的是自身。而且是可左可右的。为了确保完整,只能当作左值处理,所以不能是const的。

3.下标运算符测试[]:

#include <iostream>using namespace  std;class Point{int  x[5];public:Point(){for (int i = 0; i < 5; i++){x[i] = i;}}int& operator[](int y);};int& Point::operator[](int y){static int t = 0;if (y < 5){return x[y];}else{// cout << "下标出界" << endl;return t;}}int main(){Point a;for (int i = 0; i < 10; i++){cout << a[i] << "  ";}return 0;}
调用大同小异,不赘述了。

重载的目的:对象[x] 类似于array[x]比较复合习惯吧。可以对下标越界做出判断。

注意重载的那个函数,返回值是int& 哦。

4. ()测试案例:

#include <iostream>using namespace  std;class Point{private:int x;public:Point(int x_x = 0) :x(x_x){cout << "Using the Constructor" << endl;}const int  operator()(const Point& temp);};const int  Point::operator()(const Point& temp){return x + temp.x;}int main(){Point p1(2);Point p2(3);cout << p1(p2);}
重载的目的:obj()类似于function()用的更习惯吧,其实我没多大感觉,可能接触的比较少。注意下格式:上面 也提到过了。返回值类型是没用的,参数变是空的,返回值是有的。调用格式类似。

5.最后一个测试案例也是最常用的一个。<< 和>>

#include <iostream>using namespace  std;class Point{private:int x, y;public:Point(int x_x = 0, int y_y = 0) :x(x_x), y(y_y){cout << "Using the Constructor" << endl;}~Point(){cout << "Using the Destructor" << endl;}friend ostream& operator<<(ostream& os, const Point& p){cout << "Output x and y values in object p" << endl;os << "(" << p.x << "," << p.y << ")" << endl;return os;}friend istream& operator>>(istream& cin, Point& p){cout << "Enter the object of p's x and y values and use space the seprate them " << endl;cin >> p.x >> p.y;return cin;}};int main(){Point p1;cin >> p1;cout << p1;system("pause");return 0;}
这个地方着重解释下:

 1) 先解释下形参: 首先强调一下这个函数的形参顺序是不可以更改的。

 2)其次解释下为什么是ostream& os。

    因为ostream 也是一个类,并且是不可以进行拷贝的,也就是说你没法对cout进赋值的。所以你要操作我就直接操作我自身就好了。正如引用所解释的那样,别名而已。

   第二个形参: const Point& p; const是只读的意思,因为这个地方是答应,不会修改值,所以声明为const没什么,当然不声明const也是可以接受的。那为什么需要引用?用引用相当于操纵自身,避免了一份拷贝,当然不用也行。

   现在解释返回值类型。首先解释friend 为什么是friend,而不是成员函数?因为你同另一个类交互啊,你要不是友元怎么访问别人,所以友元是必须的。那为什么要返回引用?

   首先要去了解下返回值乎产生临时对象的问题。

   先看一个示例语句:cout<<ob1<<ob2; 同这个区分一下:cout<<ob1; cout<<ob2;

   这样的语句,先是调用operator<<(cout,ob1),但是你后面接着要调用operator<<(cout,ob2),因为你是连起来操作的,所以第一个调用的返回值必须是一个ostream 对象,不然下一次调用无法进行(如果你不是返回引用,那么你返回的是可能是临时对象或者拷贝之类的)。也就是说你第一次调用后要返回一个对象而不是一个临时对象或者拷贝之类的,不然你下一次输出是无法进行的。因为临时对象是const的,而你的第一个形参却是非const的。所以这个地方要用引用。

   关于形参表顺序的问题:

   你可以看看显示调用的方式,应该会有点收获,如果这个地方看不懂可以去看看临时对象的问题,看完再来看这个应该理解会更透彻一点。

  istream类似的就不解释了。

总结一下上面说的:

  形参用ostream& 是因为不能拷贝,返回值用引用是因为连续操作需要,还有不能拷贝造成的(如果返回是ostream ,那么最后接受的是临时对象的拷贝,但是拷贝是非法的操作,所以还是引用)。

还有很多的东西没涉及到,有机会我会继续更新。

参考了:

    csdn和博客园上的博文,因为比较多,而且当时忘记记录地址了,所以就不一一列出地址了,但是这些东西绝对是我自己写的,绝对不是copy,如果您认为我抄袭您的文章,请私信我。有错误请指正,万分感谢。

0 0
原创粉丝点击