但是事实往往并非如此,很多时候,一个程序的速度在框架设计完成时大致已经确定了,而并非是因为采用了C++语言才使其速度没有达到预期的目标。因此当一个程序的性能需要提高时,首先需要做的是用性能检测工具对其运行的时间分布进行一个准确的测量,找出关键路径和真正的瓶颈所在,然后针对瓶颈进行分析和优化,而不是一味盲目地将性能低劣归咎于所采用的语言。事实上,如果框架设计不做修改,即使用C语言或者汇编语言重新改写,也并不能保证提高总体性能。因此当遇到性能问题时,首先检查和反思程序的总体框架。然后用性能检测工具对其实际运行做准确地测量,再针对瓶颈进行分析和优化,这才是正确的思路。但不可否认的是,确实有一些操作或者C++的一些语言特性比其他因素更容易成为程序的瓶颈,一般公认的有如下因素。(1)缺页:如第四章中所述,缺页往往意味着需要访问外部存储。因为外部存储访问相对于访问内存或者代码执行,有数量级的差别。因此只要有可能,应该尽量想办法减少缺页。(2)从堆中动态申请和释放内存:如C语言中的malloc/free和C++语言中的new/delete操作非常耗时,因此要尽可能优先考虑从线程栈中获得内存。优先考虑栈而减少从动态堆中申请内存,不仅仅是因为在堆中开辟内存比在栈中要慢很多,而且还与“尽量减少缺页”这一宗旨有关。当执行程序时,当前栈帧空间所在的内存页肯定在物理内存中,因此程序代码对其中变量的存取不会引起缺页;相反,从堆中生成的对象,只有指向它的指针在栈上,对象本身却是在堆中。堆一般来说不可能都在物理内存中,而且因为堆分配内存的特性,即使两个相邻生成的对象,也很有可能在堆内存位置上相隔很远。因此当访问这两个对象时,虽然分别指向它们指针都在栈上,但是通过这两个指针引用它们时,很有可能会引起两次“缺页”。(3)复杂对象的创建和销毁:这往往是一个层次相当深的递归调用,因为一个对象的创建往往只需要一条语句,看似很简单。另外,编译器生成的临时对象因为在程序的源代码中看不到,更是不容易察觉,因此尤其值得警惕和关注。本章中专门有两节分别讲解对象的构造和析构,以及临时对象。(4)函数调用:因为函数调用有固定的额外开销,因此当函数体的代码量相对较少,且该函数被非常频繁地调用时,函数调用时的固定额外开销容易成为不必要的开销。C语言的宏和C++语言的内联函数都是为了在保持函数调用的模块化特征基础上消除函数调用的固定额外开销而引入的,因为宏在提供性能优势的同时也给开发和调试带来了不便。在C++中更多提倡的是使用内联函数,本章会有一节专门讲解内联函数。 构造函数与析构函数构造函数和析构函数的特点是当创建对象时,自动执行构造函数;当销毁对象时,析构函数自动被执行。这两个函数分别是一个对象最先和最后被执行的函数,构造函数在创建对象时调用,用来初始化该对象的初始状态和取得该对象被使用前需要的一些资源,比如文件/网络连接等;析构函数执行与构造函数相反的操作,主要是释放对象拥有的资源,而且在此对象的生命周期这两个函数都只被执行一次。创建一个对象一般有两种方式,一种是从线程运行栈中创建,也称为“局部对象”,一般语句为:{ …… Objectobj; ① ……} ②销毁这种对象并不需要程序显式地调用析构函数,而是当程序运行出该对象所属的作用域时自动调用。比如上述程序中在①处创建的对象obj在②处会自动调用该对象的析构函数。在这种方式中,对象obj的内存在程序进入该作用域时,编译器生成的代码已经为其分配(一般都是通过移动栈指针),①句只需要调用对象的构造函数即可。②处编译器生成的代码会调用该作用域内所有局部的用户自定义类型对象的析构函数,对象obj属于其中之一,然后通过一个退栈语句一次性将空间返回给线程栈。另一种创建对象的方式为从全局堆中动态创建,一般语句为:{ …… Object*obj=newObject; ① …… deleteobj; ② ……} ③当执行①句时,指针obj所指向对象的内存从全局堆中取得,并将地址值赋给obj。但指针obj本身却是一个局部对象,需要从线程栈中分配,它所指向的对象从全局堆中分配内存存放。从全局堆中创建的对象需要显式调用delete销毁,delete会调用该指针指向的对象的析构函数,并将该对象所占的全局堆内存空间返回给全局堆,如②句。执行②句后,指针obj所指向的对象确实已被销毁。但是指针obj却还存在于栈中,直到程序退出其所在的作用域。即执行到③处时,指针obj才会消失。需要注意的是,指针obj的
C 重要知识点 来自淘豆网m.daumloan.com转载请标明出处.