引用类型的本质

编译器是怎么实现引用类型的呢?

预备知识1:使用const定义常量

  使用const可以采用类似定义变量的方法来定义常量,在定义的时候必须初始化以指明常量值。比如 const int a = 1; 。

  那么编译器会给const定义的常量分配内存空间吗?如果分配了内存空间,那么每次使用这个常量都要访问这个地址,空间效率暂且不论,时间效率不也被大大浪费?在这里可能一开始大家都会有这样的疑问。特别是单片机编程出身的我,在那个一无所知的时候觉得浪费效率简直浑身难受(单片机的计算能力非常有限)。

  先说是否给const常量分配空间,答案是会。可以对一个const常量取地址,因此无疑const常量在内存中是有一席之地的。

  再说时间效率,实际上编译器都会采用常量折叠技术来优化代码。具体说就是像宏替换一样把常量替换成立即数。但与宏替换不同的是,这个是在编译阶段完成的。这样凡是用到const常量的时候,都不需要访问内存去取出常量值,而是直接用立即数(这个数是直接写在机器指令里的)。这样的时间效率和宏替换相当。尽可能多的使用const关键字吧,这样可以大大减少bug数量。

//C++语句与对应的反汇编,可以看到给a分配了空间并初始化为1
//但是在之后用到a的地方使用了立即数1//如果看不懂反汇编也没关系,代码中要表达的在上文中已经全陈述过了
    const int a = 1;
00C516EE  mov         dword ptr [a],1
    int    b;
    b = fun(a);
00C516F5  push        1     

预备知识2:指针

一般说的“指针”是指“指针变量”。但是指针有时候也被理解成地址的同义词,所以指针变量不过是指“一个变量,里面存储的是一个指针/地址”。这里想说的是常量指针和指针常量。

常量指针是说“一个指向常量的指针”(也不一样指向一个常量,但*ptr被视为一个常量),本质上是一个指针变量。定义的方法是 const int * ptr ; 。

指针常量是说“一个保存了指针/地址的常量”,所以指针常量本质上是一个常量,定义的方法是 int * const ptr = &a;。就像一个const常量在定义时必须被初始化一样,常量指针也必须在定义时初始化,也就是说明自己是哪一个地址。

上一小节说明了什么叫常量折叠,和const常量是如何被替换成立即数的。那么指针常量当然也会被这样替换成立即数,只不过这个立即数表示一个地址,比如等于1480091972什么的。

预备知识3:变量的三个属性

  变量的最基本的属性基本上有三种:

(1)名字(必须显示说明)

(2)类型 (必须显示说明)

(3)存储类别 (缺省方式或显示说明(使用:auto、register、static、extern))

  作用域和生存期(作用域和生存期被认为是另两种属性,但这两个属性不像前三种基本)实际上由存储类别和定义变量的位置决定,所以变量最基本的属性就是上面三种。当看到一段C++源代码的某一个变量的时候,合格的程序员应该能说明这个变量的三种属性是什么,比如一个全局变量是int类型,名字是a。

  可是这些属性只对程序员和编译器有意义,机器只懂机器码不懂C++。也就是说编译器在编译源代码的时候需要这些信息,这些信息也在源代码中有体现,所以编译器也可以得到这些信息。编译器根据这些信息进行编译,但是编译之后这些信息就都丢失了,不保证再能从机器码里得到这些信息。所以名字对编译器是重要的信息,但编译后一块内存不再需要有名字,因为内存只需要编号就够了。名字只对程序员和编译器重要。

引用

经典的解释什么是引用的说法是:引用是变量的别名。确实是的,这句话完美到无懈可击,如果说有什么瑕疵那就是这句话不太好理解。什么叫别名?那么为什么要给变量多起一个名字呢?特别是在形参类型是引用时,如何能把一个“名字”作为参数呢。前面说了名字只是变量的属性之一,如果只是一个名字的话那连变量都不是啊,什么叫引用呢?

我先接触的是C语言,对于指针并没有太多困惑,但是C++的引用着实让我困惑了一阵。看看引用的使用场景:

1 int a = 1;
2 int &b = a;
3 b = 2;
4 cout << a << endl;    //输出2

上面的代码仿佛可以解释什么叫“引用是变量的别名”。b是a的别名,就是对b的操作和对a的操作一样, b = 2; 就可以理解成 a = 2; 。仿佛没什么难以理解。但是引用的应用价值在于作为形参的引用类型和返回引用类型。

在C语言中参数传递是传值的,想要实现对实参的修改需要借助指针,一个典型的swap函数的实现如下:

1 void swap(int*a,int *b){
2   int temp = *a;
3   *a = *b;
4   *b = temp;
5 }

交换两个变量a和b,需要传入a和b的地址 swap(&a,&b); 。想要交换两个盒子里的苹果,必须先找到这两个盒子,所以变量的地址在这个算法中是必不可少的。只懂C语言的我认为这自然而然:想要修改实参的值就必须传入地址。可是到接触了c++就发现实现同样的功能可以借助引用,而且要简单得多。既然“变量的地址在这个算法中是必不可少的”,那么引用到底是怎么实现传入地址的?

实际上引用可以看成指针常量。对const常量有定义时必须初始化的要求,同样对引用也有这样的要求:定义引用时必须指明是谁的引用(作为形参的引用类型是在传入实参的时候创建并初始化的)。把引用看成指针常量就可以解释传入引用为什么和传入指针一样的作用了。

什么是别名呢,就是地址或者叫指针常量

时间: 2024-11-24 02:48:04

引用类型的本质的相关文章

《C#图解教程》读书笔记之六:接口和转换

本篇已收录至<C#图解教程>读书笔记目录贴,点击访问该目录可获取更多内容. 一.接口那点事儿 (1)什么是接口? 一组函数成员而未实现的引用类型.只有类和结构能实现接口. (2)从IComparable接口看接口实例: 假设有如下一段代码,它使用Array类的一个静态方法Sort对一个未排序的int类型数组进行排序,并输出排序后的结果. using System; class Program { static void Main() { var myInt = new[] { 20, 4, 1

NET中的类型和装箱/拆箱原理

谈到装箱拆箱,DebugLZQ相信给位园子里的博友一定可以娓娓道来,大概的意思就是值类型和引用类型的相互转换呗---值类型到引用类型叫装箱,反之则叫拆箱.这当然没有问题,可是你只知道这么多,那么DebugLZQ建议你花点时间看看楼主这篇文章,继续前几篇博文的风格--浅谈杂侃. 1. .NET中的类型 为了说明装箱和拆箱,那首先必须先说类型.在.NET中,我们知道System.Object类型是所有内建类型的基类.注意这里说的是内建类型,程序员可以编写不继承子自System.Object的类型,这

[AaronYang]C#人爱学不学[1]

1. 记事本写C#,脱离vs 新建记事本,名字为 helloworld.cs using System; namespace Hello{ public class HelloWorldSay{ static void Main(){ Console.WriteLine("你好,世界"); Console.ReadLine(); return; } } } 简单的使用csc命令编译: 我的目录在:C:\Program Files (x86)\Microsoft Visual Studi

Java 新手的通病(编程随想的博客)

一:对算法和数据结构不熟悉 为什么我先拿"数据结构和算法"说事捏?这玩意是写程序最最基本的东东.不管你使用 Java 还是其它的什么语言,都离不开它.而且这玩意是跨语言的,学好之后不管在哪门语言中都能用得上. 既然"数据结构和算法"这么重要,为什么很多 Java 新手却很不熟悉捏?我琢磨了一下,估计有两种可能.有些人虽然是计算机系毕业的,但是当初压根没好好学过这门课程,到工作时早都还给老师了:还有一些人是中途转行干编程,转行后又没有好好地打基础(都指望速成). 下面

C#复习笔记(2)--C#1所搭建的核心基础

通过对C#1所搭建的核心基础的深入了解,可以知道之后的C#版本在C#1的基础上做了很多扩展,而这些扩展都是基于C#搭建的核心基础而来的. 委托 一.编写委托的过程 委托经常和C语言的"函数指针"挂钩.委托是方法参数化.函数式语言一个重要的表达方式.C#1中编写一个委托要经过四部: 1.声明委托类型 delegate void StringProcessor(string param1); 这个委托指定了一种无返回值,有一个string类型的参数的方法. 这个委托继承自System.Mu

现金与存折---值类型和引用类型

在软考的时候也接触过值类型和引用类型,那时候应付做题还是可以的,可是考完之后再突然面对这两个词汇,又觉得迷茫无措了.现在想想,还是实践吧,当时只是简单的了解了其原理,没有用代码来实现,所以只能算是初步的,暂时的了解.这篇文章就是为了弥补初步的遗憾,进行深一步的学习. 理论联系实践,才是对现实的超越.就像门和钥匙一样,完美结合才有防窃和安全之功效.所以,该篇文章的主要思路也是从理论和实践两个方面分别对"值类型和引用类型"进行详细阐述. --------------------------

JS基础知识回顾:引用类型(一)

在ECMAScript中引用类型是一种数据结构,用于将数据和功能组织在一起,而对象时引用类型的一个实例. 尽管ECMAScript从技术上讲是一门面向对象的语言,但它不具备传统的面向对象语言所支持的类和接口等基本结构,所以虽然说引用类型与类看起来想死,但他们并不是相同的概念. 不过引用类型有的时候也可以被称为对象定义,因为他们描述的是一类对象所具有的属性和方法. 新对象是使用new操作符后跟一个构造函数来实现的,构造函数本身就是一个函数,只不过该函数时处于创建新对象的目的而定义的. ECMASc

JavaScript高级程序设计学习笔记第五章--引用类型(函数部分)

四.Function类型: 1.函数定义的方法: 函数声明:function sum (num1, num2) {return num1 + num2;} 函数表达式:var sum = function(num1, num2){return num1 + num2;};//注意有个分号 构造函数的方式:var sum = new Function("num1", "num2", "return num1 + num2");// 2.函数的重复声

20151024_001_C#基础知识(静态与非静态的区别,值类型和引用类型,堆和栈的区别,字符串的不可变性,命名空间)

1:我们把这些具有相同属性和相同方法的对象进行进一步的封装,抽象出来类这个概念. 类就是个模子,确定了对象应该具有的属性和方法. 对象是根据类创建出来的. 2:类:语法 [public] class 类名 { 字段; 属性; 方法; } 写好了一个类之后,我们需要创建这个类的对象,那么,我们管创建这个类的对象过程称之为类的实例化.使用关键字new 实例化类===创建类 this:表示当前这个类的对象. 类是不占内存的,而对象是占用内存的. 结构是面向过程的,类是面向对象的,之前没有面向对象的时候