渐进分析与大O表示法

渐进分析

    对于y=C1n和y=C2n2这两个式子。参数的不同只会导致两条曲线相交点的坐标不同,而不会改变他们是否相交,以及相交后的趋势。类似地,两个增长率不同的算法的时间曲线总会相交,而与系数无关。因此,当我们在估算一种算法的时间或者其他代价的时候,会经常忽略其参数。这样能简化算法分析,并使注意力集中在最重要的一点上,即增长率。这称为渐进算法分析。准确的说,渐进分析是指当输入规模很大,或者说达到极限(微积分意义上)时,对一种算法的研究。实践证明忽略这些系数很有用,因此渐进分析也广泛用于算法比较。

     但是并不是任何情况都能忽略常数。当算法要解决问题的规模很小时,系数就会起到举足轻重的作用。例如,要对5个数排序,那么用来给成千上万个数排序的算法可能就很不适合,尽管它的渐进分析表明该算法的性能良好。

     渐进分析是对算法资源开销的一种不精确的估算,千万不要忘记渐进分析的局限性,尤其是在那些系数也起到重要作用的少数情况下。

大O表示法

    如果某种算法的增长率上限(最差情况下)是f(n),那么就说这种算法在集合O(f(n))中,或者直接说在O(f(n))中,例如,如果在最差情况下T(n)增长速度与n2相同,则称算法最差情况在O(n2)中。下面是精确的定义:

     对非负函数T(n),若存在两个正常数c和n0,对任意n>n0,有T(n)<=cf(n),则称,T(n)在集合O(f(n))中。

     要知道,某种算法在O(f(n))中只是说明事情顶多能坏到某种程度。事实上并不是那么糟。因此我们总是试图给算法的时间代价找到一个最紧的上限。比如说顺序搜索法在O(n)中,那么根据定义,我们也可以说它也在 O(n2)中。但是顺序搜索法对于很大的n也是可行的。其他在O(n2)中的算法就未必如此。

     例1 比较下面两段程序的算法分析:

sum1=0;

for(i=1;i<=n;i++)

  for(j=1;j<=n;j++)

    sum1++;

 

sum2=0;

for(i=1;i<=n;i++)

  for(j=1;j<=i;j++)

    sum2++;

       在第一个双重熏黄中,内层for循环总是执行n次。因为外层循环执行n次,所以sum1++正好执行n2次。而第二个例子,时间代价为j从1加到n,近似于0.5n2

例2 并非所有嵌套for循环的时间代价都是n2,以下面的两个嵌套为例。

sum1=0;

for(k=1;k<=n;k*=2)

  for(j=1;j<=n;j++)

    sum1++;

sum2=0;

for(k=1;k<=n;k*=2)

  for(j=1;j<=k;j++)

    sum2++;

为了便于分析,我们假定n是2的幂。第一段程序的外层for循环执行log(n)次。由于内层循环执行次数为n,所以程序的总时间为i从0加到log(n),即为O(nlog(n))。第二段程序的外层循环执行执行log(n)次,内层循环重复k次,并随这外层循环的增长而倍增。时间开销为2i,其中i从0增到log(n),其合为O(n).

发表在 Data Structure | 留下评论

[转]西北唢呐之悲风

转自http://blog.sina.com.cn/s/blog_4958159e01009pt3.html

美文,情不自禁转了,在此致谢!

 

莽莽高原,唢呐声咽,歌闻四野,兀鹰高飞。

唢呐,据说公元三世纪从中亚传来,金元时期始入中原,至于何时开始在甘肃一带流行,并无确切的史料记载,更何

况,宋至金元,秦陇之地轮番易手吐蕃,党项与宋朝之间,往去西域的丝路商道被冷落百年之久,文化的交流也亦阻

隔,这从唐末至元,秦陇一带寥寥无几的文献记载便可知其一二,那么唢呐之声因何遍布陇上,或许要归功于那些尊

崇佛教的吐蕃贵族和四处弘法的吐蕃喇嘛们,他们也许是最早从河西走廊的甘州回鹘人那里学到了这个乐器,并逐渐

带入藏地,初,用于沙场以壮军威,后又成礼佛之法器,而被侵占的河陇各地,则也自然而然吹起了唢呐独特的调子

,并在黄土高原上一吹就是几千年,到今天甘陇一带民间唢呐无论从形制还是吹奏的曲牌,几乎与藏式唢呐,也就是

藏语里所说的“甲林”无异。

听唢呐的时候,经常会有一种错觉,一种时空的错位感,似乎只有它那种独特的音调才能真正阐述出古凉州,古陇右

道诸地的精魄,边城塞外,旌旗猎猎,连天烽火,唢呐声声,战死疆场的悲壮与西北高原的雄浑苍凉无休止的交织在

一起,或悲怆激昂,或哀怨缠绵。无论是婚丧还是嫁娶,无论悲还是喜,都显得那么嘹亮悠扬、荡气回肠,这也许本

身就是一种根植在西北人骨子里头,看待生命的独特方式。丧调里,吹者,双眼紧闭,两腮鼓起,血管暴涨,那一声

声的调子里,有哀有叹,有哭有诉,婉转激越,撕肝裂肺,在呜呜咽咽中,死亡显得如此的悲壮和令人敬畏。如果说

要用什么乐器来为亡者做最后的生命挽歌,除了唢呐之外似乎无有其他。经常会在眼前浮出一个画面来,秋日,在荒

草蔓烟,满是沟壑的黄土高原上,纷纷扬扬的纸钱,浩浩荡荡的人群,哭天抢地的哀号,再加上唢呐那无与伦比尖利

的悲音,那种交织着凄惶与悲切的,如潮般的大悲大恸令天地动容,如此的壮阔,又如此的悲壮。也许惟有唢呐的调

子才能引领亡魂归息,也许只有它才配得为生命的高贵做最后的祭奠。

翻汉书、唐史、五代史、宋史,时不时会在脑海里蹦出一个忽隐忽现的问题来,为何偏偏唢呐与西北黄土地相结合后

便将其发挥的如此阳刚,痛快淋漓,百折不回。那种钢硬质野、宽广浑厚的劲足壮气又源于何处。河陇各地从秦襄王

二十七年(公元前280年)置陇西郡始,至唐贞观元年(687年)设陇右道。经过汉王朝与匈奴长期的战争,以及李唐王

朝的苦心经营,中央政府对西北的用兵推进至西域,关陇一带成为大汉、大唐内陆属地。此时的陇上也早已是胡汉和

睦杂处,游牧文化与农耕文化并存。而这两种文化的撞击和融合,使得东望关陕,西眺河洮,南走川蜀,北通宁朔的秦陇

一带孕育出了独特的民风及人文气质,勇猛强悍、宽厚侠义,名臣将相层出不穷,忠烈候纪信,营平候赵充国,飞将

军李广,朔宁王隗嚣,平襄侯姜维,以及那个“左手奋七尺大刀,右手执丈八蛇矛,近交则刀矛俱发,辄害五六”的

壮士陈安。陈安死后,陇上父老莫不悲泣,做《壮士歌》来纪念这位陇上豪杰,歌云:“陇上壮士有陈安,驱干虽小

腹中宽,爱养将士同心肝。聂骢父马铁瑕鞍,七尺大刀奋如湍,丈八蛇矛左右盘,十荡十决无当前。战始三交失蛇矛

,弃我聂骢窜严幽,为我外援而悬头。西流之水东流河,一去不还奈子何!”也许是英雄惜英雄,杀害他的刘曜“闻

而嘉伤,命乐府歌之。”使得《壮士歌》与《项羽吟》一起,成为南北朝时期京师闾里相为传唱的名曲。

这种自秦汉绵延千年,并深深根植于陕甘黄土高原血脉中的彪悍与朴实,使得唢呐一出现就死死抓住了他们的灵魂。

在西北各处遗留至今的唢呐曲牌数不胜数,以甘谷为例,主要曲牌就有《大开门》、《升官图》、《朝天子》、《将

军令》、《姜维招魂曲》等几十种之多,而其中尤以祭奠古之壮士,英烈为著。于高亢里,时而婉转低吟,时而粗狂

雄壮,时而古朴深沉,曲曲无词皆有词,声声是歌亦是泪。历千年,杀伐之声慢慢褪去,唢呐还是一如既往的陪伴着

那些世世代代,在黄土地上辛勤耕耘着历史的人们,于艰难困苦中,一声声高亢近乎于呐喊的悲音,不断的雕琢和沁

透着历史的厚重与苍茫,痛苦中含着泪水微笑着,那种历万千劫难后的洒脱与达观,那种悲情中透着的敞亮劲,这是

唢呐的馈赠,是唢呐的声音不断的演绎和见证着那片神奇的土地上所发生的一切。那些深埋于西北黄土地下,厚重的

心灵哲思,在唢呐的喉管里缓缓流淌,静静诉说,于千年而绵延不绝。唢呐声声吹透了西北黄土的厚重,同样,西北

之地的粗犷苍凉也成就了唢呐那无与伦比的高原悲音。

发表在 音乐 | 留下评论

[chapter 13]Debugging

13.1 Debugging:Remove errors

 

bug可以分为编译错误和运行时错误。

编译错误又分为两个阶段。第一阶段是从C++文件转为.o文件时发生错误,这个时候发生的错误主要有两种:

  1. 错误的C++语法
  2. 使用未定义的变量或函数

这都会使编译器不能理解C++代码,从而导致错误。

第二阶段是编译器链接.o文件生成可运行的ns文件时发生错误。一般有:

  1. 把抽象类实例化。这会导致有未被实现的纯虚函数。解决方法是实现这些纯虚函数。最简单(但不一定正确)的方法是加一个{}。
  2. 在没有创建子类.o文件时修改基类。通常是由于Makefiles中的文件依存关系没有正确的定义。当某个class修改以后,编译器没有修改它的子类从而导致错误。解决方法是正确的定义Makefiles里的文件依存关系,或者在编译代码之前去掉所有相关的.o文件。

注意OTcl是脚本语言,并不需要在执行前进行编译。所以在OTcl域中没有编译错误。

运行时错误

运行时错误出现在NS2运行时。原因是不合适的OTcl或者C++编程。由于OTcl有追错机制,所以OTcl的错误警告清晰且精确,而C++则不然(例如段错误)。

OTcl的错误通常有:

  1. 错误的OTcl语法
  2. 相关实例变量,实例程序或者命令不存在

C++的运行是错误包括:

  1. 段错误:通常由于对内存的无效访问。例如,在只定义了a[3]的时候,试图去访问a[6].
  2. 没有实现某个强制函数。除了使用纯虚函数,NS2提供了强制函数来迫使子函数实现某个函数。

Tracing and Result Compilation 略

发表在 NS2 | 留下评论

[chapter 12] Related Helper Classes

“帮助类”通常不是网络的一部分,但他们通常用于NS2的内部机制或者是特殊情况下的网络部件。本章讲述3个主要的帮助类:Timer,random number Generate,ErrorModel。

本人只关注了Timer帮助类

Timer

image

 

上图是Timer的状态图。当我们生成了一个Timer时,它默认为IDLE状态。要使用这个Timer,我们必须设定它的WAITING TIME.然后Timer转到WAITING状态。WAITING时间一到,状态转到EXPIRED状态,执行Timer所定义的动作。动作完成后转至IDLE状态。等待下一轮启动。我们也可以CANCLE和RESTART该Timer。当Timer接收到CANCLE命令,Timer状态会从WAITING状态转至IDLE。收到RESTART命令时,Timer会从SET WAITING TIME状态开始轮转。

从上图也可以看出,每个Timer都必须完成3个机制:

  1. 等待机制
  2. 对于CANCEL ,RESTART等命令的接口
  3. 期满动作

前两个机制每个Timer都类似,可以在基类中定义,最后一个机制每个Timer都不同,因此必须在子类中实现。

在NS2中,在C++域和OTcl域都有Timer的实现方法。但是二者并没有通过TclClass映射起来。下图列出了相关的函数和变量。

image

通常情况下,Timer用于和另一个Object的交叉引用。即:Object用Timer作为一个等待工具(具备开始,取消,重新开始等功能)。另一方面,Timer在到期后会通知Object,然后Object启动到期动作。

最常用的交叉引用方法是这样的:

  1. 把一个Timer声明为一个Object的变量。
  2. 在Timer类中定义一个指向该Object的指针。
  3. 给Timer定义一个非默认构造函数。把指向该Object的指针放在构造函数的输入参数之中。
  4. 在这个Object的构造函数中初始化该Timer。如:Timer_(this).该Object在初始化的时候就会通过this指针初始化Timer的一个对象Timer_。

第3,4步如:

class BJTUmac_Timer : public Handler
{
    public:
        BJTUmac_Timer(BJTUmac* m):mac(m),rtime(0){busy_=paused_=0;stime=0.0;};

…………

}

BJTUmac::BJTUmac():Mac(),BsAckTimer_(this),AsAckTimer_(this),SensingTimer_(this)
{

…………

}

发表在 NS2 | 留下评论

[chapter 7] Link and Buffer Management

Link -链路是一个OTcl对象:负责把两个节点连接起来,并在其间传递数据包。其中用途最为广泛的是SimpleLink。为了模拟在节点之间传递数据包,SimpleLink对象模拟了包传输时间,链路传输时延和包缓存等功能。

image

基本对象:

  • head_ : SimpleLink 的入口。
  • queue_:是Queue对象,queue_模拟了一个“real”路由器的包缓存。
  • link_:是DelayLink对象,模拟了包传输时间和链路传播时延。
  • ttl_:是一个Time To Live检测器。负责在包头的生存域中减掉一个单位的包生存时间。如果减掉之后生存时间仍为正,继续传递该包。否则,该包会被丢弃。
  • drophead_:Link的包丢弃点。被丢弃的包通常会传递给此对象。它们通常连接到Simulator的null代理,以便所有的SimpleLink对象的丢包地点相同。

Tracing 对象:

只有定义了$traceAllFile_变量的时候,才会插入下面这些对象。

  • enqT_:记录包进入queue_.
  • deqT_:记录包离开queue_.
  • drpT_:记录包从queue_丢弃。
  • rcvT_:记录包离开Link或者说被下个节点接收到(二者同义)。

下图是SipleLink的构造函数,对应图7.1的结构。

image

配置SimpleLink对象的实例函数为Simulator类的Simple-link{..}和duplex-link{..}函数。

语法如下:

$ns simplex-link $n1 $n2 <bandwidth> <delay> <queue_type>
$ns duplex-link $n1 $n2 <bandwidth> <delay> <queue_type>

simplex-link定义了从节点n1到节点n2的一条单向线路,源节点,目的节点,链路带宽,链路时延和queue类型作为输入参数。

duplex-link实际上是两个SimpleLink对象,分别是从n1到n2,从n2到n1。

7.2 模拟包从上游节点离开

一个数据包的离开过程包括包传输时间和链路传播时延。前者是定义了包在上游节点中的时间,前者和后者加起来的时间是一个包从上游节点到下游节点所要消耗的总时间。

  1. 包传输时间:包从上游对象离开。定义为包传输时间=Packet size/bandwidth。
  2. 链路传播时间:包到达下游节点。定义为一个bit从链路开头到链路结尾所要消耗的时间。

总之,包从上游节点发送到上游节点接受要花费的时间是包传输时间和链路传播时间之和。

7.3 缓存管理

SimpleLink的另一个重要部件是Queue类。Queue类模拟了网络路由器的缓存机制。它把收到的包存储到缓存中,并在最近的传输时间到了之后向下游节点传递相应的包。

image

enque(p)和deque()函数分别是存储进PacketQueue对象(缓存)*ph_和从*ph_取出包。二者皆为纯虚函数,必须在子类中被实现。recv(p,h)函数是主要的包接收函数。limit(),length()函数分别返回缓存大小和缓存占用大小。resume(),blocked(),unblocked()函数用于回调机制。

7.3.1 PacketQueue类:模拟包缓存

 

缓存其实是一串包链表。

image

其中head_是链表头指针, tail_ 是链表尾指针,len_是整个链表的长度, enque_(p)把包插入到现有链表的尾部, deque()返回链表第一个包的指针或者在链表为空时返回NULL。remove(P)找到匹配的包并从链表中删除。

7.3.2 QueueHandler

image

 

QueueHandler继承自Handler。该类的默认行为是调用Queue类的resume()函数。

7.3.3 Queue blocking and CallBack mechanism

默认情况下,一个queue只允许在某一时刻发送一个包。因此定义了Queue Blocking机制来保证这一情况。blocked为1或0表示它正在发送或者没有发送。

image

如上图,blocked=0时可以用send(p,h)调用target->recv(p,h)来发送包。blocked=1时不可以发送。但当一个包发送完成时,会调用resume()函数使blocked=0。

上面所说的resume()函数实际上是从downstream对象访问了upstream的blocked变量。这种机制即为回调机制。

image

上图中在Queue中,更换了handler,将qh_传给了LinkDelay的recv(p,qh_)函数.而qh_会调用resume().这是回调机制的实质。

发表在 NS2 | 留下评论

[Chapter 6]Nodes as Routers or Computer Hosts

Ns2中,Node的作用类似于一个主机(信源和信宿)和一个路由器。

6.1 NS2中的节点概述

NS2中的节点主要有两个作用:

  1. 作为路由器:根据路由表转发包
  2. 作为主机:由指定端口向传输层传递包

image 

6.2 NS2中的路由机制

一般情况下,节点可能会连接到若干个下游NsObjects。作为路由器,节点需要选择其中的一个来转发每个到来的包。

NS2中的路由机制主要有4个主要部分:

  • Routing agent 路由代理:收集计算路由表所需的诸如网络拓扑等信息
  • Route logic 路由逻辑:利用收集到的信息计算路由表
  • Classifier 分类器:利用路由表实现包的转发过程
  • Routing module:管理Classifier

image

 

6.3 Routing Logic 略

6.4 Classifiers:Multi-target Packet Forwarders

类似于Connector,Classifier有若干个指针指向对应的target。它把这些指针存储到一个称为Slots的链表当中。基于包头携带的信息和路由表,Classifier会为每个包挑选恰当的Slot,从而转发至恰当的NsObjects。

image

上图是Classifier类的声明。

recv(p,h)函数负责接收包;find(p)函数为到来的包返回一个恰当的指向NsObject指针。classify(p)函数由find(p)函数调用,返回NsObject所在的slot下标。可见,classify(p)函数是整个Classifier的核心。细节见下图:

image

第6章中关于路由的具体内容略去。

发表在 NS2 | 留下评论

[Chapter 5] Network Objects:Creation,Configuration,and Packet Forwarding

网络对象是NS2的一个主要部件,负责数据包的传递。NS2主要靠面向对象编程中的多态概念来实现网络对象。

5.1 NS2部件概览

从功能角度来划分,NS2模块(或对象)可以分为四类:

  1. Network objects :负责发送,接收,创建和销毁与包有关的对象。这些对象都继承自NsObjects。
  2. Packet-related objects:不同种类的包。
  3. Simulation-related objects:控制仿真时间,管理整个仿真。例如events,handlers,Scheduler,Simulator。
  4. Helper Objects:并不直接参与包的传递,但间接的帮助完成整个仿真。

image

第3章我们说过,所有的编译体系的对象都继承自TclObject对象。而c++域也有一部分类并不继承于TclObject,因此不属于编译体系,也不属于解释体系。如上图的右边部分。

从图5.1我们还可以看出,NsObject直接继承于TclObject和handler类。因此它从TclObject继承了OTcl接口,也从handler继承了默认行为。NsObjects有三个主要的子类:Conector,Classifier,LanRouter。Conector类负责链接两个NsObjects,把包直接从一个NsObect传递给所连接的另一个NsObject。Classifier负责把一个NsObeject连接到多个NsObjects。一个Classifier Object 通过包头把包分类,把不同的分类传递给不同的NsObject。LanRouter类同样有多个NsObjects连接。但是它向所有的NsObjects发送每一个接收到的包。

5.2  NsObjects:A Network Object Template

NsObjects是所有 network object的基类。由于NsObjects的主要任务是包的传递,所以NsObjects定义了一个纯虚的recv(p,h)方法。

recv(p,h)函数是Ns2中包传递机制实现的基础。在Ns2中,上游的对象通过调用下游对象的recv(P,h)函数来实现包的传递。

NsObjects主要有两种包传递的方法

  • 直接包传递:调用下游的recv(p,h)方法。target->recv(p,h)
  • 时延包传递:变为Event对象,插入调度器,在时间到达之后调用recv(p,h)方法。

             Scheduler& s=Scheduler::instance();

     s.schedule(target_,p,delay);

5.3 Conector

image

image

Connector类继承自NsObject,它覆盖了纯虚的recv(p,h)函数,仅仅调用了send(p,h)函数。而后者简单的调用target->recv(),从而把包传递给了下游。

OTcl Configuration Command

前面说过,Ns2模拟分为网络配置和模拟两个阶段。Connector在网络配置阶段完成配置。Connector只配置下游对象和丢弃对象。

语法如下:$conn_obj target $down_obj

例子5.1

set conn_obj [new Connector]

set tcp [Agent/TCP]

set null [new Agent/Null]

$conn_obj target $tcp

$conn_obj drop-target $null

image

发表在 NS2 | 留下评论

Ns2 error:scheduler event uid not valid

今天终于想通了怎么使节点0即基站定时的启动感知包的发送。还没高兴完,就碰上个错误:汗!

Scheduler event uid not valid !网上查了资料再结合我的实际,我猜可能是使用定时器有错误。

In implementing a new protocol, this error can happen in two cases:

1. You are using timers. When a timer is scheduled again without the previous schedule expiring – Trace which timer is scheduled and when it will expire.

2. You are dealing with packets. A packet is also a kind of event to be scheduled and a UID is associated with it. When you create a copy or alloc again before freeing it, due to the same packet with a positive UID, it cannot be scheduled again

我的无疑是第一种错误。

在我的写的新MAC协议里,mac的初始化过程中启动定时器,结果把SENSE_PERIODS时间的值设的太小。当我把这个值改为10s时,这个错误解决了!不过这不是最根本的解决方法。

#define SENSE_PERIODS 0.02    //s

BJTUmac::BJTUmac():Mac(),BsAckTimer_(this),AsAckTimer_(this),SensingTimer_(this)
{
    NodeID = index_;
    First=0;
    if(NodeID==BsID)
    {    SensingTimer_.start(SENSE_PERIODS);
        cout<<"Bs constructure and start the Sensing Timer"<<endl;
    }
    else{
    cout<<"ss " <<NodeID<<"constructure !"<<endl;
    }
}

肯定是我的程序流程有问题,需要进一步研究。

发表在 NS2 | 留下评论

Tcl 语法学习笔记

cmd arg arg ….

Tcl 的每一个命令包含一个或几个单词,第一个单词代表命令名,另外的单词则是这个命令的参数,单词之间必须用空格或Table键隔开。

Tcl解释器对一个命令的求值过程分为两部分:分析和执行。在分析阶段,Tcl解释器运用规则把命令分成一个个独立的单词,同时进行必要的替换;在执行阶段,Tcl解释器会把第一个单词当作命令名,并岔开这个命令是否有定义,如果有定义就激活这个命令对应的C/C++过程,并把后面所有的单词作为参数传递给该命令的过程,让命令过程执行。

1 Tcl 变量

Tcl变量是在第一次使用set的指令来指派变数的值时所产生的。而当我们需要取用或者是变更变量的数值时,只要在变量名称前加上一个$的符号就可以去去用或者是变更变量的值了,这就是所谓的变数替换。

2 指令替换

“[]”可以用来达成指令替换,例如,puts "I am [expr 10 * 2] years old, and my I.Q. is [expr 100 – 25]",所以在执行此tcl script时,会先去执行[expr 10 * 2]和[expr 100 – 25],并把结果20和75取代原本tcl script中二者的位置,最后使用puts把字串显示出来。注意,“[]”中必须是一个合法的Tcl脚本,长度不限。[]脚本的值为最好一个命令的返回值。例如:

set y [expr $x+100;set b 300] //y的值为300,因为set b 300的返回值为300 300

另外,TCL语言中的反斜杠置换类似于C语言中反斜杠的用法,主要用于在单词符号中插入诸如换行符、空格、[、$等被TCL解释器当作特殊符号对待的字符。在一些特殊的字符前加”\”来表示这个字符本身.比如说”\$”,”\[”,”\]”就是用来表示这些符号本身.

例如:

set msg multiple\ space //msg的值为multiple space

如果没有’\’的话,TCL会报错,因为解释器会把这里最后两个单词之间的空格认为是分隔符,于是发现set命令有多于两个参数,从而报错。加入了’\’后,空格不被当作分隔符,’multiple space’被认为是一个单词(word)。又例如:
%set msg money\ \$3333\ \nArray\ a\[2] //这个命令的执行结果为:money $3333

3 流程控制

Tcl也提供了一些方法可以用来控制程序运作的流程,这包含了if-else,swith,while,for,foreach等指令。用法类似于C语言,仅仅格式稍有区别。例如

foreach vowel {a e i o u}

{

puts "$vowel is a vowel"

}

执行结果为:

a is a vowel

e is a vowel

i is a vowel

o is a vowel

u is a vowel

4 程序

在Tcl中也允许使用者去自定程序,定义程序的基本语法为:proc name params body,其中name为程序的名称,params是参数列表,body则是程序的主题。

5 数组

set person_info(name) "Fred Smith"

set person_info(age) "25"

set person_info(occupation) "Plumber"

foreach thing [array names person_info]

{

puts "$thing == $person_info($thing)"

}

结果:

name == Fred Smith

age == 25

occupation == Plumber

6 输出

set f [open "/tmp/myfile" "w"]

puts $f "We live in Texas. It’s already 110 degrees out here."

puts $f "456"

close $f

打开/tmp/myfile,可以看到

We live in Texas. It’s already 110 degrees out here.

456

7 反斜杠替代

 

8 组合

TCL通过空白字符来分段表示不同的字符串,而使用花括号或双引号来表示把多个字符串组合在一起来形成一个字符串.区别是双引号内可以允许进行替代,而花括号内不会进行替代

set a 24

set b “is $a”

set c {is $a}

foreach age{b c}

{

puts “my age $age”

}

结果:

my age is 24

my age is $a

9 替代与组合的顺序

TCL在解释命令参数之前,会从左到右遍历参数,确定是否要进行组合或者替代.而组合的决定是在替代之前做出的,一个组合中嵌套多个命令,这些命令的执行顺序也是从左到右依次进行的

10 注释

TCL中的注释符是’#’,’#’和直到所在行结尾的所有字符都被TCL看作注释,TCL解释器对注释将不作任何处理。不过,要注意的是,’#’必须出现在TCL解释器期望命令的第一个字符出现的地方,才被当作注释。
例如:
%#This is a comment %set a 100 # Not a comment wrong # args: should be "set varName ?newValue?" %set b 101 ; # this is a comment 101
第二行中’#’就不被当作注释符,因为它出现在命令的中间,TCL解释器把它和后面的字符当作命令的参数处理,从而导致错误。而第四行的’#’就被作为注释,因为前一个命令已经用一个分号结束,TCL解释器期望下一个命令接着出现。现在在这个位置出现’#’,随后的字符就被当作注释了。

发表在 NS2 | 留下评论

[译]NS2中OTcl类和C++类的连接(完)

当生成一个TclObject时,NS2会自动的建立一个影子编译对象。在3.4.2节中,我们解释了TclObject的生成机制。我们注意到,TclClass类与影子对象的生成有关。我们现在就解释一下TclClass还有影子对象生成的细节。

3.5.1 TclClass类综述

TclClass负责在编译体系内建议影子对象。该类把OTcl类映射给C++的一个静态的映射变量,并提供在编译体系内生成影子对象的方法。程序3.11列出了TcpClass的细节:把解释体系内的Agent/TCP类映射给编译体系内的静态映射变量class_tcp。

image

不同于其他类,TclClass类的子类在同一个地方声明,执行和实例化。在程序3.11中,TclClass的一个子类只包含两个两个函数:构造函数和creat(…)函数。其中creat函数生成影子对象。为了为OTcl的Agent/TCP生成一个影子对象,我们需要在编译体系内执行下列步骤:

i)生成影子编译类

ii)从TclClass继承一个映射类(例如TcpClass)

iii)给这个映射类添加一个实例(例如class_tcp)

iv)定义这个映射子类的构造函数(3行~11行),添加OTcl类的类名(例如Agent/TCP)作为基类构造函数的输入参数,例如TclClass)

v)定义creat函数来构造影子编译对象。调用new函数来产生影子编译对象(例如,new TcpAgent)并返回以生成的对象(程序3.11的第5行)。

3.5.2 TclObject 的生成

我们现在来解释一下TclObject的整个生成过程。考虑图3.3,TclObject 的生成过程如下:

  1. 象在3.4.2小节中一样,生成一个OTcl对象
  2. 调用TclClass函数的实例程序create-shadow
  3. 在creat-shadow函数中调用TcpClass中的create(…)函数
  4. 在程序3.11中,第5行中的create(…)函数执行“new TcpAgent”并返回生成的对象
  5. 调用其父类的构造函数构造一个TcpAgent对象
  6. 构造Agent对象。其中包括绑定在解释体系内所有变量。
  7. 返回TcpAgent类,构造TcpAgent对象,绑定在解释体系所有变量。
  8. 返回生成的影子对象给SplitObject::init{…},程序继续进行。

3.5.3 TclClass的命名规范

现在我们来讲一讲继承自TclClass类的子类和其对应静态变量的命名规范。首先,每个子类都是直接从TclClass继承而来,而不管其类体系所属。例如,RenoTcpAgent继承自TcpAgent,但是它的映射类RenoTcpClass和TcpClass均继承自TclClass。

其次,它的命名规范十分类似于C++变量的命名规范。在大多数情况下,我们仅在其C++类名后面加上Class字样即成。而其静态变量则需要在前面加上class_。表3.4列出了几个例子。

image

3.5.4 映射变量的实例化

在NS2启动时,所有的静态映射变量都会实例化。在此,TclClass类把OTcl类名存储在变量classname_中,把所有的映射变量存储在链表all_中。在所有的映射变量被存储在链表中之后,调用TclClass::bind(…)。bind(…)在系统中注册all_中所有的映射变量,并创建相对应的解释体系。bind(…)函数同时也将create-shadow和delete-shadow函数绑定于映射类(如TcpClass)的create-shadow{…}和delete-shadow{…}。在这之后,NS2重新组织所有的OTcl类名。OTcl类对象的建立遵循3.4.2小节和3.5.2小节的步骤。

发表在 NS2 | 留下评论