7.2.2 引用形参、const类形参(重要)

简介

(记住了,很重要,形参是const引用和非const引用的区别)

  • 可以使用指针来修改实参的值,也可以使用非const引用来修改实参的值。

  • 使用const 类型的引用的好处:

    • 不需要复制实参时,可以用引用类型,所有实参过大时用引用类形参,提高效率;

    • 又因为是const类型的引用,所以不能通过引用来修改实参的值

    • 非const引用只能绑定到与该引用同类型的对象;

一般情况下,将不需要修改的引用形参定义为const引用,普通的非const引用形参在使用时不太灵活,因为这样的形参不能用字面值或产生右值的表达式实参初始化。

少用非const引用类形参,一般定义引用时最好加上const(记住了!!!!)

============================================================================================

导读:

  1. 当我们调用一个函数时,第一件事就是创建形参的那两个变量,并将这两个变量初始化为调用函数时传递的实参值。
  2. 当我们使用普通的形参(非引用类形参)时,实参对形参的初始化就是直接执行复制操作,将实参的值复制给形参。在本节的形参时会讨论到这个问题,就是直接执行复制有时候会效率很低—(7.1节)

  3. 复制实参并不是在所有的情况下都适合,不适宜复制实参的情况如下,在如下的情况中,有效的解决办法是将形参定义为引用或指针类型:

    1. 当需要在函数中修改实参的值时;
    2. 当需要以大型对象作为实参传递时,对实际应用而言,复制对象所付出的时间和存储空间代价往往过大;
    3. 当没有办法实现对象的复制时(13章)。—-(7.2节)

一、引用形参

1、使用指针来修改实参的值,也可以使用非const引用来修改实参的值。**

void swap(int v1,int v2)
{int temp;
temp=v2;
v2=v1;
v1=temp;
//形参v1和v2并不会长期存在
//这个函数是想改变实参的值,但是没有办法改变,执行swap时,只交换了其实参的局部副本,而传递给swap的实参并没有修改。
}
int main(){
int i=0;
int j=20;
cout<<"Before swap():"<<i<<" "<<j<<endl;
swap(i,j);//调用上面定义的swap函数,i和j的值并没有修改
cout<<"after swap():"<<i<<" "<<j<<endl;
return 0;
}
//如果想要修改i和j 的值,可以将形参设置为引用类型
void swap(int &v1,int &v2)
{int temp;
temp=v2;
v2=v1;
v1=temp;}
//与所有引用一样,引用形参直接关联到其所绑定的对象,而并非这个对象的副本。
定义引用时,必须用与该引用绑定的对象初始化该引用。
//当调用swap(i,j) 函数时,形参v1只是对象i的另一个名字,修改v1的值就是修改i的值。

//将引用当实参与将指针当实参具有一样的效果
//将引用当实参与将指针当实参具有一样的效果
void swap(int *v1,int *v2)
{int temp;
temp=*v2;
*v2=*v1;
*v1=temp;}

//main函数里面的函数调用写成swap(&i,&j)

2、使用引用形参返回额外的信息

没有看懂!!!!!!!

  • 函数只能返回单个值,但有些时候,函数有不止一个的内容需要返回,例如定义一个find_val函数,在一个整型vector对象的元素中搜索某个特定值,如果找到满足要求的元素,则返回指向该元素的迭代器;
  • 否则返回一个迭代器,指向该vector对象的end操作返回的元素。

  • 此外,如果该值出现了不止一次,我们还希望函数可以返回其出现的次数,在这种情况下,返回的迭代器应该指向具有要寻找的值得第一个元素。

如何定义既返回一个迭代器又返回出现次数的函数呢?
我们可以定义一种包含一个迭代器和一个计数器的新类型。或者给find_val传递一个额外的引用实参,用于返回出现次数的统计结果:

//函数返回类型是vector<int>::const_iterator,返回一个迭代器
//函数类型是vector<int>::const_iterator
//函数名是find_val
//有四个形参
vector<int>::const_iterator find_val(
vector<int>::const_iterator beg,//第一个元素
vector<int>::const_iterator end,//
int value,                     //我们要的值
vector<int>::size_type &occurs//出现的次数
)  
{
//函数体开始
vector<int>::const_iterator res_iter=end;
occurs=0;
for(;beg!=end;++beg)
  if(*beg==value) { 
   if(res_iter==end)
   ret_iter=beg;
   ++occurs;
}
return res_iter;//返回的是
}
it=find_val(ivec.begin(),ivec.end(),42,ctr);
//调用后,ctr的值将是42出现的次数,如果42在ivec中出现了,则it将指向其第一次出现的位置;
//否则,it的值为ivec.end(),而ctr则为0、

3、利用const引用避免复制

  • 在向函数传递大型对象时,需要使用引用形参,这是引用形参适用的另一种情况,但是对于大部分类类型或者大型数组,它的效率太低了。(13章学到某些类类型是无法复制的,使用引用形参,函数可以直接访问实参对象,而无须复制它)

  • const 引用

    • const引用是指向const对象的引用;
    • 非const引用只能绑定到与该引用同类型的对象;
    • const引用则可以绑定到不同但相关的类型的对象或绑定到右值
const int ival=1024;
const int &refval=ival;//可以,引用和被引用对象都是const类型
int &ref2=ival;//不可以
//可以读取但是不能修改refval,因此对refval的赋值都是不合法的,不能直接对ival赋值,因此不能通过使用refval来修改ival。const引用只能绑定到与该引用同类型的对象;
const引用则可以绑定到不同但相关的类型的对象或绑定到右值

注意:

  1. 避免复制的意思是不是就是说,实参在给形参初始化的时候将值复制给形参;

  2. 因为如果是一般的形参(非引用),那么在调用函数的时候,形参的初始化时是将实参的值复制给了形参;

  3. 可是在实参很长的情况下,复制操作的效率太低了,这时候我们就可以使用引用类的形参;

  4. 而且有时候(13章)是没有办法复制的

//这个函数需要访问每个string对象的size,但不必修改这些对象所以用const类型形参。
//由于string对象相当长,所有我们希望避免复制操作,使用const引用就可以避免将实参值复制给形参;
//每一个形参都是const string类型的引用,因为形参是引用,所有不复制实参;
//又因为形参时const引用,所有函数不能使用该引用来修改实参
bool isShorter(const string &s1,const string &s2){
return s1.size()<s2.size();
}

使用const引用形参的好处:

  1. 因为是引用类型,所有实参过大时,不需要复制实参,提高效率;

  2. 又因为是const类型的引用,所有不能通过引用来修改实参的值(在一般情况下非const类的引用,因为引用是别名,是能够通过引用来修改实参的值。7.2.1节)

4、更灵活的指向const的引用

前面提到的:
- 非const引用只能绑定到与该引用同类型的对象;

  • const引用则可以绑定到不同但相关的类型的对象或绑定到右值

    • 如果函数具有普通的非const引用形参,则显然不能通过const对象进行调用,因为此时函数可以修改传递进来的对象,这样就违背了实参的const特性;

    • 调用这样的函数时,传递一个右值或具有需要转换的类型的对象同样是不允许的。

    • 应该将不需要修改的引用形参定义为const引用,普通的非const引用形参在使用时不太灵活,这样的形参既不能用字面值或产生右值的表达式实参初始化。

int incr(int &val){

return ++val;
}
int main()
{
short v1=0;
const int v2=42;
int v3=incr(v1);//错误!!!v1不是int类型
v3=incr(v2);//错误!!!v2是const类型
v3=incr(0);//错误!!!0不是一个对象名
v3=incr(v1+v2);//错误!!!只有一个形参
int v4=incr(v3);//可以,类型相同!!!!都不是const

}
//不是很懂
string::size_type find_char(string &s,char c){
 string::size_type i=0;
 while(i!=s.size()&&s[i]!=c)
 ++i;
 return i;
}

//这个函数的问题是将其string类型的实参当做普通(非const)的引用,尽管函数并没有修改这个形参的值,这样的定义带来的问题是不能通过字符串字面值来调用这个函数(因为非const引用形参只能与完全同类型的非const对象关联)。可更正为const string&s

if(find_char("hello world",'o'))//会导致编译问题
//虽然字符串字面值可以转换为string对象,但上述调用仍然会导致编译失败
//即使程序本身没有const对象,而且只使用string对象(而并非字符串字面值或产生string对象的表达式)调用find_char函数,编译阶段仍然会出现问题。
//如下,可能有另外一个函数is_sentence 调用find_char来判断一个string对象是否是句子:
bool is_sentence (const string &s){
return (find_char(s,',')==s.size() -1);
}
//函数is_sentence中的find_char的调用是一个编译错误,传递进is_sentence的形参时指向const string对象的引用,不能将这种类型的参数传递给find_char

5、形参—–指向指针的引用

通过定义变量的引用可以实现实参值的变化,通过定义一个指针的引用(指针别名)可以实现实参指针的变化。

void ptrswap(int *&v1,int *&v2){
int *tmp=v2;
v2=v1;
v1=tmp;
}

//形参 int *&v1定义从右到左,理解是首先v1是一个引用,与指向int型对象的指针的别名。
也就是说v1只是传递进ptrswap函数的任意指针的别名。
int main()
{
   int  i=10;
   int j=20;
   int *pi=&i;
   int *pj=&j;
   cout<<*pi<<" "<<*pj<<endl;
   ptrswap(pi,pj);
   cout<<*pi<<" "<<*pj<<endl;
   return 0;
}

//输出:
//10  20
// 20  10
即指针的值交换了,换句话说现在pi指向j,pj指向i。 

衔接下一节

习题7.12 什么时候用指针形参??

当函数需要处理数组且函数体不依赖于数组的长度时应使用指针形参,其他情况下应使用引用形参。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章