1、1 const 修饰函数参数返回值 1用 const 修饰函数的参数如果参数要作输出用,不论它是什么数据类型,也不论它采用“指针传递” 还是“引用传递”,都不能加 const 修饰,否则该参数将失去输出功能。const 只能修饰输入参数: (1)如果输入参数采用“指针传递” ,那么加 const 修饰可以防止意外地改动该指针,起到保护作用。例如 StringCopy 函数:void StringCopy(char *strDestination, const char *strSource);其中 strSource 是输入参数, strDestination 是输出参数。给 strSourc
2、e 加上 const 修饰后,如果函数体内的语句试图改动 strSource,编译器将指出错误。(2)如果输入参数采用“值传递” ,由于函数将自动产生临时变量用于复制该参数,该输入参数本来就无需保护,所以不要加 const 修饰。例如不 要将函数 void Func1(int x) 写成 void Func1(const int x)。 同理不要将函数 void Func2(A a) 写成void Func2(const A a)。其中 A 为用户自定义的数据类型。但是对于非内部数据类型的参数而言,象 void Func(A a) 这样声明的函数注定效率比较底。因为函数体内将产生 A 类型的临
3、时对象用于复制参数 a,而临时对象的构造、复制、析构过程都将消耗时间。为了提高效率,可以 将函数声明改为 void Func(A 如下语句将出现编译错误:char *str = GetString();正确的用法是const char *str = GetString();(2)如果函数返回值采用“值传递方式” ,由于函数会把返回值复制到外部临时的存储单元中,加 const 修饰没有任何价值。例如不要把函数 int GetInt(void) 写成 const int GetInt(void)。同理不要把函数 A GetA(void) 写成 const A GetA(void),其中 A 为用户
4、自定义的数据类型。如果返回值不是内部数据类型,将函数 A GetA(void) 改写为 const A / 赋值函数;A a, b, c; / a, b, c 为 A 的对象a = b = c; / 链式赋值(a = b).Func(); / a 被赋值后马上调用成员函数 Func如果将赋值函数的返回值加 const 修饰,那么该返回值的内容不允许被改动。上例中,语句 a = b = c 仍然正确,但是语句 (a = b).Func() 则是非法的,除非 Func 是const 类型的成员函数(见 10.5 节)。3.类中的 const 1 const 数据成员有时我们希望某些常量只在类中才有
5、效。由于#define 定义的宏常量是全局的,不能达到目的,于是想当然地觉得应该用 const 修饰数据成员来实现。 const 数据成员的确是存在的,但其含义却不是我们所期望的。const 数据成员只在某个对象生存期内是常量,而对于整个类而言却是可变的,因为类可以 创建多个对象,不同的对象其 const 数据成员的值可以不同。不能在类声明中初始化 const 数据成员。以下用法是错误的,因为类的对象未被创建时,编译器不知道 SIZE 的值是什么。class Aconst int SIZE = 100; / 错误,企图在类声明中初始化 const 数据成员int arraySIZE; / 错误
6、,未知的 SIZE;const 数据成员的初始化只能在类构造函数的初始化表中进行,例如class AA(int size); / 构造函数const int SIZE ; ;A:A(int size) : SIZE(size) / 构造函数的初始化表A a(100); / 对象 a 的 SIZE 值为 100A b(200); / 对象 b 的 SIZE 值为 200怎样才能建立在整个类中都恒定的常量呢?别指望 const 数据成员了,应该用类中的枚举常量来实现。例如class Aenum SIZE1 = 100, SIZE2 = 200; / 枚举常量int array1SIZE1; int
7、 array2SIZE2;枚举常量不会占用对象的存储空间,它们在编译时被全部求值。枚举常量的缺点是:它的隐含数据类型是整数,最大值有限,不能表示浮点数(如 PI=3.14159)。任何不会修改数据成员的函数都应该声明为 const 类型。如果在编写 const 成员函数时,不慎修改了数据成员,或者调用了其它非 const 成员函数,编译器将指出错误,这无疑会提高程序的健壮性。以下程序中,类 stack 的成员函数 GetCount 仅用于计数,从逻辑上讲 GetCount 应当为 const 函数。编译器将指出 GetCount 函数中的错误。class Stackpublic:void Pu
8、sh(int elem);void Pop(void);int GetCount(void) const; / const 成员函数private:int m_num;int m_data100;int Stack:GetCount(void) const+ m_num; / 编译错误,企图修改数据成员 m_numPop(); / 编译错误,企图调用非 const 函数return m_num;const 成员函数的声明看起来怪怪的:const 关键字只能放在函数声明的尾部,因为其它地方都已经被占用了。4. C/C+中内存分配(1)从堆中分配内存,在 C 中用 malloc 和 free,在
9、C+中用 new 和 delete. (2) Malloc 与 new 的区别在于 new 调用了构造函数。Delete 与 free 的区别在于delete 调用了析构函数。(3)分配内存的对象是指针,malloc/free,new/delete 搭配使用。例如:int len=10;char *p_Char;p_Char=(char *)malloc(sizeof(char)*(len+1);/C 中操作free(p_Char);/删除指针所指向的内容,即内存,p_Char 指向 NULL,以后不能在访问它char* p_Char=new charlen;/C+中的操作delete p_c
10、har;/每当你在 “new“ 运算式中用了 “.“ 的话,你就必须在 “delete“ 陈述中使用 “.这语法是必要的2 PRAGMASGNU C+支持两条#pragma 指令使同一个头文件有两个用途 :对象类的接口定义, 对象类完整的内容定义.#pragma interface(仅对 C+)在定义对象类的头文件中,使用这个指令可以节省大部分采用该类的目标文件的大小.一般说来,某些信息 (内嵌成员函数的备份副件,调试信息,实现虚函数的内部表格等)的本地副件必须保存在包含类定义的各个目标文件中.使用这个 pragma 指令能够避免这样的复制.当编译中引用包含#pragma interface指
11、令的头文件时,就 不会产生这些辅助信息(除非输入的主文件使用了#pragma implementation指令).作为替代 ,目标文件 将包含可被连接时解析的引用 (reference).#pragma implementation#pragma implementation “objects.h“(仅对 C+)如果要求从头文件产生完整的输出( 并且全局可见 ),你应该在主输入文件中使用这条 pragma.头文件 中应该依次使用#pragma interface指令.在 implementation 文件中将产生全部内嵌成员函数 的备份,调试信息,实现虚函数的内部表格等如果#pragma im
12、plementation不带参数,它指的是和源文件有相同基本名的包含文件;例如, allclass.cc中, #pragma implementation等于#pragma implementation allclass.h.如果某个 implementation 文件需要从多个头文件引入代码,就应该 使用这个字符串参数.不可能把一个头文件里面的内容分割到多个 implementation 文件中.文件(FILE)file.c C 源文件file.h C 头文件( 预处理文件 )file.i 预处理后的 C 源文件file.C C+源文件file.cc C+源文件file.cxx C+源文件f
13、ile.m Objective-C 源文件file.s 汇编语言文件file.o 目标文件a.out 连接的输出文件TMPDIR/cc* 临时文件LIBDIR/cpp 预处理器LIBDIR/cc1 C 编译器LIBDIR/cc1plus C+编译器LIBDIR/collect3 类的公有继承和私有继承有什么不同Effective C+:条款 42: 明智地使用私有继承条款 35 说明,C+将公有继承视为 “是一个“ 的关系。它是通过这个例子来证实的:假如某个类层次结构中,Student 类从 Person 类公有继承,为了使某个函数成功调用,编译器可以在必要时隐式 地将 Student 转换为
14、 Person。这个例子很值得再看一遍,只是现在,公有继承换成了私有继承:class Person . ;class Student: / 这一次我们private Person . ; / 使用私有继承void dance(const Person / 每个人会跳舞void study(const Student / 只有学生才学习Person p; / p 是一个人Student s; / s 是一个学生dance(p); / 正确, p 是一个人dance(s); / 错误!一个学生不是一个人很显然,私有继承的含义不是 “是一个“ ,那它的含义是什么呢?“别忙 !“ 你说。“在弄清含义之
15、前,让我们先看看行为。私有继承有那些行为特征呢?“ 那好吧。关于私有继承的第一个规则正如你现在所看到的:和公有继承相反,如果两个类之间的继承关系为私有,编译器一般不会将派生类对象(如 Student)转换成基类对象(如 Person)。这就是上面的代码中为对象 s 调用 dance 会失败的原因。第二个规则是,从私有基类继承而来的成员都 成为了派生类的私有成员,即使它们在基类中是保护或公有成员。行为特征就这些。这为我们引出了私有继承的含义:私有继承意味着 “用.来实现“ 。如果使类 D 私有继承于类 B,这样做是因为你想利用类 B 中已经存在的某些代码,而不是因为类型 B 的对象和类型 D 的
16、对象之间有什么概念上 的关系。因而,私有继承纯粹是一种实现技术。用条款 36 引入的术语来说,私有继承意味着只是继承实现,接口会被忽略。如果 D 私有继承于 B,就是说 D 对象在 实现中用到了 B 对象,仅此而已。私有继承在软件 “设计 “ 过程中毫无意义,只是在软件 “实现“ 时才有用。私有继承意味着 “用.来实现 “ 这一事实会给程序员带来一点混淆,因为条款 40 指出,“ 分层 “ 也具有相同的含义。怎么在二者之间进行选择呢?答案很简单:尽可能地使用分层,必须时才使用私有继承。什么时候必须呢?这往往是指有保护成员和/或虚函数 介入的时候 - 但这个问题过一会儿再深入讨论。条款 41 提
17、供了一种方法来写一个 Stack 模板,此模板生成的类保存不同类型的对象。你应该熟悉一下那个条款。模板是 C+最有用的组成部分之一,但一旦开始经常性地使用它,你会发现,如果实例化 一个模板一百次,你就可能实例化了那个模板的代码一百次。例如 Stack 模板,构成 Stack成员函数的代码和构成 Stack成员函数的代码是完全分开的。有时这是不可避免的,但即使模板函数实际上可以共享代码,这种代码重复还是可能存 在。这种目标代码体积的增加有一个名字:模板导致的 “代码膨胀“ 。这不是件好事。对于某些类,可以采用通用指针来避免它。采用这种方法的类存储的是指针,而不是对象,实现起来就是:? 创建一个类
18、,它存储的是对象的 void*指针。? 创建另外一组类,其唯一目的是用来保证类型安全。这些类都借助第一步中的通用类来完成实际工作。下面的例子使用了条款 41 中的非模板 Stack 类,不同的是这里存储的是通用指针,而不是对象:class GenericStack public:GenericStack();GenericStack();void push(void *object);void * pop();bool empty() const;private:struct StackNode void *data; / 节点数据StackNode *next; / 下一节点StackNod
19、e(void *newData, StackNode *nextNode): data(newData), next(nextNode) ;StackNode *top; / 栈顶GenericStack(const GenericStack / 防止拷贝和GenericStack / 条款 27);因为这个类存储的是指针而不是对象,就有可能出现一个对象被多个堆栈指向的情况(即,被压入到多个堆栈)。所以极其重要的一点是,pop 和类的析构函数销 毁任何 StackNode 对象时,都不能删除 data 指针 - 虽然还是得要删除 StackNode 对象本身。毕竟,StackNode 对象是在
20、 GenericStack 类内部分配的,所以还是得在类的内部释放。所以,条款 41 中 Stack 类的实现几乎完全满足 the GenericStack 的要求。仅有的改变只是用 void*来替换 T。仅仅有 GenericStack 这一个类是没有什么用处的,但很多人会很容易误用它。例如,对于一个用来保存 int 的堆栈,一个用户会错误地将一个指向 Cat 对象的指针压入到这个堆栈中,但编译却会通过,因为对 void*参数来说,指针就是指针。为了重新获得你所习惯的类型安全,就要为 GenericStack 创建接口类(interface class),象这样:class IntStack
21、 / int 接口类public:void push(int *intPtr) s.push(intPtr); int * pop() return static_cast(s.pop(); bool empty() const return s.empty(); private:GenericStack s; / 实现;class CatStack / cat 接口类public:void push(Cat *catPtr) s.push(catPtr); Cat * pop() return static_cast(s.pop(); bool empty() const return s.
22、empty(); private:GenericStack s; / 实现;正如所看到的,IntStack 和 CatStack 只是适用于特定类型。只有 int 指针可以被压入或弹出 IntStack,只有 Cat 指针可以被压入或弹 出 CatStack。IntStack 和CatStack 都通过 GenericStack 类来实现,这种关系是通过分层(参见条款 40)来体现的, IntStack 和 CatStack 将共享 GenericStack 中真正实现它们行为的函数代码。另外,IntStack 和 CatStack 所有成员函 数是(隐式)内联函数,这意味着使用这些接口类所带
23、来的开销几乎是零。但如果有些用户没认识到这一点怎么办?如果他们错误地认为使用 GenericStack 更高效,或者,如果他们鲁莽而轻率地认为类型安全不重要,那该怎么 办?怎么才能阻止他们绕过 IntStack 和 CatStack 而直接使用 GenericStack(这会让他们很容易地犯类型错误,而这正是设计 C+所 要特别避免的)呢?没办法!没办法防止。但,也许应该有什么办法。在本条款的开始我就提到,要表示类之间 “用.来实现“ 的关系,有一个选择是通过私有继承。现在这种情况下,这一技术就比分层更有优势,因为通过它可以让你告诉别人:GenericStack 使用起来不安全, 它只能用来实
24、现其它的类。具体做法是将 GenericStack 的成员函数声明为保护类型:class GenericStack protected:GenericStack();GenericStack();void push(void *object);void * pop();bool empty() const;private:. / 同上;GenericStack s; / 错误! 构造函数被保护class IntStack: private GenericStack public:void push(int *intPtr) GenericStack:push(intPtr); int * po
25、p() return static_cast(GenericStack:pop(); bool empty() const return GenericStack:empty(); ;class CatStack: private GenericStack public:void push(Cat *catPtr) GenericStack:push(catPtr); Cat * pop() return static_cast(GenericStack:pop(); bool empty() const return GenericStack:empty(); ;IntStack is; /
26、 正确CatStack cs; / 也正确和分层的方法一样,基于私有继承的实现避免了代码重复,因为这个类型安全的接口类只包含有对 GenericStack 函数的内联调用。在 GenericStack 类之上构筑类型安全的接口是个很花俏的技巧,但需要手工去写所有那些接口类是件很烦的事。幸运的是,你不必这样。你可以让模板来自动生成它们。下面是一个模板,它通过私有继承来生成类型安全的堆栈接口:templateclass Stack: private GenericStack public:void push(T *objectPtr) GenericStack:push(objectPtr); T
27、 * pop() return static_cast(GenericStack:pop(); bool empty() const return GenericStack:empty(); ;这是一段令人惊叹的代码,虽然你可能一时还没意识到。因为这是一个模板,编译器将根据你的需要自动生成所有的接口类。因为这些类是类型安全的,用户类型错 误在编译期间就能发现。因为 GenericStack 的成员函数是保护类型,并且接口类把GenericStack 作为私有基类来使用,用户将不可能绕过 接口类。因为每个接口类成员函数被(隐式)声明为 inline,使用这些类型安全的类时不会带来运行开销;生成的
28、代码就象用户直接使用 GenericStack 来编写的一样(假设编译器满足了 inline请求 - 参见条款 33)。因为 GenericStack 使用了 void*指针,操作堆栈的代码就只需要一份,而不管程序中使用了多少不同类型的堆栈。简而言之,这 个设计使代码达到了最高的效率和最高的类型安全。很难做得比这更好。本书的基本认识之一是,C+的各种特性是以非凡的方式相互作用的。这个例子,我希望你能同意,确实是非凡的。从这个例子中可以发现,如果使用分层,就达不到这样的效果。只有继承才能访问保护成员,只有继承才使得虚函数可以重新被定义。(虚函数的存在会引发私有继 承的使用,例子参见条款 43)因
29、为存在虚函数和保护成员,有时私有继承是表达类之间 “用. 来实现“ 关系的唯一有效途径。所以,当私有继承是你可以使用的最合适的实现方法时,就要大胆地使用它。同时,广泛意义上来说,分层是应该优先采用的技术,所以只要 有可能,就要尽量使用它。其它观点:共有继承是接口继承,成员函数的实现代码并没有继承私有继承是实现继承,成员函数的实现代码进入继承类的私有部分公有继承,子类可以访问基类的公有成员和保护成员,子类的实例也可以访问基类的公有成员。私有继承,子类只能访问基类的公有成员,面子类的实例对基类什么也访问不了. class parentvoid f();class child1 : public p
30、arent;class child2 : private parent;parent *p;child1 c1;child2 c2;p = /okp = /wrongc1.f();/okc2.f();/wrong公有继承可以隐式转换到父类类型,私有继承不可以。公有继承可以将父类的接口函数作为自己的接口函数,私有继承不可以。privateprotected 继承的优势和理由(借鉴:Herb Sutter 的Exceptional C+、comp.lang.C+.moderated)1。需要该改写虚函数;(经典理由, 如果是抽象类,则必须继承并改写)2。需要访问其保护成员;(包括数据、函数,或保护构造函数)3。需要在另一个基类子对象之前构造使用类(或析构之后还要使用);4。需要共享公用的虚拟基类,或改写虚拟基类的构造;5。得到空基类优化的好处;(编译器不一定实现这种优化)6。受限的“多态 ”(受限的 IS-A 关系);(public 继承表现强的 IS-A 关系privateprotected 继承表现 HAS-A 关系,或“照此实现”)对比于 C 语言的函数,C+增加了重载(overloaded)、内联(inline)、const 和virtual 四种新机制。其中重载和内联机制既可用于全局函数也可用于类的成员函数,const 与 virtual 机制仅用于类的成员 函数。