补充:关于前置++重载为成员函数时,返回类的引用类型的原因
Plus &operator++();//前置++,若返回值不是引用类型,则结果也对,只是返回值不能作为左值被赋值了
Plus operator++(int);//后置++
如果改成
Plus operator++();//返回值改成普通的Plus类型
void main()
{
Plus a = 202;
cout<<++(++a)<<endl;
}
会发现a输出为203,而不是204。因为当函数是值返回时,返回的只是临时变量而不是a本身,用临时变量参与外部运算,当然只能改变一次。
补充:赋值运算符重载引发的思考
转自http://blog.163.com/zjf_to/blog/static/20142906120121723236624/
如果我们不重载赋值运算符,那么类会自动为我们提供一个赋值运算符,这个默认的赋值运算符函数跟默认的复制构造函数是一样的,就是把一个对象的数据成员的值复制给另一个对象对应的数据成员,在做此测试时,我也遇到了一个问题,直接利用默认赋值函数,引发内存溢出的问题,
代码及原因解释如下:
//
//如果不重载赋值运算符,则下面这条语句
//messge2 = message1;
//将出现很低级的错误,因为这样的话两个成员变量message1和message2指针指向同一块内存地址,
//在程序退出后,第一次调用调用message2的析构函数,则Message指针的地址已经释放,此时再次调用
//message2的析构函数,即是对同一块内存第二次调用delete,那么将出现内存泄漏现象
//
那么我们自己重载了赋值运算符,其参数和返回值为什么要非是引用类型不可呢,我试着将赋值运算符函数的形参改为CMessage对象,此时再次爆发内存错误,原因是:
当我们以CMessage对象作为实参传入函数时,是以按值传递方式进行的,此时在函数内部将创建一个实参的副本,当函数返回时,该实参副本将被自动清除,实参副本中的Message指针将被释放,由于该指针指向的是对象message2的Message变量所指向的地址,因此message2对象的成员变量所指向的地址就这样莫名其妙的消失了,这样的错误编译时无法发现,但是运行时就会发生错误。运行结果为:
Constructor called!
Constructor called!
Destructor Called!
The message is 葺葺葺葺葺葺葺葺葺葺葺葺
The message is awei is the best!
Destructor Called!
Destructor Called!
当把返回类型改为CMessage对象时,此时跟上述情况雷同,此时赋值函数返回的message2原始对象的临时副本,当它销毁后,用来接收返回值的CMessage对象的Message所指向的指针也随之销毁,输出如下:
Constructor called!
Constructor called!
Destructor Called!
The message is awei is the best!
The message is 葺葺葺葺葺葺葺葺葺葺葺葺
Destructor Called!
此外,当使用(message1 = message2) = message3这样的表达式时,也是不合法的,因为左边表达式的返回结果是message1对象的临时副本,而编译器是不允许利用临时对象来调用成员函数的。
因此,这种情况是必须使用引用来传递参数和返回参数的,而且引用返回值的主要特性是可以作为左值,我们可以在赋值语句左边使用返回引用的函数,如表达式:(message1 = message2) = message3
指针和引用的区别
从概念上讲。指针从本质上讲就是存放变量地址 的一个变量,在逻辑上是独立的,它可以被改变,包括其所指向的地址的改变和其指向的地址中所存放的数据的改变。
而引用是一个别名,它在逻辑上不是独立的,它 的存在具有依附性,所以引用必须在一开始就被初始化,而且其引用的对象在其整个生命周期中是不能被改变的(自始至终只能依附于同一个变量)。
在C++ 中,指针和引用经常用于函数的参数传递,然而,指针 传递参数和引用传递参数是有本质上的不同的:
指针传递参数本质上是值传递的方式,它所传递 的是一个地址值。值传递过程中,被调函数的形式参数作为被调函数的局部变量处理,即在栈中开辟了内存空间以存放由主调函数放进来的实参的值,从而成为了实 参的一个副本。值传递的特点是被调函数对形式参数的任何操作都是作为局部变量进行,不会影响主调函数的实参变量的值。(这里是在说实参指针本身的地址值不会变)
而在引用传递过程中,被调函数的形式参数虽然 也作为局部变量在栈中开辟了内存空间,但是这时存放的是由主调函数放进来的实参变量的地址。被调函数对形参的任何操作都被处理成间接寻址,即通过栈中存放 的地址访问主调函数中的实参变量。正因为如此,被调函数对形参做的任何操作都影响了主调函数中的实参变量。
引用传递和指针传递是不同的,虽然它们都是在 被调函数栈空间上的一个局部变量,但是任何对于引用参数的处理都会通过一个间接寻址的方式操作到主调函数中的相关变量。而对于指针传递的参数,如果改变被 调函数中的指针地址,它将影响不到主调函数的相关变量。如果想通过指针参数传递来改变主调函数中的相关变量,那就得使用指向指针的指针,或者指针引用。
为了进一步加深大家对指针和引用的区别,下面我从编译的角度来阐述它们之间的区别:
程序在编译时分别将指针和引用添加到符号表上,符号表上记录的是变量名及变量所对应地址。指针变量在符号表上对应 的地址值为指针变量的地址值,而引用在符号表上对应的地址值为引用对象的地址值。符号表生成后就不会再改,因此指针可以改变其指向的对象(指针变量中的值 可以改),而引用对象则不能修改。
最后,总结一下指针和引用的相同点和不同点:
★相同点:
●都是地址的概念;
指针指向一块内存,它的内容是所指内存 的地址;而引用则是某块内存的别名。
★不同点:
●指针是一个实体,而引用仅是个别名;
●引用只能在定义时被初始化一次,之后不可 变;指针可变;引用“从一而终”,指针可以“见异思迁”;
●引用没有const ,指针有const ,const 的指针不可变;(具体指没有int& const a这种形式,而const int& a是有 的, 前者指引用本身即别名不可以改变,这是当然的,所以不需要这种形式,后者指引用所指的值不可以改变 )
●引用不能为空,指针可以为空;
●“sizeof 引用”得到的是所指向的变量( 对象) 的大小,而“sizeof 指针”得到的是指针本身的大小;
●指针和引用的自增(++) 运算意义不一样;
●引用是类型安全的,而指针不是 ( 引用比指针多了类型检查
一、引用的概念
引用引入了对象的一个同 义词。定义引用的表示方法与定义指针相似,只是用&代替了*。
例如: Point pt1(10,10);
Point &pt2=pt1; 定义了pt2为pt1的引用。通过这样的定义,pt1和pt2表示同一对象。
需要特别强调的是引用并不产生对象的副 本,仅仅是对象的同义词。因此,当下面的语句执行后:
pt1.offset(2,2);
pt1和pt2都具有(12,12)的值。
引 用必须在定义时马上被初始化,因为它必须是某个东西的同义词。你不能先定义一个引用后才
初始化它。例如下面语句是非法的:
Point &pt3;
pt3=pt1;
那么 既然引用只是某个东西的同义词,它有什么用途呢?
下面讨论引用的两个主要用途:作为函数参数以及从函数中返回左值。
二、引用参数
1、传递可变参数
传统的c 中,函数在调用时参数是通过值来传递的,这就是说函数的参数不具备返回值的能力。
所以在传统的c中,如果需要函数的参数具有返回值的能力,往往是 通过指针来实现的。比如,实现
两整数变量值交换的c程序如下:
void swapint(int *a,int *b)
{
int temp;
temp=*a;
a=*b;
*b=temp;
}
使 用引用机制后,以上程序的c++版本为:
void swapint(int &a,int &b)
{
int temp;
temp=a;
a=b;
b=temp;
}
调 用该函数的c++方法为:swapint(x,y); c++自动把x,y的地址作为参数传递给swapint函数。
2、给函数传递大型 对象
当大型对象被传递给函数时,使用引用参数可使参数传递效率得到 提高,因为引用并不产生对象的
副本,也就是参数传递时,对象无须复制 。下面的例子定义了一个有限整数集合的类:
const maxCard=100;
Class Set
{
int elems[maxCard]; // 集和中的元素,maxCard 表示集合中元素个数的最大值。
int card; // 集合中元素的个数。
public:
Set () {card=0;} //构造函数
friend Set operator * (Set ,Set ) ; //重载运算符号*,用于计算集合的交集 用对象作为传值参数
// friend Set operator * (Set & ,Set & ) 重载运算符号*,用于计算集合的交集 用对象的引用作为传值参数
...
}
先考虑集合交集的实现
Set operator *( Set Set1,Set Set2)
{
Set res;
for(int i=0;i<Set1.card;++i)
for(int j=0;j>Set2.card;++j)
if(Set1.elems[i]==Set2.elems[j])
{
res.elems[res.card++]=Set1.elems[i];
break;
}
return res;
}
由于重载运算符不能对指针单独操作,我们必须把运算数声明为 Set 类型而不是 Set * 。
每次使用*做交集 运算时,整个集合都被复制,这样效率很低。我们可以用引用来避免这种情况。
Set operator *( Set &Set1,Set &Set2)
{ Set res;
for(int i=0;i<Set1.card;++i)
for(int j=0;j>Set2.card;++j)
if(Set1.elems[i]==Set2.elems[j])
{
res.elems[res.card++]=Set1.elems[i];
break;
}
return res;
}
三、引用返回值
如 果一个函数返回了引用,那么该函数的调用也可以被赋值。这里有一函数,它拥有两个引用参数并返回一个双精度数的引用:
double &max(double &d1,double &d2)
{
return d1>d2?d1:d2;
}
关于引用型返回值
转自http://blog.csdn.net/piratejk/article/details/6162554
对于函数的返回值,看似简单,但并非如此,比如:
int func(int a);该函数会返回一个int型,如果进行一个调用int result=func(3);会发生什么情况?
首先,func将返回值复制到一个匿名临时变量中,在这里假设该临时变量为anony(其实是没有名字的,这里方便阐述);然后,再将anony的值复制到result,可以看出,这里是进行了两次复制的。而并非一次复制。
对于返回引用的函数:
int & func(int &a);假设该函数传入一个int的引用,然后再func中修改它,再返回其引用,如果调用int reslut=func(b);会发生如下情况:
返回的是b的引用,因此相当于直接将b的值复制给了result。这里就只有一次复制(少了临时变量的复制,当然也创建了一个临时变量,只是该临时变量是b的一个引用)。
需要特别注意的是,按很多人的理解,这里返回的是一个引用,因此result就是b的引用,其实并非如此,这里返回引用只是减少了一次临时变量值的复制。如果真的要让result能够引用b,可以这样做:int &result = func(b);
注:返回普通变量的引用看不出效率的差异,但是返回比较大的类或者结构体的时候效率差异比较明显。
那如果是这样申明函数int func(int a);注意,这里返回的不是引用。然后int &result=func(a);会发生什么情况呢?
如果是这样,编译器将报错:不能用一个临时变量来初始化一个非常量的引用变量。
要消除这种报错,可以这样写const int &result=func(a);这样虽然返回的不是引用,但是由于最后赋给的是一个引用变量,因此在返回过程中也只有一次复制过程。但是这样的result是不能修改其引用的内容的。
还有一种看似更为诡异但却十分合理的情况:
int &func (int &a);同样假设该函数传入一个int的引用,在func中修改它,然后返回其引用。然后这样调用func(b)=3;这样的后果是,传入的b的值变为3。原因是func返回了一个b的引用,然后再将该引用赋为3,因此b的值也变成了3。
如果要禁止这种情况的发送,可以这样声明函数:const int &func(int &a);这样返回的是一个const引用,它不允许使用该引用修改其指向的值。因此如果有func(b)=3这样的调用,将通不过编译。
以下转自http://www.cnblogs.com/fly1988happy/archive/2011/12/14/2286908.html
函数的返回主要分为以下几种情况:
1、主函数main的返回值:
允许主函数main没有返回值就可结束;可将主函数main返回的值视为状态指示器,返回0表示程序运行成功,其他大部分返回值则表示失败。
2、返回非引用类型:
- 函数的返回值用于初始化在调用函数时创建的临时对象(temporary object),如果返回类型不是引用,在调用函数的地方会将函数返回值复制给临时对象。
- 在求解表达式的时候,如果需要一个地方存储其运算结果,编译器会创建一个没命名的对象,这就是临时对象。C++程序员通常用temporary这个术语来代替temporary object。
- 用函数返回值初始化临时对象与用实参初始化形参的方法是一样的。
- 当函数返回非引用类型时,其返回值既可以是局部对象,也可以是求解表达式的结果。
3、返回引用类型:
- 当函数返回引用类型时,没有复制返回值,相反,返回的是对象本身。
- 千万不要返回局部对象的引用!千万不要返回指向局部对象的指针!
当函数执行完毕时,将释放分配给局部对象的存储空间。此时对局部对象的引用就会指向不确定的内存!返回指向局部对象的指针也是一样的,当函数结束时,局部对象被释放,返回的指针就变成了不再存在的对象的悬垂指针。
- 返回引用时,要求在函数的参数中,包含有以引用方式或指针方式存在的,需要被返回的参数
int& abc(int a, int b, int c, int& result)
{
result = a + b + c;
return result;
}
错误的方式
int& abc(int a, int b, int c)
{
return a + b + c;
}
4、返回const类型
由于返回值直接指向了一个生命期尚未结束的变量,因此,对于函数返回值(或者称为函数结果)本身的任何操作,都在实际上,是对那个变量的操作,这就是引入const类型的返回的意义。当使用了const关键字后,即意味着函数的返回值不能立即得到修改!如下代码,将无法编译通过,这就是因为返回值立即进行了++操作(相当于对变量z进行了++操作),而这对于该函数而言,是不允许的。如果去掉const,再行编译,则可以获得通过,并且打印形成z = 7的结果。
#include <iostream>
#include <cstdlib>
const int& abc(int a, int b, int c, int& result)
{
result = a + b + c;
return result;
}
int main()
{
int a = 1; int b = 2; int c=3;
int z;
abc(a, b, c, z)++; //wrong: returning a const reference
cout << "z= " << z << endl;
return 0;
}
下面是一段已改正错误的代码
#include <iostream>
using namespace std;
int j=3;//j是全局变量
int val()
{
int i = 1;
return i;
}
int & ref()
{
// int j=3;j不能是局部变量!
int &i = j;
return i; //不能返回局部对象的引用
}
int main()
{
int vv = val();
int rv = val();//int &rv = val()错误!val()返回的是一个int型的数,而给引用&rv 赋值的必须是一个同类型的变量。
int vr = ref();
int & rr = ref();
cout<<vv<<endl;
cout<<rv<<endl;
cout<<vr<<endl;
cout<<rr<<endl;
return 0;
}