C++引用总结

原文地址:https://www.cnblogs.com/jycboy/p/5184638.html

一、变量的引用:


\color{red}{引用:} \color{blue}{就是某一变量(目标)的一个别名,对引用的操作与对变量直接操作完全一样。}

\color{red}{引用的声明方法:}
\color{blue}{类型标识符\ \&引用名=目标变量名;}

\color{green}{如下:定义引用ra,它是变量a的引用,即别名。}

\color{green}{char\ ch;}

\color{green}{char\ \&rp=ch;}

  1. 引用仅是变量的别名,而不是实实在在地定义了一个变量,因此引用本身并不占用内存,而是和目标变量共同指向目标变量的内存地址.

  2. 表达式中的取地址符&不再是取变量的地址,而是用来表示该变量是引用类型的变量。

  3. 定义一个引用时,必须对其初始化。

\color{red}{示例代码:}

#include <stdio.h>
#include <iostream>
using namespace std;
 
//c++中的引用实例
void fun(int &x){
     printf("形式参数的值为:%d\n",x);
     printf("形式参数的地址为:%d\n",&x);
     x+=10;
}
void fun2(int *num){
  printf("形式参数的值为:%d\n",*num);
  printf("形式参数的地址值为:%d\n",num); //2686724
  printf("形式参数的地址为:%d\n",&num);  //2686704 这个和
  *num =(*num)+10;
 
}
 
int main(void){
   int num = 4,a=3;
 
    printf("实参num的值为:%d\n",num);
    printf("实参num的地址为:%d\n",&num);//都是一样的
    fun(num);
   cout<<"num的值为:"<<num<<endl; //14 num的值改啦
 
   fun2(&a);
   printf("实参a的值为:%d\n",a);
   printf("实参a的地址为:%d\n",&a);//
   return 0;
}



1.和是一样的,

  1. 而后边用指针作为函数参数,把地址传过去,\color{red}{指针变量的地址是2686608},而\color{blue}{它的值是2686696}(在指针的地址上存的值),2686696这个地址指向的值是3,也就是num的值。

  2. \color{red}{指针变量是有自己的值的(2686608)},和\color{red}{num的地址(2686696)}不一样,而引用的地址是和变量的值一样的。

二、引用的应用


\color{red}{1、引用作为参数}

引用的一个重要作用就是作为函数的参数。以前的C语言中函数参数传递是值传递,如果有大块数据作为参数传递的时候,采用的方案往往是指针,因为这样可以避免将整块数据全部压栈,可以提高程序的效率。但是现在(C++中)又增加了一种同样有效率的选择(在某些特殊情况下又是必须的选择),就是引用。

#include<iostream.h>
////此处函数的形参p1, p2都是引用 
void swap(int &p1,int &p2){
    int p=p1;
    p1=p2;
    p2=p;
}

  为在程序中调用该函数,则相应的主调函数的调用点处,直接以变量作为实参进行调用即可,而不需要实参
变量有任何的特殊要求。如:对应上面定义的swap函数,相应的主调函数可写为:
void main(){
    int a,b;
    cin>>a>>b;//输入a,b两个变量的值
    swap(a,b);//直接以a和b作为实参调用swap函数
    cout<<"a="<<a<<",b="<<b<<endl;
}

上述程序运行时,如果输入数据10 20并回车后,则输出结果为a=20,b=10。

\color{red}{由上例可以看出:}

\color{blue}{(1)使用引用传递函数的参数,在内存中并没有产生实参的副本,它是直接对实参操作;}而使用一般变量传递函数的参数,当发生函数调用时,需要给形参分配存储单元,形参变量是实参变量的副本;如果传递的是对象,还将调用拷贝构造函数。因此,当参数传递的数据较大时,用引用比用一般变量传递参数的效率和所占空间都好。

(2)使用指针作为函数的参数虽然也能达到与使用引用的效果,但是,在被调函数中同样要给形参分配存储单元,且需要重复使用"*指针变量名"的形式进行运算,这很容易产生错误且程序的阅读性较差;另一方面,在主调函数的调用点处,必须用变量的地址作为实参。而引用更容易使用,更清晰。

\color{blue}{如果既要利用引用提高程序的效率,又要保护传递给函数的数据不在函数中被改变,就应使用常引用。}

就如同上边的例子一样。

\color{red}{2、常引用}

常引用声明方式:const 类型标识符 &引用名 = 目标变量名;

用这种方式声明的引用,不能通过引用对目标变量的值进行修改,从而使引用的目标成为const,达到了引用的安全性。

#include <stdio.h>
#include <iostream>
using namespace std;
void test_const();
 
int main(void){
 
   test_const();
   return 0;
}
 
void test_const(){
   int a=1;
     int &b=a;
     b=2;
    cout<<"a="<<a<<endl;//2
    int c=1;
    const int &d=c;
  //   d=2;//编译错误 error: assignment of read_only reference 'd'
    c=2;//正确
    cout<<"c="<<c<<endl;//
}

这不光是让代码更健壮,也有其它方面的需求。

\color{red}{假设有如下函数声明:}

    string foo();

    void bar(string &s);

那么下面的表达式将是非法的:

    bar(foo());

    bar("hello world");

原因在于foo( )和"hello world"串都会产生一个临时对象,\color{blue}{而在C++中,临时对象都是const类型的。}因此上面的表达式就是试图将一个const类型的对象转换为非const类型,这是非法的。

\color{blue}{引用型参数应该在能被定义为const的情况下,尽量定义为const 。}
\color{red}{3、引用作为返回值}

要以引用返回函数值,则函数定义时要按以下格式:

    类型标识符  &函数名 (形参列表及类型说明)

    {  函数体  }

说明:
(1)以引用返回函数值,定义函数时需要在函数名前加&

\color{red}{(2)用引用返回一个函数值的最大好处是,在内存中不产生被返回值的副本。}

以下程序中定义了一个普通的函数fn1(它用返回值的方法返回函数值),另外一个函数fn2,它以引用的方法返回函数值。

#include <iostream>
using namespace std;
 float temp;//定义全局变量temp
 float fn1(float r);//声明函数fn1
 float &fn2(float r);//声明函数fn2 r
 float fn1(float r){//定义函数fn1,它以返回值的方法返回函数值
    temp=(float)(r*r*3.14);
    return temp;
  }
 
  float &fn2(float r){//定义函数fn2,它以引用方式返回函数值
     temp=(float)(r*r*3.14);
     return temp;
 }
 
 int main(){
     float e=10.0;
     float a=fn1(10.0);//第1种情况,系统生成要返回值的副本(即临时变量)

/****************************************
error: invalid initialization of non-const reference of type 'float&' from an rvalue of type 'float'
****************************************/
// float &b=fn1(10.0); //第2种情况,可能会出错(不同 C++系统有不同规定)

     //不能从被调函数中返回一个临时变量或局部变量的引用
     float c=fn2(10.0);//第3种情况,系统不生成返回值的副本
     //可以从被调函数中返回一个全局变量的引用
     float &d=fn2(10.0); //第4种情况,系统不生成返回值的副本
     e=d;
 
     cout<<"a="<<a<<",c="<<c<<",d="<<d<<",e="<<e<<endl;
     //a=314,c=314,d=314
     return 0;
   }

引用作为返回值,必须遵守以下规则:

(1)\color{blue}{不能返回局部变量的引用。}这条可以参照Effective C++[1]的Item 31。\color{blue}{主要原因是局部变量会在函数返回后被销毁},因此被返回的引用就成为了"无所指"的引用,程序会进入未知状态。如上面中的第2种情况出现编译错误。

(2)\color{blue}{不能返回函数内部new分配的内存的引用。}这条可以参照Effective C++[1]的Item 31。虽然不存在局部变量的被动销毁问题,可对于这种情况(返回函数内部new分配内存的引用),又面临其它尴尬局面。例如,被函数返回的引用只是作为一个临时变量出现,而没有被赋予一个实际的变量,那么这个引用所指向的空间(由new分配)就无法释放,造成memory leak。

(3)\color{blue}{可以返回类成员的引用,但最好是const。}这条原则可以参照Effective C++[1]的Item 30。主要原因是当对象的属性是与某种业务规则(business rule)相关联的时候,其赋值常常与某些其它属性或者对象的状态有关,因此有必要将赋值操作封装在一个业务规则当中。如果其它对象可以获得该属性的非常量引用(或指针),那么对该属性的单纯赋值就会破坏业务规则的完整性。

(4)引用与一些操作符的重载:流操作符<<和>>,这两个操作符常常希望被连续使用,例如:cout << "hello" << endl; 因此这两个操作符的返回值应该是一个仍然支持这两个操作符的流引用。可选的其它方案包括:返回一个流对象和返回一个流对象指针。但是对于返回一个流对象,程序必须重新(拷贝)构造一个新的流对象,也就是说,连续的两个<<操作符实际上是针对不同对象的!这无法让人接受。对于返回一个流指针则不能连续使用<<操作符。因此,返回一个流对象引用是惟一选择。这个唯一选择很关键,它说明了引用的重要性以及无可替代性,也许这就是C++语言中引入引用这个概念的原因吧。 赋值操作符=。这个操作符象流操作符一样,是可以连续使用的,例如:x = j = 10;或者(x=10)=100;赋值操作符的返回值必须是一个左值,以便可以被继续赋值。因此引用成了这个操作符的惟一返回值选择。
\color{red}{测试用返回引用的函数值作为赋值表达式的左值}

#include<iostream.h>
int &put(int n);
int vals[10];
int error=-1;
void main(){
    put(0)=10;//以put(0)函数值作为左值,等价于vals[0]=10;
    put(9)=20;//以put(9)函数值作为左值,等价于vals[9]=20; 
    cout<<vals[0]<<endl;//10
    cout<<vals[9]<<endl;//20
}
int &put(int n){
    if(n>=0 && n<=9)
        return vals[n];
    else{
        cout<<"subscript error";
        return error;
    }
}

(5)在另外的一些操作符中,却千万不能返回引用:+-*/ 四则运算符。它们不能返回引用,Effective C++[1]的Item23详细的讨论了这个问题。主要原因是这四个操作符没有side effect,因此,它们必须构造一个对象作为返回值,可选的方案包括:返回一个对象、返回一个局部变量的引用,返回一个new分配的对象的引用、返回一个静态对象引用。根据前面提到的引用作为返回值的三个规则,第2、3两个方案都被否决了。静态对象的引用又因为((a+b) == (c+d))会永远为true而导致错误。所以可选的只剩下返回一个对象了。

\color{red}{4、引用和多态}

\color{blue}{引用是除指针外另一个可以产生多态效果的手段。} \color{blue}{这意味着,一个基类的引用可以指向它的派生类实例。}

【例7】:

    class  A;

    class  B:public  A{ ... ... }

    B  b;

    A  &Ref = b;//用派生类对象初始化基类对象的引用

Ref 只能用来访问派生类对象中从基类继承下来的成员,是基类引用指向派生类。如果A类中定义有虚函数,并且在B类中重写了这个虚函数,就可以通过Ref产生多态效果。

\color{blue}{引用总结}

(1)在引用的使用中,单纯给某个变量取个别名是毫无意义的,\color{blue}{引用的目的主要用于在函数参数传递中,解决大块数据或对象的传递效率和空间不如意的问题。}

(2)\color{blue}{用引用传递函数的参数,能保证参数传递中不产生副本}\color{blue}{提高传递的效率,且通过const的使用,保证了引用传递的安全性。}

(3)\color{blue}{引用与指针的区别是,指针通过某个指针变量指向一个对象后}\color{blue}{对它所指向的变量间接操作。程序中使用指针,程序的可读性差}\color{blue}{而引用本身就是目标变量的别名,对引用的操作就是对目标变量的操作。}

(4)\color{blue}{使用引用的时机。流操作符<<和>>、赋值操作符=的返回值}\color{blue}{拷贝构造函数的参数、赋值操作符=的参数、其它情况都推荐使用引用。}

参考:https://www.cnblogs.com/jycboy/p/5184638.html

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

推荐阅读更多精彩内容