非正式的证据指出,面向对象的C + +程序的速度与用C写的程序速度相差在± 1 0 %之内,而且常常更接近。
库,简单地说就是一些人已经写的代码,按某种方式包装在一起。通常,最小的包是带有扩展名如L I B的文件和向编译器声明库中有什么的一个或多个头文件。连接器知道如何在L I B文件中搜索和提取相应的已编译的代码。但是,这只是提供库的一种方法。在跨越多种体系结构的平台上,例如U N I X,通常,提供库的最明智的方法是用源代码,这样在新的目标机上它能被重新编译。而在微软Wi n d o w s上,动态连接库是最明智的方法,这使得我们能够利用新发布的D D L经常修改我们的程序,我们的库函数销售商可能已经将新D D L发送给我们了。
。“声明”向计算机介绍名字,它说,“这个名字是什么意思”。而“定义”为这个名字分配存储空间。无论涉及到变量时还是函数时含义都一样。无论在哪种情况下,编译器都在“定义”处分配存储空间。
声明常常使用于e x t e r n关键字。如果我们只是声明变量而不是定义它,则要求使用e x t e r n。
对于函数声明, e x t e r n是可选的,不带函数体的函数名连同参数表或返回值,自动地作为一个声明。
例子:
extern int i;/*Declaration without definition*/
extern float f(float);/*function declaration*/
float(float);/*declaration*/
float b;/*Declaration & definition*/
float f(float a)/*Definition*/
{}
int h(int x){}/*Declaration & definition*/
make比较源代码文件的日期和目标文件的日期,如果目标文件的日期比源代码文件的早,make 就调用这个编译器对这个单元进行处理。
makefile 是描述项目中所有文件之间关系的文本文件。
定义这个函数时,需要完全指定它是哪一个。为了完成这个指定任务, C + +有一个新的运算符: :,即范围分解运算符(这样命名是因为名字现在能在不同的范围:在全局范围或在这个struct 的范围)。
首先,在头文件中的声明是由编译器要求的。在C + +中,不能调用未事先声明的函数,否则编译器将报告一个出错信息。
为了恰当地组织代码和写有效的头文件,有一些问题必须知道。第一个问题是将什么放进头文件中。基本规则是“只声明”,也就是说,对于编译器只需要一些信息以产生代码或创建变量分配内存。
int A;
void f(){}
struct s
{
int A;
void f();
};
void S::f()
{
::f();//全局函数,而不是结构体中的函数
::A++;//select the global A 全局变量
A--;//The A at struct scope
}
C + +语言引进了三个新的关键字,用于在s t r u c t中设置边界: p u b l i c、p r i v a t e和p r o t e c t e d
使用存取指定符,后面必须跟上一个冒号。
构造函数和析构函数是两个非常特殊的函数:它们没有返回值。这与返回值为v o i d的函数显然不同。后者虽然也不返回任何值,但我们还可以让它做点别的。而构造函数和析构函数则不允许。
析构函数的语法与构造函数一样,用类的名字作函数名。然而析构函数前面加上一个~,以和构造函数区别
当对象超出它的定义范围时,编译器自动调用析构函数。我们可以看到,在对象的定义点处构造函数被调用,但析构函数调用的唯一根据是包含该对象的右括号
内存分配都是在一个堆栈中。内存分配是通过编译器向下移动堆栈指针来实现的(这只是相对而言,实际指针值可能增加,也可能减少,这依赖于机器)。也可以在堆中分配对象的内存
缺省构造函数就是不带任何参数的构造函数。缺省的构造函数用来创建一个“香草(v a n i l l a)对象” ,当编译器需要创建一个对象而又不知任何细节时,缺省的构造函数就显得非常重要
。如果我们想打印三种不
同类型的数据:整型、字符型和实型,我们通常不得不用三个不同的函数名,如p r i n t _ i n t ( )、p r i n t _ c h a r ( )和p r i n t _ f l o a t ( ) ,这些既增加了我们的编程工作量,也给读者理解程序增加了困难。这就体现了函数重载的好处。
c o n s t的最初动机是取代预处理器# d e f i n e s进行值替代
用C语言进行程序设计时,预处理器可以不受限制地建立宏并用它来替代值。因为预处理器只做文本替代,它既没有类型检查思想,也没有类型检查工具,所以预处理器的值替代会产生一些微小的问题,这些问题在C + +中可通过使用c o n s t而避免。
C语言中预处理器用值替代名字的典型用法是这样的:
#define BUFSIZE 100
B U F S I Z E是一个名字,它不占用存储空间且能放在一个头文件里,目的是为使用它的所有编译单元提供一个值。用值替代而不是用所谓的“不可思议的数”,这对于支持代码维护是非常重要的。如果代码中用到不可思议的数,读者不仅不清楚这个数字来自哪里,而且也不知道它代表什么,进而,当决定改变一个值时,程序员必须执行手动编辑,而且还不能跟踪以保证没有漏掉其中的一个。
可以这样写:
const bufsize=100;
或用更清楚的形式:
const int bufsize=100;
与使用# d e f i n e一样,使用c o n s t必须把c o n s t定义放进头文件里。这样,通过包含头文件,可把c o n s t定义单独放在一个地方并把它分配给一个编译单元。C + +中的c o n s t默认为内部连接,也就是说,c o n s t仅在c o n s t被定义过的文件里才是可见的,而在连接时不能被其他编译单元看到。当定义一个常量(c o n s t)时,必须赋一个值给它,除非用e x t e r n作了清楚的说明:
extern const bufsize;
正如任何复杂的定义一样,是在标识符的开始处读它并从里向外读。c o n s t指定那个“最靠近”的。这样,如果要使正指向的元素不发生改变,我们得写一个像这
样的定义:
const int* x;
从标识符开始,是这样读的:“x是一个指针,它指向一个const int。”这里不需要初始化,因为说x可以指向任何东西(那是说,它不是一个c o n s t),但它所指的东西是不能被改变的。
,不管何时在一行里仅放一个指针定义,且在定义的地方初始化每个指针。正
因为这一点,才可以把‘*’“附于”数据类型上:
int* u=&w;
避免这样:int* u = &w,v=0;
如果函数是以值传递的,可用c o n s t限定函数参数,如:
void f1(const int i)
{
i++;//illegal,i为const常量,不能改变其值
}
在C中,保护效率的一个方法是使用宏( m a c r o )。宏可以不用普通函数调用就使之看起来像
函数调用。宏的实现是用预处理器而不是编译器。预处理器直接用宏代码代替宏调用,所以就
没有了参数压栈、生成汇编语言的C A L L、返回参数、执行汇编语言的R E T U R N的时间花费。
所有的工作由预处理器完成,因此,不用花费什么就具有了程序调用的便利和可读性。
为了既保持预处理器宏的效率又增加安全性,而且还能像一般成员函数一样可以在类里访问自如,C + +用了内联函数(inline function)。
任何在类中定义的函数自动地成为内联函数,但也可以使用i n l i n e关键字放在类外定义的函数前面使之成为内联函数。
在头文件里,内联函数默认为内部连接——即它是static, 并且只能在它被包含的编译单元看到(也就是说只有在包含该函数的文件中可以访问)
内联的目的是减少函数调用的开销。假如函数较大,那么花费在函数体内的时间相对于进出函数的时间的比例就会较大,所以收获会较小。而且内联一个大函数将会使该函数所有被调用的地方都做代码复制,结果代码膨胀而在速度方面获得的好处却很少或者没有。
在类中内联函数的最重要的用处之一是用于一种叫存取函数的函数。不用内联函数,考虑效率的类设计者将忍不住简单地使i为公共( p u b l i c )成员, 从而通过让用户直接存取i 而节约开销。从设计的角度看,这是很不好的。因为i将成为公共界面的一部分,所以意味着类设计者决不能修改它。我们将和称为i的一个i n t类型变量打交道。这是一个问题,因为我们可能在稍后觉得用一个f l o a t变量比用一个int 变量代表状态信息更有用一些,但因为int i是公共接口的一部分,所以我们不能改变它。另一方面,假如我们总是使用成员函数读和修改一个对象的状态信息,那么就可以满意地修改对象内部一些描述(应该永远打消在编码和测试之前能使我们的设计完善的念头)。
(以上所述很清晰的给解析在程序中使用get()和set()来读取变量的问题,值得推荐使用该方法)
C语言中预处理部分在C++中被替代(常量的定义:用const 代替C中的define;在c中的宏是函数的时候,C++中用 inline函数来取代。但在实际编程中,还是会用到预处理)
。s t a t i c最基本的含义是指“位置不变的某个东西”(像“静电”),这里则指内存中的物理位置或文件中的可见性。
C和C + +都允许在函数内部创
建一个s t a t i c对象,这个对象将存储在程序的静态数据区中,而不是在堆栈中。这个对象只在函数第一次调用时初始化一次,以后它将在两次函数之间保持它的值。
n a m e s p a c e与c l a s s、s t r u c t、u n i o n和e n u m有着明显的区别:
1) namespace只能在全局范畴定义,但它们之间可以互相嵌套。
2) 在n a m e s p a c e定义的结尾,右大括号的后面不必要跟一个分号。
3) 一个n a m e s p a c e可以在多个头文件中用一个标识符来定义,就好象重复定义一个类一样。
4) 一个n a m e s p a c e的名字可以用另一个名字来作它的别名,这样我们就不必敲打那些开发商提供的冗长的名字了。
5) 我们不能像类那样去创建一个名字空间的实例。
C + +对此提供了一种机制,称为纯虚函数。下面是它的声明语法:
virtual void x() = 0;
构造函数不能是虚的(在附录B中的技术只类似于虚构造函数)。但析构函数能够且常常必须是虚的。