2005年01月28日

别以为自己当老板很容易!有人创业第一年找不到半个客户,100万元创业资金一下子就花光;有人自行创业后,没有大公司的蔽荫,“感觉自己好像流浪狗”。更多创业者已经意识到,创业先要创势,创造出了良好的开局,形成了自己的气势,创业才有可能得以顺利进行下去。

  《左传.
曹刿论战》有云:一鼓作气,再而衰,三而竭。运兵之道是如此,创业开办企业也同样讲究这个道理。近年来,很多创业成功学的研究者都提出,开局对创业者非常
重要。一个屡战屡败的人,也许终有一天能够看到成功的希望,但是相对而言,他的历程会更坎坷、创业过程会有更多磨难。同时,失败的经历会磨损一个人的意
志,消耗一个人的斗志,也会一而再、再而三地误导一个创业者的决策力和判断力,更重要的是屡战屡败的过程中,创业者也极容易产生赌博心理,以至于在失败的
路上越走越远。

 
 创业之初,谁不想开门红呢?创业者拿出最大的勇气去开创事业,同样也期望自己的努力可以迅速得到回报。然而很多创业企业都遇到这类问题:申请营业执照
前,海阔天空、事业前景无限大,然而当攥着营业执照、搬入办公室后,却发现万事开头难。难在哪里?项目的开展不顺利、资金的运转不流畅、业务始终推广不
开、产品少人问津。创业的激情在种种问题中被渐渐消磨,创业者的万丈豪情也一天天低落。

 
 可见,在创业之初,足以使创业企业生存下来的开局尤为关键。事实上对一个创业企业来说,生存是第一位的。《科学投资》杂志始终认为,创业是一个系统的、
复杂的体系,从创业之前、创业之初到企业规模不断发展,都是需要创业者凭借智慧和勇气,不断寻找出每个阶段的关键点并迅速突破的过程。而创业初期的智慧更
为关键,它将使创业企业在诸多不利因素中或在边缘罅隙中取得自身的资本收益,从而使创业者的创业激情保持高昂和创业头脑始终保持活跃,使企业步入良性循环
的创业成功阶段。

  《科学投资》杂志对数百个创业案例进行了深入、细致的研究后发现,企业创业实现良好开局并非难事,并从诸多创业成功企业的案例中抽丝剥茧,寻找到了创业开局的六大绝招。通过案例分析,将这六大绝招展示在创业者面前。

  招数不会是一成不变的,只有掌握这六大绝招的精髓,并且学会将之组合,才可以使自己的企业迅速迎来良好开局。

绝招1:创业项目以制胜

  靠创意,赢在别人想不到的快速变迁的时代中,突破过去的框架,掌握新的环境,面对新的课题,迎接新的挑战,才能赢得新的财富。

  强手过招,靠什么取胜?靠创意。在快速变迁的时代中,突破过去的框架,掌握新的环境,面对新的课题,迎接新的挑战,才能赢得新的财富。

 
 《科学投资》杂志研究发现,对于绝大多数在激烈竞争中初创的企业来说,通过精巧构思推出的新招数、新想法,不仅可以使自己的创业之路展开一线生机,而且
可以在短时间内见到利润。所谓新招数、新想法,从其运作思路上看未必出奇,一旦被点拨开了,谁都可以做得到,但其根本却是创业者具备的功力。

  新招数,未必出奇

  《科学投资》杂志认为,“新”,
通常意味着创业竞争压力的减轻,创业空间的拓展。事实证明,很多创业者在创业初期时都巧妙地运用了这一方法,从而使自己先站住了脚。称其为新招数、新想
法,而不是新技术,是因为与后者相比,新招数、新想法更容易萌生,特别是创业者自己可能瞬间闪现出的新思路,也更容易根据自身的条件进行完善并加以运作。
借助巧妙的运用,创业者在创业初期的日子通常都会过得比较滋润,开门见喜,利润得来也轻松了许多。

  认真分析每一个用“新”创业的案例,《科学投资》杂志发现,很多时候寻找一个新的职业、一个新的经营项目、一个新的行业、一个新的产品,并不需要搜肠刮肚去想,但是一定要会去利用。

  蒋瑞颖,一位很普通的南京市民。很长一段时间里,创业无门苦苦寻觅,没想到一碗汤让她名声远扬,当上了创业明星,大家都亲切的叫她“蒋
嫂”。靠熬汤创业并不新奇,但蒋嫂的思路特别明确而且有针对性,她专门给自己家对面南京妇幼保健医院的产妇熬营养汤。产妇是一个极大的消费群体,她们最集
中的消费就是营养。绝大多数产妇家属为了产妇的身体和未来的宝宝,也为了产妇生产时能够更顺利、生产后恢复更快,通常是不计金钱,只认好的、有营养的食品。蒋嫂这一新招数恰好准确抓住产妇及其家属的这一心理,开门红自然手到擒来。

  浙江
民汤百忠的新招数说出来着实让人悚然,2001年开始,他竟然琢磨起了养苍蝇。这一招不是汤百忠自己想出来的,而是他在《农村信息报》上看到一则致富信
息,说养苍蝇能够一本万利,发家致富。他不觉眼前一亮——养苍蝇还能致富?他从未听说过,养的人一定很少,一定有“钱”途!他把这则信息抄下来,如获至
宝。但是,正是选择了这一新项目,让汤百忠可以特别得意对别人说:羡慕吧!苍蝇给我带来滚滚财源。

  上海
琳娜的“哭吧”从名字上就透着新鲜,而这个项目的由来既得益于她身为女性的细腻,也与她的从业经历有关,在经营“哭吧”之前,刘琳娜在上海一家法治类媒体
担任咨询顾问,名为“婚恋处方”的栏目是她为别人排忧释疑的一方阵地。在那段为期两年半的时间里,通过热线、书信等一系列手段,得到刘琳娜帮助的超过千
人。“在工作当中,我发现需要倾诉,需要进行心理咨询的人并不在少数,而我在长时间的实践当中已经积累了一定的经验,并形成了一套自己独特的辅导别人的模
式。接受我心理辅导的人绝大多数都是伴有眼泪的。既然如此,我何不自己创业,开一家‘哭吧’。”刘琳娜述说着当初创业想法的由来,“有了这样的想法之后,
我便就可行性找到上海心理协会的张震宇等老师进行咨询。他们认为,哭不能解决问题,但是在心理指导下的哭有助于问题的根本解决。老师们的肯定更是鼓舞了我
开办‘哭吧’的信心。”

  从三个人的创业项目选择看,从自己身边寻找,从自己的特长寻找,出“新”
并不难,蒋嫂由于打工住在妇幼保健医院,平日里总有不少产妇家属拎着冷汤找她帮忙热一下,时间长了,蒋嫂还曾经专门竖过一个牌子:“收费热汤菜,每位1
元!”在不断替人热汤的过程中,产妇爱喝什么汤、什么汤更有营养也就心知肚明了,加之守着一个如此好的地理位置,蒋嫂的“新招”得来也就极为自然了。而刘
琳娜也是极好地利用了自己的特长和专业,发掘出了自己的新生意。

  出新,需求是关键

  说起来不难,但寻找新招数、新想法却也不是人人都可以做到的。对于创业企业,新招数、新颖构思乃至新产品的开发,需要的是巧劲,而不是拙力。

 
 当一个重要的创意从你的脑子里激发出来,你肯定会无比的激动与兴奋,这时候,你不要着急马上就付诸实践,创意可不是盲目的标新立异,它要以企业实际为基
础,要适合企业自身的发展要求,你应该对新的创意冷静地思考,放在市场的基础上,审视它的可行性与科学性,经过反复考证,思路成熟了,第一个环节就完成
了。

 
 确定一个招数、想法是否有前景,不在于这个招数或想法的本身是否够新奇、够独特,而是它的存在是否有需求。很多创业者也曾经新奇特招数不断,但最终不是
无人喝彩,就是过早夭折,原因就在于创业者将这些新思路和新招数孤立在自己的想象中,没有考虑到人们对之是否存在需求。

  汤百忠养殖苍蝇说出来恐怖,很多人听到都会连连摇头,甚至觉得恶心。但汤百忠从中赚到了钱却正是因为苍蝇养殖蕴含着巨大的市场需求。汤百忠在杭州打工时,曾利用工余时间到浙江农业
学向一位教授咨询苍蝇的养殖。教授肯定了他的想法,告诉他养殖苍蝇是一种新兴的前沿致富项目,目前国内还无人问津,并把苍蝇的作用和价值向他作了一番介
绍:苍蝇在昆虫家族中占有很大的比重,是人类尚未开发利用的动物资源之一;蝇蛆中含有丰富的蛋白质、维生素和人体所必需的微量元素,将会是人类未来的一种
食物;蛆皮可以提取珍贵的甲壳素和壳聚糖,可广泛应用于医药、食品、化妆品、纺织
环保等领域,其中,壳聚糖在治疗癌症方面药效独特;蛆浆可分离出具有高效强力杀菌作用的“抗菌肽”,将会取代目前广泛应用的抗生素;另外,养蝇同其它养殖
业相比,具有周期短、见效快、产量高和抗病能力强的特点,从蝇卵发育成成虫仅需10天时间,基本上属于无风险投资项目,市场前景广阔,一笼苍蝇等于10亩
良田的收入,可谓一本万利。

  听完专家的介绍,汤百忠的疑虑顿时烟消云散,更加坚定了养苍蝇的信念。而教授也十分欣赏这位有闯劲的打工汉,热心地将汤百忠介绍给浙江省农科院的有关专家。恰巧浙江省农科院正在推广“优质动物高蛋白无菌蝇养殖技术”,但少有人尝试,见汤百忠找上门来,就把这项技术无偿地传授给他,并无偿地为他提供了优良无菌蝇种,鼓励他好好干,将来做大了形成产业规模,就在他那里设置养蝇示范基地。

  汤百忠在浙江省农科院学习了一天后,抱着一罐他们赠送的苍蝇就兴冲冲地回家了。在家中辟出一间房,作为养苍蝇的厂房。在他的精心呵护下,那些苍蝇终于在他家“安家落户”了。

  所有的新项目、新招数、新思路,是否可以存活,可以经得住市场的验证,惟一的衡量标准就是其中是否蕴含市场需求。

  运用巧心思

  新项目、新招数、新思路乃至新产品的出现,都等于开辟出了一个相对空白的市场,这种相对的空白市场,既使有着极大的需求,也需要有一个让市场认知、了解的过程。这一过程也常常是创业者最为难过的一关。

 
 白俊辉原是上海一家小毛巾厂的业务员。一次和朋友聚餐时,白俊辉偶然听到无纺布发展很快。白俊辉知道现在的毛巾大都是化纤的,而无纺布与化纤相比,具有
不掉毛、自然降解、成本低廉等优点。如果用无纺布生产毛巾一定有前景。经过对上海各大餐馆、饭店调查,发现小毛巾大都是普通化纤的。而全国用在餐桌上的一
次性用品每年消耗达上千亿元,仅用来擦手、擦嘴的一次性小毛巾大约占100亿元。

  白俊辉像发现新大陆一样高兴。他赶忙找来毛巾技术人员,将自己的想法与他们进行了交流,并得到了大家的认可。于是,白俊辉聘请了毛巾设计和制造方面的专家,经过苦心研究,终于在2002年研制成功了一种新型一次性餐饮用品——餐饮湿巾。

 
 与许多创业者不同的是,白俊辉在推销产品时更多花了一些心思。新产品再有市场,但对于一个初创且一文不名的小企业来说,从零开始的推销却并不容易,很多
创业者都面临这样的结果,新产品推出后,需要一点点的普及知识,慢慢的培养市场,但创业企业本身资金匮乏,偏偏又经不起长时间的等待。要想让自己的产品迅
“窜红”,除了产品本身“新”,还要在迎合需求上做点巧功夫。

 
 白俊辉的做法是,先对餐厅做一番调查。结果他发现,到餐厅、饭店吃饭的顾客大多都是朋友、亲戚,相互聚餐以融洽感情。但是,仅仅靠吃饭还不能满足这种需
求。如果使小餐巾成为一种烘托气氛、融洽感情的工具,不仅自己的产品在同类竞争中不愁销,而且餐厅、饭店也会增加客流。但是,如何才能让自己的餐饮湿巾达
到这种功能呢?他左思右想,突然闪出一个念头:在湿巾外包装上印制幽默笑话、漫画。

  2002
年6月,白俊辉带着餐饮湿巾,敲开了他的第一个客户——上海崇明岛的乐岛大酒店的大门。成本更低廉,卫生更有保证,同时包装袋上印有幽默笑话、漫画等活跃
气氛的内容,不但增添餐厅气氛,还可以掩盖服务上的不周到:如客人太多,上菜不及时,客人通过阅读幽默笑话打发等待时间,一席话一下子就打动了这家店的老
板,他当即订下了两箱。

  5
天后,白俊辉接到了这个老板打来的电话,急需10箱餐饮湿巾,还再三叮嘱白俊辉马上送过去。老板说:“这种湿巾消费者反应特别好,用着舒服、放心,特别是
包装袋上的幽默笑话、漫画引人入胜,别有一番风味。”第一个客户就这样稳定了。随之,第二个、第三个,白俊辉逐步将餐饮湿巾推广到了上海10多家餐厅、饭
店,迅速在创业伊始就站稳了脚跟。

  可见,奇妙的创意,精巧的新思路,如果没有科学地转化成利润,就象一辆昂贵的名牌轿车被弃置于农舍。《科学投资》杂志建议:如果创业者想利用“新”
开始自己的创业,就一定要解决三个问题,第一,新招数、新想法是否与自己的特长、经历有着巧妙的契合,是否可以利用自己的专长将这个“新”从简单的想法转
变为现实中的产品;第二,新招数、新想法、新产品,是否可以生存下去,首要考虑的是是否有需求,是否有人愿意掏腰包购买;第三,真正可以利用新招数、新想
法使自己的创业“开门红”的,除了项目与产品外,契合消费心理的巧心思也极为关键,要使用巧劲儿叩开市场之门。


绝招2无中生有开拓销售渠道

  销售渠道是小企业创业的命门,对创业企业来说,由于产品和企业的知名度低,很难进入其他企业已经稳定的销售渠道中去。

 
 销售渠道是小企业创业的命门,对创业企业来说,由于产品和企业的知名度低,很难进入其他企业已经稳定的销售渠道。因此,很多企业都不得不暂时采取高成本
低效益的营销战略,如上门推销,大打商品广告,向批发商和零售商让利,或交给任何愿意经销的企业销售。这种渠道开拓的方式通常是慢功夫,很难使创业企业尝
“开门红”的喜悦滋味。

  无中生有,变被动为主动

  利润从哪里来?人人都知道是从客户的钱包里来。任何企业的运行,都离不开客户关系的把握。但对创业企业来说,获取客户是最难跨出的一步。《科学投资》杂志认为,可以利用“无中生有”将原本不存在的销售渠道借助造势呈现出来,将原本不属于自己的客户借助造势吸引过来,变被动寻找经销商、代理商为经销商、代理商自己找上门来。这并非是简单的谋略,它需要对市场有着深入的了解,有着深刻的体会,并且抓住关键环节。

  1992年王蓝平大学毕业时,北京、上海等地出现了策划业,他敏锐地感觉到,在宁夏这块策划业处女地上,宣传和营销策划会给企业发展插上腾飞的翅膀。由此,他选定在策划上干出一番事业。蓝平广告策划公司成立了,公司小得可怜不说,当地企业对于广告策划的陌生,让蓝平广告公司的业务拓展难上加难。

  在上世纪90年代,很多广告公司的所谓业务都是通过招聘大量业务员,跑街、扫企业,挨门挨户找企业软磨硬泡,越是小广告公司,业务越难开展。1993年,全国有广告公司3000多家,也是在这一年,由于找不到客户,撑不下去的广告公司也多达500多家。

  在这种状况下,蓝平公司前景就更加不被看好。王蓝平给手下们鼓劲:“企业要发展,必然会注重公关
仪,现在市场潜力巨大,我们抢先一步,定能一举成功。机不可失!同时,开展礼仪大赛,也能为社会的发展进步做出一份贡献。”王蓝平拿出了自己仅有的3万元
钱,在报纸、电视上做广告。一时间,机关单位、街头巷尾,到处都有人议论礼仪大赛的事,俊男靓女则蜂拥报名。活动进展顺利,最后在宁夏电视台大演播厅举行
了决赛,取得了圆满成功。利用这次活动的造势,王蓝平和他的公司一举成名,并在银川掀起了一股“礼仪热”。为了进一步扩大在当地的影响,礼仪大赛落幕不
久,蓝平公司又精心策划开展了一个大型现代集体婚礼。在以后的几年中,他们还举办过几次大型现代集体婚礼,以及“百佳礼仪服务大赛”、“广告明星大赛”、
“公务员礼仪大赛”等活动。几个活动下来,蓝平广告策划公司在当地成为了一家赫赫有名的公司,很多厂商、广告客户自动找上门来。

  完美演绎“无中生有”

  经过深入研究,《科学投资》杂志认为,无中生有的关键就是给自己的产品以明确定位,并从定位中寻找到渠道开拓的突破口。

  从孤家寡人式的沿街叫卖,到10天后300家代理商组成亚琪MIS企业信息管理系统销售体系,没有一分钱投入,一个帐面资金只有10万元的小公司玄机百变,很快成为全国著名的IT企业,可谓是将无中生有开拓渠道运用到了极致。

  1997年,只有六七个人的大连亚琪公司开发出一套企业信息管理系统软件,虽然这个DOS环境下的版本并不成熟,但这时的市场需求潜力巨大,对于亚琪来说,如何把现有的不是很优秀的产品卖出去成了当务之急。

  虽然亚琪MIS也在自家店面零零星星卖着,但没有代理体系、没有销售班子的事实,几乎已经宣判了这个草创之初的小公司死亡的命运。作为亚琪的创始人,胡诚深知,要打开市场,只有通过各地的软件代理商才能最快速地把产品送到用户手中。所以要打开市场,渠道战是别无选择的首要战役。

  胡诚把不同软件公司的广告搜集了厚厚一摞,他发现,中国所有软件的代理商就是那么几百家。换句话说,如果这几百家代理商能够代理亚琪MIS,
亚琪将如鱼得水,一呼百应。但这将需要多少时间、多少投入呀!事实的确如此,因为按照业内通行的做法,发展代理商只有两种方式:登门游说,或者电话游说。
当面游说,即使不算老板不在家等特殊原因,就算一天可以谈下一家代理商。要拿下300家代理商,至少也要300天。况且,连续300天的机票、住宿费,甚
至包括额外的交际费用,亚琪怎么可能承担得起?那么电话沟通呢?按照理想情况计算,即使两小时谈一个代理商,一天说服4家,也要连续75天,一天耗费8小
时长途电话费,才能拿下300家。再说,谁能保证两小时就一定能说服人家都进你的货?这两条路都不理想,耗时长,费用高,且亚琪将全面陷于被动,甚至只有
赊货才能打动代理商。如果僵陷于此,代理体系将成为制约亚琪发展的无法逾越的障碍。亚琪所面临的问题,正是创业企业发展过程中都无法逃避的问题。

  兵法有云:未战而庙算胜者,得算多;未战而庙算不胜者,得算少。多算胜,少算不胜,而况无算!那怎样才能以最小的代价让这些代理商成为协助亚琪的臂膀,而且最好是代理商来主动找我,而不是我去找他?胡诚经过彻夜的思索,一个“无
中生有”的渠道计划呼之欲出——同一个上午,《计算机世界》、《电脑报》、《中国计算机报》、《软件报》、《中国电脑教育报》五大IT媒体广告部先后接到
来自大连的电话:“我是大连亚琪电脑公司总经理胡诚,我们计划最近一个月内每期在贵报刊登一期整版广告,同时希望与贵报建立长期的良好合作。作为合作的起
点,我们惟一的要求是每个月底刊后支付广告费。如果可行,请您将广告发布合同传真给我。请相信,作为中国知名的IT厂商,大连亚琪有足够的资金和实力,并
愿承担一切违约责任。”

  1997年,在中国软件市场,除了微软
样的外资企业,国内软件厂商限于资金、限于胆量、也限于略显沉闷的市场需求,几乎没有启动过像样的广告。大家即使在全国性媒体上打广告,一般也都是“豆腐
块”,突然凭空杀出一个大连亚琪公司,而且广告一上来就是连续的整版,这样的大客户,怎能不让人动心?更何况刊后付款本是优惠老用户的行业惯例之一。胡诚
情真意切,理由充足;五大媒体正中下怀,亦步亦趋。于是这一广告计划无一例外被所有媒体接受,亚琪的广告大战就此拉开序幕。

  现在看来,胡诚的广告战并没有什么新意。当时,很多厂商认为IT
业是高科技产业,一定要请著名广告公司设计。可这些广告公司强调的往往是广告本身的创意和美感。比如画面上倾斜而立一个手提电脑,甚至旁边什么话也不多
说,大有一切尽在不言中的味道。可这究竟是什么意思?薄?轻?跳芭蕾舞?运用自如?读者不得不费尽脑汁去猜,像猜谜语一样。这些外国人普遍接受的广告,当
时的中国人却不熟、不懂。在刚刚起步、远未成熟的中国软件市场,告诉目标用户确切、详实的产品信息才是第一位的。为此,胡诚决定亲手撰写广告。这些后来被
软件业人士笑为“傻大黑粗”的广告,上面没有好看的画面,没有高深莫测的夸张,甚至没有普通广告必不可少的煽情,他只是用大篇文字详细介绍亚琪MIS的功
能和优点:比如亚琪MIS是干什么用的;它能替你做什么;它能让你的企业在管理的哪方面上台阶。说穿了,胡诚这些广告不过大白话而已。可就是这些“广告不
是广告、文章不是文章”的文字,却让用户一目了然,知道自己为什么需要它,并由此产生购买冲动。

  但是,这些连篇累牍、篇幅巨大的广告和亚琪的代理体系有什么直接关系吗?它除了能花掉亚琪40
万元广告费用之外,真能给亚琪带来胡诚期盼的销售渠道吗?事关整个战役成败的点睛之作是这样的:在这些广告的结尾,胡诚把事先精心挑选、涵盖全国的300
多家软件代理商的信息硬生生COPY下来,所有代理商的详细名称、详细地址、具体联系方式都一一开列。胡诚在广告中最后强调:“亚琪MIS全国有售,如有
需要,请致电我公司各地代理商查询、购买。”胡诚的逻辑是这样的:其实中国老百姓最喜欢的购物方式,还是一手交钱一手交货。即使他们对产品再感兴趣,也没
多少人愿意千里迢迢打长途电话,反复询问产品详情,再到邮局汇款,然后一直等到半个月后才能取货。而这些正是胡诚“无中生有”开列代理商名单的最大目的
——他要把整个销售过程反其道而行之,让用户主动发动代理商,让用户催促代理商进货!与此同步进行的,是亚琪MIS软件的生产。一切如法炮制,胡诚和加工
商、印刷厂、
甚至录像带销售商一一签下正规合同,当然,惟一的要求同样是延期30天付款。亚琪资金一分未动,胡诚计划如约展开。一周后,第一轮广告全面刊出,第一批亚
琪MIS准备就绪。在广告刊出后的第4天,第一家代理商就主动找上了门。至此,亚琪仅用10天的时间就迎来了创业的“开门红”。

  根基来自市场调查

  你可以将无中生有的利用理解为是创新之举,但是却不可以简单地认为这只是耍个小手腕就可以做到的。准确定位,寻找到市场需求的切入点才是决定成败的关键。而是否有需求、需求的特点等诸多因素,必须要经过周密的市场调查得来。

  所以,《科学投资》杂志建议,在你准备利用“无中生有”使自己的企业迎来“开门红”之前,一定要先做好市场调查。而且市场调查工作必须有计划、有步骤地进行,以防止调查的盲目性。

  一般说来,市场调查可分为四个阶段:调查前的准备阶段、正式调查阶段、综合分析资料阶段和提出调查报告阶段。

  一、调查前的准备阶段。对企业提供的资料进行初步分析,找出问题存在的征兆,明确调查课题的关键和范围,以选择最主要也是最需要的调查目标,制定出市场调查方案。主要包括:市场调查的内容、方法和步骤,调查计划的可行性、经费预算、调查时间等等。

  二、正式调查阶段。市场调查的内容和方法很多,因企业和情况而异。

 
 三、综合分析整理资料阶段。当统计分析研究和现场直接调查完成后,市场调查人员拥有大量的一手资料。对这些资料首先要编辑,选取一切有关的、重要的资
料,剔除没有参考价值的资料。然后对这些资料进行编组或分类,使之成为某种可供备用的形式。最后把有关资料用适当的表格形式展示出来,以便说明问题或从中
发现某种典型的模式。

  四、提出调查报告阶段。经过对调查材料的综合分析整理,根据调查目的写出一份调查报告,得出调查结论。值得注意的是,调查报告不是市场调查的结束,而应继续注意市场情况变化,以检验调查结果的准确程度,并发现市场新的趋势,为改进以后的调查打好基础。

绝招3:依附成长分大企业一杯羹

  所谓企业的共生或共栖,也是从自然界中两种都能独立生存的生物但又以一定的关系生活在一起的现象,借喻企业与企业之间优势互补、共同存亡的经营模式。

  如果我们把企业视为生物种群,不同种类的企业与企业之间,就象生物种群之间可能存在着寄生或共生的关系。所谓企业的寄生是根据生物中的“寄
生”定义推理出来的,借喻一个能依法独立经营的公司而不独立经营,专门从另一个独立经营的公司获取利益的一种“经营”方式。所谓企业的共生或共栖,也是从
自然界中两种都能独立生存的生物但又以一定的关系生活在一起的现象,借喻企业与企业之间优势互补、共同存亡的经营模式。

  相对于独立生存能力很强的大公司来说,中小企业的孤军作战能力较弱,巧妙地利用“寄生”或“依附”的原理,显得尤其重要。

  当企业初创时,力量还不够强大,势单力薄时,靠自己单枪匹马奋战,且不说不会看到“开
门红”的良好局面,很多企业会由于一直生活在“巨人”的阴影下,而难以得到长足的发展,甚至会因为互相撞车而自取灭亡。硬拼不行,创业企业应当怎么办呢?
《科学投资》杂志认为,只有以巧取胜,凭借自身的优势,取长补短,依附大企业成长,充分利用大型企业的资源发展自己。

  借船出海

  海边的渔夫,如果只在海边撒网,是无法捕到大鱼的,下到海里去又太危险,如何才能安全可靠地捕到大鱼呢?他当然可以开上一条大船,借船出海。

 
 大企业有通畅的产品流通渠道,有广大的客户群体,就像一条牢固的大船。而创业企业无论在资金、技术方面,还是在人力资源和管理经验等方面都存在许多不
足,就像海边的渔夫。如果创业企业能找到与大企业利益结合点,与他们结成联盟,借大船出海,也可以跟随他们一起捕到大鱼,获得丰厚利润。

  蓝点在深圳注册信科思科
有限公司时,注册资本只有20万元,很快,他们研发出自己的产品Bluepoint(蓝点),装了Bluepoint后,所有软件都可以读写中文,字体非
常漂亮,一点不亚于Win98,蓝点预览版在网上一公布,立刻成为BBSLinux论坛上的明星。为了把产品很快地推出去,蓝点看准了与长城、TCL
厦华等电脑大牌明星的共同利益所在,找到了与他们的结合点,于是借大船出海,在长城公司的主打产品——金长城“飓风699”系列和“居易”系列电脑、厦华
三宝的主打产品“状元一族”及TCL精彩600、610、718系列电脑中全面预装上蓝点Linux,依附它们成长,使蓝点的产品很快打入了市场,在极短
的时间内销售呈现良好局面,也为其以后的壮大发展打下了坚实基础。

 
 我国沿海的许多中小企业,在短时间内迅速崛起,采用的也是依附成长的策略,通过为大型企业的出口产品生产相关的配套产品,达到出口的目的,赢得了较为丰
富的利润。对大型企业来讲,出口产品有小企业的附加产品,在国际市场上竞争能力更强;对小企业来说,在大型企业产品出口的同时,自己的产品也随之出口国
外,双方都有利可图。

  此外,他们还通过代工生产的方式,为某些跨国大公司定牌生产,借助跨国大公司的强大销售网络进入国际市场。或者是与外商合作,借用外商的资金、技术、渠道和管理,搭乘“顺风车”,通过借东风来提升品牌,把前期开拓和最为艰苦的事情让别人去做,而自己依靠个体优势去摘别人的已有成果。

  这些企业都是聪明的渔夫,懂得利用海船的强大和牢固,在大海里安全地捕获,赢得满船的金银财宝。

  大腕帮你

  借船出海,也得有眼光辨别,有能力把握,要是选择了一艘破船,就可能船沉大海,一切尽失;要是碰到一艘海盗船,甚至有可能会被人家丢到海里喂鲨鱼,性命不保。那么,怎样才能成为成功的“搭乘者”,“坐享渔利”呢?

  没有共同的利益和目标,是不可能走到一起的,共享赢利,是依附成长的前提,必须找到利益的切合点,才能跟人家去合作;另外,别人是大企业,你不找它,它可以找到千万个你,所以小企业得学会主动跟人家套近乎。

  成立于2000
年6月的泓远软件(上海)公司在上海只能算是“小不点”,但最近却接下了上海星巴克咖啡连锁的大单,泓远软件的生存之道要诀就是定位于连锁系统,称为“超
商”。“我们关注的是超商不是超市,超商不是单纯地卖东西,而是提供各种便利,比如市民缴费等服务。顾客因为要去超商缴费而顺便买点东西,买东西变成了附
带,这个革命就产生了。”

  泓远软件在对“超
商”做发展分析时发现,这是一股不可忽视的市场力量,由此他们找到了公司的定位。然而,即便是在“超商”的投标中,泓远软件也常常是最小的公司,其它竞争
对手一般都是上市公司。按通常的做法,小公司主要靠价格取胜,但泓远软件没有这么做。他们给顾客三大保证:第一,决不追加价格,这是一些大公司常用的伎
俩;第二,赔偿责任损失;最厉害的是第三招——你没想到就是我的错。而最能打动客户的是这家公司核算的成本相当经济,好多竞争对手都是开高级轿车参加招
标,而这家公司是打车去的,并且他们告诉客户,“我们把你们付的钱都用在为你们服务上,你们不必付我们昂贵的轿车费”,客户对此很感动。公司的“小”也因
此变成了优势。

  泓远软件的经营策略是:让各种连锁店应用自己提供的系统,让别人的成功成为公司成功的一部分。泓远软件先从“超商”切入,因为“超商”能拿到许多大的合约。之后会从“超商”做到各种“通路”上,包括连锁咖啡店、连锁花店、连锁服饰店。目前的客户有上海的喜之多、全佳便利店、星巴克、多样屋、仙踪林、好佳好等。

  是“依附”而不是“归并”

  对于很多小企业来说,既然没有希望与行业龙头企业竞争,可以干脆参加进去,成为行业龙头企业生产经营集团中的一员,但要注意的是,这里所说的“依附”是指参与大型企业集团的生产经营,作为大型企业生产经营网络上的一个环节,企业在产权上还是独立的。

  是“依
附”而不是“归并”,这是最重要的一点。依附的小企业是处在大型企业的松散层,与大型企业集团只是生产经营上的联系,仍然享有较大的经营自主权,并可以同
时依附几家不同的大型企业集团。这样,由于大型企业集团的生产经营相对比较稳定,因此,小型企业就有相对比较稳定的生产经营环境,并且能够随着大型企业集
团的发展而得到发展。

  杜健创办的重庆琦璐文具
锁公司是坐落在重庆市渝中区大坪虎头崖1号的一家专业从事文具连锁店的企业。重庆琦璐开业之时仅仅只有一个10平方米的小门面,以经营小百货为主,靠卖烟
酒、饮料、副食等小商品获取微利生存,有时还入不敷出。一个偶然的机会,得到了山城超市招租文具门面的消息,杜健从小巷子里面的小店面搬迁到了山城超市,
“正正规规”地营业了。由于山城超市的门面大、地理位置好、人流量也大,琦璐第一个月就盈利,以后月月攀升。

  山城超市正处于“青
壮年”时期,从渝中区开到了大坪,再开到了杨家坪,接着又是石坪桥、沙坪坝、观音桥、上清寺、牛角头……连锁店一家接着一家地开,然而这时的琦璐只是刚刚
学会走路的孩子,能不能象山城超市那样分店一个接着一个地开呢?这时,琦璐果断地将自己依附在山城超市上,不但吸取他们的管理经验,也吸取他们的发展模
式,山城超市开在哪里,琦璐就开在哪里。依托山城超市的发展,琦璐现在已经发展成了拥有30多个连锁店,员工200多人,年销售收入近5000万元。

  事实上,琦璐的“依
附”行为就象孩子在婴幼时期需要哺育一样,需要不断地吸取营养。如果站在小企业的角度研究企业的发展历程,依附甚至是任何一个小企业发展的必经阶段。小企
业审时度势,可依附核心企业,借势生存。小企业也可以接纳大企业转移出的部分产品生产线,在大企业的技术指导与质量监督下,其成品以大企业的品牌包装进入
市场,这对力量薄弱的小企业不失为一种积累实力,谋求生存空间的捷径。

  仔细分析发现,不管是小企业依附核心企业的发展模式,还是小企业“借船出海”的营销模式,对大型企业和核心企业来讲,小企业的“依附服务”要么具有附加价值,要么具有分工专业化的收益。所以准确地说,这应该不算是纯粹的寄生,而是半寄生半共生行为。

  在琦璐与山城超市的寄生、共生中,还存在一个从寄生到共生的过程。先期,由于琦璐什么也没有,缺乏资金、缺乏管理、缺乏人才、缺乏渠道、缺乏人流量,这时琦璐“合法”地依附在山城超市,不断地学习、不断地吸取,在吸取的过程中不断地消化,同时,资金积累了、知识丰富了、业务扩大了,这时琦璐就将自己积累的优势和山城超市共享,形成了共生关系。

 
 在山城超市的发展过程中遇到了困难,琦璐首先帮助解决,原因很简单,一旦山城超市垮了,店面没有了,琦璐也就跟着垮了。同理,一旦琦璐有什么困难,供货
中断了,山城超市的文具也就空缺了,而以文具为主要业务的山城超市的利润也就枯竭了,所以山城超市也就要帮助琦璐解决困难,因为他们是共生的关系,谁也离
不开谁。

绝招4:做小池塘中的大鱼

  优势不能分享,独享才有利润,利润是企业生存和发展之本。对于小企业来说,如果那些大企业说这个市场前景非常大,将来肯定不得了,那你干脆不要做了。因为一旦被大企业看中的市场,你又怎么可能血拼得了呢?惟一的生存之道就是独辟蹊径,开创自己的独有市场。

  小企业千万不要想着做大池塘里的小鱼,一定要做小池塘里的大鱼,因为一些大的企业看不上这些小池塘,不愿意跟你竞争,而这正可以成为让你成功获取创业“开门红”的巨大空间。

 
 在我们的现实生活中,常有一些只得到局部满足,根本未得到满足或正在孕育即将形成的社会需求。这样的需求盲点构成了潜在的市场空间。发现和预测潜在需
求,是一项难度极大、艺术性极强的工作。小企业一旦发现前景良好的潜在市场空间,就应着手做好开发、生产、销售、管理工作,以建立迅速扩大自己的优势,加
固经营壁垒,提高后来业者进入障碍,提高垄断能力,延长中小企业垄断这一市场区隔的时间,以期获得丰厚的经济收益。

  造就无竞争空间

  一位经济学家在飞机场的高档酒店里喝了一杯咖啡,喝完之后一结账,要价98元,经济学家一算,这不是牟取暴利吗?于是对机场的有关部门进行愤怒声讨,一时引发了众多媒体讨论应和,最后结果怎样呢?一切依然如故,没有丝毫改变。原因就在于,机场、宾馆、游乐场等地方,本来就是高档消费场所,他们拥有独享的资源,你能奈何得了人家吗?

  反过来看,小企业在创业之初也可以让自己享受到这种独享的暴利,只要可以寻找到一个只属于自己的生存发展空间。

  在山东沭河岸边有一个柳编之乡,这里拥有丰富的柳编资源,由于气候和地域原因,这里的柳树韧性强、洁白度高、质地优良,是柳编的绝好材料。一家小企业看好了这一独特的资源优势,在此投资建厂,投资并不大,但回报丰厚,几年下来利润成倍增长,企业越做越大。

  所谓资源独享就是要占有稀缺资源,或开发独家产品,把竞争对手排除在外,建立起买方的独家市场。有了偷不去、买不来、拆不开、带不走、溜不掉的独家资源,谁还能在该市场上胜得过你呢?

  河北
州乡谣公司是一个奶制品小厂,由于遭遇娃哈哈、乐百氏等大品牌的冲击,销售艰难,处境非常危险。为此,这家小厂专门找了专家进行分析,专家经过考察后发
现,娃哈哈、乐百氏在当地影响很大,要想翻身必须有特别鲜明的独特卖点,否则将很难生存。通过大量阅读资料,专家们发现,河北沧州是我国最严重的高氟区之
一,当地的饮用水源含有过量的氟,对人体健康非常不好,很多沧州人得的地方病就跟当地的水质有关系。这个资料搞清楚以后,马上跳出一个大胆的想法:能不能
生产一个降氟牛奶?只要消费者知道他们的病和“高氟水”有关,“降氟牛奶”就有戏!于是乡谣公司马上与北京食品工艺研究所合作,开发具有降氟功效的新产
品。现在乡谣牛奶在沧州已经全面上市,并且在当地引起了较大的反响。公司从以前的日销3000袋,到现在的2万袋,不但很快打开了市场局面,而且已经盈利
近30万元。

  最为关键的是,由于降氟牛奶是只针对河北沧州市的水源情况专门设计的牛奶,因此,娃哈哈、乐百氏这样的大品牌绝对不屑于为了一个小市场而改变产品生产加工的整个流程。乡谣公司反而乐得在这个小小的池塘中,过起了大鱼的轻松日子。

  惟我独有,谁与争锋?

  优势不能分享,独享才有利润,利润是企业生存和发展之本。对于小企业来说,如果那些大企业说这个市场前景非常大,将来肯定不得了,那你干脆不要做了。因为一旦被大企业看中的市场,你又怎么可能血拼得了呢?惟一的生存之道就是独辟蹊径,开创自己的独有市场。

  1993年,“煌上煌”还不过是绳金塔一家前店后坊的熟食小店,而今天,“煌上煌”已是拥有资产近亿元的集团公司。公司属下的煌上煌烤禽连锁店光在南昌市内就有约70家,在全国共有150多家,这样的业绩不仅在江西省首屈一指,在全国的熟食行业也算得上佼佼者。

  从家庭式作坊到现代化企业,徐桂芬完成了一次成功的嬗变,这其中到底有何奥秘?

  1993
年,徐桂芬遭遇人生中的一次重大考验。由于食品公司经营不景气,徐桂芬下岗了。由此,徐桂芬迈出了创业的第一步,她身揣几千元钱从小生意做起。创业必须吃
苦,但多动脑筋才能将生意做大,徐桂芬开始思考有什么商机可以将生意扩大。徐桂芬在食品公司呆了多年,对食品市场非常熟悉,于是她从南昌市的菜市场入手做
了一番调查。“我发现,不少菜市场内都有卤菜店,买卤菜的人也较多,但偌大的南昌市竟没有一家本地人开的卤菜店。”徐桂芬说:“当时南昌的卤菜生意主要由
温州人和潮州人垄断。温州人加工制作的卤菜品种比较丰富,但美中不足的是较清淡;而潮州人的卤菜大多是一些海产品和卤鹅,品种显得单一。我想,如果集这两
家之长,去两家之短,制作适合南昌人口味的卤菜食品肯定受欢迎。”

  仅仅是这一更适合南昌人口味的改变,就让徐桂芬从创业初期就拥有了一个罅隙中的庞大市场。1993
年2月,徐桂芬在绳金塔附近创办了南昌煌上煌烤禽社,烤禽社面积不过二三十平方米,员工也只有两三人。徐桂芬做的卤菜口味符合南昌人嗜辣的习惯,因此在开
张的近半年时间里烤禽社生意一直不错。那时南昌的卤菜店已有很多家,相互之间的竞争也十分激烈,在煌上煌烤禽社附近就开了好几家卤菜店。徐桂芬意识到,自
己的店应该推出独家的拳头产品才能吸引更多的顾客,才能在竞争中取胜。1993年夏,徐桂芬远赴浙江、广东等地,登门求教技术精湛的卤菜师傅和技术名家,请他们传授配料秘方和烹调技法。回到南昌后,她作出一个选择——把酱鸭作为煌上煌烤禽社的主打产品。

  让徐桂芬始料不及的是,这只小小的酱鸭竟最终让“煌上煌”坐上了南昌乃至江西熟食品牌的“头把交椅”,成就了煌上煌烤禽社的成功。

  谈起酱鸭的历史,徐桂芬劲头十足。“不
要以为一只酱鸭很容易制作,当年我为做出这只酱鸭,进行了数百次实验。”徐桂芬说,“一只酱鸭要加入30多种中药,经过几十道工序、约20个小时才能制
成。”“在酱鸭做出来后,我把1000多斤酱鸭切成小块,分放在10几个大脸盆中,在南昌的闹市区和一些大街小巷分送给过往行人,让他们免费品尝,并要他
们提意见,根据反馈的情况,我再改进酱鸭的口味。”通过不断改进,徐桂芬调制出了具有独特风味的煌上煌酱鸭。

  免费品尝这招以前还没有哪家卤菜店做过,徐桂芬的做法不但完善了酱鸭的口味,还让路人吃过后留下深刻印象,很快提高了酱鸭的知名度,南昌人逐渐知道了煌上煌酱鸭,不少人被酱鸭的味道迷住而成为“煌上煌”的回头客。

  从1994年开始,煌上煌酱鸭的魅力日渐大起来,慕名而来购买的人越来越多,在一些节假日的前夕,买酱鸭的顾客甚至排队排到了马路中间。酱鸭的热销也带动了店内其它卤菜的销售,煌上煌烤禽社开始加速发展。

  一只酱鸭不过十几块钱,徐桂芬历经9年却能在手中生出一个拥有近亿元资产的现代化企业,这样的成功似乎让人感觉不可思议。徐桂芬说:“要善于抓住身边的机会,敢于冒险,敢于实践。”当初徐桂芬敏锐地抓住了市场的商机,用一只鸭子打天下,并最终在竞争激烈的卤菜熟食市场取胜,徐桂芬的创业历程就是这么简单。

  社会的转型时期,市场商机数不胜数,“煌上煌”的故事说明,即使在鸭子这种看似不起眼的东西上做出独家的文章,并且找准市场的空白点,将其做大做强,也完全可以成就一番大事业。

  惟“独”我有,惟“利”是图

  独家优势只有独自享用,才能在竞争中取胜,赢得比别人更多的利润。那么怎样才能把独家优势变成自己的孩子,独自享受他的孝顺呢?显然最关键的是利用这种优势,开发出自己的独家产品,做成惟“独”我有,把竞争对手排除在外,构建自己的独家市场,才能惟“利”是图。

  上世纪80年代初,黄季霜还是一名煤矿职工。因为有书画特长,每天工作之余的写写画画,便成了他生活中最有意义的事情。因为长期使用毛笔,黄季霜发现:毛笔由于不透气,画出来的线条容易发腻,极大地限制了作画人的创造性。于是,他就琢磨,能不能用其它材料制作笔,写完后,既合乎传统的艺术审美理念,同时又富有特色。

  毛笔在某些方面的缺陷,或许就是一个潜在的商机,黄季霜恰恰看到了这一点。他辞掉了工作,开始专心做起了草笔的研究工作。9年后,黄季霜终于拿着用草做好的第一支笔,做了一幅画。

  草笔制作的成功,让黄季霜兴奋不已,因为事实证明,草笔的使用性能已经远远超出了毛笔。但当黄季霜想将其推向市场的时候,他却发现了新的问题:他的草笔从外观上看,和毛笔十分相似,没有明显的个性特征,同时也就失去了它的卖点。

  为了使草笔与毛笔从外观上可以有明显区别,黄季霜又在笔杆上动起脑筋。最后,他选择了当地特产的一种铁杆草,铁杆草通身泛绿,不论在室内还是在强光照射下始终是绿色。

  此时,黄季霜的草笔已名符其实,因为不仅草笔笔头用的是当地特产的野生茅翎草制作而成,就连笔杆也选用了东北特产的旱地苇杆以及苇杆与铁杆草结合制作而成。草笔的使用性能与外观设计确定后,黄季霜开始营造自己的草笔市场。

  黄季霜认为,一个新产品上市,100
个外行人说100个好,不如一位专家说一句好,同时专家的评价也是草笔市场价值定位的关键。他首先想到的是中国书协、中国美协的名家们,于是,黄季霜先是
拜访了书法大家大康先生,并得到了大康先生对草笔的高度赞扬和认可。接着,他又拜访了书画家沈鹏、欧阳中石、李成业、程中元等,无一例外,得到了他们认
可。

  2002年,黄季霜将草笔投放市场后,收到了较好的效果,按说应该扩大规模,集中上市了,但他的做法却与常规不同,黄季霜不但不去扩大生产规模,却常常有意识地限制草笔的生产数量。他深谙物以稀为贵的道理,而且,大批量生产,势必导致质量不稳定和价格的下滑。

  黄季霜之所以能够成功地运作这种销售方法,原因在于他在实施之前就已经为草笔申请专利,从而才能自如地限制草笔的生产数量。现在尽管草笔的市场价格远远高于毛笔七到八倍,卖到160元一支的高价,但产品仍然供不应求。


绝招5:学会做老二

  会做“老二”并非真的是甘居人后,而是可以从做“老二”中尝到更多的甜头,从而使自己的创业在一开始就可以借“蹭车”获得利润。

  中国台湾
业的经营管理的概念中,有一种叫“老二哲学”的说法,就是不做第一,不做第三,而只是紧紧跟在排名第一的后面做老二,瞄准机会再冲刺第一。或许是暂时不愿
做“出头鸟”,或许是想挂在后面搭个便车,但最终是没有一家会甘居第二的,“老二”也只是个过渡。创业者在创业之初,要学会做“老二”。

  事实上,会做“老二”并非真的是甘居人后,而是可以从做“老二”中尝到更多的甜头,从而使自己的创业在一开始就可以借“蹭车”获得利润。

  找对“火车头”

  不做火车头,就是人无你有的不要做。最典型的例子就是万燕做VCD
行业的火车头,最后钱都让步步高、爱多他们赚去了。当年万燕花了大把的钱,告诉消费者:VCD是好东西。直到市场培育好了,大家都知道VCD是个好东西
时,步步高、爱多出手了:建树自己的品牌,完善自己的营销网络,再把价格降下去,成功了!万燕呢,一把鼻涕一把泪地当“革命先烈”去了。

  生存第一,对于小企业来说,“慢
半拍”才是捷径。比如一个投资12万元的餐饮店,“硬件要上水平”、“服务要领先”,而且要“全方位导入企业形象设计”,这种“星级酒店水平”对它合适
吗?一家年销售十几万元的初创企业,有人建议它“技术领先”,成立“单片机”研究开发部门,申请ISO国际质量认证,要知道,这个公司目前连个专业技术人
员都没有。一个市场,10年以后的前景被描述得非常好,确实也不错,问题是,你怎样让这个企业挺过这两三年?

  眼下,不少创业者认为开发新产品应采取“先
人一步”的战略,此种先发制人的举措无可厚非,而“步人后尘”者则不应视为落伍者。这类“落伍者”之中,大多数是处于创业之初的中小型企业,他们在开发新
产品中,由于受到资金、技术力量、人才储备等诸多因素制约,新产品开发步履艰难,很难尽快形成规模,产生效益,这些也正是他们所苦恼和刻意加以解决的难
题。而有些中小企业本无“先人一步”的能力,也拼命地往前冲,不仅新产品开发没有形成气候,投入市场后难免存在这样那样的缺点,结果使企业处于困境。“先
人一步”必须具备了一定的实力方可行事,“慢人半拍”也非无能,尤其对那些技术力量单薄、资金不雄厚、技术人才缺乏的初创企业,更应令企业当家人三思而后
行。

  《科学投资》杂志认为,对于那些创业者来说,在开发新产品时,创造较好的经济效益关键不在于“先
人一步”与“慢人半拍”,而在于抓准了、抓住了开发新产品的“时间差”,打出好的“落点”,从别人产品中吸取优点和长处,不断改进自己的缺点和不足,扬长
避短,在市场上也能唱出后发制人的好戏来。也就是说,小企业不做火车头,却一定要找对火车头,也就是找对新兴的市场。对于创业企业来说,准确寻找到“火车
头”,就意味准确寻找到利润的方向,并且可以及时搭乘上这趟列车去迎接“开门红”的到来。

  陈方的“前
卫越野俱乐部”并不是国内第一家,但是他却看准了两点,第一,国内私车消费市场日趋成熟,越来越多的人购买私车,这其中包括极多的越野车爱好者。并且随着
车价的下降,购车人数迅猛增加,这将是一个极庞大的市场;第二,大漠孤烟、长河落日也仅仅是沧海一粟。想聆听大自然最真的心跳,光走走景点是远远不够的;
要真正了解西部,只有亲身投入到她的怀抱,感受她!因此西部旅游业也日渐红火起来。两个成熟因素同时存在,让陈方找到了一个不可多得的商机——场地是大自然免费奉送的,整个地区的市场是空缺的。更重要的是,谁能抵挡住在古丝绸之路上风驰电掣一回的极限诱惑?

  创业之前,陈方一直从事旅游业,作为一名忠实的爱车族,凭着自己在旅游业多年闯荡的经验,陈方觉得自己找到了全新的创业方向。四驱车的最大好处,就是能够在其他车辆难以通行的地方顺利行驶,使驾驶者更贴近大自然的原始状态,享受克服障碍、勇往直前的驾驶乐趣。西部大开发的大环境,也使越来越多的人将目光投向西部。将汽车越野与旅游结合起来的这种新兴体育休闲运动,也容易引起人们的兴趣。天时、地利、人和兼备,陈方满怀信心地踏上创业征程。

  人员到位了,车辆购齐了。甘肃前卫越野俱乐部”在兰州正式挂牌营业。兰州是通往古丝绸之路和雪域高原西藏的最佳起始点,也是前往广阔无垠的内蒙古大草原、神秘苍凉的周边大漠,以及山川秀美的巴蜀的必经之地。其特殊的地理位置为发展4×4自驾车越野运动提供了得天独厚的便利条件,而且成本也相对较低。

  由于自驾车野外探险旅行是一种高消费娱乐活动,陈方将目标客户群锁定在白领和四驱车爱好者两个群体。不过,在实际运营中,他发现来自普通收入阶层的顾客也不在少数,于是,他立即对经营策略做了调整,推出了两档后勤服务类型——普通档和豪华型,以适应不同客户的需要,同时,还为自己没车的顾客提供租车服务。

 
 俱乐部采取的是会员制,会员每年要缴纳一定数额的费用,可享受免费提供的旅行咨询信息及行程计划、车辆信息咨询、驾驶技术咨询,以及车辆维修和改装的咨
询。为扩大俱乐部的影响,陈方在宣传方面也下了大功夫,定期组织会员们参加各种联谊和公益活动,吸引了更多热爱旅游和热衷于寻求刺激的潜在顾客。

  随后的日子里,登上了西部旅游这个高速列车的陈方和他的越野俱乐部,几乎是一路畅通地发展至今。

  学会“蹭饭”

  请客吃饭,来的人中通常有两种,一种是应邀前来的,另一种则是硬要前来的。初创的小企业,要资金没资金、要技术缺少技术,因此在品尝市场大餐时,很少能够被“邀请”。不过,这并不意味着这顿饭就吃不到嘴里。在大企业“应邀”时,小企业学着“蹭顿饭”也未尝不可。而且还别小看了这“蹭饭”,小企业专门为这顿饭而去,就只管埋头吃饭,到最后吃到嘴里的反而比大企业还要多。

  山西
样红饮料就曾经踏踏实实地蹭了一顿美餐。红牛饮料刚刚进入山西市场时,整个山西市场中使用金罐子的饮料只有红牛一家。由于红牛强大的广告攻势和相对偏高的
市场价格,因此在山西消费者心目中形成了一个概念,用金罐子的饮料都比较高档。此时,别样红抓住了这个机会,也使用金罐子包装上市,马上给当地消费者造成
了一种视觉冲击,认为别样红与红牛一样,都是高档饮料。其结果就是,别样红节省了一大笔的宣传费用,并迅速敲开了市场大门。

  现在我们可以明显地看到,身处一个明星当道的时代中,只有明星,特别是全球性明星,才能在竞争激烈的社会中拥有更多属于自己的利益。迈克尔.
乔丹只有一个,第二名,尽管天分可能相差无几,但也只能拿到1/10甚至1/100的收入。日益发达的交通和通讯设施,和正在成熟的网络经济时代,正在改
变人类的生存状态,也使得企业间的竞争变得越来越残酷。第二也好,第三也好,只有先生存、找出路,才可能再谋发展。毕竟登上塔顶浪尖的企业少之又少,对于
大多数公司来说,在第一、二名的光环外找到自己的生存空间,无疑是更现实的解决之道。

  《孙子兵法》曰:不战而屈人之兵,乃上策也。技术创新“跟着走”便是不战而屈人之兵的上策。近年来,我国一些企业在技术创新中,也开始采用这一策略。日本索尼
司在不久前曾向外界公布了一个秘密,带给我们很多启示。过去,索尼在研发上投入很大,但往往只开花不结果,花了九牛二虎之力将新产品推出之后,别的公司却
每每已经掌握了相关技术,所以,索尼公司成了冤大头,为他人做嫁衣裳。为此,索尼公司改变了策略,紧跟市场,待别人推出新产品打开市场后,索尼马上研究其
不足,通过进一步的技术创新,开发并迅速推出其第二代产品,在性能、价格、设计等方面都优于对方的第一代,结果取得了“青出于蓝而胜于蓝”的技术创新和市
场竞争效果。显然,这种“跟着走”的技术创新策略是相当巧妙的,它所具有的“螳螂捕蝉,黄雀在后”的市场竞争之利也不言而喻。

  技术创新“跟
着走”虽然是条捷径,但也并非是一蹴而就的易事,它要求“跟着走”的信息一定要灵,动作一定要快,否则,就会跟不上。我国的国产手机,也曾采取在发达国家
同行后“跟着走”的技术创新策略,但由于在跟踪的过程中犯了大公司病,反应迟缓,动作不快,结果产品出厂时已届市场饱和点,致使事倍功半,留下了长久的遗
憾。这一教训十分深刻。小企业在实施“跟着走”策略时应该认真吸取。

  对于创业者来说,不仅是技术创新,就是品牌战略,也要学会做“老
二”,“老二”如何制定和发展自己品牌战略并建立品牌竞争优势,是需要综合审视自身实力,竞争者情况以及市场变化。首先,“老二”应心明眼亮自身品牌在市
场的地位以及在顾客心目中的位置,再针对“老大”的品牌战略,走有差异化的品牌路线,同时在产品服务经营上保持低的生产成本和高水平的产品质量与服务,逐
步提高品牌地位。在具备相当实力后,“老二”应确定其品牌战略目标,针对“老大”相对较弱的环节,确定相应的进攻战略,进行有足够攻击力的产品、服务、渠
道创新,并实施整合广告营销传播,向“老大”发起一场卓有成效的品牌竞争战役,从而赢得顾客,赶上甚至超过“老大”品牌的认知度、美誉度及客户忠诚度。从
百事挑战可口可乐的佳绩、佳能在复印机市场超越施乐,以及电脑行业戴尔
崛起,我们看到了“老二”们的希望。创业者学会做“老二”,并不是目的,是一种手段,目的是为了成为“老大”。不积跬步,无以致千里,不积小流,无以成江
河,创业者不学会“扫一屋”,就难以顺利地“扫天下”。学会做“老二”,是一种现实选择,是生存的需要。有限的资金,捉襟见肘的实力,技术及人才资源的不
足,创业者如果不学会做“老二”,被雄心勃勃、豪情壮语、大干快上的“创业激情”冲了头脑,无疑是不自量力,“以卵击石”。学会做“老二”,是一种经营谋
略,“上兵伐谋”就是这个道理。


绝招6:撒豆成兵小买卖做出大收成

  渠道即血管,抢在别人前面把血运送到需求者的眼前,就是胜利。渠道同样是个重要的传播过程。

  在红桃K鼎盛时期,其在全国2000多个县每县设有100名员工建制。数万名员工走向广阔的农村天地,见人发报纸,见墙刷墙标,使红桃K的业绩发生核裂变式的飞跃,成为保健品市场老大。红桃K的意图非常简单,渠道即血管,抢在别人前面把血运送到需求者的眼前,就是胜利。渠道同样是个重要的传播过程。永远不能责怪消费者不懂、无知、不靠近,而应主动靠近消费者,告诉你为他准备了什么产品和价值。

  《封神演义》写闻仲与姜尚大战岐山,由于商朝军队得到申公豹一帮道友相助,西周军队渐渐不支。关键时刻,姜尚得到燃灯古佛相助,撒豆成兵,反败为胜。这一概念如今被许多企业运用自如。但在很多小企业的思维方式中,撒豆成兵是需要资金堆砌的,因此往往不敢企及。

  “娃娃屋”遍地开花

  北京有一个专门经营陶瓷
娃的店铺,最初的时候仅仅是在西单选择了一个小小的门脸。店铺由于经营的全部是出口欧洲的精致陶瓷制作的各种形态的娃娃,销路一直很好。但是,西单这样一
个北京市一线繁华地带的店面,租金高昂可想而知,经营一个店铺能够有多大的发展前景,如何才能将店主投入的几万元尽快赚回来呢?店主想了一个办法,在店面
布置妥当并开张后,马上开始了在全国的招商。由于控制了货源渠道,因此招商效果极好。加盟店只需交纳1万元钱,就可以获得店主固定的货源提供,而开一家
30多平米的小店,第一次进货费用2-3万元就足够了。这样一来,店主从零售顺利转为批发,随着加盟店铺的增加,加盟费用、批发的收入,让这家小店很快就
收回了成本,既而进入了轻松的赢利阶段。

 
 所谓撒豆成兵,就是通过推广体系组织的功能推展经营活动,达到接受产品的目的。这里面有两个方面的含义:一是要经营好组织,也就是组建、管理好一个有特
色的根据地。二是要善于发挥组织的功效。二者相辅相成。社会发展史告诉我们,人,只有组织起来,才能产生倍增效应。比如有1
万个无组织、无纪律的各行其是的人,必然是一盘散沙,毫无战斗力。但当1万人按一定的组织原则和秩序排列成方阵,则可以产生气壮山河、震天撼地的伟力。我
们常说团结就是力量,实际上就是讲组织起来的力量。一定的组织都是一定的社会成员为了达成特定的共同目标而自觉形成的有一定秩序和功能的排列组合体。

  对于小资本运作的小型企业来说,只要项目有特色、有需求,利用连锁迅速扩张市场,获取利润并非难事。

  袁小萍,41岁,一个走在街上毫不起眼的上海女人。8个月前,她还在为没有一份固定工作而困惑烦恼,现在却已是拥有20多个连锁点的“无水洗车佳佳服务社”的老板了。而且,她的连锁点正在以平均每周增设一个的速度迅速膨胀着。

  一个偶然的机会,袁小萍在《新民晚报》上看到“车
洁灵”环保无水洗车4050项目的招标广告。由于这个项目投资少,又有政府扶植,再加上她曾经做过汽车、摩托车的生意,她觉得眼前豁然一亮。此外,与汽车
打交道也很符合她好强干练、男子汉一样的性格。但她没有盲目进入,而是花了整整两个月的时间进行了市场调查。那一年的寒冬腊月里,上海许多大停车场、商务
楼以及物业小区里,都出现过袁小萍的身影。终于,袁小萍得出结论:这一行有得干!于是,袁小萍报名参加了上海市政府专门为下岗职工开设的创业培训班,免费
享用了金融、财政等方面的“充电”。在交纳了4500元材料费和3万元启动资金后,袁小萍的第一个洗车点开张了。

  开一个无水洗车服务社能赚多少钱?她的洗车点刚刚开张时,袁小萍捋起袖子,自己上阵洗车。让她喜出望外的是,10
天后洗车量就从每天10辆猛增到70辆。但是即使她每天洗100辆车,收回3万多元的启动资金至少也要半年多。好在袁小萍脑子活泛,她看到了无水洗车的市
场前景和不断增大的需求,于是迅速又开了一个点,并且随着送洗的车越来越多,连锁点也越开越多。很快,她就收回了全部投入。

  走出两个误区

  《毛泽东选集》开篇第一句话就是:谁是我们的敌人,谁是我们的朋友,这个问题是革命的首要问题。小企业经商,建立推广体系,其首要问题,也是要根据自身产品的性质,选准加盟对象。人是构成组织的基本要件,角色定位不准,往往一无所获。

 
 《科学投资》杂志发现,在这方面,许多小企业很容易陷入一个误区,即亲情误区。一说要建网点,赶忙打开自己的关系联络图,找老同学、老战友、老熟人、老
朋友或三亲六眷。但这种网点建得再多,也如沙上垒塔,一触即垮。结果是产品没有推出去,货款也收不回来,最终友情也受到破坏,赔了夫人又折兵。所以,建立
在友情基础上的所谓网点,就如草上露、瓦上霜,太阳一出不久长。比如北京的某厂,当初依靠关系建立了1OO多个“点”,为确也热闹了一番,但到年终,不但产品也销不出去,还陷于债务危机,产品很快报废了。

  最为关键的是,并非所有的企业都适合拷贝这套“撒豆成兵”的战术,这需要跃跃欲试的小企业作出情清的商业判断。



转贴:C语言陷阱和缺陷

Posted on 2005年01月27日 11:07 PM

原文链接:http://lover_p.cstc.net.cn/lover_P/doc/Translations/CTraps/CTraps.htm

C语言陷阱和缺陷[1]

原著:Andrew Koenig – AT&T Bell Laboratories Murray Hill, New Jersey 07094
原文:收藏
翻译:lover_P
出处:本站


[译序]

那些自认为已经“学完”C语言的人,请你们仔细读阅读这篇文章吧。路还长,很多东西要学。我也是……

[概述]

C语言像一把雕刻刀,锋利,并且在技师手中非常有用。和任何锋利的工具一样,C会伤到那些不能掌握它的人。本文介绍C语言伤害粗心的人的方法,以及如何避免伤害。

[内容]

 

0 简介

C语言及其典型实现被设计为能被专家们容易地使用。这门语言简洁并附有表达力。但有一些限制可以保护那些浮躁的人。一个浮躁的人可以从这些条款中获得一些帮助。

在本文中,我们将会看一看这些未可知的益处。这是由于它的未可知,我们无法为其进行完全的分类。不过,我们仍然通过研究为了一个C程序的运行所需要做的事来做到这些。我们假设读者对C语言至少有个粗浅的了解。

第一部分研究了当程序被划分为记号时会发生的问题。第二部分继续研究了当程序的记号被编译器组合为声明、表达式和语句时会出现的问题。第三部分研究了由多
个部分组成、分别编译并绑定到一起的C程序。第四部分处理了概念上的误解:当一个程序具体执行时会发生的事情。第五部分研究了我们的程序和它们所使用的常
用库之间的关系。在第六部分中,我们注意到了我们所写的程序也不并不是我们所运行的程序;预处理器将首先运行。最后,第七部分讨论了可移植性问题:一个能
在一个实现中运行的程序无法在另一个实现中运行的原因。

1 词法缺陷

编译器的第一个部分常被称为词法分析器(lexical analyzer)。词法分析器检查组成程序的字符序列,并将它们划分为记号(token)一个记号是一个有一个或多个字符的序列,它在语言被编译时具有一个(相关地)统一的意义。在C中, 例如,记号->的意义和组成它的每个独立的字符具有明显的区别,而且其意义独立于->出现的上下文环境。

另外一个例子,考虑下面的语句:

if(x > big) big = x;

该语句中的每一个分离的字符都被划分为一个记号,除了关键字if和标识符big的两个实例。

事实上,C程序被两次划分为记号。首先是预处理器读取程序。它必须对程序进行记号划分以发现标识宏的标识符。它必须通过对每个宏进行求值来替换宏调用。最后,经过宏替换的程序又被汇集成字符流送给编译器。编译器再第二次将这个流划分为记号。

在这一节中,我们将探索对记号的意义的普遍的误解以及记号和组成它们的字符之间的关系。稍后我们将谈到预处理器。

1.1 = 不是 ==

从Algol派生出来的语言,如Pascal和Ada,用:=表示赋值而用=表示比较。而C语言则是用=表示赋值而用==表示比较。这是因为赋值的频率要高于比较,因此为其分配更短的符号。

此外,C还将赋值视为一个运算符,因此可以很容易地写出多重赋值(如a = b = c),并且可以将赋值嵌入到一个大的表达式中。

这种便捷导致了一个潜在的问题:可能将需要比较的地方写成赋值。因此,下面的语句好像看起来是要检查x是否等于y

if(x = y)
foo();

而实际上是将x设置为y的值并检查结果是否非零。在考虑下面的一个希望跳过空格、制表符和换行符的循环:

while(c == ‘ ‘ || c = ‘\t’ || c == ‘\n’)
c = getc(f);

在与‘\t’进行比较的地方程序员错误地使用=代替了==。这个“比较”实际上是将‘\t’赋给c,然后判断c的(新的)值是否为零。因为‘\t’不为零,这个“比较”将一直为真,因此这个循环会吃尽整个文件。这之后会发生什么取决于特定的实现是否允许一个程序读取超过文件尾部的部分。如果允许,这个循环会一直运行。

一些C编译器会对形如e1 = e2的条件给出一个警告以提醒用户。当你趋势需要先对一个变量进行赋值之后再检查变量是否非零时,为了在这种编译器中避免警告信息,应考虑显式给出比较符。换句话说,将:

if(x = y)
foo();

改写为:

if((x = y) != 0)
foo();

这样可以清晰地表示你的意图。

1.2 &| 不是 &&||

容易将==错写为=是因为很多其他语言使用=表示比较运算。 其他容易写错的运算符还有&&&,或|||,这主要是因为C语言中的&|运算符于其他语言中具有类似功能的运算符大为不同。我们将在第4节中贴近地观察这些运算符。

1.3 多字符记号

一些C记号,如/*=只有一个字符。而其他一些C记号,如/*==,以及标识符,具有多个字符。当C编译器遇到紧连在一起的/*时,它必须能够决定是将这两个字符识别为两个分离的记号还是一个单独的记号。C语言参考手册说明了如何决定:“如果输入流到一个给定的字符串为止已经被识别为记号,则应该包含下一个字符以组成能够构成记号的最长的字符串”。因此,如果/是一个记号的第一个字符,并且/后面紧随了一个*,则这两个字符构成了注释的开始,不管其他上下文环境。

下面的语句看起来像是将y的值设置为x的值除以p所指向的值:

y = x/*p /* p 指向除数 */;

实际上,/*开始了一个注释,因此编译器简单地吞噬程序文本,直到*/的出现。换句话说,这条语句仅仅把y的值设置为x的值,而根本没有看到p。将这条语句重写为:

y = x / *p /* p 指向除数 */;

或者干脆是

y = x / (*p) /* p指向除数 */;

它就可以做注释所暗示的除法了。

这种模棱两可的写法在其他环境中就会引起麻烦。例如,老版本的C使用=+表示现在版本中的+=。这样的编译器会将

a=-1;

视为

a =- 1;

a = a – 1;

这会让打算写

a = -1;

的程序员感到吃惊。

另一方面,这种老版本的C编译器会将

a=/*b;

断句为

a =/ *b;

尽管/*看起来像一个注释。

1.4 例外

组合赋值运算符如+=实际上是两个记号。因此,

a + /* strange */ = 1

a += 1

是一个意思。看起来像一个单独的记号而实际上是多个记号的只有这一个特例。特别地,

p – > a

是不合法的。它和

p -> a

不是同义词。

另一方面,有些老式编译器还是将=+视为一个单独的记号并且和+=是同义词。

1.5 字符串和字符

单引号和双引号在C中的意义完全不同,在一些混乱的上下文中它们会导致奇怪的结果而不是错误消息。

包围在单引号中的一个字符只是书写整数的另一种方法。这个整数是给定的字符在实现的对照序列中的一个对应的值。因此,在一个ASCII实现中,‘a’和0141或97表示完全相同的东西。而一个包围在双引号中的字符串,只是书写一个有双引号之间的字符和一个附加的二进制值为零的字符所初始化的一个无名数组的指针的一种简短方法。

线面的两个程序片断是等价的:

printf(“Hello world\n”);

char hello[] = { ‘H’, ‘e’, ‘l’, ‘l’, ‘o’, ‘ ‘, ‘w’, ‘o’, ‘r’, ‘l’, ‘d’, ‘\n’, 0 };
printf(hello);

使用一个指针来代替一个整数通常会得到一个警告消息(反之亦然),使用双引号来代替单引号也会得到一个警告消息(反之亦然)。但对于不检查参数类型的编译器却除外。因此,用

printf(‘\n’);

来代替

printf(“\n”);

通常会在运行时得到奇怪的结果。

由于一个整数通常足够大,以至于能够放下多个字符,一些C编译器允许在一个字符常量中存放多个字符。这意味着用‘yes’代替“yes”将不会被发现。后者意味着“分别包含yes和一个空字符的四个连续存贮器区域中的第一个的地址”,而前者意味着“在一些实现定义的样式中表示由字符yes联合构成的一个整数”。这两者之间的任何一致性都纯属巧合。

2 句法缺陷

要理解C语言程序,仅了解构成它的记号是不够的。还要理解这些记号是如何构成声明、表达式、语句和程序的。尽管这些构成通常都是定义良好的,但这些定义有时候是有悖于直觉的或混乱的。

在这一节中,我们将着眼于一些不明显句法构造。

2.1 理解声明

我曾经和一些人聊过天,他们那时在书写在一个小型的微处理器上单机运行的C程序。当这台机器的开关打开的时候,硬件会调用地址为0处的子程序。

为了模仿电源打开的情形,我们要设计一条C语句来显式地调用这个子程序。经过一些思考,我们写出了下面的语句:

(*(void(*)())0)();

这样的表达式会令C程序员心惊胆战。但是,并不需要这样,因为他们可以在一个简单的规则的帮助下很容易地构造它:以你使用的方式声明它。

每个C变量声明都具有两个部分:一个类型和一组具有特定格式的期望用来对该类型求值的表达式。最简单的表达式就是一个变量:

float f, g;

说明表达式fg——在求值的时候——具有类型float。由于待求值的时表达式,因此可以自由地使用圆括号:

float ((f));

者表示((f))求值为float并且因此,通过推断,f也是一个float

同样的逻辑用在函数和指针类型。例如:

float ff();

表示表达式ff()是一个float,因此ff是一个返回一个float的函数。类似地,

float *pf;

表示*pf是一个float并且因此pf是一个指向一个float的指针。

这些形式的组合声明对表达式是一样的。因此,

float *g(), (*h)();

表示*g()(*h)()都是float表达式。由于()*绑定得更紧密,*g()*(g())表示同样的东西:g是一个返回指float指针的函数,而h是一个指向返回float的函数的指针。

当我们知道如何声明一个给定类型的变量以后,就能够很容易地写出一个类型的模型(cast):只要删除变量名和分号并将所有的东西包围在一对圆括号中即可。因此,由于

float *g();

声明g是一个返回float指针的函数,所以(float *())就是它的模型。

有了这些知识的武装,我们现在可以准备解决(*(void(*)())0)()了。 我们可以将它分为两个部分进行分析。首先,假设我们有一个变量fp,它包含了一个函数指针,并且我们希望调用fp所指向的函数。可以这样写:

(*fp)();

如果fp是一个指向函数的指针,则*fp就是函数本身,因此(*fp)()是调用它的一种方法。(*fp)中的括号是必须的,否则这个表达式将会被分析为*(fp())。我们现在要找一个适当的表达式来替换fp。

这个问题就是我们的第二步分析。如果C可以读入并理解类型,我们可以写:

(*0)();

但这样并不行,因为*运算符要求必须有一个指针作为他的操作数。另外,这个操作数必须是一个指向函数的指针,以保证*的结果可以被调用。因此,我们需要将0转换为一个可以描述“指向一个返回void的函数的指针”的类型。

如果fp是一个指向返回void的函数的指针,则(*fp)()是一个void值,并且它的声明将会是这样的:

void (*fp)();

因此,我们需要写:

void (*fp)();
(*fp)();

来声明一个哑变量。一旦我们知道了如何声明该变量,我们也就知道了如何将一个常数转换为该类型:只要从变量的声明中去掉名字即可。因此,我们像下面这样将0转换为一个“指向返回void的函数的指针”:

(void(*)())0

接下来,我们用(void(*)())0来替换fp

(*(void(*)())0)();

结尾处的分号用于将这个表达式转换为一个语句。

在这里,我们就解决了这个问题时没有使用typedef声明。通过使用它,我们可以更清晰地解决这个问题:

typedef void (*funcptr)();
(*(funcptr)0)();

2.2 运算符并不总是具有你所想象的优先级

假设有一个声明了的常量FLAG是一个整数,其二进制表示中的某一位被置位(换句话说,它是2的某次幂),并且你希望测试一个整型变量flags该位是否被置位。通常的写法是:

if(flags & FLAG) …

其意义对于很多C程序员都是很明确的:if语句测试括号中的表达式求值的结果是否为0。出于清晰的目的我们可以将它写得更明确:

if(flags & FLAG != 0) …

这个语句现在更容易理解了。但它仍然是错的,因为!=&绑定得更紧密,因此它被分析为:

if(flags & (FLAG != 0)) …

这(偶尔)是可以的,如FLAG是1或0(!)的时候,但对于其他2的幂是不行的[2]

假设你有两个整型变量,hl,它们的值在0和15(含0和15)之间,并且你希望将r设置为8位值,其低位为l,高位为h。一种自然的写法是:

r = h << 4 + 1;

不幸的是,这是错误的。加法比移位绑定得更紧密,因此这个例子等价于:

r = h << (4 + l);

正确的方法有两种:

r = (h << 4) + l;

r = h << 4 | l;

避免这种问题的一个方法是将所有的东西都用括号括起来,但表达式中的括号过度就会难以理解,因此最好还是是记住C中的优先级。

不幸的是,这有15个,太困难了。然而,通过将它们分组可以变得容易。

绑定得最紧密的运算符并不是真正的运算符:下标、函数调用和结构选择。这些都与左边相关联。

接下来是一元运算符。它们具有真正的运算符中的最高优先级。由于函数调用比一元运算符绑定得更紧密,你必须写(*p)()来调用p指向的函数;*p()表示p是一个返回一个指针的函数。转换是一元运算符,并且和其他一元运算符具有相同的优先级。一元运算符是右结合的,因此*p++表示*(p++),而不是(*p)++

在接下来是真正的二元运算符。其中数学运算符具有最高的优先级,然后是移位运算符、关系运算符、逻辑运算符、赋值运算符,最后是条件运算符。需要记住的两个重要的东西是:

  1. 所有的逻辑运算符具有比所有关系运算符都低的优先级。
  2. 一位运算符比关系运算符绑定得更紧密,但又不如数学运算符。

在这些运算符类别中,有一些奇怪的地方。乘法、除法和求余具有相同的优先级,加法和减法具有相同的优先级,以及移位运算符具有相同的优先级。

还有就是六个关系运算符并不具有相同的优先级:==!=的优先级比其他关系运算符要低。这就允许我们判断ab是否具有与cd相同的顺序,例如:

a < b == c < d

在逻辑运算符中,没有任何两个具有相同的优先级。按位运算符比所有顺序运算符绑定得都紧密,每种与运算符都比相应的或运算符绑定得更紧密,并且按位异或(^)运算符介于按位与和按位或之间。

三元运算符的优先级比我们提到过的所有运算符的优先级都低。这可以保证选择表达式中包含的关系运算符的逻辑组合特性,如:

z = a < b && b < c ? d : e

这个例子还说明了赋值运算符具有比条件运算符更低的优先级是有意义的。另外,所有的复合赋值运算符具有相同的优先级并且是自右至左结合的,因此

a = b = c

b = c; a = b;

是等价的。

具有最低优先级的是逗号运算符。这很容易理解,因为逗号通常在需要表达式而不是语句的时候用来替代分号。

赋值是另一种运算符,通常具有混合的优先级。例如,考虑下面这个用于复制文件的循环:

while(c = getc(in) != EOF)
putc(c, out);

这个while循环中的表达式看起来像是c被赋以getc(in)的值,接下来判断是否等于EOF以结束循环。不幸的是,赋值的优先级比任何比较操作都低,因此c的值将会是getc(in)EOF比较的结果,并且会被抛弃。因此,“复制”得到的文件将是一个由值为1的字节流组成的文件。

上面这个例子正确的写法并不难:

while((c = getc(in)) != EOF)
putc(c, out);

然而,这种错误在很多复杂的表达式中却很难被发现。例如,随UNIX系统一同发布的lint程序通常带有下面的错误行:

if (((t = BTYPE(pt1->aty) == STRTY) || t == UNIONTY) {

这条语句希望给t赋一个值,然后看t是否与STRTYUNIONTY相等。而实际的效果却大不相同[3]

C中的逻辑运算符的优先级具有历史原因。B——C的前辈——具有和C中的&|运算符对应的逻辑运算符。尽管它们的定义是按位的 ,但编译器在条件判断上下文中将它们视为和&&||一样。当在C中将它们分开后,优先级的改变是很危险的[4]

2.3 看看这些分号!

C中的一个多余的分号通常会带来一点点不同:或者是一个空语句,无任何效果;或者编译器可能提出一个诊断消息,可以方便除去掉它。一个重要的区别是在必须跟有一个语句的ifwhile语句中。考虑下面的例子:

if(x[i] > big);
big = x[i];

这不会发生编译错误,但这段程序的意义与:

if(x[i] > big)
big = x[i];

就大不相同了。第一个程序段等价于:

if(x[i] > big) { }
big = x[i];

也就是等价于:

big = x[i];

(除非xibig是带有副作用的宏)。

另一个因分号引起巨大不同的地方是函数定义前面的结构声明的末尾[译注:这句话不太好听,看例子就明白了]。考虑下面的程序片段:

struct foo {
int x;
}

f() {

}

在紧挨着f的第一个}后面丢失了一个分号。它的效果是声明了一个函数f,返回值类型是struct foo,这个结构成了函数声明的一部分。如果这里出现了分号,则f将被定义为具有默认的整型返回值[5]

2.4 switch语句

通常C中的switch语句中的case段可以进入下一个。例如,考虑下面的C和Pascal程序片断:

switch(color) {
case 1: printf (“red”);
break;
case 2: printf (“yellow”);
break;
case 3: printf (“blue”);
break;
}

case color of
1: write (‘red’);
2: write (‘yellow’);
3: write (‘blue’);
end

这两个程序片断都作相同的事情:根据变量color的值是1、2还是3打印redyellowblue(没有新行符)。这两个程序片断非常相似,只有一点不同:Pascal程序中没有C中相应的break语句。C中的case标签是真正的标签:控制流程可以无限制地进入到一个case标签中。

看看另一种形式,假设C程序段看起来更像Pascal:

switch(color) {
case 1: printf (“red”);
case 2: printf (“yellow”);
case 3: printf (“blue”);
}

并且假设color的值是2。则该程序将打印yellowblue,因为控制自然地转入到下一个printf()的调用。

这既是C语言switch语句的优点又是它的弱点。说它是弱点,是因为很容易忘记一个break语句,从而导致程序出现隐晦的异常行为。说它是优点,是因为通过故意去掉break语句,可以很容易实现其他方法难以实现的控制结构。尤其是在一个大型的switch语句中,我们经常发现对一个case的处理可以简化其他一些特殊的处理。

例如,设想有一个程序是一台假想的机器的翻译器。这样的一个程序可能包含一个switch语句来处理各种操作码。在这样一台机器上,通常减法在对其第二个运算数进行变号后就变成和加法一样了。因此,最好可以写出这样的语句:

case SUBTRACT:
opnd2 = -opnd2;
/* no break; */
case ADD:

另外一个例子,考虑编译器通过跳过空白字符来查找一个记号。这里,我们将空格、制表符和新行符视为是相同的,除了新行符还要引起行计数器的增长外:

case ‘\n’:
linecount++;
/* no break */
case ‘\t’:
case ‘ ‘:

2.5 函数调用

和其他程序设计语言不同,C要求一个函数调用必须有一个参数列表,但可以没有参数。因此,如果f是一个函数,

f();

就是对该函数进行调用的语句,而

f;

什么也不做。它会作为函数地址被求值,但不会调用它[6]

2.6 悬挂else问题

在讨论任何语法缺陷时我们都不会忘记提到这个问题。尽管这一问题不是C语言所独有的,但它仍然伤害着那些有着多年经验的C程序员。

考虑下面的程序片断:

if(x == 0)
if(y == 0) error();
else {
z = x + y;
f(&z);
}

写这段程序的程序员的目的明显是将情况分为两种:x = 0x != 0。在第一种情况中,程序段什么都不做,除非y = 0时调用error()。第二种情况中,程序设置z = x + y并以z的地址作为参数调用f()

然而, 这段程序的实际效果却大为不同。其原因是一个else总是与其最近的if相关联。如果我们希望这段程序能够按照实际的情况运行,应该这样写:

if(x == 0) {
if(y == 0)
error();
else {
z = x + y;
f(&z);
}
}

换句话说,当x != 0发生时什么也不做。如果要达到第一个例子的效果,应该写:

if(x == 0) {
if(y ==0)
error();
}
else {
z = z + y;
f(&z);
}

3 链接

一个C程序可能有很多部分组成,它们被分别编译,并由一个通常称为链接器、链接编辑器或加载器的程序绑定到一起。由于编译器一次通常只能看到一个文件,因此它无法检测到需要程序的多个源文件的内容才能发现的错误。

在这一节中,我们将看到一些这种类型的错误。有一些C实现,但不是所有的,带有一个称为lint的程序来捕获这些错误。如果具有一个这样的程序,那么无论怎样地强调它的重要性都不过分。

3.1 你必须自己检查外部类型

假设你有一个C程序,被划分为两个文件。其中一个包含如下声明:

int n;

而令一个包含如下声明:

long n;


不是一个有效的C程序,因为一些外部名称在两个文件中被声明为不同的类型。然而,很多实现检测不到这个错误,因为编译器在编译其中一个文件时并不知道另一
个文件的内容。因此,检查类型的工作只能由链接器(或一些工具程序如lint)来完成;如果操作系统的链接器不能识别数据类型,C编译器也没法过多地强制
它。

那么,这个程序运行时实际会发生什么?这有很多可能性:

  1. 实现足够聪明,能够检测到类型冲突。则我们会得到一个诊断消息,说明n在两个文件中具有不同的类型。
  2. 你所使用的实现将intlong视为相同的类型。典型的情况是机器可以自然地进行32位运算。在这种情况下你的程序或许能够工作,好象你两次都将变量声明为long(或int)。但这种程序的工作纯属偶然。
  3. n的两个实例需要不同的存储,它们以某种方式共享存储区,即对其中一个的赋值对另一个也有效。这可能发生,例如,编译器可以将int安排在long的低位。不论这是基于系统的还是基于机器的,这种程序的运行同样是偶然。
  4. n的两个实例以另一种方式共享存储区,即对其中一个赋值的效果是对另一个赋以不同的值。在这种情况下,程序可能失败。

这种情况发生的里一个例子出奇地频繁。程序的某一个文件包含下面的声明:

char filename[] = “etc/passwd”;

而另一个文件包含这样的声明:

char *filename;

尽管在某些环境中数组和指针的行为非常相似,但它们是不同的。在第一个声明中,filename是一个字符数组的名字。尽管使用数组的名字可以产生数组第一个元素的指针,但这个指针只有在需要的时候才产生并且不会持续。在第二个声明中,filename是一个指针的名字。这个指针可以指向程序员让它指向的任何地方。如果程序员没有给它赋一个值,它将具有一个默认的0值(null[译注:实际上,在C中一个为初始化的指针通常具有一个随机的值,这是很危险的!]

这两个声明以不同的方式使用存储区,他们不可能共存。

避免这种类型冲突的一个方法是使用像lint这样的工具(如果可以的话)。为了在一个程序的不同编译单元之间检查类型冲突,一些程序需要一次看到其所有部分。典型的编译器无法完成,但lint可以。

避免该问题的另一种方法是将外部声明放到包含文件中。这时,一个外部对象的类型仅出现一次[7]

4 语义缺陷

一个句子可以是精确拼写的并且没有语法错误,但仍然没有意义。在这一节中,我们将会看到一些程序的写法会使得它们看起来是一个意思,但实际上是另一种完全不同的意思。

我们还要讨论一些表面上看起来合理但实际上会产生未定义结果的环境。我们这里讨论的东西并不保证能够在所有的C实现中工作。我们暂且忘记这些能够在一些实现中工作但可能不能在另一些实现中工作的东西,直到第7节讨论可以执行问题为止。

4.1 表达式求值顺序

一些C运算符以一种已知的、特定的顺序对其操作数进行求值。但另一些不能。例如,考虑下面的表达式:

a < b && c < d

C语言定义规定a < b首先被求值。如果a确实小于bc < d必须紧接着被求值以计算整个表达式的值。但如果a大于或等于b,则c < d根本不会被求值。

要对a < b求值,编译器对ab的求值就会有一个先后。但在一些机器上,它们也许是并行进行的。

C中只有四个运算符&&||?:,指定了求值顺序。&&||最先对左边的操作数进行求值,而右边的操作数只有在需要的时候才进行求值。而?:运算符中的三个操作数:abc,最先对a进行求值,之后仅对bc中的一个进行求值,这取决于a的值。,运算符首先对左边的操作数进行求值,然后抛弃它的值,对右边的操作数进行求值[8]

C中所有其它的运算符对操作数的求值顺序都是未定义的。事实上,赋值运算符不对求值顺序做出任何保证。

出于这个原因,下面这种将数组x中的前n个元素复制到数组y中的方法是不可行的:

i = 0;
while(i < n)
y[i] = x[i++];

其中的问题是y[i]的地址并不保证在i增长之前被求值。在某些实现中,这是可能的;但在另一些实现中却不可能。另一种情况出于同样的原因会失败:

i = 0;
while(i < n)
y[i++] = x[i];

而下面的代码是可以工作的:

i = 0;
while(i < n) {
y[i] = x[i];
i++;
}

当然,这可以简写为:

for(i = 0; i < n; i++)
y[i] = x[i];

4.2 &&||!运算符

C中有两种逻辑运算符,在某些情况下是可以交换的:按位运算符&|~,以及逻辑运算符&&||!。一个程序员如果用某一类运算符替换相应的另一类运算符会得到某些奇怪的效果:程序可能会正确地工作,但这纯属偶然。

&&||!运算符将它们的参数视为仅有“真”或“假”,通常约定0代表“假”而其它的任意值都代表“真”。这些运算符返回1表示“真”而返回0表示“假”,而且&&||运算符当可以通过左边的操作数确定其返回值时,就不会对右边的操作数进行求值。

因此!10是零,因为10非零;10 && 12是1,因为10和12都非零;10 || 12也是1,因为10非零。另外,最后一个表达式中的12不会被求值,10 || f()中的f()也不会被求值。

考虑下面这段用于在一个表中查找一个特定元素的程序:

i = 0;
while(i < tabsize && tab[i] != x)
i++;

这段循环背后的意思是如果i等于tabsize时循环结束,元素未被找到。否则,i包含了元素的索引。

假设这个例子中的&&不小心被替换为了&,这个循环可能仍然能够工作,但只有两种幸运的情况可以使它停下来。

首先,这两个操作都是当条件为假时返回0,当条件为真时返回1。只要xy都是1或0,x & yx && y都具有相同的值。然而,如果当使用了出了1之外的非零值表示“真”时互换了这两个运算符,这个循环将不会工作。

其次,由于数组元素不会改变,因此越过数组最后一个元素进一个位置时是无害的,循环会幸运地停下来。失误的程序会越过数组的结尾,因为&不像&&,总是会对所有的操作数进行求值。因此循环的最后一次获取tab[i]i的值已经等于tabsize了。如果tabsizetab中元素的数量, 则会取到tab中不存在的一个值。

4.3 下标从零开始

在很多语言中,具有n个元素的数组其元素的号码和它的下标是从1到n严格对应的。但在C中不是这样。

一个具有n个元素的C数组中没有下标为n的元素,其中的元素的下标是从0到n – 1。因此从其它语言转到C语言的程序员应该特别小心地使用数组:

int i, a[10];
for(i = 1; i <= 10; i++)
a[i] = 0;

这个例子的目的是要将a中的每个元素都设置为0,但没有期望的效果。因为for语句中的比较i < 10被替换成了i <= 10a中的一个编号为10的并不存在的元素被设置为了0,这样内存中a后面的一个字被破坏了。如果编译该程序的编译器按照降序地址为用户变量分配内存,则a后面就是i。将i设置为零会导致该循环陷入一个无限循环。

4.4 C并不总是转换实参

下面的程序段由于两个原因会失败:

double s;
s = sqrt(2);
printf(“%g\n”, s);

第一个原因是sqrt()需要一个double值作为它的参数,但没有得到。第二个原因是它返回一个double值但没有这样声名。改正的方法只有一个:

double s, sqrt();
s = sqrt(2.0);
printf(“%g\n”, s);

C中有两个简单的规则控制着函数参数的转换:(1)比int短的整型被转换为int;(2)比double短的浮点类型被转换为double。所有的其它值不被转换。确保函数参数类型的正确行使程序员的责任。

因此,一个程序员如果想使用如sqrt()这样接受一个double类型参数的函数,就必须仅传递给它floatdouble类型的参数。常数2是一个int,因此其类型是错误的。

当一个函数的值被用在表达式中时,其值会被自动地转换为适当的类型。然而,为了完成这个自动转换,编译器必须知道该函数实际返回的类型。没有更进一步声名的函数被假设返回int,因此声名这样的函数并不是必须的。然而,sqrt()返回double,因此在成功使用它之前必须要声名。

实际上,C实现通常允许一个文件包含include语句来包含如sqrt()这些库函数的声名,但是对那些自己写函数的程序员来说,书写声名也是必要的——或者说,对那些书写非凡的C程序的人来说是有必要的。

这里有一个更加壮观的例子:

main() {
int i;
char c;
for(i = 0; i < 5; i++) {
scanf(“%d”, &c);
printf(“%d”, i);
}
printf(“\n”);
}

表面上看,这个程序从标准输入中读取五个整数并向标准输出写入0 1 2 3 4。实际上,它并不总是这么做。譬如在一些编译器中,它的输出为0 0 0 0 0 1 2 3 4。

为什么?因为c的声名是char而不是int。当你令scanf()去读取一个整数时,它需要一个指向一个整数的指针。但这里它得到的是一个字符的指针。但scanf()并不知道它没有得到它所需要的:它将输入看作是一个指向整数的指针并将一个整数存贮到那里。由于整数占用比字符更多的内存,这样做会影响到c附近的内存。

c附近确切是什么是编译器的事;在这种情况下这有可能是i的低位。因此,每当向c中读入一个值,i就被置零。当程序最后到达文件结尾时,scanf()不再尝试向c中放入新值,i才可以正常地增长,直到循环结束。

4.5 指针不是数组

C程序通常将一个字符串转换为一个以空字符结尾的字符数组。假设我们有两个这样的字符串st,并且我们想要将它们连接为一个单独的字符串r。我们通常使用库函数strcpy()strcat()来完成。下面这种明显的方法并不会工作:

char *r;
strcpy(r, s);
strcat(r, t);

这是因为r没有被 初始化为指向任何地方。尽管r可能潜在地表示某一块内存,但这并不存在,直到你分配它。

让我们再试试,为r分配一些内存:

char r[100];
strcpy(r, s);
strcat(r, t);

这只有在st所指向的字符串不很大的时候才能够工作。不幸的是,C要求我们为数组指定的大小是一个常数,因此无法确定r是否足够大。然而,很多C实现带有一个叫做malloc()的库函数,它接受一个数字并分配这么多的内存。通常还有一个函数成为strlen(),可以告诉我们一个字符串中有多少个字符:因此,我们可以写:

char *r, *malloc();
r = malloc(strlen(s) + strlen(t));
strcpy(r, s);
strcat(r, t);

然而这个例子会因为两个原因而失败。首先,malloc()可能会耗尽内存,而这个事件仅通过静静地返回一个空指针来表示。

其次,更重要的是,malloc()并没有分配足够的内存。一个字符串是以一个空字符结束的。而strlen()函数返回其字符串参数 中所包含字符的数量,但不包括结尾的空字符。因此,如果strlen(s)n,则s需要n + 1个字符来盛放它。因此我们需要为r分配额外的一个字符。再加上检查malloc()是否成功,我们得到:

char *r, *malloc();
r = malloc(strlen(s) + strlen(t) + 1);
if(!r) {
complain();
exit(1);
}
strcpy(r, s);
strcat(r, t);

4.6 避免提喻法

提喻法(Synecdoche, sin-ECK-duh-key)是一种文学手法,有点类似于明喻或暗喻,在牛津英文词典中解释如下:“a more
comprehensive term is used for a less comprehensive or vice versa; as
whole for part or part for whole, genus for species or species for
genus, etc.(将全面的单位用作不全面的单位,或反之;如整体对局部或局部对整体、一般对特殊或特殊对一般,等等。)”

这可以精确地描述C中通常将指针误以为是其指向的数据的错误。正将常会在字符串中发生。例如:

char *p, *q;
p = “xyz”;

尽管认为p的值是xyz有时是有用的,但这并不是真的,理解这一点非常重要。p的值是指向一个有四个字符的数组中第0个元素的指针,这四个字符是‘x’‘y’‘z’‘\0′。因此,如果我们现在执行:

q = p;

pq会指向同一块内存。内存中的字符没有因为赋值而被复制。这种情况看起来是这样的:

要记住的是,复制一个指针并不能复制它所指向的东西。

因此,如果之后我们执行:

q[1] = ‘Y’;

q所指向的内存包含字符串xYzp也是,因为pq指向相同的内存。

4.7 空指针不是空字符串

将一个整数转换为一个指针的结果是实现相关的(implementation-dependent),除了一个例外。这个例外是常数0,它可以保证被转换为一个与其它任何有效指针都不相等的指针。这个值通常类似这样定义:

#define NULL 0

但其效果是相同的。要记住的一个重要的事情是,当用0作为指针时它决不能被解除引用。换句话说,当你将0赋给一个指针变量后,你就不能访问它所指向的内存。不能这样写:

if(p == (char *)0) …

也不能这样写:

if(strcmp(p, (char *)0) == 0) …

因为strcmp()总是通过其参数来查看内存地址的。

如果p是一个空指针,这样写也是无效的:

printf(p);

printf(“%s”, p);

4.8 整数溢出

C语言关于整数操作的上溢或下溢定义得非常明确。

只要有一次操作数是无符号的,结果就是无符号的,并且以2n为模,其中n为字长。如果两个操作数都是带符号的,则结果是未定义的。

例如,假设ab是两个非负整型变量,你希望测试a + b是否溢出。一个明显的办法是这样的:

if(a + b < 0)
complain();

通常,这是不会工作的。

一旦a + b发生了溢出,对于结果的任何赌注都是没有意义的。例如,在某些机器上,一个加法运算会将一个内部寄存器设置为四种状态:正、负、零或溢出。 在这样的机器上,编译器有权将上面的例子实现为首先将ab加在一起,然后检查内部寄存器状态是否为负。如果该运算溢出,内部寄存器将处于溢出状态,这个测试会失败。

使这个特殊的测试能够成功的一个正确的方法是依赖于无符号算术的良好定义,既要在有符号和无符号之间进行转换:

if((int)((unsigned)a + (unsigned)b) < 0)
complain();

4.9 移位运算符

两个原因会令使用移位运算符的人感到烦恼:

  1. 在右移运算中,空出的位是用0填充还是用符号位填充?
  2. 移位的数量允许使用哪些数?

第一个问题的答案很简单,但有时是实现相关的。如果要进行移位的操作数是无符号的,会移入0。如果操作数是带符号的,则实现有权决定是移入0还是移入符号位。如果在一个右移操作中你很关心空位,那么用unsigned来声明变量。这样你就有权假设空位被设置为0。

第二个问题的答案同样简单:如果待移位的数长度为n,则移位的数量必须大于等于0并且严格地小于n。因此,在一次单独的操作中不可能将所有的位从变量中移出。

例如,如果一个int是32位,且n是一个int,写n << 31n << 0是合法的,但n << 32n << -1是不合法的。

注意,即使实现将符号为移入空位,对一个带符号整数的右移运算和除以2的某次幂也不是等价的。为了证明这一点,考虑(-1) >> 1的值,这是不可能为0的。[译注:(-1) / 2的结果是0。]

5 库函数

每个有用的C程序都会用到库函数,因为没有办法把输入和输出内建到语言中去。在这一节中,我们将会看到一些广泛使用的库函数在某种情况下会出现的一些非预期行为。

5.1 getc()返回整数

考虑下面的程序:

#include <stdio.h>

main() {
char c;

while((c = getchar()) != EOF)
putchar(c);
}

这段程序看起来好像要讲标准输入复制到标准输出。实际上,它并不完全会做这些。

原因是c被声明为字符而不是整数。这意味着它将不能接收可能出现的所有字符包括EOF

因此这里有两种可能性。有时一些合法的输入字符会导致c携带和EOF相同的值,有时又会使c无法存放EOF值。在前一种情况下,程序会在文件的中间停止复制。在后一种情况下,程序会陷入一个无限循环。

实际上,还存在着第三种可能:程序会偶然地正确工作。C语言参考手册严格地定义了表达式

((c = getchar()) != EOF)

的结果。其6.1节中声明:

当一个较长的整数被转换为一个较短的整数或一个char时,它会被截去左侧;超出的位被简单地丢弃。

7.14节声明:

存在着很多赋值运算符,它们都是从右至左结合的。它们都需要一个左值作为左侧的操作数,而赋值表达式的类型就是其左侧的操作数的类型。其值就是已经付过值的左操作数的值。

这两个条款的组合效果就是必须通过丢弃getchar()的结果的高位,将其截短为字符,之后这个被截短的值再与EOF进行比较。作为这个比较的一部分,c必须被扩展为一个整数,或者采取将左侧的位用0填充,或者适当地采取符号扩展。

然而,一些编译器并没有正确地实现这个表达式。它们确实将getchar()的值的低几位赋给c。但在cEOF的比较中,它们却使用了getchar()的值!这样做的编译器会使这个事例程序看起来能够“正确地”工作。

5.2 缓冲输出和内存分配

当一个程序产生输出时,能够立即看到它有多重要?这取决于程序。

例如,终端上显示输出并要求人们坐在终端前面回答一个问题,人们能够看到输出以知道该输入什么就显得至关重要了。另一方面,如果输出到一个文件中,并最终被发送到一个行式打印机,只有所有的输出最终能够到达那里是重要的。

立即安排输出的显示通常比将其暂时保存在一大块一起输出要昂贵得多。因此,C实现通常允许程序员控制产生多少输出后在实际地写出它们。

这个控制通常约定为一个称为setbuf()的库函数。如果buf是一个具有适当大小的字符数组,则

setbuf(stdout, buf);

将告诉I/O库写入到stdout中的输出要以buf作为一个输出缓冲,并且等到buf满了或程序员直接调用fflush()再实际写出。缓冲区的合适的大小在<stdio.h>中定义为BUFSIZ

因此,下面的程序解释了通过使用setbuf()来讲标准输入复制到标准输出:

#include <stdio.h>

main() {
int c;

char buf[BUFSIZ];
setbuf(stdout, buf);

while((c = getchar()) != EOF)
putchar(c);
}

不幸的是,这个程序是错误的,因为一个细微的原因。

要知道毛病出在哪,我们需要知道缓冲区最后一次刷新是在什么时候。答案;主程序完成之后,作为库在将控制交回到操作系统之前所执行的清理的一部分。在这一时刻,缓冲区已经被释放了!

有两种方法可以避免这一问题。

首先,是用静态缓冲区,或者将其显式地声明为静态:

static char buf[BUFSIZ];

或者将整个声明移到主函数之外。

另一种可能的方法是动态地分配缓冲区并且从不释放它:

char *malloc();
setbuf(stdout, malloc(BUFSIZ));

注意在后一种情况中,不必检查malloc()的返回值,因为如果它失败了,会返回一个空指针。而setbuf()可以接受一个空指针作为其第二个参数,这将使得stdout变成非缓冲的。这会运行得很慢,但它是可以运行的。

6 预处理器

运行的程序并不是我们所写的程序:因为C预处理器首先对其进行了转换。出于两个主要原因(和很多次要原因),预处理器为我们提供了一些简化的途径。

首先,我们希望可以通过改变一个数字并重新编译程序来改变一个特殊量(如表的大小)的所有实例[9]

其次,我们可能希望定义一些东西,它们看起来象函数但没有函数调用所需的运行开销。例如,putchar()getchar()通常实现为宏以避免对每一个字符的输入输出都要进行函数调用。

6.1 宏不是函数

由于宏可以象函数那样出现,有些程序员有时就会将它们视为等价的。因此,看下面的定义:

#define max(a, b) ((a) > (b) ? (a) : (b))

注意宏体中所有的括号。它们是为了防止出现ab是带有比>优先级低的表达式的情况。

一个重要的问题是,像max()这样定义的宏每个操作数都会出现两次并且会被求值两次。因此,在这个例子中,如果ab大,则a就会被求值两次:一次是在比较的时候,而另一次是在计算max()值的时候。

这不仅是低效的,还会发生错误:

biggest = x[0];
i = 1;
while(i < n)
biggest = max(biggest, x[i++]);

max()是一个真正的函数时,这会正常地工作,但当max()是一个宏的时候会失败。譬如,假设x[0]是2、x[1]是3、x[2]是1。我们来看看在第一次循环时会发生什么。赋值语句会被扩展为:

biggest = ((biggest) > (x[i++]) ? (biggest) : (x[i++]));

首先,biggestx[i++]进行比较。由于i是1而x[1]是3,这个关系是“假”。其副作用是,i增长到2。

由于关系是“假”,x[i++]的值要赋给biggest。然而,这时的i变成2了,因此赋给biggest的值是x[2]的值,即1。

避免这些问题的方法是保证max()宏的参数没有副作用:

biggest = x[0];
for(i = 1; i < n; i++)
biggest = max(biggest, x[i]);

还有一个危险的例子是混合宏及其副作用。这是来自UNIX第八版的<stdio.h>putc()宏的定义:

#define putc(x, p) (–(p)->_cnt >= 0 ? (*(p)->_ptr++ = (x)) : _flsbuf(x, p))

putc()的第一个参数是一个要写入到文件中的字符,第二个参数是一个指向一个表示文件的内部数据结构的指针。注意第一个参数完全可以使用如*z++之类的东西,尽管它在宏中两次出现,但只会被求值一次。而第二个参数会被求值两次(在宏体中,x出现了两次,但由于 它的两次出现分别在一个:的两边,因此在putc()的一个实例中它们之中有且仅有一个被求值)。由于putc()中的文件参数可能带有副作用,这偶尔会出现问题。不过,用户手册文档中提到:“由于putc()被实现为宏,其对待stream可能会具有副作用。特别是putc(c, *f++)不能正确地工作。”但是putc(*c++, f)在这个实现中是可以工作的。

有些C实现很不小心。例如,没有人能正确处理putc(*c++, f)。另一个例子,考虑很多C库中出现的toupper()函数。它将一个小写字母转换为相应的大写字母,而其它字符不变。如果我们假设所有的小写字母和所有的大写字母都是相邻的(大小写之间可能有所差距),我们可以得到这样的函数:

toupper(c) {
if(c >= ‘a’ && c <= ‘z’)
c += ‘A’ – ‘a’;
return c;
}

在很多C实现中,为了减少比实际计算还要多的调用开销,通常将其实现为宏:

#define toupper(c) ((c) >= ‘a’ && (c) <= ‘z’ ? (c) + (‘A’ – ‘a’) : (c))

很多时候这确实比函数要快。然而,当你试着写toupper(*p++)时,会出现奇怪的结果。

另一个需要注意的地方是使用宏可能会产生巨大的表达式。例如,继续考虑max()的定义:

#define max(a, b) ((a) > (b) ? (a) : (b))

假设我们这个定义来查找abcd中的最大值。如果我们直接写:

max(a, max(b, max(c, d)))

它将被扩展为:

((a) > (((b) > (((c) > (d) ? (c) : (d))) ? (b) : (((c) > (d) ? (c) : (d))))) ?
(a) : (((b) > (((c) > (d) ? (c) : (d))) ? (b) : (((c) > (d) ? (c) : (d))))))

这出奇的庞大。我们可以通过平衡操作数来使它短一些:

max(max(a, b), max(c, d))

这会得到:

((((a) > (b) ? (a) : (b))) > (((c) > (d) ? (c) : (d))) ?
(((a) > (b) ? (a) : (b))) : (((c) > (d) ? (c) : (d))))

这看起来还是写:

biggest = a;
if(biggest < b) biggest = b;
if(biggest < c) biggest = c;
if(biggest < d) biggest = d;

比较好一些。

6.2 宏不是类型定义

宏的一个通常的用途是保证不同地方的多个事物具有相同的类型:

#define FOOTYPE struct foo
FOOTYPE a;
FOOTYPE b, c;

这允许程序员可以通过只改变程序中的一行就能改变abc的类型,尽管abc可能声明在很远的不同地方。

使用这样的宏定义还有着可移植性的优势——所有的C编译器都支持它。很多C编译器并不支持另一种方法:

typedef struct foo FOOTYPE;

这将FOOTYPE定义为一个与struct foo等价的新类型。

这两种为类型命名的方法可以是等价的,但typedef更灵活一些。例如,考虑下面的例子:

#define T1 struct foo *
typedef struct foo * T2;

这两个定义使得T1T2都等价于一个struct foo的指针。但看看当我们试图在一行中声明多于一个变量的时候会发生什么:

T1 a, b;
T2 c, d;

第一个声明被扩展为:

struct foo * a, b;

这里a被定义为一个结构指针,但b被定义为一个结构(而不是指针)。相反,第二个声明中cd都被定义为指向结构的指针,因为T2的行为好像真正的类型一样。

7 可移植性缺陷

C被很多人实现并运行在很多机器上。这也正是在一个地方写的C程序应该能够很容易地转移到另一个编程环境中去的原因。

然而,由于有很多的实现者,它们并不和其他人交流。此外,不同的系统有不同的需求,因此一台机器上的C实现和另一台上的多少会有些不同。

由于很多早期的C实现都关系到UNIX操作系统,因此这些函数的性质都是专于该系统的。当一些人开始在其他系统中实现C时,他们尝试使库的行为类似于UNIX系统中的行为。

但他们并不总是能够成功。更有甚者,很多人从UNIX系统的不同版本入手,一些库函数的本质不可避免地发生分歧。今天,一个C程序员如果想写出对于不同环境中的用户都有用的程序就必须知道很多这些细微的差别。

7.1 一个名字中都有什么?

一些C编译器将一个标识符中的所有字符视为签名。而另一些在存贮标识符是会忽略一个极限之外的所有字符。C编译器产生的目标程序同将要被加载器进行处理以访问库中的子程序。加载器对于它们能够处理的名字通常应用自己的约束。

一个常见的加载器约束是所有的外部名字必须只能是大写的。面对这样的加载器约束,C实现者会强制要求所有的外部名字都是大写的。这种约束在C语言参考手册中第2.1节由所描述。

一个标识符是一个字符和数字序列,第一个字符必须是一个字母。下划线_算作字母。大写字母和小写字母是不同的。只有前八个字符是签名,但可以使用更多的字符。可以被多种汇编器和加载器使用的外部标识符,有着更多的限制:

这里,参考手册中继续给出了一些例子如有些实现要求外部标识符具有单独的大小写格式、或者少于八个字符、或者二者都有。

正因为所有这些,在一个希望可以移植的程序中小心地选择标识符是很重要的。为两个 子程序选择print_fieldsprint_float这样的名字不是个好办法。

考虑下面这个显著的函数:

char *Malloc(unsigned n) {
char *p, *malloc();
p = malloc(n);
if(p == NULL)
panic(“out of memory”);
return p;
}

这个函数是保证耗尽内存而不会导致没有检测的一个简单的办法。程序员可以通过调用Mallo()来代替malloc()。如果malloc()不幸失败,将调用panic()来显示一个恰当的错误消息并终止程序。

然而,考虑当该函数用于一个忽略大小写区别的系统中时会发生什么。这时,名字mallocMalloc是等价的。换句话说,库函数malloc()被上面的Malloc()函数完全取代了,当调用malloc()时它调用的是它自己。显然,其结果就是第一次尝试分配内存就会陷入一个递归循环并随之发生混乱。但在一些能够区分大小写的实现中这个函数还是可以工作的。

7.2 一个整数有多大?

C为程序员提供三种整数尺寸:普通、短和长,还有字符,其行为像一个很小的整数。C语言定义对各种整数的大小不作任何保证:

  1. 整数的四种尺寸是非递减的。
  2. 普通整数的大小要足够存放任意的数组下标。
  3. 字符的大小应该体现特定硬件的本质。

许多现代机器具有8位字符,不过还有一些具有7位获9位字符。因此字符通常是7、8或9位。

长整数通常至少32位,因此一个长整数可以用于表示文件的大小。

普通整数通常至少16位,因为太小的整数会更多地限制一个数组的最大大小。

短整数总是恰好16位。

在实践中这些都意味着什么?最重要的一点就是别指望能够使用任何一个特定的精度。非正式情况下你可以假设一个短整数或一个普通整数是16位的,而一个长整
数是32位的,但并不保证总是会有这些大小。你当然可以用普通整数来压缩表大小和下标,但当一个变量必须存放一个一千万的数字的时候呢?

一种更可移植的做法是定义一个“新的”类型:

typedef long tenmil;

现在你就可以使用这个类型来声明一个变量并知道它的宽度了,最坏的情况下,你也只要改变这个单独的类型定义就可以使所有这些变量具有正确的类型。

7.3 字符是带符号的还是无符号的?

很多现代计算机支持8位字符,因此很多现代C编译器将字符实现为8位整数。然而,并不是所有的编译器都按照同将的方式解释这些8位数。

这些问题在将一个char制转换为一个更大的整数时变得尤为重要。对于相反的转换,其结果却是定义良好的:多余的位被简单地丢弃掉。但一个编译器将一个char转换为一个int却需要作出选择:将char视为带符号量还是无符号量?如果是前者,将char扩展为int时要复制符号位;如果是后者,则要将多余的位用0填充。

这个决定的结果对于那些在处理字符时习惯将高位置1的人来说非常重要。这决定着8位的字符范围是从-128到127还是从0到255。这又影响着程序员对哈希表和转换表之类的东西的设计。

如果你关心一个字符值最高位置一时是否被视为一个负数,你应该显式地将它声明为unsigned char。这样就能保证在转换为整数时是基0的,而不像普通char变量那样在一些实现中是带符号的而在另一些实现中是无符号的。

另外,还有一种误解是认为当c是一个字符变量时,可以通过写(unsigned)c来得到与c等价的无符号整数。这是错误的,因为一个char值在进行任何操作(包括转换)之前转换为int。这时c会首先转换为一个带符号整数在转换为一个无符号整数,这会产生奇怪的结果。

正确的方法是写(unsigned char)c

7.4 右移位是带符号的还是无符号的?

这里再一次重复:一个关心右移操作如何进行的程序最好将所有待移位的量声明为无符号的。

7.5 除法如何舍入?

假设我们用ba得到商为q余数为r

q = a / b;
r = a % b;

我们暂时假设b > 0

我们期望abqr之间有什么关联?

  1. 最重要的,我们期望q * b + r == a,因为这是对余数的定义。
  2. 如果a的符号发生改变,我们期望q的符号也发生改变,但绝对值不变。
  3. 我们希望保证r >= 0r < b。例如,如果余数将作为一个哈希表的索引,它必须要保证总是一个有效的索引。

这三点清楚地描述了整数除法和求余操作。不幸的是,它们不能同时为真。

考虑3 / 2,商1余0。这满足第一点。而-3 / 2的值呢?根据第二点,商应该是-1,但如果是这样的话,余数必须也是-1,这违反了第三点。或者,我们可以通过将余数标记为1来满足第三点,但这时根据第一点商应该是-2。这又违反了第二点。

因此C和其他任何实现了整数除法舍入的语言必须放弃上述三个原则中的至少一个。

很多程序设计语言放弃了第三点,要求余数的符号必须和被除数相同。这可以保证第一点和第二点。很多C实现也是这样做的。

然而,C语言的定义只保证了第一点和|r| < |b|以及当a >= 0b > 0r >= 0。 这比第二点或第三点的限制要小, 实际上有些编译器满足第二点或第三点,但不太常见(如一个实现可能总是向着距离0最远的方向进行舍入)。

尽管有些时候不需要灵活性,C语言还是足够可以让我们令除法完成我们所要做的、提供我们所想知道的。例如,假设我们有一个数n表示一个标识符中的字符的一些函数,并且我们想通过除法得到一个哈希表入口h,其中0 <= h <= HASHSIZE。如果我们知道n是非负的,我们可以简单地写:

h = n % HASHSIZE;

然而,如果n有可能是负的,这样写就不好了,因为h可能也是负的。然而,我们知道h > -HASHSIZE,因此我们可以写:

h = n % HASHSIZE;
if(n < 0)
h += HASHSIZE;

同样,将n声明为unsigned也可以。

7.6 一个随机数有多大?

这个尺寸是模糊的,还受库设计的影响。在PDP-11[10]机器上运行的仅有的C实现中,有一个称为rand()的函数可以返回一个(伪)随机非负整数。PDP-11中整数长度包括符号位是16位,因此rand()返回一个0到215-1之间的整数。

当C在VAX-11上实现时,整数的长度变为32位长。那么VAX-11上的rand()函数返回值范围是什么呢?

对于这个系统,加利福尼亚大学的人认为rand()的返回值应该涵盖所有可能的非负整数,因此它们的rand()版本返回一个0到231-1之间的整数。

而AT&T的人则觉得如果rand()函数仍然返回一个0到215之间的值 则可以很容易地将PDP-11中期望rand()能够返回一个小于215的值的程序移植到VAX-11上。

因此,现在还很难写出不依赖实现而调用rand()函数的程序。

7.7 大小写转换

toupper()tolower()函数有着类似的历史。他们最初都被实现为宏:

#define toupper(c) ((c) + ‘A’ – ‘a’)
#define tolower(c) ((c) + ‘A’ – ‘a’)

当给定一个小写字母作为输入时,toupper()将产生相应的大写字母。tolower()反之。这两个宏都依赖于实现的字符集,它们需要所有的大写字母和对应的小写字母之间的差别都是常数的。这个假设对于ASCII和EBCDIC字符集来说都是有效的,可能不是很危险,因为这些不可移植的宏定义可以被封装到一个单独的文件中并包含它们。

这些宏确实有一个缺陷,即:当给定的东西不是一个恰当的字符,它会返回垃圾。因此,下面这个通过使用这些宏来将一个文件转为小写的程序是无法工作的:

int c;
while((c = getchar()) != EOF)
putchar(tolower(c));

我们必须写:

int c;
while((c = getchar()) != EOF)
putchar(isupper(c) ? tolower(c) : c);

就这一点,AT&T中的UNIX开发组织提醒我们,toupper()tolower()都是事先经过一些适当的参数进行测试的。考虑这样重写这些宏:

#define toupper(c) ((c) >= ‘a’ && (c) <= ‘z’ ? (c) + ‘A’ – ‘a’ : (c))
#define tolower(c) ((c) >= ‘A’ && (c) <= ‘Z’ ? (c) + ‘a’ – ‘A’ : (c))

但要知道,这里c的三次出现都要被求值,这会破坏如toupper(*p++)这样的表达式。因此,可以考虑将toupper()tolower()重写为函数。toupper()看起来可能像这样:

int toupper(int c) {
if(c >= ‘a’ && c <= ‘z’)
return c + ‘A’ – ‘a’;
return c;
}

tolower()类似。

这个改变带来更多的问题,每次使用这些函数的时候都会引入函数调用开销。我们的英雄认为一些人可能不愿意支付这些开销,因此他们将这个宏重命名为:

#define _toupper(c) ((c) + ‘A’ – ‘a’)
#define _tolower(c) ((c) + ‘a’ – ‘A’)

这就允许用户选择方便或速度。

这里面其实只有一个问题:伯克利的人们和其他的C实现者并没有跟着这么做。 这意味着一个在AT&T系统上编写的使用了toupper()tolower()的程序,如果没有为其传递正确大小写字母参数,在其他C实现中可能不会正常工作。

如果不知道这些历史,可能很难对这类错误进行跟踪。

7.8 先释放,再重新分配

很多C实现为用户提供了三个内存分配函数:malloc()realloc()free()。调用malloc(n)返回一个指向有n个字符的新分配的内存的指针,这个指针可以由程序员使用。给free()传递一个指向由malloc()分配的内存的指针可以使这块内存得以重用。通过一个指向已分配区域的指针和一个新的大小调用realloc()可以将这块内存扩大或缩小到新尺寸,这个过程中可能要复制内存。

也许有人会想,真相真是有点微妙啊。下面是System V接口定义中出现的对realloc()的描述:

realloc改变一个由ptr指向的size个字节的块,并返回该块(可能被移动)的指针。 在新旧尺寸中比较小的一个尺寸之下的内容不会被改变。

而UNIX系统第七版的参考手册中包含了这一段的副本。此外,还包含了描述realloc()的另外一段:

如果在最后一次调用mallocrealloccalloc后释放了ptr所指向的块,realloc依旧可以工作;因此,freemallocrealloc的顺序可以利用malloc压缩存贮的查找策略。

因此,下面的代码片段在UNIX第七版中是合法的:

free (p);
p = realloc(p, newsize);

这一特性保留在从UNIX第七版衍生出来的系统中:可以先释放一块存储区域,然后再重新分配它。这意味着,在这些系统中释放的内存中的内容在下一次内存分配之前可以保证不变。因此,在这些系统中,我们可以用下面这种奇特的思想来释放一个链表中的所有元素:

for(p = head; p != NULL; p = p->next)
free((char *)p);

而不用担心调用free()会导致p->next不可用。

不用说,这种技术是不推荐的,因为不是所有C实现都能在内存被释放后将它的内容保留足够长的时间。然而,第七版的手册遗留了一个未声明的问题:realloc()的原始实现实际上是必须要先释放再重新分配的。出于这个原因,一些C程序都是先释放内存再重新分配的,而当这些程序移植到其他实现中时就会出现问题。

7.9 可移植性问题的一个实例

让我们来看一个已经被很多人在很多时候解决了的问题。下面的程序带有两个参数:一个长整数和一个函数(的指针)。它将整数转换位十进制数,并用代表其中每一个数字的字符来调用给定的函数。

void printnum(long n, void (*p)()) {
if(n < 0) {
(*p)(‘-’);
n = -n;
}
if(n >= 10)
printnum(n / 10, p);
(*p)(n % 10 + ‘0′);
}

这个程序非常简单。首先检查n是否为负数;如果是,则打印一个符号并将n变为正数。接下来,测试是否n >= 10。如果是,则它的十进制表示中包含两个或更多个数字,因此我们递归地调用printnum()来打印除最后一个数字外的所有数字。最后,我们打印最后一个数字。

这个程序——由于它的简单——具有很多可移植性问题。首先是将n的低位数字转换成字符形式的方法。用n % 10来获取低位数字的值是好的,但为它加上‘0′来获得相应的字符表示就不好了。这个加法假设机器中顺序的数字所对应的字符数顺序的,没有间隔,因此‘0′ + 5‘5′的值是相同的,等等。尽管这个假设对于ASCII和EBCDIC字符集是成立的,但对于其他一些机器可能不成立。避免这个问题的方法是使用一个表:

void printnum(long n, void (*p)()) {
if(n < 0) {
(*p)(‘-’);
n = -n;
}
if(n >= 10)
printnum(n / 10, p);
(*p)(“0123456789″[n % 10]);
}

另一个问题发生在当n < 0时。这时程序会打印一个负号并将n设置为-n。这个赋值会发生溢出,因为在使用2的补码的机器上通常能够表示的负数比正数要多。例如,一个(长)整数有k位和一个附加位表示符号,则-2k可以表示而2k却不能。

解决这一问题有很多方法。最直观的一种是将n赋给一个unsigned long值。然而,一些C便一起可能没有实现unsigned long,因此我们来看看没有它怎么办。

在第一个实现和第二个实现的机器上,改变一个正整数的符号保证不会发生溢出。问题仅出在改变一个负数的符号时。因此,我们可以通过避免将n变为正数来避免这个问题。

当然,一旦我们打印了负数的符号,我们就能够将负数和正数视为是一样的。下面的方法就强制在打印符号之后n为负数,并且用负数值完成我们所有的算法。如果我们这么做,我们就必须保证程序中打印符号的部分只执行一次;一个简单的方法是将这个程序划分为两个函数:

void printnum(long n, void (*p)()) {
if(n < 0) {
(*p)(‘-’);
printneg(n, p);
}
else
printneg(-n, p);
}

void printneg(long n, void (*p)()) {
if(n <= -10)
printneg(n / 10, p);
(*p)(“0123456789″[-(n % 10)]);
}

printnum()现在只检查要打印的数是否为负数;如果是的话则打印一个符号。否则,它以n的负绝对值来调用printneg()。我们同时改变了printneg()的函数体来适应n永远是负数或零这一事实。

我们得到什么?我们使用n / 10n % 10来获取n的前导数字和结尾数字(经过适当的符号变换)。调用整数除法的行为在其中一个操作数为负的时候是实现相关的。因此,n % 10有可能是正的!这时,-(n % 10)是正数,将会超出我们的数字字符数组的末尾。

为了解决这一问题,我们建立两个临时变量来存放商和余数。作完除法后,我们检查余数是否在正确的范围内,如果不是的话则调整这两个变量。printnum()没有改变,因此我们只列出printneg()

void printneg(long n, void (*p)()) {
long q;
int r;
if(r > 0) {
r -= 10;
q++;
}
if(n <= -10) {
printneg(q, p);
}
(*p)(“0123456789″[-r]);
}

8 这里是空闲空间

还有很多可能让C程序员误入迷途的地方本文没有提到。如果你发现了,请联系作者。在以后的版本中它会被包含进来,并添加一个表示感谢的脚注。

参考

《The C Programming Language》(Kernighan and Ritchie, Prentice-Hall
1978)是最具权威的C著作。它包含了一个优秀的教程,面向那些熟悉其他高级语言程序设计的人,和一个参考手册,简洁地描述了整个语言。尽管自1978
年以来这门语言发生了不少变化,这本书对于很多主题来说仍然是个定论。这本书同时还包含了本文中多次提到的“C语言参考手册”。

《The C Puzzle Book》(Feuer, Prentice-Hall, 1982)是一本少见的磨炼人们文法能力的书。这本书收集了很多谜题(和答案),它们的解决方法能够测试读者对于C语言精妙之处的知识。

《C: A Referenct Manual》(Harbison and Steele, Prentice Hall 1984)是特意为实现者编写的一本参考资料。其他人也会发现它是特别有用的——因为他能从中参考细节。


脚注

1. 这本书是基于图书《C Traps and Pitfalls》(Addison-Wesley, 1989, ISBN 0-201-17928-8)的一个扩充,有兴趣的读者可以读一读它。
2. 因为!=的结果不是1就是0。
3. 感谢Guy Harris为我指出这个问题。
4. Dennis Ritchie和Steve Johnson同时向我指出了这个问题。
5. 感谢一位不知名的志愿者提出这个问题。
6. 感谢Richard Stevens指出了这个问题。
7. 一些C编译器要求每个外部对象仅有一个定义,但可以有多个声明。使用这样的编译器时,我们何以很容易地将一个声明放到一个包含文件中,并将其定义放到其它地方。这意味着每个外部对象的类型将出现两次,但这比出现多于两次要好。
8. 分离函数参数用的逗号不是逗号运算符。例如在f(x, y)中,x和y的获取顺序是未定义的,但在g((x, y))中不是这样的。其中g只有一个参数。它的值是通过对x进行求值、抛弃这个值、再对y进行求值来确定的。
9. 预处理器还可以很容易地组织这样的显式常量以能够方便地找到它们。
10. PDP-11和VAX-11是数组设备集团(DEC)的商标。

2005年01月27日

一道蕴涵哲学的计算题!


> 如果令 A B C D E F G H I J K L M N O P Q R S T U V W X Y Z 分别等于百分之


> 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26


>


> 那么


> Hard work (努力工作)


>    H+A+R+D+W+O+R+K 8+1+18+4+23+15+18+11 = 98%


>


> Knowledge(知识)


>    K+N+O+W+L+E+D+G+E 11+14+15+23+12+5+4+7+5 = 96%


>


> Love(爱情)


>   L+O+V+E12+15+22+5 = 54%


>


> Luck(好运)


>   L+U+C+K12+21+3+11 = 47%


>


> (这些我们通常认为重要的东西往往并不是最重要的)


> 什么能使得生活变得圆满?


>


> 是Money(金钱)吗? …


>   不!


> M+O+N+E+Y = 13+15+14+5+25 = 72%


>


> 是Leadership(领导能力)吗? …


> 不!


> L+E+A+D+E+R+S+H+I+P = 12+5+1+4+5+18+19+9+16 = 89%


>


> 那么,什么能使生活变成100%的圆满呢?


>


> 每个问题都有其解决之道,只要你把目光放得远一点!


>


> ATTITUDE(心态)


>   A+T+T+I+T+U+D+E 1+20+20+9+20+21+4+5 = 100%


>


> 我们对待工作、生活的态度能够使我们的生活达到100%的圆满!


>


>


> 祝:


> 工作愉快! ^_^

BT 下载资源整理

1、BT @ China 联盟 发布总索引区
bt下载的老大,bt下载的综合站,将bt带到中国:
特点:中国最早的bt。人气旺。
索引分类:『游戏索引』 | 『影视索引』 | 『动漫索引』 | 『音乐索引』 | 『软件索引』 
发布页: http://bt.itbbs.net/bt/

2、满分bt发布区(专门的学习考试资料发布)
特点:都是学习考试的资料,包括课堂录音、真题、考试辅导软件。
索引分类:[出国考试及申请资料] [英语影视及科普] [听力口语及英语学习软件] [四六级考研及大学学习] [计算机学习考试认证资料]
发布页: http://www.manfen.net/forum/btsubsystem.php

3、tlf发布页
特点:有发布小组。
索引分类:[不分類] [动画] [漫画] [补档] [游戏] [影视] [音乐] [其他] [软件]
发布页: http://bt1.eastgame.net/index.php

4、gamesir发布页
特点:游戏动漫发布
索引分类:游戏 动漫 音乐 影视
发布页: http://bt.gamesir.com

5、E游网bt发布区,
特点:电影较多
索引分类:游戏 软件 电影 电视 动画片 漫画 书籍 连续剧 音乐
http://www.egame365.com/bt/
以下的暂时没有索引区(或者由于流量问题,关闭了索引区),但可以到论坛直接下载.

6、雷傲论坛bt下载区。
特点:人气旺,没有索引页,采用论坛的形式发布.
http://bbs.leoboard.com/cgi-bin/forums.cgi?forum=73

7、蚂蚁论坛bt下载区
特点:人气旺,没有索引页,采用论坛的形式发布.
http://www.antcn.com/bbs/index.php

8、星空动漫下载区
特点:动漫。
http://xkcomic.net/index.php

9、三夫之家下载区
特点:综合
http://teky.8866.org/bbs/index.asp

10、影视前线发布索引页(可用)
http://61.133.84.149/bt/index.asp[/replyview

11:伊美

http://www.imagegarden.net/bt

gcc编译c语言中内嵌汇编

 

gcc编译c语言中内嵌汇编

–AT&T and Intel 汇编语法对照

寄存器命名:
AT&T:  %eax
Intel: eax

AT&T 语法源地址在左侧,目的地址在右侧与Intel 方式语法相反
将eax值传入ebx
AT&T:  movl %eax, %ebx
Intel: mov ebx, eax

AT&T 语法在立即数前有前缀$.
AT&T:  movl $0×0h, %eax
Intel: mov eax,0×0h

AT&T 语法在操作符后跟表示操作数类型的后缀b,w,l分别表示字节,字,双字,相当于伪操作符ptr,如果不加的话GAS会guess
AT&T:  movw %ax, %bx
Intel: mov bx, ax

内存寻址方式
AT&T:  immed32(basepointer,indexpointer,indexscale)
Intel: [basepointer + indexpointer*indexscale + immed32]

地址计算公式为:
immed32 + basepointer + indexpointer * indexscale

直接寻址

AT&T:  _a
Intel: [_a]

间接寻址
AT&T:  (%eax)
Intel: [eax]

相对寻址
AT&T: _variable(%eax)
Intel: [eax + _variable]

AT&T:  _array(,%eax,4)
Intel: [eax*4 + array]

C 代码: *(p+1) p定义为char *
AT&T:  1(%eax) where eax has the value of p
Intel: [eax + 1]

结构体数组寻址,结构体长度为8,下标存于eax,结构体内偏移地址存于ebx,_array为结构体数组首地址

AT&T:  _array(%ebx,%eax,8)
Intel: [ebx + eax*8 + _array]
函数内部实现交换
1、输入与输出变量相同
汇编代码部分标准的交换实现,输入部分用0寄存器表示”=r”(a)中所指定的寄存器即输入与输出变量相同
int main()
{                                                         804842c:      mov    0xfffffff4(%ebp),%ecx
        int a = 10, b = 0;                                      804842f:      mov    0xfffffff0(%ebp),%edx
        printf(“before swap: a = %2d, b = %2d\n”, a , b); 8048432:      mov    %ecx,%ebx             
        __asm__(“nop;                                           8048434:      mov    %edx,%esi            
                 movl %0, %%eax;                                8048436:      nop                         
                 movl %1, %0;                                   8048437:      mov    %ebx,%eax            
                 movl %%eax, %1;                                8048439:      mov    %esi,%ebx            
                 nop;”                                          804843b:      mov    %eax,%esi            
                :                                               804843d:      nop                         
                 “=r”(a), “=r”(b)                               804843e:      mov    %ebx,%edx            
                :                                               8048440:      mov    %esi,%ecx            
                 “0″(a), “1″(b)                                 8048442:      mov    %edx,%eax            
                :                                               8048444:      mov    %eax,0xfffffff4(%ebp)
                 “%eax”                                         8048447:      mov    %ecx,%eax            
                );                                              8048449:      mov    %eax,0xfffffff0(%ebp)
        printf(“after  swap: a = %2d, b = %2d\n”, a, b);
        return 0;
}
2、输入与输出用不同的寄存器,&表示输入输出需要分配不同的寄存器
int main()                                               
{                                                               
        int a = 10, b = 0;                                      
        printf(“before swap: a = %2d, b = %2d\n”, a, b); 804842b:      mov    0xfffffff8(%ebp),%edx          
        __asm__(“nop;                                           804842e:      mov    0xfffffff4(%ebp),%eax         
                 movl %2, %1;                                   8048431:      nop                                  
                 movl %3, %0;                                   8048432:      mov    %edx,%ebx                     
                 nop;”                                          8048434:      mov    %eax,%ecx                     
                :                                               8048436:      nop                                  
                 “=&r”(a), “=&r”(b)                             8048437:      mov    %ecx,%eax                     
                :                                               8048439:      mov    %ebx,%edx                     
                 “r”(a), “r”(b)                                 804843b:      mov    %eax,%eax                     
                );                                              804843d:      mov    %eax,0xfffffff8(%ebp)         
        printf(“after  swap: a = %2d, b = %2d\n”, a , b);       8048440:      mov    %edx,%eax                     
        return 0;                                               8048442:      mov    %eax,0xfffffff4(%ebp)         
}                                                              
                                                               
3、交换函数,需要间接寻址                                                    
#include <stdio.h>

void swap(int* x, int* y)     08048400 <swap>:                                            
{                                                               8048400:      push   %ebp         
        __asm__(“nop;                                           8048401:      mov    %esp,%ebp    
                 movl (%0), %%eax;                              8048403:      push   %ebx         
                 movl (%1), %%ebx;                              8048404:      mov    0×8(%ebp),%ecx
                 movl %%ebx, (%0);                              8048407:      mov    0xc(%ebp),%edx
                 movl %%eax, (%1);                              804840a:      nop                 
                 nop;”                                          804840b:      mov    (%ecx),%eax  
                :                                               804840d:      mov    (%edx),%ebx  
                :                                               804840f:      mov    %ebx,(%ecx)  
                 “r”(x),”r”(y)                                  8048411:      mov    %eax,(%edx)  
                :                                               8048413:      nop                 
                 “eax”, “ebx”, “memory”                         8048414:      mov    (%esp,1),%ebx ;ebx还原
                );                                              8048417:      leave   ;movl %ebp, %esp; pop ebp
}                                                               8048418:      ret                 
                                                                8048419:      lea    0×0(%esi),%esi
int main()
{
        int a = 10, b = 0;
        printf(“before swap: a = %2d, b = %2d\n”, a, b);
        swap(&a, &b);
        printf(“after  swap: a = %2d, b = %2d\n”, a, b);
        return 0;
}

4、从汇编代码中分离函数

1> 获得汇编代码
这里用加法函数,源代码为:
int sum(int a, int b)
{                    
        int c = a + b;
        return c;    
}
对应的汇编代码为                                                   
                                                        
Disassembly of           section .text:                    
                                                           
00000000 <sum>:                                            
   0:   55                      push   %ebp                
   1:   89 e5                   mov    %esp,%ebp           
   3:   83 ec 04                sub    $0×4,%esp           
   6:   8b 45 0c                mov    0xc(%ebp),%eax      
   9:   03 45 08                add    0×8(%ebp),%eax      
   c:   89 45 fc                mov    %eax,0xfffffffc(%ebp)
   f:   8b 45 fc                mov    0xfffffffc(%ebp),%eax
  12:   89 c0                   mov    %eax,%eax           
  14:   c9                      leave                      
  15:   c3                      ret                        
  16:   89 f6                   mov    %esi,%esi   

2> 编写内嵌汇编语言函数
分析:为函数构建运行时堆栈情况即可使其顺利运行,由于编译器在函数执行开始和结束时会增加
routine begin:
push %ebp; mov %esp, %ebp
routine end:
leave; ret
将上面的0, 1, 14, 15去掉,返回参数放在eax中将输出部分设置为”=a”(r) 用eax寄存器 r 为需要的return type
步骤:
i   定义return_type r 变量
ii  去掉push %ebp; mov %esp, %ebp;   leave; ret
iii 输出部分为:”=a”(r):

$ vi sumassemble.c
int sum(int a, int b)                                
{                                             
        int r;                                
        __asm__(“sub $0×4, %%esp;             
                 movl 0xc(%%ebp), %%eax;      
                 addl 0×8(%%ebp), %%eax;      
                 movl %%eax, 0xfffffffc(%%ebp);
                 movl 0xfffffffc(%%ebp), %%eax;
                 movl %%eax, %%eax;”          
                :                             
                 “=a”(r)                      
                );                            
        return r;                             
}

Disassembly of           section .text:                    

00000000 <sum>:
   0:   55                      push   %ebp
   1:   89 e5                   mov    %esp,%ebp
   3:   83 ec 04                sub    $0×4,%esp
   6:   83 ec 04                sub    $0×4,%esp
   9:   8b 45 0c                mov    0xc(%ebp),%eax
   c:   03 45 08                add    0×8(%ebp),%eax
   f:   89 45 fc                mov    %eax,0xfffffffc(%ebp)
  12:   8b 45 fc                mov    0xfffffffc(%ebp),%eax
  15:   89 c0                   mov    %eax,%eax
  17:   89 c0                   mov    %eax,%eax
  19:   89 45 fc                mov    %eax,0xfffffffc(%ebp)
  1c:   8b 45 fc                mov    0xfffffffc(%ebp),%eax
  1f:   89 c0                   mov    %eax,%eax
  21:   c9                      leave 
  22:   c3                      ret   
  23:   90                      nop           
 
3> 编译可执行程序
$ vi summain.c
extern int sum(int ,int);
int main()
{
        int x = sum(1,2);
        printf(“x = %d\n”, x);
        return 0;
}
$ cc -o sum_main sum_main.c sum_assemble.c
$ ./sum_main

2005年01月24日

[组图]来教你们打领带,MM,GG必学

来教你们打领带,MM,GG必学-转自网络


来教你们打领带,MM,GG必学
1、平结
  平结为最多男士选用的领结打法之一,几乎适用于各种材质的领带。要诀:领结下方所形成的凹洞需让两边均匀且对衬。

2、交叉结
  这是对于单色素雅质料且较薄领带适合选用的领结,对于喜欢展现流行感的男士不妨多加使用“交叉结”。

3、双环结
  一条质地细致的领带再搭配上双环结颇能营造时尚感,适合年轻的上班族选用。该领结完成的特色就是第一圈会稍露出于第二圈之外,可别刻意给盖住了。

4、 温莎结
  温莎结适合用于宽领型的衬衫,该领结应多往横向发展。应避免材质过厚的领带,领结也勿打得过大。

5、双交叉结
  这样的领结很容易让人有种高雅且隆重的感觉,适合正式之活动场合选用。该领结应多运用在素色且丝质领带上,若搭配大翻领的衬衫不但适合且有种尊贵感。

6、亚伯特王子结
  适用於浪漫扣领及尖领系列衬衫
  搭配浪漫质料柔软的细款领带
  正确打法是在宽边先预留较长的空间
  并在绕第二圈时尽量贴合在一起
  即可完成此一完美结型

7、四手结(单结)
  是所有领结中最容易上手的,适用於各种款式的浪漫系列衬衫及领带

8、浪漫结
  浪漫是一种完美的结型
  故适合用於各种浪漫系列的领口及衬衫
  完成後将领结下方之宽边压以绉摺可缩小其结型
  窄边亦可将它往左右移动使其小部份出现於宽边领带旁

9、简式结(马车夫结)
  适用於质料较厚的领带
  最适合打在标准式及扣式领口之衬衫
  将其宽边以180度由上往下翻转
  并将折叠处隐藏於後方
  待完成後可再调整其领带长度
  是最常见的一种结形

10、十字结(半温莎结)
  此款结型十分优雅及罕见
  其打法亦较复杂
  使用细款领带较容易上手
  最适合搭配在浪漫的尖领及标准式领口系列衬

收藏下来,自己学习一下啦!~
自力更生,丰衣足食!

2005年01月22日

【软考】2004年下半年软件设计师(高级程序员)下午试题

作者:老顽童整理校对 出处:老顽童网站

试题一(15分)
 
 阅读下列说明和数据流图,回答问题1至问题3,将解答填入答题纸的对应栏内。
某图书管理系统的主要功能是图书管理和信息查询。对于初次借书的读者,系统自动生成读者号,并与读者基本信息(姓名、单位、地址等)一起写入读者文件。
系统的图书管理功能分为四个方面:购入新书、读者借书、读者还书以及图书注销。
  1、购入新书时需要为该书编制入库单。入库单内容包括图书分类目录号、书名、作者、价格、数量和购书日期,将这些信息写入图书目录文件并修改文件中的库存总量(表示到目前为止,购入此种图书的数量)。
 
 2、读者借书时需填写借书单。借书单内容包括读者号和所借图书分类目录号。系统首先检查该读者号是否有效,若无效,则拒绝借书;若有效,则进一步检查该
读者已借图书是否超过最大限制数(假设每位读者能同时借阅的书不超过5本),若已达到最大限制数,则拒绝借书;否则允许借书,同时将图书分类目录号、读者
号和借阅日期等信息写入借书文件中。
  3、读者还书时需填写还书单。系统根据读者号和图书分类目录号,从借书文件中读出与该图书相关的借阅记录,标明还书日期,再写回到借书文件中,若图书逾期,则处以相应的罚款。
  4、注销图书时,需填写注销单并修改图书目录文件中的库存总量。 系统的信息查询功能主要包括读者信息查询和图书信息查询。其中读者信息查询可得到读者的基本信息以及读者借阅图书的情况;图书信息查询可得到图书基本信息和图书的借出情况。
  图书管理系统的顶层图如图1-1所示;图书管理系统的第0层DFD图如图1-2所示 其中,加工2的细化图如图1-3所示。
 【数据流图1-1】


图1-1图书管理系统顶层图

 【数据流图1-2】


图1-2图书管理系统第0层DFD图

 【数据流图1-3】

 【问题1】(2分)
  数据流图1-2中有两条数据流是错误的,请指出这两条数据流的起点和终点。
 【问题2】(6分)
  数据流图1-3中缺少三条数据流,请指出这三条数据流的起点和终点。
 【问题3】(7分)
  根据系统功能和数据流图填充下列数据字典条目中的(1)和(2):
  查洵请求信息=[查询读者请求信息|查询图书请求信息]
  读者情况=读者号+姓名+所在单位+{借书情况}
  管理工作请求单=__(1)__
  入库单=__(2)__

试题二(15分)
  阅读下列说明和E-R图,回答问题1至问题3,将解答填入答题纸的对应栏内。
 【说明】
  某网上订书系统的E-R图(已消除了不必要的冗余)如图2-1所示(图中没有标出主码)。图中实体的说明如表2-1所示。相关属性说明如表2-2所示。

表2-1


实体

说明

 Books

 书店内的书

 Gustomers

 与书店有业务的顾客

 Orders

 顾客向书店下的购书单

表2-2


主要属性

说明

 qty_in_stock

 图书库存量

 year_published

 出版日期

 ordernum

 购书单编号

 cid

 顾客编号

 bid

 惟一表示每种图书的编码

 cardnum

 顾客信用卡号码

 orderdate

 填购书单日期

 qty

 订购某种图书的数量

 ship_date

 发货日期

  一个顾客可以在同一天填写多张购书单,每张购书单上可填写多种图书,每种图书可以订购多本,bid相同的图书在同一张购书单上不能出现多次。
  注:为简化起见,不考虑信用卡号码泄漏所带来的安全性等问题。
 【图2-1】

 【问题1】(9分)
  根据E-R图中给出的词汇,按照“有关模式名(属性,属性,…)”的格式,将此E-R图转换为4个关系模式,并指出每个关系模式中的主码和外码,其中模式名根据需要取实体名或联系名。
  [问题2](2分)
 
 创建Customers表时,cid使用INTEGER数据类型,cname使用CHAR(80)数据类型,address使用CHAR(200)数据
类型,cardnum使用CHAR(16)数据类型并且要求此列值惟一。请在下列用于创建表Customers的SQL语句空缺处填入正确的内容。
  CREATE TABLE Customers(cid INTEGER NOT NULL,
  cname  CHAR(80) NOT NULL,
  address CHAR(200),
  cardnum CHAR(16) NOT NULL,
  __(1)__,
  __(2)__
 【问题3】(4分)
  如下的SQL语句是书店用于查询“所有订购了bid为‘123-456’图书的用户订购其他图书的情况”的不完整语句,请在空缺处填入正确的内容。
  Select bid
  From orderlist A
  Where not exists(Select * from Orders B
  where A. ordernum = B.ordernum and B.cid __(3)__
  (Select bid from orderlist C,orders D
  where __(4)__.bid=’123-456′
  and __(5)__=D.ordernum))

试题三(15分)
  阅读下列说明和数据流图,回答问题1和问题3,将解答填入答题纸的对应栏内。
 【说明】
  某指纹门禁系统结构如图3-1所示,其主要部件有:主机(MainFrname)、锁控器(LockController)、指纹采集器(FingerReader)和电控锁(Lock)。
  (1) 系统中的每个电控锁都有一个惟一的编号。锁的状态有两种:“已锁住”和“未锁住”。
  (2) 在主机上可以设置每把锁的安全级别以及用户的开锁权限。只有当用户的开锁权限大于或等于锁的安全级别并且锁处于“已锁住”状态时,才能将锁打开。
  (3) 用户的指纹信息、开锁权限以及锁的安全级别都保存在主机上的数据库中。
  (4) 用户开锁时,只需按一下指纹采集器。指纹采集器将发送一个中断事件给锁控器,锁控器从指纹采集器读取用户的指纹并将指纹信息发送到主机,主机根据数据库中存储的信息来判断用户是否具有开锁权限,若有且锁当前处于“已锁住”状态,则将锁打开;否则系统报警。
  该系统采用面向对象方法开发,系统中的类以及类之间的关系用UML类图表示,图3-2是该系统类图的一部分;系统的动态行为采用UML序列图表示,图3-3是用户成功开锁的序列图。

 【问题1】(6分)
  图3-2是该系统类图的一部分,依据上述说明中给出的术语,给出类Look的主要属性。
 【问题2](5分)
  依据上述说明中给出的词语,将图3-3中的(1)~(5)处补充完整。
 【问题3】(4分)
组装(Composition)和聚集(Aggregation)是UML中两种非常重要的关系。请说明组装和聚集分别表示什么含义?两者的区别是什么?

试题四(15分)
  阅读下列说明和数据流图,回答问题1和问题3,将解答填入答题纸的对应栏内。
 【说明】
  在并发系统设计中,通过对信号量s的P、V操作实现进程的同步与互斥控制。
  P(S):S:=S-1,若S≥0,则执行P操作的进程继续执行;若S<0,则置该进程为阻塞状态,并将其插入阻塞队列。
  V(S):S:=S+1,若S>0,则执行V操作的进程继续执行;若S≤0,则从阻塞队列唤醒一个进程,并将其插入就绪队列,然后执行V操作的进程继续执行。
 【问题1】(4分)
 
 在某并发系统中,有一个发送进程A、一个接收进程B、一个环形缓冲区BUFFER、信号量S1和S2。发送进程不断地产生消息并写入缓冲区
BUFFER,接收进程不断地从缓冲区BUFFER取消息。假设发送进程和接收进程可以并发地执行,那么,当缓冲区的容量为N时,如何使用P、V操作才能
保证系统的正常工作。发送进程A和接收进程B的工作流程如图4-1所示。请在图4-1中的空(1)~(4)处填入正确的内容。


 【问题2】(5分)
 
 若系统中有多个发送进程和接收进程,进程间的工作流程如图4-2所示,其中空(1)~(4)的内容与图4-1相同。发送进程产生消息并顺序地写入环形缓
冲区BUFFER,接受者进程顺序地从BUFFER中取消息,且每条消息只能读取一次。为了保证进程间的正常通讯,增加了信号量SA和SB。
  ① 请说明信号量SA和SB的物理意义,并在图4-2中的空(5)和空(6)处填入正确的内容。
  ② 请从图4-2的(a)~(1)中选择四个位置正确地插入P(SA)、V(SA)、P(SB)、V(SB)。
 【图4-2]】


 【问题3】(6分)
 
 设系统中只有进程A和进程B,除了互斥地使用CPU和打印机R外,进程A和B不使用其他资源。另外,进程B的优先级比A高,而进程A先于B准备好。进程
A和B的执行情况如图4-3所示,其中粗实线表示进程在执行中,细实线表示打印机R在使用中。(每个进程具有三种状态:运行,就绪和阻塞)
  请说明进程A和B在图4-3所示的T1、T2、T3、T4时刻所处的状态;若是阻塞状态,请说明阻塞原因。
 【图4-3】

试题五(15分,每空3分)
  阅读下列函数说明和C代码,将应填入(n)处的字句写在答题纸的对应栏内。
 【说明】
  函数int Toplogical (LinkedWDigraph G)的功能是对图G中的顶点进行拓扑排序,并返回关键路径的长度。其中图G表示一个具有n个顶点的AOE-网,图中顶点从1~n依次编号,图G的存储结构采用邻接表表示,其数据类型定义如下:
  typedef struct Gnode{     /*邻接表的表结点类型*/
   int adjvex;        /*邻接顶点编号*/
   int weight;        /*弧上的权值*/
   struct Gonde*nextare;    /*指示下一个弧的结点*/
  }Gnode;

  typedef struct Adjlist{    /*邻接表的头结点类型*/
   char vdata;          /*顶点的数据信息*/
   struct Gnode*Firstadj;   /*指向邻接表的第一个表结点*/
  }Adjlist;

  typedef struct LinkedWDigraph{  /*图的类型*/
   int n ,e;          /*图中顶点个数和边数*/
   struct Adjlist head;     /*指向图中第一个顶点的邻接表的头结点*/
  }LinkedWDigraph;
  例如,某AOE-网如图5-1所示,其邻接表存储结构如图5-2所示。

  【函数】
  int Toplogical(LinkedWDigraph G)
  {Gnode *p;
      int j,w,top=0;
      int Stack,ve,*indegree;
      ve=(int )mallloc(G.n+1)*sizeof(int));
      indegree=(int*)malloc((G.n+1)*sizeof(int)); /*存储网中个顶点的入度*/
      Stack=(int*)malloc((G.n+1)*sizeof(int));   /*存储入度为0的顶点的编号*/
      If (!ve||!indegree||!Stack)            exit(0);
      for (j=1;j<=G.n;j++){
     ve[j]=0;indegree[j]=0;
    }                /*for*/
    for (j=1;j<=G.n;j++) {      /*求网中各顶点的入度*/
       p=G.head[j].Firstadj;
       while (p) {
      __(1)__;          p=p->nextarc;
     }               /*while*/
    }         /*for*/
    for (j=1;j<=G.n;j++)                /求网中入度为0的顶点并保存其编号*/
    if (!indegree[j]) Stack[++top]=j;
    while (top>0){
     w=__(2)__;
     printf(“%c”,G.head[w].vdata);
     p=G.head[w].Firstadj;
     while (p) {
      __(3)__;
      if (!indegree[p->adjvex])
       Stack[++top]=p->adjvex;
      If(__(4)__)
       Ve[p->adjvex]=ve[w]+p->weight;
      P=p->nextarc;
     }/*while*/
     return(5);
    }       /*Toplogical*/

试题六(15分,每空3分)
  阅读下列函数说明和C++代码,将应填入(__n__)处的字句写在答题纸的对应栏内。
 【说明】
  通常情况下,用户可以对应用系统进行配置,并将配置信息保存在配置文件中,应用系统在启动时首先将配置文件加载到内存中,这些内存配置信息应该有且仅有一份。
  下面的代码应用了单身模式(Singleton)以保证Configure类只能有一个实例。这样,Configure类的使用者无法定义该类的多个实例,否则会产生编译错误。
 【C++代码】
  #include <iostream.h>
  class Configure {
   __(1)__;
   Configure() {};            //构造函数
   Public;
   Static Configure*Instance();
   Public;
   Int GetConfigureData() {return data;} //获取配置信息
   Int SetConfigureDate(int m_data)
   { data=m_data;       return data; }   //设置配置信息
   private:
   static Configure*_instance;
   int data;                          //配置信息
  };
  __(2)__=NULL;
  Configure * Configure;;Instance() {
   If (_instance= =NULL) {
    _instance=__(3)__;
   //加载配置文件并设置内存配置信息,此处省略
   }
   return__(4)__;
  }
  void main() {
   Configure*t=NULL;
   t=__(5)__;
   int d=t->GetConfigureData();
   //获取配置信息后进行其它工作,此处省略
  }

试题七(15分,每空3分)
 【说明】
  类Queue表示队列,类中的方法如下表所示。

isEmpty()

判断队列是否为空。如果队列不为空,返回true;否则,返回false。

Enpueue(object newNode)

入队操作。

Dequeue()

出队操作。如果队列为空,则抛出异常。

  类Node表示队列中的元素;类emptyQueueException给出了队列操作中的异常处理操作。
 【JAVA代码】
  public class testmain { //主类
   public static viod main (string args[]){
    Queue q= new queue;
    q.enqueue(“first!”);
    q.enqueue(“second!”);
    q.enqueue(“third!”);
    __(1)__  {
     while(true)
     system.out.println(q.dequeue());
    }
    catch(__(2)__){}
   }

   public class queue {   //队列
    node m_firstnode;
    public queue(){m_firstnode=null;}
    public Boolean isempty(){
    if (m_firstnode= =null) return true;
    else return false;
    }
    public viod enqueue(object newnode){  //入队操作
    node next = m_firstnode;
    if (next = = null) m_firstnode=new node(newnode);
    else {
     while(next.getnext()!=null) next=next.getnext();
     next.setnext(new node(newnode));
    }
   }
   public object dequeue() __(3)__ {      //出队操作
    object node;
    if (is empty())
    __(4)__;                 //队列为空,抛出异常
    else {
     node =m_firstnode.getobject();
     m_firstnode=m_firstnode.getnext();
     return node;
    }
   }
  }

  public class node{
   object m_data;
   node m_next;
   public node(object data)     {m_data=data;  m_next=null;}
   public node(object data,node next)   {m_data=data;  m_next=next;}
   public void setobject(object data)   {m_data=data; }
   public object getobject(object data)  {return m_data;}
   public void setnext(node next)   {m_next=next;}
   public node getnext()     {return m_next;}
  }
  public class emptyqueueexception extends   (5)  {
   public emptyqueueexception() {
     system.out.println(“队列已空!”);
   }
  }

【软考】2004年下半年软件设计师上午试卷

全国计算机技术与软件专业技术资格(水平)考试
2004年下半年 软件设计师上午试卷

  ●内存按字节编址,地址从A4000H到CBFFFH,共有_(1)_字节。若用存储容量为32K*8bit的存储器芯片构成该内存,至少需要_(2)_ 片。
  (1)A.80K   B.96K   C.160K   D.192K
  (2)A.2    B.5    C.8     D.10

  ●中断响应时间是指_(3)_。
  (3)A.从中断处理开始到中断处理结束所用的时间
    B.从发出中断请求到中断处理结束所用的时间
    C.从发出中断请求到进入中断处理所用的时间
    D.从中断处理结束到再次中断请求的时间

  ●若指令流水线把一条指令分为取指、分析和执行三部分,且三部分的时间分别是t取指=2ns ,t分析=2ns,t执行=1ns。则100条指令全部执行完毕需_(4)_ns。
  (4)A.163   B.183   C.193   D.203

  ●在单指令流多数据流计算机(SIMD)中,各处理单元必须 _(5)_。
  (5)A.以同步方式,在同一时间内执行不同的指令
    B.以同步方式,在同一时间内执行同一条指令
    C.以异步方式,在同一时间内执行不同的指令
    D.以异步方式,在同一时间内执行同一条指令

  ●单个磁头在向盘片的磁性涂层上写入数据时,是以_(6)_方式写入的。
  (6)A.并行     B.并一串行    C.串行     D.串一并行

  ●容量为64块的Cache采用组相联方式映像,字块大小为128个字,每4块为一组。若主存容量为4096块,且以字编址,那么主存地址应为_(7)_位,主存区号应为_(8)_位。
  (7)A.16      B.17       C.18      D.19
  (8)A.5      B.6        C.7       D.8

  ●软件开发中的瀑布模型典型地刻画了软件生存周期的阶段划分,与其最相适应的软件开发方法是_(9)_。
  (9)A.构件化方法  B.结构化方法   C.面向对象方法 D.快速原型方法

  ●下述任务中,不属于软件工程需求分析阶段的是_(10)_ 。
  (10)A.分析软件系统的数据要求     B.确定软件系统的功能需求
    C.确定软件系统的性能要求     D.确定软件系统的运行平台

  ●软件设计的主要任务是设计软件的结构、过程和模块,其中软件结构设计的主要任务是要确定_(11)_。
  (11)A.模块间的操作细节        B.模块间的相似性
    C.模块间的组成关系        D.模块的具体功能

  ●系统测试是将软件系统与硬件、外设和网络等其他因素结合,对整个软件系统进行测试。_(12)_不是系统测试的内容。
  (12)A.路径测试  B.可靠性测试   C.安装测试    D.安全测试

  ●项目管理工具中,将网络方法用于工作计划安排的评审和检查的是_(13)_。
  (13)A.Gantt图   B.PERT网图    C.因果分析图   D.流程图

  ●在结构化分析方法中,数据字典是重要的文档。对加工的描述是数据字典的组成内容之一,常用的加工描述方法_(14)_。
  (14)A.只有结构化语言         B.有结构化语言和判定树
    C.有结构化语言、判定树和判定表  D.有判定树和判定表

  ●CMM模型将软件过程的成熟度分为5个等级。在_(15)_使用定量分析来不断地改进和管理软件过程。
  (15)A.优化级   B.管理级     C.定义级     D.可重复级

  ●在面向数据流的设计方法中,一般把数据流图中的数据流划分为_(16)_两种。
  (16)A.数据流和事务流         B.变换流和数据流
    C.变换流和事务流         D.控制流和事务流

  ●_(17)_属于第三层VPN协议。
  (17)A.TCP     B.IPsec      C.PPOE      D.SSL

  ●下图所示的防火墙结构属于_(18)_。
  (18)A.简单的双宿主主机结构        B.单DMZ防火墙结构
    C.带有屏蔽路由器的单网段防火墙结构  D.双DMZ防火墙结构

  ●电子商务交易必须具备抗抵赖性,目的在于防_(19)_。
  (19)A.一个实体假装成另一个实体      B.参与此交易的一方否认曾经发生过此次交易
    C.他人对数据进行非授权的修改、破坏  D.信息从被监视的通信过程中泄漏出去

  ●知识产权一般都具有法定的保护期限,一旦保护期限届满,权利将自行终止,成为社会公众可以自由使用的知识。_(20)_权受法律保护的期限是不确定的,一旦为公众所知悉,即成为公众可以自由使用的知识。
  (20)A.发明专利     B.商标      C.作品发表    D.商业秘密

  ●甲、乙两人在同一时间就同样的发明创造提交了专利申请,专利局将分别向各申请人通报有关情况,并提出多种解决这一问题的办法,不可能采用_(21)_的办法。
  (21)A.两申请人作为一件申请的共同申请人
    B.其中一方放弃权利并从另一方得到适当的补偿
    C.两件申请都不授予专利权      D.两件申请都授予专利权

  ● 《计算机软件产品开发文件编制指南》》(GB 8567-88)是_(22)_ 标准。
  (22)A.强制性国家    B.推荐性国家   C.强制性行业   D.推荐性行业

 
 ●虚拟存储管理系统的基础是程序的_(23)_理论,这个理论的基本含义是指程序执行时往往会不均匀地访问主存储器单元。根据这个理论,Denning
提出了工作集理论。工作集是进程运行时被频繁地访问的页面集合。在进程运行时,如果它的工作集页面都在_(24)_内,能够使该进程有效地运行,否则会出
现频繁的页面调入/调出现象。
  (23)A.全局性      B.局部性     C.时间全局性   D.空间全局性
  (24)A.主存储器     B.虚拟存储器   C.辅助存储器   D.u盘

  ●在UNIX操作系统中,若用户键入的命令参数的个数为1时,执行cat$1命令;若用户键入的命令参数的个数为2时,执行cat>>$2<$1命令。请将下面所示的Shell程序的空缺部分补齐。
  case_(25)_in
   1)cat$1;;
   2)cat>>$2<$1;;
   *)echo ‘default…’
  esac
  (25)A.$$        B.$@       C.$#       D.$*

  ●进程PA不断地向管道写数据,进程PB从管道中读数据并加工处理,如下图所示。如果采用PV操作来实现进程PA和进程PB间的管道通信,并且保证这两个进程并发执行的正确性,则至少需要_(26)_。
  (26)A.1个信号量,信号量的初值为0
    B.2个信号量,信号量的初值分别为0、1
    C.3个信号量,信号量的初值分别为0、0、1
    D.4个信号量,信号量的初值分别为0、0、1、1

  ●假设系统中有三类互斥资源R1、R2和R3,可用资源数分别为9、8和5。在To时刻系统中有P1、P2、P3、P4和P5五个进程,这些进程对资源的最大需求量和已分配资源数如下表所示。如果进程按_(27)_序列执行,那么系统状态是安全的。   


资源 最大需求量 已分配资源数
进程 R1 R2 R3 R1 R2 R3
P1 6 5 2 1 2 1
P2 2 2 1 2 1 1
P3 8 0 1 2 1 0
P4 1 2 1 1 2 0
P5 3 4 4 1 1 3

  (27)A.P1→P2→P4→P5→P3     B.P2→P1→P4→P5→P3
    C.P2→P4→P5→P1→P3     D.P4→P2→P4→P1→P3

  ●某一非确定性有限自动机(NFA)的状态转换图如下图所示,与该NFA等价的正规式是_(28)_,与该NFA等价的DFA是_(29)_。

  (28) A.0*|(0|1)0   B.(0|10)*   C.0*((011)0)*   D.0*(10)*
  

  ●在UML提供的图中,可以采用_(30)_对逻辑数据库模式建模;_(31)_用于接口、类和协作的行为建模,并强调对象行为的事件顺序; _(32)_用于系统的功能建模,并具强调对象间的控制流。
  (30)A.用例图   B.构件图   C.活动图    D.类图
  (31)A.协作图   B.状态图   C.序列图    D.对象图
  (32)A.状态图   B.用例图   C.活动图    D.类图

  ●在一棵完全二叉树中,其根的序号为1,_(33)_可判定序号为p和q的两个结点是否在同一层。
  (33)A.Llog2P」=Llog2q」     B.log2P = log2q
    C.Llog2P」+1=Llog2q」    D.Llog2P」=+Llog2q」+1

  ●堆是一种数据结构,_(34)_是堆。
  (34)A.(10,50,80,30,60,20,15,18) B.(10,18,15,20,50,80,30,60)
    C.(10,15,18,50,80,30,60,20) D.(10,30,60,20,15,18,50,80)

  ●_(35)_从二叉树的任一结点出发到根的路径上,所经过的结点序列必按其关键字降序排列。
  (35)A.二叉排序树 B.大顶堆    C.小顶堆    D.平衡二叉树

  ●若广义表L=((1,2,3)),则L的K度和深度分别为_(36)_。
  (36)A.1和1    B.1和2     C.1和3     D.2和2

  ●若对27个元素只进行三趟多路归并排序,则选取的归并路数为_(37)_。
  (37)A.2     B.3      C.4      D.5

  ●_(38)_是多媒体内容描述接口标准。
  (38)A.MPEG-1   B.MPEG-2    C.MPEG-4    D.MPEG-7

  ●未经压缩的数字音频数据传输率的计算公式为_(39)_。
  (39)A.采样频率(Hz)*量化位数(bit)*声道数*1/8
    B.采样频率(Hz)*量化位数(bit)*声道数
    C.采样频率(Hz)*量化位数(bit)*1/8
    D.采样频率(Hz)*量化位数(bit)*声道数*1/16

  ●彩色打印机中所采用的颜色空间是_(40)_。
  (40)A.RGB彩色空间 B.CMY彩色空间 C.YUV彩色空间 D.HSL彩色空问

  ●MPEG视频中的时间冗余信息可以采用_(41)_的方法来进行压缩编码。
  (41)A.帧间预测和变换编码     B.霍大曼编码和运动补偿
    C.变换编码和行程编码     D.帧间预测和运动补偿

 
 ●假定每一车次具有唯一的始发站和终点站。如果实体“列车时刻表”属性为车次、始发站、发车时间、终点站、到达时间,该实体的主键是_(42)_;如果
实体“列车运行表”属性为车次、日期、发车时间、到达时间,该实体的主键是_(43)_。通常情况下,上述“列车时刻表”和“列车运行表”两实体型间_
(44)_联系。
  (42)A.车次    B.始发站    C.发车时间  D.车次,始发站
  (43)A.车次    B.始发站    C.发车时间  D.车次,日期
  (44)A.不存在   B.存在一对一  C.存在一对多 D.存在多对多

  ●关系模式R(U,F),其中U=(W,X,Y,Z),F={WX→Y,W→X,X,→Z,yVw}。关系模式R的候选码是_(45)_,_(46)_是无损连接并保持函数依赖的分解。
  (45)A.W和Y    B.WY      C.WX     D.WZ
  (46)A.ρ={R1(WY),R2(XZ)}     B.ρ={R1(WZ),R2(XY)}
    C.ρ={R1(WXY),R2(XZ)}     D.ρ={R1(WX),R2(YZ)}

  ●类的实例化过程是一种实例的合成过程,而不仅仅是根据单个类型进行的空间分配、初始化和绑定。指导编译程序进行这种合成的是_(47)_。
  (47)A.类层次结构 B.实例的个数  C.多态的种类 D.每个实例的初始状态

  ●重置的基本思想是通过_(48)_机制的支持,使得子类在继承父类界面定义的前提下,用适合于自己要求的实现去置换父类中的相应实现。
  (48)A.静态绑定  B.对象引用   C.类型匹配  D.动态绑定

  ●OMT定义了三种模型来描述系统。_(49)_可以用状态图来表示;_(50)_可以用数据流图来表示。_(51)_为上述两种模型提供了基本的框架。
  (49)A.对象模型  B.功能模型   C.动态模型  D.类模型
  (50)A.对象模型  B.功能模型   C.动态模型  D.类模型
  (51)A.对象模型  B.功能模型   C.动态模型  D.类模型

  ●采用动态规划策略求解问题的显著特征是满足最优性原理,其含义是_(52)_。
  (52)A.当前所做出的决策不会影响后面的决策
    B.原问题的最优解包含其子问题的最优解
    C.问题可以找到最优解,但利用贪心法不能找到最优解
    D.每次决策必须是当前看来最优的决策才可以找到最优解

  ●下面函数中渐进时间最小的是_(53)_。
  (53)A.T1(n)=n+nlogn        B.T2(n)=2n+nlogn 
    C.T3(n)=n2—logn        D.T4(n)=n+100logn

  ●下面的程序段违反了算法的_(54)_原则。
  Void sam()
  { int n=2;
   while(!odd(n))
   n+=2;
   printf(n);
  }
  (54)A.有穷性    B.确定性   C.可行性    D.健壮性

  ●拉斯维加斯(Las Vegas)算法是一种常用的_(55)_算法。
  (55)A.确定性    B.近似    C.概率     D.加密

  ●在分支-限界算法设计策略中,通常采用_(56)_搜索问题的解空间。
  (56)A.深度优先   B.广度优先  C.自底向上   D.拓扑序列

  ●在下列算法设计方法中,_(57)_在求解问题的过程中并不从整体最优上加以考虑,而是做出在当前看来是最好的选择。利用该设计方法可以解决 _(58)_问题。
  (57)A.分治法    B.贪心法   C.动态规划方法 D.回溯法
  (58)A.排序     B.检索    C.背包     D.0/1背包

  ●以关键字比较为基础的排序算法在最坏情况下的计算时间下界为O(nlogn)。下面的排序算法中,最坏情况下计算时间可以达到O(nlogn)的是_(59)_;该算法采用的设计方法是_(60)_。
  (59)A.归并排序 B.插入排序 C.选择排序 D.冒泡排序
  (60)A.分治法 B.贪心法 C.动态规划方法 D.回溯法

  ●以太网100BASE-TX标准规定的传输介质是_(61)_ 。
  (61)A.3类UTP    B.5类UTP   C.单模光纤   D.多模光纤

  ●许多网络通信需要进行组播,以下选项中不采用组播协议的应用是 _(62)_。在IPv4中把_(63)_类地址作为组播地址。
  (62)A.VOD      B.Netmeeting C.CSCW     D.FTP
  (63)A.A       B.B      C.D       D.E

  ●将双绞线制作成交叉线(一端按EIA/TIA 568A线序,另一端按EIA/TIA 568B线序),该双绞线连接的两个设备可为_(64)_。
  (64)A.网卡与网卡         B.网卡与交换机
    C.网卡与集线器        D.交换机的以太口与下一级交换机的UPLINK口

  ●以下不属于中间件技术的是 _(65)_ 。
  (65)A.javaRMI    B.CORBA   C.DCOM      D.JavaApplet

 
 ●Networks can be interconnected by different devices in the physical
layer networks can be connected by _(66)_ or hubs .which just move the
bits from one network to an identical network. One layer up we find
bridges and switches which operate at data link layer. They can accept
_(67)_ examine the MAC address and forward the frames to a different
network while doing minor protocol translation in the process in me
network layer ,we have routers that can connect two networks, If two
networks have _(68)_ network layer, the router may be able to translate
between the packer formats. In the transport layer we find transport
gateway, which can interface between two transport connections Finally,
in the application layer, application gateways translate message _(69)_
.As an example, gateways between Internet e-mail and X.400 e-mail must
_(70)_ the e-mail message and change various header fields.
  (66)A.reapers   B.relays    C.packages   D.modems
  (67)A.frimes              B.packets  C.packages   D.cells
  (68)A.special   B.dependent C.similar     D.dissimilar
  (69)A.syntax    B.semantics C.language   D.format
  (70)A.analyze   B.parse   C.delete    D.create

 
 ● The purpose of the requirements definition phase is to produce a
clear, complete, consistent, and testable _(71)_ of the technical
requirements for the software product.
During the requirements
definition phase, the requirements definition team uses an iterative
process to expand a broad statement of the system requirements into a
complete and detailed specification of each function that the software
must perform and each _(72)_ that it must meet. The starting point is
usually a set of high level requirements from the _(73)_ that describe
the project or problem.
  In either case, the requirements
definition team formulates an overall concept for the system and then
defines _(74)_ showing how the system will be operated publishes the
system and operations concept document and conducts a system concept
review (SCR).
  Following the SCR, the team derives _(75)_
requirements for the system from the high level requirements and the
system and operations concept. using structured or object-oriented
analysis. the team specifies the software functions and algorithms
needed to satisfy each detailed requirement.
  (71)A.function   B.definition   C.specification  D.statement
  (72)A.criterion   B.standard    C.model      D.system
  (73)A.producer   B.customer    C.programmer    D.analyser
  (74)A.rules     B.principles   C.scenarios    D.scenes
  (75)A.detailed   B.outlined    C.total      D.complete

少走弯路的十条忠告(转载)
发信站: BBS 水木清华站 (Sat Jan 22 09:06:19 2005), 站内

【 以下文字转载自 Sales 讨论区 】
发信人: firerat (醉过方知酒浓), 信区: Sales
标  题: 少走弯路的十条忠告(转载)
发信站: BBS 水木清华站 (Thu Jan 20 17:13:22 2005), 站内

 刚刚走上社会的年轻人,充满了蓄势待发的豪情、青春的朝气、前卫的思想,梦
想着丰富的待遇和轰轰烈烈的事业。可是,社会毕竟是一所包罗万象、喧嚣复杂的
大学校,这里没有寒暑假,拒绝虚假和肤浅,更拒绝空想和庸碌,难以预告何时开
课何时放学。 
  
  
   如何在涉世之初少走弯路,有一个好的开端,开始一番成功的事业?以下是
一些先行者积累的10条有益的涉世忠告。好好地遵循、把握这些忠告和建议吧,比
起所学的课堂课程来,它毫不逊色! 
  
  
   1. 买个闹钟,以便按时叫醒你。贪睡和不守时,都将成为你工作和事业上的
绊脚石,任何时候都一样。不仅要学会准时,更要学会提前。就如你坐车去某地,
沿途的风景很美,你忍不住下车看一看,后来虽然你还是赶到了某地,却不是准时
到达。“闹钟”只是一种简单的标志和提示,真正灵活、实用的时间,掌握在每个
人的心中。 
  
  
   2. 如果你不喜欢现在的工作,要么辞职不干,要么就闭嘴不言。初出茅庐,
往往眼高手低,心高气傲,大事做不了,小事不愿做。不要养成挑三拣四的习惯。
不要雨天烦打伞,不带伞又怕淋雨,处处表现出不满的情绪。记住,不做则已,要
做就要做好。 
  
  
   3. 每个人都有孤独的时候。要学会忍受孤独,这样才会成熟起来。年轻人嘻
嘻哈哈、打打闹闹惯了,到了一个陌生的环境,面对形形色色的人和事,一下子不
知所措起来,有时连一个可以倾心说话的地方也没有。这时,千万别浮躁,学会静
心,学会忍受孤独。在孤独中思考,在思考中成熟,在成熟中升华。不要因为寂寞
而乱了方寸,而去做无聊无益的事情,白白浪费了宝贵的时间。 
  
  
   4. 走运时要做好倒霉的准备。有一天,一只狐狸走到一个葡萄园外,看见里
面水灵灵的葡萄垂涎欲滴。可是外面有栅栏挡着,无法进去。于是它一狠心绝食三
日,减肥之后,终于钻进葡萄园内饱餐一顿。当它心满意足地想离开葡萄园时,发
觉自己吃得太饱,怎么也钻不出栅栏了。相信任何人都不愿做这样的狐狸。退路同
样重要。饱带干粮,晴带雨伞,点滴积累,水到渠成。有的东西今天似乎一文不值
,但有朝一日也许就会身价百倍。 
  
  
   5. 不要像玻璃那样脆弱。有的人眼睛总盯着自己,所以长不高看不远;总是
喜欢怨天尤人,也使别人无比厌烦。没有苦中苦,哪来甜中甜?不要像玻璃那样脆
弱,而应像水晶一样透明,太阳一样辉煌,腊梅一样坚强。既然睁开眼睛享受风的
清凉,就不要埋怨风中细小的沙粒。 
  
  
   6. 管住自己的嘴巴。不要谈论自己,更不要议论别人。谈论自己往往会自大
虚伪,在名不副实中失去自己。议论别人往往陷入鸡毛蒜皮的是非口舌中纠缠不清
。每天下班后和你的那些同事朋友喝酒聊天可不是件好事,因为,这中间往往会把
议论同事、朋友当做话题。背后议论人总是不好的,尤其是议论别人的短处,这些
会降低你的人格。 
  
  
   7. 机会从不会“失掉”,你失掉了,自有别人会得到。不要凡事在天,守株
待兔,更不要寄希望于“机会”。机会只不过是相对于充分准备而又善于创造机会
的人而言的。也许,你正为失去一个机会而懊悔、埋怨的时候,机会正被你对面那
个同样的“倒霉鬼”给抓住了。没有机会,就要创造机会,有了机会,就要巧妙地
抓住。 
  
  
   8. 若电话老是不响,你该打出去。很多时候,电话会给你带来意想不到的收
获,它不是花瓶,仅仅成为一种摆设。交了新朋友,别忘了老朋友,朋友多了路好
走。交际的一大诀窍就是主动。好的人缘好的口碑,往往助你的事业更上一个台阶
。 
  
  
   9. 千万不要因为自己已经到了结婚年龄而草率结婚。想结婚,就要找一个能
和你心心相印、相辅相携的伴侣。不要因为放纵和游戏而恋爱,不要因为恋爱而影
响工作和事业,更不要因一桩草率而失败的婚姻而使人生受阻。感情用事往往会因
小失大。 
  
  
   10. 写出你一生要做的事情,把单子放在皮夹里,经常拿出来看。人生要有
目标,要有计划,要有提醒,要有紧迫感。一个又一个小目标串起来,就成了你一
生的大目标。生活富足了,环境改善了,不要忘了皮夹里那张看似薄薄的单子。

2005年01月21日

 用手机上网已经有好长一段时间了,每次在和朋友交流经验时,总会发现很多朋友对CMWAP和CMNET的区别还是不甚了解。对于一些新软件,经常有人会问“CMWAP可以用吗?”,所以笔者撰写了此文,希望本文能为大家解开这个疑惑。

 

  问题1.为什么会有两个接入点?

 
 在网上查阅大量资料后并经过反复的尝试与探索后,笔者对CMWAP和CMNET两种上网方式作出了一下总结:在国际上,通常只有一种GPRS接入方式,
为什么在中国会有CMWAP和CMNET两兄弟呢?(彩信之所以单独配置接入点是因为彩信服务需要连接专用的服务器,在这里不作探讨。)其实,CMWAP
和 CMNET
只是中国移动人为划分的两个GPRS接入方式。前者是为手机WAP上网而设立的,后者则主要是为PC、笔记本电脑、PDA等利用GPRS上网服务。它们在
实现方式上并没有任何差别,但因为定位不同,所以和CMNET相比,CMWAP便有了部分限制,资费上也存在差别。

  问题2.什么是WAP?

 
 WAP只是一种GPRS应用模式,它与GRPS的接入方式是无关的。WAP应用采用的实现方式是“终端+WAP网关+WAP服务器”的模式,不同于一般
Internet的“终端+服务器”的工作模式。主要的目的是通过WAP网关完成WAP-WEB的协议转换以达到节省网络流量和兼容现有WEB应用的目
的。

  WAP网关从技术的角度讲,只是一个提供代理服务的主机,它不一定由网络运营商提供。但据我所知,中国移动GPRS网络目前只有唯
一的一个WAP网关:10.0.0.172,有中国移动提供,用于WAP浏览(HTTP)服务。有一点需要注意,WAP网关和一般意义上的局域网网关是有
差别的,标准的WAP网关仅仅实现了HTTP代理的功能,并未完成路由、NAT等局域网网关的功能。这就决定了它在应用上所受到的限制。

  问题3.中国移动对CMWAP的限制?

 
 为了从应用中区别两者的定位,中国移动对CMWAP作了一定的限制,主要表现在CMWAP接入时只能访问GPRS网络内的IP(10.*.*.*),而
无法通过路由访问Internet。(少数地区的移动网络可能不存在这一限制。)我们用CMWAP浏览Internet上的网页就是通过WAP网关协议或
它提供的HTTP代理服务实现的。

  说到这里,就让笔者自然而然的联想到公司的网络,相信不少工作的朋友都有类似的体会。公司的网络在网
关上不提供路由和NAT,仅仅提供一个可以访问外网的HTTP代理。这样,我们就无法直接使用QQ、MSN等非HTTP协议的应用软件了(好在它们还提供
的有HTTP代理的连接方式),CMWAP也正是。

  问题4.CMWAP和CMNET的适用范围

 
 两种上网连接方式的适用范围才是大家最关心的问题。CMNET拥有完全的Internet访问权,这里就不多说了,主要让我们来看看CMWAP。因为有
了上面提到的限制,CMWAP的适用范围就要看WAP网关所提供的支持了。目前,中国移动的WAP网关对外只提供HTTP代理协议(80和8080端口)
和WAP网关协议(9201端口)。

  因此,只有满足以下两个条件的应用才能在中国移动的CMWAP接入方式下正常工作:

  1. 应用程序的网络请求基于HTTP协议。

  2. 应用程序支持HTTP代理协议或WAP网关协议。

  问题5.如何辨别一个应用程序的网络请求是否基于HTTP协议?

 
 这个问题还真不好回答,要完全做到这一点需要通过拦截应用程序的通信数据包进行分析。这里提供几个简单的方法给广大朋友:从表现上看,如果它的网络请求
是网址(URL)的形式,那么通常是基于HTTP协议的,如Web浏览器;如果它连接的服务器端口是80,那么它可能是基于HTTP协议的。

  问题6.如何区别一个应用程序支持HTTP代理协议还是WAP网关协议呢?

  首先看它的设置中有没有代理服务器的选项,如果有则表示它支持HTTP代理协议。如果没有,则需要按照以下步骤测试:

 
 在GPRS接入点设置的高级设置里去掉代理服务器的设置项:Server Address 和 Server
Port,如果应用程序可以正常工作,那么它是基于WAP网关协议,如Java程序、S60内置的浏览器。如果在此状态下不能正常工作,而恢复GPRS接
入点高级设置中的代理服务器设置后能够正常工作,则应用程序支持HTTP代理协议(代理设置从系统中读取)。如果仍不能正常工作,那么这个应用程序一般来
说是不支持HTTP代理协议或WAP网关协议的。

  这里需要特别说明的是JavaQQ,它有Socket和HTTP两种版本。现在网上流
传的可用于CMWAP的JavaQQ就是基于HTTP协议的。就拿那个JavaQQ 2004来说,启动画面中就明确的写着“KJava QQ
HTTP”。而SIS版的QQ和AgileMessenger(S60的MSN客户端)因为是采用的普通的Socket连接方式,因此无法用于
CMWAP。


  最后笔者总结了一下,CMWAP下可以使用的常见软件的工作方式:

  (1) 手机内置的浏览器:WAP网关协议

  (2) Opera 浏览器:HTTP代理协议(有代理设置)

  (3) Java 程序:WAP网关协议

  (4) AvantGo:HTTP代理协议(有代理设置)

  手机上网现在已经被许多手机用户广泛使用,笔者谨希望这篇文章能对用手机上网的用户有所帮助。