2009年12月24日星期四
Writing is for Thinking Better (转)
在《书写是为了更好的思考》一文中,作者阐述了书写对于思考的帮助。正如作者所说:“很多人不书写的原因是觉得没有什么可写的”,可是“你越是不开始书写,总是拿有限的思维缓存去默想一个问题,就越是没有内容可以写,如果你逼着自己将一些不成熟的想法写下来,看着自己写的内容,试着进一步拓展它们,就有可能在理性的道路上走得很远,很远”
书写是为了更好的思考
“我经常在走路和睡前总结所学过的内容,思考遗留的问题,一段时间的阅读和思考之后,一个总体的知识框架就会逐渐浮现在脑海中。然后我会将它书写下来,然而,我往往非常惊讶地发现,当我书写的时候,新的内容仍然源源不断的冒出来,就像我的键盘自己也会思考一样。”
……
书写的好处有以下几点:
* 书写是对思维的备忘:……
* 书写是与自己的对话:……
* 书写是与别人的交流:……
* 有时候,语言自己也会思考:……
在开始书写你的想法之前,我知道很多人不书写的原因是因为觉得没有什么可写的,其实这是一个怪圈,你越是不开始书写,总是拿有限的思维缓存去默想一个问题,就越是没有内容可以写,如果你逼着自己将一些不成熟的想法写下来,看着自己写的内容,试着进一步拓展它们,就有可能在理性的道路上走得很远,很远。
……
2009年12月18日星期五
make again after modifying ns-lib.tcl
2009年12月11日星期五
NS2 2.31(2) 重新编译问题 make error with “proxytrace2any.cc”(转)
Problem: NS2 2.32 make error with “proxytrace2any.cc”
proxytrace2any.cc: In function `int main(int, char**)':
proxytrace2any.cc:112: error: `IsLittleEndian' undeclared (first use this function)
proxytrace2any.cc:112: error: (Each undeclared identifier is reported only once for each function it appears in.)
proxytrace2any.cc:120: error: `ToOtherEndian' undeclared (first use this function)
Solution:
Modify file “ns-allinone-2.32/ns-2.32/indep-utils/webtrace-conv/dec/my-endian.h”
-#ifndef _ENDIAN_H_
-#define _ENDIAN_H_
+#ifndef _MY_ENDIAN_H_
+#define _MY_ENDIAN_H_
note:- remove
+ add
2009年12月9日星期三
2009年12月4日星期五
What makes a good PhD student?--《Nature》
Doing a PhD should be fun and rewarding, because you can spend all your working time discovering things and pursuing ideas — and getting paid for it, without any administrative responsibilities. Those who stick with a career in science do so because, despite the relatively poor pay, long hours and lack of security, it is all we want to do.
Unfortunately most new PhD students are ill-prepared, and as a consequence very few will fulfil their aspirations to be independent scientists. The main reasons for this are the ‘grade creep’ inherent at most universities, making it difficult to identify the really talented first-class graduates from the rest, and the
pressure on universities to graduate as many PhD students as possible. The consequence is that we enrol far too many of them without telling them clearly what doing a doctorate should entail. We therefore set ourselves, and the students, on a path of frustration and disappointment. So what should we be telling
prospective PhD students?
●Choose a supervisor whose work you admire and who is well supported by grants and departmental infrastructure.
●Take responsibility for your project.
●Work hard — long days all week and part of most weekends. If research is your passion this should be easy, and if it isn’t, you are probably in the wrong field. Note who goes home with a full briefcase to work on at the end of the day. This is a cause of success, not a consequence.
●Take some weekends off, and decent holidays, so you don’t burn out.
●Read the literature in your immediate area, both current and past, and around
it. You can’t possibly make an original contribution to the literature unless you
know what is already there.
●Plan your days and weeks carefully to dovetail experiments so that you have a
minimum amount of downtime.
●Keep a good lab book and write it up every day.
●Be creative. Think about what you are doing and why, and look for better ways to go. Don’t see your PhD as just a road map laid out by your supervisor.
●Develop good writing skills: they will make your scientific career immeasurably easier.
●To be successful you must be at least four of the following: smart, motivated,
creative, hard-working, skilful and lucky.
You can’t depend on luck, so you had better focus on the others!
■
Georgia Chenevix-Trench is principal research fellow at the Queensland
Institute of Medical Research, Royal Brisbane Hospital, Herston, Australia.
➧ www.qimr.edu.au/research/labs/georgiat/Guideforphds.doc
2009年12月2日星期三
1. NS的整体的实现
固定网络的仿真是通过下面三层合作来实现的。
Application这个层是实现数据流的层次。Agent这个层是实现所有各层协议的的层次。Node这个部分由多个分类器(Classifier)实现了所有接收数据包进行判断是否进行转发或接收到Agent的部分。Link实现了队列、时延、Agent、记录Trace等一系列的仿真问题。
2. TclObject
Handler
ParentNode
Process
NsObject
Node
Application
Connector
Classifier
Delay
Queue
Agent
Trace
AddressClassifier
图表 2 NS内部类的继承关系图
NS内部类的继承关系
此图为NS内部类的继承关系,可以看出一些类是由那些类继承来的,这样相同的属性调用的函数就可以很方便的找到出处
3. NS中函数调用的分层
在用gdb跟踪NS发送一个cbr数据包的过程可以看到一下顺序:
CBR_Traffic::start
TrafficGenerator::timeout
Application::send
UdpAgent::sendmsg
Classifier::recv
Connect::recv
Connect::send
Trace::recv 写一条数据包加入队列的记录
Connector::send
Queue::recv
DropTail::enque
DequeTrace::recv 写一条数据包弹出队列的记录
Connector::send
LinkDelay::recv 插入事件到scheduler
Scheduler::dispatch
Scheduler::run
从上面的顺序可以看出,数据包在发送以后先通过应用层(Application::send)进行发送;然后通过Agent层(UdpAgent::sendmsg),UdpAgent是在初始化Agent的时候确定的。UdpAgent还有一个作用是生成相应的数据包;然后进入Node部分的分类器Classifier(Classifier::recv),通过find()函数返回下一跳地址。这个函数是通过读取 Packet的内容得到下一跳地址的,返回给recv函数后调用node->recv()进入connector;经过 connector::recv和connector::send后确定数据包可发,进入Trace::recv,记录这个数据包加入队列的记录;之后通过connector::send,进入Queue::recv函数,将数据包正式加入发送队列,再根据已经设定好的方法确定加入队列是否成功,是否要被丢弃;再调用DequeTrace::recv记录数据包弹出队列的记录;再通过connector::send进入LinkDelay::recv,先判断目的节点是否可达,根据不同的结果将事件写入scheduler,等待按序执行。
上述的过程只是一个数据包从生成到发送出去的过程,因为NS是一个根据一个一个离散事件调度执行的,后面的过程用gdb跟不进去。但可以看出,数据包是发送给下一跳节点,可知数据包是通过每个中间节点的。
4. NS中主要函数的分析
4.1. CBR数据源
开始从下面函数进入CBR数据源发送:
void CBR_Traffic::start()
{
init(); //初始化
running_ = 1;
timeout(); //进入发送数据的循环
}
void TrafficGenerator::timeout()
{
if (! running_) //判断是否要发送数据
return;
/* send a packet */
send(size_); //发送一个设定好大小的数据包
/* figure out when to send the next one */
nextPkttime_ = next_interval(size_);
/* schedule it */
if (nextPkttime_ > 0)
timer_.resched(nextPkttime_);
else
running_ = 0;
}
4.2. 中间几个函数只是体现分层
void Application::send(int nbytes)
{
agent_->sendmsg(nbytes);
}
void Agent::sendmsg(int /*sz*/, AppData* /*data*/, const char* /*flags*/)
{
fprintf(stderr,
"Agent::sendmsg(int, AppData*, const char*) not implemented\n");
abort();
}
上述两个函数其实并没有什么实质的操作,只是这样可以看出其经过了应用层和Agent层。
4.3. Classifier的函数
void Classifier::recv(Packet* p, Handler*h)
{
NsObject* node = find(p); //查找目的节点
if (node == NULL) { //只要返回了目的节点就调用节点的recv函数
/*
* 这个将被丢弃,不用记录在trace文件中
*/
Packet::free(p);
return;
}
node->recv(p,h);
}
NsObject* Classifier::find(Packet* p)
{
NsObject* node = NULL;
int cl = classify(p); //根据发送的packet的记录找到slot
if (cl < 0 || cl >= nslot_ || (node = slot_[cl]) == 0) { //根据slot得到下一跳node
if (default_target_)
return default_target_;
/*
* 不能将数据包发送出去,因为返回结果不是一个对象.
*/
Tcl::instance().evalf("%s no-slot %ld", name(), cl);
if (cl == TWICE) {
/*
* Try again. Maybe callback patched up the table.
*/
cl = classify(p);
if (cl < 0 || cl >= nslot_ || (node = slot_[cl]) == 0)
return (NULL);
}
}
return (node); //返回给classifier::recv得到的node的值
}
这个地址分类器就是根据数据包的内容,通过偏移查找接收的节点,然后调用接收节点的recv函数。而find函数是根据数据包的内容得到slot的值从而查询出谁是接收方的node。
4.4. Connector的函数
void Connector::recv(Packet* p, Handler* h)
{
send(p, h);
}
inline void send(Packet* p, Handler* h) { target_->recv(p, h); }
connector的recv和send函数是一个接口。这个函数中最重要的是target_这个值,这个值的不同会不同的调用Trace::recv、Queue::recv、LinkDelay::recv等等,但是这个值在那确定还没有看出来。
4.5. Queue的函数
void Queue::recv(Packet* p, Handler*)
{
double now = Scheduler::instance().clock();
enque(p); //根据规定的规则加入队列
if (!blocked_) {
/*
* 这里没有堵塞. 将一个数据包发送出去.
* 我们执行一个附加的检查,因为这个队列可能丢弃这个数据包
* 即使它前面是空的。 (例如, RED队列就可能发生.)
*/
p = deque();
if (p != 0) {
utilUpdate(last_change_, now, blocked_);
last_change_ = now;
blocked_ = 1;
target_->recv(p, &qh_); //调用dequetrace
}
}
}
Queue::recv这个函数调用DropTail规则将数据包加入队列,然后判断是否堵塞,如果没有则发送一个数据包,之前判断是认定这个包是否要被发送出去。这里也使用了target_->recv(),这里调用的是DequeTrace::recv函数,将记录一个数据包出队列的记录。
4.6. LinkDelay的函数
void LinkDelay::recv(Packet* p, Handler* h)
{
double txt = txtime(p);
Scheduler& s = Scheduler::instance();
if (dynamic_) { //这个是动态链路的标志,判断这个值确定链路是否为动态链
Event* e = (Event*)p;
e->time_= txt + delay_;
itq_->enque(p); // 用一个队列来储存数据包
s.schedule(this, p, txt + delay_);
} else if (avoidReordering_) {
// 预防重新安排带宽或时延改变
double now_ = Scheduler::instance().clock();
if (txt + delay_ < latest_time_ - now_ && latest_time_ > 0) {
latest_time_+=txt;
s.schedule(target_, p, latest_time_ - now_ );//在schedule里面加入事件
} else {
latest_time_ = now_ + txt + delay_;
s.schedule(target_, p, txt + delay_); //在schedule里面加入事件
}
} else {
s.schedule(target_, p, txt + delay_); //在schedule里面加入事件
}
s.schedule(h, &intr_, txt); //在schedule里面加入事件
}
这个函数非常重要,这个是数据包最后离开这个节点的出口,由这个函数写一个事件加入schedule,在一定的时间后调用。
s.schedule(target_, p, txt)这个的含义是在txt的时间以后调用target_的事件,处理p这个数据包。
你可以通过这个链接引用该篇文章:http://naonaoruby.bokee.com/tb.b?diaryId=11857286
http://naonaoruby.bokee.com/viewdiary.11857286.html
bzero & memset
--------------
NAME
bzero - memory operations (LEGACY)
SYNOPSIS
#include "<"strings.h">"
void bzero(void *s, size_t n);
DESCRIPTION
The bzero() function shall place n zero-valued bytes in the area pointed to by s.
RETURN VALUE
The bzero() function shall not return a value.
ERRORS
No errors are defined.
===============================================================
memset
-----------------------
NAME
memset - set bytes in memory
SYNOPSIS
#include "<"string.h">"
void *memset(void *s, int c, size_t n);
DESCRIPTION
The functionality described on this reference page is aligned with the ISO C standard. Any conflict between the requirements described here and the ISO C standard is unintentional. This volume of IEEE Std 1003.1-2001 defers to the ISO C standard.
The memset() function shall copy c (converted to an unsigned char) into each of the first n bytes of the object pointed to by s.
RETURN VALUE
The memset() function shall return s; no return value is reserved to indicate an error.
ERRORS
No errors are defined.
--------------------------------
memset
void * memset ( void * ptr, int value, size_t num );
Fill block of memory
Sets the first num bytes of the block of memory pointed by ptr to the specified value (interpreted as an unsigned char).
Parameters
ptr
Pointer to the block of memory to fill.
value
Value to be set. The value is passed as an int, but the function fills the block of memory using the unsigned char conversion of this value.
num
Number of bytes to be set to the value.
Return Value
ptr is returned.
Example
/* memset example */
#include "<"stdio.h">"
#include "<"string.h">"
int main ()
{
char str[] = "almost every programmer should know memset!";
memset (str,'-',6);
puts (str);
return 0;
}
Output:
------ every programmer should know memset!
2009年10月19日星期一
卸载Cygwin(转)
在这里我说一下这个问题的解决方案:记得在安装cygwin的时候那个setup文件了吗,我们卸载它也要用这个文件,进入setup后还在按照步骤下一步....下一步,之后会来到您需要安装文件包的那个环节,如果你先前选择了All-install,则这些则把这一项改为all-uninstall即可。如果你之前安装选择了自定义的安装,则把对应项改为uninstall即可。就是这么简单是吧!!!然后点击下一步,一切就都OK了!
http://blog.sina.com.cn/s/blog_4cd90db3010009k5.html~type=v5_one&label=rela_nextarticle
2009年10月15日星期四
ns2 环境变量设置 (cygwin)
.bash_profile
.bashrc
.inputrc
三个文件从/Cygwin/etc/skel拷入其中
在.bashrc 中添加如下内容
PATH中加入“$NS_HOME/nam-1.14”为的是调用nam,否则可能找不到,不过装完ns后的提示信息没有这一条,
要想使用nam还需要设置环境变量$DISPLAY,否则会报错说找不到$DISPLAY;不设置此变量可以通过启动X11窗口,然后运行ns程序,这样也可以成功运行nam,不过比较麻烦
#======================================================#
#add for ns 2.34 by Charlie Zheng on 15/10/2009
#NS_HOME--add for ns
export NS_HOME=/myns/ns-allinone-2.34
#nam-1.14 not required by information after ns 2.34 installation, to use nam
export PATH=$NS_HOME/nam-1.14:$NS_HOME/tcl8.4.18/unix:$NS_HOME/tk8.4.18/unix:$NS_HOME/bin:$PATH
export LD_LIBRARY_PATH=$NS_HOME/tcl8.4.18/unix:$NS_HOME/tk8.4.5/unix:$NS_HOME/otcl-1.13:$NS_HOME/lib:$LD_LIBRARY_PATH
export TCL_LIBRARY=$NS_HOME/tcl8.4.18/library
#add for nam display
export DISPLAY=localhost:0.0
#=======================================================#
PS. 也可以不拷那三个文件,直接修改/Cygwin/etc 下的profile文件,不过该文件注释中提到,修改此文件可能影响系统软件
2009年10月14日星期三
装完 cygwin后在/cygwin下找不到/home文件夹的解决办法
http://forum.byr.edu.cn/wForum/disparticle.php?boardName=Communications&ID=5221&pos=1
OR
CygWIn安装问题
Preparing Cygwin for Ns2
http://hpds.ee.ncku.edu.tw/~smallko/ns2/setup_en.htm
Packages such as "XFree-*" (XFree86-base)can not be found in new version Cygwin. One can refer to following URL:
1.http://wiki.nus.edu.sg/display/CBR/Install+NS-2+on+Cygwin
2.http://my-hertz.blogspot.com/2009/06/preparing-cygwin-for-ns2.html
I have all packages of Cygwin(version 1.5.25-15) installed on my computer as 1.
2009年4月10日星期五
假如再做一次研究生----来自院士的箴言
一、研究生与大学生的区别
首先跟大家说明一下研究生和大学生的区别。大学生基本上是来接受学问、接受知识的,然而不管是对于硕士时期或是博士时期的研究而言,都应该准备要开始制造新的知识,我们在美国得到博士学位时都会领到看不懂的毕业证书,在一个偶然的机会下,我问了一位懂拉丁文的人,上面的内容为何?他告诉我:「里头写的是恭喜你对人类的知识有所创新,因此授予你这个学位。」在中国原本并没有博硕士的学历,但是在西方他们原来的用意是,恭贺你已经对人类普遍的知识有所创新,这个创新或大或小,都是对于普遍的知识有所贡献。这个创新不会因为你做本土与否而有所不同,所以第一个我们必须要很用心、很深刻的思考,大学生和研究生是不同的。
(一)选择自己的问题取向,学会创新
你一旦是研究生,你就已经进入另一个阶段,不只是要完全乐在其中,更要从而接受各种有趣的知识,进入制造知识的阶段,也就是说你的论文应该有所创新。由接受知识到创造知识,是身为一个研究生最大的特色,不仅如此,还要体认自己不再是个容器,等着老师把某些东西倒在茶杯里,而是要开始逐步发展和开发自己。做为研究生不再是对于各种新奇的课照单全收,而是要重视问题取向的安排,就是在硕士或博士的阶段里面,所有的精力、所有修课以及读的书里面都应该要有一个关注的焦点,而不能像大学那般漫无目标。大学生时代是因为你要尽量开创自己接受任何东西,但是到了硕士生和博士生,有一个最终的目的,就是要完成论文,那篇论文是你个人所有武功的总集合,所以这时候必须要有个问题取向的学习。
(二)尝试跨领域研究,主动学习
提出一个重要的问题,跨越一个重要的领域,将决定你未来的成败。我也在台大和清华教了十几年的课,我常常跟学生讲,选对一个领域和选对一个问题是成败的关键,而你自己本身必须是带着问题来探究无限的学问世界,因为你不再像大学时代一样泛滥无所归。所以这段时间内,必须选定一个有兴趣与关注的主题为出发点,来探究这些知识,产生有机的循环。由于你是自发性的对这个问题产生好奇和兴趣,所以你的态度和大学部的学生是截然不同的,你慢慢从被动的接受者变成是一个主动的探索者,并学会悠游在这学术的领域。
我举一个例子,我们的中央研究院院长李远哲先生,得了诺贝尔奖。他曾经在中研院的周报写过几篇文章,在他的言论集里面,或许各位也可以看到,他反复提到他的故事。他是因为读了一个叫做马亨教授的教科书而去美国柏克莱大学念书,去了以后才发现,这个老师只给他一张支票,跟他说你要花钱你尽量用,但是从来不教他任何东西。可是隔壁那个教授,老师教很多,而且每天学生都是跟着老师学习。他有一次就跟那个老师抱怨:「那你为什么不教我点东西呢?」那个老师就说:「如果我知道结果,那我要你来这边念书做什么?我就是因为不知道,所以要我们共同探索一个问题、一个未知的领域。」他说其实这两种教法都有用处,但是他自己从这个什么都不教他,永远碰到他只问他「有没有什么新发现」的老师身上,得到很大的成长。所以这两方面都各自蕴含深层的道理,没有所谓的好坏,但是最好的方式就是将这两个方式结合起来。我为什么讲这个故事呢?就是强调在这个阶段,学习是一种「 self-help 」,并且是在老师的引导下学习「 self-help 」,而不能再像大学时代般,都是纯粹用听的,这个阶段的学习要基于对研究问题的好奇和兴趣,要带着一颗热忱的心来探索这个领域。
然而研究生另外一个重要的阶段就是 Learn how to learn ,不只是学习而已,而是学习如何学习,不再是要去买一件很漂亮的衣服,而是要学习拿起那一根针,学会绣出一件漂亮的衣服,慢慢学习把目标放在一个标准上,而这一个标准就是你将来要完成硕士或博士论文。如果你到西方一流的大学去读书,你会觉得我这一篇论文可能要和全世界做同一件问题的人相比较。我想即使在***也应该要有这样的心情,你的标准不能单单只是放在旁边几个人而已,而应该是要放在领域的普遍人里面。你这篇文章要有新的东西,才算达到的标准,也才符合到我们刚刚讲到那张拉丁文的博士证书上面所讲的,有所贡献与创新。
二、一个老师怎么训练研究生
第二个,身为老师你要怎么训练研究生。我认为人文科学和社会科学的训练,哪怕是自然科学的训练,到研究生阶段应该更像师徒制,所以来自个人和老师、个人和同侪间密切的互动和学习是非常重要的,跟大学部坐在那边单纯听课,听完就走人是不一样的,相较之下你的生活应该要和你所追求的知识与解答相结合,并且你往后的生活应该或多或少都和这个探索有相关。
(一)善用与老师的伙伴关系,不断 Research
我常说英文 research 这个字非常有意义, search 是寻找,而 research 是再寻找,所以每个人都要 research ,不断的一遍一遍再寻找,并进而使你的生活和学习成为一体。中国近代兵学大师蒋百里在他的兵学书中曾说:「生活条件要跟战斗条件一致,近代欧洲凡生活与战斗条件一致者强,凡生活与战斗条件不一致者弱。」我就是藉由这个来说明研究生的生活,你的生活条件与你的战斗条件要一致,你的生活是跟着老师与同学共同成长的,当中你所听到的每一句话,都可能带给你无限的启发。
回想当时我在美国念书的研究生生活,只要随便在楼梯口碰到任何一个人,他都有办法帮忙解答你语言上的困难,不管是英文、拉丁文、德文、希腊文 …… 等。所以能帮助解决问题的不单只是你的老师,还包括所有同学以及学习团体。你的学习是跟生活合在一起的。当我看到有学生呈现被动或是懈怠的时候,我就会用毛泽东的「革命不是请客吃饭!」来跟他讲:「作研究生不是请客吃饭。」
(二)藉由大量阅读和老师提点,进入研究领域
怎样进入一个领域最好,我个人觉得只有两条路,其中一条就是让他不停的念书、不停的报告,这是进入一个陌生的领域最快,又最方便的方法,到最后不知不觉学生就会知道这个领域有些什么,我们在不停念书的时候常常可能会沉溺在细节里不能自拔,进而失去全景,导致见树不见林,或是被那几句英文困住,而忘记全局在讲什么。藉由学生的报告,老师可以讲述或是厘清其中的精华内容,经由老师几句提点,就会慢慢打通任督二脉,逐渐发展一种自发学习的能力,同时也知道碰到问题可以看哪些东西。就像是我在美国念书的时候,我修过一些我完全没有背景知识的国家的历史,所以我就不停的念书、不停***着自己吸收,而老师也只是不停的开书目,运用这样的方式慢慢训练,有一天我不再研究它时,我发现自己仍然有自我生产及蓄发的能力,因为我知道这个学问大概是什么样的轮廓,碰到问题也有能力可以去查询相关的资料。所以努力让自己的学习产生自发的延展性是很重要的。
(三)循序渐进地练习论文写作
到了硕士或博士最重要的一件事,是完成一篇学位论文,而不管是硕士或博士论文,其规模都远比你从小学以来所受的教育、所要写的东西都还要长得多,虽然我不知道教育方面的论文情况是如何,但是史学的论文都要写二、三十万字,不然就是十几二十万字。写这么大的一个篇幅,如何才能有条不紊、条理清楚,并把整体架构组织得通畅可读?首先,必须要从一千字、五千字、一万字循序渐进的训练,先从少的慢慢写成多的,而且要在很短的时间内训练到可以从一万字写到十万字。这么大规模的论文谁都写得出来,问题是写得好不好,因为这么大规模的写作,有这么许多的脚注,还要注意首尾相映,使论述一体成型,而不是散落一地的铜钱;是一间大礼堂,而不是一间小小分割的阁楼。为了完成一个大的、完整的、有机的架构模型,必须要从小规模的篇幅慢慢练习,这是一个最有效的办法。
因为受计算机的影响,我发现很多学生写文章能力都大幅下降。写论文时很重要的一点是,文笔一定要清楚,不要花俏、不必漂亮,「清楚」是最高指导原则,经过慢慢练习会使你的文笔跟思考产生一致的连贯性。我常跟学生讲不必写的花俏,不必展现你散文的才能,因为这是学术论文,所以关键在于要写得非常清楚,如果有好的文笔当然更棒,但那是可遇不可求的,文彩像个人的生命一样,英文叫 style , style 本身就像个人一样带有一点点天生。因此最重要的还是把内容陈述清楚,从一万字到最后十万字的东西,都要架构井然、论述清楚、文笔清晰。
我在念书的时候,有一位欧洲史、英国史的大师 Lawrence Stone ,他目前已经过世了,曾经有一本书访问十位最了不起的史学家,我记得他在访问中说了一句非常吸引人注意的话,他说他英文文笔相当好,所以他一辈子没有被退过稿。因此文笔清楚或是文笔好,对于将来文章可被接受的程度有举足轻重的地位。内容非常重要,有好的表达工具更是具有加分的作用,但是这里不是讲究漂亮的 style ,而是论述清楚。
三、研究生如何训练自己
(一)尝试接受挑战,勇于克服
研究生如何训练自己?就是每天、每周或每个月给自己一个挑战,要每隔一段时间就给自己一个挑战,挑战一个你做不到的东西,你不一定要求自己每次都能顺利克服那个挑战,但是要努力去尝试。我在我求学的生涯中,碰到太多聪明但却一无所成的人,因为他们很容易困在自己的障碍里面,举例来说,我在普林斯顿大学碰到一个很聪明的人,他就是没办法克服他给自己的挑战,他就总是东看西看,虽然我也有这个毛病,可是我会定期给我自己一个挑战,例如:我会告诉自己,在某一个期限内,无论如何一定要把这三行字改掉,或是这个礼拜一定要把这篇草稿写完,虽然我仍然常常写不完,但是有这个挑战跟没这个挑战是不一样的,因为我挑战三次总会完成一次,完成一次就够了,就足以表示克服了自己,如果觉得每一个礼拜的挑战,可行性太低,可以把时间延长为一个月的挑战,去挑战原来的你,不一定能做到的事情。不过也要切记,硕士生是刚开始进入这一个领域的新手,如果一开始问题太小,或是问题大到不能控制,都会造成以后研究的困难。
(二)论文的写作是个训练过程,不能苛求完成精典之作
各位要记得我以前的老师所说的一句话:「硕士跟博士是一个训练的过程,硕士跟博士不是写经典之作的过程。」我看过很多人,包括我的亲戚朋友们,他之所以没有办法好好的完成硕士论文,或是博士论文,就是因为他把它当成在写经典之作的过程,虽然事实上,很多人一生最好的作品就是硕士论文或博士论文,因为之后的时间很难再有三年或六年的时间,沉浸在一个主题里反复的耕耘,当你做教授的时候,像我今天被行政缠身,你不再有充裕的时间好好探究一个问题,尤其做教授还要指导学生、上课,因此非常的忙碌,所以他一生最集中又精华的时间,当然就是他写博士、或是硕士论文的时候,而那一本成为他一生中最重要的著作也就一点都不奇怪了。
不一定要刻意强求,要有这是一个训练过程的信念,应该清楚知道从哪里开始,也要知道从哪里放手,不要无限的追下去。当然我不是否认这个过程的重要性,只是要调整自己的心态,把论文的完成当成一个目标,不要成为是一种的心理障碍或是心理负担。这方面有太多的例子了,我在普林斯顿大学念书的时候,那边旧书摊有一位非常博学多文的旧书店老板,我常常赞叹的对他说:「你为什么不要在大学做教授。」他说:「因为那篇博士论文没有写完。」原因在于他把那个博士论文当成要写一本经典,那当然永远写不完。如果真能写成经典那是最好,就像美丽新境界那部电影的男主角 John Nash 一样,一生最大的贡献就是博士那二十几页的论文,不过切记不要把那个当作是目标,因为那是自然而然形成的,应该要坚定的告诉自己,所要完成的是一份结构严谨、论述清楚与言之有物的论文,不要一开始就期待它是经典之作。如果你期待它是经典之作,你可能会变成我所看到的那位旧书摊的老板,至于我为什么知道他有那么多学问,是因为那时候我在找一本书,但它并没有在旧书店里面,不过他告诉我:「还有很多本都跟他不相上下。」后来我对那个领域稍稍懂了之后,证明确实如他所建议的那般。一个旧书店的老板精熟每一本书,可是他就是永远无法完成,他梦幻般的学位论文,因为他不知道要在哪里放手,这一切都只成为空谈。
(三)论文的正式写作
1. 学习有所取舍
到了写论文的时候,要能取也要能舍,因为现在信息爆炸,可以看的书太多,所以一定要建构一个属于自己的知识树,首先,要有一棵自己的知识树,才能在那棵树挂相关的东西,但千万不要不断的挂不相关的东西,而且要慢慢的舍掉一些挂不上去的东西,再随着你的问题跟关心的领域,让这棵知识树有主干和枝叶。然而这棵知识树要如何形成?第一步你必须对所关心的领域中,有用的书籍或是数据非常熟悉。
2. 形成你的知识树
我昨天还请教林毓生院士,他今年已经七十几岁了,我告诉他我今天要来作演讲,就问他:「你如果讲这个题目你要怎么讲?」他说:「只有一点,就是那重要的五、六本书要读好几遍。」因为林毓生先生是海耶克,还有几位近代思想大师在芝加哥大学的学生,他们受的训练中很重要的一部份是精读原典。这句话很有道理,虽然你不可能只读那几本重要的书,但是那五、六本书将逐渐形成你知识树的主干,此后的东西要挂在上面,都可以参照这一个架构,然后把不相干的东西暂放一边。生也有涯,知也无涯,你不可能读遍天下所有的好书,所以要学习取舍,了解自己无法看遍所有有兴趣的书,而且一但看遍所有有兴趣的书,很可能就会落得普林斯顿街上的那位旧书店的老板一般,因为阅读太多不是自己所关心的领域的知识,它对于你来说只是一地的散钱。
3. 掌握工具
在这个阶段一定要掌握语文与合适的工具。要有一个外语可以非常流畅的阅读,要有另外一个语文至少可以看得懂文章的标题,能学更多当然更好,但是至少要有一个语文,不管是英文、日文、法文 …… 等,一定要有一个语文能够非常流畅的阅读相关书籍,这是起码的前提。一旦这个工具没有了,你的视野就会因此大受限制,因为语文就如同是一扇天窗,没有这个天窗你这房间就封闭住了。为什么你要看得懂标题?因为这样才不会有重要的文章而你不知道,如果你连标题都看不懂,你就不知道如何找人来帮你或是自己查相关的数据。其它的工具,不管是统计或是其它的任何工具,你也一定要多掌握,因为你将来没有时间再把这样的工具学会。
4. 突破学科间的界线
应该要把跨学科的学习当作是一件很重要的事,但是跨学科涉及到的东西必须要对你这棵知识树有帮助,要学会到别的领域稍微偷打几枪,到别的领域去摄取一些概念,对于本身关心的问题产生另一种不同的启发,可是不要泛滥无所归。为什么要去偷打那几枪?近几十年来,人们发现不管是科学或人文,最有创新的部份是发生在学科交会的地方。为什么会如此?因为我们现在的所有学科大部分都在西方十九世纪形成的,而中国再把它转借过来。十九世纪形成这些知识学科的划分的时候,很多都带有那个时代的思想跟学术背景,比如说,中研院的李院长的专长就是物理化学,他之所以得诺贝尔奖就是他在物理和化学的交界处做工作。像诺贝尔经济奖,这二十年来所颁的奖,如果在传统的经济学奖来看就是旁门走道,古典经济学岂会有这些东西,甚至心理学家也得诺贝尔经济奖,连 John Nash 这位数学家也得诺贝尔经济奖,为什么?因为他们都在学科的交界上,学科跟学科、平台跟平台的交界之处有所突破。在平台本身、在学科原本最核心的地方已经 search 太多次了,因此不一定能有很大的创新,所以为什么跨领域学习是一件很重要的事情。
常常一篇硕士论文或博士论文最重要、最关键的,是那一个统摄性的重要概念,而通常你在本学科里面抓不到,是因为你已经泡在这个学科里面太久了,你已经拿着手电筒在这个小仓库里面照来照去照太久了,而忘了还有别的东西可以更好解释你这些材料的现象,不过这些东西可遇而不可求。 John Nash 这一位数学家为什么会得诺贝尔数学奖?为什么他在赛局理论的博士论文,会在数十年之后得诺贝尔经济奖?因为他在大学时代上经济学导论的课,所以他认为数学可以用在经济方面来思考,而这个东西在一开始,他也没有想到会有这么大的用处。他是在数学和经济学的知识交界之处做突破。有时候在经济学这一个部分没有大关系,在数学的这一个部分也没有大关系,不过两个加在一起,火花就会蹦出来。
5. 论文题目要有延展性
对一个硕士生或博士生来说,如果选错了题目,就是失败,题目选对了,还有百分之七十胜利的机会。这个问题值得研一、博一的学生好好思考。你的第一年其实就是要花在这上面,你要不断的跟老师商量寻找一个有意义、有延展性的问题,而且不要太难。我在国科会当过人文处长,当我离开的时候,每次就有七千件申请案,就有一万四千个袋子,就要送给一万四千个教授审查。我当然不可能看那么多,可是我有个重要的任务,就是要看申诉。有些申诉者认为:「我的研究计划很好,我的著作很好,所以我来申诉。」申诉通过的大概只有百分之十,那么我的责任就是在百分之九十未通过的案子正式判决前,再拿来看一看。有几个印象最深常常被拿出来讨论的,就是这个题目不必再做了、这个题目本身没有发展性,所以使我更加确认选对一个有意义、有延展性、可控制、可以经营的题目是非常重要的。
我的学生常常选非常难的题目,我说你千万不要这样,因为没有人会仔细去看你研究的困难度,对于难的题目你要花更多的时间阅读史料,才能得到一点点东西;要挤很多东西,才能筛选出一点点内容,所以你最好选择一个难易适中的题目。
我写过好几本书,我认为我对每一本书的花的心力都是一样,虽然我写任何东西我都不满意,但是在过程中我都绞尽脑汁希望把他写好。目前为止很多人认为我最好的书,是我二十几岁刚到史语所那一年所写的那本书。我在那本书花的时间并不长,那本书的大部分的稿子,是我和许添明老师同时在当兵的军营里面写的,而且还是用我以前旧的笔记写的。大陆这些年有许多出版社,反复要求出版我以前的书,尤其是这一本,我说:「不行。」因为我用的是我以前的读书笔记,我怕引文有错字,因为在军队营区里面随时都要出操、随时就要集合,手边又没有书,怎么可能好好的去核对呢?而如果要我重新校正一遍,又因为引用太多书,实在没有力气校正。
为什么举这个例子呢?我后来想一想,那本书之所以比较好,可能是因为那个题目可延展性大,那个题目波澜起伏的可能性大。很多人都认为,我最好的书应该是剑桥大学出的那一本,不过我认为我最好的书一定是用中文写的,因为这个语文我能掌握,英文我没办法掌握得出神入化。读、写任何语文一定要练习到你能带着三分随意,那时候你才可以说对于这一个语文完全理解与精熟,如果你还无法达到三分的随意,就表示你还在摸索。
回到我刚刚讲的,其实每一本书、每一篇论文我都很想把它写好。但是有些东西没办法写好,为什么?因为一开始选择的题目不够好。因此唯有选定题目以后,你的所有训练跟努力才有价值。我在这里建议大家,选题的工作要尽早做,所选的题目所要处理的材料最好要集中,不要太分散,因为硕士生可能只有三年、博士生可能只有五年,如果你的材料太不集中,读书或看数据可能就要花掉你大部分的时间,让你没有余力思考。而且这个题目要适合你的性向,如果你不会统计学或讨厌数字,但却选了一个全都要靠统计的论文,那是不可能做得好。
6. 养成遵照学术格式的写作习惯
另一个最基本的训练,就是平时不管你写一万字、三万字、五万字都要养成遵照学术规范的习惯,要让他自然天成,就是说你论文的脚注、格式,在一开始进入研究生的阶段就要培养成为你生命中的一个部份,如果这个习惯没有养成,人家就会觉得这个论文不严谨,之后修改也要花很多时间,因为你的论文规模很大,可能几百页,如果一开始弄错了,后来再重头改到尾,一定很耗时费力,因此要在一开始就养成习惯,因为我们是在写论文而不是在写散文,哪一个逗点应该在哪里、哪一个书名号该在哪里、哪一个地方要用引号、哪一个要什么标点符号,都有一定的规定,用中文写还好,用英文有一大堆简称。在 1960 年代***知识还很封闭的时候,有一个人从美国回来就说:「美国有个不得了的情形,因为有一个人非常不得了。」有人问他为什么不得了,他说:「因为这个人的作品到处被引用。」他的名字就叫 ibid 。所谓 ibid 就是同前作者,这个字是从拉丁文发展出来的,拉丁文有一大堆简称,像 et. al. 就是两人共同编的。英文有一本 The Chicago Manual of Style 就是专门说明这一些写作规范。各位要尽早学会中英文的写作规范,慢慢练习,最后随性下笔,就能写出符合规范的文章。
7. 善用图书馆
图书馆应该是研究生阶段最重要的地方,不必读每一本书,可是要知道有哪些书。我记得我做学生时,新进的书都会放在图书馆的墙上,而身为学生最重要的事情,就是要把书名看一看。在某些程度上知道书皮就够了,但是这仍和打计算机是不一样的,你要实际上熟悉一下那本书,摸一下,看一眼目录。我知道现在从计算机就可以查到书名,可是我还是非常珍惜这种定期去 browse 新到的书的感觉,或去看看相关领域的书长成什么样子。中研院有一位院士是哈佛大学信息教授,他告诉我他在创造力最高峰的时候,每个礼拜都到他们信息系图书室里,翻阅重要的信息期刊。所以图书馆应该是身为研究生的人们,最熟悉的地方。不过切记不重要的不要花时间去看,你们生活在信息泛滥的时代,跟我生长在信息贫乏的时代是不同的,所以生长在这一个时代的你,要能有所取舍。我常常看我的学生引用一些三流的论文,却引得津津有味,我都替他感到难过,因为我强调要读有用、有价值的东西。
8. 留下时间,精致思考
还要记得给自己保留一些思考的时间。一篇论文能不能出神入化、能不能引人入胜,很重要的是在现象之上作概念性的思考,但我不是说一定要走理论的路线,而是提醒大家要在一般的层次再提升两三步, conceptualize 你所看到的东西。真切去了解,你所看到的东西是什么?整体意义是什么?整体的轮廓是什么?千万不要被枝节淹没,虽然枝节是你最重要的开始,但是你一天总也要留一些时间好好思考、慢慢沉淀。 conceptualize 是一种非常难教的东西,我记得我念书时,有位老师信誓旦旦说要开一门课,教学生如何 conceptualize ,可是从来都没开成,因为这非常难教。我要提醒的是,在被很多材料和枝节淹没的时候,要适时跳出来想一想,所看到的东西有哪些意义?这个意义有没有广泛连结到更大层面的知识价值。
傅斯年先生来到***以后,同时担任中央研究院历史语言研究所的所长及台大的校长。台大有个傅钟每小时钟声有二十一响、敲二十一次。以前有一个人,写了一本书叫《钟声二十一响》,当时很轰动。他当时对这二十一响解释是说:因为台大的学生都很好,所以二十一响是欢迎国家元首二十一响的礼炮。不久前我发现台大在每一个重要的古迹下面竖一个铜牌,我仔细看看傅钟下的解释,才知道原来是因为傅斯年当台大校长的时候,曾经说过一句话:「人一天只有二十一个小时,另外三小时是要思考的。」所以才叫二十一响。我觉得这句话大有道理,可是我觉得三小时可能太多,因为研究生是非常忙的,但至少每天要留个三十分钟、一小时思考,想一想你看到了什么?学习跳到比你所看到的东西更高一点的层次去思考。
9. 找到学习的楷模
我刚到美国念书的时候,每次写报告头皮就重的不得了,因为我们的英文报告三、四十页,一个学期有四门课的话就有一百六十页,可是你连脚注都要从头学习。后来我找到一个好办法,就是我每次要写的时候,把一篇我最喜欢的论文放在旁边,虽然他写的题目跟我写的都没关系,不过我每次都看他如何写,看看他的注脚、读几行,然后我就开始写。就像最有名的男高音 Pavarotti 唱歌剧的时候都会捏着一条手帕,因为他说:「上舞台就像下地狱,太紧张了。」他为了克服紧张,他有习惯性的动作,就是捏着白手帕。我想当年那一篇论文抽印本就像是我的白手帕一样,能让我开始好好写这篇报告,我学习它里面如何思考、如何构思、如何照顾全体、如何用英文作脚注。好好的把一位大师的作品读完,开始模仿和学习他,是入门最好的方法,逐步的,你也开始写出自己的东西。我也常常鼓励我的学生,出国半年或是一年到国外看看。像现在国科会有各式各样的机会,可以增长眼界,可以知道现在的餐馆正在卖些什么菜,回来后自己要作菜也才知道要如何着手。
四、用两条腿走路,练习培养自己的兴趣
最后还有一点很重要的,就是我们的人生是两只脚,我们不是靠一只脚走路。做研究生的时代,固然应该把所有的心思都放在学业上,探索你所要探索的那些问题,可是那只是你的一只脚,另外还有一只脚是要学习培养一、两种兴趣。很多人后来会发现他的右脚特别肥重(包括我自己在内),也就是因为忘了培养左脚。很多很有名的大学者最后都陷入极度的精神困扰之中,就是因为他只是培养他的右脚,他忘了培养他的左脚,他忘了人生用两只脚走路,他少了一个小小的兴趣或嗜好,用来好好的调解或是排遣自己。
去年夏天,香港《亚洲周刊》要访问我,我说:「我不想接受访问,我不是重要的人。」可是后来他们还是把一个简单的对话刊出来了,里面我只记得讲了一段话:做一个研究生或一个学者,有两个感觉最重要 -- 责任感与罪恶感。你一定要有很大的责任感,去写出好的东西,如果责任感还不够强,还要有一个罪恶感,你会觉得如果今天没有好好做几个小时的工作的话,会有很大的罪恶感。除非是了不得的天才,不然即使爱因斯坦也是需要很努力的。很多很了不得的人,他只是把所有的努力集中在一百页里面,他花了一千小时和另外一个人只花了十个小时,相对于来说,当然是那花一千个小时所写出来的文章较好。所以为什么说要赶快选定题目?因为如果太晚选定一个题目,只有一年的时间可以好好耕耘那个题目,早点选定可以有二、三年耕耘那个题目,是三年做出的东西好,还是一年的东西好?如果我们的才智都一样的话,将三年的努力与思考都灌在上面,当然比一年还要好。
五、营造卓越的大学,分享学术的氛围
现在很多人都在讨论,何谓卓越的大学?我认为一个好的大学,学校生活的一大部份,以及校园的许多活动,直接或间接都与学问有关,同学在咖啡厅里面谈论的,直接或间接也都会是学术相关的议题。教授们在餐厅里面吃饭,谈的是「有没有新的发现」?或是哪个人那天演讲到底讲了什么重要的想法?一定是沉浸在这种氛围中的大学,才有可能成为卓越大学。那种交换思想学识、那种互相教育的气氛不是花钱就有办法获得的。我知道钱固然重要,但不是唯一的东西。一个卓越的大学、一个好的大学、一个好的学习环境,表示里面有一个共同关心的焦点,如果没有的话,这个学校就不可能成为好的大学。
读博士的经验!读后,令人毛塞顿开的科研经典之作zz(转载)
1. 先看综述,后看论著
看综述搞清概念,看论著掌握方法
2. 早动手
在师兄师姐离开之前学会关键技术
3. 多数文章看摘要,少数文章看全文
掌握了一点查全文的技巧,往往会以搞到全文为乐,以至于没有时间看文章的内容,更不屑于看摘要。真正有用的全文并不多,过分追求全文是浪费,不可走极端。当然只看摘要也是不对的。
4. 集中时间看文献
看过总会遗忘。看文献的时间越分散,浪费时间越多。集中时间看更容易联系起来,形成整体印象。
5. 做好记录和标记
复印或打印的文献,直接用笔标记或批注。pdf 或html 格式的文献,可以用编辑器标亮或改变文字颜色。这是避免时间浪费的又一重要手段。否则等于没看。
6. 准备引用的文章要亲自看过。
转引造成的以讹传讹不胜枚举。
7. 注意文章的参考价值。
刊物的影响因子、文章的被引次数能反映文章的参考价值。但要注意引用这篇文章的其它文章是如何评价这篇文章的:支持还是反对,补充还是纠错。
8. 交流是最好的老师
做 实验遇到困难是家常便饭。你的第一反应是什么?反复尝试?放弃?看书?这些做法都有道理,但首先应该想到的是交流。对有身份的人,私下的请教体现你对他的 尊重;对同年资的人,公开的讨论可以使大家畅所欲言,而且出言谨慎。千万不能闭门造车。一个实验折腾半年,后来别人告诉你那是死路,岂不冤大头?
9. 最高层次的能力是表达能力
再 好的工作最终都要靠别人认可。表达能力,体现为写和说的能力,是需要长期培养的素质。比如发现一个罕见病例,写好了发一篇论著;写不好只能发一个病例报 道。比如做一个课题,写好了发一篇或数篇论著;写不好只能发一个论著摘要或被枪毙。一张图,一张表,无不是表达能力的体现。寥寥几百上千字的标书,可以赢 得大笔基金;虽然关系很重要,但写得太差也不行。有人说,我不学PCR,不学spss,只要学会ppt(powerpoint)就可以了。此话有一点道 理,实验室的boss 们表面上就是靠一串串ppt 行走江湖的。经常有研究生因思维敏捷条例清楚而令人肃然起敬。也经常有研究生不理解"为什么我做了大部分工作而老板却让另一个没怎么干活的人写了文章?让 他去大会发言?"你没有看到人家有张口就来的本事吗?
10. 学好英语,不学二外。
如今不论去日本还是欧洲,学术交流早已是英语的天下。你不必为看不懂一篇法语的文章而遗憾,写那篇文章的人正在为没学好英语而犯愁。如果英文尚未精通,暂且不要去学二外。
英文文章写作
1. 阅读10 篇文献,总结100 个常用句型和常用短语。经常复习。注意,文献作者必须是以英文为母语者,文献内容要与你的专业有关。这属于平时看文献的副产品。
2. 找3-5 篇技术路线和统计方法与你的课题接近的文章,精读。
写出论文的草稿。要按照标题、作者、摘要、背景、目的、材料、方法、结果、讨论、致谢、参考文献、图例、图、表、照片和说明的统一格式来写。这样做的好处是从它可以方便地改成任何杂志的格式。
3. 针对论文的每一部分,尤其是某种具体方法、要讨论的某一具体方面,各找5-8 篇文献阅读,充实完善。 这里讨论的只涉及英文表达,也只推荐给缺乏英文写作经验的人。
4. 找到你想投的杂志的稿约,再找2-3 篇该杂志的article,按它的格式改写。注
意,每次改写都要先另存为不同的文件名,以免出了问题不能恢复。
5. 找英文高手改。找不到合适的人,就去找提供英语论文编辑服务(English corr
ection and improvement,not translation)的公司 ,在此向有钱没时间的人强烈推荐。
文献管理
1. 下载电子版文献时(caj,pdf,html),把文章题目粘贴为文件名。
注意,文件名不能有特殊符号,要把 \ / : * ? < > | 以及 换行符 删掉。 每次
按照同样的习惯设置文件名,可以防止重复下载。
2. 不同主题存入不同文件夹。文件夹的题目要简短,如:PD,LTP,PKC,NO。
3. 看过的文献归入子文件夹,最起码要把有用的和没用的分开。
4. 重要文献根据重要程度在文件名前加001,002,003 编号,然后按名称排列图标,最重要的文献就排在最前了。
5. 复印或打印的文献,用打孔器(¥10-15)打孔,装入硬质文件夹(¥10-20/个)。
我们经常会在参考文献的引用上耍一些小聪明,殊不知这些都会降低论文质量。
1. 知而不引
明明借鉴了同行的类似工作,却故意不引用同行的类似工作,使自己工作看上去"新颖""领先"。实际上审稿的可能就是同行。
2. 断章取义
故意截取作者试图否定的部分来烘托自己的观点。
3. 引而不确
没有认真看原文,引文错漏。
4. 来源不实
某些字句来源不可靠(比如非正式的或非学术的出版物),且不注明来源。常见于一些统计数字。
5. 盲目自引
不是为了说明自己的工作与前期工作之间的关系,而是单纯为提高自己文章被引用次数而自引。
国内文章水平不高的几个原因:
1. 审稿人知识陈旧
年纪大的审稿人查文献和和上网的能力相当有限,无法核实该研究是否有意义,创新点在那里,方法是否可靠,结果是否可信。但匪夷所思的是他们经常提的审稿意见是"参考文献不够新"。
2. 选错审稿人
虽然一般指定两名审稿人,但编辑部经常让不懂分子生物学的人审分子生物学的文章,让不懂统计的人审统计处理比较复杂的文章。出于爱面子,很少有人提出"我不适合审这篇文章"。
3. 关系文章
有了关系,什么都简单了。
4. 不承认阴性结果
诚实的阴性结果被认为无意义。怪不得有人大声疾呼"我要办一本阴性杂志"。
5. 造假
任何人都不愿意成为制度的牺牲品。出不来预期结果就没法交差。为生存计,为按期毕业计,造吧。
动态的科研
1. 科研靠积累。
象 伦琴发现X 射线那样凭借一次简单观察就得诺贝尔奖的机会越来越少。更多的科研成果来自于实验室长期积累。最终实至名归。做科研不要指望一步登天。设计课题不要好高骛 远。基金评审也是这样。没有前期积累,获得资助的可能性小。选导师要想好:你是要白手起家,还是要为人作嫁?
2. 文献要追踪。
开题时通过查文献了解的情况,到结题的时候可能有很大不同。实验过程中要注意追踪。运气好,你可以得到更多的线索;运气不好,发现别人抢先了。据此修正你的实验。写论文之前一定要重新查一遍文献。
3. 记录要复习。
前面的实验记录要经常复习。随着经验的增加和认识的提高,你会发现最初的判断未必正确。
我曾经向一些比我有经验的人请教"什么是科研",他们没有正面回答我,只是给我打了五个比方。
1. 科研是流行歌曲
什么流行用什么,什么流行做什么。张口生物芯片,闭口纳米技术。老板是追星一族,流行的就是最好的。
2. 科研是移花接木
设计课题?课题怎么是设计出来的呢?是拼出来的。A 的材料,B 的方法,C 的指标,D 的意义。
3. 科研是傻瓜相机
原理搞不懂?恕我老朽,没时间看原理了。我能折腾,多折腾几次就出来了。为什么要做这一步?老板心里明白就行了!他每周安排的活儿我还干不完呢。
4. 科研是照葫芦画瓢
综述不会写?抄啊。论文不会写?套啊。反正不会有人追究。无知者无畏!
5. 科研是垃圾
实验完成了,论文发表了,答辩通过了。老板语重心长地说:"你们走后,这些都是垃圾"。
晕!倒!挣扎!再倒!
他们没有骗我,实用主义自有它的道理。但我从此不再随便批判国内的科研水平了,因为在某些时候我也重复着同样的故事。
写毕业论文
1. 先列提纲
不列提纲,上来就写,是坏习惯。几百字没问题,几千字勉强,几万字就难了。必须列出写作提纲,再充实完善,以保证思路的连贯和字数的均衡。
2. 平时多写
及时总结阶段性的工作,多写文章多投稿。到最后阶段,把这些文字有机地组合起来,就是一篇很好的毕业论文。
3. 不要罗列所有数据
为了保证毕业论文的分量,研究生往往会观测较多的指标。但毕业论文并非数据越多越好。一定要舍弃那些与主旨关系不大的数据。否则,要么显得累赘松散,要么成为破绽。
4. 打印修改
在电脑上直接修改,会遗漏很多错误。要尽可能地减少任何错误,一定要打印出来修改。
5. 让别人指出错误
自己修改,仍然受个人习惯的局限。错误摆在那里,却熟视无睹。让别人给你指出错误吧 ,不管他与你是不是同一专业。
怎样读文献
1. 目标:
漫 无目的则毫无效率,抓不住重点才效率低下。选题之前可能会有一段时间处于迷茫状态,不知从哪入手。胡乱看了大量文献,却不知所以然。在导师的指导下,在同 行的启发下,有些人可以迅速明确目标,有的放矢,入门就从这里开始。即使导师不导,没有定题,自己也要先设定一个具体的问题看文献。不管你将来做不做这些 东西,总比没有目标好得多,保证有收获。科研的一般法则是共通的。
2. 层次:
对于一个具体的课题来说,相关文献分属于 三个层次:研究方向、研究领域、研究课题。例如有人研究干细胞定向分化治疗帕金森病,对他来说,研究方向就是帕金森病,研究领域是帕金森病的干细胞治疗, 研究课题是某种物质诱导干细胞定向分化为分泌多巴胺的神经细胞。看文献时要分清手上的文献是属于那个层次,这决定你对它要掌握到什么程度。
研究方向层次的文献:一般涉及,基础知识,学科水准,了解当前重大进展与趋势,达到专业人员水平;
研究领域层次的文献:了解焦点与热点,已/正/将进行的课题,达到专家水平;
研究课题层次的文献:要全面,了解历史、现状、展望、主要方法、手段,达到No1专家水平。
正确分辨文章的层次,才能把精力用到点子上。
3. 形式:
广 义的文献包括可以阅读的所有出版形式。教科书、专著、会议摘要汇编、期刊、网页、甚至ppt文件。比如要了解免疫应答的基本形式,最好是看教科书;要参考 大鼠脑立体定位图谱,最好是看专著;要知道最新进展,最好是查阅期刊;要了解别人的研究动向,最好是参会或看会议论文汇编。不要找错信息源。
4. 程度:
对 文献的熟悉程度不同,阅读文献的方式大不相同。新手学习式阅读,逐字逐句,搞清细节,掌握最基本的知识点。最初的十几、几十篇要精读,精华的几篇甚至要背 诵。老手搜索式阅读,已熟悉各种研究的常见模式和一般套路,能够迅速提取关键信息,把握思路,经常不按常规顺序阅读。有人看图说话,有人辨数识字。高手批 判式阅读,一针见血,直指问题所在。实际上没有一篇论文是无懈可击的。新手要稳,老手要准,高手要狠。新手、老手、高手的代表人物分别是研究生、导师和审 稿人,但认真钻研的研究生完全可以在3年中实现从新手到高手的嬗变。对自己有清醒的定位,才能选择正确的阅读方式。
5. 矛盾:
文 献读的多了,脑子里塞满了信息。公说公有理,婆说婆有理,反而无所适从?为了解决这个问题,循证医学划分临床试验证据的等级;同理,我们看文献也要重视实 验证据的强度。发现矛盾,是第一步;找出异同,是第二步;思考解决,是第三步。从相互矛盾的结论推导中发现矛盾的根源,此时如能跳出圈外,不走思维定势, 从原始的科学问题出发,"无招胜有招",真正是到达另外一种境界了。何必翻译外国人的综述谎称自己的综述?何必重复别人做过的实验谎称自己的思路?
(注:转自此博客,未找到原始出处)
Mapping out a Research Agenda
• To satisfy intellectual curiosity
• To better understand things
• To be at the forefront of an exciting, technical field
• To always be learning new things
• Because that’s what professors do!
2.Helpful Personal Qualities in Pursuing Research
• Creativity
• Curiosity
• Independence of thought
• Good communication skills
• Perseverance
• Self-discipline
• Interaction skills
3.Choosing a Research Problem
• Problem should be important
• Problem should hold your personal interest
• Problem should have depth, in terms of aspects possibly available for investigation
• Problem might come from questioning existing literature
4.How to proceed?
• Set aside uninterruptible blocks of 'research thinking time' in your weekly schedule
• Familiarize yourself with previous work from the literature
• Critically examine previous approaches, questioning generality, practicality, validation
• Frame long-term questions to be answered
• Use short-term objectives to subdivide research into manageable pieces
– Divide work into investigations that ‘fit’ into a coherent whole
– Make progress one paper at a time
• Know what it means to ‘solve a problem’ or validate a technique
• Write papers and give talks about your work
– Intuition, intuition, intuition
– Exercise: do an in-the-elevator summary
• Develop a personal style
– One at a time vs juggling several projects
• Allow your graduate students to suggest explorations
• Re-examine your research achievements at regular intervals, to ensure progress towards answering long-term questions
5.SE Research
• What practical SW problems are you addressing?
• How will you validate your approach?
• How can you ‘keep up’ with this broad area of CS&E?
– Attend conferences and network
– Pick favorite journals and other research groups and periodically visit their websites
6.Specific Techniques
• Establish a reading group with your students
• Summarize attended conferences to others, to discuss key research issues
encountered
– 2-3 sentence summaries of each presentation
• Keep a research notebook where you can jot down ideas for later consideration
– Go back and look at your entries!
• Teach a graduate seminar in your area of interest
– Teaching is a learning experience
• Attend workshops, especially those with work-in-progress presentations
• Participate in grant evaluation panels and program committees
• Leverage your efforts with graduate students
• Use senior faculty mentor(s)
– e.g., Obtain examples of funded proposals
7.Possible Pitfalls
• Switch of research areas during junior faculty years
– Requires large time investment up front
• Controversial/risky research areas
• Obtaining negative results
• Interdisciplinary work
8.Collaboration
• Con: Need for junior faculty to establish a personal research identity
• Con: May be time-consuming
• Pro: Projects can be more complex and more realistic
• Pro: Allows groups to tap into personal strengths of participants
9.Biggest Challenge
How to develop a coherent research agenda with limited time to do so, while juggling the responsibilities of a junior faculty?
(by Barbara G. Ryder,Rutgers University)
2009年4月2日星期四
Ubuntu 8.10 U盘安装
1.准备工作:
1)Ubuntu 8.10 镜像文件:ubuntu-8.10-desktop-i386.iso
2)U盘,1G以上
2.安装:
1)用虚拟光驱加载iso文件,把里面的文件全部拷贝到u盘中,点u盘中umenu.exe文件,选择安装,然后选择帮我从CD启动,然后就重启,进入系统后点击安装图标
2)接下来的安装过程参考: 《零命令玩转Ubuntu 8.10(LiveCD光盘安装篇)》
2009年3月25日星期三
关于const char*, char const*, char*const
const char*, char const*, char*const的区别问题几乎是C++面试中每次都会有的题目。
事实上这个概念谁都有,只是三种声明方式非常相似很容易记混。
Bjarne在他的The C++ Programming Language里面给出过一个助记的方法:
把一个声明从右向左读。
char * const cp; ( * 读成 pointer to )
cp is a const pointer to char
const char * p;
p is a pointer to const char;
char const * p;
同上因为C++里面没有const*的运算符,所以const只能属于前面的类型。
C++标准规定,const关键字放在类型或变量名之前等价的。
const int n=5; //same as below
int const m=10;
const int *p; //same as below const (int) * p
int const *q; // (int) const *p
char ** p1;
// pointer to pointer to char
const char **p2;
// pointer to pointer to const char
char * const * p3;
// pointer to const pointer to char
const char * const * p4;
// pointer to const pointer to const char
char ** const p5;
// const pointer to pointer to char
const char ** const p6;
// const pointer to pointer to const char
char * const * const p7;
// const pointer to const pointer to char
const char * const * const p8;
// const pointer to const pointer to const char
说到这里,我们可以看一道以前Google的笔试题:
[题目]const char *p="hello";
foo(&p);//函数foo(const char **pp)
下面说法正确的是[]
A.函数foo()不能改变p指向的字符串内容
B.函数foo()不能使指针p指向malloc生成的地址
C.函数foo()可以使p指向新的字符串常量
D.函数foo()可以把p赋值为 NULL.
至于这道题的答案是众说纷纭。针对上面这道题,我们可以用下面的程序测试:
#include
#include
#include
void foo(const char **pp)
{
// *pp=NULL;
// *pp="Hello world!";
*pp = (char *) malloc(10);
snprintf(*pp, 10, "hi google!");
// (*pp)[1] = 'x';
}
int
main()
{
const char *p="hello";
printf("before foo %s\n",p);
foo(&p);
printf("after foo %s\n",p);
p[1] = 'x';
return;
}
结论如下:
1. 在foo函数中,可以使main函数中p指向的新的字符串常量。
2. 在foo函数中,可以使main函数中的p指向NULL。
3. 在foo函数中,可以使main函数中的p指向由malloc生成的内存块,并可以在main中用free释放,但是会有警告。但是注意,即使在foo中让p指向了由malloc生成的内存块,但是仍旧不能用p[1]='x';这样的语句改变p指向的内容。
4. 在foo中,不能用(*pp)[1]='x';这样的语句改变p的内容。
所以,感觉gcc只是根据const的字面的意思对其作了限制,即对于const char*p这样的指针,不管后来p实际指向malloc的内存或者常量的内存,均不能用p[1]='x'这样的语句改变其内容。但是很奇怪,在foo里面,对p指向malloc的内存后,可以用snprintf之类的函数修改其内容。
c++静态成员小结,c++,static(转)
静态类成员包括静态数据成员和静态函数成员两部分。
一 静态数据成员:
类体中的数据成员的声明前加上static关键字,该数据成员就成为了该类的静态数据成员。和其他数据成员一样,静态数据成员也遵守public/protected/private访问规则。同时,静态数据成员还具有以下特点:
1.静态数据成员的定义。
静态数据成员实际上是类域中的全局变量。所以,静态数据成员的定义(初始化)不应该被放在头文件中。
其定义方式与全局变量相同。举例如下:
xxx.h文件
class base{
private:
static const int _i;//声明,标准c++支持有序类型在类体中初始化,但vc6不支持。
};
xxx.cpp文件
const int base::_i=10;//定义(初始化)时不受private和protected访问限制.
注:不要试图在头文件中定义(初始化)静态数据成员。在大多数的情况下,这样做会引起重复定义这样的错误。即使加上#ifndef #define #endif或者#pragma once也不行。
2.静态数据成员被 类 的所有对象所共享,包括该类派生类的对象。即派生类对象与基类对象共享基类的静态数据成员。举例如下:
class base{
public :
static int _num;//声明
};
int base::_num=0;//静态数据成员的真正定义
class derived:public base{
};
main()
{
base a;
derived b;
a._num++;
cout<<"base class static data number _num is"<
cout<<"derived class static data number _num is"<
// 结果为1,2;可见派生类与基类共用一个静态数据成员。
3.静态数据成员可以成为成员函数的可选参数,而普通数据成员则不可以。举例如下:
class base{
public :
static int _staticVar;
int _var;
void foo1(int i=_staticVar);//正确,_staticVar为静态数据成员
void foo2(int i=_var);//错误,_var为普通数据成员
};
4.★静态数据成员的类型可以是所属类的类型,而普通数据成员则不可以。普通数据成员的只能声明为 所属类类型的 指针或引用。举例如下:
class base{
public :
static base _object1;//正确,静态数据成员
base _object2;//错误
base *pObject;//正确,指针
base &mObject;//正确,引用
};
5.★这个特性,我不知道是属于标准c++中的特性,还是vc6自己的特性。
静态数据成员的值在const成员函数中可以被合法的改变。举例如下:
class base{
public:
base(){_i=0;_val=0;}
mutable int _i;
static int _staticVal;
int _val;
void test() const{//const 成员函数
_i++;//正确,mutable数据成员
_staticVal++;//正确,static数据成员
_val++;//错误
}
};
int base::_staticVal=0;
二,静态成员函数
静态成员函数没有什么太多好讲的。
1.静态成员函数的地址可用普通函数指针储存,而普通成员函数地址需要用 类成员函数指针来储存。举例如下:
class base{
static int func1();
int func2();
};
int (*pf1)()=&base::func1;//普通的函数指针
int (base::*pf2)()=&base::func2;//成员函数指针
2.静态成员函数不可以调用类的非静态成员。因为静态成员函数不含this指针。
3.静态成员函数不可以同时声明为 virtual、const、volatile函数。举例如下:
class base{
virtual static void func1();//错误
static void func2() const;//错误
static void func3() volatile;//错误
};
最后要说的一点是,静态成员是可以独立访问的,也就是说,无须创建任何对象实例就可以访问。
(from: http://blog.csdn.net/sailing0123/archive/2007/07/12/1686269.aspx)
C++中三个修饰符的深层剖析:Static, Const, Inline
static 是c++中很常用的修饰符,它被用来控制变量的存储方式和可见性,下面我将从 static 修饰符的产生原因、作用谈起,全面分析static 修饰符的实质。
static 的两大作用:
一、控制存储方式:
static被引入以告知编译器,将变量存储在程序的静态存储区而非栈上空间。
1、引出原因:函数内部定义的变量,在程序执行到它的定义处时,编译器为它在栈上分配空间,大家知道,函数在栈上分配的空间在此函数执行结束时会释放掉,这样就产生了一个问题: 如果想将函数中此变量的值保存至下一次调用时,如何实现?
最容易想到的方法是定义一个全局的变量,但定义为一个全局变量有许多缺点,最明显的缺点是破坏了此变量的访问范围(使得在此函数中定义的变量,不仅仅受此函数控制)。
2、 解决方案:因此c++ 中引入了static,用它来修饰变量,它能够指示编译器将此变量在程序的静态存储区分配空间保存,这样即实现了目的,又使得此变量的存取范围不变。
二、控制可见性与连接类型 :
static还有一个作用,它会把变量的可见范围限制在编译单元中,使它成为一个内部连接,这时,它的反义词为”extern”.
static作用分析总结:static总是使得变量或对象的存储形式变成静态存储,连接方式变成内部连接,对于局部变量(已经是内部连接了),它仅改变其存储方式;对于全局变量(已经是静态存储了),它仅改变其连接类型。
类中的static成员:
一、出现原因及作用:
1、需要在一个类的各个对象间交互,即需要一个数据对象为整个类而非某个对象服务。
2、同时又力求不破坏类的封装性,即要求此成员隐藏在类的内部,对外不可见。
类的static成员满足了上述的要求,因为它具有如下特征:有独立的存储区,属于整个类。
二、注意:
1、对于静态的数据成员,连接器会保证它拥有一个单一的外部定义。静态数据成员按定义出现的先后顺序依次初始化,注意静态成员嵌套时,要保证所嵌套的成员已经初始化了。消除时的顺序是初始化的反顺序。
2、类的静态成员函数是属于整个类而非类的对象,所以它没有this指针,这就导致了它仅能访问类的静态数据和静态成员函数。
const 是c++中常用的类型修饰符,但我在工作中发现,许多人使用它仅仅是想当然尔,这样,有时也会用对,但在某些微妙的场合,可就没那么幸运了,究其实质原 由,大多因为没有搞清本源。故在本篇中我将对const进行辨析。溯其本源,究其实质,希望能对大家理解const有所帮助,根据思维的承接关系,分为如 下几个部分进行阐述。
c++中为什么会引入const
c++的提出者当初是基于什么样的目的引入(或者说保留)const关键字呢?,这是一个有趣又有益的话题,对理解const很有帮助。
1. 大家知道,c++有一个类型严格的编译系统,这使得c++程序的错误在编译阶段即可发现许多,从而使得出错率大为减少,因此,也成为了c++与c相比,有着突出优点的一个方面。
2. c中很常见的预处理指令 #define variablename variablevalue 可以很方便地进行值替代,这种值替代至少在三个方面优点突出:
一是避免了意义模糊的数字出现,使得程序语义流畅清晰,如下例:
#define user_num_max 107 这样就避免了直接使用107带来的困惑。
二是可以很方便地进行参数的调整与修改,如上例,当人数由107变为201时,进改动此处即可,
三是提高了程序的执行效率,由于使用了预编译器进行值替代,并不需要为这些常量分配存储空间,所以执行的效率较高。
鉴于以上的优点,这种预定义指令的使用在程序中随处可见。
3. 说到这里,大家可能会迷惑上述的1点、2点与const有什么关系呢?,好,请接着向下看:
预处理语句虽然有以上的许多优点,但它有个比较致命的缺点,即,预处理语句仅仅只是简单值替代,缺乏类型的检测机制。这样预处理语句就不能享受c++严格类型检查的好处,从而可能成为引发一系列错误的隐患。
4.好了,第一阶段结论出来了:
结论: const 推出的初始目的,正是为了取代预编译指令,消除它的缺点,同时继承它的优点。
现在它的形式变成了:
const datatype variablename = variablevalue ;
为什么const能很好地取代预定义语句?
const 到底有什么大神通,使它可以振臂一挥取代预定义语句呢?
1. 首先,以const 修饰的常量值,具有不可变性,这是它能取代预定义语句的基础。
2. 第二,很明显,它也同样可以避免意义模糊的数字出现,同样可以很方便地进行参数的调整和修改。
3. 第三,c++的编译器通常不为普通const常量分配存储空间,而是将它们保存在符号表中,这使得它成为一个编译期间的常量,没有了存储与读内存的操作, 使得它的效率也很高,同时,这也是它取代预定义语句的重要基础。这里,我要提一下,为什么说这一点是也是它能取代预定义语句的基础,这是因为,编译器不会 去读存储的内容,如果编译器为const分配了存储空间,它就不能够成为一个编译期间的常量了。
4. 最后,const定义也像一个普通的变量定义一样,它会由编译器对它进行类型的检测,消除了预定义语句的隐患。
const 使用情况分类详析
1.const 用于指针的两种情况分析:
int const *a; file://a可变,*a不可变
int *const a; file://a不可变,*a可变
分析:const 是一个左结合的类型修饰符,它与其左侧的类型修饰符和为一个类型修饰符,所以,int const 限定 *a,不限定a。int *const 限定a,不限定*a。
2.const 限定函数的传递值参数:
void fun(const int var);
分析:上述写法限定参数在函数体中不可被改变。由值传递的特点可知,var在函数体中的改变不会影响到函数外部。所以,此限定与函数的使用者无关,仅与函数的编写者有关。
结论:最好在函数的内部进行限定,对外部调用者屏蔽,以免引起困惑。如可改写如下:
void fun(int var){
const int & varalias = var;
varalias ....
.....
}
3.const 限定函数的值型返回值:
const int fun1();
const myclass fun2();
分析:上述写法限定函数的返回值不可被更新,当函数返回内部的类型时(如fun1),已经是一个数值,当然不可被赋值更新,所以,此时const无意 义,最好去掉,以免困惑。当函数返回自定义的类型时(如fun2),这个类型仍然包含可以被赋值的变量成员,所以,此时有意义。
4. 传递与返回地址: 此种情况最为常见,由地址变量的特点可知,适当使用const,意义昭然。
5. const 限定类的成员函数:
class classname {
public:
int fun() const;
.....
}
注意:采用此种const 后置的形式是一种规定,亦为了不引起混淆。在此函数的声明中和定义中均要使用const,因为const已经成为类型信息的一部分。
获得能力:可以操作常量对象。
失去能力:不能修改类的数据成员,不能在函数中调用其他不是const的函数。
在本文中,const方面的知识我讲的不多,因为我不想把它变成一本c++的教科书。我只是想详细地阐述它的实质和用处. 我会尽量说的很详细,因为我希望在一种很轻松随意的气氛中说出自己的某些想法,毕竟,编程也是轻松,快乐人生的一部分。有时候,你会惊叹这其中的世界原来 是如此的精美。
在前面谈了const后,现在再来谈一下inline这个关键字,之所以把这个问题放在这个位置,是因为inline这个关键字的引入原因和const十分相似,下面分为如下几个部分进行阐述。
c++中引入inline关键字的原因:
inline 关键字用来定义一个类的内联函数,引入它的主要原因是用它替代c中表达式形式的宏定义。
表达式形式的宏定义一例:
#define expressionname(var1,var2) (var1+var2)*(var1-var2)
为什么要取代这种形式呢,且听我道来:
1. 首先谈一下在c中使用这种形式宏定义的原因,c语言是一个效率很高的语言,这种宏定义在形式及使用上像一个函数,但它使用预处理器实现,没有了参数压栈,代码生成等一系列的操作,因此,效率很高,这是它在c中被使用的一个主要原因。
2. 这种宏定义在形式上类似于一个函数,但在使用它时,仅仅只是做预处理器符号表中的简单替换,因此它不能进行参数有效性的检测,也就不能享受c++编译器严 格类型检查的好处,另外它的返回值也不能被强制转换为可转换的合适的类型,这样,它的使用就存在着一系列的隐患和局限性。
3. 在c++中引入了类及类的访问控制,这样,如果一个操作或者说一个表达式涉及到类的保护成员或私有成员,你就不可能使用这种宏定义来实现(因为无法将this指针放在合适的位置)。
4. inline 推出的目的,也正是为了取代这种表达式形式的宏定义,它消除了它的缺点,同时又很好地继承了它的优点。
为什么inline能很好地取代表达式形式的预定义呢?
对应于上面的1-3点,阐述如下:
1. inline 定义的类的内联函数,函数的代码被放入符号表中,在使用时直接进行替换,(像宏一样展开),没有了调用的开销,效率也很高。
2. 很明显,类的内联函数也是一个真正的函数,编译器在调用一个内联函数时,会首先检查它的参数的类型,保证调用正确。然后进行一系列的相关检查,就像对待任何一个真正的函数一样。这样就消除了它的隐患和局限性。
3. inline 可以作为某个类的成员函数,当然就可以在其中使用所在类的保护成员及私有成员。
在何时使用inline函数:
首先,你可以使用inline函数完全取代表达式形式的宏定义。
另外要注意,内联函数一般只会用在函数内容非常简单的时候,这是因为,内联函数的代码会在任何调用它的地方展开,如果函数太复杂,代码膨胀带来的恶果很可能会大于效率的提高带来的益处。 内联函数最重要的使用地方是用于类的存取函数。
如何使用类的inline函数:
简单提一下inline 的使用吧:
1.在类中定义这种函数:
class classname{
.....
....
getwidth(){return m_lpicwidth;}; // 如果在类中直接定义,可以不使用inline修饰
....
....
}
2.在类中声明,在类外定义:
class classname{
.....
....
getwidth(); // 如果在类中直接定义,可以不使用inline修饰
....
....
}
inline getwidth(){
return m_lpicwidth;
}
在本文中,谈了一种特殊的函数,类的inline函数,它的源起和特点在某种说法上与const很类似,可以与const搭配起来看。另外,最近有许多朋友与我mail交往,给我谈论了许多问题,给了我很多启发,在此表示感谢。
2009年3月24日星期二
OTcl类-结构、定义、继承
Class--创建一个类(object class)
instproc--定义类的成员函数
instvar--定义类的成员变量,首先检查变量在此类和父类中已声明(declare),是则引用(reference),否则声明
-superclass--类继承,父类
$self——相当于C++中的this指针
new--创建一个类对象(object instance)
示例:
# add a member function call "great"
Class mom
mom instproc greet {} {
$self instvar age_
puts "$age_ year old mom say:
How are you doing?"
}
# Create a child class of "mom" called "kid"
#and overide the member function "greet"
Class kid -superclass mom
kid instproc greet {} {
$self instvar age_
puts "$age_ year old kid say:
What's up, dude?"
}
#Create a mom and a kid object, set each age
set a [new mom]
$a set age_ 45
set b [new kid]
$b set age_ 15
#Calling member function "greet" of each object
$a greet
$b greet
#end
(参考ns by example ex-otcl.tcl)
GOD--General Operations Director
(from ns Tutorial)
The General Operations Director (GOD) object is used to store global information about the state of the environment, network, or nodes that an omniscent observer would have, but that should not be made known to any participant in the simulation.
Currently, the god object is used only to store an array of the shortest number of hops required to reach from one node to an other. The god object does not calculate this on the fly during simulation runs, since it can be quite time consuming. The information is loaded into the god object from the movement pattern file where lines of the form
$ns_ at 899.642 "$god_ set-dist 23 46 2" |
2009年3月18日星期三
NS-2 Trace Formats
NS-2 Trace Formats
This document lists various trace formats used by the NS-2 Network Simulator. The information is based on NS2 version 2.1b9a. The best effort has been made to document correctly all of the trace formats, but be warned that this list is not complete, and may contain errors. If you find any errors or omissions, please edit this page to fix the problems.
The various traces begin with a single character or abbreviation that indicates the type of trace, followed by a fixed or variable trace format. The tables listing the trace formats differ between fixed and variable trace formats:
- For fixed trace formats, the table lists the event the triggers the trace under the Event heading and the characters that start the trace under the Abbreviation heading. The format is listed across the last two columns, and the the type and value for each element of the format are listed beneath under the Type and Value headings. Some events have multiple trace formats.
- For variable trace formats, the table lists the event the triggers the trace under the Event heading and the characters that start the trace under the Abbreviation heading. The last three columns list the possible flags, types, and values for the event under the Flag, Type, and Value headings.
Normal trace formats
This information comes from "The ns Manual" "Trace and Monitoring Support: Trace File Format" chapter. This trace is used normal wired operations. The trace starts with one of four possible characters.
The tables that list the additional wireless trace information do not have an Abbreviation column, since the information is appended to the end of the regular wireless trace format.
Event | Abbreviation | Type | Value |
---|---|---|---|
Normal Event | r: Receive d: Drop e: Error +: Enqueue -: Dequeue | %g %d %d %s %d %s %d %d.%d %d.%d %d %d | |
double | Time | ||
int | Source Node | ||
int | Destination Node | ||
string | Packet Name | ||
int | Packet Size | ||
string | Flags | ||
int | Flow ID | ||
int | Source Address | ||
int | Source Port | ||
int | Destination Address | ||
int | Destination Port | ||
int | Sequence Number | ||
int | Unique Packet ID |
The flags are read as follow (the "value" is displayed instead of `-` if flag is set). Each row is one of the "slots", from left to right. From from ns/trace/trace.cc.
Value | Meaning |
---|---|
C | ECN-echo |
P | pri_ (supposedly unused) |
- | |
A | Congestion Action |
E | Congestion Experienced (CE) |
F | Fast Start |
N | ECN-capable |
SCTP-only |
Depending on the packet type, the trace may log additional information:
Event | Type | Value |
---|---|---|
TCP Trace | %d 0x%x %d %d | |
int | Ack Number | |
hexadecimal | Flags | |
int | Header Length | |
int | Socket Address Length | |
Satellite Trace | %.2f %.2f %.2f %.2f | |
double | Source Latitude | |
double | Source Longitude | |
double | Destination Latitude | |
double | Destination Longitude |
Wireless Trace Formats
This section covers the various wireless trace format:
- Old Wireless Trace Formats
- New Wireless Trace Formats
- AODV Trace Formats
- DSDV Trace Formats
- DSR Trace Formats
- TORA Trace Formats
- Mobile node movement and energy trace formats
Old Wireless Trace Formats
This information comes from "The ns Manual" "Mobile Networking in ns: Trace Support" chapter, and the "trace/cmu-trace.cc" file. Wireless traces begin with one of four characters followed by one of two different trace formats, depending on whether the trace logs the X and Y coordinates of the mobile node.
Event | Abbreviation | Type | Value |
---|---|---|---|
Wireless Event | s: Send r: Receive d: Drop f: Forward | %.9f %d (%6.2f %6.2f) %3s %4s %d %s %d [%x %x %x %x] | |
%.9f _%d_ %3s %4s %d %s %d [%x %x %x %x] | |||
double | Time | ||
int | Node ID | ||
double | X Coordinate (If Logging Position) | ||
double | Y Coordinate (If Logging Position) | ||
string | Trace Name | ||
string | Reason | ||
int | Event Identifier | ||
string | Packet Type | ||
int | Packet Size | ||
hexadecimal | Time To Send Data | ||
hexadecimal | Destination MAC Address | ||
hexadecimal | Source MAC Address | ||
hexadecimal | Type (ARP, IP) |
Some older versions of NS2 (such as 2.1b5) have five hexidecimal values between the square braces. The first hexidecimal value is the MAC frame control information, and the remaining hexidecimal values are the same as listed above.
Depending on the packet type, the trace may log additional information:
Event | Type | Value |
---|---|---|
ARP Trace | ------- [%s %d/%d %d/%d] | |
string | Request or Reply | |
int | Source MAC Address | |
int | Source Address | |
int | Destination MAC Address | |
int | Destination Address | |
IP Trace | ------- [%d:%d %d:%d %d %d] | |
int | Source IP Address | |
int | Source Port | |
int | Destination IP Address | |
int | Destination Port Address | |
int | TTL | |
int | Next hop node (or 0 if not valid) | |
DSR Trace | %d [%d %d] [%d %d %d %d->%d] [%d %d %d %d- |
无线网络仿真Trace 旧格式
r 160.093884945_6_RTR---5 tcp 1492[a2 4 6 800]-------[65536:0 16777984:0 31 16777984][1 0]2 0ldN xs,x-LM0
7PQaF3NE0Here we see a TCP data packet being received by a node with id of 6.UID of this pkt is 5 with a cmn hdr size of 1492.The mac details shows an IP pkt(ETHERTYPE_IP is defined as 0x0800,ETHERTYPE_ARP is 0x0806),mac-id of this receiving node is 4.That of the sending node is 6 and expected time to send this data pkt over the wireless channel is a2(hex2dec conversion:160+2 sec).Additionally,IP traces information about IP src and destination addresses.The src translates(using a 3 level hier-address of 8/8/8)to a address string of 0.1.0 with port of 0.The dest address is 1.0.3 with port address of 0.The TTL value is 31 and the destination was a hop away from the src.Additionally TCP format prints information about tcp seqno of 1,ackno of 0.See other formats described in~ns//cmu-trace.cc for DSR,UDP/MESSAGE,TCP/ACK and CBR packet types.西子博客%PV:JlG+m}_R
西子博客%a,TLgu*tOther trace formats are also used by the routing agents(TORA and DSR)to log certain special routing events like"originating"(adding a SR header to a packet)or"ran off the end of a source route"indicating some sort of routing problem with the source西子博客.~&G$z1~Cw"lroute etc.These special event traces begin with"S"for DSR and"T"for Tora and may be found in~ns/tora/tora.cc for TORA and~ns/dsr/dsrgent.cc for DSR routing agent.
(from: http://blog.hz0752.com/?uid-15519-action-viewspace-itemid-163973)
2009年3月2日星期一
Ad-Hoc Routing Protocols (Characteristics)
Classification of the Characteristics of Ad-Hoc Protocols and Routing-StrategiesExtension of Lang2003 and Murthy2004.
In general, routing can be divided into two strategies:Adaptive Routing vs. Not-Adaptive Routing
Adaptive: Changes of the network-topology are adapted by the routing-strategy
Not-Adaptive: The routing is done using fixed tables
For Ad-Hoc-Netorks, only adaptiv strategies are usefull.Reactive Routing/On-Demand Routing vs. Proactive Routing/Table-Driven Routing vs. Hybrid Routing
Reactive/On-Demand
A route is only calculated, when it is needed
Does not try to keep routing-information everytime to all node
Proactive/Table-Driven
Routes are calculated before one is needed
Tries to keep routing-information to all nodes everytime up-to-date
Update of the tables:
Event-driven: only if a change is recoginzed
Periodically
Hybrid
Reactive and Proactive at the same time
E.g.: Intra-Zone: Proactiv, Inter-Zone: Reactiv
Distance-Vector Routing vs. Link-State Routing
Distance-Vector
Calculates the distance to all nodes
Exchange of these information only with the neighbours
Link-State
Mesuare the distance to the neighbours
Exchange of these information with all nodes
Flat Routing vs. Hierarchical Routing / Clustered Routing
Hierarchical Routing / Clustered Routing:
Trying to structure/cluster the network
Clusterhead:
Responsible for the creation and extension of a cluster
Builds up a hierarchie of clusters
Manages the communication inside a cluster
Gateway-Node:
Responsible for the communication between clusters
Maybe bottleneck
Flat Routing:
Network has no hierarchy
Geographical Routing / Positionbased Routing / Direction-Based Routing
No routing-tables
Information is send in any way in the direction of the destination
No overhead to find or update routes But:
Position required
Determination of the position via
internal search-process
external service
Uniform vs. Non-Uniform
Uniform: All nodes are equal
Non-Uniform: Some nodes have special roles, e.g. Clusterhead, Gateway-Node
Full vs. Reduced Topology Information
Full: All topology-information will be distributed
Reduced: Only a fraction of the known topology-information will be distributed
Past History vs. Prediction
Past History: Information of past statuses is used to make a decision
Prediction: Expectation of future statuses are used to make routing decisions
Broadcast
Local Multicast: Only to some nodes in transmission-distance
Local Broadcast: Only to the nodes in transmission-distance
Networkwide Broadcast: Flooding
Restricted Networkwide Broadcast: Flooding with a time-to-live
Recovery Strategy
Mechansims to keep or to restore routes
Link-Reversal Routing
Does not try to find an (somehow) optimal way
Only tries to find any way
Source-Routing
The sender specifies the way to go
Under the circumstances, a node inbetween can decide to redefine the way
Route-Selection-Strategies
Power-Aware Routing
Signal Strength
Link Stability
Shortest Path
Link-State Routing / Distance-Vector Routing
Direction-Based Routing / Positionbased Routing / Geographical Routing
Link-Reversal Routing
Multipath Routing "Ad-Hoc Protocols (Characteristics)" wird erwähnt auf: Ad-Hoc Protocols
理解NS2中的OTcl/tclCL
题记
真正触动我写这篇短文的原因是试图理解NS2的基本原理. 在"the NS2 manual"中, 解释了为什么采用了两种语言来建构整个系统, 然后在第三章描述了tclcl的六个类. 这个手册中的对各个类描述性文字让我如坠雾里, 不明所以. 我查找了一些NS2的文章和站点, 有一些ppt倒是很形象, 但我的认识上总有些模糊. 后来, 我逐渐明白到OTcl/Tcl的嵌入特性. --- 这才是理解NS2框架的关键.
Abstract
本文的主要目的是理解NS2的architecture, 了解NS2的基本原理. NS2采用了Tcl/C++分裂的模型, 在这种模型中OTcl是处于比较关键的位置, NS2采用了Tcl的编程模式. 使用C++来编写应用实例, 使用OTcl来操纵这些实例. 理解了OTcl就理解了NS2的框架. 本文先简述Tcl语言的嵌入特性, 然后描述了NS2的应用场景, 进而分析NS2的架构, 以及实现该架构采用的技术.
Introduction
NS2是MIT的一个作品, 它是一个面向对象的网络仿真工具. 使用NS2可以完整的仿真整个网络环境, 只要你的机器足够快 :-) NS2使用一整套C++类库实现了大多数常见的网络协议以及链路层的模型, 使用这些类的实例我们就可以搭建起整个网络的模型, 而且包括了各个细节. --- 这简直就是一种梦想的实现, 试想如果手头能有这样一个工具, 我们就可以在单机环境中模拟网络的各个元素, 加深对网络的了解和认识; 同时, 加快我们开发新协议的速度.
与NS2类似的软件有OPNET, 这是一个商用的网络仿真软件, 据说它能够针对各款交换机和路由器来搭建网络, 够牛x. 与之相比, NS2是一个免费的软件, 它可以在Windows/Unix上运行, 我们可以看到NS2的所有源代码, 另外在学术界更多的是采用NS2来做仿真.
NS2采用了Tcl/C++分裂的模型来建构它的应用, 这样做的好处是显而易见的. 使用Tcl语言我们可以灵活的配置网络环境, 定制系统; 而采用C++来编程满足了我们对仿真效率的需要. 缺点也是明了的, 要同时维护两套代码, 对使用者要求较高.
NS2的Tcl/C++架构与Windows下的COM/VBScript编程模式有些类似, 使用VC来编写和实现COM对象, 然后使用VB来操纵COM对象. Windows提供了COM接口, 这就在系统范围内保证了这种机制的有效性. --- 这就是Windows的高明之处. 与之相比, NS2则能够使Tcl脚本解到它的C++类库结构, 同时按照它的类分级来创建对象. --- 这也很了不起.
要使用NS2来仿真开发一个新协议, 就必须对NS2的类库进行某些扩展. 撇开各个协议或链路的细节不谈, 对NS2的实现机制的了解是一个关键, 否则难免会疏漏百出. 如果不了解NS2的机制, 在刚开始开发协议时, 你看着NS2的代码可能会感觉到无处下手.
NS2的手册中对它的机制和原理主要在"the NS manual"一书的第三章, 但是这一章的内容写来就象tclcl类的简单介绍, 读来非常费解. 它对NS2的整体设计思路并没有交待的很清楚, 本文就打算解析这第三章背后的话.
以下的内容安排如下, 首先简单介绍Tcl语言的嵌入特性, 然后描述NS2的应用场景, 分析NS2的框架, 然后考察实现这个NS2框架所遇到的问题. 最后以一个新协议的添加作为例子, 感受NS2的仿真开发过程.
嵌入的Tcl
OTcl称为Objected Tcl, 它是在Tcl基础上的一个面向对象的封装. Tcl语言本身比较简单, 在NS2的主页上有一些相关的链接, 可以快速了解它的基本语法, 这里对Tcl的语法并不感兴趣.
Tcl是一种脚本语言, Tool Cammand Language. Tcl语言的吸引人之处在于它简单同时支持嵌入式应用. 大家都用过Ms Word或Emacs, 这两种编辑器之所以如此强大, 很大的原因是因为它们内嵌了VB或Lisp语言. 这些内嵌的脚本能够定制编译器的环境, 使应用变的非常灵活.
你可能还听说过Windows下的脚本引擎, 把一个脚本引擎嵌入到Windows应用中, 就可以使这个应用具有类似Word的这种能力. 在Unix下类似的脚本语言嵌入很早就有, 上面提到的Emacs就是一个例子. Tcl语言也支持内嵌, 具体做法是把Tcl的库链接到应用程序中去, 从而使应用具有解释Tcl语言的能力. 当然仅仅这么做是不够的, 你还要为这个应用定制开发一些Tcl命令.
(NS2为什么不使用Lisp作为内嵌? 这可能与OTcl语言本身也是MIT的作品有关, 而且是MIT的近期作品. 但是如果采用Lisp的话, 就可以在Emacs做这个仿真了, 呵呵, 想想都要偷笑了, 可惜呀. 毕竟我是个Emacs的拥护者.)
Figure 1是Tcl C与应用程序集成的原理图. Figure 1中黑线表示C的调用, 红线表示Tcl脚本的调用. 为了使应用能够解释Tcl语言, 必须在应用程序的源代码中嵌入Tcl C库, 这个库的主要目的是实现一个Tcl语言的Parser, 并且实现了Tcl语言的一些关键命令, 如set, while, proc等. 应用程序必须还要编写一些针对应用扩展的Tcl命令, 然后注册进Tcl C库, 同时, 应用程序可以使用Tcl_LinkVar使某些C变量与Tcl库中的环境变量绑定起来.
Tcl C库的一些函数接口如下:
Tcl_CreateInterp 创建Tcl的Parser;
Tcl_CreateCommand/Tcl_CreateObjCommand 注册应用相关的命令;
Tcl_Eval 执行一条Tcl命令;
Tcl_LinkVar 将应用程序中的变量与Tcl库环境中的变量绑定;
Tcl_GetVar/Tcl_SetVar 设定/获取Tcl库环境中的变量;
下面是一个取自文献2的例子: /*
* Example 47-1
* The initialization procedure for a loadable package.
*/
/*
* random.c
*/
#include
/*
* Declarations for application-specific command procedures
*/
int RandomCmd(ClientData clientData,
Tcl_Interp *interp,
int argc, char *argv[]);
int RandomObjCmd(ClientData clientData,
Tcl_Interp *interp,
int objc, Tcl_Obj *CONST objv[]);
/*
* Random_Init is called when the package is loaded.
*/
int Random_Init(Tcl_Interp *interp) {
/*
* Initialize the stub table interface, which is
* described in Chapter 46.
*/
if (Tcl_InitStubs(interp, "8.1", 0) == NULL) {
return TCL_ERROR;
}
/*
* Register two variations of random.
* The orandom command uses the object interface.
*/
Tcl_CreateCommand(interp, "random", RandomCmd,
(ClientData)NULL, (Tcl_CmdDeleteProc *)NULL);
Tcl_CreateObjCommand(interp, "orandom", RandomObjCmd,
(ClientData)NULL, (Tcl_CmdDeleteProc *)NULL);
/*
* Declare that we implement the random package
* so scripts that do "package require random"
* can load the library automatically.
*/
Tcl_PkgProvide(interp, "random", "1.1");
return TCL_OK;
}
这个例子完整的体现了如何初始化Tcl C库, 如何向Tcl库中注册命令. 这里不打算继续讨论Tcl C库的详细问题了. 毕竟, 我们的目的是为了理解Tcl的嵌入能力, 上面的原理图已经足够.
NS2采用的是OTcl来实现它的脚本语言内嵌, 原因是NS2还有一套C++类库. 这个C++类库实现了网络仿真的各个元素, 仅仅是Tcl来操纵这套类库有些困难. 这关系到NS2的实现, 我们在后面谈.
OTcl的语法设计
这一部分的内容主要取自文献1, 放在这里的目的有两个: 一是为了了解OTcl的面向对象扩展, 从而能够方便的理解一些NS2的代码; 二是理解OTcl的实现原理.
文献1中主要介绍的是对Tcl语言本身的面向对象扩展, 它没有讲述实现如何操纵一个C++的对象. 从代码上来看OTcl本身好象并没有实现对C++对象的操纵.
OTcl和tclCL的相关文献都比较少, 这也许就是对NS2诟病较多的原因. OTcl是MIT的一个流媒体项目VuSystem的辅产品, 它并不是最早提出的Object概念的Tcl, 文献1认为OTcl的特点是Dynamic, 能够动态地创建一个对象.
OTcl的语法扩展
对OTcl的语法描述, 更详细的见网页45678.
OTcl的语言设计采用了称为"Object Command"的方法. 每个命令可能被解释成对象, 而子命令被解释成传递给对象的消息. 文献1中认为这样做可以比较方便的实现Tk的消息机制达到某种一致, 同时对象作为Tcl语言中array, list, proc等要素的补充, 这种扩展显得比较自然.
下面是OTcl一个一段示例代码, 我们可以看到对象astack的子命令set, proc等作为消息传递给对象. 而且要注意它确实是动态的, 在代码解释过程中动态的添加属性和方法. Object astack
astack set things {}
astack proc put {thing} {
$self instvar things
set things [concat [list $thing] $things]
return $thing
}
astack proc get {} {
$self instvar things
set top [lindex $things 0]
set things [lrang $things 1 end]
return $top
}
astack put toast ==> toast
astack get ==> toast
astack destroy ==> {}
注意上面的instvar方法, 代码的前面用set定义了一个things的变量, 在方法内要操纵它必须使用instvar来声明, 有些怪, 是不是? 否则things将是一个方法内的局部变量. 下表是OTcl对象的方法:
name
description
class
创建一个对象
destroy
销毁一个对象
proc
定义Tcl对象方法
set
定义Tcl对象变量
instvar
绑定实例变量
info procs
列出Tcl对象的所有方法
info args
列出Tcl对象方法的参数格式
info body
列出Tcl对象方法的函数主体
info commands
列出Tcl/C的命令
info vars
列出Tcl变量
inof class
获取class名
在文献1中提到上面方法的语境(context)如下表. 这个语境感觉好象是专门用来显示的指出对象方法的作用域. --- 如果是这样的话, OTcl的名字空间管理好象有些问题.
name
description
type
self
对象名
变量
proc
方法名
变量
class
定义的类型方法
变量
next
下一个影子方法
方法
$self类似于C++类中的this指针, $proc给出方法名, $next是指父类的同名方法, 就是C++中的函数重载, 这关系到OTcl对象的多继承机制.
OTcl的语法的进一步解释45678
在OTcl中, 类(Class)和对象(objects)是区分开来的. 类表示一种类型, 对象是类的实例. 在OTcl中, 类可以看成是一种特殊的对象. 类标志对象的类名, 类中可以放置所有对象共享的变量, 类似于C++中的静态变量.
OTcl的基类称为Object, 注意它可不是前面所说的对象. 所有的类都是从Object派生而来.
OTcl的属性都是C++意义上的public的.
instproc用来定义类的方法, 而proc用来定义对象方法. 后者定义的方法只能用于该对象.
unset用来undefine一个变量.
OTcl中, 类的instproc函数init相当于C++中的构造函数.
superclass用于继承.
OTcl的继承机制
文献1对OTcl的继承机制描述的很清晰. OTcl支持类的多继承, 唯一的要求是继承关系满足DAG(有向无环图). OTcl的类继承可以简单地通过下面这个例子来理解.
Class Safety
Safety instproc init {} {
$self next
$self set count 0
}
Safety instproc put {thing} {
$self instvar count
incr count
$self next $thing
}
Safety instproc get {} {
$self instvar count
if {$count == 0} then { return {empty!} }
incr count -1
$self next
}
Class Stack
Stack instproc init {} {
$self next
$self set things {}
}
Stack instproc put {thing} {
$self instvar things
set things [concat [list $thing] $things]
return $thing
}
Stack instproc get {} {
$self instvar things
set top [lindex $things 0]
set things [lrange $things 1 end]
return $top
}
Class SafeStack -superclass {Safety Stack}
SafeStack s
s put toast ==> toast
s get ==> toast
s get ==> empty!
s destroy ==> {}
上面的例子中, SafeStack从两个类派生而来Safety, Stack. OTcl使用"方法分发(Method Dispatch)"来描述如何从子类访问父类的重载方法. 如Figure 3所示.
从Figure 3可以了解到类是如何通过next方法来访问父类的重载方法的.
OTcl的C Api
OTcl给出了一个简洁的C Api接口, 通过这个接口我们可以在应用中访问OTcl中的对象. 主要接口描述见7和otcl.h文件. 从这些接口我们可以了解到OTcl本身并没有提供操纵C++类的方法, 这留给了tclCL来完成这一工作.
检查OTcl的Makefile, 可以看到它有三个目标: libotcl.a, owish, otclsh. 后两个都是shell程序, libotcl.a是我们关心的, 它就是OTcl的库, 当它被链接到应用程序中后, 应用程序就有了OTcl脚本内嵌的功能. libotcl.a: otcl.c
rm -f libotcl.a otcl.o
$(CC) -c $(CFLAGS) $(DEFINES) $(INCLUDES) otcl.c
ar cq libotcl.a otcl.o
$(RANLIB) libotcl.a
可以看到libotcl.a只由一个文件生成otcl.c, 这个c文件有两千多行, 好在otcl.h头文件很清晰. otcl.h一开始就声明了两个结构, 然后开始声明一些函数, 这些函数的名字很self meaning.
这里不打算继续分析otcl.c的源码了, 可以从otcl.h和OTcl的主页上了解一下C api的相关内容. 下面开始在tclcl中寻找我们感兴趣的内容.
NS2的应用场景与设计
有了上面对Tcl/OTcl的了解, 我们开始探询NS2仿真系统的设计原理. NS2的目标是仿真实际网络的各个元素, 对这些网络元素的描述有相关的资源可以获取. 如TCP协议的实现, 就可以从FreeBSD上获取, 由于TCP协议的实现版本不同, 就有了Reno发布等. 对链路的仿真, 主要是通过队列, 延迟等.
如何仿真这些网络元素是一回事, 对于NS2的系统架构是另一回事. 如此多的网络元素, 它们有可能功能重叠, 特定的仿真对象有不同的要求. 要满足这种灵活性, 一个可行的方案就是采用脚本定制仿真环境. 从而导致NS2有内嵌脚本语言的要求.
另一方面, 这些网络元素是分门别类的, 再有从实现效率上考虑, 使NS2采用了C++来对这些网络仿真对象建模. 这样就对应的要求脚本语言能够有与C++对象交互的能力. --- 这个要求可不低. 而MIT正好又有一种新开发的面向对象的脚本语言OTcl, 这些都促成了NS2采用OTcl.
NS2的应用场景是这样的: 用户在一个Tcl脚本中给出对仿真环境的描述, 键入ns xx.tcl启动NS2, NS2先完成一些初始化工作, 然后按照脚本的描述实例化各个仿真元素, 并把这些仿真元素连接起来, 在启动事件发生器, 触发网络元素动作, 中间有一些记录工作, 把这些仿真信息保存在磁盘上留待继续分析.
现在我们考虑这样一个系统如何设计. OTcl本身有对象的机制, Tcl脚本可以描述我们要仿真的对象. 但是我们遇到的问题是如何从OTcl来操纵NS2的C++对象, 这包括:
动态创建一个新的C++对象,
访问这个C++对象的属性,
调用该C++对象的方法.
C++对象的创建与删除
首先, 我们首先需要一种机制, 能够从一个描述类的字符串来动态的创建一个C++对象. 如果退到Tcl的方式, 我们就必须为每一个类写这样一种Tcl命令接口, 这不是一个好的解决方案, 它最大的问题是丧失了C++的继承关系. NS2的解决方案称为"Split Model", 它在OTcl上建立Tcl下的类, 与C++类分级保持一致, 每个C++类都对应一个OTcl的类, 但是问题还没有完全解决. 你在OTcl上初始化了一个对象, 必须要同时初始化一个对应的C++的对象.
为了解决这个问题, NS2在C++上使用了TclClass类来封装注册机制. 每个C++类都有一个对应的从TclClass派生而来的对象, 注意这里是对象, 是一个实例, NS2一启动就会实例化它. 该对象的主要目的是封装注册C++类的动态创建函数, 注册信息维护在一个hash表中, 该hash表是tclCL包的一部分, hash键是描述类的一个字符串计算而来.
接下来的问题是, 如何调用这个C++类的创建函数. NS2的方法很技巧, 前面所说的OTcl继承关系是一个关键, OTcl的对象的初始化函数都是init, 一个派生的OTcl对象首先是调用它的父类的init函数. 如前面的代码: Stack instproc init {} {
$self next
$self set things {}
}
这样, 一个OTcl的初始化肯定要调用到OTcl的Object类的init, 如果这个Object能够在此时初始化这个C++对象将是再理想不过. 这样一来, C++对象的初始化就对用户来说不可见了, 他只看到的是一个OTcl对象被初始化. NS2使用了TclObject而不是Object来派生所有的OTcl对象, 对应的, C++仿真对象也从一个C++的TclObject类派生. 由OTcl的根类TclObject来搜索C++类名hash表, 完成C++对象的创建工作. 还有一个小问题, init必须带入C++类名的字符串作为参数, 否则就没法查hash表了.
OTcl对象/C++对象的删除也是类似的道理. 至此, 第1个问题解决.
访问C++对象的属性
下一个问题是要从OTcl上操纵C++对象的属性. OTcl对象的属性并不一定要完全照抄C++对象的属性, OTcl对象属性的设计原则是能够方便的完成对C++属性的设置即可. 所以一般来说, OTcl对象属性集合要小于C++对象属性集合.
在分析如何访问C++对象属性之前, 先澄清一些OTcl名字空间的概念. OTcl是一个脚本程序, 它传统的继承了Unix下环境变量的概念, 变量的名字空间是扁平的. 而引入了对象机制后, 名字空间就有些复杂了. 显然一个OTcl对象的属性的名字与环境变量下的名字有可能重叠. 在OTcl中, 这称为名字的"context"语境.
对比C++对象的名字, 它是确定的, 这是因为有编译器的帮助, 编译器在编译一个C++源代码的时候它可以根据上下文来判断这里变量指的是什么. 而在OTcl的环境中, 也需要类似的机制, 由OTcl的Parser动态的确定一个名字的含义.
显然要确定一个名字的含义, 可以通过对象名来帮助作到这一点. 在OTcl中还有一个对象的hash表, 对象创建后要注册到这个hash表中. 对一个名字解释, 首先是要搜索对象hash表, 再搜索该对象的class及其父类, 参照前面的next指针.
在下面的代码中, $self instvar count这条语句就是切换context的. 如果不切换context, 我们将不知道是环境变量名还是其他的对象的属性. Safety instproc put {thing} {
$self instvar count
incr count
$self next $thing
}
对一个C++对象属性的访问, 有读/写两种操作. 由于存在OTcl/C++两个对象, 有可能它们的属性并不一致. 但是要注意到OTcl的对象属性是给用户看的, 只要保证在读/写OTcl对象属性的时候能够作到与C++对象一致就行了, 完全保持二者的一致性是没有必要的. 有了这样的要求, 下面的方式才是可行的.
NS2采用了一种trap机制来捕获对OTcl对象属性的访问. 具体来说, 在tclCL中是以InstVar类对象来封装这种Trap机制. Trap的位置安装在语境切换的时刻, 因为只有在语境切换后, 才有可能对该
OTcl对象的属性进行访问.
对C++对象非static属性的访问
现在来考虑一下实现trap或者说C++对象属性绑定的细节问题. 首先, 绑定的是一个C++对象的属性(对绑定一个C++类的static属性问题在后面谈), 这意味着要知道该C++对象属性的位置, 所以C++对象属性的位置是绑定的一个必要条件.
其次, 如何设计这个Trap? 假设我们要对某个OTcl的变量进行写操作, 一般的操作是利用Tcl的Parser直接写, 但是这里我们要保持与某个位置上的信息同步, 就还需要向这个位置写相应的信息. 要解决问题, 可以修改Tcl的Parser, 让它写同步位置即可. --- 这就是InstVar的思路, 我们可以在语境切换的时刻安装一个定制的Parser, 让这个定制的Parser来向C++对象属性的位置写, 问题就解决了.
剩下的问题是, 何时安装这样一个定制Parser? 原则上任何时候都是可以的, 只要你知道这个C++对象属性的位置. 但是一个方便的做法是在该C++对象构造函数内做. 当调用这个binding函数的时候, 它会在OTcl对象属性位置上做一个标记, 表示有绑定的属性. 当进行语境切换的时候, 如果在该OTcl对象的属性位置上有绑定标志, 则OTcl动态安装定制的Parser, 这个定制的Parser就是tclCL的InstVar的一个成员函数.
由于OTcl是脚本语言, 是若类型的语言, 它用字符串表示所有的变量, 只有当它在eval的时候才会知道它具体是char/int/real等. 所有InstVar有几个派生类, 原因是这个定制的Parser要向C++属性写不同类型的信息.
给个例子 ASRMAgent::ASRMAgent() {
bind("pdistance_", &pdistance_); \* real variable */
bind("requestor_", &requestor_); \* integer variable */
bind_time("lastSent_", &lastSessSent_); \* time variable */
bind_bw("ctrlLimit_", &ctrlBWLimit_); \* bandwidth variable */
bind_bool("running_", &running_); \* boolean variable */
}
对C++对象static属性的访问
C++对象的静态属性是放在局部堆上的, 而且是该类的所有对象共享的. 如果在C++对象的构造函数中绑定这个静态属性, 显然有效率上的问题, 在同时存在多个该类的C++对象时, 就会多次绑定. 更严重的是, 它绑定到的对应的是OTcl对象上, 这样在OTcl对象上该static属性就不同一了.
注意到NS2启动时, C++类对应的TclClass把C++类要注册到OTcl的类hash表上, 这个时刻是做对C++类工作的绝好机会.
分析OTcl的类/对象机制, 可以看到OTcl无法象C++对象一样有静态变量, 这算是OTcl设计的一个缺陷? 用OTcl类初始化一个OTcl对象, 意味着完整拷贝OTcl的信息. 对次, NS2采用了一种变通的方法. 它首先在OTcl类上添加了一个属性, 以后用该类初始化OTcl对象时, 所有对象都有这样一个属性. 然后在该属性上注册一个定制的Parser到这个属性上, 这个Parser直接访问了C++对象静态变量. ---这样就在OTcl对象上作出了一个静态变量. 见下面的从文献3中摘录的代码.
假设C++类的static变量为 class Packet {
......
static int hdrlen_;
};
Packet的TclClass中定义如下: class PacketHeaderClass : public TclClass {
protected:
PacketHeaderClass(const char* classname, int hdrsize);
TclObject* create(int argc, const char*const* argv);
\* These two implements OTcl class access methods */
virtual void bind();
virtual int method(int argc, const char*const* argv);
};
void PacketHeaderClass::bind()
{
\* Call to base class bind() must precede add_method() */
TclClass::bind();
add_method("hdrlen");
}
int PacketHeaderClass::method(int ac, const char*const* av)
{
Tcl& tcl = Tcl::instance();
\* Notice this argument translation; we can then handle them as if in TclObject::command() */
int argc = ac - 2;
const char*const* argv = av + 2;
if (argc == 2) {
if (strcmp(argv[1], "hdrlen") == 0) {
tcl.resultf("%d", Packet::hdrlen_);
return (TCL_OK);
}
} else if (argc == 3) {
if (strcmp(argv[1], "hdrlen") == 0) {
Packet::hdrlen_ = atoi(argv[2]);
return (TCL_OK);
}
}
return TclClass::method(ac, av);
}
OTcl脚本如下, 这个脚本模拟了读写两种操作. PacketHeader hdrlen 120
set i [PacketHeader hdrlen]
几点感想
定制Parser在上面的代码中体现无疑.
上面在对C++对象非static属性访问的代码中bind函数值得回味. 它把一个private/protected的属性给暴露出去了, 而C++编译器却照样编译通过, 有意思.
调用C++对象的方法
在NS2中, 很少有OTcl本身再实现一个对象的方法的, 因为从效率的角度考虑, 这样做会得不偿失. 一般的情况都是直接调用C++对象的方法来处理. 从实现上来看, 要调用一个对象的方法并不困难, 只要在合适的语境中, 给出参数直接调用就可以了. 所以NS2中实现对C++对象的方法的引用至多也就是定制Tcl的Parser.
注册顶级命令
OTcl继承了Tcl的某些特性, 可以通过TclCommand类定制一个顶级命令注册到OTcl的Parser中, 不过这种方法不值得推荐. 下面的例子3给出了实现方法:
要在OTcl中注册的顶级命令是hi: % hi this is ns [ns-version]
hello world, this is ns 2.0a12
下面是实现代码, 构造函数直接以TclCommand的构造函数注册"hi"命令, command()函数是命令的实现部分. class say_hello : public TclCommand {
public:
say_hello();
int command(int argc, const char*const* argv);
};
say_hello() : TclCommand("hi") {}
#include
int say_hello::command(int argc, const char*const* argv) {
cout << "hello world:"; for (int i = 1; i <> method?*/
virtual int command(int argc, const char*const* argv);
virtual void trace(TracedVar*);
...
可以看到, command()是一个虚函数, 这么做的目的是为了保证所有的command()函数都一样. 再看看3中给的一个例子: int ASRMAgent::command(int argc, const char*const*argv) {
Tcl& tcl = Tcl::instance();
if (argc == 3) {
if (strcmp(argv[1], "distance?") == 0) {
int sender = atoi(argv[2]);
SRMinfo* sp = get_state(sender);
tcl.tesultf("%f", sp->distance_);
return TCL_OK;
}
}
return (SRMAgent::command(argc, argv));
}
注意到最后一行return (SRMAgent::command(argc, argv)); --- 问题清楚了, command()函数是在C++对象一侧上溯到它的父类的, 这样做显然效率要高一些. 因此, ccommand()应该是在C++对象初始化的时候, 被注册到OTcl对象的cmd{}上.
最后交待一下OTcl如何调用cmd{}. 有两种方式, 一种是显示的调用cmd{} $srmObject cmd distance?
另一种是隐式的调用: $srmObject distance?
在隐式调用方式下, Tcl的Parser先检查有OTcl对象没有distance?这样的命令, 这里显然没有. 然后Parser会把解释权传递给OTcl对象的unknown{}函数, unknown{}函数会使用上面的显示调用的方式来调用C++对象的command()函数.
在OTcl对象中实现命令的例子
虽然在OTcl对象中实现方法的例子不常见, 文献3还是给出了一个例子: Agent/SRM/Adaptive instproc distance? addr {
$self instvar distanceCache_
if ![info exists distanceCache_($addr)] {
set distanceCache_($addr) [$self cmd distance? $addr]
}
set distanceCache_($addr)
}
这个例子说明了在OTcl对象中中重载C++对象的命令.
到现在为止, 我们已经大致了解了NS2的基本架构和设计原理. 还有一些细节问题留到下一节对tclCL模块的类说明.
tclcl
tclCL是在OTcl基础上的封装, 它们与Tcl之间的关系如图Figure 2. tclCL实际上搭建了NS2的框架, NS2的类库都是建立在tclCL基础上的. 在文献3第三章简单介绍了tclCL的六个类: Tcl, TclObject, TclClass, TclCommand, EmbeddedTcl, InstVar.
其中, Tcl类可以看成是一个Tcl的C++接口类, 它提供C++访问Tcl库的接口. TclObject是Tcl/C++两个面向对象语言的类库的基类, 在最新的tclcl中, 采用了SplitObject的术语. TclClass注册编译分级, 保持了编译分级的层次结构, 同时给OTcl对象提供了创建C++对象的方法. TclCommand用于定义简单的全局解释命令. EmbeddedTcl是定制的Tcl命令. InstVar类包含了从Tcl访问C++类成员变量的方法.
这里不打算对tclcl的六个类再详细介绍了, 这篇文章到此为止已经基本达到它的目的. 下面的内容只挑选我认为是容易遗漏的地方. 文献3作为开发NS2手头必备的工具应该详细研读.
Class Tcl
Tcl类提供如下的方法
获取Tcl实例句柄
Tcl类只有一个实例, 该实例在NS2启动时初始化. 获取Tcl类的实例方法是: Tcl& tcl = Tcl::instance();
解析执行Tcl脚本命令
返回结果给Parser, 设置Tcl的环境变量
Tcl类的结果是指tcl_->result, 与脚本执行的退出码不同. 如下的代码 if (strcmp(argv[1], "now") == 0) {
tcl.resultf("%.17g", clock());
return TCL_OK;
}
tcl.result("Invalid operation specified");
return TCL_ERROR;
报告错误, 以一种统一的方式退出执行脚本
有两种错误退出方式, 一种是返回TCL_ERROR的退出码, 另一种是以tcl.error()函数退出, 两者稍有区别. 前者可以在解释器中trap这个错误, 然后打出出错的调用栈桢; 后者则不行.
存储并搜索TclObject对象
Tcl类中有一个C++对象的hash表, hash键是对象名. 这里值得注意的是C++对象的hash表而不是OTcl对象的hash表. 有些奇怪, 难道OTcl对象的hash表在OTcl模块中?
插入定制的Parser
tcl.interp(void)函数是tcl的Parser句柄, 可以修改它, 加入定制的Parser.
Class TclObject
在文献3中, OTcl对象称为解释分级对象, C++对象称为编译分级对象. TclObject并不包括Simulator, Node, Link, rtObject等对象. 对用户来说, 如果只使用Tcl配置脚本的话, 一般只看得到OTcl对象的创建, 而C++对象的创建正如我们前面分析的, 对用户是不可见的.
对象的创建和销毁
OTcl对象包括TclObject, Simulator等都使用在~/tclcl/tcl-object.tcl文件中定义的new{}和delete{}函数来初始化和销毁. 这一部分的内容前面已经分析的比较清楚.
每个OTcl对象在创建的时候会获取一个id返回给用户. OTcl的基类TclObject使用create-shadow{}函数来创建C++对象. 在C++对象的构造函数中一般都会调用属性绑定函数, 进行C++/OTcl属性绑定. C++对象创建后, 被插入到Tcl类对象的hash表中. 然后用C++对象的command()函数在OTcl对象中注册cmd{}函数.
下面的这张图比较清楚的反映了对象创建的过程.
OTcl中TclObject对象的函数create-shadow{}是TclClass注册的一个命令. 虚线表示C++编译器自动的调用父类的初始化函数.
变量绑定
OTcl/C++对象的属性初始化在~ns/tcl/lib/ns-default.tcl中做.
变量跟踪
命令方法
Class TclClass
Class TclCommand
Class EmbeddedTcl
Class InstVar
两个例子
本来打算写一两完整的例子, 展示如何向NS2中添加一个新的模块, 但我发现网上有两个比较好的资源可以借用910, 这里就免了.
结束语
这篇文章的后面两节实在没时间写下去了, 就省略, 省略, 再省略, ...
等我以后有时间在续吧.
References
[1] Extending Tcl for Dynamic Object-Oriented Programming
[2] Practical Programming in Tcl and Tk
[3] The ns Manual
[4] http://bmrc.berkeley.edu/research/cmt/cmtdoc/otcl/tutorial.html
[5] http://bmrc.berkeley.edu/research/cmt/cmtdoc/otcl/object.html
[6] http://bmrc.berkeley.edu/research/cmt/cmtdoc/otcl/class.html
[7] http://bmrc.berkeley.edu/research/cmt/cmtdoc/otcl/capi.html
[8] http://bmrc.berkeley.edu/research/cmt/cmtdoc/otcl/autoload.html
[9] http://nile.wpi.edu/NS/linkage.html
[10] http://140.116.72.80/~smallko/ns2/module.htm