C#CopyTo.docx
《C#CopyTo.docx》由会员分享,可在线阅读,更多相关《C#CopyTo.docx(29页珍藏版)》请在冰豆网上搜索。
C#CopyTo
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数组对象是引用对象,在进行赋值的时候,实际上返回的是源对象的另一份引用而已;因此如果要对数组对象进行真正的复制(深拷贝),那么需要新建一份数组对象,然后将源数组的值逐一拷贝到目的对象中
3.C/C++中Static的作用详述
在C语言中,static的字面意思很容易把我们导入歧途,其实它的作用有三条。
(1)先来介绍它的第一条也是最重要的一条:
隐藏。
当我们同时编译多个文件时,所有未加static前缀的全局变量和函数都具有全局可见性。
为理解这句话,我举例来说明。
我们要同时编译两个源文件,一个是a.c,另一个是main.c。
下面是a.c的内容
chara='A';//globalvariable
voidmsg()
{
printf("Hello\n");
}
下面是main.c的内容
intmain(void)
{
externchara;//externvariablemustbedeclaredbeforeuse
printf("%c",a);
(void)msg();
return0;
}
程序的运行结果是:
AHello
你可能会问:
为什么在a.c中定义的全局变量a和函数msg能在main.c中使用?
前面说过,所有未加static前缀的全局变量和函数都具有全局可见性,其它的源文件也能访问。
此例中,a是全局变量,msg是函数,并且都没有加static前缀,因此对于另外的源文件main.c是可见的。
如果加了static,就会对其它源文件隐藏。
例如在a和msg的定义前加上static,main.c就看不到它们了。
利用这一特性可以在不同的文件中定义同名函数和同名变量,而不必担心命名冲突。
Static可以用作函数和变量的前缀,对于函数来讲,static的作用仅限于隐藏,而对于变量,static还有下面两个作用。
static定义的函数或变量是属于整个类的,不属于某个对象,调用是要用[类名]:
:
[函数名or变量名]
(2)static的第二个作用是保持变量内容的持久。
存储在静态数据区的变量会在程序刚开始运行时就完成初始化,也是唯一的一次初始化。
共有两种变量存储在静态存储区:
全局变量和static变量,只不过和全局变量比起来,static可以控制变量的可见范围,说到底static还是用来隐藏的。
虽然这种用法不常见,但我还是举一个例子。
#include
intfun(void){
staticintcount=10;//事实上此赋值语句从来没有执行过
returncount--;
}
intcount=1;
intmain(void)
{
printf("global\t\tlocalstatic\n");
for(;count<=10;++count)
printf("%d\t\t%d\n",count,fun());
return0;
}
程序的运行结果是:
globallocalstatic
110
29
38
47
56
65
74
83
92
101
(3)static的第三个作用是默认初始化为0。
其实全局变量也具备这一属性,因为全局变量也存储在静态数据区。
在静态数据区,内存中所有的字节默认值都是0x00,某些时候这一特点可以减少程序员的工作量。
不妨做个小实验验证一下。
#include
inta;
intmain(void)
{
inti;
staticcharstr[10];
printf("integer:
%d;string:
(begin)%s(end)",a,str);
return0;
}
程序的运行结果如下
integer:
0;string:
(begin)(end)
最后对static的三条作用做一句话总结。
首先static的最主要功能是隐藏,其次因为static变量存放在静态存储区,所以它具备持久性和默认值0。
4.switch中接受的合法参数有哪些类型
int
char
byte
short
long
enum
5.引用和指针
★相同点:
1.都是地址的概念;
指针指向一块内存,它的内容是所指内存的地址;引用是某块内存的别名。
★区别:
1.指针是一个实体,而引用仅是个别名;
2.引用使用时无需解引用(*),指针需要解引用;
3.引用只能在定义时被初始化一次,之后不可变;指针可变;
引用“从一而终”^_^
4.引用没有const,指针有const,const的指针不可变;
5.引用不能为空,指针可以为空;
6.“sizeof引用”得到的是所指向的变量(对象)的大小,而“sizeof指针”得到的是指针本身(所指向的变量或对象的地址)的大小;
typeid(T)==typeid(T&)恒为真,sizeof(T)==sizeof(T&)恒为真,
但是当引用作为成员时,其占用空间与指针相同(没找到标准的规定)。
7.指针和引用的自增(++)运算意义不一样;
★联系
1.引用在语言内部用指针实现(如何实现?
)。
2.对一般应用而言,把引用理解为指针,不会犯严重语义错误。
引用是操作受限了的指针(仅容许取内容操作)。
6.memset,memcpy和strcpy的根本区别?
Memset
用来对一段内存空间全部设置为某个字符,一般用在对定义的字符串进行初始化为‘’或‘\0’;
例:
chara[100];memset(a,'\0',sizeof(a));
memset可以方便的清空一个结构类型的变量或数组,如:
structsample_struct
{
char csName[16];
int iSeq;
int iType;
};
对于变量
structsample_strcut stTest;
一般情况下,清空stTest的方法:
stTest.csName[0]='\0';
stTest.iSeq=0;
stTest.iType=0;
用memset就非常方便:
memset(&stTest,0,sizeof(structsample_struct));
如果是数组:
structsample_struct TEST[10];
则
memset(TEST,0,sizeof(structsample_struct)*10);
memcpy
用来做内存拷贝,你可以拿它拷贝任何数据类型的对象,可以指定拷贝的数据长度。
例:
chara[100],b[50];memcpy(b,a,sizeof(b));注意如用sizeof(a),会造成b的内存地址溢出。
Strcpy
就只能拷贝字符串了,它遇到'\0'就结束拷贝。
例:
chara[100],b[50];strcpy(a,b);如用strcpy(b,a),要注意a中的字符串长度(第一个‘\0’之前)是否超过50位,如超过,则会造成b的内存地址溢出。
str也可以用用个参数的strncpy(a,b,n)
========================================================
memset主要应用是初始化某个内存空间。
memcpy是用于copy源空间的数据到目的空间中。
strcpy用于字符串copy,遇到‘\0’,将结束。
如果你理解了这些,你应该知道他们的区别:
例如你初始化某块空间的时候,用到memcpy,那么应该怎么写,是不是显得很笨。
int m[100]
memset((void*)m,0x00,sizeof(int)*100); //=intm[100]={0}; //Ok!
memcpy((void*)m,"\0\0\0\0....",sizeof(int)*100); //it’swrong.
//注:
Win32编程:
ZeroMemory(buf,sizeof(buf))来清空内存更方便;
7.解释一下const
关于C++中的const关键字的用法非常灵活,而使用const将大大改善程序的健壮性,现将本人的一些体会总结如下,期望对大家有所帮助:
一const基础
如果const关键字不涉及到指针,我们很好理解,下面是涉及到指针的情况:
intb=500;
constint*a=&b;[1]
intconst*a=&b;[2]
int*consta=&b;[3]
constint*consta=&b;[4]
如果你能区分出上述四种情况,那么,恭喜你,你已经迈出了可喜的一步。
不知道,也没关系,我们可以参考《Effectivec++》Item21上的做法,如果const位于星号的左侧,则const就是用来修饰指针所指向的变量,即指针指向为常量;如果const位于星号的右侧,const就是修饰指针本身,即指针本身是常量。
因此,[1]和[2]的情况相同,都是指针所指向的内容为常量(const放在变量声明符的位置无关),这种情况下不允许对内容进行更改操作,如不能*a=3;[3]为指针本身是常量,而指针所指向的内容不是常量,这种情况下不能对指针本身进行更改操作,如a++是错误的;[4]为指针本身和指向的内容均为常量。
另外const的一些强大的功能在于它在函数声明中的应用。
在一个函数声明中,const可以修饰函数的返回值,或某个参数;对于成员函数,还可以修饰是整个函数。
有如下几种情况,以下会逐渐的说明用法:
A&operator=(constA&a);
voidfun0(constA*a);
voidfun1()const;//fun1()为类成员函数
constAfun2();
二const的初始化
先看一下const变量初始化的情况
1)非指针const常量初始化的情况:
Ab;
constAa=b;
2)指针(引用)const常量初始化的情况:
A*d=newA();
constA*c=d;
或者:
constA*c=newA();
引用:
Af;
constA&e=f;//这样作e只能访问声明为const的函数,而不能访问一般的成员函数;
[思考1]:
以下的这种赋值方法正确吗?
constA*c=newA();
A*e=c;
[思考2]:
以下的这种赋值方法正确吗?
A*constc=newA();
A*b=c;
三作为参数和返回值的const修饰符
其实,不论是参数还是返回值,道理都是一样的,参数传入时候和函数返回的时候,初始化const变量
1修饰参数的const,如voidfun0(constA*a);voidfun1(constA&a);
调用函数的时候,用相应的变量初始化const常量,则在函数体中,按照const所修饰的部分进行常量化,如形参为constA*a,则不能对传递进来的指针的内容进行改变,保护了原指针所指向的内容;如形参为constA&a,则不能对传递进来的引用对象进行改变,保护了原对象的属性。
[注意]:
参数const通常用于参数为指针或引用的情况;
2修饰返回值的const,如constAfun2();constA*fun3();
这样声明了返回值后,const按照"修饰原则"进行修饰,起到相应的保护作用。
constRationaloperator*(constRational&lhs,constRational&rhs)
{
returnRational(lhs.numerator()*rhs.numerator(),
lhs.denominator()*rhs.denominator());
}
返回值用const修饰可以防止允许这样的操作发生:
Rationala,b;
Radionalc;
(a*b)=c;
一般用const修饰返回值为对象本身(非引用和指针)的情况多用于二目操作符重载函数并产生新对象的时候。
[总结]一般情况下,函数的返回值为某个对象时,如果将其声明为const时,多用于操作符的重载。
通常,不建议用const修饰函数的返回值类型为某个对象或对某个对象引用的情况。
原因如下:
如果返回值为某个对象为const(constAtest=A实例)或某个对象的引用为const(constA&test=A实例),则返回值具有const属性,则返回实例只能访问类A中的公有(保护)数据成员和const成员函数,并且不允许对其进行赋值操作,这在一般情况下很少用到。
[思考3]:
这样定义赋值操作符重载函数可以吗?
constA&operator=(constA&a);
四类成员函数中const的使用
一般放在函数体后,形如:
voidfun()const;
如果一个成员函数的不会修改数据成员,那么最好将其声明为const,因为const成员函数中不允许对数据成员进行修改,如果修改,编译器将报错,这大大提高了程序的健壮性。
例如:
constclassobj;
obj.fun(); //isok
如果没加const在fun()后面,则会出错!
五使用const的一些建议
1要大胆的使用const,这将给你带来无尽的益处,但前提是你必须搞清楚原委;
2要避免最一般的赋值操作错误,如将const变量赋值,具体可见思考题;
3在参数中使用const应该使用引用或指针,而不是一般的对象实例,原因同上;
4const在成员函数中的三种用法(参数、返回值、函数)要很好的使用;
5不要轻易的将函数的返回值类型定为const;
6除了重载操作符外一般不要将返回值类型定为对某个对象的const引用;
[思考题答案]
1这种方法不正确,因为声明指针的目的是为了对其指向的内容进行改变,而声明的指针e指向的是一个常量,所以不正确;
2这种方法正确,因为声明指针所指向的内容可变;
3这种做法不正确;
在co