详解C++指针和引用

C++是在C语言的基础上发展来的。C++除了有C语言的指针外,还增加一个新的概念——引用,初学者容易把引用和指针混淆一起,面试或者笔试经常被考到。

要弄清楚这两个概念,先从变量说起。

一:变量的形式

什么是变量呢?变量(variable)的定义在计算机科学中到底是如何定义的?然后variable到底是在内存中如何存储值的呢?那么跟着上面的问题,我们来一一的解答。

首先最重要的,变量的定义,当你申明一个变量的时候,计算机会将指定的一块内存空间和变量名进行绑定;这个定义很简单,但其实很抽象,例如:int x = 5; 这是一句最简单的变量赋值语句了, 我们常说“x等于5”,其实这种说法是错误的,x仅仅是变量的一个名字而已,它本身不等于任何值的。这条语句的正确翻译应该是:“将5赋值于名字叫做x的内存空间”,其本质是将值5赋值到一块内存空间,而这个内存空间名叫做x。切记:x只是简单的一个别名而已,x不等于任何值。其图示如下:

 变量在内存中的操作其实是需要经过2个步骤的:

1)找出与变量名相对应的内存地址。

2)根据找到的地址,取出该地址对应的内存空间里面的值进行操作。

二:指针

首先介绍到底什么是指针?指针变量和任何变量一样,也有变量名,和这个变量名对应的内存空间,只是指针的特殊之处在于:指针变量相对应的内存空间存储的值恰好是某个内存地址。这也是指针变量区别去其他变量的特征之一。例如某个指针的定义如下:


intx=5;

int*ptr=&x;

ptr即是一个指正变量名。通过指针获取这个指针指向的内存中的值称为dereference,间接引用。

其相对于内存空间的表示如下:


使用指针的优点和必要性:

    指针能够有效的表示数据结构;

    能动态分配内存,实现内存的自由管理;

    能较方便的使用字符串;

    便捷高效地使用数组

    指针直接与数据的储存地址有关,比如:值传递不如地址传递高效,因为值传递先从实参的地址中取出值,再赋值给形参代入函数计算;而指针则把形参的地址直接指向实参地址,使用时直接取出数据,效率提高,特别在频繁赋值等情况下(注意:形参的改变会影响实参的值!)

三:引用

引用是C++引入的新语言特性,是C++常用的一个重要内容之一。

引用(reference)在C++中也是经常被用到,尤其是在作为函数参数的时候,需要在函数内部修改更新函数外部的值的时候,可以说是引用场景非常丰富。正确、灵活地使用引用,可以使程序简洁、高效。

我在工作中发现,许多人使用它仅仅是想当然,只是知道怎么应用而已,而不去具体分析这个reference。

在某些微妙的场合,很容易出错,究其原由,大多因为没有搞清本源。

下面我就来简单的分析一下这个reference。首先我们必须明确的一点就是:reference是一种特殊的pointer。从这可以看出reference在内存中的存储结构应该跟上面的指针是一样的,也是存储的一块内存的地址。例如reference的定义如下:

intx=5;

int&y=x;

引用就是某一变量(目标)的一个别名,对引用的操作与对变量直接操作完全一样。

  引用的声明方法:类型标识符 &引用名=目标变量名;

上面的代码,定义了引用y,它是变量x的引用,别名,这样子,目标变量有两个名称,即该目标原名称和引用名,且不能再把该引用名作为其他变量名的别名。

四、引用和指针有什么区别?

(1)指针:指针是一个变量,只不过这个变量存储的是一个地址,指向内存的一个存储单元;而引用跟原来的变量实质上是同一个东西,只不过是原变量的一个别名而已。如:

inta=1;int*p=&a;

inta=1;int&b=a;

    上面定义了一个整形变量和一个指针变量p,该指针变量指向a的存储单元,即p的值是a存储单元的地址。

而下面2句定义了一个整形变量a和这个整形a的引用b,事实上a和b是同一个东西,在内存占有同一个存储单元。

(2)  引用不可以为空,当被创建的时候,必须初始化,初始化后就不会再改变了;而指针可以是空值,可以在任何时候被初始化,指针的值在初始化后可以改变,即指向其它的存储单元。

(3)可以有const指针,但是没有const引用;

(4)指针可以有多级,但是引用只能是一级(int **p;合法 而 int &&a是不合法的)

(5)”sizeof引用”得到的是所指向的变量(对象)的大小,而”sizeof指针”得到的是指针本身的大小;

(6)指针和引用的自增(++)运算意义不一样;

(7)如果返回动态内存分配的对象或者内存,必须使用指针,引用可能引起内存泄漏;

(8)从内存分配上看,程序为指针变量分配内存区域,而不为引用分配内存区域,因为引用声明时必须初始化,从而指向一个已经存在的对象。引用不能指向空值。

        注:标准没有规定引用要不要占用内存,也没有规定引用具体要怎么实现,具体随编译器 http://bbs.csdn.net/topics/320095541

(9)从编译上看,程序在编译时分别将指针和引用添加到符号表上,符号表上记录的是变量名及变量所对应地址。指针变量在符号表上对应的地址值为指针变量的地址值,而引用在符号表上对应的地址值为引用对象的地址值。符号表生成后就不会再改,因此指针可以改变指向的对象(指针变量中的值可以改),而引用对象不能改。这是使用指针不安全而使用引用安全的主要原因。从某种意义上来说引用可以被认为是不能改变的指针。

(10)不存在指向空值的引用这个事实,意味着使用引用的代码效率比使用指针的要高。因为在使用引用之前不需要测试它的合法性。相反,指针则应该总是被测试,防止其为空。

下面用通俗易懂的话来概述一下:

指针-对于一个类型T,T*就是指向T的指针类型,也即一个T*类型的变量能够保存一个T对象的地址,而类型T是可以加一些限定词的,如const、volatile等等。见下图,所示指针的含义:


引用-引用是一个对象的别名,主要用于函数参数和返回值类型,符号X&表示X类型的引用。见下图,所示引用的含义:


总之,可以归结为"指针指向一块内存,它的内容是所指内存的地址;而引用则是某块内存的别名,引用不改变指向。"

五、指针传递和引用传递

在C++中,指针和引用经常用于函数的参数传递,然而,指针传递参数和引用传递参数是有本质上的不同的:

        指针传递参数本质上是值传递的方式,它所传递的是一个地址值。值传递过程中,被调函数的形式参数作为被调函数的局部变量处理,即在栈中开辟了内存空间以存放由主调函数放进来的实参的值,从而成为了实参的一个副本。值传递的特点是被调函数对形式参数的任何操作都是作为局部变量进行,不会影响主调函数的实参变量的值。(这里是在说实参指针本身的地址值不会变)

        而在引用传递过程中,被调函数的形式参数虽然也作为局部变量在栈中开辟了内存空间,但是这时存放的是由主调函数放进来的实参变量的地址。被调函数对形参的任何操作都被处理成间接寻址,即通过栈中存放的地址访问主调函数中的实参变量。正因为如此,被调函数对形参做的任何操作都影响了主调函数中的实参变量。

        引用传递和指针传递是不同的,虽然它们都是在被调函数栈空间上的一个局部变量,但是任何对于引用参数的处理都会通过一个间接寻址的方式操作到主调函数中的相关变量。而对于指针传递的参数,如果改变被调函数中的指针地址,它将影响不到主调函数的相关变量。如果想通过指针参数传递来改变主调函数中的相关变量,那就得使用指向指针的指针,或者指针引用。


六、返回引用和返回指针

C++返回引用类型

    A& a(){ return *this;} 就生成了一个固定地址的指针,并把指针带给你。

但A a() { return *this;}会生成一个临时对象变量,并把这个临时变量给你,这样就多了一步操作。

当返回一个变量时,会产生拷贝。当返回一个引用时,不会发生拷贝,你可以将引用看作是一个变量的别名,就是其他的名字,引用和被引用的变量其实是一个东西,只是有了两个名字而已。

问题的关键是,当你想要返回一个引用而不是一个拷贝时,你要确保这个引用的有效性,比如:

        int & fun() { int a; a=10; return a; }

这样是不行的,因为a会在fun退出时被销毁,这时返回的a的引用是无效的。

这种情况下,如果fun的返回类型不是int & 而是int就没有问题了。

返回指针的话,谁调用该函数,谁负责接触返回的指针。

全局变量,局部静态变量,局部动态分配变量 都可以作为函数返回值。 

局部自动变量不行

函数内部等局部变量,存储在栈中的变量是不能作为返回值的,虽然可以读取正确的值,但是这是一块未分配的内存,当别的进程用到时就会出错,这个指针相当于野指针。返回值可以是局部动态分配的内存空间,这一部分分配在堆上,在主动释放之前别的进程是无法使用的内存区域。

不管是指针还是引用都是如此。


七、特别之处const

为什么要提到const关键字呢?因为const对指针和引用的限定是有差别的:

常量指针VS常量引用

常量指针:指向常量的指针,在指针定义语句的类型前加const,表示指向的对象是常量。

定义指向常量的指针只限制指针的间接访问操作,而不能规定指针指向的值本身的操作规定性。


 常量指针定义"const int* pointer=&a"告诉编译器,*pointer是常量,不能将*pointer作为左值进行操作。

常量引用:指向常量的引用,在引用定义语句的类型前加const,表示指向的对象是常量。也跟指针一样不能对引用指向的变量进行重新赋值操作。



指针常量VS引用常量

在指针定义语句的指针名前加const,表示指针本身是常量。在定义指针常量时必须初始化!而这是引用与生俱来的属性,无需使用const。

指针常量定义"int* const pointer=&b"告诉编译器,pointer(地址)是常量,不能作为左值进行操作,但是允许修改间接访问值,即*pointer(地址所指向内存的值)可以修改。


常量指针常量VS常量引用常量

常量指针常量:指向常量的指针常量,可以定义一个指向常量的指针常量,它必须在定义时初始化。


定义"const int* const pointer=&c"

告诉编译器,pointer和*pointer都是常量,他们都不能作为左值进行操作。


而不存在所谓的"常量引用常量",因为引用变量就是引用常量。C++不区分变量的const引用和const变量的引用。程序决不能给引用本身重新赋值,使他指向另一个变量,因此引用总是const的。如果对引用应用关键字const,起作用就是使其目标称为const变量。即

没有:const double const& a=1;

只有const double& a=1;

double b=1;

constdouble& a=b;

b=2;//正确

a=3;//出错error: assignment of read-only reference `a'

总结:有一个规则可以很好的区分const是修饰指针,还是修饰指针指向的数据——画一条垂直穿过指针声明的星号(*),如果const出现在线的左边指针指向的数据为常量;如果const出现在右边指针本身为常量。而引用本身就是常量,即不可以改变指向。

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

推荐阅读更多精彩内容

  • 1.语言中变量的实质 要理解C指针,我认为一定要理解C中“变量”的存储实质, 所以我就从“变量”这个东西开始讲起吧...
    金巴多阅读 1,742评论 0 9
  • 前言 把《C++ Primer》[https://book.douban.com/subject/25708312...
    尤汐Yogy阅读 9,513评论 1 51
  • 指针是C语言中广泛使用的一种数据类型。 运用指针编程是C语言最主要的风格之一。利用指针变量可以表示各种数据结构; ...
    朱森阅读 3,430评论 3 44
  • 基本内置类型 算术类型字符整型布尔值浮点数 空类型(void) 算术类型 带符号类型和无符号类型int、short...
    2625K阅读 3,142评论 0 1
  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,092评论 1 32