二.(时间紧,以后写)
三.就是今天的大学考试到此结束,哈哈哈。。。以补考来结束我的大学考试,有意思。继续考研了了。。。
凤凰淑女:关于这个话题,看起来好沉重!本来博士群体应该是真正的天之骄子,但现在却弄成了不但是穷人还是"受剥削受压迫者"。一方面是在校读博士的人生活清苦,思想压力和经济压力大,以后的就业压力也大,他们带着美好的心愿,过着清教徒一般的生活,本来指望着读个博士,能出点成果,也让自己的内心有所追求,但是,他们看不到希望,看不到光明,美好的希望在现实面前被打的粉碎。因为导师忙自己的事情,对他们不关心。另一方面,学术界很腐败,导师未必想让学生有所作为。再有,就是钱大气粗的在职、有钱读书者,他们挤占了老师的时间和精力,他们勾兑老师的目的是功利的,他们有钱有手段,这些人对在校读博的青年学生构成很大的压力,老师当然就天平倾斜,哪里还会有时间和精力好好地带在校读博的清贫学生?
所以,假如读博士的人没有很好的心理素质、顽强的拼搏精神、没有钉子一般的自学和钻研精神,那些混饭吃的老师拿不出课题来给你研究和指导你,你自己也放弃努力去混时间,那样情况就是,我们国家的博士人才培养制度就真的完了!
一个急功近利的时代,一些浮躁惟利是图的老师,能培养出好的博士吗?根本是不可能的。
不过我也想问问你们这些读博士的人,当你们在学校起那个心要考硕士和博士的时候,按道理你们已经看到了中国那些所谓的导师的弊病,在本科阶段就知道他们从来不会和你见上面:好的,在国外国内飞来飞去,差的在国内各地飞来飞去,一年有大半年在外面跑,怎么可能指导学生呢?这些情况怎么不引起你们的警惕、还要去考他的博士?你可以不选这样的老师啊!很多优秀人才就是看到导师不敬业,才直接考到外国去读博的,当然,考出去,不但是看到外国的教学机制,还有学费生活费全免,最重要的,是学术风气比国内好,容易出成果.。
很多人问:北大和清华的学生为什么那么多人出国了?听听这些国内读博的学生的心声吧,他们遇到了多么尴尬和苦闷的学术环境、经济压力、心理压力、就业压力,这就是大部分优秀学子出去读书的原因。
夏天,正是学生毕业照象的日子,北大门口有两个穿着理科博士服装的年轻人照相,他们比本科生老,但比本科生孱弱,他们历经沧桑缺乏营养的脸上,能够读出来他们的生活和环境,我从心里佩服他们的毅力,艰苦啊!真的不是一般的人能过的日子,他们能毕业,还是在北大毕业,那真是有值得称赞的超强毅力。
我的熟人最终选择了出国硕博连读,他说国内导师不负责任,看不到人影,天天飘来飘去,半年在国内,半年在国外,对学生无法指导。剩下的助手导师,在国内思想陈旧,对学生困难不了解,不宽容,对学生的基本品质不能掌握,常常发生误会,没有学术气氛。本科受点气呢就算了,读博还这样,那人怎么活?!
到了国外,拿人家的钱读书,还有生活费不少,日子宽余,当然要做很多事情,比如带大一同学做实验什么的。最重要的,是选导师了,导师的确有绝对的权威,因为导师手上有钱为你付出生活费和学费。但你可以自己选择认为合适的导师,这对导师来说,也是很重要的,假如没有学生选他为导师,他第二年大概就无法续聘。有个北京的同学说,他连续三年都没有成果,他的导师耐心的等待,到第四年,他还有事情回了国一趟,导师也没有取消他的学籍,一直给他保留着,第五年,他回到导师那里,如泉涌般的创造发明就出来了,结果出了许多成果。假如没有导师的理解和等待,他是无法完成这些的。后来的学生听到这个例子,当然很愿意选这个老师做自己的导师。
因此,我说中国的博士制度,现在是很混乱的,对博士生培养,导师的心被分成两半:一半需要对付在职在位钱大气粗的那部分不在围墙里的这批所谓的学生,在职混文凭的人厉害,可以给导师带来荣耀,带来经费和课题,老师忙着和他们一起,实惠多多呢!导师需要让他们及时拿到博士这块敲门砖,以便更多的在社会上捞取好处。剩下不多的一半精力才用来对付这些辛苦考进去的在校读博穷学生。一个导师每天有多少精力啊?居然能带三十多个博士?!真是不怕误人子弟啊!
谁都知道在职读博士的比学校围墙内的博士软火,可是导师可不这么看,否则导师就不会连着几个月都不见自己招进来的正规博士生了。这是弊端啊!教育的又一个弊端。我真的想问问,这教育怎么总是走向歧途?正规读书的、凭本事考进去的读博士生,导师不好好培养,却有精力有时间带着其他在职的那些所谓的学生混,那些人又得名又得利,一头工作拿钱,一头还拿个高级文凭,厉害,世界上的好事情都拿给他们弄完了,也不怕闪了腰!
中国要崛起,是要加强教育的!但加强教育,必须加强博士生培养:一.对待在校博士这一批高端人才,国家要控制导师的质量,只有业务能力和思想能力都达到标准的,才能招博士生,没有爱学生的心、没有甘当人梯、培养人才的心,这样的人不该给他带博士生的名额和机会!美国著名的大学,考核导师的条件就有一条:对学生是不是关心?这是一条硬指标,达不到的,不再聘用。当然,对每个有资格招博士的导师,学校和财团该给他核定一定的经费,在经费允许内,让他有能力发给招来的博士生全额奖学金,也就控制了招生的人数。这些博导的资格,是要有硬指标考核的。
另外,对在校博士、和在职在位读博士的学生,要分别管理,老师也要分开,否则就不能保证公平,在校穷学生就会吃亏的。成人教育是一辈子的事情,对成人的教育和对在校生的教育应该分成两部分导师,不要搅在一起。
还有,就是对待国内读博士的学生,包括硕博连读的学生,应该提高他们的待遇,给他们奖学金和生活费,特别是博士生,年龄偏大,又有了家,才300、400一个月生活费,太清贫了,不符合高端人才的鼓励机制。必须给他们适当的费用,否则,是留不住人才在国内读书的。有人说,好点的人才都跑了,我们在为外国培养人才。怎么不是呢,美国对待读博士的学生,一个月相当于人民币2000块生活费,不但养自己,养老婆都够了。所以,我们的教育要出人才,特别是高端人才,必须在奖学金方面大方点。
另外,就是要在生活上思想上多关心这些博士,他们读出来基本上都是国家的栋梁之材,应该好好培养他们的心理素质,使他们能够健康成长。
这次网络讨论,说读博士的都是傻子,还有很多负面的说法,比如说博士找不到工作、(千万别喊我博士!)找不到老婆和嫁不出去等等,我想,新的“读书无用论”说法又卷土重来了,但这次不能怪学生,形成这种社会负面看法的,是社会机制。年轻人知道什么,他们只看现实。而社会必须高屋建瓴,必须看到人才是一个国家的战略储备,从科技兴国,走创新之路等等国家方针来看,对待培养博士的教育方法都有必要改进,一直改到能够吸引和留住最优秀的人才在国内。
一、教你一招:使用IE不能打开新窗口的解决办法
最近发现我的IE不能打开新窗口,具体表现形式是:用鼠标左键点击超链接没有反应,用鼠标右键点击超链接,在弹出的菜单中选择“在新窗口打开”也没有动静。怎么办呢?经过查找试验,终于找到了解决方法:
1、在“开始”菜单中打开“运行”窗口,在其中输入“regsvr32 actxprxy.dll”,然后“确定”,接着会出现一个信息对话框“DllRegisterServer in actxprxy.dll succeeded”,再次点击“确定”。
2、再次打开“运行”窗口,输入“regsvr32 shdocvw.dll”,“确定”后在出现的信息对话框中点击“确定”。
3、重新启动Windows,运行IE,随便打开一个网页,点击一个超链接,你会发现IE又能打开新窗口。再试试用鼠标右键选择“在新窗口打开”,问题解决。
二、MIE2浏览网页时,随时会出现如下故障提示: A Unhandled Exception Occured. Error Code=C0000005
解决方法:换用ie或者卸载MYIE2试验试验~如果信得过“windows优化大师”可以扫描一下错误或者用其所带的“系统医生检测一下”,估计是系统问题或者软件兼容问题。
三、开一个浏览器看网页,CPU使用率就能到100%
解决方法:
1、通俗一些,你的系统是否稍微有些弱或配件搭配不好存在“瓶颈”。反映在内存方面,个人感觉是否开双通道是关键(128M单条跟64x2应该速度差异挺大),另外就是内存大小(单位p4 2.x 128M 硕泰克 vs 家用xp1800+ 256x2 磐正8RDA,后者性能明显优于前者,前者p4机器有你所谓的“短路”现象)。
2、病毒防火墙或网络防火墙导致系统“短路”现象,试将其关闭后观察。
3、长时间使用,系统错误过多,导致“短路”现象,建议用优化大师进行彻底的清理和优化。
4、重装系统,尽量少安装应用软件,此时观察是否有“短路”现象。如有,说明硬件问题。
5、上贴所说再加一条内存开双通道(需主板支持)
6、升级主板coms(不建议采用)
另:关于你所说的“CPU使用率就能到100%”的现象,不知是否看错了?是空闲100%还是使用率100%?如果长时间“100%”,相信你会经常死机的~另外看看是什么导致你cpu使用率100%
四、IE看网页时有的flsah动画不能显示
解决方法:
1、系统禁用了FLSH播放功能,打开即可
2、升级浏览器到6.0以上版本,或干脆修复安装
五、IE非法操作,即将关闭
解决方法:
1、可能是系统冲突,建议修复安装
2、如果修复不可用,高格后安装,如果是刚刚安装的系统出项以上问题,可能是你的98安装盘或备份的系统的有问题,换一个光盘即可解决。
六、alonetree 的经验总结
第一招:在、属性、常规、设置、查看文件中删掉所有插件,通常是3721或FLASH及一些国外色情站点的插件,不能删掉的,在添加删除程序里卸掉,能解决大部分IE不稳定的问题(如非法操作等、窗口自动关闭的-跟3721的广告拦截有一些关系);
第二招:常备98、2K、XP的才安装后的Internet Exploer注册表键值,出现莫名其妙的问题时,直接删掉原来的主键,再导入备份的注册表(不能打开链接或第二层链接的,百试百灵)
第三招:实在没辙,升级到最高版IE6.0SP1
第四招:如果以上几招不足以解决问题,估计系统核心文件受损,在排除病毒影响后,最好格了重装。
一般来说,前二招足以解决80%的问题了,用到第四招不到1%,如果有更好的方法,请帖出来大家共学哦!
其实道理很简单:IE也是一个软件,起作用的就是程序文件夹里的几个文件和注册表里的一些设置,同时调用到系统核心里的一些DLL,这些DLL在2K或XP中受系统保护,一般很少出问题。大家只要对文件或设置对症下药即可
七、WIN2000 SERVER+IE6.0 打开浏览器一会,就会出现错误,提示发送或不发送,不管点击什么浏览器就关闭了。
解决方法:由于IE核心的某些DLL文件在注册表中的相关内容(注册信息)丢失或者出错,一般是因为安装了某些软件引起的,特别是一些设计存在问题的软件。解决步骤如下:
1、开始----运行
2、输入命令regsvr32 actxprxy.dll------确定
3、输入命令regsvr32 shdocvw.dll------确定
4、重启,一般可以解决。
如果还有问题,按上面的方法把以下几个DLL文件同样进行注册:
mshtml.dll,urlmon.dll,msjava.dll,browseui.dll,oleaut32.dll,shell32.dll
5、修复安装或升级到6.0 的浏览器或用其他的浏览器也可以
①对IE 5.0的重装可按以下步骤进行:
第一步:打开“注册表编辑器”,找到[HKEY_LOCAL_ MACHINE\Software\Microsoft\Internet Explorer],单击其下的Version Vector键。
第二步:在右侧窗格中双击IE子键,将原来的“5.0002”改为“4.0”,单击“确定”后退出“注册表编辑器”。
第三步:重启后,就可以重装IE 5.0了。
②IE 6.0的重装有两种方法:
方法1:打开“注册表编辑器”,找到[HKEY_LOCAL_ MACHINE\SOFTWARE\Microsoft\Active Setup\Installed Components\{89820200-ECBD-11cf-8B85-00AA005B4383}],将IsInstalled的DWORD值改为0就可以了。
方法2:放入Windows XP安装盘,在“开始→运行”窗口键入“rundll32.exe setupapi,InstallHinfSection DefaultInstall 132 %windir%\Inf\ie.inf”。
6、针对不同情况,可分别用以下方法关闭IE发送错误报告功能:
①对IE 5.x用户,执行“控制面板→添加或删除程序”,在列表中选择“Internet Explorer Error Reporting”选项,然后单击“更改/删除”按钮,将其从系统中删除。
②对Windows 9x/Me/NT/2000下的IE 6.0用户,则可打开“注册表编辑器”,找到[HKEY_LOCAL_MACHINE\Software \Microsoft\Internet Explorer\Main],在右侧窗格创建名为IEWatsonEnabled的DWORD双字节值,并将其赋值为0。
③对Windows XP的IE 6.0用户,执行“控制面板→系统”,切换到“高级”选项卡,单击“错误报告”按钮,选中“禁用错误报告”选项,并选中“但在发生严重错误时通知我”,最后单击“确定”按钮。
八、使IE浏览器窗口打开后为最大化
IE有记忆功能,即重新开启的窗口默认为前一次关闭的状态,使它最大化,进行如下操作:运行注册表:
开始——运行——regedit,找到
HKEY-CURRENT-USER\software\microsoft\internet explorer\main 子键,在右边窗口中删除“windows_piacement".
HKEY-CURRENT-USER\software\microsoft\internet explorer\desktop\old work-areas子键,在右边删除"old workarearects",关闭编辑器,重新启动计算机,连续辆次最大化(最大化-最小化-最大化)后,关闭浏览器,重新启动浏览器即可。
九、联网状态下,浏览器无法打开某些站点
【故障现象】上网后,在浏览某些站点时遇到各种不同的连接错误。
【故障点评】这种错误一般是由于网站发生故障或者你没有浏览权限所引起。
【故障解决】针对不同的连接错误,IE会给出不同的错误信息提示,比较常见的有以下几个:
①提示信息:404 NOT FOUND这是最为常见的IE错误信息。主要是因为IE不能找到你所要求的网页文件,该文件可能根本不存在或者已经被转移到了其他地方。
②提示信息:403 FORBIDDEN常见于需要注册的网站。一般情况下,可以通过在网上即时注册来解决该问题,但有一些完全“封闭”的网站还是不能访问的。
③提示信息:500 SERVER ERROR通常由于所访问的网页程序设计错误或者数据库错误而引起,你只有等待对方网页纠正错误后再浏览了。
十、怎样修复浏览器
1、设置主页 主页就是你打开浏览器时自动打开的网页,设置方法:
工具\internet选项\常规\主页\可更改主页地址,输入你常去的网址,或在打开网页后,工具\internet选项\常规\主页\可更改主页地址,按"使用当前页"即可。
2、有些恶意网站会更改你的主页,更可恶的是,修改主页按钮变成灰色,即不让你修改。这是因为网站程序修改了你的注册表,你必须修改注册表才能激活该选项,但修改注册表是复杂且危险的操作,一般用户不敢轻易动手,且有些网站还会把你的注册表锁定,这里推荐几个小程序,轻松搞定 (相关软件下载区)
3、第二天,当你打开电脑时,可能那个可恶的网站一开机就自动打开了,你昨天白忙乎。这是因为该网站修改了你的启动程序。好在windows自带了一个小程序帮你修改注册表,方法是:开始\程序\附件\系统工具\系统信息\系统配置实用程序\启动,把含有网址的可疑项前面的"√"去掉,确定,OK了。还有一个方法调用系统配置实用程序:开始\运行\输入"msconfig",确定。
十、如何清除浏览器上设置的分级审查密码
简单点----如果有的话,用兔子和大师也可以做到
复杂点----关于注册表的修改
点击“开始→运行”,输入Regedit.exe,按下“确定”按钮启动注册表编辑器。依次选择“HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Policies”,删除Ratings键值,然后在C:\Windows\System目录下将Ratings.pol文件重命名为其他文件名,重新启动电脑,在下一次使用分级审查时,系统将会提示你输入一个新的密码。
十一、手工快速清除恶意网页灌水
上网最讨厌的莫过于恶意网页,现在越流行越历害,种类越多,今撰文以谢网友支持。
恶意网页的种类:
一、开机画面弹出型,在系统启过程中,弹出“请登陆少女脱衣网站 www.???.com",这类网站不会对系统产生危害,但这话语不登大雅之堂,使许多网友在异性朋友\上司\家长面前,很狼狈,通常重装系统不启任何作用;
二、自动弹出收费 *** 或黄色网站型,你一但登陆过该网站,下次只要打开IE,这类网站就会弹出,无限超链接,你关也关不了,越关越多,越发着紧急,因为这类话语不堪入目,图片下流,打搅了你的正常上网,甚至你无法下载清除恶意网页之类的软件;
三、无止境发问型,你只要打开该网页,就会出现诸如“你吃过了吗?”,你点确定,他又问“你找对象发吗?”,一直问这些无聊的话语;
四、锁死鼠标\键盘\定格画面型,你要是受到危害,就会使你的机子假死。
恶意网页产生原因:
一、邮箱,当你打开一封信件,一开始你并不知道他是内容,打开后就大呼上当;
二、收费电影网,你一但浏览譔网站,又改变注意不要注册,那么你就受骚拢了,下次你要打开IE,他就超链接到此处;
三、QQ,网友恶作剧,你俩聊天时他告诉你一个网站,你打开后就上当了,他在偷偷笑;
四、黄色网站(成年人网站),你要是打开就会出现这种情况;
五、鼠标经过浮动的动画图标,这完全是不自觉的,但也是无赖的。
恶意网页形成原理:
一、在网站原代码之中加入恶意代码,诸如鼠标经过热区,进行超链接;
二、恶意修改注册表,使你的机子为他调配;
三、恶意黑客程序。
恶意网页清除之法:
这里完全是中子教授首创。上述一\二类情况,在该类别发生时,你先不要惊慌,一类情况重装系统不起作用,格式化才行,二类情况重装系统后完全清除,但中子教授不赞成这种无谓的方式,你要先准备一张纸,一支笔,把这些恶意网址全部记下来,然后开始-------运行--------regedit--------编辑-------查找--------你刚才记下的网站,如“www.???.com”,一个一个来,不要心急给大家推荐几个修复浏览器的软件。
修复浏览器的软件
1、IE修复器 3.0 Build 2003.10.01
软件大小:1376KB
软件语言:简体中文
软件类别:国产软件 / 免费版 / 浏览辅助
运行环境:Win9x/WinNT/2000/ME/XP
软件介绍:
是否觉得您的IE浏览器经常在浏览的时候被恶意更改呢?时候觉得束手无策呢?不用怕,IE修复器的发布是你的福音,可以使你随心所欲地尽情在网上冲浪,再也不用怕哪些恶意网页对您爱机中的IE浏览器进行恶意地修改,可以保证到你的爱机的安全。内置工具有IE修复器、IE监测器及快速启动程序,通过这些工具的完美结合,可以令你在网上冲浪更加安心。
下载地址: http://www.86down.com/downinfo/9723.html
2、黄山IE修复专家 7.0
软件大小:4382KB
软件语言:简体中文
软件类别:国产软件 / 免费版 / 浏览辅助
运行环境:Win9x/WinNT/2000/ME/XP
软件介绍:
修复恶意网页破坏的IE ,系统优化.
http://www.superdown.com/soft/6022.htm
3、瑞星杀毒软件2003免A盘免序列号原版光盘 ISO 精简版
http://www.86down.com/downinfo/18261.html
http://www.86down.com/downinfo/6329.html
这个不用多说了,大家都知道了
4、江民的杀毒软件有修复浏览器的功能,大家可以到一下地址去看
http://www.jiangmin.com/
5、瑞星的注册表修复工具: http://it.rising.com.cn/service/technology/RegClean_download.htm
瑞星的在线修复网站:
http://online.rising.com.cn/free/index.htm
6、毒霸的注册表修复工具: http://db.kingsoft.com/download/3/8.shtml
7、推荐几个很好的在线修复:http://www.cw88.net/cw88com/new/fixie.htm
http://cn.zs.yahoo.com/start.htm
http://www.v111.com/wz/ie.htm
总结一下,一个优秀个人网站站长,应该具备的基本技能与素质:
1、至少有1个以上的顶级域名(.com、.cn……均可)。
2、至少运营一个网站一年以上。
3、至少熟悉一种web服务器(IIS、apache、……)。
4、至少熟悉一种操作系统(windows、linux、……)。
5、至少了解一些基本的网页代码和脚本(HTML,javascript,css,……),并且能修改代码。
6、至少熟悉一种以上服务器端网页语言(ASP、ASP.NET、PHP、JSP、……)。
7、至少熟悉一种数据库(ACESS,MSSQL,MYSQL,ORACLE……)。
8、能用PHOTOSHOP、flash等制作一些基本的图片和动画。
9、熟悉SEO,至少能做简单网页优化。
10、对互联网有敏锐的洞察力。
11、了解用户的需求,能站在用户的角度考虑问题。
未完,待续……
还没有追人家呢,就有人说:东哥,听说你在13楼搞定一个,真是,帮忙占位的玩得好的,总会有闲人乱说话了,世界总不能没有这些人,要好好利用这帮人,。。。。。。。纯属炒作,让他们炒作去吧,生活太平淡来点报料岂不更好,哈哈
肯定有问题。。。。 我肯定有有问题吗?。。。。。^_^,还是该改注意一下放肆的生活作风了,呵呵!
它人笑我太疯巅,我笑他人看不穿。。。,人的身体太软弱了,哈哈,还好有坚强的意念来补充,世俗太炎凉了,还好有哲学来指引,实事求是,要客观对待,其实意识也是客观的,否则就会出问题,遭遇失败,这是我的新的突破,总算没有白看马哲学。。。。

介绍个网上听歌的地方:以前可以在www.aboutplayer.com听,现在不行了,http://zjp.hubu.net/aboutplayer/?page=3可以听的,很好的喔。。。。。有歌词。。。

××××××××××××××××给我好好想想自己最想要的。。。××××××××××××××××
看一个国家的国民教育,要看他的公共厕所。
看一个男人的品味,要看他的袜子。
看一个女人是否养尊处优,要看她的手。
看一个人的气血,要看他的头发。
看一个人的心术,要看他的眼神。
看一个人的身价,要看他的对手。
看一个人的底牌.要看他身边的好友。
看一个人的性格,要看他的字写得怎样。
看一个人是否快乐,不要看笑容,要看清晨梦醒时的一刹那表情。
看一个人的胸襟,要看他如何面对失败及被人出卖。
看两个人的关系,要看发生意外时,另一方的紧张程度。
一、扩展你的Smarty
1、准备功夫
PHP代码:--------------------------------------------------------------------------------
function Smarty_function_page ( $params, &$Smarty )
{
$href = '#';
$space = ' ';
$frist = NULL;
$last = NULL;
$page = 5;
extract($params);
if ( !$row || $row <= 1 ) return ' ';
$pages = $row;
$curr_page = $now ? $now : 1;
$offset = 2;
$from = $curr_page - $offset;
$to = $curr_page + $page - $offset - 1;
if ( $page > $pages )
{
$from = 1;
$to = $pages;
}
else
{
if ( $from < 1)
{
$to = $curr_page + 1 - $from;
$from = 1;
if ( ( $to - $from ) < $page && ( $to - $from ) < $pages )
{
$to = $page;
}
}
elseif ( $to > $pages )
{
$from = $curr_page - $pages + $to;
$to = $pages;
if ( ( $to - $from ) < $page && ( $to - $from) < $pages )
{
$from = $pages - $page + 1;
}
}
}
if ( $frist && ( $curr_page - 1 ) >= 1 ) $p['frist'] = '' . $frist . '';
if ( $prev && ( $i = $curr_page - 1 ) >= 1 ) $p['prev'] = '' . $prev . '';
for( $i = $from; $i <= $to; $i ++ )
{
if ( $i == $curr_page )
{
$p[$i] = '[' . $i . ']';
}
else
{
$p[$i] = '' . $i . '';
}
}
if ( $next && ( $i = $curr_page + 1 ) <= $pages ) $p['next'] = '' . $next . '';
if ( $last && ( $curr_page + 1 ) <= $pages ) $p['last'] = '' . $last . '';
return implode( $space, $p );
} // end func
--------------------------------------------------------------------------------
将上面的代码命名为"function.page.php"保存到Smarty的plugins目录里
代码:--------------------------------------------------------------------------------
{page row=10}
{page row=10 now=5}
{page row=10 now=5 href="plugins.php?a=1&b=2&page=" frist="第一页" prev="上一页" next="下一页" last="最后页"}
{page row=10 now=5 href="plugins.php?a=1&b=2&page=" frist="第一页" prev="上一页" next="下一页" last="最后页"}
{page row=10 now=1 href="plugins.php?a=1&b=2&page=" frist="第一页" prev="上一页" next="下一页" last="最后页"}
{page row=10 now=10 href="plugins.php?a=1&b=2&page=" frist="第一页" prev="上一页" next="下一页" last="最后页"}
--------------------------------------------------------------------------------
将上面的代码命名为"plugins.html"保存到Smarty的template目录里
2、测试程序
PHP代码:--------------------------------------------------------------------------------
$Smarty->display( 'plugins.html' );
--------------------------------------------------------------------------------
3、使用说明
我懒得打了,对比一下"plugins.html"的5个{page}用法,以及看看显示出来的效果就明白是什么了
4、插件说明
“《Smarty手册》第十六章.以插件扩展Smarty ”的应用。像中文字符截取之类的都可以以plugins扩展Smarty,Smarty自带的截取不支持中文。
__________________
二、Smarty自动生成静态页面
如果你的文件扩展名为".html"~~~~~嘿嘿,这不就是静态页面了吗?-_-!
至于怎么取得静态的文件名呢?
PHP代码:--------------------------------------------------------------------------------
/**
*
*/
class template extends Smarty
{
/**
*
*/
function template ()
{
$this->Smarty();
} // end func
/**
*
*/
function name ( $tpl_file, $cache_id = null, $compile_id = null )
{
if (!isset($compile_id)) $compile_id = $this->compile_id;
$_auto_id = $this->_get_auto_id( $cache_id, $compile_id );
$_cache_file = $this->_get_auto_filename( $this->cache_dir, $tpl_file, $_auto_id );
return basename( $_cache_file );
} // end func
} // end class
$Smarty = new template;
$file_name = $Smarty->name( 'plugins.html', 'cache_name' );#html文件的名字(不包含路径)
$Smarty->cache_lifetime = -1;#静态文件永不过期
$Smarty->fetch( 'plugins.html', 'cache_name' );#生成静态html文件
这是村里翻译的,实际上Smarty的应用还远不止于此。
One of the unique aspects about Smarty is the template compling. This means Smarty reads the template files and creates PHP scripts from them. Once they are created, they are executed from then on. Therefore there is no costly template file parsing for each request, and each template can take full advantage of PHP compiler cache solutions such as Zend Accelerator (http://www.zend.com) or PHP Accelerator (http://www.php-accelerator.co.uk).
Smarty的特点之一是"模板编译"。意思是Smarty读取模板文件然后用他们创建php脚本。这些脚本创建以后将被执行。因此并没有花费模板文件的语法解析,同时每个模板可以享受到诸如Zend加速器(http://www.zend.com) 或者PHP加速器(http://www.php-accelerator.co.uk)。这样的php编译器高速缓存解决方案。
Some of Smarty's features:
Smaty的一些特点:
It is extremely fast.
非常非常的快!
It is efficient since the PHP parser does the dirty work.
用php分析器干这个苦差事是有效的
No template parsing overhead, only compiles once.
不需要多余的模板语法解析,仅仅是编译一次
It is smart about recompiling only the template files that have changed.
仅对修改过的模板文件进行重新编译
You can make custom functions and custom variable modifiers, so the template language is extremely extensible.
可以编辑'自定义函数'和自定义'变量',因此这种模板语言完全可以扩展
Configurable template delimiter tag syntax, so you can use {}, {{}}, , etc.
可以自行设置模板定界符,所以你可以使用{}, {{}}, , 等等
The if/elseif/else/endif constructs are passed to the PHP parser, so the {if ...} expression syntax can be as simple or as complex as you like.
诸如 if/elseif/else/endif 语句可以被传递到php语法解析器,所以 {if ...} 表达式是简单的或者是复合的,随你喜欢啦
Unlimited nesting of sections, ifs, etc. allowed.
如果允许的话,section之间可以无限嵌套
It is possible to embed PHP code right in your template files, although this may not be needed (nor recommended) since the engine is so customizable.
引擎是可以定制的.可以内嵌php代码到你的模板文件中,虽然这可能并不需要(不推荐)
Built-in caching support
内建缓存支持
Arbitrary template sources
独立模板文件
Custom cache handling functions
可自定义缓存处理函数
Plugin architecture
插件体系结构
键盘在所有的驱动之中最为简单的一种,但它却包含了驱动的基本框架,对以后继续深入学习其他复杂的驱动大有裨益,以下便为你逐步剖析驱动的开发。采用的是查询方式。
一.内核模块的注册和撤销
在加载模块的时候,首先运行的是内核模块的注册函数。它的功能包括内核注册设备以及变量的初始化。
static int head,tail;
int _init Keypad_init(void)
{
int result;
result=register_chrdev(KEY_LED_MAJOR,KEY_LED_NAME,&Keypad_fops);
Keypad_clear();
init_waitqueue_head(&queue);
prink("%s %s initialized.\n",KEY_LED_NAME,KEY_LED_VERSION);//不能用prinf
return 0;
}
module_init(Keypad_init);//加载模块
void _exit Keypad_cleanup(void)
{
del_timer(&timer);
unregister_chrdev(KEY_LED_MAJOR,KEY_LED_NAME);
prink("Keypad driver removed \n");
}
module_exit(Keypad_cleanup);//卸载该模块
二.虚拟文件系统与硬件驱动的接口
static struct file_operations Keypad_fops={
open:Keypad_open,
read:Keypad_read,
poll:Keypad_poll,
fasync:Keypad_fasync,
release:Keypad_release,
};
该接口定义完之后一些便是对这几个具体函数的实现了!现在我们一起进入下一步吧,是不是觉得其实没什么难度的呢?别那么早开心着呢?这几个函数的实现时候,涉及到很多技术,包括内核定时器,等待队列的具体实现(阻塞方式),异步方式的具体实现技巧,循环队列。看到这么多技术你是否感到很兴奋呢?以下本人将以通俗的方式为你讲解,希望你能理解。
三.设备的打开操作接口函数具体实现(Keypad_open)
设备打开一般包括两大操作,一是完成设备的初始化,二是设备引用计数器加1
static int Keypad_open(struct inode *inode,struct file *filp)
{
read_xy();
try_module_get(THIS_MODULE);//此函数为linux 2.6内核增加的,不同于2.4内核,功能是计数器的值加1
return 0;
}
static void read_xy(void)
{
new_data();//获取键值函数
keypad_starttimer();//开启内核定时器,在固定周期时间内获取键盘新的变化
}
以下实现键盘键值获取函数read_xy()
主要是从KEY_CS(对应的读入地址,之前可以根据具体的硬件设备定义,比如#define kEY_CS(*
(volatile unsigned short *)(0xf820000))此处应该根据具体的不同而不同!
将读入的键值存入buf[]缓存中,环形缓冲的写指针是head,读指针是tail,前面已经定义过了
////////////////////////////////键盘事件的数据结构定义/////////////////////////////////
typedef struct{
ulong status;//按键的值
ulong click;//是否有按键按下,1表示有,0表示没有
}KEY_EVENT
static KEY_EVENT cur_data,buf[BUFSIZE];//BUFSIZE为宏定义,用于定义环形缓冲的大小
static void new_data(void)
{
if((KEY_CS & 0xff)!=0xff) //从KEY_CS地址读入数据,若有一个为0则表示有一个按键被按下了(此处硬件电路为低电平有效)
{
switch(KEY_CS & 0xff){
case ~KEY0 & 0xff:
cur_data.status=1;///////1被按下
break;
case ~KEY1 & 0xff:
cur_data.status=2;//2被按下
break;
/////////其他一样添加,懂吗??
}
cur_data.click=1;
}
else if(KEY_CS & 0xff==0xff){
cur_data.click=0;
cur_data.status=0;
}
if(head!=tail){////////循环队列缓冲区的应用在此开始了^_^
int last=head--;
if(last<0)////////若已经到了对首之前,则跳到队尾,以实现循环队列
last=BUFSIZE-1;
}
//////按键信息存入循环队列缓冲区中
buf[head]=cur_data;
if(++head==BUFSIZE)
head=0;
if(head==tail && tail++=BUFSIZE)
tail=0;
if(fasync)
kill_fasync(&fasyc,SIGIO,POLL_IN);
wake_up_interruptible(&queue);
}
接下来我们介绍其他几个文件接口函数的实现
四.先介绍关闭函数keypad_release(),为什么先介绍它呢?道理很简单,应该它比较简单,先让大家做下热身运动,在介绍完这个之后,继续会介绍一个比较复杂的函数.
关闭操作主要实现的是:关闭设备异步通知,设备计数器减1,删除定时器信号中断
static int Keypad_release(struct inode *inode,struct)
{
Keypad_fasync(-1,filp,0);
module_put(THIS_MODULE);
del_timer(&timer);
return 0;
}
五.设备读取操作接口函数实现Keypad_read()
主要作用是从缓冲区读取键值,通过调用get_data()实现,通过copy_to_user()函数将键值复制到用户的数据区中
static ssize_t Keypad_read(struct file *filp,char *buf,ssize_t count,loff_t *l)
{
DECLEARE_WAITQUEUE(wait,current);//声明等待队列,将当前进程加入到等待队列中
KEY_EVENT t;
ulong out_buf[2];
if(head==tail)//当前循环队列中没有数据可以读取
{
if(filp->f_flags & O_NONBLOCK)//假如用户采用的是非堵塞方式读取
return _EAGAIN;
add_wait_queue(&queue,&wait);//将当前进程加入等待队列
current->state=TASK_INTERRUPTIBLE;//设置当前进程的状态
while((head==tail)&&!signal_pending(current))//假若还没有数据到循环队列并且当前进程没有受到信号
{
shedule();//进程调度
current->state=TASK_INTERRUPTIBLE;
}
current->state=TASK_RUNNING;
remove_wait_queue(&queue,&wait);
if(head==tail)
return count;
t=get_data();//调用get_data()函数,得到缓冲区中的数据,下面将给予详细的 介绍
out_buf[0]=t.status;
out_buf[1]=t.click;
copy_to_user(buf,&out_buf,sizeof(out_buf));//将得到的键值拷贝到用户数据区
return count;
}
}
很自然我们就应该要介绍get_data()函数的实现了,该函数的功能就是从我们定义的循环队列缓冲区中读出我们要的键值,所以其实很简单的如果理解循环队列的原理,在此不多加解释,大家应该具备一般的数据结构相关的知识吧
static KEY_EVENT get_data(void)
{
int last=tail
if(++tail==BUFSIZE)
tail=0;
return buf[last];
}
上面如果你看得懂得话,那么可以进入下面的学习了,主要介绍的是内核定时器的使用,利用等待队列实现阻塞型I/O,poll系统调用,异步通知方式,介绍完之后,我将给出一个应用实例,对于有使用过文件操作系统调用的来说,对我们所写的键盘驱动来说,他们基本上是一样的。废话少说,我们马上开始我们精彩的驱动开发!
六.内核定时器的使用
在该驱动中,我们假设对键盘的获取是以0.2s为周期执行。源代码如下
static struct timer_list timer;///////我们定义的定时器,也许你会问timer_list是什么来的,其实一看名称就应该就知道了,而为什么要用到list那么多定时器呢?其实在linux中还有很多相同的定义,比如说信号,我们定义的也是信号集,你可以定义该list是一个元素的,也可以是多个的。所以对于 timer_list就可以这样描述:在未来某一个特定时刻执行某一系列特定任务的功能。下面我们还会给出内核中timer_list的具体描述,
static int Keypad_starttimer(void)
{
init_timer(&timer);//初始化定时器结构
timer.function=Keypad_timer;//超时服务程序
timer.expires=jiffies+20;//当前时刻加0.2s
add_timer(&timer);
return 0;
}
///超时服务程序
static void Keypad_timer(unsigned long data)
{
read_xy();
}
/////////接下来说下timer-list这个数据结构,如果你不感兴趣的话可以跳过,该结构在
include\linux\timer.h中定义
struct timer_list
{
struct list_head entry;
unsigned long expries;
spinlock_t lock;
unsigned long magic;
void (*function)(unsigned long);
unsigner long data;
struct tvec_t_base_s *base;
}
七.利用等待队列实现阻塞型I\O
在用户程序执行读操作的时候有可能尚且没有数据可以读取,为此需要让read操作等待,直到有数据可以读取,这就是阻塞型i\o,阻塞型io可以通过使用进程休眠方法实现。在无数据可以读取的时候,采用等待队列让进程休眠,直到有数据到达的时候才唤醒进程完成数据的读操作。
在本驱动中的read,若循环队列缓冲区中没有数据,则进程进入休眠态,定时器函数每隔0.2s读取键值一次,将按键状态放入缓冲并且适时唤醒进程读取数据。
等待队列的使用流程如下:
1.声明一个等待队列
2.把当前进程加入到等待队列中
3.把进程的状态设置为TASK_INTERRUPTIBLE或TASK_UNINTERRUPTIBLE;
4.调用schedule,以让出cpu
5.检测所需要的资源是否可用,若是,把当前进程从等待队列中删除,否则转3循环
接下来我们在对read中有关等待队列阻塞实现做具体的解释
static ssize_t Keypad_read(struct file *filp,char *buf,ssize_t count,loff_t *l)
{
DECLEARE_WAITQUEUE(wait,current);//声明等待队列,将当前进程加入到等待队列中
KEY_EVENT t;
ulong out_buf[2];
if(head==tail)//当前循环队列中没有数据可以读取
{
if(filp->f_flags & O_NONBLOCK)//假如用户采用的是非堵塞方式读取
return _EAGAIN;
add_wait_queue(&queue,&wait);//将当前进程加入等待队列
current->state=TASK_INTERRUPTIBLE;//设置当前进程的状态
while((head==tail)&&!signal_pending(current))//假若还没有数据到循环队列并且当前进程没有受到信号(该类信号具体来说是未决的休眠)
{
shedule();//进程调度
current->state=TASK_INTERRUPTIBLE;
}
current->state=TASK_RUNNING;//该进程恢复执行
remove_wait_queue(&queue,&wait);//移出等待队列
if(head==tail)
return count;
t=get_data();//调用get_data()函数,得到缓冲区中的数据,下面将给予详细的 介绍
out_buf[0]=t.status;
out_buf[1]=t.click;
copy_to_user(buf,&out_buf,sizeof(out_buf));//将得到的键值拷贝到用户数据区
return count;
}
}
八.poll系统调用操作接口函数
当程序需要进行对多个文件读写时,如果某个文件没有准备好,则系统就会处于读写阻塞的状态,这影响了其他文件的读写,为了避免读写阻塞,一般可以在应用程序中使用poll或者select函数。当poll函数返回时,会给出一个文件是否可读写的标志,应用程序根据不同的标志读写相应的文件,实现非阻塞的读写,poll()函数通过poll系统调用,调用对应设备驱动的poll()接口函数,poll返回不同的标志,告诉主进程文件是否可以读写,这些返回标志存放在include\asm\poll.h中
标志 含义
POLLIN 如果设备无阻塞的读,就返回该值
POLLRDNORM 通常的数据已经准备好,可以读了,就返回
该值。通常的做法是会返回(POLLLIN|POLLRDNORA)
POLLRDBAND 如果可以从设备读出带外数据,就返回该值,它只可在linux内核的某些网络代码中使用,通常不用在设备驱动程序中
POLLPRI 如果可以无阻塞的读取高优先级(带外)数据,就返回该值,返回该值会导致select
报告文件发生异常,以为select八带外数据当作异常处理POLLHUP 当读设备的进程到达文件尾时,驱动程序必须返回该值,依照select的功能描述,调用select的进程被告知进程时可读的。
POLLERR 如果设备发生错误,就返回该值。
POLLOUT 如果设备可以无阻塞地些,就返回该值
POLLWRNORM 设备已经准备好,可以写了,就返回该值。通常地做法是(POLLOUT|POLLNORM)
POLLWRBAND 于POLLRDBAND类似
在本章地驱动程序中,Keypad_poll()函数在缓冲区有新数据时(当head!=tail),返回一个
POLLIN|POLLRDNORM,告诉主进程有新的
九.在设备驱动中实现异步通知
虽然大多数时候阻塞型和非阻塞型操作的组合及poll方法可以有效查询设备是否可以读写,但是如果驱动程序能避免主动的查询,改主动为被动的信号通知触发,则可以提高程序的效率,这也就是异步通知的目的。异步通知向进程发送SIGIO信号,通知访问设备的进程,表示该设备已经准备好IO读写了。
之后就是如何实现异步通知的问题了,要启动异步通知,必须执行两个步骤:首先,须要制定某个作为文件的“属主”。文件属主的进程ID保存在filp- >f_owner中,这可以通过fcntl()系统调用执行F_SETOWN命令设置。此外,用户程序还必须曙色之设备的FASYNC标志,以真正启动异步通知机制。这里的FASYNC标志也使用fcntl()设置。
在完成这两个步骤之后,当新数据到达时就会产生一个SIGNO信号,此信号发送到存放在filp->owner中的进程。
从驱动的角度看,则主要时通过调用两个内核提供的函数来实现就是了。他们分别是:int
fasync_helper()和void kill_fasync();这两个函数定义在:include\linux\fs\fcntl.h
要实现异步,驱动中只要如下编写即可
static struct fasync_struct *fasync;//首先是定义一个结构体
static int Keypad_release(struct inode *inode,struct file *filp)
{
Keypad_fasync(-1,filp,0);//这是一个异步通知
。。。。。。。
}
static int Keypad_fasync(int fd,struct file *filp,int on)
{
int retval;
retval=fasync_helper(fd,filp,on,&fasync);
if(retval<0)
return retval;
return 0;
}
到此为止,键盘驱动已经介绍完了,接下来就介绍下一个利用使用驱动的应用实例了。
以下程序的主体是一个条件循环,每次循环执行一次,就读取一次键值。
1。打开Keypad设备
#define DEV_NAME "/dev/Keypad"
int fb=0;
fb=open(DEV_NAME,O_RNONLY);
if(!fb){
printf("Error:cannot open Keypad device.\n");
exit(1);
}
printf("The Keypad device was opened successfully.\n");
}
2.读取键值
unsigned long keydata[2];
int input=1;
while(input!=0)
{
if(read(fd,(char*)keydata,sizeof(keydata))==-1){
printf("Error reading the keypad data");
close(fb);
exit(2);
}
if(keydata[0]){
switch(keydata[1]){
case 1:printf("KEYPUSED 1");//1键被按下
input=0;////下此循环退出
break;
。。。。。。。。。。。。。。。。。。
}
}
}
3。关闭Keypad设备
close(fb);
printf("Good bye Keypad");
键盘驱动到此介绍完毕!!

C语言的最大特点是:功能强、使用方便灵活。C编译的程序对语法检查并不象其它高级语
言那么严格,这就给编程人员留下“灵活的余地”,但还是由于这个灵活给程序的调试带
来了许多不便,尤其对初学C语言的人来说,经常会出一些连自己都不知道错在哪里的错误
。看着有错的程序,不知该如何改起,本人通过对C的学习,积累了一些C编程时常犯的错
误,写给各位学员以供参考。
1.书写标识符时,忽略了大小写字母的区别。
main()
{
int a=5;
printf("%d",A);
}
编译程序把a和A认为是两个不同的变量名,而显示出错信息。C认为大写字母和小写字母是
两个不同的字符。习惯上,符号常量名用大写,变量名用小写表示,以增加可读性。
2.忽略了变量的类型,进行了不合法的运算。
main()
{
float a,b;
printf("%d",a%b);
}
%是求余运算,得到a/b的整余数。整型变量a和b可以进行求余运算,而实型变量则不允许
进行“求余”运算。
3.将字符常量与字符串常量混淆。
char c;
c="a";
在这里就混淆了字符常量与字符串常量,字符常量是由一对单引号括起来的单个字符,字
符串常量是一对双引号括起来的字符序列。C规定以“\”作字符串结束标志,它是由系统
自动加上的,所以字符串“a”实际上包含两个字符:‘a'和‘\',而把它赋给一个字符变
量是不行的。
4.忽略了“=”与“==”的区别。
在许多高级语言中,用“=”符号作为关系运算符“等于”。如在BASIC程序中
可以写
if (a=3) then …
但C语言中,“=”是赋值运算符,“==”是关系运算符。如:
if (a==3) a=b;
前者是进行比较,a是否和3相等,后者表示如果a和3相等,把b值赋给a。由于习惯问题,
初学者往往会犯这样的错误。
5.忘记加分号。
分号是C语句中不可缺少的一部分,语句末尾必须有分号。
a=1
b=2
编译时,编译程序在“a=1”后面没发现分号,就把下一行“b=2”也作为上一行语句的一
部分,这就会出现语法错误。改错时,有时在被指出有错的一行中\未发现错误,就需要看
一下上一行是否漏掉了分号。
{ z=x+y;
t=z/100;
printf("%f",t);
}
对于复合语句来说,最后一个语句中最后的分号不能忽略不写(这是和PASCAL
不同的)。
6.多加分号。
对于一个复合语句,如:
{ z=x+y;
t=z/100;
printf("%f",t);
};
复合语句的花括号后不应再加分号,否则将会画蛇添足。
又如:
if (a%3==0);
I++;
本是如果3整除a,则I加1。但由于if (a%3==0)后多加了分号,则if语句到此结束,程序将
执行I++语句,不论3是否整除a,I都将自动加1。
再如:
for (I=0;I<5;I++);
{scanf("%d",&x);
printf("%d",x);}
本意是先后输入5个数,每输入一个数后再将它输出。由于for()后多加了一个分号,使循
环体变为空语句,此时只能输入一个数并输出它。
7.输入变量时忘记加地址运算符“&”。
int a,b;
scanf("%d%d",a,b);
这是不合法的。Scanf函数的作用是:按照a、b在内存的地址将a、b的值存进去。“&a”指
a在内存中的地址。
8.输入数据的方式与要求不符。
①scanf("%d%d",&a,&b);
输入时,不能用逗号作两个数据间的分隔符,如下面输入不合法:
3,4
输入数据时,在两个数据之间以一个或多个空格间隔,也可用回车键,跳格键tab。
②scanf("%d,%d",&a,&b);
C规定:如果在“格式控制”字符串中除了格式说明以外还有其它字符,则在输入数据时应
输入与这些字符相同的字符。下面输入是合法的:
3,4
此时不用逗号而用空格或其它字符是不对的。
3 4 3:4
又如:
scanf("a=%d,b=%d",&a,&b);
输入应如以下形式:
a=3,b=4
9.输入字符的格式与要求不一致。
在用“%c”格式输入字符时,“空格字符”和“转义字符”都作为有效字符输入。
scanf("%c%c%c",&c1,&c2,&c3);
如输入a b c
字符“a”送给c1,字符“ ”送给c2,字符“b”送给c3,因为%c只要求读入一个字符,后
面不需要用空格作为两个字符的间隔。
10.输入输出的数据类型与所用格式说明符不一致。
例如,a已定义为整型,b定义为实型
a=3;b=4.5;
printf("%f%d\n",a,b);
编译时不给出出错信息,但运行结果将与原意不符。这种错误尤其需要注意。
11.输入数据时,企图规定精度。
scanf("%7.2f",&a);
这样做是不合法的,输入数据时不能规定精度。
12.switch语句中漏写break语句。
例如:根据考试成绩的等级打印出百分制数段。
switch(grade)
{ case 'A':printf("85~100\n");
case 'B':printf("70~84\n");
case 'C':printf("60~69\n");
case 'D':printf("<60\n");
default:printf("error\n");
由于漏写了break语句,case只起标号的作用,而不起判断作用。因此,当grade值为A时,
printf函数在执行完第一个语句后接着执行第二、三、四、五个printf函数语句。正确写
法应在每个分支后再加上“break;”。例如
case 'A':printf("85~100\n");break;
13.忽视了while和do-while语句在细节上的区别。
(1)main()
{int a=0,I;
scanf("%d",&I);
while(I<=10)
{a=a+I;
I++;
}
printf("%d",a);
}
(2)main()
{int a=0,I;
scanf("%d",&I);
do
{a=a+I;
I++;
}while(I<=10);
printf("%d",a);
}
可以看到,当输入I的值小于或等于10时,二者得到的结果相同。而当I>10时,二者结果就
不同了。因为while循环是先判断后执行,而do-while循环是先执行后判断。对于大于10的
数while循环一次也不执行循环体,而do-while语句则要执行一次循环体。
14.定义数组时误用变量。
int n;
scanf("%d",&n);
int a[n];
数组名后用方括号括起来的是常量表达式,可以包括常量和符号常量。即C不允许对数组的
大小作动态定义。
15.在定义数组时,将定义的“元素个数”误认为是可使的最大下标值。
main()
{static int a[10]={1,2,3,4,5,6,7,8,9,10};
printf("%d",a[10]);
}
C语言规定:定义时用a[10],表示a数组有10个元素。其下标值由0开始,所以数组元素a[
10]是不存在的。
16.在不应加地址运算符&的位置加了地址运算符。
scanf("%s",&str);
C语言编译系统对数组名的处理是:数组名代表该数组的起始地址,且scanf函数中的输入
项是字符数组名,不必要再加地址符&。应改为:scanf("%s",str);
17.同时定义了形参和函数中的局部变量。
int max(x,y)
int x,y,z;
{z=x>y?x:y;
return(z);
}
形参应该在函数体外定义,而局部变量应该在函数体内定义。应改为:
int max(x,y)
int x,y;
{int z;
z=x>y?x:y;
return(z);
}
指针是一个特殊的变量,它里面存储的数值被解释成为内存里的一个地址。 要搞清一个指针需要搞清指针的四方面的内容:指针的类型,指针所指向的 类型,指针的值或者叫指针所指向的内存区,还有指针本身所占据的内存区。让我们分别说明。
先声明几个指针放着做例子:
例一:
(1)int*ptr;
(2)char*ptr;
(3)int**ptr;
(4)int(*ptr)[3];
(5)int*(*ptr)[4];
指针的类型
从语法的角度看,你只要把指针声明语句里的指针名字去掉,剩下的部分就是这个指针的类型。这是指针本身所具有的类型。让我们看看例一中各个指针的类型:
(1)int*ptr;//指针的类型是int*
(2)char*ptr;//指针的类型是char*
(3)int**ptr;//指针的类型是int**
(4)int(*ptr)[3];//指针的类型是int(*)[3]
(5)int*(*ptr)[4];//指针的类型是int*(*)[4]
怎么样?找出指针的类型的方法是不是很简单?
指针所指向的类型
当你通过指针来访问指针所指向的内存区时,指针所指向的类型决定了编译器将把那片内存区里的内容当做什么来看待。
从语法上看,你只须把指针声明语句中的指针名字和名字左边的指针声明符*去掉,剩下的就是指针所指向的类型。例如:
(1)int*ptr;//指针所指向的类型是int
(2)char*ptr;//指针所指向的的类型是char
(3)int**ptr;//指针所指向的的类型是int*
(4)int(*ptr)[3];//指针所指向的的类型是int()[3]
(5)int*(*ptr)[4];//指针所指向的的类型是int*()[4]
在指针的算术运算中,指针所指向的类型有很大的作用。
指针的类型(即指针本身的类型)和指针所指向的类型是两个概念。当你对C越来越熟悉时,你会发现,把与指针搅和在一起的"类型"这个概念分成"指针的类型"和"指针所指向的类型"两个概念,是精通指针的关键点之一。我看了不少书,发现有些写得差的书中,就把指针的这两个概念搅在一起了,所以看起书来前后矛盾,越看越糊涂。
指针的值,或者叫指针所指向的内存区或地址
指针的值是指针本身存储的数值,这个值将被编译器当作一个地址,而不是一个一般的数值。在32位程序里,所有类型的指针的值都是一个32位整数,因为32位程序里内存地址全都是32位长。指针所指向的内存区就是从指针的值所代表的那个内存地址开始,长度为si zeof(指针所指向的类型)的一片内存区。以后,我们说一个指针的值是XX,就相当于说该指针指向了以XX为首地址的一片内存区域;我们说一个指针指向了某块内存区域,就相当于说该指针的值是这块内存区域的首地址。
指针所指向的内存区和指针所指向的类型是两个完全不同的概念。在例一中,指针所指向的类型已经有了,但由于指针还未初始化,所以它所指向的内存区是不存在的,或者说是无意义的。
以后,每遇到一个指针,都应该问问:这个指针的类型是什么?指针指的类型是什么?该指针指向了哪里?
指针本身所占据的内存区
指针本身占了多大的内存?你只要用函数sizeof(指针的类型)测一下就知道了。在32位平台里,指针本身占据了4个字节的长度。
指针本身占据的内存这个概念在判断一个指针表达式是否是左值时很有用。
指针的算术运算
指针可以加上或减去一个整数。指针的这种运算的意义和通常的数值的加减运算的意义是不一样的。例如:
例二:
1、chara[20];
2、int*ptr=a;
...
...
3、ptr++;
在上例中,指针ptr的类型是int*,它指向的类型是int,它被初始化为指向整形变量a。接下来的第3句中,指针ptr被加了1,编译器是这样处理的:它把指针ptr的值加上了sizeof(int),在32位程序中,是被加上了4。由于地址是用字节做单位的,故ptr所指向的地址由原来的变量a的地址向高地址方向增加了4个字节。
由于char类型的长度是一个字节,所以,原来ptr是指向数组a的第0号单元开始的四个字节,此时指向了数组a中从第4号单元开始的四个字节。
我们可以用一个指针和一个循环来遍历一个数组,看例子:
例三:
intarray[20];
int*ptr=array;
...
//此处略去为整型数组赋值的代码。
...
for(i=0;i<20;i++)
{
(*ptr)++;
ptr++;
}
这个例子将整型数组中各个单元的值加1。由于每次循环都将指针ptr加1,所以每次循环都能访问数组的下一个单元。
再看例子:
例四:
1、chara[20];
2、int*ptr=a;
...
...
3、ptr+=5;
在这个例子中,ptr被加上了5,编译器是这样处理的:将指针ptr的值加上5乘sizeof(int),在32位程序中就是加上了5乘4=20。由于地址的单位是字节,故现在的ptr所指向的地址比起加5后的ptr所指向的地址来说,向高地址方向移动了20个字节。在这个例子中,没加5前的ptr指向数组a的第0号单元开始的四个字节,加5后,ptr已经指向了数组a的合法范围之外了。虽然这种情况在应用上会出问题,但在语法上却是可以的。这也体现出了指针的灵活性。
如果上例中,ptr是被减去5,那么处理过程大同小异,只不过ptr的值是被减去5乘sizeof(int),新的ptr指向的地址将比原来的ptr所指向的地址向低地址方向移动了20个字节。
总结一下,一个指针ptrold加上一个整数n后,结果是一个新的指针ptrnew,ptrnew的类型和ptrold的类型相同,ptrnew所指向的类型和ptrold所指向的类型也相同。ptrnew的值将比ptrold的值增加了n乘sizeof(ptrold所指向的类型)个字节。就是说, ptrnew所指向的内存区将比ptrold所指向的内存区向高地址方向移动了n乘sizeof(ptrold所指向的类型)个字节。
一个指针ptrold减去一个整数n后,结果是一个新的指针ptrnew,ptrnew的类型和ptrold的类型相同,ptrnew所指向的类型和 ptrold所指向的类型也相同。ptrnew的值将比ptrold的值减少了n乘sizeof(ptrold所指向的类型)个字节,就是说, ptrnew所指向的内存区将比ptrold所指向的内存区向低地址方向移动了n乘sizeof(ptrold所指向的类型)个字节。
运算符&和*
这里&是取地址运算符,*是...书上叫做"间接运算符"。
&a的运算结果是一个指针,指针的类型是a的类型加个*,指针所指向的类型是a的类型,指针所指向的地址嘛,那就是a的地址。
*p的运算结果就五花八门了。总之*p的结果是p所指向的东西,这个东西有这些特点:它的类型是p指向的类型,它所占用的地址是p所指向的地址。
例五:
inta=12;
intb;
int*p;
int**ptr;
p=&a;
//&a的结果是一个指针,类型是int*,指向的类型是int,指向的地址是a的地址。
*p=24;
//*p的结果,在这里它的类型是int,它所占用的地址是p所指向的地址,显然,*p就是变量a。
ptr=&p;
//&p的结果是个指针,该指针的类型是p的类型加个*,在这里是int **。该指针所指向的类型是p的类型,这里是int*。该指针所指向的地址就是指针p自己的地址。
*ptr=&b;
//*ptr是个指针,&b的结果也是个指针,且这两个指针的类型和所指向的类型是一样的,所以用&b来给*ptr赋值就是毫无问题的了。
**ptr=34;
//*ptr的结果是ptr所指向的东西,在这里是一个指针,对这个指针再做一次*运算,结果就是一个int类型的变量。
指针表达式
一个表达式的最后结果如果是一个指针,那么这个表达式就叫指针表式。
下面是一些指针表达式的例子:
例六:
inta,b;
intarray[10];
int*pa;
pa=&a;//&a是一个指针表达式。
int**ptr=&pa;//&pa也是一个指针表达式。
*ptr=&b;//*ptr和&b都是指针表达式。
pa=array;
pa++;//这也是指针表达式。
例七:
char*arr[20];
char**parr=arr;//如果把arr看作指针的话,arr也是指针表达式
char*str;
str=*parr;//*parr是指针表达式
str=*(parr+1);//*(parr+1)是指针表达式
str=*(parr+2);//*(parr+2)是指针表达式
由于指针表达式的结果是一个指针,所以指针表达式也具有指针所具有的四个要素:指针的类型,指针所指向的类型,指针指向的内存区,指针自身占据的内存。
好了,当一个指针表达式的结果指针已经明确地具有了指针自身占据的内存的话,这个指针表达式就是一个左值,否则就不是一个左值。
在例七中,&a不是一个左值,因为它还没有占据明确的内存。*ptr是一个左值,因为*ptr这个指针已经占据了内存,其实*ptr就是指针pa,既然pa已经在内存中有了自己的位置,那么*ptr当然也有了自己的位置。
数组和指针的关系
数组的数组名其实可以看作一个指针。看下例:
例八:
intarray[10]={0,1,2,3,4,5,6,7,8,9},value;
...
...
value=array[0];//也可写成:value=*array;
value=array[3];//也可写成:value=*(array+3);
value=array[4];//也可写成:value=*(array+4);
上例中,一般而言数组名array代表数组本身,类型是int[10],但如果把array看做指针的话,它指向数组的第0个单元,类型是int*,所指向的类型是数组单元的类型即int。因此*array等于0就一点也不奇怪了。同理,array+3是一个指向数组第3个单元的指针,所以*(array+ 3)等于3。其它依此类推。
例九:
char*str[3]={
"Hello,thisisasample!",
"Hi,goodmorning.",
"Helloworld"
};
chars[80];
strcpy(s,str[0]);//也可写成strcpy(s,*str);
strcpy(s,str[1]);//也可写成strcpy(s,*(str+1));
strcpy(s,str[2]);//也可写成strcpy(s,*(str+2));
上例中,str是一个三单元的数组,该数组的每个单元都是一个指针,这些指针各指向一个字符串。把指针数组名str当作一个指针的话,它指向数组的第0号单元,它的类型是char**,它指向的类型是char*。
*str 也是一个指针,它的类型是char*,它所指向的类型是char,它指向的地址是字符串"Hello,thisisasample!"的第一个字符的地址,即'H'的地址。 str+1也是一个指针,它指向数组的第1号单元,它的类型是char**,它指向的类型是char*。
*(str+1)也是一个指针,它的类型是char*,它所指向的类型是char,它指向 "Hi,goodmorning."的第一个字符'H',等等。
下面总结一下数组的数组名的问题。声明了一个数组TYPEarray[n],则数组名称array就有了两重含义:第一,它代表整个数组,它的类型是TYPE[n];第二,它是一个指针,该指针的类型是TYPE*,该指针指向的类型是TYPE,也就是数组单元的类型,该指针指向的内存区就是数组第0号单元,该指针自己占有单独的内存区,注意它和数组第0号单元占据的内存区是不同的。该指针的值是不能修改的,即类似array++的表达式是错误的。
在不同的表达式中数组名array可以扮演不同的角色。
在表达式sizeof(array)中,数组名array代表数组本身,故这时sizeof函数测出的是整个数组的大小。
在表达式*array中,array扮演的是指针,因此这个表达式的结果就是数组第0号单元的值。sizeof(*array)测出的是数组单元的大小。
表达式array+n(其中n=0,1,2,....。)中,array扮演的是指针,故array+n的结果是一个指针,它的类型是TYPE*,它指向的类型是TYPE,它指向数组第n号单元。故sizeof(array+n)测出的是指针类型的大小。
例十
intarray[10];
int(*ptr)[10];
ptr=&array;:
上例中ptr是一个指针,它的类型是int(*)[10],他指向的类型是int[10] ,我们用整个数组的首地址来初始化它。在语句ptr=&array中,array代表数组本身。
本节中提到了函数sizeof(),那么我来问一问,sizeof(指针名称)测出的究竟是指针自身类型的大小呢还是指针所指向的类型的大小?答案是前者。例如:
int(*ptr)[10];
则在32位程序中,有:
sizeof(int(*)[10])==4
sizeof(int[10])==40
sizeof(ptr)==4
实际上,sizeof(对象)测出的都是对象自身的类型的大小,而不是别的什么类型的大小。
指针和结构类型的关系
可以声明一个指向结构类型对象的指针。
例十一:
structMyStruct
{
inta;
intb;
intc;
}
MyStructss={20,30,40};
//声明了结构对象ss,并把ss的三个成员初始化为20,30和40。
MyStruct*ptr=&ss;
//声明了一个指向结构对象ss的指针。它的类型是MyStruct*,它指向的类型是MyStruct。
int*pstr=(int*)&ss;
//声明了一个指向结构对象ss的指针。但是它的类型和它指向的类型和ptr是不同的。
请问怎样通过指针ptr来访问ss的三个成员变量?
答案:
ptr->a;
ptr->b;
ptr->c;
又请问怎样通过指针pstr来访问ss的三个成员变量?
答案:
*pstr;//访问了ss的成员a。
*(pstr+1);//访问了ss的成员b。
*(pstr+2)//访问了ss的成员c。
虽然我在我的MSVC++6.0上调式过上述代码,但是要知道,这样使用pstr来访问结构成员是不正规的,为了说明为什么不正规,让我们看看怎样通过指针来访问数组的各个单元:
例十二:
intarray[3]={35,56,37};
int*pa=array;
通过指针pa访问数组array的三个单元的方法是:
*pa;//访问了第0号单元
*(pa+1);//访问了第1号单元
*(pa+2);//访问了第2号单元
从格式上看倒是与通过指针访问结构成员的不正规方法的格式一样。
所有的C/C++编译器在排列数组的单元时,总是把各个数组单元存放在连续的存储区里,单元和单元之间没有空隙。但在存放结构对象的各个成员时,在某种编译环境下,可能会需要字对齐或双字对齐或者是别的什么对齐,需要在相邻两个成员之间加若干个"填充字节",这就导致各个成员之间可能会有若干个字节的空隙。
所以,在例十二中,即使*pstr访问到了结构对象ss的第一个成员变量a,也不能保证*(pstr+1)就一定能访问到结构成员 b。因为成员a和成员b之间可能会有若干填充字节,说不定*(pstr+1)就正好访问到了这些填充字节呢。这也证明了指针的灵活性。要是你的目的就是想看看各个结构成员之间到底有没有填充字节,嘿,这倒是个不错的方法。
过指针访问结构成员的正确方法应该是象例十二中使用指针ptr的方法。
指针和函数的关系
可以把一个指针声明成为一个指向函数的指针。intfun1(char*,int);
int(*pfun1)(char*,int);
pfun1=fun1;
....
....
inta=(*pfun1)("abcdefg",7);//通过函数指针调用函数。
可以把指针作为函数的形参。在函数调用语句中,可以用指针表达式来作为实参。
例十三:
intfun(char*);
inta;
charstr[]="abcdefghijklmn";
a=fun(str);
...
...
intfun(char*s)
{
intnum=0;
for(inti=0;i{
num+=*s;s++;
}
returnnum;
}
这个例子中的函数fun统计一个字符串中各个字符的ASCII码值之和。前面说了,数组的名字也是一个指针。在函数调用中,当把str作为实参传递给形参s后,实际是把str的值传递给了s,s所指向的地址就和str所指向的地址一致,但是str和s各自占用各自的存储空间。在函数体内对s进行自加1运算,并不意味着同时对str进行了自加1运算。
指针类型转换
当我们初始化一个指针或给一个指针赋值时,赋值号的左边是一个指针,赋值号的右边是一个指针表达式。在我们前面所举的例子中,绝大多数情况下,指针的类型和指针表达式的类型是一样的,指针所指向的类型和指针表达式所指向的类型是一样的。
例十四:
1、floatf=12.3;
2、float*fptr=&f;
3、int*p;
在上面的例子中,假如我们想让指针p指向实数f,应该怎么搞?是用下面的语句吗?
p=&f;
不对。因为指针p的类型是int*,它指向的类型是int。表达式&f的结果是一个指针,指针的类型是 float*,它指向的类型是float。两者不一致,直接赋值的方法是不行的。至少在我的MSVC++6.0上,对指针的赋值语句要求赋值号两边的类型一致,所指向的类型也一致,其它的编译器上我没试过,大家可以试试。为了实现我们的目的,需要进行"强制类型转换":
p=(int*)&f;
如果有一个指针p,我们需要把它的类型和所指向的类型改为TYEP*TYPE, 那么语法格式是:
(TYPE*)p;
这样强制类型转换的结果是一个新指针,该新指针的类型是TYPE*,它指向的类型是TYPE,它指向的地址就是原指针指向的地址。而原来的指针p的一切属性都没有被修改。
一个函数如果使用了指针作为形参,那么在函数调用语句的实参和形参的结合过程中,也会发生指针类型的转换。
例十五:
voidfun(char*);
inta=125,b;
fun((char*)&a);
...
...
voidfun(char*s)
{
charc;
c=*(s+3);*(s+3)=*(s+0);*(s+0)=c;
c=*(s+2);*(s+2)=*(s+1);*(s+1)=c;
}
}
注意这是一个32位程序,故int类型占了四个字节,char类型占一个字节。函数fun的作用是把一个整数的四个字节的顺序来个颠倒。注意到了吗?在函数调用语句中,实参&a的结果是一个指针,它的类型是int*,它指向的类型是int。形参这个指针的类型是char*,它指向的类型是char。这样,在实参和形参的结合过程中,我们必须进行一次从int*类型到char*类型的转换。结合这个例子,我们可以这样来想象编译器进行转换的过程:编译器先构造一个临时指针char*temp,然后执行temp=(char*)&a,最后再把temp的值传递给s。所以最后的结果是:s的类型是char*,它指向的类型是char,它指向的地址就是a的首地址。
我们已经知道,指针的值就是指针指向的地址,在32位程序中,指针的值其实是一个32位整数。那可不可以把一个整数当作指针的值直接赋给指针呢?就象下面的语句:
unsignedinta;
TYPE*ptr;//TYPE是int,char或结构类型等等类型。
...
...
a=20345686;
ptr=20345686;//我们的目的是要使指针ptr指向地址20345686(十进制
)
ptr=a;//我们的目的是要使指针ptr指向地址20345686(十进制)
编译一下吧。结果发现后面两条语句全是错的。那么我们的目的就不能达到了吗?不,还有办法:
unsignedinta;
TYPE*ptr;//TYPE是int,char或结构类型等等类型。
...
...
a=某个数,这个数必须代表一个合法的地址;
ptr=(TYPE*)a;//呵呵,这就可以了。
严格说来这里的(TYPE*)和指针类型转换中的(TYPE*)还不一样。这里的(TYPE*)的意思是把无符号整数a的值当作一个地址来看待。上面强调了a的值必须代表一个合法的地址,否则的话,在你使用ptr的时候,就会出现非法操作错误。
想想能不能反过来,把指针指向的地址即指针的值当作一个整数取出来。完 全可以。下面的例子演示了把一个指针的值当作一个整数取出来,然后再把这个整数当作一个地址赋给一个指针:
例十六:
inta=123,b;
int*ptr=&a;
char*str;
b=(int)ptr;//把指针ptr的值当作一个整数取出来。
str=(char*)b;//把这个整数的值当作一个地址赋给指针str。
现在我们已经知道了,可以把指针的值当作一个整数取出来,也可以把一个整数值当作地址赋给一个指针。
指针的安全问题
看下面的例子:
例十七:
chars='a';
int*ptr;
ptr=(int*)&s;
*ptr=1298;
指针ptr是一个int*类型的指针,它指向的类型是int。它指向的地址就是s的首地址。在32位程序中,s占一个字节,int类型占四个字节。最后一条语句不但改变了s所占的一个字节,还把和s相临的高地址方向的三个字节也改变了。这三个字节是干什么的?只有编译程序知道,而写程序的人是不太可能知道的。也许这三个字节里存储了非常重要的数据,也许这三个字节里正好是程序的一条代码,而由于你对指针的马虎应用,这三个字节的值被改变了!这会造成崩溃性的错误。
让我们再来看一例:
例十八:
1、chara;
2、int*ptr=&a;
...
...
3、ptr++;
4、*ptr=115;
该例子完全可以通过编译,并能执行。但是看到没有?第3句对指针ptr进行自加1运算后,ptr指向了和整形变量a相邻的高地址方向的一块存储区。这块存储区里是什么?我们不知道。有可能它是一个非常重要的数据,甚至可能是一条代码。而第4句竟然往这片存储区里写入一个数据!这是严重的错误。所以在使用指针时,程序员心里必须非常清楚:我的指针究竟指向了哪里。在用指针访问数组的时候,也要注意不要超出数组的低端和高端界限,否则也会造成类似的错误。
在指针的强制类型转换:ptr1=(TYPE*)ptr2中,如果sizeof(ptr2的类型)大于sizeof(ptr1的类型),那么在使用指针ptr1来访问ptr2所指向的存储区时是安全的。如果sizeof(ptr2的类型)小于sizeof(ptr1的类型),那么在使用指针 ptr1来访问ptr2所指向的存储区时是不安全的。至于为什么,读者结合例十七来想一想,应该会明白的。