您好,欢迎访问三七文档
当前位置:首页 > 商业/管理/HR > 质量控制/管理 > 编写高质量代码改善C程序的157个建议
前言本文主要来学习记录前三个建议。建议1、正确操作字符串建议2、使用默认转型方法建议3、区别对待强制转换与as和is其中有很多需要理解的东西,有些地方可能理解的不太到位,还望指正。建议1、正确操作字符串字符串应该是所有编程语言中使用最频繁的一种基础数据类型。如果使用不慎,我们就会为一次字符串的操作所带来的额外性能开销而付出代价。本条建议将从两个方面来探讨如何规避这类性能开销:1、确保尽量少的装箱2、避免分配额外的内存空间先来介绍第一个方面,请看下面的两行代码:Stringstr1=str1+9;Stringstr2=str2+9.ToString();从IL代码可以得知,第一行代码在运行时完成一次装箱的行为,而第二行代码中并没有发生装箱的行为,它实际调用的是整型的ToString()方法,效率要比装箱高。所以,在使用其他值引用类型到字符串的转换并完成拼接时,应当避免使用操作符“+”来完成,而应该使用值引用类型提供的ToString()方法。第二方面,避免分配额外的内存空间。对CLR来说,string对象(字符串对象)是个很特殊的对象,它一旦被赋值就不可改变。在运行时调用System.String类中的任何方法或进行任何运算(如“=”赋值、“+”拼接等),都会在内存中创建一个新的字符串对象,这也意味着要为该新对象分配新的内存空间。像下面的代码就会带来运行时的额外开销。privatestaticvoidNewMethod1(){strings1=abc;s1=123+s1+456;////以上两行代码创建了3个字符串对象对象,并执行了一次string.Contact方法}privatestaticvoidNewMethod2(){stringre=9+456;////该方法发生了一次装箱,并调用一次string.Contact方法}关于装箱拆箱的问题大家可以查看我之前的文章而以下代码,字符串不会在运行时进行拼接,而是会在编译时直接生成一个字符串。privatestaticvoidNewMethod3(){stringre2=123+abc+456;///该代码等效于///stringre2=123abc456;}privatestaticvoidNewMethod4(){conststringa=t;stringre=abc+a;///因为a是一个常量,所以该代码等效于string=re=abc+t;最终等效于stringre=abct;}由于使用System.String类会在某些场合带来明显的性能损耗,所以微软另外提供了一个类型StringBuilder来弥补String的不足。StringBuilder并不会重新创建一个string对象,它的效率源于预先以非托管的方式分配内存。如果StringBuilder没有先定义长度,则默认分配的长度为16。当StringBuilder字符串长度小于等于16时,StringBuilder不会重新分配内存;当StringBuilder字符长度大于16小于32时,StringBuilder又会重新分配内存,使之成为16的倍数。在上面的代码中,如果预先判断字符串的长度将大于16,则可以为其设定一个更加合适的长度(如32)。StringBuilder重新分配内存时是按照上次容量加倍进行分配的。当然,我们需要注意,StringBuilder指定的长度要合适,太小了,需要频繁分配内存,太大了,浪费空间。查看以下代码,比较下面两种字符串拼接方式,哪种效率更高:privatestaticvoidNewMethod1(){=t;a+=e;a+=s;a+=t;}privatestaticvoidNewMethod2(){stringa=t;stringb=e;stringc=s;stringd=t;stringresult=a+b+c+d;}结果可以得知:两者的效率都不高。不要以为前者比后者创建的字符串对象更少,事实上,两者创建的字符串对象相等,且前者进行了3次string.Contact方法调用,比后者还多了两次。要完成这样的运行时字符串拼接(注意:是运行时),更佳的做法是使用StringBuilder类型,代码如下所示:publicstaticvoidNewMethod(){////定义了四个变量stringa=t;stringb=e;stringc=s;stringd=t;StringBuildersb=newStringBuilder(a);sb.Append(b);sb.Append(c);sb.Append(d);提示是运行时,所以没有使用以下代码//StringBuildersb=newStringBuilder(t);//sb.Append(e);//sb.Append(s);//sb.Append(t);//stringresult=sb.ToString();}微软还提供了另外一个方法来简化这种操作,即使用string.Format方法。string.Format方法在内部使用StringBuilder进行字符串的格式化,代码如下所示:publicstaticvoidNewMethod4(){stringa=t;stringb=e;stringc=s;stringd=t;stringresult=string.Format({0}{1}{2}{3},a,b,c,d);}对于String和StringBuilder的简单介绍也可以参考我之前的一篇文章建议2、使用默认转型方法1、使用类型的转换运算符,其实就是使用类型内部的一方方法(即函数)。转换运算符分为两类:隐式转换和显式转换(强制转换)。基元类型普遍都提供了转换运算符。所谓“基元类型”,是指编译器直接支持的数据类型。基元类型包括:sbyte、byte、short、ushort、int、uint、long、ulong、char、float、double、bool、decimal、object、string。inti=0;floatj=0;j=i;///int到float存在一个隐式转换i=(int)j;///float到int必须存在一个显式转换用户自定义的类型也可以通过重载转换运算符的方式提供这一类转换:publicclassIp{IPAddressvalue;publicIp(stringip){value=IPAddress.Parse(ip);}//重载转换运算符,implicit关键字用于声明隐式的用户定义类型转换运算符。publicstaticimplicitoperatorIp(stringip){Ipiptemp=newIp(ip);returniptemp;}//重写ToString方法publicoverridestringToString(){returnvalue.ToString();}}classProgram{publicstaticvoidMain(string[]args){Ipip=192.168.1.1;//通过Ip类的重载转换运算符,实现字符串到Ip类型的隐式转换Console.WriteLine(ip.ToString());Console.ReadLine();}}提供的就是字符串到类型Ip之间的隐式转换。2、使用类型内置的Parse、TryParse,或者如ToString、ToDouble、ToDateTime等方法比如从string转换为int,因为其经常发生,所以int本身就提供了Parse和TryParse方法。一般情况下,如果要对某类型进行转换操作,建议先查阅该类型的API文档。3、使用帮助类提供的方法可以使用System.Convert类、System.BitConverter类来进行类型的转换。System.Convert提供了将一个基元类型转换为其他基元类型的方法,如ToChar、ToBoolean方法等。值得注意的是,System.Convert还支持将任何自定义类型转换为任何基元类型,只要自定义类型继承了IConvertible接口就可以。如上文中的IP类,如果将Ip转换为string,除了重写Object的ToString方法外,还可以实现IConvertible的ToString()方法继承IConvertible接口必须同时实现其他转型方法,如上文的ToBoolean、ToByte,如果不支持此类转型,则应该抛出一个InvalidCastException,而不是一个NotImplementedException。4、使用CLR支持的转型CLR支持的转型,即上溯转型和下溯转型。这个概念首先是在Java中提出来的,实际上就是基类和子类之间的相互转换。就比如:动作Animal类、Dog类继承Animal类、Cat类也继承自Amimal类。在进行子类向基类转型的时候支持隐式转换,如Dog显然就是一个Animal;而当Animal转型为Dog的时候,必须是显式转换,因为Animal还可能是一个Cat。Animalanimal=newAnimal();Dogdog=newDog();animal=dog;/////隐式转换,因为Dog就是Animal///dog=animal;////编译不通过dog=(dog)animal;/////必须存在一个显式转换建议3、区别对待强制转换与as和is首先来看一个简单的实例FirstTypefirstType=newFirstType();SecondTypesecondType=newSecondType();secondType=(SecondType)firstType;从上面的三行代码可以看出,类似上面的应该就是强制转换。首先需要明确强制转换可能意味这两件不同的事情:1、FirstType和SecondType彼此依靠转换操作来完成两个类型之间的转换。2、FirstType是SecondType的基类。类型之间如果存在强制转换,那么它们之间的关系要么是第一种,要么是第二种。不可能同时是继承的关系,又提供了转型符。针对第一种情况:publicclassFirstType{publicstringName{get;set;}}publicclassSecondType{publicstringName{get;set;}publicstaticexplicitoperatorSecondType(FirstTypefirstType){SecondTypesecondType=newSecondType(){Name=转型自:+firstType.Name};returnsecondType;}}classProgram{staticvoidMain(string[]args){FirstTypefirstType=newFirstType(){Name=FirstType};SecondTypesecondType=(SecondType)firstType;///此转换是成功的secondType=firstTypeasSecondType;///编译不通过Console.ReadLine();}}这里上面也有添加注释,通过强制转换是可以转换成功的,但是使用as运算符是不成功的编译就不通过。这里就是通过转换符进行处理的结果。接下来我们再在Program类中添加一个方法staticvoidDoWithSomeType(objectobj){///编译器首先判断的是,SeondType和ojbect之间有没有继承关系。///因为在C#中,所有的类型都是继承自object的,所以这里编译没有什么问题。///但编译器会自动产生代码来检查obj在运行时是不是SecondType,这样就绕过了操作转换符,导致转换失败。SecondTypesecondType=(Se
本文标题:编写高质量代码改善C程序的157个建议
链接地址:https://www.777doc.com/doc-2140996 .html