科技行者

行者学院 转型私董会 科技行者专题报道 网红大战科技行者

知识库

知识库 安全导航

至顶网软件频道基础软件由C++转向C#需要注意的问题(2)

由C++转向C#需要注意的问题(2)

  • 扫一扫
    分享文章到微信

  • 扫一扫
    关注官方公众号
    至顶头条

C#提供了一个数组类,它比C/C++中传统的数组更智能化。例如,在C#中写数组时不会超出边界。此外,数组还有一个更智能的伙伴—ArrayList,可以动态地增长,管理对数组大小不断变化的需求

来源:soft6 2008年5月22日

关键字: 转向 C++ C# Windows

  • 评论
  • 分享微博
  • 分享邮件

数组

C#提供了一个数组类,它比C/C++中传统的数组更智能化。例如,在C#中写数组时不会超出边界。此外,数组还有一个更智能的伙伴—ArrayList,可以动态地增长,管理对数组大小不断变化的需求。 

C#中的数组有三种形式:一维数组、多维均匀数组(象C++中传统的数组那样)、非均匀数组(数组的数组)。我们可以通过下面的代码创建一维数组: 

int[]myIntArray=newint[5]; 

另外,还可以以如下的方式对它进行初始化: 

int[]myIntArray={2,4,6,8,10}; 

我们可以通过如下方式创建一个4×3的均匀数组: 

int[,]myRectangularArray=newint[rows,columns]; 

我们可以按如下方式对该数组进行初始化: 

int[,]myRectangularArray=

{

{0,1,2},{3,4,5},{6,7,8},{9,10,11}

};

 

由于非均匀数组是数组的数组,因此,我们只能创建一维非均匀数组: 

int[][]myJaggedArray=newint[4][]; 

然后再创建内部的每个数组: 

myJaggedArray[0]=newint[5];

myJaggedArray[1]=newint[2];

myJaggedArray[2]=newint[3];

myJaggedArray[3]=newint[5]; 

由于数组是由继承System.Array对象而得到的,因此,它们带有许多包括Sort、Reverse在内的许多有用的方法。 

索引器

我们可以创建象数组一样的对象。例如,我们可以创建一个显示一系列字符串的列表框,可以把列表框当作一个数组,使用一个索引就可以很方便地访问列表框中的内容。 

stringtheFirstString=myListBox[0];

stringtheLastString=myListBox[Length-1]; 

这是通过索引器完成的。索引器在很大程度上象一个属性,但支持索引操作的语法。图4显示了一个后面跟着索引操作符的属性,图5显示如何完成一个很简单的ListBox类并对它进行索引: 

界面

软件界面是二种对象之间如何进行交互的契约。如果一个对象发布了一个界面,就等于向所有可能的客户声明:我支持下面的方法、属性、事件和索引器。 

C#是一种面向对象的语言,因此这些契约被封装在一个被称作界面的实体中,界面定义了封装着契约的引用型类型的对象。从概念上来讲,界面与抽象类非常相似,二者的区别是抽象类可以作为一系列衍生类的基础类,界面则是与其他继承树结合在一起的。

IEnumerable界面

再回到上面的例子中。象在普通的数组中那样,使用foreach-loop循环结构就能够很好地打印ListBoxTest类中的字符串,通过在类中实现IEnumerable界面就能实现,这是由foreach-loop循环结构隐性地完成的。在任何支持枚举和foreach-loop循环的类中都可以实现IEnumerable界面。 

IEnumerable界面只有一个方法GetEnumerator,其任务是返回一个特别的IEnumerator的实现。从语法的角度来看,Enumerable类能够提供一个IEnumerator。 

Figure5ListBoxClass

usingSystem;

//简化的ListBox控制

publicclassListBoxTest

{

//用字符串初始化该ListBox

publicListBoxTest(paramsstring[]initialStrings)

{

//为字符串分配空间

myStrings=newString[256];

//把字符串拷贝到构造器中

foreach(stringsininitialStrings)

{

myStrings[myCtr++]=s;

}

}

//在ListBox的末尾添加一个字符串

publicvoidAdd(stringtheString)

{

myStrings[myCtr++]=theString;

}

publicstringthis[intindex]

{

get

{

if(index<0||index>=myStrings.Length)

{

//处理有问题的索引

}

returnmyStrings[index];

}

set

{

myStrings[index]=value;

}

}

//返回有多少个字符串

publicintGetNumEntries()

{

returnmyCtr;

}

privatestring[]myStrings;

privateintmyCtr=0;

}

publicclassTester

{

staticvoidMain()

{

//创建一个新的列表并初始化

ListBoxTestlbt=newListBoxTest("Hello","World");

//添加一些新字符串

lbt.Add("Who");

lbt.Add("Is");

lbt.Add("John");

lbt.Add("Galt");

stringsubst="Universe";

lbt[1]=subst;

//访问所有的字符串

for(inti=0;i
{

Console.WriteLine("lbt[{0}]:{1}",i,lbt[i]);

}

}

Enumerator必须实现IEnumerator方法,这可以直接通过一个容器类或一个独立的类实现,后一种方法经常被选用,因为它可以将这一任务封装在Enumerator类中,而不会使容器类显得很混乱。我们将在上面代码中的ListBoxTest中添加Enumerator类,由于Enumerator类是针对我们的容器类的(因为ListBoxEnumerator必须清楚ListBoxTest的许多情况),我们将使它在ListBoxTest中成为不公开的。在本例中,ListBoxTest被定义来完成IEnumerable界面,IEnumerable界面必须返回一个Enumerator。 

publicIEnumeratorGetEnumerator()

{

return(IEnumerator)newListBoxEnumerator(this);

注意,方法将当前的ListBoxTest对象(this)传递给Enumerator,这将使Enumerator枚举这一指定的ListBoxTest对象中的元素。 

实现这一类的Enumerator在这里被实现为ListBoxEnumerator,它在ListBoxTest中被定义成一个私有类,这一工作是相当简单的。 

被枚举的ListBoxTest作为一个参数被传递给constructor,ListBoxTest被赋给变量myLBT,构造器还会将成员变量index设置为-1,表明对象的枚举还没有开始。 

publicListBoxEnumerator(ListBoxTesttheLB)

{

myLBT=theLB;

index=-1;

MoveNext方法对index进行加1的操作,然后确保没有超过枚举的对象的边界。如果超过边界了,就会返回false值,否则返回true值。 

publicboolMoveNext()

{

index++;

if(index>=myLBT.myStrings.Length)

returnfalse;

else

returntrue;

Reset的作用仅仅是将index的值设置为-1。 

Current返回最近添加的字符串,这是一个任意的设定,在其他类中,Current可以有设计人员确定的意义。无论是如何设计的,每个进行枚举的方法必须能够返回当前的成员。 

publicobjectCurrent

{

get

{

return(myLBT[index]);

}

对foreach循环结构的调用能够获取枚举的方法,并用它处理数组中的每个成员。由于foreach循环结构将显示每一个字符串,而无论我们是否添加了一个有意义的值,我们将myStrings的初始化改为8个条目,以保证显示的易于处理。 

myStrings=newString[8]; 

使用基本类库

为了更好地理解C#与C++的区别和解决问题方式的变化,我们先来看一个比较简单的例子。我们将创建一个读取文本文件的类,并在屏幕上显示其内容。我将把它做成多线程程序,以便在从磁盘上读取数据时还可以做其他的工作。 

在C++中,我们可能会创建一个读文件的线程和另一个做其他工作的线程,这二个线程将各自独立地运行,但可能会需要对它们进行同步。在C#中,我们也可以完成同样的工作,由于.NET框架提供了功能强大的异步I/O机制,在编写线程时,我们会节省不少的时间。 

异步I/O支持是内置在CLR中的,而且几乎与使用正常的I/O流类一样简单。在程序的开始,我们首先通知编译器,我们将在程序中使用许多名字空间中的对象: 

usingSystem;

usingSystem.IO;

usingSystem.Text; 

在程序中包含System,并不会自动地包含其所有的子名字空间,必须使用using关健字明确地包含每个子名字空间。我们在例子中会用到I/O流类,因此需要包含System.IO名字空间,我们还需要System.Text名字空间支持字节流的ASCII编码。 

由于.NET架构为完成了大部分的工作,编写这一程序所需的步骤相当简单。我们将用到Stream类的BeginRead方法,它提供异步I/O功能,将数据读入到一个缓冲区中,当缓冲区可以处理时调用相应的处理程序。 

我们需要使用一个字节数组作为缓冲区和回叫方法的代理,并将这二者定义为驱动程序类的private成员变量。 

publicclassAsynchIOTester

{

privateStreaminputStream;

privatebyte[]buffer;

privateAsyncCallbackmyCallBack; 

inputStream是一个Stream类型的变量,我们将对它调用BeginRead方法。代理与成员函数的指针非常相似。代理是C#的第一类元素。 

当缓冲区被磁盘上的文件填满时,.NET将调用被代理的方法对数据进行处理。在等待读取数据期间,我们可以让计算机完成其他的工作。(在本例中是将1个整型变量由1增加到50000,但在实际的应用程序中,我们可以让计算机与用户进行交互或作其他有意义的工作。) 

本例中的代理被定义为AsyncCallback类型的过程,这是Stream的BeginRead方法所需要的。System空间中AsyncCallback类型代理的定义如下所示: 

publicdelegatevoidAsyncCallback(IAsyncResultar); 

这一代理可以是与任何返回void类型值、将IAsyncResult界面作为参数的方法相关联的。在该方法被调用时,CLR可以在运行时传递IAsyncResult界面对象作为参数。我们需要如下所示的形式定义该方法: 

voidOnCompletedRead(IAsyncResultasyncResult) 

然后在构造器中与代理连接起来: 

AsynchIOTester()

{

???

myCallBack=newAsyncCallback(this.OnCompletedRead);

上面的代码将代理的实例赋给成员变量myCallback。下面是全部程序的详细工作原理。在Main函数中,创建了一个类的实例,并让它开始运行: 

publicstaticvoidMain()

{

AsynchIOTestertheApp=newAsynchIOTester();

theApp.Run();

new关健字能够启动构造器。在构造器中我们打开一个文件,并得到一个Stream对象。然后在缓冲中分配空间并与回调机制联结起来。 

AsynchIOTester()

{

inputStream=File.OpenRead(@"C:\MSDN\fromCppToCS.txt");

buffer=newbyte[BUFFER_SIZE];

myCallBack=newAsyncCallback(this.OnCompletedRead);

在Run方法中,我们调用了BeginRead,它将以异步的方式读取文件。 

inputStream.BeginRead(

buffer,//存放结果

0,//偏移量

buffer.Length,//缓冲区中有多少字节

myCallBack,//回调代理

null);//本地对象 

这时,我们可以完成其他的工作。 

for(longi=0;i<50000;i++)

{

if(i%1000==0)

{

Console.WriteLine("i:{0}",i);

}

文件读取操作结束后,CLR将调用回调方法。 

voidOnCompletedRead(IAsyncResultasyncResult)

在OnCompletedRead中要做的第一件事就是通过调用Stream对象的EndRead方法找出读取了多少字节: 

intbytesRead=inputStream.EndRead(asyncResult); 

对EndRead的调用将返回读取的字节数。如果返回的数字比0大,则将缓冲区转换为一个字符串,然后将它写到控制台上,然后再次调用BeginRead,开始另一次异步读的过程。 

if(bytesRead>0)

{

Strings=Encoding.ASCII.GetString(buffer,0,bytesRead);

Console.WriteLine(s);

inputStream.BeginRead(buffer,0,buffer.Length,

myCallBack,null);

现在,在读取文件的过程中就可以作别的工作了(在本例中是从1数到50000),但我们可以在每次缓冲区满了时对读取的数据进行处理(在本例中是向控制台输出缓冲区中的数据)。有兴趣的读者可以点击此处下载完整的源代码。 

异步I/O的管理完全是由CLR提供的,这样,在网络上读取文件时,会更好些。

    • 评论
    • 分享微博
    • 分享邮件
    邮件订阅

    如果您非常迫切的想了解IT领域最新产品与技术信息,那么订阅至顶网技术邮件将是您的最佳途径之一。

    重磅专题
    往期文章
    最新文章