c#1所搭建的核心基础之值类型和引用类型
这个主题很重要,在.NET中做的一切其实都是在和一个值类型或者引用类型打交道。
现实世界中的值和引用
假定你在读一份非常棒的东西,希望一个朋友也去读他。于是你到复印室里复印了一份。这个时候他获得了属于他自己的一份完整副本。在这种情况下,我们处理的是值类型的行为。你和你朋友是各自独立的。你可以在自己的上面加一些注释,他的报纸不会改变。
再假定你在qq空间里发表了一篇日志,你想让朋友看到,这一次,你唯一需要给你朋友的是你qq空间日志所在的url地址。这就是引用类型的行为。当你修改你的日志时,你的朋友就你那个看到改变。
在c#和.net中,值类型和引用类型的差异与现实世界中差别类似。.Net中的大多数类型都是引用类型。除了一下总结的特殊情况,类(使用class来声明)是引用类型,而结构(使用struct来声明)是值类型。特殊情况包括如下方面:
- 数组类型时引用类型,即使元素类型是值类型(所以int[]仍是引用类型,即使int是值类型)
- 枚举(使用enum声明)是值类型
- 委托类型(使用delegate声明)是引用类型
- 接口类型(使用interface声明)是引用类型,但是可由值类型实现
值类型和引用类型基础知识
学习值类型和引用类型时,要掌握的重要概念是一个特殊表达式的值时什么。为使问题具体化,使用表达式最常见的例子-变量。但是,同样的道理也适用于属性、方法调用、所引器和其他表达式。
大多数表达式都有与其相关的静态类型。对于值类型的表达式,它的值就是表达式的值,例如“2+3”的值就是5。 然而,引用类型的表达式,他的值是一个引用,而不是该引用所指代的一个对象。所以string.Empty的值不是一个空字符串-而是对空字符串的一个引用。在平常的讨论中,甚至在一些专业文档中,经常混淆这一区别。例如,你可能这样描述: string.Concat的作用是返回一个字符串,该字符串将所有参数都连接到一起。
变量的值是在它声明时的位置存储的。局部变量的值总是存储在栈中。实例变量的值总是存储到实例本身存储的地方。引用实例(对象)总是存储在堆中,静态变量也是。
两种类型的另一个差异在于,值类型不可以派生出其他类型。这将导致的一个结果就是,值不需要额外的信息来描述值实际是什么类型。把他同引用类型比较,对于引用类型来说,每个对象的开头都包含一个数据块,它标识了对象的实际类型,同时还提供了其他一些信息。永远都不能改变对象的类型—执行简单强制转换时,运行时会获取一个引用,检查它引用的对象是不是目标类型的一个有效的对象,如果有效就返回原始引用,否则抛出异常。
走出误区
误区1:“结构是轻量级的类”
这个误区存在多种形式。
有人认为值类型不能或者不应该有方法或其他有意义的行为。对于这种说法,一个非常典型的反例就是DateTime类型:它作为值类型来提供是很有道理的,因为他非常适合作为数字或者字符形似的一个基本单位来使用。另外它也应该赋予对它的值执行计算的能力。换个角度看这个问题,是数据转移类型一般都是引用类型。总之,具体应该如何决定,应取决于需要的是值类型的语义还是引用类型的语义,而不是取决于这个类型简单与否。
还有一些人认为值类型之所以显得比较轻,是因为性能。事实是在某些情况下值类型很能干,它们不需要垃圾回收,不会因引用类型标识而产生开销,也不需要取值这一步操作。但是在其他方面,引用类型更能干,在传递参数、赋值、将值返回和执行类似的操作时,只需复制4或8字节,而不是复制全部数据。假定ArrayList是一个所谓“纯的”值类型,那么将一个ArrayList传递给一个方法时,就得复制它的所有数据!几乎在所有情况下,性能问题都不是根据这种判断决定的。瓶颈从来不是想当然的,在你根据性能进行设计之前,需要衡量不同的选择。
值得注意的是,将这两者结合也不能解决问题:类型(不管是类还是结构)拥有多少方法并不重要,每个实例所占用的内存不会受到影响(代码本身消耗内存,但这只会发生一次,而不是每个实例都发生)
误区二“引用类型在堆上,值类型在栈上”
这个误区主要归咎于转述这句话的人没有动脑筋。第一部分是正确的—引用类型的实例总是在堆上创建的。但第二部分就有问题了。前面讲过,变量的值是在他生明的位置存储的。
所有假定一个类中有一个int类型的实例变量,那么在这个类的任何对象中,该变量的值总是和对象中的其他数据在一起,也就是在堆上。只有局部变量(方法内部声明的变量)和方法参数在栈上。但是在c#2及更高版本,很多局部变量并不完全放在栈上。
误区三:对象在c#中是通过引用传递的
这或许是传播最广的一个误区了。同样,说这句话的人一般(并不总是)知道c#实际的行为是什么,但不知道“引用传递”的真正意义是什么。重要的一点是:假如以引用传递的方式来传送一个变量,那么调用的方法通过更改其参数值,来改变调用者的变量值。现在请记住:引用类型变量是值的引用,而不是对象本身。不需要引用来传递参数本身,就可以更改改参数引用的那个对象的内容。
例子:
using System;
using System.Collections.Generic;
using System.Linq; using System.Text;
using System.Threading.Tasks;
namespace ConsoleApplication1 {
class Program {
public void AppendHello(StringBuilder builder) {
builder.Append("hello");
builder = new StringBuilder("xyz");
}
static void Main(string[] args) {
StringBuilder mybuilder = new StringBuilder("123");
new Program().AppendHello(mybuilder);
Console.Write(mybuilder);
}
}
}
运行结果是123hello,说明参数对象没有发生改变。说明参数不是引用
调用这个方法时,参数值(对某StringBuilder的一个引用)是以值传递(pass by value)的方式传递的。如果想再方法内部更改builder变量的值,如执行builder=null,语句,调用者看不到这一改变。
有趣的是,在这种错误的说法中,不仅“引用传递”的说法有误,而且“对象传递”的说法也存在问题。无论是引用传递还是值传递,对象本身永远不会被传递。涉及一个引用类型时,要么以“引用传递”方式传递变量,要么以“传值”的方式传递参数值。最起码,这回答了当null做为一个传值参数的值来使用时会发生什么的问题。假如传递的是对象,这个时候就会出问题,因为没有一个对象可供传递!相反,null引用会采用和其他引用一样的“值传递”的方式传递。
装箱和拆箱
有时候我们不想用值类型的值,就是想用一个引用。之所以会发生这种情况,有多种原因,幸好,c#和.Net提供了一个名为装箱的机制,它允许根据值类型来创建一个对象,然后使用这个对象的一个引用。在接触实际的例子之前,先来回顾两个重要的事实:
- 对于引用类型的变量,他的值永远是一个引用
- 对于值类型的变量,他的值永远是该值类型的一个值
基于这两个事实,下面3行代码第一眼看上去似乎没有太多道理:
int i=5;
object o=i;
int j=(int) o;
这里有两个变量:i是值类型的变量,o是引用类型的变量。将i的值赋给o有道理吗?o的值必须是一个引用,而数字5不是引用,它是一个整数值。实际发生的事情就是装箱:运行时将咋堆上创建一个包含值(5)的对象(它是一个普通对象)。o的值是对该新对象的一个引用。该对象的值时原始值的一个副本,改变i的值不会改变箱内的值。
第三行执行相反的操作:拆箱。必须告诉编译器将object拆箱成什么类型。如果使用了错误的类型,就会抛出一个InvalidCastException异常。同样,拆箱也会复制箱内的值,在赋值以后,j和该对象之间不再有任何联系。
上面这一段话其实已经简单明了地解释了装箱和拆箱。剩下的唯一的问题就是要知道装箱和拆箱在什么时候发生。拆箱一般是很明显的,因为要在代码中明确地显示一个强制类型转换。装箱则可能在没有意识的时候发生。上例展示的是一个简单的版本。但是,为一个类型的值调用ToString,Equals或者GetHashCode方法时,如果该类型没有覆盖这些方法,也会发生装箱。另外,将值作为接口表达式使用时—把它赋给一个接口类型的变量,或者把它作为接口类型的参数来传递也会发生装箱。例如,IComparable x=5;语句会对数字5进行装箱。
之所以要留意装箱和拆箱,是由于它们可能会降低性能。一次装箱或拆箱是微不足道的,但,假如执行千百次这样的操作,那么不仅会增大程序本身的操作开销,还会创建数量众多的对象,而这些对象会加重垃圾回收器的负担。同样,这种性能损失通常也不是大问题,但还是应该引起注意。
整理自:c# in depth
c#1所搭建的核心基础之值类型和引用类型的更多相关文章
- C#面试基础知识点:值类型和引用类型(1)(填坑文)
目录 前言 C#值类型和引用类型 基类(共同点) 值类型继承基类(不同点) 应用类型继承 技术经理的问题 值类型与引用类型都可以用Equals来比较吗? 如何将一个数组a的值赋予数组b然后对b做修改而 ...
- C#基础:值类型、引用类型与ref关键字
在C#中,ref的意思是按引用传递.可以参考C++: int a = 10, b = 20; void swap(int x, int y) { int temp = x; x = y; y = te ...
- C#复习笔记(2)--C#1所搭建的核心基础
通过对C#1所搭建的核心基础的深入了解,可以知道之后的C#版本在C#1的基础上做了很多扩展,而这些扩展都是基于C#搭建的核心基础而来的. 委托 一.编写委托的过程 委托经常和C语言的“函数指针”挂钩. ...
- C#基础知识系列二(值类型和引用类型、可空类型、堆和栈、装箱和拆箱)
前言 之前对几个没什么理解,只是简单的用过可空类型,也是知道怎么用,至于为什么,还真不太清楚,通过整理本文章学到了很多知识,也许对于以后的各种代码优化都有好处. 本文的重点就是:值类型直接存储其值,引 ...
- C#基础--值类型和引用类型
C#中大多数类型都是引用类型,只有个别特殊情况是值类型. 值类型: 枚举(enum) 结构(struct) 基础类型:int, short, char, bool....(string是引用类型) 引 ...
- c#基础系列1---深入理解值类型和引用类型
"大菜":源于自己刚踏入猿途混沌拾起,自我感觉不是一般的菜,因而得名"大菜",于自身共勉. 不知不觉已经踏入坑已10余年之多,对于c#多多少少有一点自己的认识, ...
- [No0000B9]C# 类型基础 值类型和引用类型 及其 对象复制 浅度复制vs深度复制 深入研究2
接上[No0000B5]C# 类型基础 值类型和引用类型 及其 对象判等 深入研究1 对象复制 有的时候,创建一个对象可能会非常耗时,比如对象需要从远程数据库中获取数据来填充,又或者创建对象需要读取硬 ...
- [No0000B5]C# 类型基础 值类型和引用类型 及其 对象判等 深入研究1
引言 本文之初的目的是讲述设计模式中的 Prototype(原型)模式,但是如果想较清楚地弄明白这个模式,需要了解对象克隆(Object Clone),Clone其实也就是对象复制.复制又分为了浅度复 ...
- C#基础篇五值类型和引用类型
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace P01M ...
随机推荐
- Activity的创建和使用
Activity: 1:创建一个类继承Activity或者它的子类 public class MainActivity extends Activity { @Override protected v ...
- ListBox控件
主要介绍:自定义数据.绑定数据库数据 前台代码: <div> <asp:ListBox ID=" Width ="100px"> <asp: ...
- CentOS下Samba文件服务器的安装与配置
CentOS下Samba文件服务器的安装与配置 http://blog.csdn.net/limingzhong198/article/details/22064801 一.安装配置 1. 安装sam ...
- c++,operator=
operator=为什么值得注意? 从语法上讲,下面的程序可以编译通过,我在另一篇笔记示例里面也这样用了. class A1 { public: int operator=(int a)//参数是in ...
- [置顶] java的foreach循环
foreach语句是java5之后的新特征之一,在循环遍历数组.集合方面更加简洁. 使用foreach循环遍历数组和集合时,无需获得数组和集合的长度,无须根据索引来访问数组元素和集合元素,foreac ...
- zoj3713 7Bit
意思是把一行字符串的长度按照找7位一个字节输出,如果长度能够存在7位里,字节的最高位置0,否则只输出7位并且输出字节的最高位置1,直到全部输出长度. 要注意的是有空串要输出00,其他按照16进制输出就 ...
- iOS开发之XMPP即时通讯简单实现
首先搭载服务器和数据库 搭载服务器我用的是openfire,数据库用的是mysql 这里推荐两个链接 配置mysql,用的是mysql workbench http://justsee.iteye.c ...
- (诊断)为GitHub添加SSH key时出现“Could not open a connection to your authentication agent”错误的应对方案(转)
在为windows 环境下的github账户添加SSH key时,需要在Git Bash执行如下命令: 第一步:检查已有的SSH keys $ ls -al ~/.ssh 第二步:生成新的SSH ke ...
- boost/lexical_cast.hpp的简单使用方法_行动_新浪博客
boost/lexical_cast.hpp的简单使用方法_行动_新浪博客 boost/lexical_cast.hpp的简单使用方法 (2010-03-19 16:31:13) ...
- SSH框架之Hibernate(1)——映射关系
ORM的实现思想就是将关系数据库中表的数据映射成对象.以对象的形式展现,这样开发者就能够把对数据库的操作转化为对这些对象的操作.Hibernate正是实现了这样的思想,达到了方便开发者以面向对象的思想 ...