解析C++引用

引言

我选择写C++中的引用是因为我感觉大多数人误解了引用。而我之所以有这个感受是因为我主持过很多C++的面试,并且我很少从面试者中得到关于C++引用的正确答案。

那么c++中引用到底意味这什么呢?通常一个引用让人想到是一个引用的变量的别名,而我讨厌将c++中引用定义为变量的别名。这篇文章中,我将尽量解释清楚,c++中根本就没有什么叫做别名的东东。

背景

在c/c++中,访问一个变量只能通过两种方式被访问,传递,或者查询。这两种方式是:

1.通过值访问/传递变量

2.通过地址访问/传递变量–这种方法就是指针

除此之外没有第三种访问和传递变量值的方法。引用变量也就是个指针变量,它也拥有内存空间。最关键的是引用是一种会被编译器自动解引用的指针。很难相信么?

下面是一段使用引用的简单c++代码

#include <iostream.h>
int main()
{
    int i = 10;   // A simple integer variable
    int &j = i;   // A Reference to the variable i
    j++;   // Incrementing j will increment both i and j.
    // check by printing values of i and j
    cout<<  i  <<  j  <<endl; // should print 11 11
    // Now try to print the address of both variables i and j
    cout<<  &i  <<  &j  <<endl;
    // surprisingly both print the same address and make us feel that they are
    // alias to the same memory location.
    // In example below we will see what is the reality
    return 0;
}   

引用其实就是c++中的常量指针。表达式int &i = j;将会被编译器转化成int *const i = &j;而引用之所以要初始化是因为const类型变量必须初始化,这个指针也必须有所指。下面我们再次聚焦到上面这段代码,并使用编译器的那套语法将引用替换掉。

#include <iostream.h>
int main()
{
    int i = 10;            // A simple integer variable
    int *const j = &i;     // A Reference to the variable i
    (*j)++;                // Incrementing j. Since reference variables are
                          // automatically dereferenced by compiler
    // check by printing values of i and j
    cout<<  i  <<  *j  <<endl; // should print 11 11
    // A * is appended before j because it used to be reference variable
    // and it should get automatically dereferenced.
    return 0;
}  

读者一定很奇怪为什么我上面这段代码会跳过打印地址这步。这里需要一些解释。因为引用变量时会被编译器自动解引用的,那么一个诸如cout << &j << endl;的语句,编译器就会将其转化成语句cout << &*j << endl;现在&*会相互抵消,这句话变的毫无意义,而cout打印的j值就是i的地址,因为其定义语句为int *const j = &i;

所以语句cout << &i << &j << endl;变成了cout << &i << &*j << endl;这两种情况都是打印输出i的地址。这就是当我们打印普通变量和引用变量的时候会输出相同地址的原因。

下面给出一段复杂一些的代码,来看看引用在级联(cascading)中是如何运作的。

#include <iostream.h>
int main()
{
    int i = 10; // A Simple Integer variable
    int &j = i; // A Reference to the variable
    // Now we can also create a reference to reference variable.
    int &k = j; // A reference to a reference variable
    // Similarly we can also create another reference to the reference variable k
    int &l = k; // A reference to a reference to a reference variable.
    // Now if we increment any one of them the effect will be visible on all the
    // variables.
    // First print original values
    // The print should be 10,10,10,10
    cout<<  i  <<  ","  <<  j  <<  ","  <<  k  <<  ","  <<  l  <<endl;
    // increment variable j
    j++;
    // The print should be 11,11,11,11
    cout<<  i  <<  ","  <<  j  <<  ","  <<  k  <<  ","  <<  l  <<endl;
    // increment variable k
    k++;
    // The print should be 12,12,12,12
    cout<<  i  <<  ","  <<  j  <<  ","  <<  k  <<  ","  <<  l  <<endl;
    // increment variable l
    l++;
    // The print should be 13,13,13,13
    cout<<  i  <<  ","  <<  j  <<  ","  <<  k  <<  ","  <<  l  <<endl;
    return 0;
}  

下面这段代码是将上面代码中的引用替换之后代码,也就是说明我们不依赖编译器的自动替换功能,手动进行替换也能达到相同的目标。

#include <iostream.h>
int main()
{
    int i = 10;         // A Simple Integer variable
    int *const j = &i;     // A Reference to the variable
    // The variable j will hold the address of i
    // Now we can also create a reference to reference variable.
    int *const k = &*j;     // A reference to a reference variable
    // The variable k will also hold the address of i because j
    // is a reference variable and
    // it gets auto dereferenced. After & and * cancels each other
    // k will hold the value of
    // j which it nothing but address of i
    // Similarly we can also create another reference to the reference variable k
    int *const l = &*k;     // A reference to a reference to a reference variable.
    // The variable l will also hold address of i because k holds address of i after
    // & and * cancels each other.
    // so we have seen that all the reference variable will actually holds the same
    // variable address.
    // Now if we increment any one of them the effect will be visible on all the
    // variables.
    // First print original values. The reference variables will have * prefixed because
    // these variables gets automatically dereferenced.
    // The print should be 10,10,10,10
    cout<<  i  <<  ","  <<  *j  <<  ","  <<  *k  <<  ","  <<  *l  <<endl;
    // increment variable j
    (*j)++;
    // The print should be 11,11,11,11
    cout<<  i  <<  ","  <<  *j  <<  ","  <<  *k  <<  ","  <<  *l  <<endl;
    // increment variable k
    (*k)++;
    // The print should be 12,12,12,12
    cout<<  i  <<  ","  <<  *j  <<  ","  <<  *k  <<  ","  <<  *l  <<endl;
    // increment variable l
    (*l)++;
    // The print should be 13,13,13,13
    cout  <<  i  <<  ","  <<  *j  <<  ","  <<  *k  <<  ","  <<  *l  <<endl;
    return 0;
}  

我们通过下面代码可以证明c++的引用不是神马别名,它也会占用内存空间的。

#include <iostream.h>
class Test
{
    int &i;   // int *const i;
    int &j;   // int *const j;
    int &k;   // int *const k;
};
int main()
{
    // This will print 12 i.e. size of 3 pointers
    cout<<  "size of class Test = "  <<   sizeof(class Test)  <<endl;
    return 0;
}  

结论

我希望这篇文章能把c++引用的所有东东都解释清楚,然而我要指出的是c++标准并没有解释编译器如何实现引用的行为。所以实现取决于编译器,而大多数情况下就是将引用实现为一个const指针。

引用支持c++虚函数机制的代码

#include <iostream.h>
class A
{
public:
         virtual void print() { cout<<"A.."<<endl; }
};
class B : public A
{
public:
         virtual void print() { cout<<"B.."<<endl; }
};  

class C : public B
{
public:
         virtual void print() { cout<<"C.."<<endl; }
};
int main()
{
         C c1;
         A &a1 = c1;
         a1.print(); // prints C
         A a2 = c1;
         a2.print(); // prints A
         return 0;
}  

上述代码使用引用支持虚函数机制。如果引用仅仅是一个别名,那如何实现虚函数机制,而虚函数机制所需要的动态信息只能通过指针才能实现,所以更加说明引用其实就是一个const指针。

以上就是解析C++引用的详细内容,更多关于C++引用的资料请关注我们其它相关文章!

(0)

相关推荐

  • C++11右值引用和转发型引用教程详解

    右值引用 为了解决移动语义及完美转发问题,C++11标准引入了右值引用(rvalue reference)这一重要的新概念.右值引用采用T&&这一语法形式,比传统的引用T&(如今被称作左值引用 lvalue reference)多一个&. 如果把经由T&&这一语法形式所产生的引用类型都叫做右值引用,那么这种广义的右值引用又可分为以下三种类型: 无名右值引用 具名右值引用 转发型引用 无名右值引用和具名右值引用的引入主要是为了解决移动语义问题. 转发型引用的引

  • C++11 模板参数的“右值引用”是转发引用吗

    在C++11中,&&不再只有逻辑与的含义,还可能是右值引用: void f(int&& i); 但也不尽然,&&还可能是转发引用: template<typename T> void g(T&& obj); "转发引用"(forwarding reference)旧称"通用引用"(universal reference),它的"通用"之处在于你可以拿一个左值绑定给转发引用

  • C++11的右值引用的具体使用

    C++11 引入了 std::move 语义.右值引用.移动构造和完美转发这些特性.由于这部分篇幅比较长,分为3篇来进行阐述. 在了解这些特性之前,我们先来引入一些问题. 一.问题导入 函数返回值是传值的时候发生几次对象构造.几次拷贝? 函数的形参是值传递的时候发生几次对象构造? 让我们先来看一段代码, // main.cpp #include <iostream> using namespace std; class A{ public: A(){ cout<<"cla

  • 详解C++ 引用

    引用变量是一个别名,也就是说,它是某个已存在变量的另一个名字.一旦把引用初始化为某个变量,就可以使用该引用名称或变量名称来指向变量. C++ 引用 vs 指针 引用很容易与指针混淆,它们之间有三个主要的不同: 不存在空引用.引用必须连接到一块合法的内存. 一旦引用被初始化为一个对象,就不能被指向到另一个对象.指针可以在任何时候指向到另一个对象. 引用必须在创建时被初始化.指针可以在任何时间被初始化. C++ 中创建引用 试想变量名称是变量附属在内存位置中的标签,您可以把引用当成是变量附属在内存位

  • C++ Primer注解之引用和指针

    引用(reference) 引用: 指的是左值引用(lvalue reference) 引用:取小名,达到绑定对象的作用,而不是将初始值拷贝给引用 special: 不能和 字面值 和 计算结果 绑定 引用不是对象 对引用的操作,都是在与之绑定的对象上进行的 除了两种例外外,引用的类型 和 与之绑定的对象 要严格匹配 int i = 1024; int &r = i; //√ int型的r,来引用int型的i double dval = 3.14; int &reval = dval; /

  • C++中的循环引用

    虽然C++11引入了智能指针的,但是开发人员在与内存的斗争问题上并没有解放,如果我门实用不当仍然有内存泄漏问题,其中智能指针的循环引用缺陷是最大的问题. // // main.cpp // test // // Created by 杜国超 on 17/9/9. // Copyright © 2017年 杜国超. All rights reserved. // #include <iostream> #include <memory> #include <vector>

  • 详解C++中指针和引用的区别

    1.指针和引用的本质(是什么) (1)指针是存放内存地址的一种变量,特殊的地方就在它存放的是内存地址.因此,指针的大小不会像其他变量一样变化,只跟当前平台相关--不同平台内存地址的范围是不一样的,32位平台下,内存最大为4GB,因此只需要32bit就可以存下,所以sizeof(pointer)的大小是4字节.64位平台下,32位就不够用了,要想内存地址能够都一一表示,就需要64bit(但是目前应该没有这么大的内存吧?),因此sizeof(pointer)是8. (2)引用的本质是"变量的别名&q

  • 深入了解c++11 移动语义与右值引用

    1.移动语义 C++11新标准中一个最主要的特性就是提供了移动而非拷贝对象的能力.如此做的好处就是,在某些情况下,对象拷贝后就立即被销毁了,此时如果移动而非拷贝对象会大幅提升性能.参考如下程序: //moveobj.cpp #include <iostream> #include <vector> using namespace std; class Obj { public: Obj(){cout <<"create obj" << e

  • 解析C++引用

    引言 我选择写C++中的引用是因为我感觉大多数人误解了引用.而我之所以有这个感受是因为我主持过很多C++的面试,并且我很少从面试者中得到关于C++引用的正确答案. 那么c++中引用到底意味这什么呢?通常一个引用让人想到是一个引用的变量的别名,而我讨厌将c++中引用定义为变量的别名.这篇文章中,我将尽量解释清楚,c++中根本就没有什么叫做别名的东东. 背景 在c/c++中,访问一个变量只能通过两种方式被访问,传递,或者查询.这两种方式是: 1.通过值访问/传递变量 2.通过地址访问/传递变量–这种

  • 用JQuery 实现AJAX加载XML并解析的脚本

    1,Content-Type 很多时候无法解析就是Content-Type的问题. 如果本身就是xml文件,请跳过这一步 动态生成的XML一定要将其设置为text/xml,否则默认就是text/html也就是普通的文本了. 常见语言的Content-Type设置 复制代码 代码如下: header("Content-Type:text/xml"); //php response.ContentType="text/xml" //asp response.setHea

  • 使用java8的方法引用替换硬编码的示例代码

    背景 想必大家在项目中都有遇到把一个列表的多个字段累加求和的情况,也就是一个列表的总计.有的童鞋问,这个不是给前端做的吗?后端不是只需要把列表返回就行了嘛...没错,我也是这样想的,但是在一场和前端的撕逼大战中败下阵来之后,这个东西就落在我身上了.当时由于工期原因,时间比较紧,也就不考虑效率和易用性了,只是满足当时的需求,就随便写了个方法统计求和.目前稍微闲下来了,就把原来的代码优化下.我们先来看一下原来的代码... 原代码 工具类 import org.apache.commons.lang3

  • JVM(Java虚拟机)简介(动力节点Java学院整理)

    一.概要 1.Java虚拟机(Jvm)是什么? 2.Java虚拟机是用来干什么的? 3.Java虚拟机它的体系结构是什么样子的? 4.Java虚拟机在工作做扮演什么角色? 5.Java虚拟机在运行时数据区? 二.Jvm基础概念 Java虚拟机(Jvm)是可运行Java代码的假想计算机. Java虚拟机包括一套字节码指令集.一组寄存器.一个栈.一个垃圾回收堆和一个存储方法域. 在了解Jvm之前,大家如果有兴趣的,也可以先去了解下Java 中的堆和栈. 三.Jvm 我们都知道Java源文件,通过编译

  • 使用ASP.NET MVC引擎开发插件系统

    一.前言 我心中的插件系统应该是像Nop那样(更牛逼的如Orchard,OSGI.NET),每个插件模块不只是一堆实现了某个业务接口的dll,然后采用反射或IOC技术来调用,而是一个完整的mvc小应用,我可以在后台控制插件的安装和禁用,目录结构如下: 生成后放在站点根目录下的Plugins文件夹中,每个插件有一个子文件夹 Plugins/Sms.AliYun/ Plugins/Sms.ManDao/ 我是一个有强迫症的的懒人,我不想将生成的dll文件拷贝到bin目录. 二.要解决的问题 1.as

  • javascript中的delete使用详解

    在这篇文章中作者从<JavaScript面向对象编程指南>一书中关于 delete 的错误讲起,详细讲述了关于 delete 操作的实现, 局限以及在不同浏览器和插件(这里指 firebug)中的表现. 下面翻译其中的主要部分. ...书中声称 "函数就像一个普通的变量那样--可以拷贝到不同变量,甚至被删除" 并附上了下面的代码片段作为说明: 复制代码 代码如下: >>> var sum = function(a, b) {return a+b;};>

  • 删除Javascript Object中间的key

    这个也不会,回家种田去吧你 复制代码 代码如下: delete thisIsObject[key] or delete thisIsObject.key 顺便我们来谈谈delete的用法 几个礼拜前, 我有了个机会去翻阅Stoyan Stefanov的 Object-Oriented Javascript 一书. 这本书在亚马逊上拥有很高的评价(12篇评论, 5颗星), 所以我很好奇地想看看它到底是不是那么值得推荐的一本书, 于是我开始阅读函数的那章. 我非常欣赏这本书解释事物的方式, 例子们被

  • jquery的总体架构分析及实现示例详解

    jQuery整体框架甚是复杂,也不易读懂,这几日一直在研究这个笨重而强大的框架.jQuery的总体架构可以分为:入口模块.底层模块和功能模块.这里,我们以jquery-1.7.1为例进行分析. jquery的总体架构 复制代码 代码如下: 16 (function( window, undefined ) {          // 构造 jQuery 对象   22     var jQuery = (function() {   25         var jQuery = functio

  • Java String类详解_动力节点Java学院整理

    引题 在Java语言的所有数据类型中,String类型是比较特殊的一种类型,同时也是面试的时候经常被问到的一个知识点,本文结合Java内存分配深度分析关于String的许多令人迷惑的问题.下面是本文将要涉及到的一些问题,如果读者对这些问题都了如指掌,则可忽略此文. 1.Java内存具体指哪块内存?这块内存区域为什么要进行划分?是如何划分的?划分之后每块区域的作用是什么?如何设置各个区域的大小? 2.String类型在执行连接操作时,效率为什么会比StringBuffer或者StringBuild

  • 浅谈Java的虚拟机结构以及虚拟机内存的优化

    工作以来,代码越写越多,程序也越来越臃肿,效率越来越低,对于我这样一个追求完美的程序员来说,这是绝对不被允许的,于是除了不断优化程序结构外,内存优化和性能调优就成了我惯用的"伎俩". 要对Java程序进行内存优化和性能调优,不了解虚拟机的内部原理(或者叫规范更严谨一点)是肯定不行的,这里推荐一本好书<深入Java虚拟机(第二版)>(Bill Venners著,曹晓刚 蒋靖 译,实际上本文正是作者阅读本书之后,对Java虚拟机的个人理解阐述).当然了,了解Java虚拟机的好处

随机推荐