Top Ten Traps in C# for C++ Programmers中文版(轉)(2)
來源:互聯網
上載者:User
c++|中文 陷阱六.虛方法必須被顯式重載
在C#中,如果程式員決定重載一個虛方法,他(她)必須顯式使用override關鍵字。
讓我們考察一下這樣做的好處。假定公司A寫了一個Window類,公司B購買了公司A的Window類的一個拷貝作為基類。公司B的程式員從中派生【譯註:原文為...using...,從下文來看,顯然是“派生”之意。事實上,使用類的方式還有“組合”(也有說為“嵌入”或“包容”(COM語義)等等),後者不存在下文所描述的問題】出ListBox類和RadioButton類。公司B的程式員不知道或不能控制Window類的設計,包括公司A將來對Window類可能做的修改。
現在假定公司B的程式員決定為ListBox類加入一個Sort方法:
public class ListBox : Window
{
public virtual void Sort() {}
}
這是沒有問題的—直到公司A的Window類作者發布了Window類的版本2,公司A的程式員向Window類也加入了一個public的Sort方法:
public class Window
{
public virtual void Sort() {}
}
在C++中,Window類新的虛方法Sort將會作為ListBox虛方法的基類方法。當你試圖調用Window的Sort時,實際上調用的是ListBox的Sort。C#中虛方法【譯註:原文寫成virtual function】永遠被認為是虛擬調度的根。這就是說,只要C#找到了一個虛方法,它就不會再沿著繼承層次進一步尋找了,如果一個新的Sort虛方法被引入Window,ListBox的運行時行為不會被改變。當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關鍵字即可。
陷阱七:不可以在頭部進行初始化
C#裡的初始化不同於C++。假定你有一個類Person,它有一個私人成員變數age;一個衍生類別Employee,它有一個私人成員變數salaryLeverl。在C++中,你可以在Employee構造器的成員初始化列表部分初始化salaryLevel:
Employee::Employee(int theAge, int theSalaryLevel):
Person(theAge) // 初始化基類
salaryLevel(theSalaryLevel) // 初始化成員變數
{
// 構造器體
}
在C#中,這個構造器是非法的。儘管你仍可以如此初始化基類,但對成員變數的初始化將導致一個編譯時間錯誤。你可以在成員變數聲明處對其賦初始值:
Class Employee : public Person
{
// 在這兒聲明
private salaryLevel = 3; //初始化
}
【譯註:以上代碼有誤LC#中,正確寫法如下:
class Employee: Person
{
private int salaryLevel = 3;
}
】
你不需要在每一個類聲明的後面都加上一個分號。每一個成員都必須要有顯式的存取層級聲明。
陷阱8.不能把布爾值轉換為整型值
在C#中,布爾值(true、false)不同於整型值。因此,不能這麼寫:
if ( someFuncWhichReturnsAValue() )//【譯註:假定這個方法不返回布爾值】
也不能指望如果someFuncWhichReturnsAValue返回一個0它將等於false,否則為true。一個好訊息是誤用賦值操作符而不是相等操作符的老毛病不會再犯了。因此,如果這麼寫:
if ( x = 5 )
將會得到一個編譯時間錯誤,因為x = 5的結果為5,而它不是布爾值。
【譯註:以下是C++裡一不小心會犯的邏輯錯誤,編譯器不會有任何提示L運行得很順暢,不過結果並不是你想要的:
C++:
#include "stdafx.h"
int main(int argc, char* argv[])
{
int n = 0;
if (n = 1)//編譯器啥都沒說L一般推薦寫為1 == n,萬一寫成1 = n編譯器都不同意J
{
printf("1\n");
}
else
{
printf("0\n");
}
return 0;
}
以上運行結果為1,這未必是你想要的。
C#:
using System;
public class RyTestBoolApp
{
public static void Main()
{
int n = 0;
if (n = 1)//編譯器不同意J無法將int轉換成bool
{
Console.WriteLine("1");
}
else
{
Console.WriteLine("0");
}
}
}
但如果是這種情況:
bool b = false;
if (b = true)
...
不管是C++還是C#都沒招L
】
【譯註:C++程式員一般是喜歡這種自由的寫法:
if (MyRef)
if (MyInt)
但在C#裡,必須寫成:
if (MyRef != null)//或if (null != MyRef)
if (MyInt != 0)//或if (0 != MyInt)
等。
】
陷阱九.switch語句不可“貫穿”【譯註:即fall through,Beta2的聯機文檔就是如此譯法】
在C#中,如果在case語句裡有代碼的話,那它就不可“貫穿”到下一句。因此,儘管下面代碼在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();
}
【譯註:以下是使用switch的完整例子,它還說明了switch語句的參數類型可以是字串,此例同時還示範了屬性的使用方法。
using System;
class RySwitchTest
{
public RySwitchTest(string AStr)
{
this.StrProperty = AStr;
}
protected string StrField;
public string StrProperty
{
get
{
return this.StrField;
}
set
{
this.StrField = value;
}
}
public void SwitchStrProperty()
{
switch (this.StrProperty)
{
case ("ry01"):
Console.WriteLine("ry01");
break;
case ("ry02"):
Console.WriteLine("ry02");
break;//如果這一行注釋掉,編譯器會報控制不能從一個case標籤(case "ry02":)貫穿到另一個標籤,如果你確實需要,可以這麼寫:goto case ("ry03");或goto default。
case ("ry03"):
Console.WriteLine("ry03");
break;
default:
Console.WriteLine("default");
break;
}
}
}
class RySwitchTestApp
{
public static void Main()
{
RySwitchTest rst = new RySwitchTest("ry02");
rst.SwitchStrProperty();
}
}
】
陷阱十.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型的,就避免了在方法外初始化它們的需要。但當從被傳入的方法返回時,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);
【譯註:完整樣本如下:
C#:[例1:使用ref修飾的方法參數]
using System;
class RyRefTest
{
public RyRefTest()
{
this.IntField = 1;
this.StrField = "StrField";
}
protected int IntField;
protected string StrField;
public void GetFields(ref int AInt, ref string AStr)
{
AInt = this.IntField;
AStr = this.StrField;
}
}
class RyRefTestApp
{
public static void Main()
{
RyRefTest rrt = new RyRefTest();
int IntVar = 0;//如果是int IntVar; 編譯器會報使用了未賦值的變數IntVar
string StrVar = "0";//如果是string StrVar; 編譯器會報使用了未賦值的變數StrVar
rrt.GetFields(ref IntVar, ref StrVar);
Console.WriteLine("IntVar = {0}, StrVar = {1}", IntVar, StrVar);
}
}
C#:[例2:使用out修飾的方法參數]
using System;
class RyRefTest
{
public RyRefTest()
{
this.IntField = 1;
this.StrField = "StrField";
}
protected int IntField;
protected string StrField;
public void GetFields(out int AInt, out string AStr)
{
AInt = this.IntField;
AStr = this.StrField;
}
}
class RyRefTestApp
{
public static void Main()
{
RyRefTest rrt = new RyRefTest();
int IntVar;//這樣就可以了,如果寫成int IntVar = 0;當然也沒問題J
string StrVar; //這樣就可以了,如果寫成string StrVar = "0";當然也沒問題J
rrt.GetFields(out IntVar, out StrVar);
Console.WriteLine("IntVar = {0}, StrVar = {1}", IntVar, StrVar);
}
}
】