面向对象编程中引用和const的结合增强程序的效率和健壮性
摘要:在使用c++做面向对象编程的时候,合理的使用引用和const关键字可以达到提高程序的效率和健壮性的目的
一、引用的介绍
1.1 什么是引用
引用(Reference)通过&来标记,用来为存储器取别名。例如:
Int X;
Int &ref = X;
分配了一个int单元,它拥有两个名字:X和ref。执行:X = 3;或则 ref = 3;都将3存储到该int 单元。
1.2 引用的用法
由定义可以知道:引用就是给存储器取别名。因此可以通过引用直接访问某个存储单元。通常在函数调用过程中,传递参数有两种方式:传值(Call by value)和传地址(Call by Referenc)。而在函数返也有两种方式:传值返回(Return by Value)和引用返回(Return by Reference)。因此本节用将分别从函数调用和函数返回两个方面介绍引用的用法。
1.2.1 引用调用
在传值调用时,实参传递给形参的只是一个实参的临时拷贝,虽然在函数执行时改变了形参,但是当函数结束后,形参的生命周期就结束了。此时,形参的改变不能反映到实参上去。例如:
Void swap( int a ,int b)
{
Int Temp;
Temp = a;
a = b;
b = temp;
}
执行如下语句:
int x = 5;
int y = 10;
swap(x,y);
cout<<x<<”,”<<y<<endl;
后输出的仍然是:5,10.
而引用调用时,引用参数将实际的实参地址传递给函数,而不是将实参的一个临时拷贝传递给函数。如果将上述代码中的函数定义部分改成:swap(&a,&b)其他部分不做修改,那么运行结果将是:10,5.这是因为在执行swap(x,y)时,形参a直接指向了实参x的存储空间,形参b直接指向了实参y的存储空间。此时,swap函数不是对x,y的临时拷贝进行操作,而是直接对x,y本身进行操作。因此函数结束时就完成了对变量交换的目的。
由此,我们如果需要利用一个函数实现对实参的修改,那么我们可以在定义形参的时候使用引用类型。
1.2.2 引用返回
在c++中,默认情况下,当函数执行:return expression ; 时,expression被求值,然后把这个值拷贝到临时存储空间,以便函数调用者访问。这种返回方式叫做:传值返回。调用如下函数:
Int F( )
{
// the body ………
Return I ;
}时,I的值将拷贝到临时存储空间,然后函数结束,I的生命周期也结束,调用者可以获得I的一个副本,但不能再访问I的值,因为它的生命周期已经结束。如:J = F ( );
则I的值拷贝到临时空间,然后再拷贝到J.
函数返回的另外一种形式:引用返回(Return by Reference)。定义引用返回的形式如下:
Int & F()
{
//the body ….
Return I;
}
在这种情况下,返回值I不再被拷贝到临时存储单元,甚至对return 语句所用的存储单元对调用者来说也是可访问的。因此:j = F( )的执行不再是将I的副本拷贝到j 中,而是直接将I的值拷贝给j 。
使用引用返回的一个好处是:如果一个函数以引用凡是返回,则这个函数调用可以出现在赋值号的左边。如:F( ) = 8;此时如果要访问F()函数中的return语句之后的变量I值,那么需要I的生命周期没有结束才可以,也就是说,I不能定义成一个作用域在F()内的局部变量。如:
Int F( )
{
Int I;
//………..
Return I;
}
包含一个错误,当F返回I时,I已经不存在了(因为I是局部变量)。因此,如果函数调用者想使用F( )= 8;来访问I的值就是不可能的。此时可以将I定义成全局变量,或者定义成一个引用型变量:
Int F(Int & I)
{
//……….
Return I;
}
或者将被返回的变量定义成一个static 类型的变量:
Int F ( )
{
Static int I;
Return I;
}
1.3 引用的特点
通过前面两节可以知道:使用引用传参形式可以直接修改实际参数的内容,解决了形参的局部性限制问题;使用引用返回,可以达到直接访问返回变量的目的,而且可以为一个函数调用赋值!
二、const 介绍
2.1 什么是const
Const的中文意思是:常数,不变的。其英文解释可以是:An attribute of a data object that declares that the value of the object cannot be changed. 也就是说用const修饰的变量、函数等是只读的,不可以被修改。
2.2 const 的用法
在c++中,const大致可以用来实现如下功能:定义const参数,定义const函数,用const限定函数的返回值,定义常量指针,定义指针常量。在这里只介绍前面三种用法,而对常量指针和指针常量不做介绍。
2.2.1 定义const参数
在如下的类声明中:
Class person{
Public:
Void SetName(const string &n){ name = n;}
// ….other public members.
Private:
String name;
};
函数SetName的string类型参数n标记为const,表明SetName不会改变n,只会将n的值赋给数据成员name。同时注意在这里n被定义成引用类型。 使用const限定符和string &n形式组合作为参数传递给函数是一种良好的习惯.可以提效率.string& n将用引作为参数时,系统不用再为参数建立临时对象,提高了效率.再加上限定符 const就可以保证用引变量不会被修改.从而实现了值传递,而且还减少了系统开销。
2.2.2 定义const函数
在如下的类声明中:
Class person{
Public:
Void SetName(const string &n){ name = n;}
String GetName()const { return name;}
// ….other public members.
Private:
String name;
};
成员函数:GetName不需要改变类person的任何数据成员,我们将这种用const标记的函数称为:只读函数。将成员函数标记为只读函数可以防止成员函数对类的数据成员的修改,这样提高了程序的健壮性。同时注意,只读函数不能在其内部调用非只读函数,因为非只读函数有可能修改类的数据成员。如果一个成员函数不需要直接或则间接的修改类的数据成员,那么最好将该函数声明为只读函数。
2.2.3 使用const限定函数的返回值
在如下的类声明中:
Class person{
Public:
Void SetName(const string &n){ name = n;}
Const String& GetName()const { return name;}
// ….other public members.
Private:
String name;
};
成员函数GetName的返回数据成员name的一个const型引用,此处的const表明谁也不能通过这个引用来修改数据成员name的值。同时注意这个类展示了const的三种不同用法。
三、引用和const对程序的效率和健壮性的贡献
3.1通过引用来传递和返回对象提高程序的效率
和其他类型一样,对象作为参数传递时有两种方式:传值和引用。当对象作为一个函数的返回值的时候也有两种方式:传值返回和引用返回。
不管是参数的传值传递还是函数的传值返回,他们都有一个共同的问题:将对象拷贝到一个临时变量(对象)中,然后在传递给函数或则返回给调用者。当一个类的对象很复杂的时候,这个拷贝临时对象的过程将会是数据增大,浪费内存,从而明显影响程序的效率。
因此,除非迫不得已,一般来说应该采用引用方式进行对象的传递和返回,而不采用传值方式。
3.2 利用const关键字增强程序的健壮性
如果一个成员函数的形参定义成引用类型,那么我们就可以在该函数中改变传递给这个函数的实参的值,而有时候出于安全考虑,我们希望成员函数不要修改传递给其的实参的值,这时,我们可以使用const关键字加以限定(定义const参数)。
如果我们希望一个类的成员函数不要对这个类的成员变量进行修改,那么我们也可以使用const关键字加以限定(定义const函数。
同理,如果我们不希望通过引用返回的方式修改类的数据成员,那么可以利用const关键字限定一个成员函数的返回类型。
3.3 引用与const联合提高程的序效率和健壮性
通过前面两节的介绍,我们知道,合理的使用引用可以提高程序的效率,但它却有影响程序健壮性的可能,因为引用的使用可以修改实参,也可以对类的成员函数进行修改。而const的作用正好是强制限定了这些修改功能。因此在利用c++做面向对象编程的时候将引用和const结合起来,可以达到提高效率的同时也增强程序的健壮性的目的。
四、示例测试
#include "stdafx.h"
#include "iostream"
#include "string"
using namespace std;
class person1{
public:
void SetName(string n){name = n;}
string GetName(){return name;}
private:
string name;
};
class person2{
public:
void SetName(string &n){n= "ppp2";name = n;}
string& GetName(){return name;}
private:
string name;
};
class person3{
public:
void SetName(const string& n)
{
//n = "p3"; Error !!
name = n;
}
string GetName()const {return name;}
// string &GetName()const {return name;} Error 函数返回是const就不能指定函数返回为引用
private:
string name;
};
class person4{
public:
void SetName(const string& n)
{
//n = "p4"; Error!!!
name = n;
}
const string& GetName()const {return name;}
private:
string name;
};
int main(int argc, char* argv[])
{
person1 p1;
person2 p2;
person3 p3;
person4 p4;
//test of class person1:
p1.SetName("p1");
cout<<"test of class person1: "<<p1.GetName()<<endl<<endl;
//test of class person2:
string p2name = "p2";
p2.SetName(p2name);
cout<<"value of p2name:"<<p2name<<endl<<endl;
cout<<"test of class person2:"<<p2.GetName()<<endl<<endl;
p2.GetName()="p2";
cout<<"test of class person2:"<<p2.GetName()<<endl<<endl;
//test of class person3:
p3.SetName("p3");
cout<<"test of class person3:"<<p3.GetName()<<endl<<endl;
p3.GetName() = "ppp3";
cout<<"test of class person3:"<<p3.GetName()<<endl<<endl;
//test of class person4:
p4.SetName("p4");
cout<<"test of class person4:"<<p4.GetName()<<endl<<endl;
//p4.GetName() = "pppp4"; 不能对const的引用返回赋值!!
return 0;
}
五、示例分析
在上面的例子中我们可以看到第一个类person1是最无效率的,它的参数,函数返回类型都是传值的。但是它比较安全。第二个类person2的参数类型和函数返回都定义成了引用类型,效率提高了,但是它是最不安全的,可以在函数体里面修改参数的值,也可以通过函数返回修改类成员的值。第三个类person3将SetName函数的参数类型修改为const限制的引用,提高了效率和安全性,同时将GetName函数函数指定为了const类型函数,保证了安全性,但此时不能将函数指定为引用返回,这影响了函数返回的效率。第四个类中修正了第三个类的缺点,将GetName函数的返回类型指定为const限定的引用类型,这样达到了效率与安全性的兼顾。