java string怎么赋值 (javastring类的常用方法)
本文目录导航:
java string怎么赋值
Java是一种广泛使用的计算机编程语言,拥有跨平台、面向对象、泛型编程的特性,广泛应用于企业级Web应用开发和移动应用开发。

任职于太阳微系统的詹姆斯·高斯林等人于1990年代初开发Java语言的雏形,最初被命名为Oak,目标设置在家用电器等小型系统的程序语言,应用在电视机、电话、闹钟、烤面包机等家用电器的控制和通信。
由于这些智能化家电的市场需求没有预期的高,Sun公司放弃了该项计划。
随着1990年代互联网的发展,Sun公司看见Oak在互联网上应用的前景,于是改造了Oak,于1995年5月以Java的名称正式发布。
Java伴随着互联网的迅猛发展而发展,逐渐成为重要的网络编程语言。
Java编程语言的风格十分接近C++语言。
继承了C++语言面向对象技术的核心,Java舍弃了C++语言中容易引起错误的指针,改以引用取代,同时移除原C++与原来运算符重载,也移除多重继承特性,改用接口取代,增加垃圾回收器功能。
在JavaSE1.5版本中引入了泛型编程、类型安全的枚举、不定长参数和自动装/拆箱特性。
太阳微系统对Java语言的解释是:“Java编程语言是个简单、面向对象、分布式、解释性、健壮、安全与系统无关、可移植、高性能、多线程和动态的语言”
Java不同于一般的编译语言或直译语言。
它首先将源代码编译成字节码,然后依赖各种不同平台上的虚拟机来解释执行字节码,从而实现了“一次编写,到处运行”的跨平台特性。
在早期JVM中,这在一定程度上降低了Java程序的运行效率。
但在J2SE1.4.2发布后,Java的运行速度有了大幅提升。
与传统类型不同,Sun公司在推出Java时就将其作为开放的技术。
全球数以万计的Java开发公司被要求所设计的Java软件必须相互兼容。
“Java语言靠群体的力量而非公司的力量”是Sun公司的口号之一,并获得了广大软件开发商的认同。
这与微软公司所倡导的注重精英和封闭式的模式完全不同,此外,微软公司后来推出了与之竞争的平台以及模仿Java的C#语言。
后来Sun公司被甲骨文公司并购,Java也随之成为甲骨文公司的产品。
现时,移动操作系统Android大部分的代码采用Java编程语言编程。
早期的Java
Java的发明人詹姆斯·高斯林
Duke,Java语言的吉祥物
语言最开始只是Sun计算机(SunMicroSystems)公司在1990年12月开始研究的一个内部项目。
Sun计算机公司的一个叫做帕特里克·诺顿的工程师被公司自己开发的C++和C语言编译器搞得焦头烂额,因为其中的API极其难用。
帕特里克决定改用NeXT,同时他也获得了研究公司的一个叫做“Stealth计划”的项目的机会。
“Stealth计划”后来改名为“Green计划”,詹姆斯·高斯林和麦克·舍林丹也加入了帕特里克的工作小组。
他们和其他几个工程师一起在加利福尼亚州门罗帕克市沙丘路的一个小工作室里面研究开发新技术,瞄准下一代智能家电(如微波炉)的程序设计,Sun公司预料未来科技将在家用电器领域大显身手。
团队最初考虑使用C++语言,但是很多成员包括Sun的首席科学家比尔·乔伊,发现C++和可用的API在某些方面存在很大问题。
工作小组使用的是嵌入式系统,可以用的资源极其有限。
很多成员发现C++太复杂以至很多开发者经常错误使用。
他们发现C++缺少垃圾回收系统,还有可移植的安全性、分布程序设计、和多线程功能。
最后,他们想要一种易于移植到各种设备上的平台。
根据可用的资金,乔伊决定开发一种集C语言和Mesa语言大成的新语言,在一份报告上,乔伊把它叫做“未来”,他提议Sun公司的工程师应该在C++的基础上,开发一种面向对象的环境。
最初,高斯林试图修改和扩展C++的功能,他自己称这种新语言为C++++--,但是后来他放弃了。
他将要创造出一种全新的语言,被他命名为“Oak”(橡树),以他的办公室外的橡树命名。
就像很多开发新技术的秘密工程一样,工作小组没日没夜地工作到了1993年的夏天,他们能够演示新平台的一部分了,包括Green操作系统,Oak的程序设计语言,类库及其硬件。
最初的尝试是面向一种类PDA设备,被命名为Star7,这种设备有鲜艳的图形界面和被称为“Duke”的智能代理来帮助用户。
1992年12月3日,这台设备进行了展示。
同年11月,Green计划被转化成了“FirstPerson有限公司”,一个Sun公司的全资子公司,团队也被重新安排到了帕洛阿尔托。
FirstPerson团队对建造一种高度交互的设备感兴趣,当时代华纳发布了一个关于电视机顶盒的征求提议书时(Requestforproposal),FirstPerson改变了他们的目标,作为对征求意见书的响应,提出了一个机顶盒平台的提议。
但是有线电视业界觉得FirstPerson的平台给予用户过多的控制权,因此FirstPerson的投标败给了SGI。
与3DO公司的另外一笔关于机顶盒的交易也没有成功,由于他们的平台不能在电视工业产生任何效益,公司被并回Sun公司。
Java和互联网
1994年6月,在经历了一场历时三天的脑力激荡的讨论后,约翰·盖吉、詹姆斯·高斯林、比尔·乔伊、帕特里克·诺顿、韦恩·罗斯因和埃里克·斯库米,团队决定再一次改变了努力的目标,这次他们决定将该技术应用于万维网。
他们认为随着Mosaic浏览器的到来,因特网正在向同样的高度互动的远景演变,而这一远景正是他们在有线电视网中看到的。
作为原型,帕特里克·诺顿写了一个小型万维网浏览器,WebRunner,后来改名为HotJava[12]。
1994年10月,HotJava和Java平台为公司高层进行演示。1994年,Java
1.0a版本已经可以提供下载,但是Java和HotJava浏览器的第一次公开发布却是在1995年3月23日SunWorld大会上进行的。
升阳公司的科学指导约翰·盖吉宣告Java技术。
这个发布是与网景公司的执行副总裁马克·安德森的惊人发布一起进行的,宣布网景将在其浏览器中包含对Java的支持。
1996年1月,升阳公司成立了Java业务集团,专门开发Java技术。
Java和“Java”
由于商标搜索显示,Oak已被一家显示卡制造商注册。
于是同年,Oak被改名为Java。
当使用十六进制编辑器打开由Java源代码编译出的二进制文件(文件)的话,最前面的32位将显示为CAFEBABE,即词组“CAFEBABE”(咖啡屋宝贝)。
Java近况
一套Java编辑器软件
J2SE环境与CDC的关连
JVM的所在角色
JSPX网页的概念
在流行几年之后,Java在浏览器中的地位被逐步侵蚀。
它在简单交互性动画方面的用途已经完全被Adobe公司的Flash排挤,2005年Java倾向只被用于雅虎游戏那样的更为复杂的应用程序。
Java同时遭受到来自微软的反对,他们决定在新版本的InternetExplorer和Windows中不再附带Java平台。
与此相反。
在万维网的服务器端和手持设备上,Java变得更加流行。
很多网站在后端使用JSP和其他的Java技术。
在桌面系统上,独立的Java程序还是相对少见,这是因为Java平台的运行开销较大,而许多人的电脑上没有安装Java,由于网络带宽在以前较小,下载Java曾经是个耗时的事情。但是随着计算机计算能力、网络带宽在10年中获取了很大的进步,同时虚拟机和编译器的质量得到了提高,许多应用程序得到了广泛的使用,包括:
开源软件:
NetBeans和Eclipse等软件开发工具
Android操作系统
AzureusBitTorrent客户端。
JNode操作系统
Apache软件基金会的Ant、Derby、Hadoop、Jakarta、POI和Tomcat
JBoss和GlassFish应用服务器
商业软件:
EIOffice(永中Office)
纯Java3D游戏合金战士Chrome
IBMWebsphere、ColdFusion和WebLogic
IntelliJIDEA
还有许多用Java写的软件,可以在找到。在以下网页可以看到非常多用Java写的程序:下面有大量用JavaSwing开发的桌面Java程序目前Java提供以下三个版本:
JavaPlatform,EnterpriseEdition(JavaEE:Java平台企业版)
JavaPlatform,StandardEdition(JavaSE:Java平台标准版)
JavaPlatform,MicroEdition(JavaME:Java平台微型版)
JavaPlatform,CardEdition
Java开放源代码项目
2006年SUN在JavaOne公布Java开放源代码项目,并推出OpenJDK项目。
[13]Java虚拟机、Java编译器和Java类库以GNU通用公共许可证公开。
版本历史
主条目:Java版本历史
1995年5月23日,Java语言诞生
1996年1月,第一个JDK-JDK1.0诞生
1996年4月,10个最主要的操作系统供应商申明将在其产品中嵌入JAVA技术
1996年9月,约8.3万个网页应用了JAVA技术来制作
1997年2月18日,JDK1.1发布
1997年4月2日,JavaOne会议召开,参与者逾一万人,创当时全球同类会议规模之纪录
1997年9月,JavaDeveloperConnection社区成员超过十万
1998年2月,JDK1.1被下载超过2,000,000次
1998年12月8日,JAVA2企业平台J2EE发布
1999年6月,SUN公司发布Java的三个版本:标准版(J2SE)、企业版(J2EE)和微型版(J2ME)
2000年5月8日,JDK1.3发布
2000年5月29日,JDK1.4发布
2001年6月5日,NOKIA宣布,到2003年将出售1亿部支持Java的手机
2001年9月24日,J2EE1.3发布
2002年2月26日,J2SE1.4发布,自此Java的计算能力有了大幅提升
2004年9月30日18:00PM,J2SE1.5发布,成为Java语言发展史上的又一里程碑。为了表示该版本的重要性,J2SE1.5更名为JavaSE5.0
2005年6月,JavaOne大会召开,SUN公司公开JavaSE6。此时,Java的各种版本已经更名,以取消其中的数字“2”:J2EE更名为JavaEE,J2SE更名为JavaSE,J2ME更名为JavaME
2006年12月,SUN公司发布JRE6.0
2009年12月,SUN公司发布JavaEE6
2010年11月,由于Oracle公司对于Java社区的不友善,因此Apache扬言将退出JCP[14]
2011年7月28日,Oracle公司发布JavaSE7
2014年3月18日,Oracle公司发表JavaSE8
语言特性
Java之所以被开发,是要达到以下五个目的:
应当使用面向对象程序设计方法学
应当允许同一程序在不同的计算机平台执行
应当包括内建的对计算机网络的支持
应当被设计成安全地执行远端代码
应当易于使用,并借鉴以前那些面向对象语言(如C++)的长处。
Java技术主要分成几个部分:Java语言、Java运行环境、类库。
一般情况下说Java时并不区分指的是哪个部分。
Java在1.5版本时,做了重大改变,Sun公司并1.5版本重命名为Java5.0。
面向对象
Java的特点之一就是面向对象,是程序设计方法的一种。
“面向对象程序设计语言”的核心之一就是开发者在设计软件的时候可以使用自定义的类型和关联操作。
代码和数据的实际集合体叫做“对象”。
一个对象可以想象成绑定了很多“行为(代码)”和“状态(数据)”的物体。
对于数据结构的改变需要和代码进行通信然后操作,反之亦然。
面向对象设计让大型软件工程的计划和设计变得更容易管理,能增强工程的健康度,减少失败工程的数量。
跨平台性
Java语言的第二个特性就是跨平台性,也就是说使用Java语言编写的程序可以在编译后不用经过任何更改,就能在任何硬件设备条件下运行。
这个特性经常被称为“一次编译,到处运行”。
执行Java应用程序必须安装爪哇运行环境(JavaRuntimeEnvironment,JRE),JRE内部有一个Java虚拟机(JavaVirtualMachine,JVM)以及一些标准的类库(ClassLibrary)。
通过JVM才能在电脑系统执行Java应用程序(JavaApplication),这与的情况一样,所以电脑上没有安装JVM,那么这些程序将不能够执行。
实现跨平台性的方法是大多数编译器在进行Java语言程序的编码时候会生成一个用字节码写成的“半成品”,这个“半成品”会在Java虚拟机(解释层)的帮助下运行,虚拟机会把它转换成当前所处硬件平台的原始代码。
之后,Java虚拟机会打开标准库,进行数据(图片、线程和网络)的访问工作。
主要注意的是,尽管已经存在一个进行代码翻译的解释层,有些时候Java的字节码代码还是会被JIT编译器进行二次编译。
有些编译器,比如GCJ,可以自动生成原始代码而不需要解释层。
但是这些编译器所生成的代码只能应用于特定平台。
并且GCJ目前只支持部分的JavaAPI。
甲骨文公司对于Java的许可是“全兼容的”,这也导致了微软和升阳关于微软的程序不支持RMI和JNI接口、并且增加特性为己所用的法律争端。
升阳最终赢得了官司,获得了大约两千万美元的赔偿,法院强制要求微软执行升阳公司关于Java的许可要求。
作为回应,微软不再在Windows系统中捆绑Java,最新的Windows版本,WindowsVista和InternetExplorer7.0版本也不再提供对于Java应用程序和控件的支持。
但是升阳公司和其他使用Java运行时系统的公司在Windows操作系统下对用户提供无偿的第三方插件和程序支持。
Java语言在最开始应用的时候是没有解释层的,所有需要编译的代码都直接转换成机器的原始代码。
这样做的后果就是获得了最佳的性能,但是程序臃肿异常。
从JIT技术开始,Java的程序都经过一次转换之后才变成机器码。
很多老牌的第三方虚拟机都使用一种叫做“动态编译”的技术,也就是说虚拟机实时监测和分析程序的运行行为,同时选择性地对程序所需要的部分进行编译和优化。
所有这些技术都改善了代码的运行速度,但是又不会让程序的体积变得失常。
程序的轻便性事实上是软件编写很难达到的一个目标,Java虽然成功地实现了“一次编译,到处运行”,但是由于平台和平台之间的差异,所编写的程序在转换代码的时候难免会出现微小的、不可察觉的错误和意外。
有些程序员对此非常头疼,他们嘲笑Java的程序不是“一次编译,到处运行”,而是“一次编译,到处调试”。
以Java
AWT为例,早期Java
AWT内提供的按钮、文字区等均是以计算机系统所默认的样式而显示。这令Java程序在有些没有提供图案的计算机系统产生错误(在Microsoft
Windows设有窗口管理器,在一些Linuxdistribution则没有)。后来SUN公司针对JavaAWT一些问题而推出Java
平台无关性让Java在服务器端软件领域非常成功。
很多服务器端软件都使用Java或相关技术建立。
自动垃圾回收(GarbageCollection)
C++语言被用户诟病的原因之一是大多数C++编译器不支持垃圾收集机制。
通常使用C++编程的时候,程序员于程序中初始化对象时,会在主机内存堆栈上分配一块内存与地址,当不需要此对象时,进行析构或者删除的时候再释放分配的内存地址。
如果对象是在堆栈上分配的,而程序员又忘记进行删除,那么就会造成内存泄漏(Memory
Leak)。
长此以往,程序运行的时候可能会生成很多不清除的垃圾,浪费了不必要的内存空间。
而且如果同一内存地址被删除两次的话,程序会变得不稳定,甚至崩溃。
因此有经验的C++程序员都会在删除之后将指针重置为NULL,然后在删除之前先判断指针是否为NULL。
C++中也可以使用“智能指针”(SmartPointer)或者使用C++托管扩展编译器的方法来实现自动化内存释放,智能指针可以在标准类库中找到,而C++
(题主你怎么可以问这么沙比的问题!)
托管扩展被微软的Visual
7.0及以上版本所支持。
智能指针的优点是不需引入缓慢的垃圾收集机制,而且可以不考虑线程安全的问题,但是缺点是如果不善使用智能指针的话,性能有可能不如垃圾收集机制,而且不断地分配和释放内存可能造成内存碎片,需要手动对堆进行压缩。
除此之外,由于智能指针是一个基于模板的功能,所以没有经验的程序员在需要使用多态特性进行自动清理时也可能束手无策。
Java语言则不同,上述的情况被自动垃圾收集功能自动处理。
对象的创建和放置都是在内存堆栈上面进行的。
当一个对象没有任何引用的时候,Java的自动垃圾收集机制就发挥作用,自动删除这个对象所占用的空间,释放内存以避免内存泄漏。
注意程序员不需要修改finalize方法,自动垃圾收集也会发生作用。
但是内存泄漏并不是就此避免了,当程序员疏忽大意地忘记解除一个对象不应该有的引用时,内存泄漏仍然不可避免。
IBM有一篇有关Java内存回收机制比不激活垃圾收集机制的C++内存处理快数倍的技术文章[15],而著名的Java技术书籍《Java编程思想》(ThinkinginJava)也有一段论述Java内存及性能达到甚至超过C++的章节[16]。
基本语法
编写Java程序前应注意以下几点:
大小写敏感:Java是大小写敏感的,这就意味着标识符Hello与hello是不同的。
类名:对于所有的类来说,类名的首字母应该大写。
如果类名由若干单词组成,那么每个单词的首字母应该大写,例如MyFirstJavaClass。
方法名:所有的方法名都应该以小写字母开头。
如果方法名含有若干单词,则后面的每个单词首字母大写,例如myFirstJavaMethod。
源文件名:源文件名必须和类名相同。
当保存文件的时候,你应该使用类名作为文件名保存(切记Java是大小写敏感的),文件名的后缀为。
(如果文件名和类名不相同则会导致编译错误)。
主方法入口:所有的Java程序由publicstaticvoidmain(String[]args)方法开始执行。
Java关键字
关键字下面列出了Java关键字。
这些关键字不能用于常量、变量、和任何标识符的名称。
关键字
描述
抽象方法,抽象类的修饰符
断言条件是否满足
不执行循环体剩余部分
switch语句中的默认分支
循环语句,循环体至少会执行一次
64-bit双精度浮点数
if条件不成立时执行的分支
枚举类型
表示一个类是另一个类的子类
表示定义常量
无论有没有异常发生都执行代码
32-bit单精度浮点数
for循环语句
用于流程跳转(不可用,意即不可随意跳转)
条件语句
implements
表示一个类实现了接口
导入类
instanceof
测试一个对象是否是某个类的实例
32位整型数
接口,一种抽象的类型,仅有方法和常量的定义
64位整型数
表示方法用非java代码实现
分配新的类实例
一系列相关类组成一个包
表示私有字段,或者方法等,只能从类内部访问
表示保护类型字段
表示共有属性或者方法
方法返回值
16位数字
表示在类级别定义,所有实例共享的
浮点数比较使用严格的规则
表示基类
选择语句
synchronized
表示同一时间只能由一个线程访问的代码块
调用当前实例或者调用另一个构造函数
抛出异常
定义方法可能抛出的异常
修饰不要序列化的字段
表示代码块要做异常处理
标记方法不返回任何值
标记字段可能会被多个线程同时访问,而不做同步
while循环
注释
注释作用:标识程序是干什么的,以及它是如何构建的。
注释帮助程序员进行相互沟通以及理解程序。
注释不是程序设计语言,所以编译器编译程序时忽略它们。
接口和类别
Java自带了创建接口的类别,可以这样使用:
publicinterfaceDeleteable{
voiddelete();
这段代码的意思是任何实现(implement)Deleteable接口的类别都必须实现delete()方法。
每个类别对这个方法的实现可以自行定制。
由此概念可以引出很多种使用方法,下面是一个类别的例子:
什么是野指针如何避免野指针
Rust从编译器阶段就杜绝了内存安全问题,包括单线程与多线程情况,这得益于rust编译器级复杂的所有权机制,使得任何人都无法写出不“安全”的代码,这是法制。
使用C/C++,世界上任何程序员都没有办法保证写的代码绝对的内存安全,虽然C++是屠龙刀,但是问题也很显著。
也就是人治是没有办法保证代码的内存安全,线程安全。
C++智能指针,还是存在使用过程中容易产生问题的地方,比如最新的shared_ptr在多线程环境下,有个程序员忘记执行锁定操作怎么办?这种情况在rust下就不会发生,使用Arc机制,你不锁定都没有办法访问到数据。
就算单线程环境,你能保证所有程序员不会把智能指针实例的引用再传递出去?如此这般不胜枚举。
C++根本没办法在编译阶段发现这些问题。
Rust执行的是法制,而非人治。
数据访问有冲突,根本不能编译!
C++为什么不加入垃圾回收机制
Java的爱好者们经常批评C++中没有提供与Java类似的垃圾回收(Gabage Collector)机制(这很正常,正如C++的爱好者有时也攻击Java没有这个没有那个,或者这个不行那个不够好),导致C++中对动态存储的官吏称为程序员的噩梦,不是吗?你经常听到的是内存遗失(memory leak)和非法指针存取,这一定令你很头疼,而且你又不能抛弃指针带来的灵活性。
在本文中,我并不想揭露Java提供的垃圾回收机制的天生缺陷,而是指出了C++中引入垃圾回收的可行性。
请读者注意,这里介绍的方法更多的是基于当前标准和库设计的角度,而不是要求修改语言定义或者扩展编译器。
什么是垃圾回收?作为支持指针的编程语言,C++将动态管理存储器资源的便利性交给了程序员。
在使用指针形式的对象时(请注意,由于引用在初始化后不能更改引用目标的语言机制的限制,多态性应用大多数情况下依赖于指针进行),程序员必须自己完成存储器的分配、使用和释放,语言本身在此过程中不能提供任何帮助,也许除了按照你的要求正确的和操作系统亲密合作,完成实际的存储器管理。
标准文本中,多次提到了“未定义(undefined)”,而这大多数情况下和指针相关。
某些语言提供了垃圾回收机制,也就是说程序员仅负责分配存储器和使用,而由语言本身负责释放不再使用的存储器,这样程序员就从讨厌的存储器管理的工作中脱身了。
然而C++并没有提供类似的机制,C++的设计者Bjarne Stroustrup在我所知的唯一一本介绍语言设计的思想和哲学的著作《The Design and Evolution of C++》(中译本:C++语言的设计和演化)中花了一个小节讨论这个特性。
简而言之,Bjarne本人认为,“我有意这样设计C++,使它不依赖于自动垃圾回收(通常就直接说垃圾回收)。
这是基于自己对垃圾回收系统的经验,我很害怕那种严重的空间和时间开销,也害怕由于实现和移植垃圾回收系统而带来的复杂性。
还有,垃圾回收将使C++不适合做许多底层的工作,而这却正是它的一个设计目标。
但我喜欢垃圾回收的思想,它是一种机制,能够简化设计、排除掉许多产生错误的根源。
需要垃圾回收的基本理由是很容易理解的:用户的使用方便以及比用户提供的存储管理模式更可靠。
而反对垃圾回收的理由也有很多,但都不是最根本的,而是关于实现和效率方面的。
已经有充分多的论据可以反驳:每个应用在有了垃圾回收之后会做的更好些。
类似的,也有充分的论据可以反对:没有应用可能因为有了垃圾回收而做得更好。
并不是每个程序都需要永远无休止的运行下去;并不是所有的代码都是基础性的库代码;对于许多应用而言,出现一点存储流失是可以接受的;许多应用可以管理自己的存储,而不需要垃圾回收或者其他与之相关的技术,如引用计数等。
我的结论是,从原则上和可行性上说,垃圾回收都是需要的。
但是对今天的用户以及普遍的使用和硬件而言,我们还无法承受将C++的语义和它的基本库定义在垃圾回收系统之上的负担。
”以我之见,统一的自动垃圾回收系统无法适用于各种不同的应用环境,而又不至于导致实现上的负担。
稍后我将设计一个针对特定类型的可选的垃圾回收器,可以很明显地看到,或多或少总是存在一些效率上的开销,如果强迫C++用户必须接受这一点,也许是不可取的。
关于为什么C++没有垃圾回收以及可能的在C++中为此做出的努力,上面提到的著作是我所看过的对这个问题叙述的最全面的,尽管只有短短的一个小节的内容,但是已经涵盖了很多内容,这正是Bjarne著作的一贯特点,言简意赅而内韵十足。
下面一步一步地向大家介绍我自己土制佳酿的垃圾回收系统,可以按照需要自由选用,而不影响其他代码。
构造函数和析构函数C++中提供的构造函数和析构函数很好的解决了自动释放资源的需求。
Bjarne有一句名言,“资源需求就是初始化(Resource Inquirment Is Initialization)”。
因此,我们可以将需要分配的资源在构造函数中申请完成,而在析构函数中释放已经分配的资源,只要对象的生存期结束,对象请求分配的资源即被自动释放。
那么就仅剩下一个问题了,如果对象本身是在自由存储区(Free Store,也就是所谓的“堆”)中动态创建的,并由指针管理(相信你已经知道为什么了),则还是必须通过编码显式的调用析构函数,当然是借助指针的delete表达式。
智能指针幸运的是,出于某些原因,C++的标准库中至少引入了一种类型的智能指针,虽然在使用上有局限性,但是它刚好可以解决我们的这个难题,这就是标准库中唯一的一个智能指针::std::auto_ptr。
它将指针包装成了类,并且重载了反引用(dereference)运算符operator *和成员选择运算符operator ->,以模仿指针的行为。
关于auto_ptr的具体细节,参阅《The C++ Standard Library》(中译本:C++标准库)。
例如以下代码,#include < cstring >#include < memory >#include < iostream >class string{public:string(const char* cstr) { _data=new char [ strlen(cstr)+1 ]; strcpy(_data, cstr); }~string() { delete [] _data; }const char* c_str() const { return _data; }private:char* _data;};void foo(){::std::auto_ptr < string > str ( new string( hello ) );::std::cout << str->c_str() << ::std::endl;}由于str是函数的局部对象,因此在函数退出点生存期结束,此时auto_ptr的析构函数调用,自动销毁内部指针维护的string对象(先前在构造函数中通过new表达式分配而来的),并进而执行string的析构函数,释放为实际的字符串动态申请的内存。
在string中也可能管理其他类型的资源,如用于多线程环境下的同步资源。
下图说明了上面的过程。
现在我们拥有了最简单的垃圾回收机制(我隐瞒了一点,在string中,你仍然需要自己编码控制对象的动态创建和销毁,但是这种情况下的准则极其简单,就是在构造函数中分配资源,在析构函数中释放资源,就好像飞机驾驶员必须在起飞后和降落前检查起落架一样。
),即使在foo函数中发生了异常,str的生存期也会结束,C++保证自然退出时发生的一切在异常发生时一样会有效。
auto_ptr只是智能指针的一种,它的复制行为提供了所有权转移的语义,即智能指针在复制时将对内部维护的实际指针的所有权进行了转移,例如auto_ptr < string > str1( new string( < str1 > ) );cout << str1->c_str();auto_ptr < string > str2(str1); // str1内部指针不再指向原来的对象cout << str2->c_str();cout << str1->c_str(); // 未定义,str1内部指针不再有效某些时候,需要共享同一个对象,此时auto_ptr就不敷使用,由于某些历史的原因,C++的标准库中并没有提供其他形式的智能指针,走投无路了吗?另一种智能指针但是我们可以自己制作另一种形式的智能指针,也就是具有值复制语义的,并且共享值的智能指针。
需要同一个类的多个对象同时拥有一个对象的拷贝时,我们可以使用引用计数(Reference Counting/Using Counting)来实现,曾经这是一个C++中为了提高效率与COW(copy on write,改写时复制)技术一起被广泛使用的技术,后来证明在多线程应用中,COW为了保证行为的正确反而导致了效率降低(Herb Shutter的在C++ Report杂志中的Guru专栏以及整理后出版的《More Exceptional C++》中专门讨论了这个问题)。
然而对于我们目前的问题,引用计数本身并不会有太大的问题,因为没有牵涉到复制问题,为了保证多线程环境下的正确,并不需要过多的效率牺牲,但是为了简化问题,这里忽略了对于多线程安全的考虑。
首先我们仿造auto_ptr设计了一个类模板(出自Herb Shutter的《More Execptional C++》),template < typename T >class shared_ptr{private:class implement // 实现类,引用计数{public:implement(T* pp):p(pp),refs(1){}~implement(){delete p;}T* p; // 实际指针size_trefs; // 引用计数};implement* _impl;public:explicitshared_ptr(T* p): _impl(new implement(p)){}~shared_ptr(){decrease(); // 计数递减}shared_ptr(const shared_ptr& rhs): _impl(rhs._impl){increase(); // 计数递增}shared_ptr& operator=(const shared_ptr& rhs){if (_impl != rhs._impl) // 避免自赋值{decrease(); // 计数递减,不再共享原对象_impl=rhs._impl; // 共享新的对象increase(); // 计数递增,维护正确的引用计数值}return *this;}T* operator->() const{return _impl->p;}T& operator*() const{return *(_impl->p);}private:void decrease(){if (--(_impl->refs)==0){ // 不再被共享,销毁对象delete_impl;}}void increase(){++(_impl->refs);}};这个类模板是如此的简单,所以都不需要对代码进行太多地说明。
这里仅仅给出一个简单的使用实例,足以说明shared_ptr作为简单的垃圾回收器的替代品。
void foo1(shared_ptr < int >& val){shared_ptr < int > temp(val);*temp=300;}void foo2(shared_ptr < int >& val){val=shared_ptr < int > ( new int(200) );}int main(){shared_ptr < int > val(new int(100));cout<<val=<<*val;foo1(val);cout<<val=<<*val;foo2(val);cout<<val=<<*val;}在main()函数中,先调用foo1(val),函数中使用了一个局部对象temp,它和val共享同一份数据,并修改了实际值,函数返回后,val拥有的值同样也发生了变化,而实际上val本身并没有修改过。
然后调用了foo2(val),函数中使用了一个无名的临时对象创建了一个新值,使用赋值表达式修改了val,同时val和临时对象拥有同一个值,函数返回时,val仍然拥有这正确的值。
最后,在整个过程中,除了在使用shared_ptr 的构造函数时使用了new表达式创建新之外,并没有任何删除指针的动作,但是所有的内存管理均正确无误,这就是得益于shared_ptr的精巧的设计。
拥有了auto_ptr和shared_ptr两大利器以后,应该足以应付大多数情况下的垃圾回收了,如果你需要更复杂语义(主要是指复制时的语义)的智能指针,可以参考boost的源代码,其中设计了多种类型的智能指针。
标准容器对于需要在程序中拥有相同类型的多个对象,善用标准库提供的各种容器类,可以最大限度的杜绝显式的内存管理,然而标准容器并不适用于储存指针,这样对于多态性的支持仍然面临困境。
使用智能指针作为容器的元素类型,然而标准容器和算法大多数需要值复制语义的元素,前面介绍的转移所有权的auto_ptr和自制的共享对象的shared_ptr都不能提供正确的值复制语义,Herb Sutter在《More Execptional C++》中设计了一个具有完全复制语义的智能指针ValuePtr,解决了指针用于标准容器的问题。
然而,多态性仍然没有解决,我将在另一篇文章专门介绍使用容器管理多态对象的问题。
语言支持为什么不在C++语言中增加对垃圾回收的支持?根据前面的讨论,我们可以看见,不同的应用环境,也许需要不同的垃圾回收器,不管三七二十一使用垃圾回收,需要将这些不同类型的垃圾回收器整合在一起,即使可以成功(对此我感到怀疑),也会导致效率成本的增加。
这违反了C++的设计哲学,“不为不必要的功能支付代价”,强迫用户接受垃圾回收的代价并不可取。
相反,按需选择你自己需要的垃圾回收器,需要掌握的规则与显式的管理内存相比,简单的多,也不容易出错。
最关键的一点, C++并不是“傻瓜型”的编程语言,他青睐喜欢和善于思考的编程者,设计一个合适自己需要的垃圾回收器,正是对喜爱C++的程序员的一种挑战。