C# 拷贝数组的几种方式.docx
- 文档编号:28106074
- 上传时间:2023-07-08
- 格式:DOCX
- 页数:24
- 大小:50.58KB
C# 拷贝数组的几种方式.docx
《C# 拷贝数组的几种方式.docx》由会员分享,可在线阅读,更多相关《C# 拷贝数组的几种方式.docx(24页珍藏版)》请在冰豆网上搜索。
C#拷贝数组的几种方式
C#拷贝数组的几种方式
C#中数组复制有多种方法
数组间的复制,int[]pins={9,3,4,9};int[]alias=pins;这里出了错误,也是错误的根源,以上代码并没有出错,但是根本不是复制,因为pins和alias都是引用,存在于堆栈中,而数据9,3,4,3是一个int对象存在于堆中,int[]alias=pins;只不过是创建另一个引用,alias和pins同时指向{9,3,4,3},当修改其中一个引用的时候,势必影响另一个。
复制的意思是新建一个和被复制对象一样的对象,在C#语言中应该有如下4种方法来复制。
方法一:
使用for循环
int[]pins={9,3,7,2}
int[]copy=newint[pins.length];
for(inti=0;i!
=copy.length;i++)
{
copy[i]=pins[i];
}
方法二:
使用数组对象中的CopyTo()方法
int[]pins={9,3,7,2}
int[]copy2=newint[pins.length];
pins.CopyTo(copy2,0);
方法三:
使用Array类的一个静态方法Copy()
int[]pins={9,3,7,2}
int[]copy3=newint[pins.length];
Array.Copy(pins,copy3,copy.Length);
方法四:
使用Array类中的一个实例方法Clone(),可以一次调用,最方便,但是Clone()方法返回的是一个对象,所以要强制转换成恰当的类类型。
int[]pins={9,3,7,2}
int[]copy4=(int[])pins.Clone();
方法五:
string[]student1={"$","$","c","m","d","1","2","3","1","2","3"};
string[]student2={"0","1","2","3","4","5","6","6","1","8","16","10","45","37","82"};
ArrayListstudent=newArrayList();
foreach(strings1instudent1)
{
student.Add(s1);
}
foreach(strings2instudent2)
{
student.Add(s2);
}
string[]copyAfter=(string[])student.ToArray(typeof(string));
两个数组合并,最后把合并后的结果赋给copyAfter数组,这个例子可以灵活变通,很多地方可以用。
首先说明一下,数组是引用类型的,所以注意不要在复制时复制了地址而没有复制数值哦!
其实在复制数组的时候,一定要用new在堆中开辟一块新的空间专门用于存放数组,这样才是有效的。
(1)
int[]pins={9,3,7,2};
int[]copy=newint[pins.length];
for(inti=0;i { copy[i]=pins[i]; } (2) int[]copy=newint[pins.Length]; pins.CopyTo(copy,0); (3) Int[]pins=newint[4]{9,3,7,2}; Int[]alias=pins; 注意这种复制只是一种引用而已,只是把数据的地址传递给了alias数组,所以不太推荐这种方式来复制数组; (4) Array.Copy(pins,copy,copy.Length) (5) Int[]copy=(int[])pins.Clone(); 这里说明一下为什么要用到int[]的强制类型转换,原因就在于Clone的结果类型是object的,所以需要强制转换为int[] Object类其实就是我们所有类的基类。 C#数组中CopyTo()和Clone()的区别(转) CopyTo()和Clone() 1.CopyTo()和Clone() 相信大多数C#程序员都有查阅MSDN的好习惯,但是MSDN中提到这两个方法最大的区别就是: 一个方法创建了一个新Array对象,一个方法只是复制了Array引用.这句话本身没有错误,而且也正是他们的区别所在.只是这样会让人感到很迷惑.到底是什么区别呢? 这里还是先说说他们的共同点: CopyTo()和Clone()都属于浅拷贝,这一点是毋庸置疑的.对于浅拷贝: 如果数组中的成员为值类型(如: int,float,double,byte等),则完全复制数值到目标数组中,如果是引用类型(如用户自定义类型: classStudent,classPeople,或者是类库中的类类型: ArrayList等),则指复制引用给目标数组. 那么CopyTo()和Clone()方法的区别是什么呢? 其实他们的区别,也就是MSDN上说的最大的区别就是用法上的区别.我们可以在VS弹出智能提示的时候看看他们的返回值,CopyTo()的返回值是void,使用方法如下Array1.CopyTo(Array2,0);其中Array2必须是一个已经实例化的数组.而Clone()的返回值是object.使用方法如下Array2=Array1.Clone();其中Array2不必实例化.这样,我相信理解这两个方法的区别就很容易了.本质上并没有什么区别.都属于浅拷贝.如果拷贝所有的数组,就是用Clone().但是如果只是拷贝一部分,就可以选择CopyTo()了,CopyTo()的参数提示是这样的CopyTo(Arrayarray,intIndex).第二个参数index(索引)是指明从数组中的第几个对象开始复制. 2.浅拷贝和深拷贝的区别. 如上面所说的,浅拷贝对于值类型则复制值,对于引用类型则复制对象的引用(类似于指针).深拷贝则是完完全全的创建一个个新对象.对原来数组中的所有对象全部创建新对象.对新数组中的修改不会影响原来数组中的值或对象.但是如何实现深拷贝呢? .NET库中似乎没有深拷贝的方法.这和实现深拷贝的原理有关系.若用户希望实现深拷贝.希望出现两个完全一样但又互不影响的数组.则必须自己写方法,对原数组中的每个对象实现拷贝,层层深入,直到这个对象中的对象中的对象……中的对象为值类型为止,因为只有值类型才是完全拷贝,对一个值进行修改不会影响另一个相同的值.这么说又有点难理解了.1.深拷贝是指源对象与拷贝对象互相独立,其中任何一个对象的改动都不会对另外一个对象造成影响。 举个例子,一个人名叫张三,后来用他克隆(假设法律允许)了另外一个人,叫李四,不管是张三缺胳膊少腿还是李四缺胳膊少腿都不会影响另外一个人。 比较典型的就是Value(值)对象,如预定义类型Int32,Double,以及结构(struct),枚举(Enum)等。 考虑以下写法 intsource=int.MaxValue;// (1)初始化源对象为整数的最大值2,147,483,647 intdest=source;// (2)赋值,内部执行深拷贝 dest=1024;//(3)对拷贝对象进行赋值 source=2048;//(4)对源对象进行赋值 首先 (2)中将source赋给dest,执行了深拷贝动作,其时dest和source的值是一样的,都是int.MaxValue;(3)对dest进行修改,dest值变为1024,由于是深拷贝,因此不会运行source,source仍然是int.MaxValue;(4)对source进行了修改,同样道理,dest仍然是1024,同时int.MaxValue的值也不变,仍然是2,147,483,647;只有source变成了2048。 再考虑以下写法 structPoint { publicintX; publicintY; publicPoint(intx,inty) { X=x; Y=y; } } Pointsource=newPoint(10,20); Pointdest=source; dest.X=20 当dest.X属性变成20后,source的X属性仍然是10 2.浅拷贝是指源对象与拷贝对象共用一份实体,仅仅是引用的变量不同(名称不同)。 对其中任何一个对象的改动都会影响另外一个对象。 举个例子,一个人一开始叫张三,后来改名叫李四了,可是还是同一个人,不管是张三缺胳膊少腿还是李四缺胳膊少腿,都是这个人倒霉。 比较典型的就有Reference(引用)对象,如Class(类)。 考虑以下写法 classPoint { publicintX; publicintY; publicPoint(intx,inty) { X=x; Y=y; } } Pointsource=newPoint(10,20); Pointdest=source; dest.X=20; 由于Point现在是引用对象,因此Pointdest=source的赋值动作实际上执行的是浅拷贝,最后的结果应该是source的X字段值也变成了20。 即它们引用了同一个对象,仅仅是变量明source和dest不同而已。 3.引用对象的浅拷贝原理 引用对象之间的赋值之所以执行的是浅拷贝动作,与引用对象的特性有关,一个引用对象一般来说由两个部分组成 (1)一个具名的Handle,也就是我们所说的声明(如变量) (2)一个内部(不具名)的对象,也就是具名Handle的内部对象。 它在MangedHeap(托管堆)中分配,一般由新增引用对象的New方法是进行创建 如果这个内部对象已被创建,那么具名的Handle就指向这个内部对象在MangedHeap中的地址,否则就是null(从某个方面来讲,如果这个具名的handle可以被赋值为null,说明这是一个引用对象,当然不是绝对)。 两个引用对象如果进行赋值,它们仅仅是复制这个内部对象的地址,内部对象仍然是同一个,因此,源对象或拷贝对象的修改都会影响对方。 这也就是浅拷贝 4.引用对象如何进行深拷贝 由于引用对象的赋值仅仅是复制具名Handle(变量)指向的地址,因此要对引用对象进行深拷贝就要重新创建一份该对象的实例,并对该对象的字段进行逐一赋值,如以下写法 classPoint { publicintX; publicintY; publicPoint(intx,inty) { X=x; Y=y; } } Pointsource=newPoint(10,20); Pointdest=newPoint(source.X,source.Y); //或以下写法 //Pointdest=newPoint() //dest.X=source.X //dest.Y=source.Y 其时,source和dest就是两个互相独立的对象了,两者的修改都不会影响对方 5.一些需要注意的东西 (1): String字符串对象是引用对象,但是很特殊,它表现的如值对象一样,即对它进行赋值,分割,合并,并不是对原有的字符串进行操作,而是返回一个新的字符串对象 (2): Array数组对象是引用对象,在进行赋值的时候,实际上返回的是源对象的另一份引用而已;因此如果要对数组对象进行真正的复制(深拷贝),那么需要新建一份数组对象,然后将源数组的值逐一拷贝到目的对象中 在C#中怎么部分复制数组? Array.Copy()方法有四种重载,其中有一个重载可以指定从数组的第几个元素开始复制,复制多少个。 具体用法如下: publicstaticvoidCopy( ArraysourceArray,//包含要复制的数据 longsourceIndex,//64位整数,它表示sourceArray中复制开始处的索引 ArraydestinationArray,//目的数组 longdestinationIndex,//64位整数,它表示destinationArray中存储开始处的索引 longlength//64位整数,它表示要复制的元素数目 ) C#数组复制的另外两种方式。 字节偏移复制与安全复制。 代码如下: staticvoidMain(string[]args) { int[]src=new[]{1,2,3,4,5,6}; constintdestLen=4;//目标数组大小 intint_size=sizeof(int);//用于获取值类型的字节大小。 int[]dest=newint[destLen]; //只支持基元类型,按字节偏移复制 Buffer.BlockCopy(src,(src.Length-destLen)*int_size,dest,0,destLen*int_size); foreach(variindest) { Console.Write(i+""); } Console.WriteLine("\n-------------------------------------------"); string[]srcstr=new[]{"A","B","C","D","E","F"}; object[]destobj=newobject[src.Length-2]; //移除的元素个数 constintdellen=2; //保证不破坏目标数组的元素(回滚)。 不装箱、拆箱、或向下转换,否则报错。 //如果srcstr改为src则报错,因为装箱。 Array.ConstrainedCopy(srcstr,dellen,destobj,0,srcstr.Length-dellen); foreach(varsindestobj) { Console.Write(s+""); } } 效果如下: C#中的浅拷贝与深拷贝 浅拷贝: 如果数组中的成员为值类型(如: int,float,double,byte等),则完全复制数值到目标数组中,如果是引用类型(如用户自定义类型: classStudent,classPeople,或者是类库中的类类型: ArrayList等),则指复制引用给目标数组。 文字有时候不如代码来得容易理解.但是这里也许用图更容易理解,看下图: 假定创建一个学生类数组Student[],然后浅拷贝到另一个学生类数组Student1[]中 从图中很容易看出所谓的浅拷贝对于引用类型,仅仅只是复制引用.通过一个数组修改内存中的值会影响另一个数组对内存对象的引用。 Array类中的CopyTo和Clone函数都属于浅拷贝。 深拷贝则是完完全全的创建一个个新对象.对原来数组中的所有对象全部创建新对象.对新数组中的修改不会影响原来数组中的值或对象。 C#线程锁使用全功略 前两篇简单介绍了线程同步lock,Monitor,同步事件EventWaitHandler,互斥体Mutex的基本用法,在此基础上,我们对它们用法进行比较,并给出什么时候需要锁什么时候不需要的几点建议。 最后,介绍几个FCL中线程安全的类,集合类的锁定方式等,做为对线程同步系列的完善和补充。 1.几种同步方法的区别 lock和Monitor是.NET用一个特殊结构实现的,Monitor对象是完全托管的、完全可移植的,并且在操作系统资源要求方面可能更为有效,同步速度较快,但不能跨进程同步。 lock(Monitor.Enter和Monitor.Exit方法的封装),主要作用是锁定临界区,使临界区代码只能被获得锁的线程执行。 Monitor.Wait和Monitor.Pulse用于线程同步,类似信号操作,个人感觉使用比较复杂,容易造成死锁。 互斥体Mutex和事件对象EventWaitHandler属于内核对象,利用内核对象进行线程同步,线程必须要在用户模式和内核模式间切换,所以一般效率很低,但利用互斥对象和事件对象这样的内核对象,可以在多个进程中的各个线程间进行同步。 互斥体Mutex类似于一个接力棒,拿到接力棒的线程才可以开始跑,当然接力棒一次只属于一个线程(ThreadAffinity),如果这个线程不释放接力棒(Mutex.ReleaseMutex),那么没办法,其他所有需要接力棒运行的线程都知道能等着看热闹。 EventWaitHandle类允许线程通过发信号互相通信。 通常,一个或多个线程在EventWaitHandle上阻止,直到一个未阻止的线程调用Set方法,以释放一个或多个被阻止的线程。 2.什么时候需要锁定 首先要理解锁定是解决竞争条件的,也就是多个线程同时访问某个资源,造成意想不到的结果。 比如,最简单的情况是,一个计数器,两个线程同时加一,后果就是损失了一个计数,但相当频繁的锁定又可能带来性能上的消耗,还有最可怕的情况死锁。 那么什么情况下我们需要使用锁,什么情况下不需要呢? 1)只有共享资源才需要锁定 只有可以被多线程访问的共享资源才需要考虑锁定,比如静态变量,再比如某些缓存中的值,而属于线程内部的变量不需要锁定。 2)多使用lock,少用Mutex 如果你一定要使用锁定,请尽量不要使用内核模块的锁定机制,比如.NET的Mutex,Semaphore,AutoResetEvent和ManuResetEvent,使用这样的机制涉及到了系统在用户模式和内核模式间的切换,性能差很多,但是他们的优点是可以跨进程同步线程,所以应该清楚的了解到他们的不同和适用范围。 3)了解你的程序是怎么运行的 实际上在web开发中大多数逻辑都是在单个线程中展开的,一个请求都会在一个单独的线程中处理,其中的大部分变量都是属于这个线程的,根本没有必要考虑锁定,当然对于ASP.NET中的Application对象中的数据,我们就要考虑加锁了。 4)把锁定交给数据库 数据库除了存储数据之外,还有一个重要的用途就是同步,数据库本身用了一套复杂的机制来保证数据的可靠和一致性,这就为我们节省了很多的精力。 保证了数据源头上的同步,我们多数的精力就可以集中在缓存等其他一些资源的同步访问上了。 通常,只有涉及到多个线程修改数据库中同一条记录时,我们才考虑加锁。 5)业务逻辑对事务和线程安全的要求 这条是最根本的东西,开发完全线程安全的程序是件很费时费力的事情,在电子商务等涉及金融系统的案例中,许多逻辑都必须严格的线程安全,所以我们不得不牺牲一些性能,和很多的开发时间来做这方面的工作。 而一般的应用中,许多情况下虽然程序有竞争的危险,我们还是可以不使用锁定,比如有的时候计数器少一多一,对结果无伤大雅的情况下,我们就可以不用去管它。 3.InterLocked类 Interlocked类提供了同步对多个线程共享的变量的访问的方法。 如果该变量位于共享内存中,则不同进程的线程就可以使用该机制。 互锁操作是原子的,即整个操作是不能由相同变量上的另一个互锁操作所中断的单元。 这在抢先多线程操作系统中是很重要的,在这样的操作系统中,线程可以在从某个内存地址加载值之后但是在有机会更改和存储该值之前被挂起。 我们来看一个InterLock.Increment()的例子,该方法以原子的形式递增指定变量并存储结果,示例如下: classInterLockedTest { publicstaticInt64i=0; publicstaticvoidAdd() { for(inti=0;i<100000000;i++) { Interlocked.Increment(refInterLockedTest.i); //InterLockedTest.i=InterLockedTest.i+1; } } publicstaticvoidMain(string[]args) { Threadt1=newThread(newThreadStart(InterLockedTest.Add)); Threadt2=newThread(newThreadStart(InterLockedTest.Add)); t1.Start(); t2.Start(); t1.Join(); t2.Join(); Console.WriteLine(InterLockedTest.i.ToString()); Console.Read(); } } 输出结果200000000,如果InterLockedTest.Add()方法中用注释掉的语句代替Interlocked.Increment()方法,结果将不可预知,每次执行结果不同。 InterLockedTest.Add()方法保证了加1操作的原子性,功能上相当于自动给加操作使用了lock锁。 同时我们也注意到InterLockedTest.Add()用时比直接用+号加1要耗时的多,所以说加锁资源损耗还是很明显的。 另外InterLockedTest类还有几个常用方法,具体用法可以参考MSDN上的介绍。 4.集合类的同步 .NET在一些集合类,比如Queue、ArrayList、HashTable和Stack,已经提供了一个供lock使用的对象SyncRoot。 用Reflector查看了SyncRoot属性(Stack.SynchRoot略有不同)的源码如下: publicvirtualobjectSyncRoot { get { if(this._syncRoot==null) { //如果_syncRoot和null相等,将newobject赋值给_syncRoot //Interlocked.CompareExchange方法保证多个线程在使用syncRoot时是线程安全的 Interlocked.CompareExchange(refthis._syncRoot,newobject(),null); } returnthis._syncRoot; } } 这里要特别注意的是MSDN提到: 从头到尾对一个集合进行枚举本质上并不是一个线程安全的过程。 即使一个集合已进行同步,其他线程仍可以修改该集合,这将导致枚举数引发异常。 若要在枚举过程中保证线程安全,可以在整个枚举过程中锁定集合,或者捕捉由于其他线程进行的更改而引发的异常。 应该使用下面的代码: Queue使用lock示例 Queueq=newQueue(); lock(q.SyncRoot) { foreach(objectiteminq) { //dosomething } } 还有一点需要说明的是,集合类提供了一个是和同步相关的方法Synch
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- C# 拷贝数组的几种方式 拷贝 数组 方式
![提示](https://static.bdocx.com/images/bang_tan.gif)