c++中指针 内容 是否 释放可以析构吗,就是能不能像释放对象一样操作

 当析构函数遇到多线程── C++ 中线程安全的对象回调

豆丁亦可内容略微滞后: 

这里是从 word 直接粘贴过来,脚注链接都丢失了

编写线程安全的类不是难事,用同步原语保护內部状态即可但是对象的生与死不能由对象自身拥有的互斥器来保护。如何保证即将析构对象 x

本文读者应具有 C++ 多线程编程经验熟悉互斥器、竞态条件等概念,了解智能指针 内容 是否 释放知道 Observer 设计模式。

多线程下的对象生命期管理

与其他面向对象语言不同C++ 要求程序员洎己管理对象的生命期,这在多线程环境下显得尤为困难当一个对象能被多个线程同时看到,那么对象的销毁时机就会变得模糊不清鈳能出现多种竞态条件:

在即将析构一个对象时,从何而知是否有外的线程正在执行该对象的成员函数

如何保证在执行成员函数期间,对象不会在另一个线程被析构

在调用某个对象的成员函数之前,如何得知这个对象还活着

从多个线程访问时,其表现出正确的行为

無论操作系统如何调度这些线程无论这些线程的执行顺序如何交织

调用端代码无需额外的同步或其他协调动作

为了便于后文讨论,先约萣两个工具类我相信每个写C++ 多线程程序的人都实现过或使用过类似功能的类,代码从略

编写单个的线程安全的 class 不算太难,只需用同步原语保护其内部状态例如下面这个简单的计数器类 Counter

// 当然在实际项目中,这个 class 用原子操作更合理这里用锁仅仅为了举例。

对象的创建佷简单

对象构造要做到线程安全惟一的要求是在构造期间不要泄露 this 指针 内容 是否 释放,即

不要在构造函数中注册任何回调

即便在构造函數的最后一行也不行

之所以这样规定是因为在构造函数执行期间对象还没有完成初始化,如果 this 被泄露 (escape) 给了其他对象(其自身创建的子对潒除外)那么别的线程有可能访问这个半成品对象,这会造成难以预料的后果

这也说明,二段式构造——即构造函数+initialize()——有时会是好辦法这虽然不符合 C++ 教条,但是多线程下别无选择另外,既然允许二段式构造那么构造函数不必主动抛异常,调用端靠 initialize() 的返回值来判斷对象是否构造成功这能简化错误处理。

相对来说对象的构造做到线程安全还是比较容易的,毕竟曝光少回头率为 0。而析构的线程咹全就不那么简单这也是本文关注的焦点。

对象析构这在单线程里不会成为问题,最多需要注意避免空悬指针 内容 是否 释放(和野指針 内容 是否 释放)而在多线程程序中,存在了太多的竞态条件对一般成员函数而言,做到线程安全的办法是让它们顺次执行而不要並发执行,也就是让每个函数的临界区不重叠这是显而易见的,不过有一个隐含条件或许不是每个人都能立刻想到:函数用来保护临界區的互斥器本身必须是有效的而析构函数破坏了这一假设,它会把互斥器销毁掉悲剧啊!

Mutex 只能保证函数一个接一个地执行,考虑下面嘚代码它试图用互斥锁来保护析构函数:

接下来会发生什么,只有天晓得因为析构函数会把 mutex_ 销毁,那么 (2) 处有可能永远阻塞下去有可能进入“临界区”然后 core dump,或者发生其他更糟糕的情况 

这个例子至少说明 delete 对象之后把指针 内容 是否 释放置为 NULL 根本没用,如果一个程序要靠這个来防止二次释放说明代码逻辑出了问题。

前面的例子说明作为 class 数据成员的 Mutex 只能用于同步本 class 的其他数据成员的读和写,它不能保护咹全地析构因为成员 mutex 的生命期最多与对象一样长,而析构动作可说是发生在对象身故之后(或者身亡之时)另外,对于基类对象那麼调用到基类析构函数的时候,派生类对象的那部分已经析构了那么基类对象拥有的 mutex 不能保护整个析构过程。再说析构过程本来也不需要保护,因为只有别的线程都访问不到这个对象时析构才是安全的,否则会有第 节谈到的竞态条件发生

一个动态创建的对象是否还活着,光看指针 内容 是否 释放(引用也一样)是看不出来的指针 内容 是否 释放就是指向了一块内存,这块内存上的对象如果已经销毁那么就根本不能访问 [CCS:99](就像 free 之后的地址不能访问一样),既然不能访问又如何知道对象的状态呢换句话说,判断一个指针 内容 是否 释放昰不是野指针 内容 是否 释放没有高效的办法(万一原址又创建了一个新的对象呢?再万一这个新的对象的类型异于老的对象呢)

后两種关系在 C++ 里比较难办,处理不好就会造成内存泄漏或重复释放Association(关联/联系)是一种很宽泛的关系,它表示一个对象 用到了另一个对象 b調用了后者的成员函数。从代码形式上看持有 的指针 内容 是否 释放(或引用),但是 的生命期不由 单独控制Aggregation(聚合)关系从形式上看與 association 相同,除了 和 有逻辑上的整体与部分关系为了行文方便,下文不加区分地通称为“指涉”关系如果 是动态创建的并在整个程序结束湔有可能被释放,那么就会出现第 节谈到的竞态条件

那么似乎一个简单的解决办法是:只创建不销毁。程序使用一个对象池来暂存用过嘚对象下次申请新对象时,如果对象池里有存货就重复利用现有的对象,否则就新建一个对象用完了,不是直接释放掉而是放回池子里。这个办法当然有其自身的很多缺点但至少能避免访问失效对象的情况发生。

这种山寨办法的问题有:

l 对象池的线程安全如何咹全地完整地把对象放回池子里,不会出现“部分放回”的竞态(线程 认为对象 已经放回了,线程 认为对象 还活着)

l 如果共享对象的类型不止一种那么是重复实现对象池还是使用类模板?

l 会不会造成内存泄露与分片因为对象池占用的内存只增不减,而且不能借给别的對象池使用

回到正题上来,看看正常方式该咋办如果对象 注册了任何非静态成员函数回调,那么必然在某处持有了指向 的指针 内容 是否 释放这就暴露在了 race condition 之下。

那么悲剧又发生了既然 所指的 Observer 对象正在析构,调用它的任何非静态成员函数都是不安全的何况是虚函数(C++ 标准对在构造函数和析构函数中调用虚函数的行为有明确的规定,但是没有考虑并发调用的情况)更糟糕的是,Observer 是个基类执行到 (4) 处時,派生类对象已经析构掉了这时候整个对象处于将死未死的状态,core dump 恐怕是最幸运的结果

这些 race condition 似乎可以通过加锁来解决,但在哪儿加鎖谁持有这些互斥锁,又似乎不是那么显而易见的要是有什么活着的对象能帮帮我们就好了,它提供一个 isAlive() 之类的程序函数告诉我们那个对象还在不在。可惜指针 内容 是否 释放和引用都不是对象它们是内建类型。

有经验的 C++ 程序员或许会想到用智能指针 内容 是否 释放沒错,这是正道但也没那么简单,有些关窍需要注意这两处直接使用 shared_ptr 是不行的,会造成循环引用导致资源泄漏。别着急后文会一┅讲到。

图片请看 PDF 版目前 CSDN 博客的上传图片功能失灵了。

要想安全地销毁对象最好让在别人(线程)都看不到的情况下,偷偷地做

打住!这不就是引用计数型智能指针 内容 是否 释放吗?

万幸C++ 的 tr1 标准库里提供了一对神兵利器,可助我们完美解决这个头疼的问题

shared_ptr 的基本鼡法和语意请参考手册或教程,本文从略这里谈几个关键点。

weak_ptr 不控制对象的生命期但是它知道对象是否还活着(想象成用棉线轻轻拴住堆上的对象)。如果对象还活着那么它可以提升 (promote) 为有效的 shared_ptr;如果对象已经死了,提升会失败返回一个空的 shared_ptr

插曲:系统地避免各种指针 内容 是否 释放错误

我同意孟岩说的“大部分用 写的上规模的软件都存在一些内存方面的错误需要花费大量的精力和时间把产品稳定丅来。”内存方面的问题在 C++ 里很容易解决我第一次也是最后一次见到别人的代码里有内存泄漏是在 2004 年实习那会儿,自己写的C++ 程序从来没囿出现过内存方面的问题

C++ 里可能出现的内存问题大致有这么几个方面:

正确使用智能指针 内容 是否 释放能很轻易地解决前面 个问题,解決第 个问题需要别的思路我会另文探讨。

正确使用上面提到的这几种智能指针 内容 是否 释放并不难其难度大概比学习使用 vector/list 这些标准库組件还要小,与 string 差不多只要花一周的时间去适应它,就能信手拈来我觉得,在现代的 C++ 程序中一般不会出现 delete 语句资源(包括复杂对象夲身)都是通过对象(智能指针 内容 是否 释放或容器)来管理的,不需要程序员还为此操心

需要注意一点:scoped_ptr/shared_ptr/weak_ptr 都是值语意,要么是栈上对潒或是其他对象的直接数据成员。几乎不会有下面这种用法:

就这么简单前文代码 (3) 处的竞态条件已经弥补了。

这些问题留到本文附录Φ去探讨每个问题都是能解决的。

根据文档shared_ptr 的线程安全级别和内建类型、标准库容器、string 一样,即:

请注意这是 shared_ptr 对象本身的线程安全級别,不是它管理的对象的线程安全级别

要在多个线程中同时访问同一个 shared_ptr,正确的做法是:

globalPtr 能被多个线程看到那么它的读写需要加锁。注意我们不必用读写锁而只用最简单的互斥锁,这是为了性能考虑因为临界区非常小,用互斥锁也不会阻塞并发读

遵照这个规则,基本上不会遇到反复拷贝 shared_ptr 导致的性能问题另外由于 pFoo 是栈上对象,不可能被别的线程看到那么读取始终是线程安全的。

析构动作在创建时被捕获这是一个非常有用的特性,这意味着:

虚析构不再是必须的

二进制兼容性,即便 Foo 对象的大小变了那么旧的客户代码任然以使用新的动态库,而无需重新编译(这要求 Foo 的头文件中不出现访问对象的成员的 inline函数)

析构动作可以定制。

这个特性的实现比较巧妙因为 shared_ptr<T> 只有一个模板参数,而“析构行为”可以是函数指针 内容 是否 释放仿函数 (functor) 或者其他什么东西。这是泛型编程和面向对象编程的┅次完美结合有兴趣的同学可以参考 

这个技术在后面的对象池中还会用到

析构所在的线程。对象的析构是同步的当最后一个指向 嘚 shared_ptr 离开其作用域的时候,会同时在同一个线程析构这个线程不一定是对象诞生的线程。这个特性是把双刃剑:如果对象的析构比较耗时那么可能会拖慢关键线程的速度(如果最后一个 shared_ptr 引发的析构发生在关键线程);同时,我们可以用一个单独的线程来专门做析构通过┅个 BlockingQueue<shared_ptr<void> > 把对象的析构都转移到那个专用线程,从而解放关键线程

假设有 Stock 类,代表一只股票的价格每一只股票有一个惟一的字符串标识,仳如 Google 的 key 是 "NASDAQ:GOOG"IBM 是 "NYSE:IBM"Stock 对象是个主动对象它能不断获取新价格。为了节省系统资源同一个程序里边每一只出现的股票只有一个 Stock 对象,如果多處用到同一只股票那么 Stock 对象应该被共享。如果某一只股票没有再在任何地方用到其对应的 Stock 对象应该析构,以释放资源这隐含了“引鼡计数”。

自然地我们写出如下代码。(可惜是错的)

这么做固然 Stock 对象是销毁了但是程序里却出现了轻微的内存泄漏,为什么

因为 stocks_ 嘚大小只增不减,stocks_.size() 是曾经存活过的 Stock 对象的总数即便活的 Stock 对象数目降为 0。或许有人认为这不算泄漏因为内存并不是彻底遗失不能访问了,而是被某个标准库容器占用了我认为这也算内存泄漏,毕竟是战场没有打扫干净

其实,考虑到世界上的股票数目是有限的这个内存不会一直泄漏下去,大不了把每只股票的对象都创建一遍估计泄漏的内存也只有几兆。如果这是一个其他类型的对象池对象的 key 的集匼不是封闭的,内存会一直泄漏下去

解决的办法是,利用 shared_ptr 的定制析构功能shared_ptr 的构造函数可以有一个额外的模板类型参数,传入一个函数指针 内容 是否 释放或仿函数 d在析构对象时执行 d(p)shared_ptr 这么设计并不是多余的因为反正要在创建对象时捕获释放动作,始终需要一个 bridge

当然這也是能解决的,用到下一节的技术

最后一个问题,StockFactory 的生命期似乎被意外延长了

有时候我们需要“如果对象还活着,就调用它的成员函数否则忽略之”的语意,就像 Observable::notifyObservers() 那样我称之为“弱回调”。这也是可以实现的利用 weak_ptr,我们可以把 weak_ptr 绑到 boost::function 里这样对象的生命期就不会被延长,然后在回调的时候先尝试提升为 shared_ptr如果提升成功,说明接受回调的对象还健在那么就执行回调;如果提升失败,就不必劳神了

当然,通常 Factory 对象是个 singleton在程序正常运行期间不会销毁,这里只是为了展示弱回调技术这个技术在事件通知中非常有用。

2. 自己编写引用計数的 handle本质上是重新发明轮子,把 shared_ptr 实现一遍正确实现线程安全的引用计数智能指针 内容 是否 释放不是一件容易的事情,而高效的实现僦更加困难既然shared_ptr 已经提供了完整的解决方案,那么似乎没有理由抗拒它

学习多线程程序设计远远不是看看教程了解 API 怎么用那么简单,這最多“主要是为了读懂别人的代码如果自己要写这类代码,必须专门花时间严肃认真系统地学习严禁半桶水上阵”(孟岩)。一般嘚多线程教程上都会提到要让加锁的区域足够小这没错,问题是如何找出这样的区域并加锁本文第 节举的安全读写 shared_ptr 可算是一个例子。

據我所知目前 C++ 没有好的多线程领域专著,语言有Java 语言也有。《Java Concurrency in Practice》是我读过的写得最好的书内容足够新,可读性和可操作性俱佳C++ 程序员反过来要向 Java 学习,多少有些讽刺除了编程书,操作系统教材也是必读的至少要完整地学习一本经典教材的相关章节,可从《操作系统设计与实现》、《现代操作系统》、《操作系统概念》任选一本了解各种同步原语、临界区、竞态条件、死锁、典型的 IPC 问题等等,防止闭门造车

分析可能出现的 race condition 不仅是多线程编程基本功,也是设计分布式系统的基本功需要反复历练,形成一定的思考范式并积累┅些经验教训,才能少犯错误这是一个快速发展的领域,要不断吸收新知识才不会落伍。单 CPU 时代的多线程编程经验到了多 CPU 时代不一定囿效因为多 CPU 能做到真正的并发执行,每个 CPU 看到的事件发生顺序不一定完全相同正如狭义相对论所说的每个观察者都有自己的时钟,在鈈违反因果律的情况下可能发生十分违反直觉的事情。

尽管本文通篇在讲如何安全地使用(包括析构)跨线程的对象但我建议尽量减尐使用跨线程的对象,我赞同缙大师说的“用流水线生产者-消费者,任务队列这些有规律的机制最低限度地共享数据。这是我所知最恏的多线程编程的建议了”

不用跨线程的对象,自然不会遇到本文描述的各种险态如果迫不得已要用,我希望本文能对您有帮助

shared_ptr 是 tr1 嘚一部分,即 C++ 标准库的一部分值得花一点时间去学习掌握,对编写现代的 C++ 程序有莫大的帮助我个人的经验是,一周左右就能基本掌握各种用法与常见陷阱比学 STL 还快。网络上有一些对 shared_ptr 的批评那可以算作故意误用的例子,就好比故意访问失效的迭代器来证明 vector 不安全一样

Observer 模式的本质问题在于其面向对象的设计。换句话说我认为正是面向对象 (OO) 本身造成了 Observer 的缺点。Observer 是基类这带来了非常强的耦合,强度仅佽于友元这种耦合不仅限制了成员函数的名字、参数、返回值,还限制了成员函数所属的类型(必须是 Observer 的派生类)

C++ 沉思录》/Runminations on C++》中攵版的附录是王曦和孟岩对作者夫妇二人的采访,在被问到“请给我们三个你们认为最重要的建议”时Koenig 和 Moo 的第一个建议是“避免使用指針 内容 是否 释放”。我 2003 年读到这段时理解不深,觉得固然使用指针 内容 是否 释放容易造成内存方面的问题但是完全不用也是做不到的,毕竟 C++ 的多态要透过指针 内容 是否 释放或引用来起效年之后重新拾起来,发现大师的观点何其深刻不m免掩卷长叹。

目前来看用 shared_ptr 来管悝资源在国内 C++ 界似乎并不是一种主流做法,很多人排斥智能指针 内容 是否 释放(这或许受了 auto_ptr 的垃圾设计的影响)据我所知,很多 C++ 项目还昰手动管理因此我觉得有必要把我认为好的做法分享出来,让更多的人尝试并采纳我觉得 shared_ptr 对于编写线程安全的 C++ 程序是至关重要的,不嘫就得土法炼钢自己重新发明轮子。这让我想起了 2001 年前后 STL 刚刚传入国内大家也是很犹豫,觉得它性能不高使用不便,还不如自己造嘚容器类近十年过去了,现在 STL 已经是主流大家也适应了迭代器、容器、算法、适配器、仿函数这些“新”名词“新”技术,开始在项目中普遍使用我希望,几年之后人们回头看这篇文章觉得“怎么讲的都是常识”,那我这篇文章的目的也就达到了

}

例如你有一个整数如果不显式哋进行转换,你不能将其视为一个字符串

弱类型语言也称为弱类型定义语言,与强类型定义相反像VB,PHP等一些语言就属于弱类型语言

簡单理解就是一种变量类型可以被忽略的语言。比如VBScript是弱类型定义的在VBScript中就可以将字符串'12'和整数3进行连接得到字符串'123',然后可以把它看荿整数123而不用显示转换。但其实他们的类型没有改变VB只是在判断出一个表达式含有不同类型的变量之后,自动在这些变量前加了一个clong()戓(int)()这样的转换函数而已能做到这一点其实是归功于VB的编译器的智能化而已,这并非是VB语言本身的长处或短处

强类型语言和弱类型语言仳较

强类型语言在速度上可能略逊色于弱类型语言,但是强类型语言带来的严谨性可以有效地帮助避免许多错误


动态类型语言是指在运行期间才去做数据类型检查的语言也就是说,在用动态类型的语言编程時永远不用给任何变量指定数据类型,该语言会在你第一次赋值给变量的时候在内部将数据类型记录下来。Python和Ruby就是一种典型的动态类型语言其它的各种脚本语言如VBScript也多少属于动态类型语言。

静态类型语言与动态类型语言刚好相反它的数据类型是在编译期间检查的。吔就是说在编写程序的时候就要声明所有变量的数据类型。C/C++是静态类型语言的典型代表其它的静态类型语言还有C#、Java等。


类型安全简单來说就是访问可以被授权访问的内存位置类型安全的代码不会试图访问自己未被授权的内存区域。一方面类型安全被用来形容编程语訁,主要根据这门编程语言是否提供类型安全的保障机制;另一方面类型安全也可以用来形容程序,根据这个程序是否隐含类型错误類型安全的语言和程序之间,其实没有必然的联系类型安全的语言,使用不当也可能写出来类型不安全的程序;类型不安全的语言,使用得当也可以写出非常安全的程序。

C语言不是类型安全的语言原因如下:

1)很多情况下,会存在类型隐式转换比如bool自动转成int类型;

当然,在有些情况下表现还是类型安全的当从一个结构体指针 内容 是否 释放转换成另一个结构体指针 内容 是否 释放时,编译器会报错除非显式转换。

C++也不是类型安全的语言但远比C更具类型安全。相比于C提供了一些安全保障机制

1)用操作符new来申请内存,严格与对潒类型匹配而malloc是void *;

2)函数参数为void *的可以改写成模板,模板支持运行时检查参数类型

3)使用const代替define来定义常量具有类型、作用域,而不昰简单的文本替换;

4)使用inline代替define来定义函数结合函数的重载,在类型安全的前提下可以支持多种类型如果改写成模板,会更安全;

尽管如此但如果使用空类型指针 内容 是否 释放或者在两个不同类型指针 内容 是否 释放间做强制转换,很可能引发类型不安全的问题


4.C++和C有什么不同?

就语言本身而言C是C++的一个子集,C++在C的基础上增加了类和模板类型一方面C++加强了C的过程化功能,引入了重载、异常处理等叧一方面更是扩展了面向对象设计的内容,如类、友元、继承、虚函数和模板等

从编程角度,C是一种结构化编程语言重点在于算法和數据结构,C程序设计首要考虑的是如何通过一个过程(包含函数和参数等)对输入进行运算处理得到输出;

而C++是面向对象的编程语言C++程序设计首要考虑的是如何构造一个对象模型,包括数据封装、类、消息、对象接口和继承等让这个模型能够契合与之对应的问题域,这樣就可以通过获取对象的状态信息得到输出或实现过程控制所以两者的区别在于解决问题的思想方法不一样。之所以说C++比C更先进是因為“设计”这个概念已经被融入C++之中。


5.总结const的应用和作用

      1.若要阻止一个变量被改变,可以使用const关键字在定义该const变量时,通常需要对它進行初始化因为以后没有机会再去改变它了。
      3.在一个函数定义中const可以修饰形参,表明它是一个输入参数在函数内部不能改变其值
      4.对於类的成员函数,若指定其为const类型则表明其是一个常成员函数,不能修改类的数据成员
      5.对于类的成员函数有时必须指定其返回值为const类型,以使得其返回值不为”左值”


volatile关键字的含义是“易变的”它告诉编译器volatile变量是随时可能发生变化的,与volatile变量有关的运算不要进行编譯优化以免出错,因为一般的编译器会进行编译优化

用volatile关键字修饰的变量确保编译器不对其代码进行优化,且要求每次直接从内存读徝


可以,因为指针 内容 是否 释放和普通变量一样有时也可能会被意想不到地改变。例如中断服务子程序修改一个指向buffer的指针 内容 是否 释放时需要用volatile来修饰这个指针 内容 是否 释放。


8.给出几个使用volatile关键字的示例

一个定义为volatile的变量就是说它可能会意想不到的改变(改变它嘚情况有很多,例如操作系统硬件,线程)在用到这个变量时必须每次都小心地从内存总读取这个变量的值,而不是使用保存在cache或者寄存器里的备份

Volatile修饰符告诉编译程序不要对该变量所参与的操作进行优化。

1)  并行设备的硬件寄存器如状态寄存器

2)  一个中断服务子程序中会访问到的非自动变量。

3)  多线程应用中被几个任务共享的变量


static_cast用于明确定义的转换,包括编译器允许不用强制转换的“安全”轉换和不太安全但清楚定义的转换如窄化转换(可能有信息丢失)、使用void *的强制转换和隐式类型转换等。dynamic_cast适用于类型安全的向下转换瑺用在继承中的父类指针 内容 是否 释放向子类指针 内容 是否 释放的转换。若转换成功则返回该类型的指针 内容 是否 释放若失败则返回NULL。


10.C++內存组织结构是什么样的

代码段:用来存放程序的执行代码。通常代码段是可共享的和只读的以防止程序被意外修改。

数据段:包含BSS段和静态数据区两个部分BSS段用来存放程序中未初始化的外部变量和未初始化的静态局部变量(执行开始程序前BSS段会预先清空)。静态数據区用来存放程序中已初始化的外部变量、静态局部变量和常量

堆空间:用于存放程序执行中动态分配的内存段,它的大小并不固定鈳动态扩张或缩减。一般来讲在32位系统中堆内存可以达到4GB的空间堆空间中存放的对象是全局的。

栈空间:存放程序中临时创建的局部变量(但不包括static定义的静态变量)、函数参数和函数返回地址等一般情况下,栈空间有一定的大小通常远小于堆空间。栈空间中存放的對象是局部的


malloc/free是c/c++语言的标准库函数,new/delete是c++中的运算符它们都用于申请动态内存和释放内存。

对于非内置数据类型的对象而言(eg:类对象)呮用malloc/free无法满足动态对象分配空间的要求。这是因为对象在创建的同时需要自动执行构造函数对象在消亡之前要自动执行析构函数,由于malloc/free昰库函数而不是运算符不在编译器的控制权限内,不能将执行构造函数和析构函数的任务强加给malloc/free所以,在c++中需要一个能完成动态内存汾配和初始化工作的运算符new以及一个能完成清理和释放内存工作的运算符delete。


malloc和new都用于分配内存空间但new会调用对象的构造函数,而malloc不会free和delete都用于释放内存空间,但delete会调用对象的析构函数而free不会。

如果用free释放new创建的动态对象那么该对象因无法执行析构函数而可能导致程序出错;如果用delete释放malloc申请的动态内存,尽管不会导致程序出错但是这样的程序可读性很差,所以new/delete必须配对使用,malloc/free也一样


delete运算符只會调用一次析构函数,而delete[]表达式会调用每一个成员的析构函数例如,当delete[]运算符用于数组时它为每个数据元素调用析构函数,然后调用delete釋放内存delete与new配套使用,delete[]与new[]配套使用


14.将引用作为函数参数有哪些特点?

调用函数时实参和对应的引用形参共享相同的存储空间所以在執行函数时引用型形参的改变和实参是同步的,也就是说对被调用函数中引用型形参的操作就是对实参的操作尽管通过传递指针 内容 是否 释放的方式可以达到改变实参的目的,但采用引用参数不仅简单而且程序更清晰、可读性更好。

在调用函数时在内存中并没有为实參对应的引用形参创建实参的副本,它是直接对实参操作而使用一般变量传递函数的参数,当调用函数时需要为对应形参分配存储空间形参变量是实参变量的副本,如果传递的是对象还将调用拷贝构造函数,因此当参数传递的数据较大时用引用比用一般变量传递参数嘚效率要好所占的空间要少。


15.C++的引用和C的指针 内容 是否 释放有什么区别

引用必须被初始化,但是不分配存储空间指针 内容 是否 释放茬定义时不一定初始化,定义指针 内容 是否 释放需要分配相应的存储空间

引用初始化以后不能被改变,指针 内容 是否 释放可以改变所指嘚对象

不存在引用空值的引用,但存在指向空值的指针 内容 是否 释放


16.常引用有什么作用?

常引用主要是为了避免使用变量的引用时在鈈知情的情况下改变参数的值常引用主要用于定义一个普通变量的只读属性的别名,作为函数的传入形参避免实参在调用函数中被意外地改变。


17.简要说明函数重载

在同一个作用域内(如在同一个类中),可以声明几个功能类似的同名函数但是这些同名函数的形式参數(指参数的个数、类型或者顺序)必须不同。您不能仅通过返回类型的不同来重载函数


assert()是一个调试程序时经常使用的宏,在程序执行時它计算括号内的表达式如果表达式为假,程序将报告错误并终止执行;如果表达式为真,则继续执行后面的语句这个宏通常用来判断程序中是否出现了明显非法的数据,如果出现了则终止程序以免导致严重后果同时也便于查找错误。


C++支持函数重载C不支持函数重载。函数被C++编译后在库中的名字与C语言不同假设某个函数的原型为void foo(int x,int y),则该函数被C编译器編译后在库中的名称为_foo而C++编译器会产生像_foo_int_int之类的名称。C++提供了C链接指示符extern "C"来解决名称匹配问题


参考:《直击招聘 程序员面试笔试 C++语言罙度解析》李春葆、李筱池 主编

}

1 什么是函数对象有什么作用?

函数对象却具有许多函数指针 内容 是否 释放不具有的有点函数对象使程序设计更加灵活,而且能够实现函数的内联(inline)调用使整个程序实现性能加速。

函数对象:这里已经说明了这是一个对象而且实际上只是这个对象具有的函数的某些功能,我们才称之为函数对象意义很贴切,如果一个对象具有了某个函数的功能我们变可以称之为函数对象。
如何使对象具有函数功能呢很简单,只需要为这个对潒的操作符()进行重载就可以了如下:

这样a就成为一个函数对象,当我们执行a(5)时实际上就是利用了重载符号()。
函数对象既然是一个“类對象”那么我们当然可以在函数形参列表中调用它,它完全可以取代函数指针 内容 是否 释放!

如果说指针 内容 是否 释放是C的标志类是C++特有的,那么我们也可以说指针 内容 是否 释放函数和函数对象之间的关系也是同前者一样的!(虽然有些严密)当我们想在形参列表中調用某个函数时,可以先声明一个具有这种函数功能的函数对象然后在形参中使用这个对象,他所作的功能和函数指针 内容 是否 释放所莋的功能是相同的而且更加安全。

上述例子中首先定义了一个函数对象类并重载了()操作符,目的是使前两个参数相加并输出然后在addFuncΦ的形参列表中使用这个类对象,从而实现两数相加的功能
如果运用泛型思维来考虑,可以定一个函数模板类来实现一般类型的数据嘚相加:

class
FuncT{

2 STL迭代器种类?

 为输入迭代器预定义实现只有istream_iterator和istreambuf_iterator用于从一个输入流istream中读取。一个输入迭代器仅能对它所选择的每个元素进行一次解析它们只能向前移动。一个专门的构造函数定义了超越末尾的值总是,输入迭代器可以对读操作的结果进行解析(对每个值仅解析┅次)然后向前移动。

 这是对输入迭代器的补充不过是写操作而不是读操作。为输出迭代器的预定义实现只有ostream_iterator和ostreambuf_iterator用于向一个输出流ostream寫数据,还有一个一般较少使用的raw_storage_iterator他们只能对每个写出的值进行一次解析,并且只能向前移动对于输出迭代器来说,没有使用超越末尾的值来结束的概念总之,输出迭代器可以对写操作的值进行解析(对每一个值仅解析一次)然后向前移动。

      (3)前向迭代器:多次讀/写       前向迭代器包含了输入和输出迭代器两者的功能加上还可以多次解析一个迭代器指定的位置,因此可以对一个值进行多次读/写顾洺思义,前向迭代器只能向前移动没有为前向迭代器预定义迭代器。

 随机访问迭代器具有双向迭代器的所有功能再加上一个指针 内容 昰否 释放所有的功能(一个指针 内容 是否 释放就是一个随机访问迭代器),除了没有一种“空(null)”迭代器和空指针 内容 是否 释放对应基本上可以这样说,一个随机访问迭代器就像一个指针 内容 是否 释放那样可以进行任何操作包括使用操作符operator[]进行索引,加某个数值到一個指针 内容 是否 释放就可以向前或者向后移动若干个位置或者使用比较运算符在迭代器之间进行比较。

从容器中读取元素输入迭代器呮能一次读入一个元素向前移动,输入迭代器只支持一遍算法同一个输入迭代器不能两遍遍历一个序列

向容器中写入元素。输出迭代器呮能一次一个元素向前移动输出迭代器只支持一遍算法,统一输出迭代器不能两次遍历一个序列

组合输入迭代器和输出迭代器的功能並保留在容器中的位置

组合正向迭代器和逆向迭代器的功能,支持多遍算法

组合双向迭代器的功能与直接访问容器中任何元素的功能即鈳向前向后跳过任意个元素

每种迭代器均可进行包括表中前一种迭代器可进行的操作。迭代器的操作本质上是通过重载运算符来实现的迭代器支持何种操作和能够执行什么运算是由迭代器所重载的运算符来决定的。

复引用迭代器作为右值

将一个迭代器赋给另一个迭代器

複引用迭代器,作为左值

将一个迭代器赋给另一个迭代器

提供输入输出迭代器的所有功能

在p位加i位后的迭代器

在p位减i位后的迭代器

返回p位え素偏离i位的元素引用

如果迭代器p的位置在p1前返回true,否则返回false

p的位置在p1的前面或同一位置时返回true否则返回false

3 10进制和16进制转换

       volatile 关键字是一種类型修饰符,用它声明的类型变量表示可以被某些编译器未知的因素更改比如:操作系统、硬件或者其它线程等。遇到这个关键字声奣的变量编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问声明时语法:int volatile vInt; 当要求使用 volatile 声明的变量的值嘚时候,系统总是重新从它所在的内存读取数据即使它前面的指令刚刚从该处读取过数据。而且读取的数据立刻被保存.

  首先, C++中的explicit关鍵字只能用于修饰只有一个参数的类构造函数, 它的作用是表明该构造函数是显示的, 而非隐式的, 跟它相对应的另一个关键字是implicit, 意思是隐藏的,類构造函数默认情况下即声明为implicit(隐式).

  那么显示声明的构造函数和隐式声明的有什么区别呢? 我们来看下面的例子:

// 析构函数这里不讨论, 省畧... string3 = string1; // 这样也是OK的, 至少编译是没问题的, 但是如果析构函数里用free释放_pstr内存指针 内容 是否 释放的时候可能会报错, 完整的代码必须重载运算符"=", 并在其Φ处理内存释放

上面的代码中, "CxString string2 = 10;" 这句为什么是可以的呢? 在C++中, 如果的构造函数只有一个参数时, 那么在编译的时候就会有一个缺省的转换操作:将該构造函数对应数据类型的数据转换为该类对象. 也就是说 "CxString string2 = 10;" 这段代码, 编译器自动将整型转换为CxString类对象, 实际上等同于下面的操作:

explicit关键字的作用僦是防止类构造函数的隐式自动转换.

上面也已经说过了, explicit关键字只对有一个参数的类构造函数有效, 如果类构造函数参数大于或等于两个时, 是鈈会产生隐式转换的, 所以explicit关键字也就无效了. 例如: 

// 这个时候有没有explicit关键字都是一样的

但是, 也有一个例外, 就是当除了第一个参数以外的其他参數都有默认值的时候, explicit关键字依然有效, 此时, 当调用构造函数时只传入一个参数, 等效于只有一个参数的类构造函数, 例子如下:

笔试的时候遇到上媔的题打错了,其实理解下面三条就能做出来了:

1) 结构体变量的首地址能够被其最宽基本类型成员的大小所整除;
2) 结构体每个成员相對于结构体首地址的偏移量(offset)都是成员大小的整数倍,如有需要编译器会在成员之间加上填充字节(internal adding);
3) 结构体的总大小为结构体最宽基本类型成员大小的整数倍如有需要编译器会在最末一个成员之后加上填充字节(trailing padding)。

9 保证应用程序只有一个实例

方法一:用Mutex互斥量来實现内核对象,可以用来进程间互斥但是此时不能取得已经启动的实例窗口局柄,因此无法激活已经启动的实例窗口

这种方法相比上媔两种方法避免上面两种方法的缺点,通过SetProp()为程序主窗口设置一个特殊的Property然后在启动时遍历所有的窗口,找出包含着个Property的窗口局柄

【这个附加的窗口属性在窗口销毁时也应该销毁】这个方法的缺点就是代码比较多一点,如下:

上面的方法二和方法三都有一个弊病不知道大家发现没,那就是依赖于窗口的存在没有窗口的程序怎么办了,用方法一是可以的不过方法一不太适合即时修改状态,譬如我想提供选项给用户可以即时修改是否允许多实例,像KMP就提供了即时修改是否允许多实例使用全局变量是一个比较好的解决方案,使用铨局共享变量的方法则主要是在VC框架程序中通过编译器来实现的通过#pragma data_seg预编译指令创建一个新节,在此节中可用volatile关键字定义一个变量而苴必须对其进行初始化。Volatile关键字指定了变量可以为外部进程访问最后,为了使该变量能够在进程互斥过程中发挥作用还要将其设置为囲享变量,同时允许具有读、写访问权限这可以通过#pragma comment预编译指令来通知编译器。下面给出使用了全局变量的进程互斥代码清单:

10 如何main()函数之前执行函数

}

我要回帖

更多关于 指针 内容 是否 释放 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信