时空主站

嗨,我是时空,一名来自中国的开发者。

0%

这个问题是由于Mysql 8.0的认证方式不是mysql_native_password,需要手动修改

方法一

修改需要登录的用户

ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'YourPassword';
FLUSH PRIVILEGES;

方法二

全局修改,将默认的身份认证插件改为mysql_native_password。

vim /etc/my.cnf

在[mysqld]中添加

default_authentication_plugin=mysql_native_password

重启MySQL

service mysqld restart

最近快播的段子都快传疯了。很多网友也在那里分析快播有没有罪,辩护有没有问题之类的。其实结果对我来说一点都不重要。快播有没有罪,这个留给法庭审判。我今天要说的是庭审所暴露出来的一些问题,一些我日常中经常看到的问题。

我们先看两个段子(其实基本是庭审里的事实):

审判长问:文件加了密,你为什么不解密呢?

张克东:如果达不到一定的码率,快播软件就会启动缓存服务器开始加速,达到了码率,就会自动断开。
法官:软件它为什么会知道?它是机器人么?

也许有些人看到段子1,会喷国家审查啊什么的。其实这不是审查,而是无知。审查一定是懂行的,无知才会要人解密。至于段子2,也有类似的问题——码率的衡量问题在大多数程序员,甚至包括懂一点技术的用户那里都不是秘密。问“软件为什么知道”,实际上非常无知。

甚至同样的问题,也存在于某些网友的评论中。例如这篇。我引用这个评论段落:

我看到不少网友以此取笑法官如何外行。其实法官非常聪明,不管张克东如何回复,都回避不开“软件如何知道”,也就是系统如何识别片源问题,只要这个回答了,你快播就摆脱不了你”清楚”播放哪些视频的问题。

如果懂一点技术的话,应该明白一点。对软件而言,码率一定是知道的。而内容,则未必。或者换个对不了解技术的同学们更友好一点说法。对浏览器而言,网页上的图片的大小,格式,都是知道的对吧?浏览器也可以用来浏览黄色网站对吧?那问题也一样,软件它为什么会知道?

红衣主教,是不是该轮到你出庭聊一聊了?

同样,所有聚焦点在“软件应当可以监管浏览内容”的人,同时也面临这么个问题,浏览器是否可以监管黄色网页?作为处理数据流的软件,肯定是了解被处理数据的格式,大小,码率等参数的。那么处理软件是否应当对被处理的数据的内容合法性负责呢?如果是的话,那毫无疑问,所有黄色信息都必然经过了Intel和AMD两家生产的CPU,而且绝大多数也经过了Windows。

实际上,整个庭审中通篇都是法官和公诉人各种技术外行。例如用IP地址标识服务器,硬盘大小标识硬盘等等。如果在国外正常庭审,这证据其实就算废掉了。因为被告可以当场演示给你看改IP地址,你连服务器是不是他的都无法证明,还怎么构成证据链呢?当然,在中国来说,最后是否取信还是合议庭说了算的。

对于程序员来说,这个故事是不是很熟悉?是不是觉得和产品经理打交道很像?一样是对技术一无所知,却对一无所知不以为耻。我碰到过一个产品经理,自以为提了一个很犀利的需求——为什么我们不通过软件自动算出地址,还要客户自己填?

我听到这个问题的时候,估计表情和听到“为什么不解密”时的表情是一样的。心里一万头草泥马奔腾而过——合着不是你实现啊。尼马我怎么算?IP地址只能知道大致地区,还不一定准。GPS当时还不是每个浏览器都支持,就算支持也只能算出坐标,算不出地址。

很多时候,产品上的一点小小的改变,往往是技术上的翻天覆地。不信你考虑一个论坛系统,如果做到像腾讯那么大流量的时候会是什么样一个样子?再考虑一个交易系统,在同样一个量下是什么样子?前者应该还能做,后者应该已经没法弄了吧。为什么同样是个网站,同样的量,一个没事,一个挂了?

因为论坛系统是典型的无事务低写高读系统,因此可以使用分库分表,多层缓存的方法来增强性能。而交易系统是典型的事务,没法用这个手段。所以交易系统在海量压力下的技术难度,和论坛系统在海量压力下的技术难度,其水平和重点根本不是一类的。从这个意义上说,淘宝双11,很不容易。

而这种技术难度,产品经理是不知道的。所以他搞不好会给一个超大流量论坛提出一个很实在的需求——咱们弄一个内部交易系统吧,应该会变成不错的盈利点。呵呵。这个系统要么做出来会有各种各样的事务问题,要不然这个系统大量使用的时候,整个系统(做的好的话应该不会波及论坛)就会慢到无法接受。

当然,现实中一般是没有这么大论坛的。京东实打实在事务性能上吃过亏没错,但是那时候京东已经多大体量了?一个论坛,要引流过去那么大体量,自身得多大?但是这个例子本身说明技术难题的非直观性和在下判断时的必要性。如果你不了解这个技术本身会引发什么问题,你就无法得到合理的结论。

作为产品经理,或者要和技术打交道的一切人士。不懂技术并不可耻,可耻的是不懂技术又无视技术。既不去学,也不去问。装作技术不会对他产生影响,装作不懂技术并不影响他决策的权威性。

与此形成对比的,是微软垄断诉讼案中法官的表现。那个案子里,微软律师强调的是IE内核和windows系统的严密整合,导致两者几乎无法拆分。从软件架构角度来说,这没错。但是法官很明白里面的门槛——保留IE内核不代表保留IE。鼠标一点,IE直接从系统中干掉。虽然内核还在系统上,但是不额外安装IE的话,无法使用浏览器。如果没有后来的一些事的话,估计微软就难逃一劫了。

话题拉回到快播庭审的事情上来。快播有没有问题?排除色情非罪化(我支持内容分级审查和色情非罪化),我觉得是有的。就好像我们说**的时候,一般都会想到约炮。(由于某些因素,我不能写出这两个字来。但是如果你这两个字和我想的不一样,那你的问题就大了)如果快播没有放任色情传播的话,是很难做到这么大的,更不可能做到我们一说找XX内容就想到快播。这不能说因为检方派了几个猪队友,所以快播干净了。

但是这个问题是不是罪?这不好说。定罪这个事情是个专业性很强的活,我的法律知识不足以下这个判断。但是个人意见,我觉得应该无罪,至少不能拿缓存服务器上的色情信息来定罪。如果缓存服务器上有色情内容就可以定罪的话,那铁通小区宽带就可以直接去转型了。因为他们为了减少对电信的依赖,降低流量结算费。在小区里都是装了缓存服务器的,而且是强制透明缓存,想不做都不行。小区里有没有人看色情网站?他们为什么不人工看?

看,这又是一个不懂技术就不知道我在说什么的问题。对用户来说,可能铁通的感觉就是“不好用”。技术人员才知道,电信和铁通的流量结算价格问题,还有铁通(以及各种小ISP)的透明代理的存在。知道这个透明代理和快播的缓存是不是在技术上是等效的。

还有解密,可行不可行?对于快播来说,要解密应该是可以的。但是对于百度网盘来说,连解密都不行。我把我的一堆生活照在百度网盘上备了个份。为求安全,我用GPG做了加密,然后再用SHA256。我自己倒是问心无愧,但是对百度来说,里面到底是不是色情内容?我觉得百度要是还能解开,不妨把技术卖给CIA。

转自Shell Xu

1986年11月8日,有个叫Aaron Swartz的人在美国芝加哥伊利诺伊州出生。因为他父母创办了一个软件公司,所以,Aaron在3岁的时候就接触到了电脑,然后就着迷了。

我们先通过Aaron Swartz 的青少年时期来看一下他是怎么样的一个天才:

12岁的时候Aaron就创建了一个类似于Wikipedia式的网站(那时还没有Wikipedia),13岁的时候,Aaron赢得为年轻人而设,创作教育及协同非商业网站的ArsDigita Prize比赛首名。 奖品包括参观麻省理工学院及与网际网路界的知名人士见会。

14岁的时候,他就成为了RSS1.0的开发组的一员。(后来,他和 John Gruber一起开发了Markdown)

15岁的时候,进入W3C的 RDF 核心工作组,并写了RFC3870——这个文档描述了一个新的media type – “RDF/XML“,用于定义互联网上的“语义网络

17岁进入斯坦福大学,1年半后,18岁的时候因为受不了教条式的教育缀学,并通过Y Combinator公司的夏季创办人计划成立Infogami软件公司,在那里,他设想了一个Wiki平台来实现他的Internet Open Library——一个开放的网络图书馆。并写了著名的web.py 开发框架。但他觉得自己太年轻,还要有一个合伙人,于是Y Combinator建议他和Reddit合并。于是他在19岁的时候成了Reddit的创始人

虽然Reddit不挣钱,但是相当火,当他20岁的时候(2006年10月),他们把Reddit卖给了Condé Nast出版社,据说挣到了百万美金。然后,他去了这家出版社工作,受不了办公室的那种工作环境,2007年1月离职。

但是,你能想得到这么天才的一个人,于2013年1月11日自杀了么?那年他才26岁。

从前面Aaron的经历我们可以看到,他是一个特别喜欢Wiki的人,也是非常喜欢开放的人,但并不喜欢那些有CopyRight的东西,也也不喜欢那些循规蹈矩的东西,他喜欢质疑,他喜欢打破常规,他用生命坚持着互联网真正的开放精神。但是这样一来,必然会和守旧的世界相冲突。

他在YC搞的那个Internet Open Library(互联网开放图书馆)的项目,他就想把那些没有Copyright的书籍和学术期刊放在网上让全世界的人免费查阅。他就认为固体的图书馆遮蔽了知识的传播,互联网理应成为连接书籍,读者,作者,纸张与思想的最好载体,他非常痛恨任何一家巨型的机构独吞所有书籍的做法。他想把Public Access 变成 Public Domain。在他的青少年时期,他就在不懈地和一切限制信息自由交换和自由共享的做法做斗争。这是他认为的互联网精神,他同时也觉得这和美国民主自由的宪法的精神是一致的。

其中有一个例子是这样的,美国法院行政办公室有一个叫 PACER(Public Access to Court Electronic Records) 的政府服务。这个服务会把法庭记录的文件放在网上,如果你要看的话,一页要付费8美分(注意是每页,不是每个文档,美国政府说这只是成本式的收费),这个事他非常不能理解,他觉得这些文件本来就属于公众,没有CopyRight,为什么属于公众的东西还要收费。PACER这个服务每年可以为政府带来1.2亿美金的收入。

于是Aaron在2008年9月4日到20日,他22岁的时候,他用Perl在AWS上写了一个程序,从PACER上下载了270万的文档(2000万页,纽约时报里说他下载大约是总量的20%,但是也有人不到总量的1%)。于是FBI对他调查了两个多月,但最终没有对他起诉。(今天,PACER还在收费,不过你可以使用一个叫RECAP的Firefox插件来免费浏览当年Aaron下载的相关的法律文档)

2008年同年,Aaron创建了Watchdog.net – “the good government site with teeth” 专门用来收集和呈现和政客相关的数据(这个网站访问不到了,不过你可以在Aaron的blog上看一下他的想法)。然后,他还起草了*Guerrilla Open Access Manifesto*(中文版) 下面是节选

信息就是能源。但就像所有能源一样,有些人只想占为己有。世界上所有的科学和文化遗产,已在书籍和期刊上发布了数个世纪,正渐渐地被少数私有的公司数字化并上锁。想要阅读那些有着最著名研究成果的论文?你必须支付给如 Reed Elsevier 这样的出版商大把钱。

…… ……

我们要夺回信息,无论它们被存在何处,制作我们的副本并和全世界分享。我们要取到版权到期的东西并将它们归档,我们要买下秘密的资料库并将它们放到网上。我们要下载科学期刊并将它们上传到文件分享网络。我们要为游击队开放访问而战。

只要全世界有足够多的我们,那就不仅是传达了一个反对知识私有化的强有力信号,我们还将让它成为过去。你愿意和我们一起吗?

亚伦·斯沃茨 (Aaron Swartz) 2008 年 7 月,意大利 Eremo

Aaron觉得那些对人类有价值的科学和文化遗产属于全人类,美国大学每年会向那些出版学术期刊、论文的机构(比如 ISI,Jstor)支付许可费用,许可费用极高,他觉得这是这个时代的悲剧。于是完美主义的他产生了一种责任感。

2009年,他成立了Progressive Change Campaign Committee(进步改变运动委员会),2010年,他又创建了 Demand Progress (求进会)——利用互联网来组织群众与议会和政府对话。

也因为Aaron并不理解政府和这个时代的这些荒唐的行为,于是他开始学习各种政治上的东西去寻求突破,这让他在2010年到2011年,在哈佛大学Edmond J. Safra研究实验室以Lab Fellow的身份主导到了“制度腐败”课题的研究。也因为这个身份,Aaron在MIT做访问学者的时候有 JSTOR的帐号可以通过MIT的网络访问大量的学术期刊。

于是,他把他的laptop放到了地下室网络交换机的机房中,直接插上网线,然后全天后地下载那些JSTOR的学术期刊。(他利用了这些学术期刊的URL链接中的规律来下载所有的期刊),一开始JSTOR把他的帐号和IP封了,并报告给了警,美国的国家安全警察找到了那间楼道里的机房,然后让JSTOR不禁止他访问,并在那间机房里安了摄像头,钓鱼执法。然后等Aaron去换硬盘时录好像,2011年1月6日就把他给抓了。

那年Aaron才24岁。2011年7月11日,检查官以通信欺诈、计算机欺诈、非法获得信息,以及破坏被保护的罪名电脑来起诉他。可能会受到35年以上的牢狱之灾。这是相当重的罪名。你能想像得到为什么罪名会这么重吗?

事后,JSTOR发声明,说他们并不想起诉Aaron,起诉Aaron的是政府行为,而MIT方面虽然也放弃起诉,并也发表了相关的说明——保持中立。保持中立让MIT基本上名誉扫地,因为这种保持中立的行为违背于MIT一贯鼓吹的黑客文化,MIT成了千夫之指。

当然,美国政府的检查官坚持以重罪起诉他。当时,放在Aaron前有两条路:1)认罪,承认犯下重罪,35年的判决会变成3个月入狱+1年的居家监禁(不得使用电脑),2)不认罪,那就有可能接受35监禁年的最坏结果。Aaron选择了后者,而他的女友则选择了认罪。他的第一任女友后来非常的悔恨,面对国家机器,个体太渺小了。

在起诉期间,大家是否还记得美国那个臭名昭著的SOPA( Stop Online Piracy Act)法案?Aaron通过他的 Demand Progress 把民众们网聚起来,和政府做斗争,最终导致了整个社会都在反对SOPA,也导致了那些议员纷纷改变自己的想法,并导致了白宫最终放弃了这个法案。这是一次民主的胜利,与Aaron有密切的相关。(相信大家都还记得那时美国各大网站都在反对这个网络审查制度)

斯沃茨在2012年反对禁止网络盗版法案(SOPA)的抗议活动上发言 斯沃茨在2012年反对禁止网络盗版法案(SOPA)的抗议活动上发言

而在次年2012年9月,政府对Aaron进行了更为严厉的起诉,新加入了另外9条起诉,如果成立,Aaron最多获刑50年外加100万美金的罚款。同样,检察官给出了优惠条件,只要Aaron认罪,那就只起诉他6个月的监禁。Aaron再次拒绝。

看到这里,你觉得下载一些期刊,也没有挣钱,为什么要判他这么重呢?这后面有什么故事呢?这是不是更像是一种政治迫害呢(这段时间,好像这些消息并没有进入中国,我们的大多数人依然在使用百度在墙内活得很滋润,另外,这个事在美国那边的IT 圈闹得很大,但似乎也不见各个IT圈的老大们有没有什么表态)

不过,可以肯定的是,美国政府受够了像阿桑奇这样的人了,而Aaron让美国政府更为害怕在有规模有组织的事,所以一定少不了相关的政治迫害,天下政府一般黑。

之后,2013年1月11日,Aaron自杀了。大家觉得他是因为来自美国政府的长期恐吓的压力和以及长期的抑郁(理想主义者可能都会有或多或少的抑郁证)

这就是Aaron Swartz传奇的一生。他用他的生命捍卫了互联网的开放和自由。

87d31fea0996abbedb297c70b8b0b945_b

互联网之父,Tim Berners-Lee,在2012伦敦奥运会上的网络环节我们都见过这个人。世界上第一个web网站是1991年8月6日在CERN内的NeXT服务器上运行(今天这个网站依然可以访问:链接),Tim并被没有用这个发明挣钱,而是无偿地把WWW的构想和设计推广给了全世界。《时代》周刊评论他的时候用了这样的一条话:“与所有的推动人类进程的发明不同,这是一件纯粹个人的劳动成果”。

而Aaron最崇拜的人就是Tim,Tim也是Aaron的精神导师。

Aaron死了以后,Aaron朋友和合作者,哈佛大学法学院教授Laurence Lessig,回忆说,他当年和仅15岁的Aaron 有过一次谈话。Aaron问他:“您刚才讲到网络审查和管制的这些弊病,那您有没有什么实际的方案来解决这些问题呢?”Lessig有点尴尬地说:“没有。我是个学者,我只负责做研究,解决问题不关我的事儿。”Aaron接着问:“您是个学者,所以解决问题不关你的事儿。那,您作为一个公民,又该如何呢?”

有个男孩叫 Jack Andraka,来自巴尔的摩,14岁,阅读了 Aaron 自杀前推广的JSTOR 的免费学术论文,想出了一种提早检测胰腺癌的方法(一般胰腺癌被查出的时候就是你死的时候。)以此,他成功去了约翰霍普金斯大学做研究。Jack说——

“我之所以上了新闻,是因为我们的实验成功了,而这就是为什么 Aaron 做的事有那么重要……这个宇宙中的真理不是只有那些政策制定者曾经弄清楚过的,比如应该限速多少,它还包括那些能让你的孩子,不会因胰腺癌而死的研究。如果没有访问阅读权,那个能解决你的问题的人,可能就永远找不到答案。”

强烈推荐纪录片——《互联网之子

Aaron说的一句话让我挺有感触的——

相信你应该真的每时每刻都问自己,现在这世界有什么最重要的事是我能参与去做的?

如果你没在做那最重要的事,那又是为什么?

aaron_swartz__freedom_fighter_by_caq_qoq-d5rzbi8

延伸阅读偷了世界的程序员

(全文完)

来源:**酷 壳 – CoolShell**

很多朋友问我为什么不在微信公众号上写文章。我都没有直接回答,老实说,我也是扭扭捏捏的,才去开了个个人的微信的公众号,而且还只能搜索我博客这边的文章,我承认现在的阅读都在移动端,而且微信的公众号是国内移动端的文章流量及分享的入口,但是我还是更愿意使用blog这样的方式分享文章,最多也是在blog这边写好文章后,再去微信公众号那边贴一下。这个原因,不是因为我是一个老顽固,有习惯思维,而是,我不觉得微信公众号是一个好的信息传播和交流的平台。

我下面的言论仅仅代表我的个人观点,我不想强加给别人,我只是想说明一下为什么我不把我的blog迁移到微信公众号上。

首先,互联网是开放和共享的,不是封闭的。信息的传播更是需要开放的,大家可以看看互联网之子

  • 我希望我的文章能够被rss feed到各种阅读器中。
  • 我希望我的文章可以被搜索引擎所检索到。
  • 我希望我的文章能被别人整理,与其它人的文章放在一起互补。
  • 我希望我的文章能被修改,因为文章会有错误,也会需要时常更新。

然而,微信公众号都不能很好的支持。我希望我的文章能成为生态圈的里的一部份。所谓生态圈是相互融合,不是唯我独尊。这个和做开源软件的道理一样,开源软件不是把源代码开出来就好了,而是要去和已有的其它软件互相融合,互相兼容,互相支持,这本就是软件设计的真谛(参看《UNIX编程艺术》)。所以,我想,写文章也一样。

下面是我觉得文章传播的姿势。

文章传播的姿势

我希望我的文章是被检索的,这意味着,就算文章写过了好多年,它依然可以被检索到,而不是在社交圈上被大众转了3-4天后就完了,然后再也没有然后了

今天,我十多年前写的文章依然可以被检索到,依然对后来的新人有帮助。因为我的文章被搜索引擎检索了,我的文章被转载fork出去了,被人引用和标注,所以,可以长期被传播。

今天的酷壳(CoolShell.cn)已经很长时间没有更新了,然而里面的很多文章依然在被转发着,在被搜索着,在被重复阅读和被人推荐着,文章不断的被后来的人阅读。这就是被检索被共享被转载的好处。

同时,我并不希望成为某个平台写文章的苦力。在微信公众号下,你需要不断的更新才会积累起人气(订阅者),而文章的保鲜期非常有限,因为不能被检索,所以,你写的越多,你过去的文章也会被遗忘的越快。而微信公众平台让能写文章的人好像成为了这个平台的一个写作的奴隶,而不是让他们的文章中的内容和观点可以有长时间的影响力。换言之,在社交网络上,如果你要有影响力,你就要使劲写,需要更多的粉丝和订阅者。我个人认为这是违反了信息传播规律的。

最重要的是,我希望我的文章和观点是有讨论的,希望我的文章能被指正和批评,最好是引发讨论和思辨,这样才会让我们每一个人都可以在交流中成长。很多时候,文章本身并没有什么太大的价值,而引发的讨论和思辨才更有价值,这是我认为文章传播最正确的姿势。而微信的公众号在讨论方面人为的阻止或大大消弱了大家的沟通和讨论。虽然我承认有些讨论也是无效的,而且还有漫骂和跑题,但是我依然觉得讨论的利大于弊。

我私以为,信息的传播正确姿势,是被检索、讨论、引用、整理、补充和更新,而不是社交网络的转发、点赞、粉丝、订阅和打赏

换句话说,我关注的是的文章的长期价值,而不是短期的表象

关于文章的版权

很多人认为,封闭的平台有个比开放平台天然的优势,就是盗版和抄袭的问题,可以通过平台举报和惩罚对方。我以前也受到一些被抄袭和盗用的困扰,还曾经拿起来法律的武器维护自己的权利。

可能是我经历这样的事情比较早,所以,我今天在这个问题上不纠结了。

1、好的有价值的文章总是被人盗用抄袭的,这也算是对作者的一种认可吧。

2、我从来没有见过有人靠抄袭和盗用别人文章而成功的,无非就是收获几个赞罢了。

3、原创文章被人过抄袭和盗用,反而容易得到更多的关注。

微信公众号的原创保护也只是局限在微信上,微信之外的平台,它也无能为力,所以,对于我的文章会被转到很多地方的这种情况来看,微信公众号的原创保护也非常有限。

现在,我倒是不纠结有人会盗用和抄袭我的文章,因为,一方面,你可以有一些小伎俩来保护你的文章,比如在文章内容中放入一些你自己特有的标识,另一方面,我的文章被人盗用了抄袭的时候,总有一些网友能在盗用者那边指出来原文章是什么,并批评之。所以,还是应该把主要精力放在文章的内容和质量上,并让文章可以被检索和被更多的地方所引用,这样,你的文章才会得到最大的保护

另外,既然别人对我的文章有抄袭和盗用的需求,要不我就让别人干得更体面一些。所以,我的文章完全可以自由的转载,但不得用于商业目的,只需要注明作者和出处就好了

关于写文章挣钱

首先,如果你觉得写文章出书是可以挣钱的,那么你可以洗洗睡了,尤其是在中国,这几乎是不可能的。

当然,你要挣点小钱是可以有的,但是,你需要写软文中植入广告,或是消费热点主题,比如前段时间的魏则西事件,有的公众号被打赏了一些钱。

老实说,这对我来说完全无感,因为,我的逻辑是这样的:我觉得一个人有一定的影响力,其中有很大一部份原因来自他的独立性,如果我开始写软文了,相当于我把自己卖了

所以,我现在从来不通过写文章挣钱,因为我会写代码,我还是通过我的技能挣钱,通过给一些公司做咨询、培训、企业服务挣钱,老实说,靠自己的能力挣钱,比写文章挣钱挣得多多了,因为我觉得,像我写的这类的文章本来就是用来分享和传播的,不是用来挣钱的。写文章的目的是分享和影响,不是挣钱。

关于独立性,这里说两个花絮吧——

我在Amazon的时候,我和公司讲,我想在我的博客上写几篇关于亚马逊的文章,介绍亚马逊的技术和一些做事的方法,也算是个宣传,让我的团队也好招人,但是,我当时的老板和我说,你的博客之所以有影响力是因为你的独立性,不要写亚马逊的,这样会把你自己卖了,千万别这么做。

然而,我在Alibaba的时候,我的大老板要求我用我的博客和微博帮阿里云做营销,我非常委婉地拒绝了,结果,团队合作的价值观不达标了。呵呵。

P.S. 本来酷壳上是不做广告的,今天,酷壳上也广告,但是广告费是全部捐给Wikipedia的,广告主的钱是没有到我的手的。

微信公众号上的文章都有软文和广告植放,我觉得这不是我的调调,我害怕微信公众平台的整体格调影响了我的格调。就好像我认为我的网络被百度检索到了会我的网站的格调下降好几个档次。所以,我还是保持一定的距离吧。

这么说吧,我写文章不是为了挣钱,我也不认为写文章能挣到钱,我写文章就是为了分享和影响,我会借助社交网络,但不会寄宿在社交网络上,更不会被社交网络所绑架。

谢谢看我的唠叨!

(全文完)

来源:**酷 壳 – CoolShell**

C++讲义

第二章 变量和基本类型

wchar_t

在字符字面前加L就能够得到wchar_t类型的宽字符字面值

1
L'a'

const

const 限定符把一个对象转化为一个常量

1
const int bufSize = 512;

任何修改bufSize的尝试都会导致编译错误i,定义时必须初始化。

const默认为文件的局部变量

1
2
3
4
5
6
//file_1.cpp
int counter;

//file_2.cpp
extern int count;
++counter;

除特别声明,在全局作用域声明的const变量是定义该对象文件的局部变量,不能被别的文件访问。

1
2
3
4
5
6
//file_1.cpp
extern const int bufSize = fcn();

//file_2.cpp
extern const int bufSize; //在这里bufSize的声明同样是extern; 这样extern 标志着bufSize是一个声明,所以没有初始化式
a = bufSize; //使用文件1中的bufSize

引用

引用就是对象的另一个名字,在实际程序中主要用作函数的形式参数。

引用是一种复合类型,在变量名前“&”来定义,不能定义引用类型的引用,但是能定义任何其他类型的引用

1
2
3
4
int ival = 1024;
int &refVal = ival; //√
int &refVa2; //× 必须初始化
int &refVa3 = 10; //× 初始化必须是对象

const 引用

const引用是指向const对象的引用:

1
2
3
const int ival = 1024;
const int &refVal = ival;//√
int &ref2 = ival;//× 会导致能够对ival进行修改

可以读取但是不可以修改refVal,因此对refVal的赋值都是不合法的。

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
#include <iostream>
int main()
{
int i, &ri = i;
i = 5; ri = 10;
std::cout << i << " " << ri << std::endl; return 0; } ``` &gt; 52页</iostream>

const引用可以初始化为不同类型的的对象或者初始化为右值

​```c
int i = 1024;
const int &r = 1024;//对比前面
const int &r2 = r + i;//因为只读
</code></pre>

<h2>第三章 标准库类型</h2>

<h3>标准string类型</h3>

string类型支持长度可变的字符串

string s1(n,'c')//表示将s1初始化为字符‘c'的n个副本

<blockquote>
由于历史原因以及为了与C语言兼容,字符串字面值与标准库string类型不是同一类型。
</blockquote>

<pre><code class="c">int main()
{
string line;
while(getline(cin,line))//一个输入流一个string对象
cout << line << endl; return 0; //由于getline 函数返回时丢弃换行符,换行符将不会存储在string对象中 } ``` ### string对象的操作 - s.empty() //如果是空串返回true - s.size() //串字符的个数 不要把size的返回值赋给一个int变量(unsigned) ### string对象中字符的处理 ``#include&lt;cctype&gt;`` - isalnum(c) //c是字母或数字 返回true - isalpha(c) //c是字母 - isgraph(c) //c不是空格,但可打印 - tolower(c)//如果c是大写则返回其小写字母,否则返回c - toupper(c) ### 标准vector类型 &gt; P78

vector是同一种类型的对象的集合,每个对象都有一个对应的整数索引值。

一个容器中所有对象必须是同一种类型的

- `vector&lt;int&gt; ivec;` 和其他变量定义一样,定义vector对象要指定类型和一个变量列表(int 类型对象vector 变量名为ivec)
- `vector&lt;T&gt; v(n,i)` v包含n个值为i的元素
- `vector&lt;int&gt; fvec(10)`

### vector 对象的操作

- v.empty()
- v.size()
- v.push_back(t)

大家认为vector的下标操作可以添加元素,以下是错误

​```c
vector<int> ivec
for(vector<int>::size_type ix=0;ix=0;ix != 10; ++ix)
ivec[ix] = ix;
​```</int></int>

但是ivec是vector对象,而且下标只能用于获取已存在的元素

正确写法:

​```c
forvector<int>::size_type ix=0;ix!=10;++ix)
ivec.push_back(ix);
​```</int>

> `const char*`与`char const*`效果一样,都是不允许修改指针指向的地址空间的值,即把值作为常量,而`char * const`则是不允许修改指针自身,不能再指向其他地方,把指针自己当作常量使用。需要注意的是,使用`char * const`定一个常量指针的时候一定记得赋初始值,否则再其他地方就没法赋值了。

​```c
#include <iostream>
using std::cout;
using std::endl;</iostream>

int main()
{
const char *st = "The expense of spirit\n";
int len = 0;
while (*st) { ++len; ++st; }
cout << len << ": " << st << endl;
return 0;
}
</code></pre>

<pre><code class="c">#include <iostream>
using std::cout;
using std::endl;</iostream>

const char *st = "The expense of spirit\n";

int main()
{
int len = 0;
const char *p = st;
while (*p) { ++len; ++p; }
cout << len << ": " << st << endl;
return 0;
}
</code></pre>

<pre><code class="c">#include <string>
using std::string;
#include <iostream>
using std::cout; using std::endl;</iostream></string>

int main()
{
string substr = "Hello";
string phrase = "Hello World";
string slang = "Hiya";

if (substr < phrase) cout << "substr is smaller" << endl; if (slang > substr) cout << "slang is greater" << endl; if (slang > phrase) cout << "slang is greater" << endl;

return 0;
}
</code></pre>

<pre><code class="c">#include <iostream>
#include <vector></vector></iostream>

using std::cout;
using std::endl;
using std::vector;

int main()
{
// empty vector
vector<int> ivec;
int val;
// give each element a new value
for (vector<int>::size_type ix = 0;
ix != 10; ++ix)
ivec.push_back(ix);</int></int>

cout << "ivec.size: " << ivec.size() << endl; // prints 10

// reset the elements in the vector to zero
for (vector<int>::size_type ix = 0; ix != ivec.size(); ++ix)
ivec[ix] = 0;</int>

// is there anything to print?
if (ivec.empty() == false) {
cout << "vector contents: " << endl;
// print each element separated by a newline
for (vector<int>::size_type ix = 0;
ix != ivec.size(); ++ix)
cout << ivec[ix] << endl;
}
return 0;
}
​```</int>

​```c
#include <vector>
#include <string>
#include <iostream></iostream></string></vector>

using std::vector;
using std::string;
using std::cin;
using std::cout;
using std::endl;

int main()
{
vector<int> ivec(10);</int>

// reset all the elements in ivec to 0
for (vector<int>::size_type ix = 0; ix != ivec.size(); ++ix)
ivec[ix] = 0;</int>

// print what we've got so far: should print 10 0's
for (vector<int>::size_type ix = 0; ix != ivec.size(); ++ix)
cout << ivec[ix] << " ";
cout << endl;</int>

// equivalent loop using iterators to reset all the elements in ivec to 0
for (vector<int>::iterator iter = ivec.begin();
iter != ivec.end(); ++iter)
*iter = 0; // set element to which iter refers to 0</int>

// print using iterators: should print 10 0's
for (vector<int>::iterator iter = ivec.begin();
iter != ivec.end(); ++iter)
cout << *iter << " "; // print the element to which iter refers
cout << endl;</int>

vector<int>::iterator iter = ivec.begin();
while (iter != ivec.end()) {
*iter = 0;
++iter;
}
return 0;
}
​```</int>

## 迭代器

> P84

除了使用下标来访问vector对象的元素外,标准库还提供了另外一种访问元素的方法。迭代器是一种检查容器内元素并遍历元素的数据类型。

1. 容器的iterator类型
vector&lt;int&gt;::iterator iter;//定义了一个名为iter的变量,它的数据类型是由vector&lt;int&gt;定义的iterator类型。

2. begin和end操作
end操作返回的迭代器并不指向vector中的任何实际元素,哨兵的作用。

3. 应用程序示例
​```c
for (vector<int>::iterator iter = ivec.begin();iter !=ivec.end(); ++iter)
*iter = 0;
​```</int>

4. const_iterator
该类型只能用于读取容器内的元素,但不能修改他的值。

const iteator的区别:
使用const_iterator类型时,得到一个迭代器,自身值是可以改变的,但不能用来改变其所指向的元素的值(可以自增但不能赋值)

const 的迭代器必须初始化迭代器,初始化后不能改变。

​```c
vector<int> nums(10);
const vector <int>::iterator cit = nums.begin();
*cit =1;
++cit;//×
​```</int></int>

const迭代器几乎没什么用,一旦初始化后只能用它来改写其指向的元素但不能指向别的元素。

## bitset

> P88

- `bitset&lt;32&gt; bitvec` 初始化32位,每位都是0
- `bitset&lt;n&gt; b(u)` b是unsigned long类型的一个副本
- `bitset&lt;n&gt; b(s)`
- `bitset b(s,pos,n)` b是s中从位置pos开始的n个位副本
- `string strval("1100")`
- `bitset &lt;32&gt; bitset4(strval)` 高阶置0,反向转化的
- `b.any()`
- `b.none()`
- `b.count()`
- `b.size()`
- `b[pos]`
- `b.set()` `b.set(pos)`
- `b.reset()`
- `b.flip()`

​```c
#include &lt;cstddef&gt;
#include &lt;iostream&gt;
#include &lt;string&gt;
using std::cout;
using std::endl;
using std::string;
using std::size_t;

#include &lt;bitset&gt;
using std::bitset;
int main()
{
bitset&lt;32&gt; bitvec; // 32 bits, all zero
bool is_set = bitvec.any(); // false, all bits are zero
bool is_not_set = bitvec.none(); // true, all bits are zero

cout &lt;&lt; "bitvec: " &lt;&lt; bitvec &lt;&lt; endl;

size_t sz = bitvec.size(); // returns 32

size_t bits_set = bitvec.count(); // returns number of bits that are on

// assign 1 to even numbered bits
for (int index = 0; index != 32; index += 2)
bitvec[index] = 1;

// equivalent loop using set operation
for (int index = 0; index != 32; index += 2)
bitvec.set(index);

unsigned i;

cout &lt;&lt; "bitvec: positions turned on:\n\t";
for (int index = 0; index != 32; ++index)
if (bitvec[index])
cout &lt;&lt; index &lt;&lt; " ";
cout &lt;&lt; endl;

// equivalent; turn off first bit
bitvec.reset(0);
bitvec[0] = 0;

bitvec.reset(); // set all the bits to 0.
bitvec.set(); // set all the bits to 1

bitvec.flip(0); // reverses value of first bit
bitvec[0].flip(); // also reverses the first bit
bitvec.flip(); // reverses value of all bits

// leaves bitvec unchanged; yields a copy of bitvec with all the bits reversed
bitvec = ~bitvec;

// bitvec1 is smaller than the initializer
bitset&lt;16&gt; bitvec1(0xffff); // bits 0 ... 15 are set to 1

// bitvec2 same size as initializer
bitset&lt;32&gt; bitvec2(0xffff); // bits 0 ... 15 are set to 1; 16 ... 31 are 0

// on a 32-bit machine, bits 0 to 31 initialized from 0xffff
bitset&lt;128&gt; bitvec3(0xffff); // bits 32 through 127 initialized to zero
cout &lt;&lt; "bitvec1: " &lt;&lt; bitvec1 &lt;&lt; endl;
cout &lt;&lt; "bitvec2: " &lt;&lt; bitvec2 &lt;&lt; endl;
cout &lt;&lt; "bitvec2[0] " &lt;&lt; bitvec2[0] &lt;&lt; endl;
cout &lt;&lt; "bitvec2[31] " &lt;&lt; bitvec2[31] &lt;&lt; endl;
cout &lt;&lt; "bitvec3: " &lt;&lt; bitvec3 &lt;&lt; endl;

string strval("1100");
bitset&lt;32&gt; bitvec4(strval);

cout &lt;&lt; "strval: " &lt;&lt; strval &lt;&lt; endl;
cout &lt;&lt; "bitvec4: " &lt;&lt; bitvec4 &lt;&lt; endl;

{
string str("1111111000000011001101");
bitset&lt;32&gt; bitvec5(str, 5, 4); // 4 bits starting at str[5], 1100
bitset&lt;32&gt; bitvec6(str, str.size() - 4); // use last 4 characters

cout &lt;&lt; "str: " &lt;&lt; str &lt;&lt; endl;
cout &lt;&lt; "bitvec5: " &lt;&lt; bitvec5 &lt;&lt; endl;

cout &lt;&lt; "str: " &lt;&lt; str &lt;&lt; endl;
cout &lt;&lt; "bitvec6: " &lt;&lt; bitvec6 &lt;&lt; endl;
}

{
unsigned long ulong = bitvec3.to_ulong();
cout &lt;&lt; "ulong = " &lt;&lt; ulong &lt;&lt; endl;
}

bitset&lt;32&gt; bitvec7 = bitvec2 &amp; bitvec4;

cout &lt;&lt; "bitvec7: " &lt;&lt; bitvec7 &lt;&lt; endl;

bitset&lt;32&gt; bitvec8 = bitvec2 | bitvec4;

cout &lt;&lt; "bitvec8: " &lt;&lt; bitvec8 &lt;&lt; endl;

cout &lt;&lt; "bitvec2: " &lt;&lt; bitvec2 &lt;&lt; endl;
return 0;
}

第四章 数组和指针

指针的定义和初始化

string* 错误的理解成一种数据类型

string* ps1,ps2 实际上是ps1是指针,ps2是一个普通的string对象

未初始化的指针是无效的,直到给该指针赋值后,才能使用它

赋值操作的约束

int型变量赋值给指针式非法的,尽管这个int型变量的值可能是0,允许把数值0或在编译时可获得0值的const量赋值给指针

1
2
3
4
5
6
7
int ival;
int zero = 0;
const int c_ival = 0;
int *pi = ival;(×)
pi = zero;(×)
pi = c_ival;
pi = 0;

从C继承下来的预处理变量:

1
int *pi = NULL;

void* 指针

C++ 提供了一种特殊的指针类型 void*,它可以保存任何类型对象的地址:

1
2
3
4
double obj = 3.14;
double *pd = &obj;
void *pv = &obj;
pv = pd;

void* 指针只支持几种有限的操作:与另一个指针进行比较;向函数传递void* 指针或从函数返回 void* 指针;给另一个 void* 指针赋值。

不允许使用void* 指针操纵它所指向的对象。

指针操作

指针和引用的比较

第一个区别在于引用总是指向某个对象:定义引用时没有初始化是错误的。第二个重要区别则是赋值行为的差异:给引用赋值修改的是该引用所关联的对象的值,而并不是使引用与另一个对象关联。引用一经初始化,就始终指向同一个特定对象。

指针和const限定符

介绍了指针和 const 限定符之间的两种交互类型:指向 const对象的指针和 const 指针。

指向const 对象的指针

我们使用指针来修改其所指对象的值。但是如果指针指向const 对象,则不允许用指针来改变其所指的 const 值。

为了保证这个特性,C++ 语言强制要求指向 const 对象的指针也必须具有 const 特性:

1
const double *cptr;

这里的 cptr 是一个指向 double 类型 const 对象的指针,const 限定了cptr 指针所指向的对象类型,而并非 cptr 本身。也就是说,cptr 本身并不是const。在定义时不需要对它进行初始化,如果需要的话,允许给 cptr 重新赋值,使其指向另一个 const 对象。但不能通过 cptr 修改其所指对象的值:

不能使用 void* 指针保存 const 对象的地址,而必须使用 const void* 类型的指针保存 const 对象的地址:

1
2
3
const int universe = 42;
const void *cpv = &universe; // ok
void *pv = &universe; // error

允许把非 const 对象的地址赋给指向 const 对象的指针,例如:

1
2
double dval = 3.14;
cptr = &dval;

但是同样必须遵循不能通过cptr修改对象的值

const指针

const指针指的是指针本身的值不能改变

指向const对象的const指针

1
2
const double pi = 3.14159;
const double *const pi_ptr = π

既不能修改 pi_ptr 所指向对象的值,也不允许修改该指针的指向。

指针和 typedef (讨论)

下面是一个几乎所有人刚开始时都会答错的问题。假设给出以下语句:

1
2
typedef string *pstring;
const pstring cstr;

请问 cstr 变量是什么类型?

简单的回答是 const pstring 类型的指针。

进一步问:const pstring 指针所表示的真实类型是什么?很多人都认为真正的类型是:

1
const string *cstr;

也就是说,const pstring 是一种指针,指向 string 类型的 const 对象,但这是错误的。

错误的原因在于将 typedef 当做文本扩展了。声明 const pstring 时,const 修饰的是 pstring 的类型,这是一个指针。因此,该声明语句应该是把cstr 定义为指向 string 类型对象的 const 指针,这个定义等价于:

1
string *const cstr;

创建动态数组

每一个程序在执行时都占用一块可用的内存空间,用于存放动态分配的对象,此内存空间称为程序的自由存储区或堆。

const 对象的动态数组

如果我们在自由存储区中创建的数组存储了内置类型的 const 对象,则必须为这个数组提供初始化:因为数组元素都是 const 对象,无法赋值。实现这个要求的唯一方法是对数组做值初始化:

1
2
const int *pci_bad = new const int[100];(×)
const int *pci_ok = new const int[100]();(√)

C++ 允许定义类类型的 const 数组,但该类类型必须提供默认构造函数:

1
const string *pcs = new const string[100];

已创建的常量元素不允许修改――因此这样的数组实际上用处不大。

第五章 表达式

箭头操作符

1
->` 相当于 `(*sp)

const对象的动态分配和回收

1
const int *pci = new const int (1024);

与其他常量一样,动态创建的const对象必须在创建时初始化,并且一经初始化,其值就不能修改。

对于类类型的const动态对象,如果该类提供了默认构造函数,则此类可以隐身初始化,但是内置类型对象以及未提供默认构造函数的类类型对象必须显示初始化。

有符号与无符号类型之间的转换

如果int类型足够表示所有的unsigned short型的值,则将unsigned short转换为int,否则将这两个操作数转换为 unsigned int.

同理 unsigned intlong转换为unsigned long.

Git讲义

讲义内容参考:http://www.cnblogs.com/wilber2013/p/4189920.html

Git简介

Git是一个分布式版本控制工具

集中式版本控制

一个系统中只有一个机器是服务端,其他机器全是客户端。

缺点:

  • 网络依赖性强,工作环境保持网络连接,如果网络断掉了,所有的客户端就无法工作了。
  • 安全性较弱,所有的代码以及版本信息保存在服务器中,一旦服务器挂掉了,代码和版本控制信息就丢失了。

分布式版本控制

没有服务端/客户端的概念,每台机器都是一个服务器。但是一般选一个机器作为中心服务器,方便大家交换更新。

优点:

  • 每台机器都是一台服务器,无需依赖网络就可以帮自己的更新提交到本地服务器,支持离线工作。当有网络环境的时候,就可以把更新推送给其他服务器。
  • 安全性高,每台机器都有代码以及版本信息的维护,所有即使某些机器挂掉了,代码依然是安全的。

Git下载

Git刚开始只能支持Linux和Unix环境,后来才慢慢的支持Windows系统。

主程序:Git

Windows下可视化界面:TortoiseGit

Git安装后的配置

安装完成后,一般都会对本机的Git进行一些基本的配置。下面的命令就是给Git环境配置全局的用户名和邮箱地址,这样每一个从这台机器上提交的更新都会标上这些用户信息。

  • 当第一次安装完Git时

设置全局名字:设置成自己在GitHub上的账号显示名

1
2
3
4
# 设置名字
$ git config --global user.name "你的名字"
# 设置邮箱
$ git config --global user.email "你的邮箱"
  • 当每一次创建项目时

设置项目中自己的名字:这里设置的名字是针对本项目的,本地设置的user.name要和服务器上的账号显示名一致

1
2
3
4
# 设置名字
$ git config user.name "你的名字"
# 设置邮箱(邮箱与全局相同可以不设置)
$ git config user.email "你的邮箱"

在git设置中注意关闭忽略大小写,这一项貌似设成全局的不管用,所以每个项目创建时都要设置一下

1
2
#关闭忽略大小写
git config core.ignorecase false

尽量使用UTF-8编码

Git基本概念

在Git中,每个版本库都叫做一个仓库(repository,缩写repo),每个仓库可以简单理解成一个目录,这个目录里面的所有文件都通过Git来实现版本管理,Git都能跟踪并记录在该目录中发生的所有更新。

我们现在建立一个仓库(repo),那么在建立仓库的这个目录中会有一个”.git”的文件夹。这个文件夹非常重要,所有的版本信息、更新记录,以及Git进行仓库管理的相关信息全都保存在这个文件夹里面。所以,不要修改/删除其中的文件,以免造成数据的丢失。

创建仓库

通过”Git Bash”命令行窗口进入到想要建立版本仓库的目录,通过git init就可以轻松的建立一个仓库。

添加

先编写一个文件reader.txt

通过git status可以查看WorkSpace的状态,看到输出显示reader.txt没有被Git跟踪,并且提示我们可以使用”git add …”把该文件添加到待提交区(暂存区)。

1
2
git add reader.txt
git add .

通过git status可以查看WorkSpace的状态,这时的更新已经从WorkSpace保存到了Stage中。

1
git commit -m "xxx"

xxx是对此操作的描述,这时的更新已经又从Stage保存到了Local Repo中。

更新

修改文件reader.txt

修改文件后,查看WorkSpace的状态,会发现提示文件有更新,但是更新只是在WorkSpace中,没有存到暂存区中。

同样,通过add、commit的操作,我们可以把文件的更新先存放到暂存区,然后从暂存区提交到repo中。

git diff

通过”git diff”来查看WorkSpace和Stage的diff情况,当我们把更新add到Stage中,diff就不会有任何输出了。

当然,我们也可以把WorkSpace中的状态跟repo中的状态进行diff,命令如下,关于HEAD,将在后面解释。

1
$ git diff HEAD~n

撤销更新

根据前面对基本概念的了解,更新可能存在三个地方,WorkSpace中、Stage中和repo中。下面就分别介绍一下怎么撤销这些更新。

  • 撤销WorkSpace中的更新

即:暂存区有内容,即有未commit的内容,此操作是将暂存区覆盖到WorkSpace,即将最后没add的内容取消

1
$ git checkout -- filename

注意,在使用这种方法撤销更新的时候一定要慎重,因为通过这种方式撤销后,更新将没有办法再被找回。

  • 撤销Stage中的更新

即:add完成,但是还没commit

1
$ git reset HEAD filename

此时,就把暂存区的更新移出到WorkSpace中。如果想继续撤销WorkSpace中的更新,请参考上面一步。

  • 撤销repo中的更新

即:commit操作完成

查看历史记录:通过下面的命令我们可以查看commit的历史记录

1
$ git log

撤销commit有两种方式:使用HEAD指针和使用commit id

HEAD指针指向当前分支中最新的提交:

1
2
$ git reset --hard HEAD^
$ git reset --hard 一长串数字

注:当前版本,我们使用”HEAD^”,那么再前一个版本可以使用”HEAD^^”,如果想回退到更早的提交,可以使用”HEADn”。(也就是,HEAD^=HEAD1,HEAD^^=HEAD~2)

  • 恢复撤销repo中的更新
1
$ git reflog

git reflog 查看记录在这个仓库中的所有分支所有更新记录,包括已经撤销的更新

注:在”.git/logs”文件夹内有一个HEAD文件和refs目录,这些就是记录reflog的地方

git log 查看当前分支中的commit记录

有了reflog,我们就可以查找到刚才撤销的提交,然后可以通过下面命令来恢复撤销操作。

1
2
$ git reset --hard [email protected]{1}
$ git reset --hard 一长串数字

–hard和–soft

–hard:撤销并删除相应的更新
–soft:撤销相应的更新,把这些更新的内容放的Stage中

删除文件

1
$ git rm

Git对象模型

Git对象

在Git系统中有四种类型的对象,所有的Git操作都是基于这四种类型的对象。

  • “blob”:这种对象用来保存文件的内容
  • “tree”:可以理解成一个对象关系树,它管理一些”tree”和 “blob”对象
  • “commit”:只指向一个”tree”,它用来标记项目某一个特定时间点的状态。它包括一些关于时间点的元数据,如时间戳、最近一次提交的作者、指向上次提交(初始commit没有这一项)
  • “tag”:给某个提交(commit) 增添一个标记

SHA1哈希值

在Git系统中,每个Git对象都有一个特殊的ID来代表这个对象,这个特殊的ID就是我们所说的SHA1哈希值。

SHA1哈希值是通过SHA1算法(SHA算法家族的一种)计算出来的哈希值,对于内容不同的对象,会有不同的SHA1哈希值。前面讲的根据commit id撤销更新的,这里的commit id就是一个SHA1哈希值。

Git对象模型实例

    1. 新建一个仓库,添加一个文件

通过git log --pretty=raw可以得到每个commit的SHA1哈希值,也可以得到这个commit对应的tree的哈希值。

所以,一个commit对象一般包含以下信息:代表commit的哈希值、指向tree 对象的哈希值、作者、提交者、注释

在Git对象模型的研究中,有一个很有用的命令git cat-file,可以通过这个命令查询特定对象的信息:

1
2
3
4
#通过一个对象的哈希值查看对象的类型(blob、tree、commit或tag)
$ git cat-file -t commit_id
#通过对象的哈希值可以查看这个对象的内容
$ git cat-file -p commit_id
  • 2.更新文件,添加新东西
1
2
$ git log --pretty=raw
$ git cat-file

.git目录

进入.git目录,通过ls -l查看.git目录中的文件和子目录

  • (D) hooks:这个目录存放一些shell脚本,可以设置特定的git命令后触发相应的脚本;在搭建gitweb系统或其他git托管系统会经常用到hook script
  • (D) info:包含仓库的一些信息
  • (D) logs:保存所有更新的引用记录
  • (D) objects:所有的Git对象都会存放在这个目录中,对象的SHA1哈希值的前两位是文件夹名称,后38位作为对象文件名
  • (D) refs:这个目录一般包括三个子文件夹:heads、remotes和tags,heads中的文件标识了项目中的各个分支指向的当前commit
  • (F) COMMIT_EDITMSG:保存最新的commit message,Git系统不会用到这个文件,只是给用户一个参考
  • (F) config:这个是Git仓库的配置文件
  • (F) description:仓库的描述信息,主要给gitweb等git托管系统使用
  • (F) index:这个文件就是我们前面文章提到的暂存区(stage),是一个二进制文件
  • (F) HEAD:这个文件包含了一个当前分支(branch)的引用,通过这个文件Git可以得到下一次commit的parent
  • (F) ORIG_HEAD:HEAD指针的前一个状态

Git引用

Git系统中的分支名、远程分支名、tag等都是指向某个commit的引用。比如master分支,origin/master远程分支,命名为V1.0.0.0的tag等都是引用,它们通过保存某个commit的SHA1哈希值指向某个commit。

  • HEAD

HEAD也是一个引用,一般情况下间接指向你当前所在的分支的最新的commit上。

HEAD跟Git中一般的引用不同,它并不包含某个commit的SHA1哈希值,而是包含当前所在的分支,所以HEAD直接指向当前所在的分支,然后间接指向当前所在分支的最新提交。

  • Git索引(index)

index(索引)显示一个存放了已排序的路径的二进制文件,并且每个路径都对应一个SHA1哈希值。

1
2
#显示index文件的内容
$ git ls-files --stage

从命令的输出可以看到,所有的记录都对应仓库中的文件(包含全路径)。通过git cat-file命令查看reader.txt对应的哈希值,可以看到这个哈希值就是代表reader.txt的blob对象。

现在我们更新之前的文件,加上新内容并通过”git add”添加到暂存区,这时发现index中之前对象的哈希值已经变化了。

通过这个例子,我们也可以理解diff操作应该会有怎样的输出了:

git diff:比较WorkSpace和stage,add之前有diff输出;add之后没有diff输出
git diff HEAD:比较WorkSpace和repo,add之前之后都有diff输出
git diff –cached:比较stage和repo,add之前没有diff输出;add之后有diff输出

对象的存储

前面提到所有的Git对象都会存放在”.git/objects”目录中,对象SHA1哈希值的前两位是文件夹名称,后38位作为对象文件名。

所以,我们前面提到的master上最新的commit对象的哈希值是”4ea6c317a67e73b0befcb83c36b915c1481f2efe”,那么这个对象会被存储在”.git/objects/4e/a6c317a67e73b0befcb83c36b915c1481f2efe”。进入objects目录后,我们确实找到了这个文件。
在Git系统中有两种对象存储的方式,松散对象存储和打包对象存储。

  • 松散对象(loose object)

松散对象存储就是前面提到的,每一个对象都被写入一个单独文件中,对象SHA1哈希值的前两位是文件夹名称,后38位作为对象文件名。

  • 打包对象(packed object)

对于松散存储,把每个文件的每个版本都作为一个单独的对象,它的效率比较低,而且浪费空间。所以就有了通过打包文件(packfile)的存储方式。

Git使用打包文件(packfile)去节省空间.。在这个格式中,,Git只会保存第二个文件中改变了的部分,然后用一个指针指向相似的那个文件。

一般Git系统会自动完成打包的工作,在已经发生过打包的Git仓库中,”.git/objects/pack”目录下会成对出现很多”pack-.idx”和”pack-\.pack”文件。

Git 分支

在代码版本控制工具中,都会有branch的概念。刚开始建立版本仓库的时候,我们只有一个主分支(master branch),我们不可能把日常的新功能开发、代码优化以及bug修复等概念工作全都放在主分支上,这样会使主分支很难维护。这就是为什么会有branch。

分支的创建

1
2
3
4
git checkout -b dev //创建并切换到分支dev
#相当于:
$ git branch dev
$ git checkout dev

查看当前分支

1
$ git branch

注:checkout – 表示还原,checkout 表示切换。

可以git log查看一下

分支的删除

1
$ git branch -d dev

分支的合并

branch的创建是为了方便开发、修复bug以及保持master的稳定。但是最终branch上的内容还是要合并到master的,接下来就看看分支的合并。

1
2
#合并到master上
$ git merge dev

注意:一定要先切换回master分支git checkout master后再合并。

合并冲突

在branch的合并中,很多时候我们会遇到冲突,那么我们就需要手动解决冲突,然后再提交了。

为了演示冲突合并,我们回退master到上一次提交

1
$ git reset --hard HEAD~1

然后在master分支上修改reader.txt,之后add、commit

这时我们在合并master和dev上的内容,就会出现冲突。

在Git中,会用”<<<<<<<”,”=======”,”>>>>>>>”标记出冲突区域,我们需要根据这些符号找到所有的冲突并解决,之后再次add、commit即可。

查看日志

1
$ git log --graph --pretty=oneline --abbrev-commit
  • –graph:显示commit的历史,包括不同分支的提交
  • –pretty=oneline:只显示一行提交的信息; 多行:–pretty=raw
  • –abbrev-commit:以精简的方式显示commit的哈希值

stash

在branch的使用过程中,我们还会经常使用到stash和diff操作,下面分别进行介绍。

在Git中,stash是个很有用的命令,可以保存我们做到一半的工作,可以理解成一个未完成工作的保存区。

假如我们在dev branch做了一些更新,但是想做的事情还没有全部完成,不能提交,这时我们又要切换到master branch,此时Git会禁止branch切换。

这个时候我们就需要使用stash来保存未完成的工作了。

注:在要保存的分支上进行stash操作。

1
2
3
4
# 保存未提交的内容
$ git stash
# 查看所有stash内容
$ git stash list

此时,我们就可以在其他分支上继续做别的工作了。

当其他工作完成,想回来继续刚才的操作的时候,我们可以通过git stash apply还原被保存的工作状态。

1
2
3
4
# 查看dev分支的状态
$ git status
# 还原被保存的工作
$ git stash apply

stash空间就像是一个栈空间,每次通过stash保存等内容都会被压入stash栈。命令不仅仅是支持简单的list、apply操作,接下来我们看看更多的stash命令。

  • git stash save可以通过自定义的信息来描述一个stash
1
2
3
$ git stash save "Reupdate reader.txt"
#再次查看当前stash的内容
$ git stash list
  • git stash apply [email protected]{n}通过这个命令,我们可以选择stash栈中的stash,从而恢复到特定的状态;”git stash apply”使用栈顶来还原WorkSpace。
  • git stash pop就像”git stash apply”使用栈顶来还原WorkSpace,但是同时会从stash栈中删除当前的栈顶stash。

branch之间的diff

在前面的文章中我们通过diff比较过同一个分支上的内容在WrokSpace、stage和repo中的差别。

同样diff可以支持分支之间的比较。

1
git diff branchName`把当前branch跟branchName进行比较,也可以使用`git diff branchNameA branchNameB

git diff branchName -- fileName比较两个branch的fileName文件差异


Git 远程仓库

前面文章中出现的所有Git操作都是基于本地仓库的,但是日常工作中需要多人合作,不可能一直都在自己的代码仓库工作。所以,这里我们就开始介绍Git远程仓库。

在Git系统中,用户可以通过push/pull命令来推送/获取别的开发人员的更新,当时对于一个工作组来说,这种方式会效率比较低。所以,在一个Git系统中,都会有一个中心服务器,大家都通过中心服务器来推送/获取更新。

为了方便本篇例子的进行,我就使用多个目录来模拟多个用户以及中心服务器,这样就不用搭建Git服务器了。

中心服务器:C:\VM\CentralRepo

用户A:C:\VM\AWorkSpace

用户B:C:\VM\BWorkSpace

建立中心服务器(服务器文件)

前面通过git init来建立一个Git仓库,这里,我们使用git init --bare来建立中心仓库。

git init --bare方法创建的是一个裸仓库,之所以叫裸仓库是因为这个仓库只保存Git历史提交的版本信息,而不允许用户在上面进行各种git操作。

之所以有裸仓库,是因为用git init初始化的版本库,用户也可以在该目录下执行所有git方面的操作。但别的用户在将更新push上来的时候容易出现冲突。在这种情况下,最好就是使用”–bare”选项建立一个裸仓库。

Clone一个仓库 (A仓库的位置)

在Git中,我们有两种方式建立Git仓库:一个是通过git init建立一个新的仓库,另一个是通过git clone命令clone一个已有的Git仓库。

既然中心服务器,用户A就可以通过clone命令来复制一个Git仓库。

1
$ git clone /c/vm/CentralRepo/ #被clone的仓库的地址

这时,用户A就从中心服务器clone了一个空的仓库,接下来A就可以在这个本地仓库工作了。

更新的push和pull

现在A在本地仓库中添加了一个”calc.py”的文件,并且提交到了本地仓库。

1
2
$ git add calc.py
$ git commit -m "add calc file"

为了使其他用户可以得到这个更新,A需要把这个更新push到中心服务器上。

1
$ git push origin master

现在用户B可以通过clone方式获得心服务器上的仓库副本,通过”git log”显示A的更新已经自动被clone了下来。

1
2
3
$ git clone /c/vm/CentralRepo/
$ cd CentralRepo
$ git log

现在,B提交了一个新的更新,那么A就可以通过git pull从中心服务器得到这个更新

上游仓库(upstream repository)

在Git系统中,通常用”origin” 来表示上游仓库。我们可以通过 git branch -r命令查看上游仓库里所有的分支,再用 origin/name-of-upstream-branch 的名字来抓取(fetch)远程追踪分支里的内容。

中心服务器上建立两个新的”dev1”和”dev2”分支,通过git pull ,用户B就看到了这些上游分支。

–set-upstream-to

当我们在本地仓库建立一个branch的时候,我们的pull操作会遇到以下问题,提示我们这个branch没有任何的跟踪信息。仔细想想也是,我们应该把本地仓库中的分支与上游分支关联起来。这时,我们就可以根据Git的提示,使用”–set-upstream-to”命令进行关联了。

1
2
3
4
5
6
7
8
# 先在B上创建同名分支
$ git checkout -b dev1
# 拉取
$ git pull # 出错!
# 本地仓库中的分支与上游分支关联起来
$ git branch --set-upstream-to=origin/dev1 dev1
# 此时拉取就能成功了
$ git pull

还可以通过下面的命令创建关联分支

1
2
#一般,"localBranchName"名称跟"remoteBranchName"名称设置成一样。
$ git checkout -b localBranchName origin/remoteBranchName

patch

在Git中patch绝对是一个很有用的东西。假设在一个没有网络的环境中,A和B还要继续工作,这时B有一个更新,A需要基于这个更新进行下一步的工作。如果是集中式的代码版本工具,这种情况就没有办法工作了,但是在Git中,我们就可以通过patch的方式,把B的更新拷贝给A。

在Git中有两种patch的方式

  • 通过git diff生成一个标准的patch

  • 通过

    1
    git format-patch

    生成一个Git专用的patch。

  • 基于git diff的patch

假设现在B更新”calc.py”文件并且通过git diff生成了一个patch。

1
2
3
4
# B更新后,通过`git diff `查看B的更新
$ git diff
# 把diff的结果保存到一个文件中,生成一个patch
$ git diff > BPatch

下面,我们把 BPatch这个文件拷贝到A的工作目录中,然后通过git apply应用这个patch,从而得到B的更新。

1
2
3
4
# A通过`git apply`命令应用BPatch得到了B的更新
$ git apply BPatch
# 通过`git diff`查看B的更新
$ git diff
  • 基于git format-patch的patch

B更新提交之后,使用”git format-patch”来生成patch。

1
$ git format-patch origin/master

注意git format-patch命令的参数”origin/master”,这个参数的含义是,找出当前master跟origin/master之间的差别,然后生成一个patch,比如:0001-update-calc.py.patch。

把patch文件拷贝到A的工作目录,则此我们通过”git am”命令来应用这个patch。

1
2
3
4
# 应用patch
$ git am 0001-update-calc.py.patch
# 再查看日志
$ git log
  • 两种patch方式的比较

patch兼容性:git diff生成的patch兼容性强。也就是说,如果别人的代码版本库不是Git,那么必须使用git diff生成的patch才能被别的版本控制系统接受。
patch合并提示:对于git diff生成的patch,你可以用git apply –check 查看patch是否能够干净顺利地应用到当前分支中;如果git format-patch生成的patch不能打到当前分支,git am会给出提示,帮你解决冲突,两者都有较好的提示功能。
patch信息管理:通过git format-patch生成的patch中有很多信息,比如开发者的名字,因此在应用patch时,这个名字会被记录进版本库,这样做是比较合理的。

.gitignore

项目中可能会经常生成一些Git系统不需要追踪(track)的文件,在编译生成过程中 产生的文件或是编程器生成的临时备份文件。我们可以在使用”git add”是避免添加这些文件到暂存区中,但是每次都这么做会比较麻烦。

所以,为了满足上面的需求,Git系统中有一个忽略特定文件的功能。我们可以在工作目录中添加一个叫”.gitignore”的文件,j将要忽略的内容写进文件,来告诉Git系统要忽略哪些文件。

注意:

  • 在windows环境中不支持文件名为”.gitignore”,所以可以把文件命名为”.gitignore.”
  • “.gitignore”文件只会对当前目录以及所有当前目录的子目录生效;也就是说如果我们把”.gitignore”文件移到”advance”文件夹中,那么过滤规则就是会对”advance”及其子目录生效了
  • 建议把”.gitignore”文件提交到仓库里,这样其他的开发人员也可以共享这套过滤规则

过滤语法

  • 斜杠”/“开头表示目录
  • 星号”*”通配多个字符
  • 问号”?”通配单个字符
  • 方括号”[]”包含单个字符的匹配列表
  • 叹号”!”表示不忽略匹配到的文件或目录
    下面举一些简单的例子:
  • foo/*:忽略目录 foo下的全部内容
  • *.[oa]:忽略所有.o和.a文件
  • !calc.o:不能忽略calc.o文件

远程仓库命令

git branch

  • git branch -r 列出远程分支
  • git branch -a 列出所有分支

git remote

  • git remote 列出所有远程主机
  • git remote -v列出所有远程主机,并显示远程主机地址

git push

push命令用来将本地分支的更新推送的远程仓库,该命令的格式如下:

1
git push <远程主机名> <本地分支名>:<远程分支名>

通过”git push”更新、创建、删除远程分支

  • git push origin dev1:dev1 将本地dev1分支推送到远程origin的dev1分支上
  • git push origin dev1 省略远程分支名时,推送到远程origin下的同名(dev1)分支上
  • git push origin dev0 如果远程分支不存在,则创建一个同名远程分支与本地分支相关联
  • git push origin :dev1 省略本地分支名时,表示删除指定的远程分支,因为这等同于推送一个空的本地分支到远程分支。等价于git push origin --delete dev1,只是删除了远程分支,本地还有~

git pull

pull命令的作用是取回远程某个分支的更新,然后合并到指定的本地分支,pull命令格式如下:

1
git pull <远程主机名> <远程分支名>:<本地分支名>

git pull origin dev1:dev1:取回origin主机dev1分支的更新,与本地的dev1分支合并。

一般来说,pull命令都是在关联的本地分支和远程分支之间进行;当然,你可以使用不关联的本地分支和远程分支进行pull操作,但是不建议这么做。

省略本地分支名:git pull origin dev1,表示取回origin/dev1远程分支的更新,然后合并到当前分支

如果当前分支存在上游(关联)分支,可以直接使用git pull origin

git pull操作实际上等价于,先执行git fetch获得远程更新,然后git merge与本地分支进行合并。

pull命令也支持使用rebase模式进行合并:

1
git pull --rebase <远程主机名> <远程分支名>:<本地分支名>

在这种情况下,git pull就等价于git fetch加上git rebase

建议使用”git fetch”加上”git rebase”的方式来取代”git pull”获取远程更新。

git fetch

fetch命令比较简单,作用就是将远程的更新取回到本地。

git fetch origin表示将远程origin主机的所有分支上的更新取回本地

git fetch origin master只取回远程origin主机上master分支上的更新

Git 的merge和 rebase

merge

merge命令会有三种情况发生:

  1. merge命令不生效

当前分支已经是最新的了,在这种情况下merge命令没有任何效果。

1
$ git merge dev

提示”Already up-to-date.”,表示merge不生效,因为master比dev还要新,就不用merge了。

  1. Fast-forward合并模式

git merge --no-ff master 合并master时可以禁止Fast-forward, 以保留版本演进的历史信息,否则commit仍然是线性的

  1. 三方合并:不使用!

cherry-pick

在实际应用中,我们可能会经常碰到这种情况,在分支A上提交了一个更新,但是后来发现我们同样需要在分支B上应用这个更新。那么这时cherry-pick就可以帮助你解决问题。

我们要做的就是通过git reflog找到A上那个更新的SHA1哈希值,然后切换到B分支上使用git cherry-pick

1
2
#假设A上的更新是00abc3f
$ git cherry-pick 00abc3f

如果出错,按提示修改,先进行手动合并冲突,之后git add,然后继续cherry-pick,git cherry-pick --continue即可。

如果想要终止cherry-pick:git cherry-pick --abort

rebase

在merge的过程中,比较好的就是我们可以看到分支的历史信息,但是,如果分支很多的时候,这个分支历史可能就会变得很复杂了。如果我们使用rebase,那么提交的历史会保持线性。

rebase的原理:先将当前分支的HEAD引用指向目标分支和当前分支的共同祖先commit节点,然后将当前分支上的commit一个个apply到目标分支上,apply完以后再将HEAD引用指向当前分支。是不是有点绕,下面我们看个实例。
下面就开始rebase的介绍,我们会基于master新建一个release-1.0的分支,并在该分支上提交一个更新。

这时,我们在release-1.0分支上执行”git rebase master”,就会得到下面的对象关系图。

根据rebase的工作原理进行分析:
把当前分支的HEAD引用指向”00abc3f”
然后将当前分支上的commit一个个apply到目标分支,这里就是把”ed53897”更新apply到master上;注意,如果没有冲突,这里将直接生成一个新的更新
最后更新当前分支的HEAD引用执行最新的提交”8791e3b”
这个就是rebase操作,可以看到通过rebase操作的话,我们的commit历史会一直保持线性。在这种情况下,当我们切换到master分支,然后进行”git merge release-1.0”分支合并时,master将会直接是Fast-forward合并模式,commit历史也会是线性的。
当然rebase操作也会产生冲突,当一个冲突发生的时候,我们可以skip过当前的patch(一定要当心,不要随便使用,以免丢失更新);也可以手动的解决冲突,然后继续rebase操作

rebase交互模式
其实,rebase还有别的很强大功能,比如rebase交互模式,通过交互模式我们可以改变commit的信息,删除commit,合并commit以及更改commit的顺序等等。
假如我们现在想要更新一个commit的信息,我们就可以使用rebase交互模式,找到commit的哈希值,然后进入交互模式。

根据rebase的操作提示,我们选择edit操作,然后保存退出。

这时,Git将会提示我们,是进行更改,还是可以继续操作。这里我们通过”git commit –amend”进入编辑模式。

在编辑模式中对commit进行更新,然后保存退出,继续rebase操作。

关于rebase交互模式的其他命令,这里就不做介绍了,有兴趣的同学可以google一下。

总结

这篇文章主要对merge和rebase进行了介绍。对于最终的结果而言,rebase和merge是没有区别的,不会发生rebase和merge导致最终更新不一致的情况。
rebase和merge的差别主要是:
rebase更清晰,因为commit历史是线性的,但commit不一定按日期先后排,而是当前分支的commit总在后面(参考rebase原理)
merge之后commit历史变得比较复杂,并且一定程度上反映了各个分支的信息,而且 commit按日期排序的。
大家可以根据自己的项目需求进行选择使用哪种方式拉取远程的更新。

QT学习

什么是QT

Qt是一个1991年由奇趣科技开发的跨平台C++图形用户界面应用程序开发框架。它既可以开发GUI程序,也可用于开发非GUI程序,比如控制台工具和服务器。Qt是面向对象的框架,使用特殊的代码生成扩展(称为元对象编译器(Meta Object Compiler, moc))以及一些宏,易于扩展,允许组件编程。2008年,奇趣科技被诺基亚公司收购,QT也因此成为诺基亚旗下的编程语言工具。2012年,Qt被Digia收购。2014年4月,跨平台集成开发环境Qt Creator 3.1.0正式发布,实现了对于iOS的完全支持,新增WinRT、Beautifier等插件,废弃了无Python接口的GDB调试支持,集成了基于Clang的C/C++代码模块,并对Android支持做出了调整,至此实现了全面支持iOS、Android、WP。

学习视频教程网站http://my.tv.sohu.com/pl/5176735/

QT/安装步骤/环境搭建

下面介绍Windows版QT开发环境Qt Creater + MinGW + Qt libraries配置方法

下载安装MinGW

从MinGW网站下载,默认安装到C盘根目录下:C:\MinGW,安装时选择C和C++ compiler,默认只选中了C编译器。

下载安装配置QT libraries

http://qt-project.org/downloads

默认安装路径为C:\Qt\4.8.3,安装时需要指定MinGW的安装路径为C:\MinGW。

安装完后需要把C:\Qt\4.8.3\bin目录添加到系统变量的Path路径中。

并新建系统环境变量QMAKESPEC,32位系统把值设置为C:\Qt\4.8.3\mkspecs\win32-g++;如果是64位系统,需要把值设置为C:\Qt\4.8.3\mkspecs\tru64-g++

还要新建系统环境变量QTDIR,值为C:\Qt\4.8.3

下载安装配置QT Creater

默认安装到C:\Qt\qtcreator-2.6.0目录下。

需要把C:\Qt\qtcreator-2.6.0\bin目录添加到系统变量的Path路径中。如果不设置系统环境变量,则创建工程时kit不能设置成功,并且可创建的工程类型也会受到限制。

设置QT Creator构建和运行配置项:打开QT Creator,选择菜单“工具/选项”,选择左边的”构建和运行”,再选择“Qt版本”选项卡,点击“添加”,qmake路径:C:\Qt\4.8.3\bin\qmake.exe。

还需要设置Compilers选项卡中的“手动设置”项的编译器,Name设置为MinGW,编译器路径设置为C:\MinGW\bin\mingw32-g++.exe。

hello word程序

1
2
3
4
5
6
7
8
9
#include <qcoreapplication>
#include<qdebug>
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);</qdebug></qcoreapplication>

qDebug()<<"hello world";
return a.exec();
}

用界面标签显示Hello World

新建—>其他项—>空的QT项目—>c++ source

1
2
3
4
5
6
7
8
9
10
11
###basic application and HTML WIDEGTS
#include<qapplication>
#include<qlabel>
int main(int argc ,char *argv[])
{</qlabel></qapplication>

QApplication app(argc, argv);
QLabel *label =new QLabel("Hello World");//显示Hello World
label->show();
return app.exec();
}

pushButton按钮

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include "mainwindow.h"
#include "ui_mainwindow.h"

MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
ui->pushButton->setText("close");
}

MainWindow::~MainWindow()
{
delete ui;
}

添加事件—>信号槽—>clicked()—>close(), 这是直接在控件操作的

1
2
3
4
5
void Dialog::on_pushButton_clicked()
{

QMessageBox::information(this,"hello world","hello world");
}

和MFC中的messagebox(),效果一样。

1
2
3
4
5
6
7
void Dialog::on_pushButton_clicked()
{

//QMessageBox::information(this,"hello world","hello world");
ui->lineEdit->setText("Hello world!");
ui->lineEdit_2->setText("sssssss");
}

按一下按钮就在EDIT上面显示字符串”hello world”.

Signal和slot程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include "mainwindow.h"
#include "ui_mainwindow.h"

MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
connect(ui->horizontalSlider,SIGNAL(valueChanged(int)),ui->progressBar,SLOT(setValue(int)));//添加信号
}

MainWindow::~MainWindow()
{
delete ui;
}

或者直接用信号槽和上面的一样

如果再加一条工程条,则代码一样

1
connect(ui->horizontalSlider,SIGNAL(valueChanged(int)),ui->progressBar_2,SLOT(setValue(int)));

只是其中的名字发生了改变ui->progressBar–>ui->progressBar_2

下面为取消信号槽

1
disconnect(ui->horizontalSlider,SIGNAL(valueChanged(int)),ui->progressBar_2,SLOT(setValue(int)));

显示对话框

首先先创立工程文件 DialogTest—->然后点“在这里输入”—–>”输入File”——>在File下面输入所需要的列表”New Window”——->在界面上拖入文本框

然后执行下列程序

1
setCentralWidget(ui->plainTextEdit);//是文本框左右充满整个mainwindow

在DialogTest下面添加新的类MyDialog

然后在”New Window”添加触发事件trigged()

下面往里面添加代码

1
2
3
4
5
6
7
8
9
10
void MainWindow::on_actionNew_Window_triggered()
{

MyDialog dialog;

// dialog.setModal(true);
// dialog.exec();//显示Dialog(第一种显示方法)
dialog.show();这样直接是不好使的,dialog一闪就没

}

第二种dialog显示是在MainWindow类下直接写#include "mydialog.h"

然后在mainwindow.h里面private中声明MyDialog *mydialog

然后直接在

1
2
3
4
5
void MainWindow::on_actionNew_Window_triggered()
{
mydialog=new MyDialog(this);
mydialog->show();
}

layout

直接创建Qdialog,添加事件。

Basic Application and HTML Aware Widgets

对之前的helloworld进行文件的变化(比如加粗或者上颜色)

1
2
3
4
5
6
7
8
9
10
11
12
13
#include<qapplication>
#include<qlabel>
int main(int argc ,char *argv[])
{</qlabel></qapplication>

QApplication app(argc, argv);
QLabel *label =new QLabel("<b>Hello</b> <span color="red" style="color: red;"> <i>World</i></font color =red>");//标签属性
label->show();
return app.exec();
//QLabel *label =new QLabel("</span>
<h2>Hello <span color="red" style="color: red;"> <i>World</i></font color =red></span></h2>
");标签可以随便加
}

Horizontal and Vertical Layouts

界面的布局

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include<qapplication>
#include<qpushbutton>
#include<qhboxlayout>
#include<qvboxlayout>
int main(int argc ,char *argv[])
{</qvboxlayout></qhboxlayout></qpushbutton></qapplication>

QApplication app(argc, argv);
QWidget *window =new QWidget;//创建一个新的window
window->setWindowTitle("my app");//把window的title 设置为 my app
QPushButton *button1=new QPushButton("one");//button1名字为one
QPushButton *button2=new QPushButton("two");
QPushButton *button3=new QPushButton("three");
QHBoxLayout *layout=new QHBoxLayout;//创建一个水平布局变量
//QVBoxLayout *layout=new QVBoxLayout;//创建一个垂直布局变量
layout->addWidget(button1);
layout->addWidget(button2);
layout->addWidget(button3);
window->setLayout(layout);
window->show();
return app.exec();
}

QGridLayout

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include<qapplication>
#include<qtgui>
#include<qtcore></qtcore></qtgui></qapplication>

int main(int argc ,char *argv[])
{

QApplication app(argc, argv);
QWidget *window =new QWidget;
window->setWindowTitle("my app");
QGridLayout *layout=new QGridLayout;
QLabel *label=new QLabel("Name:");
QLineEdit *txtName=new QLineEdit;
layout->addWidget(label,0,0);
layout->addWidget(txtName,0,1);
window->setLayout(layout);
window->show();
return app.exec();
}

如果还要在加一行

1
2
layout->addWidget(label2,1,0);//把名字改变就行,然后里面的参数也要改变
layout->addWidget(txtName2,1,1);

往里面添加按钮

1
layout->addWidget(button,2,0,1,2);//行,列 ,上下宽度,左右长度

Splitters

Qdialog

用到spliter键,我们可以用两个按钮进行演示—->先用水平splider—>水平布局

QDir

调用#include<QDir>

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
#include <qcoreapplication>
#include <qdebug>
#include <qdir>
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
QDir m_dir("C:\\TEXT");
qDebug() << m_dir.exists();
return a.exec();
}
​```</qdir></qdebug></qcoreapplication>

其作用是判断一个某个路径下的文件是否存在
如果存在返回“true”不存在返回"false"

​```c
int main(int argc, char *argv[])
{
QDir m_dir;
foreach (QFileInfo M_Item,m_dir.drives())
{
qDebug() << M_Item.absoluteFilePath();//返回硬盘的路径

}

}

下面这段程序为判断是否这有没”3.txt”文件,如果没有则创建,然后输出”created”,否则输出”already exists”

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
QDir m_dir;
QString m_path="C:\\3.txt";
if(!m_dir.exists(m_path))
{
m_dir.mkpath(m_path);
qDebug()<<"Created";

}
else
{

qDebug()<<"Already Exits";

}
QDir m_dir;
QString m_path="C:/";
m_dir.setPath(m_path);
foreach (QFileInfo m_Item, m_dir.entryInfoList())
{

qDebug()<<m_Item.absoluteFilePath();//为查看当前下的文件目录
}

QFile文件读写

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
27
28
29
30
31
32
33
void write(QString Filename)//文件写
{
QFile mFile(Filename);
if(!mFile.open(QFile::WriteOnly|QFile::Text))
{
qDebug()<<"could not open the file for writting";
return;
}
else
{
QTextStream out(&mFile);
out<<"Hello man!";
qDebug()<<"Write Correctly";

}
mFile.flush();
mFile.close();

}
void read(QString Filename)//文件读
{
QFile mFile(Filename);
if(!mFile.open(QFile::ReadOnly|QFile::Text))
{
qDebug()<<"could not open the file for writting";
return;
}
else
{
QTextStream in(&mFile);
QString s=in.readAll();
qDebug() << s;
qDebug() << "Read Correctly"; } mFile.close(); } ``` ### CheckBox和RadioBOX、Commbox, listwidget ```c void Dialog::on_pushButton_clicked() { if(ui->checkBox->isChecked()//ui->radioBox->isChecked())//判断是否已经选择 { QMessageBox::information(this,"","selected"); } else { QMessageBox::information(this,"","not selected"); } } ``` ``ui->comboBox->addItem("123");//commbox加项`` 显示当前comboBox所选择项的文本 ```c QMessageBox::information(this,"",ui->comboBox->currentText());

显示当前listwidget所选择项的文本

1
2
3
4
QMessageBox::information(this,"hello world",ui->listWidget->currentItem()->text());
QListWidgetItem *item=ui->listWidget->currentItem();
item->setText("123");//选中当前项然后改变的文本
item->setTextColor(Qt::red);//把当前文本变成红色

qtreewidget

首先在Dialog.h里面声明两个函数 (addroot 和 addchild)

1
2
void AddRoot(QString name, QString Description);
void AddChild(QTreeWidgetItem *parent, QString name, QString Description);

然后在构造函数里面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
ui->treeWidget->setColumnCount(2);
AddRoot("HELLO","WORLD");
AddRoot("2","3");
void Dialog::AddRoot(QString name ,QString Description)
{
QTreeWidgetItem *itm=new QTreeWidgetItem(ui->treeWidget);
itm->setText(0,name);
itm->setText(1,Description);
ui->treeWidget->addTopLevelItem(itm);//这如果删除会产生什么情况
AddChild(itm,"one","hello");
AddChild(itm,"two","world");
}
void Dialog::AddChild(QTreeWidgetItem *parent,QString name,QString Description)
{

QTreeWidgetItem *itm=new QTreeWidgetItem(ui->treeWidget//这如果删除会产生什么情况);
itm->setText(0,name);
itm->setText(1,Description);
parent->addChild(itm);
}

在AddRoot()中加入AddChild()

1
2
3
4
5
6
7
8
9
void Dialog::AddRoot(QString name, QString Description)
{
QTreeWidgetItem *itm=new QTreeWidgetItem(ui->treeWidget);
itm->setText(0,name);
itm->setText(1,Description);
ui->treeWidget->addTopLevelItem(itm);
AddChild(itm,"one","hello");
AddChild(itm,"two","world");
}

button里面

1
2
3
4
5
6
7
void Dialog::on_pushButton_clicked()
{

ui->treeWidget->currentItem()->setBackgroundColor(0,Qt::red);//设置第一排颜色为red
ui->treeWidget->currentItem()->setBackgroundColor(1,Qt::yellow);//设置第一排颜色为yellow

}

设置头标签

1
2
3
4
5
6
7
8
9
10
Dialog::Dialog(QWidget *parent) :
QDialog(parent),
ui(new Ui::Dialog)
{
ui->setupUi(this);

ui->treeWidget->setColumnCount(2);
//ui->treeWidget->setHeaderLabel("123");
ui->treeWidget->setHeaderLabels(QStringList()<<"123"<<"456"); AddRoot("HELLO","WORLD"); AddRoot("2","3"); } ui->treeWidget->setHeaderLabel("123");
ui->treeWidget->setHeaderLabels(QStringList()<<"123"<<"456");

Java 笔记

数据类型和运算符

数据类型 字节数
int 4
long 8
char 2
float 4
double 8
bool 1

常量

普通常量

1
2
3
4
5
6
7
8
9
public class Test1 {

final int A = 20;
public static void main(String[] args) {
Test1 test1 = new Test1();
System.out.println(test1.A);
}

}

类常量

1
2
3
4
5
6
7
public class Test1 {

static final int B = 20;
public static void main(String[] args) {
System.out.println(B);
}
}

常量一般都定义为大写字母

运算符

1
2
3
4
5
6
7
public class Test2 {
public static void main(String[] args) {
int a = 30;
System.out.println(a++);
System.out.println(++a);
}
}

输出结果

30
32

数组

binarySearch(), 使用二分搜索法来搜索指定数组,以获得指定对象。

在进行此调用之前,必须根据元素的自然顺序对数组进行升序排序(通过 sort() 方法)

参数:

  • a - 要搜索的数组
  • key - 要搜索的值

返回:

  • 如果它包含在数组中,则返回搜索键的索引;
  • 否则返回 (-(插入点) - 1) 。
  • 插入点被定义为将键插入数组的那一点:即第一个大于此键的元素索引,如果数组中的所有元素都小于指定的键,则为a.length
  • 注意,这保证了当且仅当此键被找到时,返回的值将 >= 0。
1
2
3
4
5
6
7
8
9
10
11
12
13
public class SearchArray
{
public static void main(String[] args)
{
int[] array = {2, 4, 6, 1, 3, 5, 9, 11, -5, 30, 100};
Arrays.sort(array);
int index = Arrays.binarySearch(array, 11);
System.out.println(Arrays.toString(array));
System.out.println("The index of 11 is "+index);
index = Arrays.binarySearch(array, 10);
System.out.println("The index of 10 is "+index);
}
}

输出结果

[-5, 1, 2, 3, 4, 5, 6, 9, 11, 30, 100]
The index of 11 is 8
The index of 10 is -9

String

  1. 串连接:+concat()
  2. 提取子字符串:substring()
  3. 提取字符:charAt()
  4. 获取字符串长度:length
  5. 判断字符串是否相等:equal()

有的登陆系统密码忽略大小写。此时java语言中有个方法就是equalsIgnoreCase(String str),这个方法忽略字符串大小写。

常见疑难: equals 和 == 的区别

java中的数据类型,可分为两类:

  1. 基本数据类型,也称原始数据类型。

byte,short,char,int,long,float,double,boolean

他们之间的比较,应用双等号(==),比较的是他们的值。

  1. 复合数据类型(类)

当他们用(==)进行比较的时候,比较的是他们在内存中的存放地址,所以,除非是同一个new出来的对象,他们的比较后的结果为true,否则比较后结果为false。 JAVA当中所有的类都是继承于Object这个基类的,在Object中的基类中定义了一个equals的方法,这个方法的初始行为是比较对象的内存地址,但在一些类库当中这个方法被覆盖掉了,如String,Integer,Date在这些类当中equals有其自身的实现,而不再是比较类在堆内存中的存放地址了。

对于复合数据类型之间进行equals比较,在没有覆写equals方法的情况下,他们之间的比较还是基于他们在内存中的存放位置的地址值的,因为Object的equals方法也是用双等号(==)进行比较的,所以比较后的结果跟双等号(==)的结果相同。

StringBuffer

  1. 追加:append()
  2. 指定位置插入字符串:insert()
1
2
3
4
5
6
7
8
9
public class StringBufferTest {

public static void main(String[] args) {

StringBuffer strbuf1 = new StringBuffer();
System.out.println(strbuf1.capacity());
System.out.println(strbuf1.length());
}
}

输出结果

16
0

常见疑难 :String 和 StringBufffer 的区别

StringBuffer对象的内容可以修改;String对象一旦产生后就不可以被修改,重新赋值其实是两个对象。

StringBuffer的内部实现方式和String不同,StringBuffer在进行字符串处理时,不生成新的对象,在内存使用上要优于String类。所以在实际使用时,如果经常需要对一个字符串进行修改,例如插入、删除等操作,使用StringBuffer要更加适合一些。

String

在String类中没有用来改变已有字符串中的某个字符的方法,由于不能改变一个java字符串中的某个单独字符,所以在JDK文档中称String类的对象是不可改变的。然而,不可改变的字符串具有一个很大的优点:编译器可以把字符串设为共享的。

StringBuffer

StringBuffer类属于一种辅助类,可预先分配指定长度的内存块建立一个字符串缓冲区。这样使用StringBuffer类的append方法追加字符 比 String使用 + 操作符添加字符 到 一个已经存在的字符串后面有效率得多。因为使用 + 操作符每一次将字符添加到一个字符串中去时,字符串对象都需要寻找一个新的内存空间来容纳更大的字符串,这无凝是一个非常消耗时间的操作。添加多个字符也就意味着要一次又一次的对字符串重新分配内存。使用StringBuffer类就避免了这个问题。
StringBuffer是线程安全的,在多线程程序中也可以很方便的进行使用,但是程序的执行效率相对来说就要稍微慢一些。

类和对象

访问控制符:public ,private ,protected, default

private default protected public
同一类中可见
同一包中对子类可见
同一包中对非子类可见
不同包中对子类可见
不同包中对非子类可见

基础类 :Math,Date

重载和包

重载

重载的实质

  • 方法名相同
  • 参数个数可以不同
  • 参数类型可以不同
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
27
28
29
30
class overload {
// /一个普通的方法,不带参数
void test() {
System.out.println("No parameters");
}

// /重载上面的方法,并且带了一个整型参数
void test(int a) {
System.out.println("a: " + a);
}

// /重载上面的方法,并且带了两个参数
void test(int a, int b) {
System.out.println("a and b: " + a + " " + b);
}

// /重载上面的方法,并且带了一个双精度参数,与上面带一个参数的重载方法不一样
double test(double a) {
System.out.println("double a: " + a);
return a * a;
}

public static void main(String args[]) {
overload o = new overload(); //创建了对象o
o.test();
o.test(2);
o.test(2, 3);
o.test(2.0);
}
}

输出结果

No parameters
a: 2
a and b: 2 3
double a: 2.0

包 package

import com.stx8.test,相当于C#里的using语句,C++里的头文件及namespace

继承和多态

继承

extends

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
class People {
int a;

People() {
a = 1;
}

People(int a) {
this.a = a;
}
}

public class men extends People {
int b;

men(int a, int b) {
super(a);
this.b = b;
}

public static void main(String[] args) {
men m = new men(10, 20); //通过带参构造函数创建men类对象
System.out.println(m.a + " " + m.b); //调用对象的成员变量a和b
}
}

设计类的继承时的建议

不要使用受保护字段,即protected字段

如果不希望自己的类再被扩展,可以在类的声明之前加上final关键字。

构造函数

  • 构造函数名字与类名相同(包括大小写)
  • 一个类可以有多个构造函数
  • 构造函数没有返回值,也不用写void关键字
  • 构造函数总和new运算符一起被调用
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
27
28
29
30
31
32
33
34
35
class Test3_super{
public Test3_super() {
// TODO 自动生成的构造函数存根
System.out.println("父类构造函数");
}
public void fun1(){
System.out.println("父类F1");
}
protected void fun2() {
System.out.println("父类F2");
}
private void fun3() {
System.out.println("父类F3");
}

}

public class Test3 extends Test3_super{

Test3(){
super();
System.out.println("子类构造函数");
}
public void fun2() {
System.out.println("子类F2");

}
public static void main(String[] args) {

Test3 test3 = new Test3();
test3.fun1();
test3.fun2();

}
}

输出结果

父类构造函数
子类构造函数
父类F1
子类F2

java 的单继承性

针对同一方法,子类的访问控制权限只能等于或大于父类。

类之间的关系

多态

多态一定要遵守两个规则:

  • 方法名称一定要一样
  • 传入参数的类型一定要不一样

多态的两种表现形势

  • 重载
  • 覆盖

接口 interface

接口中只有方法名,没有具体实现的方法体。

接口的声明默认是 public,有时候也可以省略。

类实现接口是要使用 implements关键字

在类实现接口时要注意:

  • 声明类需要实现指定接口
  • 提供接口中所有方法的定义
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
27
package com.interfacetest;

interface Alarm {
void alarm();
}

class AlarmDoor implements Alarm {

void open() {
System.out.println("door open");
}
void close() {
System.out.println("door close");
}
public void alarm() {
System.out.println("a door with alarm");
}
}

public class InterfaceTest {

public static void main(String[] args) {

AlarmDoor alarmDoor = new AlarmDoor();
alarmDoor.alarm();
}
}

接口的多重实现

为解决java类中的单继承,一个类可以实现一个接口,也可以实现其他接口。

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
27
28
29
package com.interfacetest;

interface Alarm {
void alarm();
}

interface Window {
void window();
}

class AlarmDoor implements Alarm, Window {

public void window() {
System.out.println("a door with window");
}

public void alarm() {
System.out.println("a door with alarm");
}
}

public class InterfaceTest2 {

public static void main(String[] args) {

AlarmDoor alarmDoor = new AlarmDoor();
alarmDoor.alarm();
}
}

接口的属性

接口不是一个类,不可以使用关键字new来生成一个接口的实例。但是可以声明一个接口变量 “school sc”.

如果要生成一个接口的实例,可以让接口变量指向一个已经实现该接口的类的对象。

1
school sc = new student();

在接口中,不能声明实例字段及静态方法,但可以声明常量。接口不一定要有方法,可以全是常量。

接口的继承

和类一样,使用extends关键字实现

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
27
28
29
30
31
32
package com.interfacetest;

interface Lock {
void lock();
}

interface HighLock extends Lock{
void hignlock();
}

class Door implements HighLock {

@Override
public void hignlock() {
// TODO 自动生成的方法存根
System.out.println("WOW high lock");
}
@Override
public void lock() {
// TODO 自动生成的方法存根
System.out.println("just lock");
}
}

public class InterfaceTest3 {

public static void main(String[] args) {

Door door = new Door();
door.lock();
}
}

内部类

内部类就是在类的内部再创建一个类。

内部类的基本结构

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
package com.interclass;

//外部类
class Out {
private int age = 12;

//内部类
class In {
public void print() {
System.out.println(age);
}
}
}

public class InterclassTest1 {
public static void main(String[] args) {

Out.In in = new Out().new In();
in.print();

Out out = new Out();
Out.In in2 = out.new In();
in2.print();

}
}

匿名内部类

匿名内部类也就是没有名字的内部类,正因为没有名字,所以匿名内部类只能使用一次,它通常用来简化代码编写。但使用匿名内部类还有个前提条件:必须继承一个父类或实现一个接口

不使用匿名内部类来实现抽象方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.interclass;

abstract class Person {
public abstract void eat();
}

class Child extends Person {
public void eat() {
System.out.println("eat something");
}
}

public class InterclassTest2 {
public static void main(String[] args) {
Person p = new Child();
p.eat();
}
}

运行结果:

eat something

可以看到,我们用Child继承了Person类,然后实现了Child的一个实例,将其向上转型为Person类的引用,但是,如果此处的Child类只使用一次,那么将其编写为独立的一个类岂不是很麻烦?

这个时候就引入了匿名内部类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.interclass;

abstract class Person1 {
public abstract void eat();
}

public class InterclassTest3 {
public static void main(String[] args) {
Person p = new Person() {
public void eat() {
System.out.println("eat something");
}
};
p.eat();
}
}

运行结果:

eat something

由上面的例子可以看出,只要一个类是抽象的或是一个接口,那么其子类中的方法都可以使用匿名内部类来实现

最常用的情况就是在多线程的实现上,因为要实现多线程必须继承Thread类或是继承Runnable接口

Thread类的匿名内部类实现:

1
2
3
4
5
6
7
8
9
10
11
12
public class Demo {
public static void main(String[] args) {
Thread t = new Thread() {
public void run() {
for (int i = 1; i <= 5; i++) {
System.out.print(i + " ");
}
}
};
t.start();
}
}

Runnable接口的匿名内部类实现

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
public class Demo {
public static void main(String[] args) {
Runnable r = new Runnable() {
public void run() {
for (int i = 1; i <= 5; i++) { System.out.print(i + " "); } } }; Thread t = new Thread(r); t.start(); } } ``` 内部类的好处: - 内部类的对象能够访问创建他的对象的所有方法和属性,包括私有数据 - 对于同一个包中的其他类来说,内部类是隐形的 。 - 匿名内部类可以很方便的定义回调 - 使用内部类可以方便的编写时间驱动的程序。 作为一个单独的类,只能有 `default` 和 `public` 两种访问控制符,但是作为内部类,可以使用 `private` 控制符。当内部类设置为 `private` 时,包含此内部类的外部类的方法才可以访问它。 ### 使用内部类来访问对象 内部类这个机制之所以出现,是因为存在如下两个目的: - 可以让程序中逻辑上相关的类结合在一起 - 内部类可以直接访问外部类的成员。 ## 常见疑难 接口与继承的区别: - 属性:接口中的所有属性都是公开静态常量,继承则无所谓 - 方法:接口中所有方法都是公开抽象方法,继承中所有方法不一定是抽象的 - 接口方法:接口没有构造器,继承有构造器 ## 抽象和封装 抽象就是讲拥有共同方法和属性的对象提取出来,提取后,重新设计一个更加通用、更加大众的类,这个类成为抽象类。 ### 抽象类 `abstract` 具有一个或多个抽象方法的类,本身就要被定义为抽象类。含有抽象方法的类一定是抽象类。 抽象类不仅可以有抽象方法,也可以又具体的方法,一个类中只要有一个抽象方法,就是抽象类。 抽象类中不一定含有抽象的方法,也可以全部都是具体的方法。 抽象类是可以继承的,如果子类没有实现抽象类的全部抽象方法,那么子类也是抽象类。如果实现了抽象类的全部抽象方法,那么子类就不是抽象类。 抽象类不可以被实例化。但是可以声明一个抽象类的变量指向具体子类的对象。 抽象类的好处在于,有的方法在父类中不想实现时,可以不用实现。 ```java package com.abstracttest; abstract class Door{ abstract void open(); abstract void close(); } class MyDoor extends Door{ @Override void open() { // TODO 自动生成的方法存根 System.out.println("i can open"); } @Override void close() { // TODO 自动生成的方法存根 System.out.println("i can close"); } } public class AbstractTest1 { public static void main(String[] args) { MyDoor myDoor = new MyDoor(); myDoor.close(); } } ``` ## 常见疑难 抽象与接口的区别: 共同点 - 都不能创建实例对象 - 可以声明变量,通过指向子类或实现类的对象类,来创建对象实例。 不同点 - Java 不支持多重继承,即一个子类只能有一个父类,但一个子类可以实现多个接口。 - 接口内不能有实例字段,只能有静态变量,抽象类可以拥有实例字段 - 接口内方法自动设置为 `public`的,抽象类中的方法必须手动声明访问控制符。 ## 枚举 `enum` ## 反射 程序自己能够检查自身信息。反射使得java语言具有了“动态性”,即程序首先会检查某个类中的方法、属性等信息,然后再动态的调用或动态的创建该类或该类的对象。 ### 反射类的基石 ――`Class`类 任何事物都可以用类表示,那么java中的类可以用一个什么类表示呢? 从JDK1.2开始,就出现了Class类,该类描述Java中的一切事物,该类描述了关于类事务的类名字、类的访问属性、类所属的包名等。 ### 反射的基本应用 所谓反射就是把java类中的各种成分映射成相应的java类。 不仅java类,可以用`Class`类的对象表示,而java类的各种成员:成员变量、方法、构造方法、包等也可以用相应的类表示。 Java反射机制主要提供了以下功能: - 在运行时判断任意一个对象所属的类; - 在运行时构造任意一个类的对象; - 在运行时判断任意一个类所具有的成员变量和方法; - 在运行时调用任意一个对象的方法。 反射一般会设计如下类 : - `Class` : 表示一个类的类 - `Filed` : 表示属性的类 - `Method` : 表示方法的类 - `Constructor` : 表示类的构造方法的类 > `Class`类位于java.lang包中,而后面3个的类都位于java.lang.reflect包中

编写Java反射程序的步骤:

- 必须首先获取一个类的Class对象
- 然后分别调用Class对象中的方法来获取一个类的属性/方法/构造方法的结构

现有一个类:

​```java
package com.reflection;
public class TestReflection {

private String username;
private String password;
private int[] age;

public void setUserName(String username) {
this.username = username;
}

private void setPassWord(String password) {
this.password = password;
}
}
</code></pre>

获取一个类的Class对象:

<pre><code class="java">TestReflection tfReflection = new TestReflection();
Class c1 = TestReflection.class;
Class c2 = Class.forName("com.reflection.TestReflection");
Class c3 = tfReflection.getClass();
</code></pre>

获取指定的包名:

<pre><code class="java">String package01 = c1.getPackage().getName();
</code></pre>

获取类的修饰符:

<pre><code class="java">int mod = c1.getModifiers();
String modifier = Modifier.toString(mod);
</code></pre>

获取指定类的完全限定名:

<pre><code class="java">String className = c1.getName();
</code></pre>

获取指定类的父类:

<pre><code class="java">Class superClazz = c1.getSuperclass();
String superClazzName = superClazz.getName();
</code></pre>

获取实现的接口:

<pre><code class="java">Class[] interfaces = c1.getInterfaces();
for (Class t : interfaces) {
System.out.println("interfacesName = " + t.getName());
}
</code></pre>

获取指定类的成员变量:

<pre><code class="java">Field[] fields = c1.getDeclaredFields();
for (Field field : fields) {
modifier = Modifier.toString(field.getModifiers()); // 获取每个字段的访问修饰符
Class type = field.getType(); // 获取字段的数据类型所对应的Class对象
String name = field.getName(); // 获取字段名
if (type.isArray()) { // 如果是数组类型则需要特别处理
String arrType = type.getComponentType().getName() + "[]";
System.out.println("" + modifier + " " + arrType + " " + name + ";");
}
else {
System.out.println("" + modifier + " " + type + " " + name + ";");
}
}
</code></pre>

获取类的构造方法:

<pre><code class="java">Constructor[] constructors = c1.getDeclaredConstructors();
for (Constructor constructor : constructors) {
String name = constructor.getName(); // 构造方法名
modifier = Modifier.toString(constructor.getModifiers()); // 获取访问修饰符
System.out.println("" + modifier + " " + name + "(");
Class[] paramTypes = constructor.getParameterTypes(); // 获取构造方法中的参数
for (int i = 0; i < paramTypes.length; i++) { if (i > 0) {
System.out.print(",");
}
if (paramTypes[i].isArray()) {
System.out.println(paramTypes[i].getComponentType().getName() + "[]");
} else {
System.out.print(paramTypes[i].getName());
}
}
System.out.println(");");
}
</code></pre>

获取成员方法:

<pre><code class="java">Method[] methods = c1.getDeclaredMethods();
for (Method method : methods) {
modifier = Modifier.toString(method.getModifiers());
Class returnType = method.getReturnType(); // 获取方法的返回类型
if (returnType.isArray()) {
String arrType = returnType.getComponentType().getName() + "[]";
System.out.print("" + modifier + " " + arrType + " " + method.getName() + "(");
} else {
System.out.print("" + modifier + " " + returnType.getName() + " " + method.getName() + "(");
}
Class[] paramTypes = method.getParameterTypes();
for (int i = 0; i < paramTypes.length; i++) { if (i > 0) {
System.out.print(",");
}
if (paramTypes[i].isArray()) {
System.out.println(paramTypes[i].getComponentType().getName() + "[]");
} else {
System.out.print(paramTypes[i].getName());
}
}
System.out.println(");");
}
</code></pre>

反射调用方法,可以通过Method类的invoke方法实现动态方法的调用:

<code>public Object invoke(Object obj, Object... args)</code>
第一个参数代表对象
第二个参数代表执行方法上的参数

若反射要调用类的某个私有方法,可以在这个私有方法对应的Mehtod对象上先调用<code>setAccessible(true)</code>

<pre><code class="java">public static void test02() throws InstantiationException, IllegalAccessException, SecurityException,
NoSuchMethodException, IllegalArgumentException, InvocationTargetException {

Class c1 = TestReflection.class;
TestReflection t1 = (TestReflection) c1.newInstance(); // 利用反射来创建类的对象

Method method = c1.getDeclaredMethod("setUserName", String.class);
method.invoke(t1, "Java反射的学习");
method = c1.getDeclaredMethod("setPassWord", String.class);
method.setAccessible(true);
method.invoke(t1, "反射执行某个Private修饰的方法");

}

</code></pre>

<h3>反射的一些应用</h3>

既然String是不可变字符串对象,如何才能改变让其可变?[反射的一些应用][1]

<pre><code class="java">public static void stringReflection() throws Exception {

String s = "Hello World";

System.out.println("s = " + s); //Hello World

//获取String类中的value字段
Field valueField = String.class.getDeclaredField("value");

//改变value属性的访问权限
valueField.setAccessible(true);

char[] value = (char[]) valueField.get(s);

//改变value所引用的数组中的第5个字符
value[5] = '_';

System.out.println("s = " + s); //Hello_World
}
</code></pre>

既然String对象中没有对外提供可用的public setters等方法,因此只能通过Java中的反射机制实现。因此,前文中说到的String是不可变字符串对象只是针对“正常情况下”。而非必然。

<blockquote>
Java的反射机制的概念:
在Java运行时环境中,对于任意一个类,能否知道这个类的哪些属性和方法?对于任意一个对象,能否调用它的任意一个方法?答案是肯定的。这种动态获取类的信息以及动态调用对象的方法的功能来自于Java语言的反射(Reflection)机制。

Reflection是Java被视为动态(准动态)语言的一个关键性质。这个机制允许程序在运行时透过Reflection APIs取得任何一个已知名称的class的内部信息,包括modifiers(诸如static、public等等)、superclass(例如Object)、实现interfaces(例如Serializable),也包括fields和methods的所有信息,并可于运行时改变fields内容或调用methods。
</blockquote>

<h2>标注 Annotation</h2>

在实际的应用中期可以部分或全部的取代传统的XML等部署描述文件。之所以要出现标注特性,是因为部署描述文件很复杂,在具体编写时很容易出错。

<h3>标注的简单使用</h3>

<code>@SuppressWarning</code>

<pre><code class="java">public class SimpleAnnotation {
@SuppressWarnings("deprecation")
public static void main(String[] args) {
System.runFinalizersOnExit(true);
}
}
</code></pre>

<h3>几个简单的内置标注</h3>

<code>@Override</code>

<pre><code class="java">package com.Annotation;

class People{
public String toString() {
return "people name";
}
}

class Student extends People{
@Override
public String toString() {
return "student name";

}
}
public class Annotation_Test {
public static void main(String[] args) {
Student student = new Student();
System.out.println(student.toString());
}

}
</code></pre>

<blockquote>
<code>@Override</code> 是方法标注,只能作用于方法,在覆盖父类方法却又写错了方法名的时候发挥作用。
</blockquote>

<code>@Deprecated</code>

很多时候,设计了一个包含<code>sayHello()</code>方法的类<code>Hello.java</code>,但是经过一段时间发现,<code>sayHello1()</code>可以更好更快的实现相同的功能。但是这个时候如果去掉方法<code>sayHello()</code>,那么调用该方法的类就会出现错误。为了兼容之前的类,而又不建议新设计的类使用方法<code>sayHello()</code>,就需要把<code>Hello.java</code>中的方法<code>sayHello()</code>做<code>@Deprecated</code>标注。

<pre><code class="java">package com.Annotation;

class Hello{

@Deprecated
public void sayHello(){
System.out.println("已经过时的方法");
}
public void sayHello1() {
System.out.println("现在的方法");
}
}
public class Deprecated_Test {
public static void main(String[] args) {
Hello hello = new Hello();
hello.sayHello();
hello.sayHello1();

}

}
</code></pre>

当一个类或者类成员使用<code>@Deprecated</code>修饰的话,编译器将不鼓励使用这个被标注的程序元素,而且这种修饰具有一定的“延续性”,即在代码中通过继承或者覆盖使用了这个过时的类型或者成员,虽然继承或者覆盖后的类型并不是被声明为<code>@Deprecated</code>,但是编译器仍要报警。

<blockquote>
<code>@Deprecated</code>标注不仅可以用在方法前面,而且可以用在参数或类的前面。
</blockquote>

<code>@SuppressWarning</code>

可以用作标注类,属性、方法等成员,主要用于屏蔽警告。该标注于前面两个标注最大不同点在于其带有参数,并且参数可以是一个,可以是多个。参数的值为警告的类型。如:

<ul>
<li>已过时的警告 : deprecation</li>
<li>没有使用警告 : unused</li>
<li>类型不安全警告 : unchecked</li>
</ul>

<blockquote>
当<code>@SuppressWarning</code> 接收的参数为多个值得时候,必须使用数组的方式为参数赋值。例如@SuppressWarning({"deprecation","unused","unchecked"})
</blockquote>

<h2>泛型 <code><E></code></h2>

所谓泛型,其本质就是实现参数化类型,也就是说所操作的数据类型被指定为一个参数。

<h3>泛型概念的提出:</h3>

<pre><code class="java">public class GenericTest {

public static void main(String[] args) {
List list = new ArrayList();
list.add("qqyumidi");
list.add("corn");
list.add(100);

for (int i = 0; i < list.size(); i++) { String name = (String) list.get(i); // 1 System.out.println("name:" + name); } } } ``` 运行结果 name:qqyumidi name:corn Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String at com.fan.GenericTest.main(GenericTest.java:15) > Java2的集合框架,抽其核心,主要有三种:List、Set和Map。
需要注意的是,这里的 Collection、List、Set和Map都是接口(Interface),不是具体的类实现。 List lst = new ArrayList(); 这是我们平常经常使用的创建一个新的List的语句,在这里, List是接口,ArrayList才是具体的类。
常用集合类的继承结构如下:
Collection<--List<--Vector
Collection<--List<--ArrayList
Collection<--List<--LinkedList
Collection<--Set<--HashSet
Collection<--Set<--HashSet<--LinkedHashSet
Collection<--Set<--SortedSet<--TreeSet
Map<--SortedMap<--TreeMap
Map<--HashMap

定义了一个List类型的集合,先向其中加入了两个字符串类型的值,随后加入一个Integer类型的值。这是完全允许的,因为此时list默认的类型为Object类型。在之后的循环中,由于忘记了之前在list中也加入了Integer类型的值或其他编码原因,很容易出现类似于//1中的错误。因为编译阶段正常,而运行时会出现“java.lang.ClassCastException”异常。因此,导致此类错误编码过程中不易发现。

在如上的编码过程中,我们发现主要存在两个问题:

1.当我们将一个对象放入集合中,集合不会记住此对象的类型,当再次从集合中取出此对象时,改对象的编译类型变成了Object类型,但其运行时类型任然为其本身类型。

2.因此,//1处取出集合元素时需要人为的强制类型转化到具体的目标类型,且很容易出现“java.lang.ClassCastException”异常。

那么有没有什么办法可以使集合能够记住集合内元素各类型,且能够达到只要编译时不出现问题,运行时就不会出现“java.lang.ClassCastException”异常呢?答案就是使用泛型。

### 什么是泛型?

泛型,即“参数化类型”。一提到参数,最熟悉的就是定义方法时有形参,然后调用此方法时传递实参。那么参数化类型怎么理解呢?顾名思义,就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),然后在使用/调用时传入具体的类型(类型实参)。

​```java
public class GenericTest {

public static void main(String[] args) {
/*
List list = new ArrayList();
list.add("qqyumidi");
list.add("corn");
list.add(100);
*/

List<string> list = new ArrayList<string>();
list.add("qqyumidi");
list.add("corn");
//list.add(100); // 1 提示编译错误</string></string>

for (int i = 0; i < list.size(); i++) {
String name = list.get(i); // 2
System.out.println("name:" + name);
}
}
}

自定义泛型接口、泛型类和泛型方法

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
27
28
public class GenericTest1 {

public static void main(String[] args) {

Box<string> name = new Box<string>("corn");
Box<integer> num = new Box<integer>(4);
System.out.println("name:" + name.getData());
System.out.println("num:" + num.getData());
}</integer></integer></string></string>

}

class Box<t> {</t>

private T data;

public Box() {

}

public Box(T data) {
this.data = data;
}

public T getData() {
return data;
}
}

运行结果

name:corn
num:4

在泛型接口、泛型类和泛型方法的定义过程中,我们常见的如T、E、K、V等形式的参数常用于表示泛型形参,由于接收来自外部使用时候传入的类型实参。那么对于不同传入的类型实参,生成的相应对象实例的类型是不是一样的呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class GenericTest {

public static void main(String[] args) {

Box<string> name = new Box<string>("corn");
Box<integer> age = new Box<integer>(712);</integer></integer></string></string>

System.out.println("name class:" + name.getClass()); // com.qqyumidi.Box
System.out.println("age class:" + age.getClass()); // com.qqyumidi.Box
System.out.println(name.getClass() == age.getClass()); // true

}

}

运行结果

name class:class com.fan.Box
num class:class com.fan.Box
true

由此,我们发现,在使用泛型类时,虽然传入了不同的泛型实参,但并没有真正意义上生成不同的类型,传入不同泛型实参的泛型类在内存上只有一个,即还是原来的最基本的类型(本实例中为Box),当然,在逻辑上我们可以理解成多个不同的泛型类型。

究其原因,在于Java中的泛型这一概念提出的目的,导致其只是作用于代码编译阶段,在编译过程中,对于正确检验泛型结果后,会将泛型的相关信息擦出,也就是说,成功编译过后的class文件中是不包含任何泛型信息的。泛型信息不会进入到运行时阶段。

对此总结成一句话:泛型类型在逻辑上看以看成是多个不同的类型,实际上都是相同的基本类型。

关于泛型通配符

接着上面的结论,我们知道,Box和Box实际上都是Box类型,现在需要继续探讨一个问题,那么在逻辑上,类似于Box<Number>Box<Integer>是否可以看成具有父子关系的泛型类型呢?

所有的包装类(Integer、Long、Byte、Double、Float、Short)都是抽象类Number的子类。

关于泛型的一些特性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class GenericTest {

public static void main(String[] args) {

Box<number> name = new Box<number>(99);
Box<integer> age = new Box<integer>(712);</integer></integer></number></number>

getData(name);
getData(age); // 1

}

public static void getData(Box<number> data){
System.out.println("data :" + data.getData());
}</number>

}

通过提示信息,我们知道Box<Number>在逻辑上不能视为Box<Integer>的父类.

我们需要一个在逻辑上可以用来表示同时是Box<Integer>Box<Number>的父类的一个引用类型,由此,类型通配符应运而生。

类型通配符一般是使用 ?代替具体的类型实参。注意了,此处是类型实参,而不是类型形参!且Box<?>在逻辑上是Box<Integer>Box<Number>…等所有Box<具体类型实参>的父类。由此,我们依然可以定义泛型方法,来完成此类需求。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class GenericTest {

public static void main(String[] args) {

Box<string> name = new Box<string>("corn");
Box<integer> age = new Box<integer>(712);
Box<number> number = new Box<number>(314);</number></number></integer></integer></string></string>

getData(name);
getData(age);
getData(number);
}

public static void getData(Box<?> data) {
System.out.println("data :" + data.getData());
}

}

有时候,我们还可能听到类型通配符上限和类型通配符下限。具体有是怎么样的呢?

在上面的例子中,如果需要定义一个功能类似于getData()的方法,但对类型实参又有进一步的限制:只能是Number类及其子类。此时,需要用到类型通配符上限。

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
public class GenericTest {

public static void main(String[] args) {

Box<string> name = new Box<string>("corn");
Box<integer> age = new Box<integer>(712);
Box<number> number = new Box<number>(314);</number></number></integer></integer></string></string>

getData(name);
getData(age);
getData(number);

//getUpperNumberData(name); // 1
getUpperNumberData(age); // 2
getUpperNumberData(number); // 3
}

public static void getData(Box<?> data) {
System.out.println("data :" + data.getData());
}

public static void getUpperNumberData(Box<? extends Number> data){
System.out.println("data :" + data.getData());
}

}

此时,显然,在代码//1处调用将出现错误提示,而//2 //3处调用正常。

类型通配符上限通过形如Box<? extends Number>形式定义,相对应的,类型通配符下限为Box<? super Number>形式,其含义与类型通配符上限正好相反,在此不作过多阐述了。

封装

线程

程序是计算机指令集合,它以文件形式存储在磁盘上

进程就是一个执行中的程序,每一个进程都有一个独立的内存空间和资源系统。

线程是CPU调度和分配的基本单位,一个进程可以由多个线程组成,而这多个线程共享同一个存储空间,这使得线程间的通信比较容易。

线程的创建

创建线程的方法有两种 :

  • 通过实现Runnable接口的方式创建线程
  • 通过集成Threa类来创建线程

通过实现Runnable接口的方式创建线程

在java中,线程是一种对象,而不是所有的对象都可以被称为线程,只有实现了Runnable接口的对象才可以被称为线程。

Runnable接口的定义:

public interface new Runnable() {
public abstract void run() ;
}

只有实现了该接口的类才有资格被称为线程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.threadtest;

class ThreadTest implements Runnable {
public void run() {
System.out.println("thread 1");
}
}
class ThreadTest2 implements Runnable{
public void run() {
System.out.println("thread 2");
}
}
public class ThreadTest1 {
public static void main(String[] args) {
ThreadTest test = new ThreadTest();
ThreadTest2 test2 = new ThreadTest2();
Thread thread = new Thread(test);
Thread thread2 = new Thread(test2);
thread.start();
thread2.start();
}

}

在java技术中,线程通常是通过调度模块来执行的。所谓抢占式调度模式是指:许多线程处于可以运行状态,即等待状态,但实际只有一个线程在运行。该线程一直运行直到他终止或是另一个优先级更高的线程变成可运行状态。

通过继承Thread类的方式创建线程

其实Thread本身也实现了Runnable接口,所以只要让一个类继承Thread类,并覆盖run()方法,也会创建进程。

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
27
package com.threadtest;

class Threadtest_3 extends Thread {
public void run() {
System.out.println("thread 1");

}
}
class ThreadTest_4 extends Thread{
public void run() {
System.out.println("thread 2");

}
}
public class ThreadTest2 {
public static void main(String[] args) {
Threadtest_3 threadtest_3 =new Threadtest_3();
ThreadTest_4 threadTest_4 = new ThreadTest_4();

Thread thread3 = new Thread(threadtest_3);
Thread thread4 = new Thread(threadTest_4);

thread3.start();
thread4.start();

}
}

线程的状态

线程包括5种状态:新建状态、就绪状态、运行状态、阻塞状态和死亡状态。

新建状态

线程对象通过new关键字已经建立,在内存中有一个活跃的对象,但是没有启动该线程,所以它仍不能做任何事情,此时线程处于新建状态,程序中没有运行线程中的代码,如果线程要运行需要处于就绪状态。

就绪状态

一个线程一旦调用了start()方法,该线程就处于就绪状态。此时线程等待CPU时间片,一旦获得CPU时间周期,一旦获得CPU周期,线程就可以执行。这种状态下的任何时刻,线程是否执行完全取决于系统的调度程序。

运行状态

一旦处于就绪状态的线程获得CPU执行周期,就处于运行状态,执行多线程代码部分的运算。线程一旦运行,只是在CPU周期内获得执行权利,而一旦CPU的时间片用完,操作系统会给其他的线程运行的机会,而剥夺当前线程的执行。在选择哪个线程可以执行时,操作系统的调度程序会考虑现成的优先级,该内容后续讲解。

阻塞状态

该状态下线程无法运行,必须满足一定条件条件后才可以执行。如果线程处于阻塞状态,JVM调度机不会为其分配CPU周期。而线程满足一定条件就被解除阻塞,线程处于就绪状态,此时就获得了被执行的机会。当发生以下情况的时候线程会进入阻塞状态:

  • 线程正在等待一个输入输出操作,该操作完成前不会返回其调用者。
  • 线程调用了wait()方法或是sleep()方法。
  • 线程需要满足某种条件之后可以继续执行。

死亡状态

线程一旦退出run()方法就处于死亡状态。

线程的优先级

线程的执行顺序是一种抢占方式,优先级高的比优先级低的要获得更多的执行时间,如果想让一个线程比其他线程有更多的运行时间,可以通过设置线程的优先级解决。

具体方法如下:

public final void setPriority(int newPriority);

其中,newPriority是一个1~10的正整数,数值越大,优先级别越高。系统定义了一些常用的数值如下:

  • public final static int MIN_PRIORITY = 1 :表示最低优先级
  • public final static int MAX_PRIORITY = 10 :表示最高优先级
  • public final static int NORM_PRIORITY = 5 :表示默认优先级
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
27
28
29
30
31
32
33
34
35
36
37
package com.threadtest;

import org.omg.CORBA.PUBLIC_MEMBER;

class Threadtest_3 extends Thread {
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(i);
}

}
}
class ThreadTest_4 extends Thread{
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("hello"+ i);
}

}
}
public class ThreadTest2 {
public static void main(String[] args) {

Threadtest_3 threadtest_3 =new Threadtest_3();
ThreadTest_4 threadTest_4 = new ThreadTest_4();

Thread thread3 = new Thread(threadtest_3);
Thread thread4 = new Thread(threadTest_4);

thread3.setPriority(3);
thread4.setPriority(Thread.MIN_PRIORITY);

thread3.start();
thread4.start();

}
}

线程的休眠与唤醒

线程的休眠 sleep()

线程处于等待状态。

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
27
28
package com.cjgong.avd;
///这是一个主运行类
///创建一个线程对象,让其运行
public class thread5
{
public static void main(String[] args)
{
compute27 t=new compute27();
t.start();
}
}
///创建一个线程类,在这个类中,通过休眠来输出不同结果
class compute27 extends Thread
{
int i=0;
public void run()
{
System.out.println("在工作中,不要打扰");
try
{
sleep(1000000);
}
catch(Exception e)
{
System.out.println("哦,电话来了");
}
}
}

线程唤醒 interrup()

当一个线程处于休眠状态,如果开始设置了休眠时间是1000ms,但是想在休眠了500ms的时候,让它继续执行,这时候就可以使用线程唤醒功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package com.cjgong.avd;
public class thread6
{
public static void main(String[] args)
{
compute28 t=new compute28();
t.start();
t.interrupt();
}
}
///创建一个线程类,在这个类中,通过休眠,让线程运行输出不同的结果
class compute28 extends Thread
{
int i=0;
public void run()
{
System.out.println("在工作中,不要打扰");
try
{
sleep(1000000);
}
catch(Exception e){System.out.println("哦,电话来了");}
}
}

线程让步 yield()

所谓线程让步,就是使当前正在运行的线程对象退出运行状态,让其他线程运行。

这个方法不能讲运行权让给指定的线程,只是允许这个线程把运行权让出来,至于给谁,就看谁能抢到。

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package com.cjgong.avd;
///这是一个主运行类
///在主运行方法中,通过创建两个线程对象,让其交替执行
public class thread7
{
public static void main(String[] args)
{
compute29 t=new compute29();
compute30 t1=new compute30();
t.start();
t1.start();
}
}
///创建一个线程类
///通过循环语句来输出十个整型数据
///通过让步程序让此线程停止运行
class compute29 extends Thread
{
int i=0;
public void run()
{
for(int i=0;i<10;i++)
{
System.out.println(i);
yield();
}
}
}
///创建一个线程类
///通过循环语句来输出说明语句
class compute30 extends Thread
{
public void run()
{
for(int i=0;i<10;i++)
{
System.out.println("这个数字是:"+i);
}
}
}

线程同步

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
27
28
29
30
31
package com.cjgong.avd;
//这是一个主运行类
///在主运行方法中,通过创建两个线程对象,让其交替执行
public class thread8
{
public static void main(String[] args)
{
compute31 t=new compute31('a');
compute31 t1=new compute31('b');
t.start();
t1.start();
}
}
///创建一个线程类
///在这线程类中,使用循环语句输出字符
class compute31 extends Thread
{
char ch;
compute31(char ch)
{
this.ch=ch;
}

public void run()
{
for(int i=0;i<10;i++)
{
System.out.print(ch);
}
}
}

两个线程循环输出,就会出现抢占现象。解决这个问题的办法是可以使用线程同步,解决同步的两个方法:

  • 同步块
  • 同步化方法

同步块

同步块是使具有某个对象监视点的线程,获得运行权限的一种方法,每个对象只能在拥有这个监视点的情况下,才能获得运行权限。

同步块的结构如下:

synchronized (bObject) {
程序段
}

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
package com.cjgong.avd;
//这是一个主运行类
///在主运行方法中,通过创建两个线程对象,让其交替执行
public class thread8
{
public static void main(String[] args)
{
compute31 t=new compute31('a');
compute31 t1=new compute31('b');
t.start();
t1.start();
}
}
///创建一个线程类
///在这线程类中,使用循环语句输出字符
class compute31 extends Thread
{
char ch;
static Object bObject = new Object();
compute31(char ch)
{
this.ch=ch;
}

public void run()
{
synchronized (bObject) {

for(int i=0;i<10;i++) { System.out.print(ch); } } } } ``` ### 同步化方法 同步化方法就是对整个方法进行同步: 结构如下: synchronized void f() { 代码 } ```java //这是一个主运行类 ///在主运行方法中,通过创建三个线程对象,让其交替执行 public class thread11 { public static void main(String[] args) { compute34 t=new compute34(); new Thread(t).start(); new Thread(t).start(); new Thread(t).start(); } } ///创建一个线程类 ///在这线程类中,使用循环语句输出字符 ///在run方法中,使用同步块来给线程加一把锁 class compute34 extends Thread { int i=10; static Object obj=new Object(); public void print() { System.out.println(Thread.currentThread().getName()+":"+i); i--; } public void run() { while(i>0) { synchronized(obj) { print(); } try { sleep(1000); } catch(Exception e){} } } } ``` ## 异常的处理和内存管理 ## Java 输入和输出 Java程序类库中包含大量的输入输出类,提供不同情况的不同功能。其中包括: - 关于文件操作的类 `File` - 关于以字节方式访问文件的类 `InputStream`和类`OutStream` - 关于以字符方式访问文件的类`Reader`和类`Writer` > 在编写程序的过程中,若是要使用输入输出类的方法和属性值,就需要引入`java.io`类

## Java 的I/O操作

Java中I/O操作主要是指使用Java进行输入,输出操作.

Java所有的I/O机制都是基于数据流进行输入输出,这些数据流表示了字符或者字节数据的流动序列。Java的I/O流提供了读写数据的标准方法。任何Java中表示数据源的对象都会提供以数据流的方式读写它的数据的方法。

总结的基本概念如下:

数据流:

一组有序,有起点和终点的字节的数据序列。包括输入流和输出流。

输入流:

程序从输入流读取数据源。数据源包括外界(键盘、文件、网络…),即是将数据源读入到程序的通信通道

输出流:

程序向输出流写入数据。将程序中的数据输出到外界(显示器、打印机、文件、网络…)的通信通道。

采用数据流的目的就是使得输出输入独立于设备。

数据流分类:

流序列中的数据既可以是未经加工的原始二进制数据,也可以是经一定编码处理后符合某种格式规定的特定数据。因此Java中的流分为两种:
1) 字节流:数据流中最小的数据单元是字节
2) 字符流:数据流中最小的数据单元是字符, Java中的字符是Unicode编码,一个字符占用两个字节。

### 标准I/O

### java.IO层次体系结构

在整个Java.io包中最重要的就是5个类和一个接口。

5个类指的是`File`、`OutputStream`、`InputStream`、`Writer`、`Reader`;

1个接口指的是`Serializable`.

掌握了这些IO的核心操作那么对于Java中的IO体系也就有了一个初步的认识了。

主要的类如下:

File(文件特征与管理):用于文件或者目录的描述信息,例如生成新目录,修改文件名,删除文件,判断文件所在路径等。
InputStream(二进制格式操作):抽象类,基于字节的输入操作,是所有输入流的父类。定义了所有输入流都具有的共同特征。
OutputStream(二进制格式操作):抽象类。基于字节的输出操作。是所有输出流的父类。定义了所有输出流都具有的共同特征。

Java中字符是采用Unicode标准,一个字符是16位,即一个字符使用两个字节来表示。为此,JAVA中引入了处理字符的流。

Reader(文件格式操作):抽象类,基于字符的输入操作。
Writer(文件格式操作):抽象类,基于字符的输出操作。
RandomAccessFile(随机文件操作):它的功能丰富,可以从文件的任意位置进行存取(输入输出)操作。

### 文件或目录信息的处理 `File`

`File` 类提供了与文件或目录相关的信息

文件处理方法:

​```java
package com.filetest;

import java.io.File;

public class FileTest1 {
public static void main(String[] args) {

File file = new File("d:\\","file.txt");
System.out.println(file.getName());
System.out.println(file.getParent());
System.out.println(file.getPath());
}
}

运行结果:

file.txt
d:
d:\file.txt

文件和目录的操作:

在Java中,目录被当做一种特殊的文件使用。类File是唯一代表磁盘文件对象的类。

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
package com.cjgong.chaozuo;

import java.io.*;

///通过print方法来判断这个文件类对象的性质
///通过print1方法来获取文件对象的信息
///通过print2方法来获取文件对象的信息
public class file1 {
public void print(File f) {
//通过print方法来判断这个文件类对象的性质
if (f.isDirectory()) {
//判断f对象是否为目录
System.out.println("这是一个目录!");
} else {
System.out.println("这不是一个目录!");
}
if (f.exists()) {
//判断f对象是否存在
System.out.println("这个文件存在的!");
} else {
System.out.println("抱歉,这个文件不存在的!");
try {
f.createNewFile();
//当文件不存在时,创建一个文件
} catch (Exception e) {
}
}
}

public void print1(File f) {
//通过print1方法来获取文件目录对象的信息
System.out.println(f.getName());
System.out.println(f.getParent());
System.out.println(f.getPath());
}

public void print2(File f) {
//通过print2方法来获取文件对象的信息
if (f.isFile()) {
System.out.println(f.lastModified());
System.out.println(f.length());
}
}

public static void main(String[] args) {
file1 f1 = new file1(); //创建一个f1对象
File f = new File("d:\\filetest");
//调用相应的方法
f1.print(f);
f1.print1(f);
f1.print2(f);
}
}

运行结果

这不是一个目录!
抱歉,这个文件不存在的!
filetest
d:
d:\filetest
1452923459623
0

使用文件字节输入流读取文件 FileInputStream

FileInputStream类是是InputStream的子类,并且不是抽象类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import java.io.*;

///创建一个文件类f
///创建一个输入流对象fis,并且以f作为参数
///使用循环语句将文本文件中的字符读出
public class file3 {
public static void main(String[] args) throws Exception {
File f = new File("d:\\filetest\\file.txt");
//创建一个文件类f
FileInputStream fis = new FileInputStream(f);
//创建对象f的文件输入流
char ch;
//声明一个字符串对象ch
for (int i = 0; i < f.length(); i++) {
//通过循环读取文件类f所对应的文件
ch = (char) fis.read();
System.out.print(ch);
}
fis.close();
}
}

使用文件字节输出流输出文件 FileOutputStream

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import java.io.*;

///创建一个文件类f
///创建一个输入流对象fis,并且以f作为参数
///将所有的字节都保存到一个字节数组b中。
///使用循环语句将b中的字符读出
public class file4 {
public static void main(String[] args) throws Exception {
File f = new File("d:\\filetest\\file.txt");
//创建一个文件类f
FileInputStream fis = new FileInputStream(f);
//创建一个输入流对象fis,并且以f作为参数
byte[] b = new byte[(int) f.length()];
//创建一个字节数组对象b
fis.read(b);
//读取到的内容存储到字节数组对象b
for (int i = 0; i < f.length(); i++) {
//通过循环输出相应内容
System.out.print((char) b[i]);
}
fis.close();
}
}

Java中对数据的处理

基本数据类型和对象数据类型的转换

为什么要将基本类型转换成对象数据类型:

Java语言是一个面向对象的语言,但是Java中的基本数据类型却是不面向对象的,这在实际使用时存在很多的不便,为了解决这个不足,在设计类时为每个基本数据类型设计了一个对应的类进行代表,这样八个和基本数据类型对应的类统称为包装类(Wrapper Class)

基本数据类型 包装类
byte Byte
boolean Boolean
short Short
char Character
int Integer
long Long
float Float
double Double

由于所有的包装类具有比较相似的成员,这里以Integer类为例:

Integer类的构造方法:

public integer(int value):将整型值value包装成持有此值的Integer类对象。
public integer(string s):将由数字字符组成的串s包装成持有此值的Integer类对象,若s不是数字构成的话,则会报错。

1
2
3
4
5
6
7
8
9
10
11
12
public class file1 {
public static void main(String[] args) {
int x = 12; //创建一个整形类型变量x
String str = "13579"; //创建一个字符串类型变量str
//把变量x,str转换成对象类型t1和t2
Integer t1 = new Integer(x);
Integer t2 = new Integer(str);
//通过tostring()把对象转换成字符串。
System.out.println(t1);
System.out.println(t2);
}
}

如何处理随机性数据 Random

如何对数据进行排列、整理 Arrays

数据结构接口

Collection接口

Collection接口是数据集合接口,它位于数据结构API的最上部,构成Collection的单位被称为元素。可将Collection接口分为三个部分,分别是Map接口、Set接口和List接口。

Python学习

第一个Python程序

1
2
>>> 100+200
300

如果要让Python打印出指定的文字,可以用print()函数,然后把希望打印的文字用单引号或者双引号括起来,但不能混用单引号和双引号:
第一个hello world!

1
2
>>> print('hello, world')
hello, world

这种用单引号或者双引号括起来的文本在程序中叫字符串,今后我们还会经常遇到。

最后,用exit()退出Python,我们的第一个Python程序完成!唯一的缺憾是没有保存下来,下次运行时还要再输入一遍代码。

输入和输出

用print()在括号中加上字符串,就可以向屏幕上输出指定的文字。比如输出’hello, world’,用代码实现如下:

1
>>> print('hello, world')

输出

print()函数也可以接受多个字符串,用逗号“,”隔开,就可以连成一串输出:

1
2
>>> print('The quick brown fox', 'jumps over', 'the lazy dog')
The quick brown fox jumps over the lazy dog

print()也可以打印整数,或者计算结果:

1
2
3
4
>>> print(300)
300
>>> print(100 + 200)
300

因此,我们可以把计算100 + 200的结果打印得更漂亮一点:

1
2
>>> print('100 + 200 =', 100 + 200)
100 + 200 = 300

输入

现在,你已经可以用print()输出你想要的结果了。但是,如果要让用户从电脑输入一些字符怎么办?Python提供了一个input(),可以让用户输入字符串,并存放到一个变量里。比如输入用户的名字:

1
2
3
4
>>> name = input()
Michael
name = input()
print('hello,', name)

数据类型和变量

数据类型

整数 浮点数 字符串

字符串:’I'm "OK"!’表示的字符串内容是:I’m “OK”!

布尔值

布尔值和布尔代数的表示完全一致,一个布尔值只有True、False两种值,要么是True,要么是False,在Python中,可以直接用True、False表示布尔值(请注意大小写),也可以通过布尔运算计算出来:

1
2
3
4
5
6
7
8
>>> True
True
>>> False
False
>>> 3 > 2
True
>>> 3 > 5
False

布尔值可以用and、or和not运算

1
2
3
4
5
6
7
8
>>> True and True
True
>>> True and False
False
>>> False and False
False
>>> 5 > 3 and 3 > 1
True

not运算是非运算,它是一个单目运算符,把True变成False,False变成True:

1
2
3
4
5
6
>>> not True
False
>>> not False
True
>>> not 1 > 2
True

布尔值经常用在条件判断中,比如:

1
2
3
4
if age >= 18:
print('adult')
else:
print('teenager')

常量

所谓常量就是不能变的变量,比如常用的数学常数π就是一个常量。在Python中,通常用全部大写的变量名表示常量:

PI = 3.14159265359

最后解释一下整数的除法为什么也是精确的。在Python中,有两种除法,一种除法是/:

1
2
>>> 10 / 3
3.3333333333333335

除法计算结果是浮点数,即使是两个整数恰好整除,结果也是浮点数:

1
2
>>> 9 / 3
3.0

还有一种除法是//,称为地板除,两个整数的除法仍然是整数:

1
2
>>> 10 // 3
3

Python的字符串

1
2
>>> print('包含中文的str')
包含中文的str

对于单个字符的编码,Python提供了ord()函数获取字符的整数表示,chr()函数把编码转换为对应的字符:

1
2
3
4
5
6
7
8
>>> ord('A')
65
>>> ord('中')
20013
>>> chr(66)
'B'
>>> chr(25991)
'文'

要计算str包含多少个字符,可以用len()函数:

1
2
3
4
>>> len('ABC')
3
>>> len('中文')
2

len()函数计算的是str的字符数,如果换成bytes,len()函数就计算字节数:

1
2
3
4
5
6
>>> len(b'ABC')
3
>>> len(b'\xe4\xb8\xad\xe6\x96\x87')
6
>>> len('中文'.encode('utf-8'))
6

格式化

在Python中,采用的格式化方式和C语言是一致的,用%实现,举例如下:

1
2
3
4
>>> 'Hello, %s' % 'world'
'Hello, world'
>>> 'Hi, %s, you have $%d.' % ('Michael', 1000000)
'Hi, Michael, you have $1000000.'

如果你不太确定应该用什么,%s永远起作用,它会把任何数据类型转换为字符串:

1
2
>>> 'Age: %s. Gender: %s' % (25, True)
'Age: 25. Gender: True'

有些时候,字符串里面的%是一个普通字符怎么办?这个时候就需要转义,用%%来表示一个%:

1
2
>>> 'growth rate: %d %%' % 7
'growth rate: 7 %'

使用list和tuple

list

Python内置的一种数据类型是列表:list。list是一种有序的集合,可以随时添加和删除其中的元素。

比如,列出班里所有同学的名字,就可以用一个list表示:

1
2
3
>>> classmates = ['Michael', 'Bob', 'Tracy']
>>> classmates
['Michael', 'Bob', 'Tracy']

变量classmates就是一个list。用len()函数可以获得list元素的个数:

1
2
>>> len(classmates)
3

用索引来访问list中每一个位置的元素,记得索引是从0开始的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
>>> classmates[0]
'Michael'
>>> classmates[1]
'Bob'
>>> classmates[2]
'Tracy'
>>> classmates[3]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
IndexError: list index out of range
​```</module></stdin>

如果要取最后一个元素,除了计算索引位置外,还可以用-1做索引,直接获取最后一个元素:

​```python
>>> classmates[-1]
'Tracy'

以此类推,可以获取倒数第2个、倒数第3个:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
>>> classmates[-2]
'Bob'
>>> classmates[-3]
'Michael'
>>> classmates[-4]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
IndexError: list index out of range
​```</module></stdin>

list是一个可变的有序表,所以,可以往list中追加元素到末尾:
​```python
>>> classmates.append('Adam')
>>> classmates
['Michael', 'Bob', 'Tracy', 'Adam']

要删除list末尾的元素,用pop()方法:

1
2
3
4
>>> classmates.pop()
'Adam'
>>> classmates
['Michael', 'Jack', 'Bob', 'Tracy']

也可以把元素插入到指定的位置,比如索引号为1的位置:

1
2
3
>>> classmates.insert(1, 'Jack')
>>> classmates
['Michael', 'Jack', 'Bob', 'Tracy', 'Adam']

list元素也可以是另一个list,比如:

1
2
3
>>> s = ['python', 'java', ['asp', 'php'], 'scheme']
>>> len(s)
4

tuple

另一种有序列表叫元组:tuple。tuple和list非常类似,但是tuple一旦初始化就不能修改,比如同样是列出同学的名字:

1
>>> classmates = ('Michael', 'Bob', 'Tracy')

现在,classmates这个tuple不能变了,它也没有append(),insert()这样的方法。其他获取元素的方法和list是一样的,你可以正常地使用classmates[0],classmates[-1],但不能赋值成另外的元素。

1
2
3
>>> t = (1, 2)
>>> t
(1, 2)

如果要定义一个空的tuple,可以写成():

1
2
3
>>> t = ()
>>> t
()

但是,要定义一个只有1个元素的tuple,如果你这么定义:

1
2
3
>>> t = (1)
>>> t
1

定义的不是tuple,是1这个数!这是因为括号()既可以表示tuple,又可以表示数学公式中的小括号,这就产生了歧义,因此,Python规定,这种情况下,按小括号进行计算,计算结果自然是1。

1
2
3
>>> t = (1,)
>>> t
(1,)

Python在显示只有1个元素的tuple时,也会加一个逗号,,以免你误解成数学计算意义上的括号。

最后来看一个“可变的”tuple:

1
2
3
4
5
>>> t = ('a', 'b', ['A', 'B'])
>>> t[2][0] = 'X'
>>> t[2][1] = 'Y'
>>> t
('a', 'b', ['X', 'Y'])

条件判断

1
2
3
4
age = 20
if age >= 18:
print('your age is', age)
print('adult')

当然上面的判断是很粗略的,完全可以用elif做更细致的判断:

1
2
3
4
5
6
7
age = 3
if age >= 18:
print('adult')
elif age >= 6:
print('teenager')
else:
print('kid')

循环

Python的循环有两种,一种是for…in循环,依次把list或tuple中的每个元素迭代出来,看例子:

1
2
3
names = ['Michael', 'Bob', 'Tracy']
for name in names:
print(name)

再比如我们想计算1-10的整数之和,可以用一个sum变量做累加:

1
2
3
4
sum = 0
for x in [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]:
sum = sum + x
print(sum)

如果要计算1-100的整数之和,从1写到100有点困难,幸好Python提供一个range()函数,可以生成一个整数序列,再通过list()函数可以转换为list。比如range(5)生成的序列是从0开始小于5的整数:

1
2
3
4
sum = 0
for x in range(101):
sum = sum + x
print(sum)

使用dict和set

Python内置了字典:dict的支持,dict全称dictionary,在其他语言中也称为map,使用键-值(key-value)存储,具有极快的查找速度。

举个例子,假设要根据同学的名字查找对应的成绩,如果用list实现,需要两个list:

1
2
3
4
5
6
7
8
9
names = ['Michael', 'Bob', 'Tracy']
scores = [95, 75, 85]

``
如果用dict实现,只需要一个“名字”-“成绩”的对照表,直接根据名字查找成绩,无论这个表有多大,查找速度都不会变慢。用Python写一个dict如下:
​```python
>>> d = {'Michael': 95, 'Bob': 75, 'Tracy': 85}
>>> d['Michael']
95

把数据放入dict的方法,除了初始化时指定外,还可以通过key放入:

1
2
3
>>> d['Adam'] = 67
>>> d['Adam']
67

由于一个key只能对应一个value,所以,多次对一个key放入value,后面的值会把前面的值冲掉:

1
2
3
4
5
6
>>> d['Jack'] = 90
>>> d['Jack']
90
>>> d['Jack'] = 88
>>> d['Jack']
88

要避免key不存在的错误,有两种办法,一是通过in判断key是否存在:

1
2
>>> 'Thomas' in d
False

要删除一个key,用pop(key)方法,对应的value也会从dict中删除:

1
2
3
4
>>> d.pop('Bob')
75
>>> d
{'Michael': 95, 'Tracy': 85}

和list比较,dict有以下几个特点:

1.查找和插入的速度极快,不会随着key的增加而增加;
2.需要占用大量的内存,内存浪费多。
而list相反:

1.查找和插入的时间随着元素的增加而增加;
2.占用空间小,浪费内存很少。

set

set和dict类似,也是一组key的集合,但不存储value。由于key不能重复,所以,在set中,没有重复的key。

要创建一个set,需要提供一个list作为输入集合:

1
2
3
>>> s = set([1, 2, 3])
>>> s
{1, 2, 3}

注意,传入的参数[1, 2, 3]是一个list,而显示的{1, 2, 3}只是告诉你这个set内部有1,2,3这3个元素,显示的顺序也不表示set是有序的。。

重复元素在set中自动被过滤:

1
2
3
>>> s = set([1, 1, 2, 2, 3, 3])
>>> s
{1, 2, 3}

通过add(key)方法可以添加元素到set中,可以重复添加,但不会有效果:

1
2
3
4
5
6
>>> s.add(4)
>>> s
{1, 2, 3, 4}
>>> s.add(4)
>>> s
{1, 2, 3, 4}

通过remove(key)方法可以删除元素:

1
2
3
>>> s.remove(4)
>>> s
{1, 2, 3}

set可以看成数学意义上的无序和无重复元素的集合,因此,两个set可以做数学意义上的交集、并集等操作:

1
2
3
4
5
6
>>> s1 = set([1, 2, 3])
>>> s2 = set([2, 3, 4])
>>> s1 & s2
{2, 3}
>>> s1 | s2
{1, 2, 3, 4}

不可变对象

对于可变对象,比如list,对list进行操作,list内部的内容是会变化的,比如:

1
2
3
4
>>> a = ['c', 'b', 'a']
>>> a.sort()
>>> a
['a', 'b', 'c']

而对于不可变对象,比如str,对str进行操作:

1
2
3
4
5
>>> a = 'abc'
>>> a.replace('a', 'A')
'Abc'
>>> a
'abc'

函数

调用函数

调用abs函数:

1
2
3
4
5
6
>>> abs(100)
100
>>> abs(-20)
20
>>> abs(12.34)
12.34

而max函数max()可以接收任意多个参数,并返回最大的那个:

1
2
3
4
>>> max(1, 2)
2
>>> max(2, 3, 1, -5)
3

数据类型转换

Python内置的常用函数还包括数据类型转换函数,比如int()函数可以把其他数据类型转换为整数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
>>> int('123')
123
>>> int(12.34)
12
>>> float('12.34')
12.34
>>> str(1.23)
'1.23'
>>> str(100)
'100'
>>> bool(1)
True
>>> bool('')
False

函数名其实就是指向一个函数对象的引用,完全可以把函数名赋给一个变量,相当于给这个函数起了一个“别名”:

1
2
3
>>> a = abs # 变量a指向abs函数
>>> a(-1) # 所以也可以通过a调用abs函数
1

定义函数

我们以自定义一个求绝对值的my_abs函数为例:

1
2
3
4
5
def my_abs(x):
if x >= 0:
return x
else:
return -x

返回多个值

比如在游戏中经常需要从一个点移动到另一个点,给出坐标、位移和角度,就可以计算出新的新的坐标:

1
2
3
4
5
6
import math

def move(x, y, step, angle=0):
nx = x + step * math.cos(angle)
ny = y - step * math.sin(angle)
return nx, ny

然后,我们就可以同时获得返回值:

1
2
3
>>> x, y = move(100, 100, 60, math.pi / 6)
>>> print(x, y)
151.96152422706632 70.0

函数的参数

位置参数

我们先写一个计算x*x的函数:

1
2
def power(x):
return x * x

当我们调用power函数时,必须传入有且仅有的一个参数x:

1
2
3
4
>>> power(5)
25
>>> power(15)
225

现在,如果我们要计算x3怎么办?可以再定义一个power3函数,但是如果要计算x4、x5……怎么办?我们不可能定义无限多个函数。

你也许想到了,可以把power(x)修改为power(x, n),用来计算xn,说干就干:

1
2
3
4
5
6
def power(x, n):
s = 1
while n > 0:
n = n - 1
s = s * x
return s

对于这个修改后的power(x, n)函数,可以计算任意n次方:

1
2
3
4
>>> power(5, 2)
25
>>> power(5, 3)
125

递归函数

举个例子,我们来计算阶乘n! = 1 x 2 x 3 x … x n,用函数fact(n)表示,可以看出:

fact(n) = n! = 1 x 2 x 3 x … x (n-1) x n = (n-1)! x n = fact(n-1) x n

于是,fact(n)用递归的方式写出来就是:

1
2
3
4
def fact(n):
if n==1:
return 1
return n * fact(n - 1)

上面就是一个递归函数。可以试试:

1
2
3
4
5
6
>>> fact(1)
1
>>> fact(5)
120
>>> fact(100)
93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000

上面的fact(n)函数由于return n * fact(n - 1)引入了乘法表达式,所以就不是尾递归了。要改成尾递归方式,需要多一点代码,主要是要把每一步的乘积传入到递归函数中:

1
2
3
4
5
6
7
def fact(n):
return fact_iter(n, 1)

def fact_iter(num, product):
if num == 1:
return product
return fact_iter(num - 1, num * product)

尾递归调用时,如果做了优化,栈不会增长,因此,无论多少次调用也不会导致栈溢出。

环境变量GNUPGHOME:C:\Program Files (x86)\GnuPG\bin\gpg.exe

1
2
3
4
5
git config --global gpg.program "C:\Program Files (x86)\GnuPG\bin\gpg.exe"

git config --global user.signingkey 你的公钥

git config --global commit.gpgsign true