C++编程人员轻易犯的10个C#错误

pew6

贡献于2013-10-20

字数:0 关键词: .NET开发 C# C/C++

C++编程人员轻易犯的 10 个C#错误 学习各种外挂制作技术,马上去百度搜索 """"魔鬼作坊"""" 点击第一个站进入、快 速成为做挂达人。 我们知道,C#的语法与 C++非常相似,实现从 C++向C#的转变,其困难不在于语言本身,而 在于熟悉。NET 的可治理环境和对。NET 框架的理解。尽管 C#与C++在语法上的变化是很小 的,几乎不会对我们有什么影响,但有些变化却足以使一些粗心的 C++编程人员时刻铭记在 心。在本篇文章中我们将讨论 C++编程人员最轻易犯的十个错误。 错误1: 没有明确的结束方法 几乎可以完全肯定地说,对于大多数 C++编程人员而言,C#与C++最大的不同之处就在 于碎片收集。这也意味着编程人员再也无需担心内存泄露和确保删除所有没有用的指针。但 我们再也无法准确地控制杀死无用的对象这个过程。事实上,在 C#中没有明确的 destructor. 假如使用非可治理性资源,在不使用这些资源后,必须明确地释放它。对资源的隐性控 制是由 Finalize 方法(也被称为 finalizer)提供的,当对象被销毁时,它就会被碎片收 集程序调用收回对象所占用的资源。finalizer 该当只释放被销毁对象占用的非可治理性资 源,而不应牵涉到其他对象。假如在程序中只使用了可治理性资源,那就无需也不应当执行 Finalize 方法,只要在非可治理性资源的处理中才会用到 Finalize 方法。由于 finalizer 需要占用一定的资源,因此应当只在需要它的方法中执行 finalizer.直接调用一个对象的 Finalize 方法是绝对不答应的(除非是在子类的 Finalize 中调用基础类的 Finalize.),碎 片收集程序会主动地调用 Finalize. 从语法上看,C#中的 destructor 与C++非常相似,但其实它们是完全不同的。C#中的 destructor 只是定义 Finalize 方法的捷径。因此,下面的二段代码是有区别的: ~MyClass() {// 需要完成的任务 } MyClass.Finalize() {// 需要完成的任务 base.Finalize(); } 错误2:Finalize 和Dispose 使用谁? 从上面的论述中我们已经很清楚,显性地调用 finalizer 是不答应的,它只能被碎片收 集程序调用。假如期望尽快地释放一些不再使用的数量有限的非可治理性资源(如文件句 柄),则该当使用 IDisposable 界面,这一界面有个 Dispose 方法,它能够帮你完成这个任 务。Dispose 是无需等待 Finalize 被调用而能够释放非可治理性资源的方法。 假如已经使用了 Dispose方法,则应当阻止碎片收集程序再对相应的对象执行 Finalize 方法。为此,需要调用静态方法 GC.SuppressFinalize,并将相应对象的指针传递给它作为 参数,Finalize 方法就能调用 Dispose 方法了。据此,我们能够得到如下的代码: public void Dispose() { // 完成清理操作 // 通知 GC 不要再调用 Finalize 方法 GC.SuppressFinalize(this); } public override void Finalize() { Dispose(); base.Finalize(); } 对于有些对象,可能调用 Close 方法就更合适(例如,对于文件对象调用 Close 就比 Dispose 更合适),可以通过创建一个 private 属性的 Dispose 方法和 public 属性的 Close 方法,并让 Close 调用 Dispose 来实现对某些对象调用 Close 方法。 由于不能确定一定会调用 Dispose,而且 finalizer 的执行也是不确定的(我们无法控 制GC会在何时运行),C#提供了一个 Using 语句来保证 Dispose 方法会在尽可能早的时间被 调用。一般的方法是定义使用哪个对象,然后用括号为这些对象指定一个活动的范围,当碰 到最内层的括号时,Dispose 方法就会被主动调用,对该对象进行处理。 using System.Drawing; class Tester { public static void Main() { using (Font theFont = new Font("Arial", 10.0f)) { // 使用 theFont对象 }// 编译器将调用 Dispose 处理 theFont对象 Font anotherFont = new Font("Courier",12.0f); using (anotherFont) { // 使用 anotherFont 对象 }// 编译器将调用 Dispose 处理 anotherFont 对象 } } 在本例的第一部分中,Font 对象是在 Using 语句中创建的。当 Using 语句结束时,系 统就会调用 Dispose,对 Font 对象进行处理。在本例的第二部分,Font 对象是在 Using 语 句外部创建的,在决定使用它时,再将它放在 Using 语句内,当Using 语句结束时,系统就 会调用 Dispose.Using 语句还能防止其他意外的发生,保证系统一定会调用 Dispose. 错误3:C#中的值型变量和援用型变量是有区别的 与C++一样,C#也是一种强类型编程语言。C#中的数据类型被分为了二大类:C#语言本 身所固有的数据类型和用户自定义数据类型,这一点也与 C++相似。 此外,C#语言还把变量分为值类型和援用类型。除非是被包括在一个援用类型中,值类 型变量的值保留在栈中,这一点与 C++中的变量非常相似。援用类型的变量也是栈的一种, 它的值是堆中对象的地址,与C++中的指针非常地相似。值类型变量的值被直接传递给方法, 援用型变量在被作为参数传递给方法时,传递的是索引。类和界面可以创建援用类变量,但 需要指出的是,结构数据类型是 C#的一种内置数据类型,同时也是一种值型的数据类型。 错误4:注重隐性的数据类型转换 Boxing 和unboxing 是使值型数据类型被当作索引型数据类型使用的二个过程。值型变 量可以被包装进一个对象中,然后再被解包回值型变量。包括内置数据类型在内的所有 C# 中的数据类型都可以被隐性地转化为一个对象。包装一个值型变量就会生成一个对象的实 例,然后将变量拷贝到实例中。 Boxing 是隐性的,假如在需要索引型数据类型的地方使用了值型数据类型的变量,值 型变量就会隐性地转化为索引型数据类型的变量。Boxing 会影响代码执行的性能,因此应 当尽量避免,尤其是在数据量较大的时候。 假如要将一个打包的对象转换回原来的值型变量,必须显性地对它进行解包。解包需要 二个步骤:首先对对象实例进行反省,确保它们是由值型的变量被包装成的;第二步将实例 中的值拷贝到值型变量中。为了确保解包成功,被解包的对象必须是通过打包一个值型变量 的值生成的对象的索引。 using System; public class UnboxingTest { public static void Main() { int i = 123; // 打包 object o = i; // 解包(必须是显性的) int j = (int) o; Console.WriteLine("j: {0}", j); } } 假如被解包的对象是无效的,或是一个不同数据类型对象的索引,就会产生 InvalidCastException 异外。 错误5:结构与对象是有区别的 C++中的结构与类差不多,唯一的区别是,在缺省状态下,结构的访问权限是 public, 其继续权限也是 public.一些 C++编程人员将结构作为数据对象,但这只是一个约定而非是 必须这样的。在 C#中,结构只是一个用户自定义的数据类型,并不能取代类。尽管结构也 支持属性、方法、域和操作符,但不支持继续和 destructor. 更重要的是,类是一种索引型数据类型,结构是值型数据类型。因此,结构在表达无需 索引操作的对象方面更有用。结构在数组操作方面的效率更高,而在集合的操作方面则效率 较低。集合需要索引,结构必须打包才适合在集合的操作中使用,类在较大规模的集合操作 中的效率更高。 错误6:虚方法必须被明确地覆盖 在C#语言中,编程人员在覆盖一个虚方法时必须显性地使用 override 关健字。假设一 个Window 类是由 A公司编写的,ListBox 和RadioButton 类是由 B公司的和编程人员在购 买的 A公司编写的 Window 类的基础上编写的,B公司的编程人员对包括 Window 类未来的变 化情况在内的设想知之甚少。假如 B公司的一位编程人员要在 ListBox 上添加一个 Sort 方 法: public class ListBox : Window { public virtual void Sort() {"} } 在A公司发布新版的 Window 类之前,这不会有任何问题。假如 A公司的编程人员也在 Window 类中添加了一个 Sort 方法。 public class Window {//" public virtual void Sort() {"} } 在C++中,Windows 类中的 Sort 方法将成为 ListBox 类中 Sort 方法的基础方法,在期 望调用 Windows 类中的 Sort 方法时,ListBox 类中的 Sort 方法就会被调用。在C#中,虚拟 函数总是被认为是虚拟调度的根。也就是说,一旦 C#发现一个虚拟的方法,就不会再在虚 拟链中查找其他虚拟方法。假如 ListBox 再次被编译,编译器就会生成一个警告信息: "class1.cs(54,24): warning CS0114: 'ListBox.Sort()' hides inherited member 'Window.Sort()'. 要使当前的成员覆盖原来的方法,就需要添加 override 关健字,或者添加 new 关健字。 要消除警告信息,编程人员必须搞清楚他想干什么。可以在 ListBox 类中的 Sort 方法 前添加 new,表明它不该当覆盖 Window 中的虚方法: public class ListBox : Window { public new virtual void Sort() {"} 这样就可以清除警告信息。假如编程人员确实期望覆盖掉 Window 中的方法,就必须使 用override 关健字来显性地表明其意图。 错误7:类成员变量的初始化 C#中的初始化与 C++中不同。假设有一个带有 private 性质的成员变量 age 的Person 类,Employee 是由继续 Person 类而生成的,它有一个 private 性质的 salaryLevel 成员变 量。在C++中,我们可以在 Employee 的构造器的初始化部分初始化 salaryLevel,如下面的 代码所示: Employee::Employee(int theAge, int theSalaryLevel): Person(theAge) // 初始化基础类 salaryLevel(theSalaryLevel) // 初始化成员变量 { // 构造器的代码 } 这种方法在 C#中是非法的。尽管仍然可以初始化基础类,但象上面的代码那样对成员 变量初始化就会引起编译错误。在 C#中,我们可以在定义成员变量时的同时对它进行初始 化: Class Employee : public Person {// 成员变量的定义 private salaryLevel = 3; // 初始化 } 注重:必须明确地定义每个变量的访问权限。 错误8:布尔型变量与整型变量是两回事儿 if( someFuncWhichReturnsAValue()) 在C#中,布尔型变量与整型变量并不相同,因此下面的代码是不正确的: if( someFuncWhichReturnsAValue() ) if someFuncWhichReturnsAValue 返回零表示 false,否则表示 true 的想法已经行不通 了。这样的好处是原来存在的将赋值运算与相等相混淆的错误就不会再犯了。因此下面的代 码: if ( x = 5 ) 在编译时就会出错,因为 x=5只是把5赋给了 X,而不是一个布尔值。 错误9:switch 语句中会有些语句执行不到 在C#中,假如一个 switch 语句执行了一些操作,则程序就可能不能执行到下一个语句。 因此,尽管下面的代码在 C++中是合法的,但在 C#中却不合法: switch (i) { case 4: CallFuncOne(); case 5: // 错误,不会执行到这里 CallSomeFunc(); } 要实现上面代码的目的,需要使用一个 goto 语句: switch (i) { case 4: CallFuncOne(); goto case 5; case 5: CallSomeFunc(); } 假如 case 语句不执行任何代码,则所有的语句都会被执行。如下面的代码: switch (i) { case 4: // 能执行到 case 5: // 能执行 到 case 6: CallSomeFunc(); } 错误10:C#中的变量要求明确地赋值 在C#中,所有的变量在使用前都必须被赋值。因此,可以在定义变量时不对它进行初 始化,假如在把它传递给一个方法前,必须被赋值。 假如只是通过索引向方法传递一个变量,并且该变量是方法的输出变量,这是就会带来 问题。例如,假设有一个方法,它返回当前时间的小时、分、秒,假如象下面这样编写代码: int theHour; int theMinute; int theSecond; timeObject.GetTime( ref theHour, ref theMinute, ref theSecond) 假如在使用 theHour、theMinute 和theSecond 这三个变量之前没有对它们进行初始化, 就会产生一个编译错误: Use of unassigned local variable 'theHour' Use of unassigned local variable 'theMinute' Use of unassigned local variable 'theSecond' 我们可以通过将这些变量初始化为0或其他对方法的返回值没有影响的值,以处理编译 器的这个小问题: int theHour = 0; int theMinute = 0; int theSecond = 0; timeObject.GetTime( ref theHour, ref theMinute, ref theSecond) 这样就有些太麻烦了,这些变量传递给 GetTime 方法,然后被改变而已。为了处理这一 问题,C#专门针对这一情况提供了 out 参数修饰符,它可以使一个参数无需初始化就可以被 援用。例如,GetTime 中的参数对它本身没有一点意义,它们只是为了表达该方法的输出。 在方法中返回之前,Out 参数中必须被指定一个值。下面是经过修改后的 GetTime 方法: public void GetTime(out int h, out int m, out int s) { h = Hour; m = Minute; s = Second; } 下面是新的 GetTime 方法的调用方法: timeObject.GetTime( out theHour, out theMinute, out theSecond);

下载文档,方便阅读与编辑

文档的实际排版效果,会与网站的显示效果略有不同!!

需要 5 金币 [ 分享文档获得金币 ] 0 人已下载

下载文档

相关文档